├── .gitignore ├── files ├── etc │ ├── unbound │ │ ├── root-anchors │ │ │ ├── root-anchors.p7s │ │ │ └── root-anchors.xml │ │ ├── icannbundle.pem │ │ ├── unbound_srv.conf │ │ └── root.hints │ ├── uci-defaults │ │ ├── z70-fix-unbound-scripts-execution │ │ ├── z70-fix-default-shell │ │ ├── z99-done │ │ ├── 00-fix-collectd-spam │ │ ├── z70-fix-btop │ │ ├── z70-fix-cron │ │ ├── z80-tweak-nss-ecm │ │ ├── 00-patch-avahi-early-startup │ │ ├── z70-fix-unbound-control-app │ │ ├── z4-umdns │ │ ├── z5-aria2 │ │ ├── z60-enable-services │ │ ├── z70-fix-avahi │ │ ├── z4-tailscale │ │ ├── 00-patch-netdata-core-affinity │ │ ├── z5-plexmediaserver │ │ ├── z0-cpu-perf │ │ ├── z2-usteer │ │ ├── z5-ttyd │ │ ├── z60-disable-services │ │ ├── z70-fix-ksmbd │ │ ├── z1-sqm │ │ ├── z4-chrony │ │ ├── z1-fstab │ │ ├── z0-system │ │ ├── z4-watchcat │ │ ├── z70-fix-net-config │ │ ├── z5-ksmbd │ │ ├── z5-adblock │ │ ├── z5-minidlna │ │ ├── z2-luci_netports │ │ ├── z2-dhcp │ │ ├── z1-network │ │ ├── z2-unbound │ │ ├── z2-wireless │ │ ├── z3-firewall │ │ └── z2-luci_statistics │ ├── apk │ │ └── repositories.conf │ ├── opkg │ │ └── distfeeds.conf │ ├── bash.bashrc │ ├── rc.local │ ├── hotplug.d │ │ └── iface │ │ │ ├── 20-chrony-sync │ │ │ ├── 99-restart-collectd │ │ │ └── 99-smp-vpn-affinity │ ├── sysupgrade.conf │ ├── init.d │ │ ├── smp_affinity_vpn │ │ ├── first_boot_log │ │ ├── unbound_cert_fix │ │ └── smp_affinity │ ├── sysctl.d │ │ └── 99-zram-tuning.conf │ ├── netdata │ │ └── netdata.conf │ └── ksmbd │ │ └── ksmbd.conf.template.example └── usr │ ├── libexec │ └── ttyd-btop │ ├── bin │ ├── is-default-config │ ├── has-internet │ ├── speedtest-sqm │ ├── install-it-tools │ ├── configure-firewall │ ├── add-wan-rules-to-firewall │ ├── configure-interface │ ├── configure-guest │ ├── speedtest-netperf │ └── webapps │ └── lib │ └── unbound │ └── odhcp.sh ├── packages ├── luci-app-pairdrop │ ├── root │ │ ├── etc │ │ │ ├── config │ │ │ │ └── pairdrop │ │ │ └── init.d │ │ │ │ └── pairdrop │ │ ├── usr │ │ │ └── share │ │ │ │ ├── luci │ │ │ │ └── menu.d │ │ │ │ │ └── luci-app-pairdrop.json │ │ │ │ └── rpcd │ │ │ │ └── acl.d │ │ │ │ └── luci-app-pairdrop.json │ │ └── www │ │ │ └── luci-static │ │ │ └── resources │ │ │ └── view │ │ │ └── pairdrop.js │ └── Makefile ├── luci-app-zerotier │ ├── root │ │ ├── usr │ │ │ └── share │ │ │ │ ├── luci │ │ │ │ └── menu.d │ │ │ │ │ └── luci-app-zerotier.json │ │ │ │ └── rpcd │ │ │ │ └── acl.d │ │ │ │ └── luci-app-zerotier.json │ │ └── www │ │ │ └── luci-static │ │ │ └── resources │ │ │ └── view │ │ │ └── zerotier.js │ └── Makefile ├── luci-app-tailscale │ ├── root │ │ ├── usr │ │ │ └── share │ │ │ │ ├── luci │ │ │ │ └── menu.d │ │ │ │ │ └── luci-app-tailscale.json │ │ │ │ └── rpcd │ │ │ │ └── acl.d │ │ │ │ └── luci-app-tailscale.json │ │ └── www │ │ │ └── luci-static │ │ │ └── resources │ │ │ └── view │ │ │ └── tailscale.js │ └── Makefile └── luci-app-plexmediaserver │ ├── root │ ├── usr │ │ └── share │ │ │ ├── luci │ │ │ └── menu.d │ │ │ │ └── luci-app-plexmediaserver.json │ │ │ └── rpcd │ │ │ └── acl.d │ │ │ └── luci-app-plexmediaserver.json │ └── etc │ │ └── config │ │ └── plexmediaserver │ └── Makefile ├── .idea └── .gitignore ├── LICENSE ├── .github └── workflows │ └── scheduled-release.yml └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | /workdir 2 | /files 3 | *.log 4 | .idea -------------------------------------------------------------------------------- /files/etc/unbound/root-anchors/root-anchors.p7s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DarkGhostHunter/OpenWRT-NSS-Full/main/files/etc/unbound/root-anchors/root-anchors.p7s -------------------------------------------------------------------------------- /files/etc/uci-defaults/z70-fix-unbound-scripts-execution: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Ensure unbound scripts are executable 4 | chmod +x /usr/lib/unbound/* 5 | 6 | exit 0 7 | -------------------------------------------------------------------------------- /files/etc/uci-defaults/z70-fix-default-shell: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # change root shell to bash 4 | sed -i -E s/'^(root:.*:\/bin\/)ash'/'\1bash'/ /etc/passwd 5 | 6 | exit 0 7 | -------------------------------------------------------------------------------- /packages/luci-app-pairdrop/root/etc/config/pairdrop: -------------------------------------------------------------------------------- 1 | 2 | config pairdrop 'main' 3 | option enabled '0' 4 | option port '8082' 5 | option pairdrop_version '' 6 | option node_version '' -------------------------------------------------------------------------------- /files/etc/uci-defaults/z99-done: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | logger -p notice -t luci-defaults "First boot setup complete." 4 | 5 | # --- MARK AS FIRST BOOT DONE --- 6 | touch /etc/first_boot_done 7 | 8 | exit 0 -------------------------------------------------------------------------------- /files/etc/apk/repositories.conf: -------------------------------------------------------------------------------- 1 | # Custom firmware build (Kernel 6.12 + NSS). 2 | # External repositories are disabled to prevent kernel module mismatches. 3 | # To install new software, rebuild the firmware image. 4 | -------------------------------------------------------------------------------- /files/etc/opkg/distfeeds.conf: -------------------------------------------------------------------------------- 1 | # Custom firmware build (Kernel 6.12 + NSS). 2 | # External repositories are disabled to prevent kernel module mismatches. 3 | # To install new software, rebuild the firmware image. 4 | -------------------------------------------------------------------------------- /files/etc/uci-defaults/00-fix-collectd-spam: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Ensure the RRD directory exist immediately to avoid CollectD whining 4 | # until the end of the first boot process. 5 | mkdir -p /tmp/rrd 6 | 7 | exit 0 -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Editor-based HTTP Client requests 5 | /httpRequests/ 6 | # Datasource local storage ignored files 7 | /dataSources/ 8 | /dataSources.local.xml 9 | -------------------------------------------------------------------------------- /files/etc/uci-defaults/z70-fix-btop: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # fix btop "--utf-force" bad argument if it exist 4 | if [ -f /etc/profile.d/btop.sh ]; then 5 | sed -i 's/--utf-force/--force-utf/g' /etc/profile.d/btop.sh 6 | fi 7 | 8 | exit 0 -------------------------------------------------------------------------------- /files/etc/uci-defaults/z70-fix-cron: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Disable Cron to avoid Busybox parsing bugs. We will enable it by default next year. 4 | if [ -f /etc/init.d/cron ]; then 5 | /etc/init.d/cron stop 6 | /etc/init.d/cron disable 7 | fi 8 | 9 | exit 0 -------------------------------------------------------------------------------- /files/etc/uci-defaults/z80-tweak-nss-ecm: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # tweak ecm 4 | if [ -f /etc/init.d/qca-nss-ecm ]; then 5 | uci set ecm.global.acceleration_engine='nss' 6 | uci set ecm.general.disable_gro='1' 7 | 8 | uci commit ecm 9 | fi 10 | 11 | exit 0 12 | -------------------------------------------------------------------------------- /files/etc/bash.bashrc: -------------------------------------------------------------------------------- 1 | # System-wide .bashrc file 2 | 3 | # Continue if running interactively 4 | [[ $- == *i* ]] || return 0 5 | alias history0='history | sed -E s/'"'"'^[0-9 ]+'"'"'//' 6 | [[ -f /usr/bin/forkrun.bash ]] && source /usr/bin/forkrun.bash 7 | [ ! -s /etc/shinit ] || . /etc/shinit 8 | -------------------------------------------------------------------------------- /files/etc/rc.local: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Create the RRD directory for statistics to prevent collectd errors on boot 4 | mkdir -p /tmp/rrd 5 | 6 | # Note: Time sync is now handled by /etc/hotplug.d/iface/20-chrony-sync 7 | # This ensures boot completes immediately (Green Light) without waiting for internet. 8 | 9 | exit 0 -------------------------------------------------------------------------------- /packages/luci-app-pairdrop/root/usr/share/luci/menu.d/luci-app-pairdrop.json: -------------------------------------------------------------------------------- 1 | { 2 | "admin/services/pairdrop": { 3 | "title": "PairDrop", 4 | "order": 50, 5 | "action": { 6 | "type": "view", 7 | "path": "pairdrop" 8 | }, 9 | "depends": { 10 | "acl": [ "luci-app-pairdrop" ] 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /packages/luci-app-zerotier/root/usr/share/luci/menu.d/luci-app-zerotier.json: -------------------------------------------------------------------------------- 1 | { 2 | "admin/services/zerotier": { 3 | "title": "ZeroTier", 4 | "order": 90, 5 | "action": { 6 | "type": "view", 7 | "path": "zerotier" 8 | }, 9 | "depends": { 10 | "acl": [ "luci-app-zerotier" ] 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /packages/luci-app-tailscale/root/usr/share/luci/menu.d/luci-app-tailscale.json: -------------------------------------------------------------------------------- 1 | { 2 | "admin/services/tailscale": { 3 | "title": "Tailscale", 4 | "order": 90, 5 | "action": { 6 | "type": "view", 7 | "path": "tailscale" 8 | }, 9 | "depends": { 10 | "acl": [ "luci-app-tailscale" ] 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /files/etc/uci-defaults/00-patch-avahi-early-startup: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Avahi starts at 61. Since it starts too early, it does not pick up the 4 | # server hostname. We will force avahi to run last (99) 5 | /etc/init.d/avahi-daemon disable 6 | sed -i 's/^START=[0-9]\+/START=99/' /etc/init.d/avahi-daemon 7 | /etc/init.d/avahi-daemon enable 8 | 9 | exit 0 -------------------------------------------------------------------------------- /packages/luci-app-plexmediaserver/root/usr/share/luci/menu.d/luci-app-plexmediaserver.json: -------------------------------------------------------------------------------- 1 | { 2 | "admin/services/plexmediaserver": { 3 | "title": "Plex Media Server", 4 | "order": 90, 5 | "action": { 6 | "type": "view", 7 | "path": "plexmediaserver" 8 | }, 9 | "depends": { 10 | "acl": [ "luci-app-plexmediaserver" ] 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /files/etc/uci-defaults/z70-fix-unbound-control-app: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Generate stuff for unbound 4 | if [ -f /etc/init.d/unbound ]; then 5 | # Setup the unbound control app 6 | unbound-control-setup 7 | 8 | # Ensure the Unbound Control App is "Local Host, Encrypted". 9 | uci set unbound.ub_main.unbound_control='2' 10 | uci commit unbound 11 | fi 12 | 13 | exit 0 14 | -------------------------------------------------------------------------------- /packages/luci-app-pairdrop/Makefile: -------------------------------------------------------------------------------- 1 | include $(TOPDIR)/rules.mk 2 | 3 | PKG_NAME:=luci-app-pairdrop 4 | PKG_VERSION:=1.0 5 | PKG_RELEASE:=1 6 | 7 | PKG_LICENSE:=Apache-2.0 8 | PKG_MAINTAINER:=Italo ISrael Baeza Cabrera 9 | 10 | LUCI_TITLE:=LuCI support for Pairdrop 11 | LUCI_DEPENDS:=+luci-base 12 | LUCI_PKGARCH:=all 13 | 14 | include $(TOPDIR)/feeds/luci/luci.mk 15 | 16 | $(eval $(call BuildPackage,luci-app-pairdrop)) -------------------------------------------------------------------------------- /files/etc/uci-defaults/z4-umdns: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if /usr/bin/is-default-config umdns; then 4 | 5 | touch /etc/config/umdns 6 | 7 | uci -q batch <<'EOF' 8 | delete umdns.@umdns[0] 9 | add umdns umdns 10 | set umdns.@umdns[0]=umdns 11 | set umdns.@umdns[0].jail='1' 12 | add_list umdns.@umdns[0].network='lan' 13 | add_list umdns.@umdns[0].network='IoT' 14 | 15 | commit umdns 16 | EOF 17 | 18 | fi 19 | 20 | exit 0 21 | -------------------------------------------------------------------------------- /packages/luci-app-tailscale/Makefile: -------------------------------------------------------------------------------- 1 | include $(TOPDIR)/rules.mk 2 | 3 | PKG_NAME:=luci-app-tailscale 4 | PKG_VERSION:=2025.11 5 | PKG_RELEASE:=1 6 | 7 | PKG_LICENSE:=Apache-2.0 8 | PKG_MAINTAINER:=Italo ISrael Baeza Cabrera 9 | 10 | LUCI_TITLE:=LuCI support for Tailscale 11 | LUCI_DEPENDS:=+luci-base +tailscale +ethtool 12 | LUCI_PKGARCH:=all 13 | 14 | include $(TOPDIR)/feeds/luci/luci.mk 15 | 16 | $(eval $(call BuildPackage,luci-app-tailscale)) -------------------------------------------------------------------------------- /packages/luci-app-plexmediaserver/Makefile: -------------------------------------------------------------------------------- 1 | include $(TOPDIR)/rules.mk 2 | 3 | PKG_NAME:=luci-app-plexmediaserver 4 | PKG_VERSION:=2.0 5 | PKG_RELEASE:=1 6 | 7 | PKG_LICENSE:=Apache-2.0 8 | PKG_MAINTAINER:=Italo ISrael Baeza Cabrera 9 | 10 | LUCI_TITLE:=LuCI support for Plex Media Server 11 | LUCI_DEPENDS:=+luci-base 12 | LUCI_PKGARCH:=all 13 | 14 | include $(TOPDIR)/feeds/luci/luci.mk 15 | 16 | $(eval $(call BuildPackage,luci-app-plexmediaserver)) -------------------------------------------------------------------------------- /packages/luci-app-zerotier/root/usr/share/rpcd/acl.d/luci-app-zerotier.json: -------------------------------------------------------------------------------- 1 | { 2 | "luci-app-zerotier": { 3 | "description": "Grant access to ZeroTier configuration", 4 | "read": { 5 | "uci": [ "zerotier" ], 6 | "file": { 7 | "/etc/config/zerotier": [ "read" ] 8 | } 9 | }, 10 | "write": { 11 | "uci": [ "zerotier" ], 12 | "file": { 13 | "/etc/config/zerotier": [ "write" ] 14 | } 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /files/etc/uci-defaults/z5-aria2: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if /usr/bin/is-default-config aria2 && [ ! -f /etc/config/aria2 ]; then 4 | 5 | touch /etc/config/aria2 6 | 7 | uci -q batch <<'EOF' 8 | delete adblock.main 9 | set aria2.main.enabled='0' 10 | set aria2.main.user='SMBUSER' 11 | set aria2.main.group='SMBUSER' 12 | set aria2.main.dir='/mnt/sda1/downloads' 13 | set aria2.main.config_dir='/mnt/sda1/downloads/.aria2' 14 | 15 | commit aria2 16 | EOF 17 | 18 | fi 19 | 20 | exit 0 -------------------------------------------------------------------------------- /packages/luci-app-zerotier/Makefile: -------------------------------------------------------------------------------- 1 | include $(TOPDIR)/rules.mk 2 | 3 | PKG_NAME:=luci-app-zerotier 4 | PKG_VERSION:=1.0.0 5 | PKG_RELEASE:=1 6 | 7 | PKG_LICENSE:=Apache-2.0 8 | PKG_MAINTAINER:=Italo ISrael Baeza Cabrera 9 | 10 | LUCI_TITLE:=LuCI support for Zerotier 11 | LUCI_DEPENDS:=+luci-base +tailscale +ethtool 12 | LUCI_PKGARCH:=all 13 | 14 | include $(INCLUDE_DIR)/package.mk 15 | include $(TOPDIR)/feeds/luci/luci.mk 16 | 17 | # Call BuildPackage - OpenWrt buildroot signature 18 | $(eval $(call BuildPackage,luci-app-zerotier)) -------------------------------------------------------------------------------- /files/etc/uci-defaults/z60-enable-services: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # ------------------------------------------------------------------ 4 | # Enable selected services 5 | # ------------------------------------------------------------------ 6 | 7 | # All these services do not start by default, so we need to do that. 8 | SERVICES=" 9 | smp_affinity 10 | unbound_cert_fix 11 | " 12 | 13 | for service in $SERVICES; do 14 | # shellcheck disable=SC2086 15 | if [ -f /etc/init.d/$service ]; then 16 | /etc/init.d/$service enable 17 | fi 18 | done 19 | 20 | exit 0 -------------------------------------------------------------------------------- /files/etc/hotplug.d/iface/20-chrony-sync: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Only run if the interface is 'wan' and the action is 'ifup' 4 | [ "$ACTION" = "ifup" ] && [ "$INTERFACE" = "wan" ] || exit 0 5 | 6 | # Check if the tool exists and runs it 7 | if [ -x /usr/sbin/chrony-hotplug ] && /usr/bin/has-internet; then 8 | logger -t chrony-hotplug "Interface $INTERFACE is up and connected to the Internet. Triggering time sync..." 9 | /usr/sbin/chrony-hotplug 10 | else 11 | logger -t chrony-hotplug "Interface $INTERFACE is up but not connected to Internet. Aborting time sync..." 12 | fi -------------------------------------------------------------------------------- /files/etc/uci-defaults/z70-fix-avahi: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Add avahi group and user with the ID 105 4 | if [ -f /etc/init.d/avahi-daemon ]; then 5 | # Check if group 'avahi' exists, if not add it 6 | if ! grep -q "^avahi:" /etc/group; then 7 | echo "avahi:x:105:" >> /etc/group 8 | fi 9 | 10 | # Check if user 'avahi' exists, if not add it 11 | if ! grep -q "^avahi:" /etc/passwd; then 12 | echo "avahi:*:105:105:avahi:/var/run/avahi-daemon:/bin/false" >> /etc/passwd 13 | fi 14 | 15 | /etc/init.d/avahi-daemon restart 16 | fi 17 | 18 | exit 0 -------------------------------------------------------------------------------- /packages/luci-app-plexmediaserver/root/etc/config/plexmediaserver: -------------------------------------------------------------------------------- 1 | 2 | config plexmediaserver 'main' 3 | option enabled '0' 4 | option browser_root '/mnt/sda1/Media' 5 | option library_dir '/mnt/sda1/Media/.plex/Library' 6 | option application_support_dir '/mnt/sda1/Media/.plex/Library/Application Support' 7 | option compressed_archive_path '/mnt/sda1/Media/.plex/Library/Application/plexmediaserver.sqfs' 8 | option tmp_dir '/tmp/plexmediaserver' 9 | option version '' 10 | option bin_dir '' 11 | option claim_code '' 12 | option force_version '' 13 | option force_update_download_url '' -------------------------------------------------------------------------------- /files/etc/uci-defaults/z4-tailscale: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if /usr/bin/is-default-config tailscale; then 4 | 5 | touch /etc/config/tailscale 6 | 7 | uci -q batch <<'EOF' 8 | delete tailscale.settings 9 | add tailscale.settings 10 | set tailscale.settings.log_stderr='1' 11 | set tailscale.settings.log_stdout='0' 12 | set tailscale.settings.port='41641' 13 | set tailscale.settings.state_file='/etc/tailscale/tailscaled.state' 14 | set tailscale.settings.fw_mode='nftables' 15 | set tailscale.settings.enable='0' 16 | set tailscale.settings.udp_gro_enable='0' 17 | 18 | commit tailscale 19 | EOF 20 | 21 | fi 22 | 23 | exit 0 -------------------------------------------------------------------------------- /packages/luci-app-plexmediaserver/root/usr/share/rpcd/acl.d/luci-app-plexmediaserver.json: -------------------------------------------------------------------------------- 1 | { 2 | "luci-app-plexmediaserver": { 3 | "description": "Grant access to Plex Media Server control commands", 4 | "read": { 5 | "uci": [ 6 | "plexmediaserver" 7 | ] 8 | }, 9 | "write": { 10 | "uci": [ 11 | "plexmediaserver" 12 | ], 13 | "file": { 14 | "/etc/init.d/plexmediaserver": [ "exec" ], 15 | "/usr/bin/pgrep": [ "exec" ], 16 | "/bin/sh": [ "exec" ], 17 | "/sbin/ip": [ "exec" ], 18 | "/sbin/logread": [ "exec" ] 19 | } 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /files/etc/sysupgrade.conf: -------------------------------------------------------------------------------- 1 | ## This file contains files and directories that should 2 | ## be preserved during an upgrade. 3 | 4 | # /etc/example.conf 5 | # /etc/openvpn/ 6 | /etc/fw_env.config 7 | /etc/passwd- 8 | /etc/shadow- 9 | /etc/board.json 10 | /etc/dropbear 11 | /etc/uhttpd.crt 12 | /etc/uhttpd.key 13 | /etc/passwd 14 | /etc/sysctl.conf 15 | /etc/group 16 | /etc/unbound 17 | /etc/netdata 18 | /etc/ksmbd 19 | /etc/sysctl.d 20 | /etc/shadow 21 | /etc/urandom.seed 22 | /etc/rc.local 23 | /etc/config 24 | /etc/sysupgrade.conf 25 | /etc/first_boot_done 26 | /usr/share/tang/db/ 27 | /root/.monit.id 28 | /root/.monit.state 29 | /root/.lesshst 30 | /root/.bash_history 31 | -------------------------------------------------------------------------------- /packages/luci-app-pairdrop/root/usr/share/rpcd/acl.d/luci-app-pairdrop.json: -------------------------------------------------------------------------------- 1 | { 2 | "luci-app-pairdrop": { 3 | "description": "Grant access to PairDrop control commands", 4 | "read": { 5 | "uci": [ 6 | "pairdrop" 7 | ], 8 | "file": { 9 | "/mnt/sda1/.webapps/": [ "read" ] 10 | } 11 | }, 12 | "write": { 13 | "uci": [ 14 | "pairdrop" 15 | ], 16 | "file": { 17 | "/etc/init.d/pairdrop": [ "exec" ], 18 | "/bin/mount": [ "exec" ], 19 | "/sbin/mount": [ "exec" ], 20 | "/bin/sh": [ "exec" ], 21 | "/mnt/sda1/.webapps/": [ "write" ] 22 | } 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /files/etc/uci-defaults/00-patch-netdata-core-affinity: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # We will permanently pin netdata to wokr on Core 0 4 | # 5 | # This modifies the init script so that ANY time netdata starts 6 | # (manually or automatically), it stays on Core 0. 7 | if [ -f /etc/init.d/netdata ]; then 8 | # Check if we haven't already patched it 9 | if ! grep -q "taskset" /etc/init.d/netdata; then 10 | # Inject taskset -c 0 before the binary execution 11 | # Core 0 is your OS/Generic core (per smp_affinity), keeping Netdata off NSS/WiFi cores. 12 | sed -i "s|procd_set_param command /usr/sbin/netdata|procd_set_param command /usr/bin/taskset -c 0 /usr/sbin/netdata|g" /etc/init.d/netdata 13 | fi 14 | fi 15 | 16 | exit 0 -------------------------------------------------------------------------------- /files/etc/uci-defaults/z5-plexmediaserver: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if /usr/bin/is-default-config plexmediaserver; then 4 | 5 | touch /etc/config/plexmediaserver 6 | 7 | uci -q batch <<'EOF' 8 | delete plexmediaserver.main 9 | set plexmediaserver.main=plexmediaserver 10 | set plexmediaserver.main.enabled='0' 11 | set plexmediaserver.main.browser_root='/mnt/sda1/Media' 12 | set plexmediaserver.main.library_dir='/mnt/sda1/Media/.plex' 13 | set plexmediaserver.main.application_support_dir='/mnt/sda1/Media/.plex/Application Support' 14 | set plexmediaserver.main.compressed_archive_path='/mnt/sda1/Media/.plex/Application/plexmediaserver.sqfs' 15 | set plexmediaserver.main.tmp_dir='/tmp/plexmediaserver' 16 | 17 | commit plexmediaserver 18 | EOF 19 | 20 | fi 21 | 22 | exit 0 -------------------------------------------------------------------------------- /files/etc/init.d/smp_affinity_vpn: -------------------------------------------------------------------------------- 1 | #!/bin/sh /etc/rc.common 2 | 3 | START=99 4 | 5 | # Helper: Set IRQ Affinity 6 | set_irq_affinity() { 7 | local mask="$1" 8 | local pattern="$2" 9 | for irq in $(grep -i "$pattern" /proc/interrupts | awk '{print $1}' | tr -d ':'); do 10 | if [ -d "/proc/irq/$irq" ]; then 11 | echo "$mask" > "/proc/irq/$irq/smp_affinity" 12 | fi 13 | done 14 | } 15 | 16 | start() { 17 | # Only runs if the user explicitly enabled the service 18 | logger -p notice -t smp_affinity_vpn "Service started: Moving Crypto Hardware (ce) interrupts to CPU3..." 19 | set_irq_affinity 8 "ce[0-9]" 20 | } 21 | 22 | stop() { 23 | # We leave it as-is to avoid disrupting active connections 24 | true 25 | } 26 | -------------------------------------------------------------------------------- /files/etc/unbound/root-anchors/root-anchors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | . 4 | 5 | 19036 6 | 8 7 | 2 8 | 49AAC11D7B6F6446702E54A1607371607A1A41855200FD2CE1CDDE32F24E8FB5 9 | 10 | 11 | 20326 12 | 8 13 | 2 14 | E06D44B80B8F1D39A95C0B0D7C65D08458E880409BBC683457104237C7F8EC8D 15 | 16 | 17 | -------------------------------------------------------------------------------- /files/etc/uci-defaults/z0-cpu-perf: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if /usr/bin/is-default-config cpu-perf; then 4 | 5 | touch /etc/config/cpu-perf 6 | 7 | uci -q batch <<'EOF' 8 | delete cpu-perf.config 9 | set cpu-perf.ondemand=governor 10 | set cpu-perf.conservative=governor 11 | set cpu-perf.performance=governor 12 | set cpu-perf.schedutil=governor 13 | set cpu-perf.config=main 14 | set cpu-perf.config.enabled='1' 15 | set cpu-perf.cpu0=cpu 16 | set cpu-perf.cpu0.scaling_governor='performance' 17 | set cpu-perf.cpu1=cpu 18 | set cpu-perf.cpu1.scaling_governor='performance' 19 | set cpu-perf.cpu2=cpu 20 | set cpu-perf.cpu2.scaling_governor='performance' 21 | set cpu-perf.cpu3=cpu 22 | set cpu-perf.cpu3.scaling_governor='performance' 23 | 24 | commit cpu-perf 25 | EOF 26 | 27 | fi 28 | 29 | exit 0 30 | -------------------------------------------------------------------------------- /files/etc/uci-defaults/z2-usteer: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if /usr/bin/is-default-config usteer; then 4 | 5 | touch /etc/config/usteer 6 | 7 | uci -q batch <<'EOF' 8 | delete usteer.@usteer[0] 9 | add usteer usteer 10 | set usteer.@usteer[0]=usteer 11 | set usteer.@usteer[0].network='lan' 12 | set usteer.@usteer[0].syslog='1' 13 | set usteer.@usteer[0].local_mode='1' 14 | set usteer.@usteer[0].ipv6='0' 15 | set usteer.@usteer[0].debug_level='1' 16 | set usteer.@usteer[0].band_steering_threshold='20' 17 | set usteer.@usteer[0].band_steering_interval='10000' 18 | set usteer.@usteer[0].band_steering_min_snr='-75' 19 | set usteer.@usteer[0].signal_diff_threshold='20' 20 | set usteer.@usteer[0].initial_connect_delay='1000' 21 | set usteer.@usteer[0].ssid_list='OpenWrt_WiFi' 22 | 23 | commit usteer 24 | EOF 25 | 26 | fi 27 | 28 | exit 0 29 | -------------------------------------------------------------------------------- /files/usr/libexec/ttyd-btop: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | SOCKET="/tmp/ttyd_btop.sock" 3 | export XDG_CONFIG_HOME=/etc/btop-config 4 | 5 | # 1. Restore Environment Variables 6 | export LC_ALL=C.UTF-8 7 | export LANG=C.UTF-8 8 | export TERM=xterm-256color 9 | 10 | # 2. Force tmux settings (Shell + Title Fix) 11 | TMUX_CONF="/tmp/tmux_btop.conf" 12 | { 13 | # Force the shell to /bin/sh to prevent crashes 14 | echo "set-option -g default-shell /bin/sh" 15 | } > "$TMUX_CONF" 16 | 17 | # 3. If attach failed, create a new one 18 | if ! /usr/bin/tmux -S "$SOCKET" -f "$TMUX_CONF" -u -2 attach-session -t system_stats 2>/dev/null; then 19 | rm -f "$SOCKET" 20 | exec /usr/bin/tmux -S "$SOCKET" -f "$TMUX_CONF" -u -2 \ 21 | new-session -s system_stats '/usr/bin/btop --force-utf' \; \ 22 | set-option -g destroy-unattached on 23 | fi -------------------------------------------------------------------------------- /files/etc/uci-defaults/z5-ttyd: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if /usr/bin/is-default-config ttyd && [ ! -f /etc/config/ttyd ]; then 4 | 5 | touch /etc/config/ttyd 6 | 7 | uci -q batch <<'EOF' 8 | delete ttyd.@ttyd[0] 9 | 10 | add ttyd ttyd 11 | set ttyd.@ttyd[0].enable='0' 12 | set ttyd.@ttyd[0].command='/bin/login' 13 | set ttyd.@ttyd[0].port='7681' 14 | set ttyd.@ttyd[0].interface='@lan' 15 | set ttyd.@ttyd[0].debug='7' 16 | set ttyd.@ttyd[0].once='1' 17 | 18 | delete ttyd.@ttyd[1] 19 | add ttyd ttyd 20 | set ttyd.@ttyd[1].enable='1' 21 | set ttyd.@ttyd[1].command='/usr/libexec/ttyd-btop' 22 | set ttyd.@ttyd[1].port='7682' 23 | set ttyd.@ttyd[1].uid='65534' 24 | set ttyd.@ttyd[1].gid='65534' 25 | set ttyd.@ttyd[1].interface='@lan' 26 | set ttyd.@ttyd[1].debug='3' 27 | add_list ttyd.@ttyd[1].client_option='titleFixed=Statistics' 28 | 29 | commit ttyd 30 | EOF 31 | 32 | fi 33 | 34 | exit 0 -------------------------------------------------------------------------------- /packages/luci-app-tailscale/root/usr/share/rpcd/acl.d/luci-app-tailscale.json: -------------------------------------------------------------------------------- 1 | { 2 | "luci-app-tailscale": { 3 | "description": "Grant UCI access for luci-app-tailscale", 4 | "read": { 5 | "uci": [ "tailscale", "network", "ethtool" ], 6 | "cgi-io": [ "exec" ], 7 | "file": { 8 | "/etc/tailscale/tailscaled.state": [ "read" ], 9 | "/tmp/tailscale*": [ "read" ], 10 | "/usr/sbin/tailscale": [ "exec" ], 11 | "/usr/sbin/tailscaled": [ "exec" ], 12 | "/usr/sbin/ethtool": [ "exec" ], 13 | "/usr/bin/ethtool": [ "exec" ], 14 | "/etc/init.d/tailscale": [ "exec" ], 15 | "/bin/sh": [ "exec" ] 16 | }, 17 | "ubus": { 18 | "network.interface": [ "dump" ], 19 | "network.device": [ "dump" ], 20 | "system": [ "board", "info" ], 21 | "file": [ "exec" ] 22 | } 23 | }, 24 | "write": { 25 | "uci": [ "tailscale", "ethtool" ] 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /files/etc/sysctl.d/99-zram-tuning.conf: -------------------------------------------------------------------------------- 1 | # --- ZRAM Performance Tuning --- 2 | 3 | # Force the kernel to use ZRAM aggressively (keep apps in RAM, not cache). 4 | vm.swappiness=180 5 | 6 | # Disable read-ahead. ZRAM is instant; reading ahead just wastes CPU. 7 | vm.page-cluster=0 8 | 9 | # Prevent "panic" swapping that causes freezes. 10 | vm.watermark_boost_factor=0 11 | vm.watermark_scale_factor=125 12 | 13 | # Write dirty data to disk immediately (background) to prevent large freeze-ups. 14 | # Force to write data to the disk in tiny, continuous trickles rather than one giant wave. 15 | vm.dirty_background_ratio=1 16 | 17 | # Protect Directory Caching (Speed up Plex library browsing) 18 | # Default is 100. Lowering to 50 tells the kernel: 19 | # "Keep folder listings (dentries) in RAM longer; don't drop them just to free space." 20 | vm.vfs_cache_pressure=50 21 | 22 | # Increase Network Buffer Reserve. 23 | # Prevents network starvation when the CPU is busy transcoding or compressing ZRAM. 24 | vm.min_free_kbytes=65536 -------------------------------------------------------------------------------- /files/etc/uci-defaults/z60-disable-services: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # ------------------------------------------------------------------ 4 | # Disable and stop selected services 5 | # ------------------------------------------------------------------ 6 | 7 | # All these services require configuration, except for sysntpd (the 8 | # system native NTP Client) because we're using chrony instead. 9 | # Also, these makes the server not boot-loop if the conf is bad 10 | SERVICES=" 11 | plexmediaserver 12 | zerotier 13 | tailscale 14 | ddns 15 | banip 16 | sysntpd 17 | minidlna 18 | watchcat 19 | netdata 20 | smp_affinity_vpn 21 | " 22 | 23 | for service in $SERVICES; do 24 | # shellcheck disable=SC2086 25 | if [ -f /etc/init.d/$service ]; then 26 | /etc/init.d/$service disable 27 | fi 28 | done 29 | 30 | # Additional check: if the it-tools file does not exist, also disable it 31 | if [ ! -f /srv/it-tools.squashfs ] && [ -f /etc/init.d/it-tools ]; then 32 | /etc/init.d/it-tools disable 33 | else 34 | /etc/init.d/it-tools enable 35 | fi 36 | exit 0 -------------------------------------------------------------------------------- /files/etc/uci-defaults/z70-fix-ksmbd: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if [ -f /etc/init.d/ksmbd ]; then 4 | # Add SMBUSER for ksmbd, with the "SMBPASSWORD" as password. User may change it later. 5 | grep -q 'SMBUSER' >/etc/group 6 | grep -q 'SMBUSER' >/etc/shadow 7 | grep -q 'SMBUSER' >/etc/passwd 8 | 9 | # Makes adding the user idempotent 10 | ksmbd.adduser SMBUSER -p SMBPASSWORD 11 | 12 | # Check if the config template is the same as default. If it is, change it for our default. 13 | if ! [ -f /etc/ksmbd/ksmbd.conf.template ] \ 14 | || ! grep -qE '.+' /etc/ksmbd/ksmbd.conf.template \ 15 | || [ "$(cat /etc/ksmbd/ksmbd.conf.template)" = "$(cat /rom/etc/ksmbd/ksmbd.conf.template)" ]; then 16 | mv -f /etc/ksmbd/ksmbd.conf.template.example /etc/ksmbd/ksmbd.conf.template 17 | fi 18 | fi 19 | 20 | exit 0 -------------------------------------------------------------------------------- /files/etc/init.d/first_boot_log: -------------------------------------------------------------------------------- 1 | #!/bin/sh /etc/rc.common 2 | # Capture first boot logs until explicitly stopped 3 | 4 | START=01 5 | STOP=99 6 | 7 | LOGFILE="/etc/first-boot-$(date +%Y%m%d-%H%M%S).log" 8 | PIDFILE="/var/run/first_boot_log.pid" 9 | FLAGFILE="/etc/first_boot_done" 10 | 11 | start() { 12 | # Only run if first boot flag does not exist 13 | if [ -f "$FLAGFILE" ]; then 14 | logger -p notice -t first_boot_log "First boot already done, skipping first bot log." 15 | return 0 16 | fi 17 | 18 | # Start continuous logging in background 19 | logread -f | lzma > "$LOGFILE.lzma" & 20 | echo $! > "$PIDFILE" 21 | 22 | logger -p notice -t first_boot_log "Boot logging started, PID $(cat $PIDFILE)" 23 | } 24 | 25 | stop() { 26 | # Stop background logging process 27 | if [ -f "$PIDFILE" ]; then 28 | logger -p notice -t first_boot_log "Boot logging stopping..." 29 | kill "$(cat $PIDFILE)" 2>/dev/null 30 | rm -f "$PIDFILE" 31 | 32 | logger -p notice -t first_boot_log "Boot logging stopped." 33 | fi 34 | } 35 | -------------------------------------------------------------------------------- /files/etc/uci-defaults/z1-sqm: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | grep -q act_mirred > /etc/modules.d/70-sched-core 5 | 6 | if /usr/bin/is-default-config sqm; then 7 | 8 | touch /etc/config/sqm 9 | 10 | uci -q batch <<'EOF' 11 | delete sqm.eth1 12 | delete sqm.wan 13 | set sqm.wan=queue 14 | set sqm.wan.enabled='0' 15 | set sqm.wan.interface='wan' 16 | set sqm.wan.qdisc='fq_codel' 17 | set sqm.wan.script='nss-zk.qos' 18 | set sqm.wan.overhead='44' 19 | set sqm.wan.linklayer='ethernet' 20 | set sqm.wan.linklayer_advanced='1' 21 | set sqm.wan.tcMPU='84' 22 | set sqm.wan.ingress_ecn='ECN' 23 | set sqm.wan.egress_ecn='NOECN' 24 | set sqm.wan.squash_dscp='0' 25 | set sqm.wan.squash_ingress='0' 26 | set sqm.wan.tcMTU='2047' 27 | set sqm.wan.tcTSIZE='512' 28 | set sqm.wan.qdisc_advanced='1' 29 | set sqm.wan.qdisc_really_really_advanced='1' 30 | set sqm.wan.itarget='auto' 31 | set sqm.wan.etarget='auto' 32 | 33 | commit sqm 34 | EOF 35 | 36 | fi 37 | 38 | exit 0 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Italo 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /files/etc/uci-defaults/z4-chrony: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if /usr/bin/is-default-config chrony; then 4 | 5 | touch /etc/config/chrony 6 | 7 | uci -q batch <<'EOF' 8 | delete chrony.@pool[0] 9 | add chrony pool 10 | set chrony.@pool[0]=pool 11 | set chrony.@pool[0].hostname='stratum1.time.cifelli.xyz' 12 | set chrony.@pool[0].maxpoll='12' 13 | set chrony.@pool[0].iburst='1' 14 | set chrony.@pool[0].nts='1' 15 | 16 | delete chrony.@dhcp_ntp_server[0] 17 | add chrony dhcp_ntp_server 18 | set chrony.@dhcp_ntp_server[0]=dhcp_ntp_server 19 | set chrony.@dhcp_ntp_server[0].iburst='1' 20 | set chrony.@dhcp_ntp_server[0].disabled='0' 21 | 22 | delete chrony.@allow[0] 23 | add chrony allow 24 | set chrony.@allow[0]=allow 25 | set chrony.@allow[0].interface='lan' 26 | 27 | delete chrony.@makestep[0] 28 | add chrony makestep 29 | set chrony.@makestep[0]=makestep 30 | set chrony.@makestep[0].threshold='1.0' 31 | set chrony.@makestep[0].limit='3' 32 | 33 | delete chrony.@nts[0] 34 | add chrony nts 35 | set chrony.@nts[0]=nts 36 | set chrony.@nts[0].rtccheck='1' 37 | set chrony.@nts[0].systemcerts='1' 38 | 39 | commit chrony 40 | EOF 41 | 42 | fi 43 | 44 | exit 0 45 | -------------------------------------------------------------------------------- /files/etc/uci-defaults/z1-fstab: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # The fstab ROM Config does not exist, it's generated on-demand, but if the 4 | # config already exists, leave as it-is 5 | if /usr/bin/is-default-config fstab && [ ! -f /etc/config/fstab ]; then 6 | 7 | touch /etc/config/fstab 8 | 9 | uci -q batch <<'EOF' 10 | delete fstab.@global[0] 11 | add fstab global 12 | set fstab.@global[0]=global 13 | set fstab.@global[0].anon_swap='0' 14 | set fstab.@global[0].anon_mount='0' 15 | set fstab.@global[0].auto_swap='1' 16 | set fstab.@global[0].auto_mount='1' 17 | set fstab.@global[0].delay_root='5' 18 | set fstab.@global[0].check_fs='1' 19 | 20 | delete fstab.@mount[0] 21 | add fstab mount 22 | set fstab.@mount[0]=mount 23 | set fstab.@mount[0].enabled='0' 24 | set fstab.@mount[0].label='CHANGE ME FOR THE PROPER TARGET' 25 | set fstab.@mount[0].target='/mnt/sda1' 26 | 27 | delete fstab.@swap[0] 28 | add fstab swap 29 | set fstab.@swap[0]=swap 30 | set fstab.@swap[0].enabled='1' 31 | set fstab.@swap[0].device='/dev/zram0' 32 | 33 | delete fstab.@uvol[0] 34 | add fstab uvol 35 | set fstab.@uvol[0]=uvol 36 | set fstab.@uvol[0].initialized='1' 37 | 38 | commit fstab 39 | EOF 40 | 41 | fi 42 | 43 | exit 0 44 | -------------------------------------------------------------------------------- /files/etc/hotplug.d/iface/99-restart-collectd: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Only run when WAN interface comes up 4 | [ "$ACTION" = "ifup" ] && [ "$INTERFACE" = "wan" ] || exit 0 5 | 6 | ( 7 | logger -t hotplug-collectd "WAN up. Blocking execution until Chrony confirms sync..." 8 | 9 | # chronyc waitsync [max-tries] [max-correction] [max-skew] [interval] 10 | # 60 tries * 10 seconds = 10 minutes timeout 11 | # 0.1 = threshold (wait until clock is within 0.1s of true time) 12 | if /usr/bin/chronyc waitsync 60 0.1 0 10 >/dev/null 2>&1; then 13 | 14 | # Double check WAN is still up before restarting services 15 | if ubus call network.interface.wan status 2>/dev/null | grep -q '"up": true'; then 16 | logger -t hotplug-collectd "Chrony reports sync! Restarting statistics." 17 | 18 | [ -x /etc/init.d/luci_statistics ] && /etc/init.d/luci_statistics restart 19 | [ -x /etc/init.d/collectd ] && /etc/init.d/collectd restart 20 | else 21 | logger -t hotplug-collectd "Sync complete, but WAN is down. Aborting." 22 | fi 23 | 24 | else 25 | logger -t hotplug-collectd "Chrony sync timed out or failed." 26 | fi 27 | ) & -------------------------------------------------------------------------------- /files/etc/uci-defaults/z0-system: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # The system ROM Config does not exist, it's generated on-demand, but if the 4 | # config already exists, leave as it-is 5 | if /usr/bin/is-default-config system && [ ! -f /etc/config/system ]; then 6 | 7 | touch /etc/config/system 8 | 9 | uci -q batch <<'EOF' 10 | # System ROM does not exist, so we will just set the configuration 11 | uci -q batch <<'EOF' 12 | delete system.@system[0] 13 | add system system 14 | set system.@system[0]=system 15 | set system.@system[0].hostname='OpenWrt_WRX36' 16 | set system.@system[0].ttylogin='0' 17 | set system.@system[0].log_size='64' 18 | set system.@system[0].urandom_seed='0' 19 | set system.@system[0].log_proto='udp' 20 | set system.@system[0].conloglevel='8' 21 | set system.@system[0].cronloglevel='5' 22 | set system.@system[0].zram_comp_algo='lz4' 23 | set system.@system[0].description='OpenWrt on Dynalink DL-WRX36 (NSS)' 24 | set system.@system[0].notes='Custom NSS-enabled OpenWrt firmware for Dynalink DL-WRX36' 25 | 26 | delete system.ntp 27 | set system.ntp=timeserver 28 | set system.ntp.enabled='0' 29 | 30 | delete system.@rngd[0] 31 | add system rngd 32 | set system.@rngd[0]=rngd 33 | set system.@rngd[0].enabled='1' 34 | set system.@rngd[0].device='/dev/urandom' 35 | 36 | commit system 37 | EOF 38 | 39 | fi 40 | 41 | exit 0 42 | -------------------------------------------------------------------------------- /files/etc/unbound/icannbundle.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDdzCCAl+gAwIBAgIBATANBgkqhkiG9w0BAQsFADBdMQ4wDAYDVQQKEwVJQ0FO 3 | TjEmMCQGA1UECxMdSUNBTk4gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxFjAUBgNV 4 | BAMTDUlDQU5OIFJvb3QgQ0ExCzAJBgNVBAYTAlVTMB4XDTA5MTIyMzA0MTkxMloX 5 | DTI5MTIxODA0MTkxMlowXTEOMAwGA1UEChMFSUNBTk4xJjAkBgNVBAsTHUlDQU5O 6 | IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRYwFAYDVQQDEw1JQ0FOTiBSb290IENB 7 | MQswCQYDVQQGEwJVUzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKDb 8 | cLhPNNqc1NB+u+oVvOnJESofYS9qub0/PXagmgr37pNublVThIzyLPGCJ8gPms9S 9 | G1TaKNIsMI7d+5IgMy3WyPEOECGIcfqEIktdR1YWfJufXcMReZwU4v/AdKzdOdfg 10 | ONiwc6r70duEr1IiqPbVm5T05l1e6D+HkAvHGnf1LtOPGs4CHQdpIUcy2kauAEy2 11 | paKcOcHASvbTHK7TbbvHGPB+7faAztABLoneErruEcumetcNfPMIjXKdv1V1E3C7 12 | MSJKy+jAqqQJqjZoQGB0necZgUMiUv7JK1IPQRM2CXJllcyJrm9WFxY0c1KjBO29 13 | iIKK69fcglKcBuFShUECAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8B 14 | Af8EBAMCAf4wHQYDVR0OBBYEFLpS6UmDJIZSL8eZzfyNa2kITcBQMA0GCSqGSIb3 15 | DQEBCwUAA4IBAQAP8emCogqHny2UYFqywEuhLys7R9UKmYY4suzGO4nkbgfPFMfH 16 | 6M+Zj6owwxlwueZt1j/IaCayoKU3QsrYYoDRolpILh+FPwx7wseUEV8ZKpWsoDoD 17 | 2JFbLg2cfB8u/OlE4RYmcxxFSmXBg0yQ8/IoQt/bxOcEEhhiQ168H2yE5rxJMt9h 18 | 15nu5JBSewrCkYqYYmaxyOC3WrVGfHZxVI7MpIFcGdvSb2a1uyuua8l0BKgk3ujF 19 | 0/wsHNeP22qNyVO+XVBzrM8fk8BSUFuiT/6tZTYXRtEt5aKQZgXbKU5dUF3jT9qg 20 | j/Br5BZw3X/zd325TvnswzMC1+ljLzHnQGGk 21 | -----END CERTIFICATE----- 22 | -------------------------------------------------------------------------------- /files/etc/uci-defaults/z4-watchcat: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if /usr/bin/is-default-config watchcat; then 4 | 5 | touch /etc/config/watchcat 6 | 7 | # Watch Google DNS and Cloudflare DNS. If these fail, we will understand there is no 8 | # Internet and we will only restart the wan interface instead of the whole router. 9 | # If the Internet is gone for more than two hours, we will restart the router. 10 | uci -q batch <<'EOF' 11 | delete watchcat.@watchcat[0] 12 | add watchcat watchcat 13 | set watchcat.@watchcat[0]=watchcat 14 | set watchcat.@watchcat[0].mode='ping_reboot' 15 | set watchcat.@watchcat[0].period='2h' 16 | set watchcat.@watchcat[0].pinghosts='8.8.8.8' 17 | set watchcat.@watchcat[0].forcedelay='1m' 18 | set watchcat.@watchcat[0].addressfamily='any' 19 | set watchcat.@watchcat[0].pingperiod='60s' 20 | set watchcat.@watchcat[0].pingsize='standard' 21 | set watchcat.@watchcat[0].interface='wan' 22 | 23 | delete watchcat.@watchcat[1] 24 | add watchcat watchcat 25 | set watchcat.@watchcat[0]=watchcat 26 | set watchcat.@watchcat[0].mode='restart_iface' 27 | set watchcat.@watchcat[0].period='15m' 28 | set watchcat.@watchcat[0].pinghosts='1.1.1.1' 29 | set watchcat.@watchcat[0].addressfamily='ipv4' 30 | set watchcat.@watchcat[0].pingperiod='30s' 31 | set watchcat.@watchcat[0].pingsize='standard' 32 | set watchcat.@watchcat[0].interface='wan' 33 | 34 | commit watchcat 35 | EOF 36 | 37 | fi 38 | 39 | exit 0 40 | -------------------------------------------------------------------------------- /files/etc/uci-defaults/z70-fix-net-config: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Ensure nf_conntrack disables strict TCP window checks; required on NSS-enabled routers 4 | # because hardware-accelerated NAT/SSL offload can cause valid SSL/TLS packets to be dropped 5 | # if conntrack enforces window validation. Setting this to 1 improves SSL stability. 6 | grep -q 'net.netfilter.nf_conntrack_tcp_no_window_check' >/etc/sysctl.conf 8 | 9 | COMMENT="# Recommended net.core.rmem_max values: 10 | # - 512KB (524288) for small home or office setups with light traffic, 11 | # - 1MB (1048576) for medium workloads such as multiple users streaming or light VPN use 12 | # - 2MB (2097152) for heavier scenarios like gigabit WAN links or frequent SSL/TLS tunnels 13 | # - 4MB (4194304) for demanding environments with high‑throughput VPNs, large file transfers, 14 | # or many concurrent encrypted sessions. 15 | # 16 | # Adjust based on router memory and workload, especially if you're using Adblock, Plex or else." 17 | 18 | # Add the comment if not already present, and ensure default 512KB if option is missing 19 | grep -q 'Recommended net.core.rmem_max values' /etc/sysctl.conf || echo "$COMMENT" >>/etc/sysctl.conf 20 | 21 | # If the line does not exist, add it. 22 | grep -q 'net.core.rmem_max' < /etc/sysctl.conf || echo 'net.core.rmem_max=524288' >> /etc/sysctl.conf 23 | 24 | exit 0 -------------------------------------------------------------------------------- /files/etc/hotplug.d/iface/99-smp-vpn-affinity: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 1. Exit immediately if not an "ifup" action 4 | [ "$ACTION" = "ifup" ] || exit 0 5 | 6 | # 2. Exit immediately if the master service is NOT enabled 7 | # This respects the user's decision to keep optimization off. 8 | /etc/init.d/smp_affinity_vpn enabled || exit 0 9 | 10 | MASK=8 # CPU3 11 | 12 | # Helper to pin process by name (userspace or kernel thread) 13 | pin_process() { 14 | local pattern="$1" 15 | local name="$2" 16 | 17 | # Find PIDs (pgrep -f matches full command line) 18 | for pid in $(pgrep -f "$pattern"); do 19 | if [ -d "/proc/$pid" ]; then 20 | taskset -p $MASK $pid >/dev/null 2>&1 21 | logger -p notice -t sm_vpn "Pinned $name (PID $pid) to CPU3" 22 | fi 23 | done 24 | } 25 | 26 | # --- Logic for specific VPNs --- 27 | 28 | # A. WireGuard (Kernel Threads) 29 | # Look for threads matching 'wg-crypt-[interface_name]' 30 | if [ -n "$(pgrep -f "wg-crypt-$DEVICE")" ]; then 31 | pin_process "wg-crypt-$DEVICE" "WireGuard Thread" 32 | fi 33 | 34 | # B. Tailscale (Userspace) 35 | # Tailscale benefits from CPU3 to share cache with NSS incoming packets 36 | # Tailscale interfaces usually start with 'tailscale', like 'tailscale0'. 37 | case "$DEVICE" in 38 | tailscale*) 39 | pin_process "tailscaled" "Tailscale Daemon" 40 | ;; 41 | esac 42 | 43 | # C. ZeroTier (Userspace) 44 | # Like Tailscale, benefits from avoiding CPU0->CPU3 context switching 45 | # ZeroTier interfaces usually start with 'zt', like 'zt87654321' 46 | case "$DEVICE" in 47 | zt*) 48 | pin_process "zerotier-one" "ZeroTier Daemon" 49 | ;; 50 | esac 51 | -------------------------------------------------------------------------------- /files/etc/uci-defaults/z5-ksmbd: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Because ksmbd seems to be weird, we will check if the config is default by its description. 4 | if [ "$(uci get ksmbd.@globals[0].description)" = 'Ksmbd on OpenWrt' ]; then 5 | 6 | touch /etc/config/ksmbd 7 | 8 | uci -q batch <<'EOF' 9 | delete ksmbd.@globals[0] 10 | add ksmbd globals 11 | set ksmbd.@globals[0]=globals 12 | set ksmbd.@globals[0].workgroup='WORKGROUP' 13 | set ksmbd.@globals[0].interface='lan' 14 | set ksmbd.@globals[0].description='OpenWrt_WRX36' 15 | 16 | delete ksmbd.@share[0] 17 | add ksmbd share 18 | set ksmbd.@share[0]=share 19 | set ksmbd.@share[0].name='Downloads' 20 | set ksmbd.@share[0].path='/mnt/sda1/Downloads' 21 | set ksmbd.@share[0].read_only='no' 22 | set ksmbd.@share[0].users='SMBUSER' 23 | set ksmbd.@share[0].guest_ok='no' 24 | set ksmbd.@share[0].create_mask='0666' 25 | set ksmbd.@share[0].dir_mask='0777' 26 | 27 | delete ksmbd.@share[1] 28 | add ksmbd share 29 | set ksmbd.@share[1]=share 30 | set ksmbd.@share[1].name='Shared' 31 | set ksmbd.@share[1].path='/mnt/sda1/Shared' 32 | set ksmbd.@share[1].read_only='no' 33 | set ksmbd.@share[1].users='SMBUSER' 34 | set ksmbd.@share[1].guest_ok='yes' 35 | set ksmbd.@share[1].hide_dot_files='no' 36 | set ksmbd.@share[1].create_mask='0666' 37 | set ksmbd.@share[1].dir_mask='0777' 38 | 39 | delete ksmbd.@share[2] 40 | add ksmbd share 41 | set ksmbd.@share[2]=share 42 | set ksmbd.@share[2].name='Media' 43 | set ksmbd.@share[2].path='/mnt/sda1/Media' 44 | set ksmbd.@share[2].read_only='no' 45 | set ksmbd.@share[2].users='SMBUSER' 46 | set ksmbd.@share[2].guest_ok='yes' 47 | set ksmbd.@share[2].create_mask='0666' 48 | set ksmbd.@share[2].dir_mask='0777' 49 | 50 | commit ksmbd 51 | EOF 52 | 53 | fi 54 | 55 | exit 0 56 | -------------------------------------------------------------------------------- /files/usr/bin/is-default-config: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # If its the first time this router booted OpenWRT, the `first_boot_done` file does not exist. 4 | if [ ! -s /etc/first_boot_done ]; then 5 | exit 0 6 | fi 7 | 8 | PKG_NAME="$1" 9 | 10 | # Validation: Check if argument is provided. 11 | : "${PKG_NAME:?Usage: $0 }" 12 | 13 | CONFIG="/etc/config/$PKG_NAME" 14 | ROM_CONFIG="/rom/etc/config/$PKG_NAME" 15 | 16 | # 1. Immediate Check: If active config is missing or empty, 17 | # it effectively needs to be "restored/updated". 18 | if [ ! -s "$CONFIG" ]; then 19 | exit 0 20 | fi 21 | 22 | # 2. Check if ROM config exists to compare against. 23 | # If ROM version doesn't exist, the user created this file manually. 24 | # Therefore, it is NOT default (Exit 1). 25 | if [ ! -f "$ROM_CONFIG" ]; then 26 | exit 1 27 | fi 28 | 29 | # FUNCTION: Aggressive Normalization Pipeline 30 | # 1. sed 's/#.*//' -> Removes comments first (Critical: doing this later might merge comments into code) 31 | # 2. tr -d ' \t' -> Deletes ALL spaces and tabs everywhere (e.g. "option lan" becomes "optionlan") 32 | # 3. sed '/^$/d' -> Removes lines that are now completely empty 33 | # 4. sort -> Sorts lines alphabetically to ignore order changes 34 | # 5. md5sum -> Hashes the result 35 | calc_checksum() { 36 | sed 's/#.*//' "$1" | \ 37 | tr -d ' \t' | \ 38 | sed '/^$/d' | \ 39 | sort | \ 40 | md5sum | awk '{print $1}' 41 | } 42 | 43 | # Calculate hashes for both files 44 | SUM_ACTIVE=$(calc_checksum "$CONFIG") 45 | SUM_ROM=$(calc_checksum "$ROM_CONFIG") 46 | 47 | # Compare Hashes 48 | if [ "$SUM_ACTIVE" = "$SUM_ROM" ]; then 49 | exit 0 # Hashes match: File is semantically "Default" 50 | else 51 | exit 1 # Hashes differ: File is "Customized" 52 | fi -------------------------------------------------------------------------------- /files/etc/uci-defaults/z5-adblock: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if /usr/bin/is-default-config adblock; then 4 | 5 | touch /etc/config/adblock 6 | 7 | uci -q batch <<'EOF' 8 | delete adblock.global 9 | set adblock.global=adblock 10 | set adblock.global.adb_enabled='0' 11 | set adblock.global.adb_debug='0' 12 | set adblock.global.adb_forcedns='1' 13 | set adblock.global.adb_safesearch='0' 14 | set adblock.global.adb_dnsfilereset='0' 15 | set adblock.global.adb_mail='0' 16 | set adblock.global.adb_report='0' 17 | set adblock.global.adb_backup='1' 18 | set adblock.global.adb_dns='unbound' 19 | set adblock.global.adb_fetchutil='curl' 20 | set adblock.global.adb_fetchcommand='curl' 21 | set adblock.global.adb_dnsflush='1' 22 | set adblock.global.adb_triggerdelay='60' 23 | set adblock.global.adb_dnsforce='0' 24 | set adblock.global.adb_nicelimit='0' 25 | set adblock.global.adb_lookupdomain='gstatic.com' 26 | 27 | add_list adblock.global.adb_trigger='wan' 28 | add_list adblock.global.adb_zonelist='IoT' 29 | add_list adblock.global.adb_zonelist='lan' 30 | 31 | # List sources for new Adblock version 32 | add_list adblock.global.adb_feed='adguard' 33 | add_list adblock.global.adb_feed='adguard_tracking' 34 | add_list adblock.global.adb_feed='android_tracking' 35 | add_list adblock.global.adb_feed='anti_ad' 36 | add_list adblock.global.adb_feed='anudeep' 37 | add_list adblock.global.adb_feed='bitcoin' 38 | add_list adblock.global.adb_feed='disconnect' 39 | add_list adblock.global.adb_feed='firetv_tracking' 40 | add_list adblock.global.adb_feed='phishing_army' 41 | add_list adblock.global.adb_feed='smarttv_tracking' 42 | add_list adblock.global.adb_feed='spam404' 43 | add_list adblock.global.adb_feed='wally3k' 44 | add_list adblock.global.adb_feed='whocares' 45 | add_list adblock.global.adb_feed='winspy' 46 | add_list adblock.global.adb_feed='yoyo' 47 | 48 | commit adblock 49 | EOF 50 | 51 | fi 52 | 53 | exit 0 54 | -------------------------------------------------------------------------------- /files/etc/unbound/unbound_srv.conf: -------------------------------------------------------------------------------- 1 | ############################################################################## 2 | # User custom options added in the server: clause part of UCI 'unbound.conf' 3 | # 4 | # Add your own option statements here when they are not covered by UCI. This 5 | # file is placed _inside_ the server: clause with an include: statement. Do 6 | # not start other clauses here, because that would brake the server: clause. 7 | # Use 'unbound_ext.conf' to start new clauses at the end of 'unbound.conf'. 8 | ############################################################################## 9 | 10 | so-reuseport: yes 11 | outgoing-port-avoid: 32400 #; Plex 12 | outgoing-port-avoid: 6800 #; AniaNG 13 | outgoing-port-avoid: 1194 #; OpenVPN UDP port 14 | outgoing-port-avoid: 5060-5061 #; SIP Signaling UDP port 15 | outgoing-port-avoid: 9001 #; TOR Relay 16 | outgoing-port-avoid: 9030 #; TOR Relay 17 | msg-buffer-size: 65552 18 | outgoing-range: 8192 19 | num-queries-per-thread: 4096 20 | outgoing-num-tcp: 32 21 | incoming-num-tcp: 32 22 | rrset-cache-size: 32m 23 | msg-cache-size: 16m 24 | stream-wait-size: 16m 25 | key-cache-size: 8m 26 | neg-cache-size: 2m 27 | ratelimit-size: 4m 28 | ip-ratelimit-size: 4m 29 | http-query-buffer-size: 8m 30 | http-response-buffer-size: 8m 31 | infra-cache-numhosts: 16384 32 | do-tcp: yes 33 | do-udp: yes 34 | use-caps-for-id: yes 35 | prefetch: yes 36 | prefetch-key: yes 37 | rrset-roundrobin: yes 38 | val-clean-additional: yes 39 | target-fetch-policy: "-1 4 3 2 1 0" 40 | minimal-responses: yes 41 | aggressive-nsec: yes 42 | disable-dnssec-lame-check: no 43 | hide-trustanchor: yes 44 | hide-version: yes 45 | hide-identity: yes 46 | harden-short-bufsize: no 47 | harden-large-queries: no 48 | harden-glue: yes 49 | harden-below-nxdomain: yes 50 | tls-cert-bundle: /var/lib/unbound/ca-certificates.crt 51 | serve-expired: yes 52 | serve-expired-ttl: 86400 # one day, in seconds 53 | serve-expired-client-timeout: 1000 54 | -------------------------------------------------------------------------------- /files/etc/uci-defaults/z5-minidlna: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if /usr/bin/is-default-config minidlna; then 4 | 5 | touch /etc/config/minidlna 6 | 7 | SERIAL="$(cat /dev/urandom | tr -dc 'A-Z0-1' | head -c16)" 8 | UUID=$(cat /proc/sys/kernel/random/uuid) 9 | 10 | uci -q batch </dev/null 2>&1: Suppress all text output 61 | if ping -c "$COUNT" -W "$TIMEOUT" -q $PING_OPTS "$ip" >/dev/null 2>&1; then 62 | success_counter=$((success_counter + 1)) 63 | fi 64 | done 65 | 66 | # Check if we met the threshold 67 | if [ "$success_counter" -ge "$REQUIRED_UP" ]; then 68 | # Connection is considered UP 69 | exit 0 70 | else 71 | # Connection is considered DOWN 72 | exit 1 73 | fi -------------------------------------------------------------------------------- /files/etc/uci-defaults/z2-luci_netports: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if /usr/bin/is-default-config luci_netports; then 4 | 5 | touch /etc/config/luci_netports 6 | 7 | uci -q batch <<'EOF' 8 | delete luci_netports.global 9 | set luci_netports.global=global 10 | set luci_netports.global.default_additional_info='0' 11 | set luci_netports.global.default_h_mode='1' 12 | set luci_netports.global.hv_mode_switch_button='1' 13 | 14 | delete luci_netports.@port[0] 15 | add luci_netports port 16 | set luci_netports.@port[0]=port 17 | set luci_netports.@port[0].ifname='lo' 18 | set luci_netports.@port[0].disable='1' 19 | 20 | delete luci_netports.@port[1] 21 | add luci_netports port 22 | set luci_netports.@port[1]=port 23 | set luci_netports.@port[1].ifname='wan' 24 | set luci_netports.@port[1].name='WAN' 25 | set luci_netports.@port[1].type='auto' 26 | 27 | delete luci_netports.@port[2] 28 | add luci_netports port 29 | set luci_netports.@port[2]=port 30 | set luci_netports.@port[2].ifname='lan1' 31 | set luci_netports.@port[2].name='LAN 1 (lan)' 32 | set luci_netports.@port[2].type='auto' 33 | 34 | delete luci_netports.@port[3] 35 | add luci_netports port 36 | set luci_netports.@port[3]=port 37 | set luci_netports.@port[3].ifname='lan2' 38 | set luci_netports.@port[3].name='LAN 2 (lan)' 39 | set luci_netports.@port[3].type='auto' 40 | 41 | delete luci_netports.@port[4] 42 | add luci_netports port 43 | set luci_netports.@port[4]=port 44 | set luci_netports.@port[4].ifname='lan3' 45 | set luci_netports.@port[4].name='LAN 3 (lan)' 46 | set luci_netports.@port[4].type='auto' 47 | 48 | delete luci_netports.@port[5] 49 | add luci_netports port 50 | set luci_netports.@port[5]=port 51 | set luci_netports.@port[5].ifname='lan4' 52 | set luci_netports.@port[5].name='LAN 4 (IoT)' 53 | set luci_netports.@port[5].type='auto' 54 | 55 | delete luci_netports.@port[6] 56 | add luci_netports port 57 | set luci_netports.@port[6]=port 58 | set luci_netports.@port[6].ifname='phy0-ap0' 59 | set luci_netports.@port[6].name='WiFi (5 GHz)' 60 | set luci_netports.@port[6].type='auto' 61 | 62 | delete luci_netports.@port[7] 63 | add luci_netports port 64 | set luci_netports.@port[7]=port 65 | set luci_netports.@port[7].ifname='phy1-ap0' 66 | set luci_netports.@port[7].name='WiFi (2.4 GHz)' 67 | set luci_netports.@port[7].type='auto' 68 | 69 | commit luci_netports 70 | EOF 71 | 72 | fi 73 | 74 | exit 0 75 | -------------------------------------------------------------------------------- /files/etc/init.d/unbound_cert_fix: -------------------------------------------------------------------------------- 1 | #!/bin/sh /etc/rc.common 2 | 3 | # Start before Unbound 4 | START=18 5 | 6 | UNBOUND_PATH=/var/lib/unbound 7 | 8 | start() { 9 | logger -p notice -t unbound_cert_fix "Fixing unbound directory paths at ${UNBOUND_PATH}..." 10 | # 1. Ensure the unbound directory exists (it is often in tmpfs) 11 | if [ ! -d "$UNBOUND_PATH" ]; then 12 | mkdir -p $UNBOUND_PATH 13 | fi 14 | 15 | # Set permissions so the unbound user can access the unbound working directory. 16 | chown unbound:unbound $UNBOUND_PATH 17 | chmod 755 $UNBOUND_PATH 18 | 19 | # 2. Copy the certificate bundle to the chroot location expected by unbound_srv.conf if it does not exists 20 | if [ -f /etc/ssl/certs/ca-certificates.crt ] && [ ! -f $UNBOUND_PATH/ca-certificates.crt ]; then 21 | logger -p notice -t unbound_cert_fix "Copying CA Certificate bundle into unbound workdir path..." 22 | cp /etc/ssl/certs/ca-certificates.crt $UNBOUND_PATH/ca-certificates.crt 23 | else 24 | logger -p warn -t unbound_cert_fix "CA Certificate bundle does not exist or already exists on workdir..." 25 | fi 26 | 27 | # Ensure the file is readable by the unbound user 28 | if [ -f $UNBOUND_PATH/ca-certificates.crt ]; then 29 | logger -p notice -t unbound_cert_fix "Fixing CA Certificate bundle permissions..." 30 | chmod 644 $UNBOUND_PATH/ca-certificates.crt 31 | else 32 | logger -p warn -t unbound_cert_fix "CA Certificate bundle not found in unbound workdir path..." 33 | fi 34 | 35 | # 3. Only copy root.key if it exists (it might not if we skipped anchor) 36 | if [ -f /etc/unbound/root.key ] && [ ! -f $UNBOUND_PATH/root.key ]; then 37 | logger -p notice -t unbound_cert_fix "Copying [root.key] into unbound workdir path..." 38 | cp -p /etc/unbound/root.key $UNBOUND_PATH/root.key 39 | else 40 | logger -p warn -t unbound_cert_fix "Root key file does not exist or already exists on workdir..." 41 | fi 42 | 43 | # Ensure the file is readable by the unbound user 44 | if [ -f $UNBOUND_PATH/root.key ]; then 45 | logger -p notice -t unbound_cert_fix "Fixing Root key permissions..." 46 | chmod 644 $UNBOUND_PATH/root.key 47 | else 48 | logger -p warn -t unbound_cert_fix "Root key not found in unbound workdir path..." 49 | chmod 644 $UNBOUND_PATH/root.key 50 | fi 51 | } 52 | -------------------------------------------------------------------------------- /files/usr/bin/speedtest-sqm: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Configuration 4 | SQM_IFACE="wan" 5 | SAFETY_MARGIN_PERCENT=95 6 | LOG_FILE="/var/log/sqm_autotune.log" 7 | 8 | log() { 9 | echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE" 10 | } 11 | 12 | log "Starting SQM Auto-Tune..." 13 | 14 | # 1. Check for Internet Connectivity 15 | log "Checking internet connectivity..." 16 | if ! /usr/bin/has-internet; then 17 | log "ERROR: No internet connection detected. Aborting." 18 | exit 1 19 | fi 20 | 21 | # 2. Check for speedtest tool 22 | if ! command -v speedtest >/dev/null 2>&1; then 23 | log "ERROR: 'speedtest' command not found. Please install a speedtest package." 24 | exit 1 25 | fi 26 | 27 | # 3. Run Speedtest 28 | log "Running speedtest (this may take 30-60 seconds)..." 29 | SPEED_OUTPUT=$(speedtest 2>&1) 30 | 31 | # 4. Parse Output 32 | # Regex looks for lines starting with "Download:" or "Upload:" and captures the float value 33 | DL_RAW=$(echo "$SPEED_OUTPUT" | grep -i "Download" | awk '{print $2}') 34 | UL_RAW=$(echo "$SPEED_OUTPUT" | grep -i "Upload" | awk '{print $2}') 35 | 36 | # Check if we got numbers 37 | if [ -z "$DL_RAW" ] || [ -z "$UL_RAW" ]; then 38 | log "ERROR: Could not parse speedtest output." 39 | log "Raw output: $SPEED_OUTPUT" 40 | exit 1 41 | fi 42 | 43 | log "Measured Raw Speed: Down: ${DL_RAW} Mbit/s | Up: ${UL_RAW} Mbit/s" 44 | 45 | # 5. Calculate SQM Values (95% of measured speed to minimize bufferbloat) 46 | # We use awk for floating point calculation, then printf %.0f to round to nearest integer for UCI 47 | DL_SET=$(awk "BEGIN {printf \"%.0f\", $DL_RAW * 1000 * ($SAFETY_MARGIN_PERCENT / 100)}") 48 | UL_SET=$(awk "BEGIN {printf \"%.0f\", $UL_RAW * 1000 * ($SAFETY_MARGIN_PERCENT / 100)}") 49 | 50 | # Validate results are greater than 0 51 | if [ "$DL_SET" -le 0 ] || [ "$UL_SET" -le 0 ]; then 52 | log "ERROR: Calculated speeds are invalid (0 or less). Aborting." 53 | exit 1 54 | fi 55 | 56 | log "Setting SQM (ingress: $DL_SET kbit/s, egress: $UL_SET kbit/s)" 57 | 58 | # 6. Apply to UCI 59 | uci set sqm.${SQM_IFACE}.enabled='1' 60 | uci set sqm.${SQM_IFACE}.download="${DL_SET}" 61 | uci set sqm.${SQM_IFACE}.upload="${UL_SET}" 62 | uci commit sqm 63 | 64 | # 7. Restart SQM 65 | if /etc/init.d/sqm restart; then 66 | log "SQM successfully updated and restarted." 67 | else 68 | log "WARNING: UCI updated, but SQM service failed to restart." 69 | fi 70 | 71 | exit 0 72 | -------------------------------------------------------------------------------- /files/etc/uci-defaults/z2-dhcp: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if /usr/bin/is-default-config dhcp; then 4 | 5 | touch /etc/config/dhcp 6 | 7 | uci -q batch <<'EOF' 8 | delete dhcp.lan 9 | set dhcp.lan=dhcp 10 | set dhcp.lan.interface='lan' 11 | set dhcp.lan.start='100' 12 | set dhcp.lan.limit='150' 13 | set dhcp.lan.leasetime='4h' 14 | set dhcp.lan.dhcpv4='server' 15 | set dhcp.lan.dhcpv6='server' 16 | set dhcp.lan.ra='server' 17 | set dhcp.lan.ra_management='1' 18 | set dhcp.lan.ra_default='2' 19 | set dhcp.lan.netmask='255.255.255.0' 20 | set dhcp.lan.force='1' 21 | set dhcp.lan.router='10.0.0.1' 22 | set dhcp.lan.domain='lan' 23 | add_list dhcp.lan.ra_flags='managed-config' 24 | add_list dhcp.lan.ra_flags='other-config' 25 | add_list dhcp.lan.dhcp_option='1,255.255.255.0' 26 | add_list dhcp.lan.dhcp_option='3,10.0.0.1' 27 | add_list dhcp.lan.dhcp_option='4,10.0.0.1' 28 | add_list dhcp.lan.dhcp_option='6,10.0.0.1' 29 | add_list dhcp.lan.dhcp_option='12,OpenWrt_WRX36' 30 | add_list dhcp.lan.dhcp_option='15,lan' 31 | 32 | delete dhcp.wan 33 | set dhcp.wan=dhcp 34 | set dhcp.wan.interface='wan' 35 | set dhcp.wan.ignore='1' 36 | 37 | delete dhcp.odhcpd 38 | set dhcp.odhcpd=odhcpd 39 | set dhcp.odhcpd.maindhcp='1' 40 | set dhcp.odhcpd.leasefile='/var/lib/unbound/odhcpd/dhcp.leases' 41 | set dhcp.odhcpd.leasetrigger='/usr/lib/unbound/odhcpd.sh' 42 | set dhcp.odhcpd.loglevel='4' 43 | 44 | delete dhcp.IoT 45 | set dhcp.IoT=dhcp 46 | set dhcp.IoT.interface='IoT' 47 | set dhcp.IoT.start='100' 48 | set dhcp.IoT.limit='150' 49 | set dhcp.IoT.leasetime='12h' 50 | set dhcp.IoT.dhcpv4='server' 51 | set dhcp.IoT.dhcpv6='server' 52 | set dhcp.IoT.ra='server' 53 | set dhcp.IoT.ra_management='1' 54 | set dhcp.IoT.ra_default='2' 55 | set dhcp.IoT.netmask='255.255.255.0' 56 | set dhcp.IoT.force='1' 57 | set dhcp.IoT.router='192.168.0.1' 58 | set dhcp.IoT.domain='lan' 59 | add_list dhcp.IoT.ra_flags='managed-config' 60 | add_list dhcp.IoT.ra_flags='other-config' 61 | add_list dhcp.IoT.dhcp_option='1,255.255.255.0' 62 | add_list dhcp.IoT.dhcp_option='3,192.168.0.1' 63 | add_list dhcp.IoT.dhcp_option='4,192.168.0.1' 64 | add_list dhcp.IoT.dhcp_option='6,192.168.0.1' 65 | add_list dhcp.IoT.dhcp_option='12,OpenWrt_WRX36-IoT' 66 | add_list dhcp.IoT.dhcp_option='15,lan' 67 | 68 | delete dhcp.@host[0] 69 | add dhcp host 70 | set dhcp.@host[0]=host 71 | set dhcp.@host[0].name='AP-IoT' 72 | set dhcp.@host[0].ip='192.168.0.254' 73 | set dhcp.@host[0].mac='02:00:C0:A8:00:FE' 74 | 75 | commit dhcp 76 | EOF 77 | 78 | fi 79 | 80 | exit 0 81 | -------------------------------------------------------------------------------- /files/etc/uci-defaults/z1-network: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # The network ROM Config does not exist, it's generated on-demand, but if the 4 | # config already exists, leave as it-is 5 | if /usr/bin/is-default-config network && [ ! -f /etc/config/network ]; then 6 | 7 | touch /etc/config/network 8 | 9 | uci -q batch <<'EOF' 10 | delete network.loopback 11 | set network.loopback=interface 12 | set network.loopback.device='lo' 13 | set network.loopback.proto='static' 14 | set network.loopback.ipaddr='127.0.0.1' 15 | set network.loopback.netmask='255.0.0.0' 16 | set network.globals=globals 17 | 18 | delete network.@device[0] 19 | add network device 20 | set network.@device[0]=device 21 | set network.@device[0].name='br-lan' 22 | set network.@device[0].type='bridge' 23 | add_list network.@device[0].ports='lan1' 24 | add_list network.@device[0].ports='lan2' 25 | add_list network.@device[0].ports='lan3' 26 | set network.@device[0].bridge_empty='1' 27 | set network.@device[0].stp='1' 28 | set network.@device[0].igmp_snooping='1' 29 | 30 | delete network.lan 31 | set network.lan=interface 32 | set network.lan.device='br-lan' 33 | set network.lan.proto='static' 34 | set network.lan.ip6assign='60' 35 | set network.lan.broadcast='10.0.0.255' 36 | set network.lan.dns_search='lan' 37 | set network.lan.ipaddr='10.0.0.1/24' 38 | set network.lan.dns='10.0.0.1' 39 | set network.lan.delegate='0' 40 | 41 | delete network.wan 42 | set network.wan=interface 43 | set network.wan.device='wan' 44 | set network.wan.proto='dhcp' 45 | set network.wan.force_link='1' 46 | set network.wan.peerdns='0' 47 | set network.wan.dns='127.0.0.1' 48 | set network.wan.delegate='0' 49 | delete network.wan6 50 | set network.wan6=interface 51 | set network.wan6.device='wan' 52 | set network.wan6.proto='dhcpv6' 53 | set network.wan6.auto='0' 54 | set network.wan6.reqaddress='try' 55 | set network.wan6.reqprefix='auto' 56 | set network.wan6.peerdns='0' 57 | 58 | delete network.IoT 59 | set network.IoT=interface 60 | set network.IoT.device='lan4' 61 | set network.IoT.proto='static' 62 | set network.IoT.ip6assign='60' 63 | set network.IoT.broadcast='192.168.0.255' 64 | set network.IoT.ipaddr='192.168.0.1/24' 65 | set network.IoT.dns_search='lan' 66 | set network.IoT.dns='192.168.0.1' 67 | set network.IoT.delegate='0' 68 | 69 | delete network.tailscale 70 | set network.tailscale=interface 71 | set network.tailscale.proto='none' 72 | set network.tailscale.device='tailscale0' 73 | set network.tailscale.auto='0' 74 | set network.tailscale.disabled='1' 75 | 76 | delete network.zerotier 77 | set network.zerotier=interface 78 | set network.zerotier.proto='none' 79 | set network.zerotier.device='ztXXXXXXXX' 80 | set network.zerotier.auto='0' 81 | set network.zerotier.disabled='1' 82 | 83 | delete network.@device[1] 84 | add network device 85 | set network.@device[1]=device 86 | set network.@device[1].name='lan4' 87 | 88 | commit network 89 | EOF 90 | 91 | fi 92 | 93 | exit 0 94 | -------------------------------------------------------------------------------- /files/etc/init.d/smp_affinity: -------------------------------------------------------------------------------- 1 | #!/bin/sh /etc/rc.common 2 | 3 | START=99 4 | 5 | # Helper function to set affinity 6 | # $1: Mask (1=CPU0, 2=CPU1, 4=CPU2, 8=CPU3) 7 | # $2: Name pattern to match in /proc/interrupts 8 | set_affinity() { 9 | local mask="$1" 10 | local pattern="$2" 11 | 12 | # Grep for the pattern, ignore case (-i) 13 | # awk '{print $1}' gets the IRQ number (e.g., "43:") 14 | # tr -d ':' removes the colon 15 | for irq in $(grep -i "$pattern" /proc/interrupts | awk '{print $1}' | tr -d ':'); do 16 | if [ -d "/proc/irq/$irq" ]; then 17 | logger -p notice -t smp_affinity "Setting IRQ $irq ($pattern) to Mask $mask" 18 | echo "$mask" > "/proc/irq/$irq/smp_affinity" 19 | fi 20 | done 21 | } 22 | 23 | boot() { 24 | logger -p notice -t smp_affinity "Running NSS SMP Affinity optimization..." 25 | 26 | # --- STRATEGY --- 27 | # CPU0 (Mask 1): Reserved for OS, USB, and generic tasks. Network is OFF this core. 28 | # CPU1 (Mask 2): Ethernet (EDMA), Crypto & Secondary NSS Core 29 | # CPU2 (Mask 4): WiFi (REO/RxDMA) 30 | # CPU3 (Mask 8): Primary NSS Core (The heaviest load) 31 | 32 | # 1. Ethernet (EDMA) - Exception path only -> CPU1 33 | set_affinity 2 "edma" 34 | 35 | # 2. Crypto Engine (ce) -> CPU1 36 | set_affinity 2 "ce[0-9]" 37 | 38 | # 3. WiFi Receive Offload (REO/RxDMA) -> CPU2 39 | set_affinity 4 "reo2host" 40 | set_affinity 4 "rxdma" 41 | set_affinity 4 "ppdu-end" 42 | 43 | # 4. NSS Queues Dynamic Logic 44 | # Finds all nss_queue IRQs, sorts them numerically, and splits them in half. 45 | # First half (Core 0) -> CPU3 46 | # Second half (Core 1) -> CPU1 47 | logger -p notice -t smp_affinity "Applying Split NSS Affinity Strategy (Dynamic Count)..." 48 | 49 | # Get all NSS IRQs sorted numerically 50 | nss_irqs=$(grep -i "nss_queue" /proc/interrupts | awk '{print $1}' | tr -d ':' | sort -n) 51 | 52 | # Count how many we found 53 | total_count=$(echo "$nss_irqs" | wc -w) 54 | 55 | if [ "$total_count" -gt 0 ]; then 56 | # Calculate the split point (integer division) 57 | # e.g., if 8 queues, split_point is 4. 58 | split_point=$((total_count / 2)) 59 | current_idx=0 60 | 61 | for irq in $nss_irqs; do 62 | current_idx=$((current_idx + 1)) 63 | 64 | if [ -d "/proc/irq/$irq" ]; then 65 | if [ "$current_idx" -le "$split_point" ]; then 66 | # First half goes to CPU3 (Mask 8) 67 | echo "8" > "/proc/irq/$irq/smp_affinity" 68 | logger -p notice -t smp_affinity " - NSS Core 0 (IRQ $irq) -> CPU3" 69 | else 70 | # Second half goes to CPU1 (Mask 2) 71 | echo "2" > "/proc/irq/$irq/smp_affinity" 72 | logger -p notice -t smp_affinity " - NSS Core 1 (IRQ $irq) -> CPU1" 73 | fi 74 | fi 75 | done 76 | else 77 | logger -p warn -t smp_affinity "No NSS Queues found to optimize!" 78 | fi 79 | 80 | # 5. NSS Housekeeping (Buffer handling, etc) -> CPU3 81 | # Keeps the 'maintenance' work with the Primary Core 82 | set_affinity 8 "nss_empty" 83 | 84 | logger -p notice -t smp_affinity "NSS SMP Affinity applied successfully." 85 | } -------------------------------------------------------------------------------- /.github/workflows/scheduled-release.yml: -------------------------------------------------------------------------------- 1 | name: Weekly OpenWRT Firmware Build 2 | 3 | on: 4 | schedule: 5 | # Trigger every Friday at 17:00 UTC 6 | - cron: '0 17 * * 5' 7 | # Allow manual triggering of the workflow for testing 8 | workflow_dispatch: 9 | 10 | permissions: 11 | contents: write 12 | 13 | jobs: 14 | build: 15 | name: Build Firmware 16 | runs-on: ubuntu-latest 17 | timeout-minutes: 360 # Allow up to 6 hours for compilation 18 | 19 | steps: 20 | - name: Checkout Repository 21 | uses: actions/checkout@v4 22 | 23 | # OpenWRT builds consume massive disk space (potentially 60GB+). 24 | # This action frees up as much space as possible (approx 30-40GB freed + 14GB default = ~50GB total). 25 | # Note: If the build strictly requires >60GB, it may still hit limits on standard runners. 26 | - name: Free Disk Space 27 | uses: jlumbroso/free-disk-space@main 28 | with: 29 | tool-cache: true 30 | android: true 31 | dotnet: true 32 | haskell: true 33 | large-packages: true 34 | docker-images: true 35 | swap-storage: true 36 | 37 | # 1. RESTORE CACHE (Only restores, does not save) 38 | - name: Restore ccache 39 | uses: actions/cache/restore@v4 40 | with: 41 | path: workdir/openwrt/.ccache 42 | key: ccache-openwrt-${{ github.sha }} 43 | restore-keys: | 44 | ccache-openwrt- 45 | 46 | - name: Make Build Script Executable 47 | run: chmod +x build.sh 48 | 49 | - name: Run Build Script 50 | id: compile 51 | run: | 52 | # Pipe inputs to build.sh to bypass interactive prompts: 53 | # 1. Download/Update sources? -> 'y' 54 | # 2. Number of threads? -> '1' (Max/All threads) 55 | # 3. Verbose output? -> 'y' (Required for CI logs) 56 | # - WARNING: Max Threads + Verbose triggers a "Press Enter" pause. We send an extra '\n'. 57 | # 4. Pause for custom packages? -> 'n' (CRITICAL: 'y' hangs the build) 58 | printf 'y\n1\ny\n\nn\n' | ./build.sh --no-animation 59 | 60 | # 2. SAVE CACHE (Saves always, even if previous steps failed) 61 | - name: Save ccache 62 | uses: actions/cache/save@v4 63 | if: always() 64 | with: 65 | path: workdir/openwrt/.ccache 66 | key: ccache-openwrt-${{ github.sha }} 67 | 68 | - name: Upload Build Logs (On Failure) 69 | if: failure() 70 | uses: actions/upload-artifact@v4 71 | with: 72 | name: build-logs-failure 73 | path: "*.log" 74 | retention-days: 5 75 | 76 | - name: Generate Release Tag 77 | id: tag 78 | run: echo "date=$(date +'%Y.%m.%d-%H%M')" >> $GITHUB_OUTPUT 79 | 80 | - name: Upload Firmware Release 81 | uses: softprops/action-gh-release@v2 82 | with: 83 | tag_name: build-${{ steps.tag.outputs.date }} 84 | name: OpenWRT Firmware ${{ steps.tag.outputs.date }} 85 | # recursive glob to grab the binaries from the target directory 86 | files: workdir/openwrt/bin/targets/**/* 87 | body: | 88 | Automated weekly build. 89 | 90 | **Build Date:** ${{ steps.tag.outputs.date }} 91 | **Script Version:** [Link](${{ github.server_url }}/${{ github.repository }}/blob/${{ github.sha }}/build.sh) 92 | env: 93 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 94 | -------------------------------------------------------------------------------- /files/etc/unbound/root.hints: -------------------------------------------------------------------------------- 1 | ; This file holds the information on root name servers needed to 2 | ; initialize cache of Internet domain name servers 3 | ; (e.g. reference this file in the "cache . " 4 | ; configuration file of BIND domain name servers). 5 | ; 6 | ; This file is made available by InterNIC 7 | ; under anonymous FTP as 8 | ; file /domain/named.cache 9 | ; on server FTP.INTERNIC.NET 10 | ; -OR- RS.INTERNIC.NET 11 | ; 12 | ; last update: April 18, 2024 13 | ; related version of root zone: 2024041801 14 | ; 15 | ; FORMERLY NS.INTERNIC.NET 16 | ; 17 | . 3600000 NS A.ROOT-SERVERS.NET. 18 | A.ROOT-SERVERS.NET. 3600000 A 198.41.0.4 19 | A.ROOT-SERVERS.NET. 3600000 AAAA 2001:503:ba3e::2:30 20 | ; 21 | ; FORMERLY NS1.ISI.EDU 22 | ; 23 | . 3600000 NS B.ROOT-SERVERS.NET. 24 | B.ROOT-SERVERS.NET. 3600000 A 170.247.170.2 25 | B.ROOT-SERVERS.NET. 3600000 AAAA 2801:1b8:10::b 26 | ; 27 | ; FORMERLY C.PSI.NET 28 | ; 29 | . 3600000 NS C.ROOT-SERVERS.NET. 30 | C.ROOT-SERVERS.NET. 3600000 A 192.33.4.12 31 | C.ROOT-SERVERS.NET. 3600000 AAAA 2001:500:2::c 32 | ; 33 | ; FORMERLY TERP.UMD.EDU 34 | ; 35 | . 3600000 NS D.ROOT-SERVERS.NET. 36 | D.ROOT-SERVERS.NET. 3600000 A 199.7.91.13 37 | D.ROOT-SERVERS.NET. 3600000 AAAA 2001:500:2d::d 38 | ; 39 | ; FORMERLY NS.NASA.GOV 40 | ; 41 | . 3600000 NS E.ROOT-SERVERS.NET. 42 | E.ROOT-SERVERS.NET. 3600000 A 192.203.230.10 43 | E.ROOT-SERVERS.NET. 3600000 AAAA 2001:500:a8::e 44 | ; 45 | ; FORMERLY NS.ISC.ORG 46 | ; 47 | . 3600000 NS F.ROOT-SERVERS.NET. 48 | F.ROOT-SERVERS.NET. 3600000 A 192.5.5.241 49 | F.ROOT-SERVERS.NET. 3600000 AAAA 2001:500:2f::f 50 | ; 51 | ; FORMERLY NS.NIC.DDN.MIL 52 | ; 53 | . 3600000 NS G.ROOT-SERVERS.NET. 54 | G.ROOT-SERVERS.NET. 3600000 A 192.112.36.4 55 | G.ROOT-SERVERS.NET. 3600000 AAAA 2001:500:12::d0d 56 | ; 57 | ; FORMERLY AOS.ARL.ARMY.MIL 58 | ; 59 | . 3600000 NS H.ROOT-SERVERS.NET. 60 | H.ROOT-SERVERS.NET. 3600000 A 198.97.190.53 61 | H.ROOT-SERVERS.NET. 3600000 AAAA 2001:500:1::53 62 | ; 63 | ; FORMERLY NIC.NORDU.NET 64 | ; 65 | . 3600000 NS I.ROOT-SERVERS.NET. 66 | I.ROOT-SERVERS.NET. 3600000 A 192.36.148.17 67 | I.ROOT-SERVERS.NET. 3600000 AAAA 2001:7fe::53 68 | ; 69 | ; OPERATED BY VERISIGN, INC. 70 | ; 71 | . 3600000 NS J.ROOT-SERVERS.NET. 72 | J.ROOT-SERVERS.NET. 3600000 A 192.58.128.30 73 | J.ROOT-SERVERS.NET. 3600000 AAAA 2001:503:c27::2:30 74 | ; 75 | ; OPERATED BY RIPE NCC 76 | ; 77 | . 3600000 NS K.ROOT-SERVERS.NET. 78 | K.ROOT-SERVERS.NET. 3600000 A 193.0.14.129 79 | K.ROOT-SERVERS.NET. 3600000 AAAA 2001:7fd::1 80 | ; 81 | ; OPERATED BY ICANN 82 | ; 83 | . 3600000 NS L.ROOT-SERVERS.NET. 84 | L.ROOT-SERVERS.NET. 3600000 A 199.7.83.42 85 | L.ROOT-SERVERS.NET. 3600000 AAAA 2001:500:9f::42 86 | ; 87 | ; OPERATED BY WIDE 88 | ; 89 | . 3600000 NS M.ROOT-SERVERS.NET. 90 | M.ROOT-SERVERS.NET. 3600000 A 202.12.27.33 91 | M.ROOT-SERVERS.NET. 3600000 AAAA 2001:dc3::35 92 | ; End of file -------------------------------------------------------------------------------- /files/etc/uci-defaults/z2-unbound: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if /usr/bin/is-default-config unbound; then 4 | 5 | touch /etc/config/unbound 6 | 7 | uci -q batch <<'EOF' 8 | delete unbound.ub_main 9 | set unbound.ub_main=unbound 10 | set unbound.ub_main.interface_auto='1' 11 | set unbound.ub_main.hide_binddata='1' 12 | set unbound.ub_main.listen_port='53' 13 | set unbound.ub_main.extended_luci='1' 14 | set unbound.ub_main.localservice='1' 15 | # Add the extra hosts in the `/etc/config/dhcp` file. 16 | set unbound.ub_main.add_extra_dns='4' 17 | set unbound.ub_main.num_threads='1' 18 | set unbound.ub_main.rate_limit='0' 19 | set unbound.ub_main.rebind_protection='1' 20 | set unbound.ub_main.rebind_localhost='1' 21 | set unbound.ub_main.root_age='5' 22 | set unbound.ub_main.ttl_min='120' 23 | set unbound.ub_main.ttl_neg_max='1000' 24 | set unbound.ub_main.validator='1' 25 | set unbound.ub_main.verbosity='1' 26 | set unbound.ub_main.enabled='1' 27 | set unbound.ub_main.extended_stats='0' 28 | set unbound.ub_main.dhcp_link='odhcpd' 29 | set unbound.ub_main.recursion='aggressive' 30 | set unbound.ub_main.resource='default' 31 | set unbound.ub_main.domain='lan' 32 | set unbound.ub_main.unbound_control='2' 33 | set unbound.ub_main.protocol='ip6_local' 34 | set unbound.ub_main.manual_conf='0' 35 | set unbound.ub_main.edns_size='1232' 36 | set unbound.ub_main.dns64='1' 37 | set unbound.ub_main.domain_type='static' 38 | set unbound.ub_main.add_local_fqdn='4' 39 | set unbound.ub_main.add_wan_fqdn='0' 40 | set unbound.ub_main.dhcp4_slaac6='1' 41 | set unbound.ub_main.subnetcache='0' 42 | # Do not create DNS records for hosts with Public IPv6 Addresses, preventing stale DNS 43 | # records when the prefix is dynamic by the router. 44 | set unbound.ub_main.exclude_ipv6_ga='1' 45 | 46 | add_list unbound.ub_main.trigger_interface='lan' 47 | add_list unbound.ub_main.trigger_interface='IoT' 48 | add_list unbound.ub_main.trigger_interface='wan' 49 | add_list unbound.ub_main.iface_lan='IoT' 50 | add_list unbound.ub_main.iface_lan='lan' 51 | add_list unbound.ub_main.iface_wan='wan' 52 | add_list unbound.ub_main.iface_wan='wan6' 53 | add_list unbound.ub_main.iface_trig='IoT' 54 | add_list unbound.ub_main.iface_trig='lan' 55 | add_list unbound.ub_main.iface_trig='wan' 56 | 57 | delete unbound.auth_icann 58 | set unbound.auth_icann=zone 59 | set unbound.auth_icann.enabled='1' 60 | set unbound.auth_icann.fallback='1' 61 | set unbound.auth_icann.url_dir='https://www.internic.net/domain/' 62 | set unbound.auth_icann.zone_type='auth_zone' 63 | add_list unbound.auth_icann.server='lax.xfr.dns.icann.org' 64 | add_list unbound.auth_icann.server='iad.xfr.dns.icann.org' 65 | add_list unbound.auth_icann.zone_name='.' 66 | add_list unbound.auth_icann.zone_name='arpa.' 67 | add_list unbound.auth_icann.zone_name='in-addr.arpa.' 68 | add_list unbound.auth_icann.zone_name='in-addr-servers.arpa.' 69 | add_list unbound.auth_icann.zone_name='ip4only.arpa.' 70 | add_list unbound.auth_icann.zone_name='ip6.arpa.' 71 | add_list unbound.auth_icann.zone_name='ip6-servers.arpa.' 72 | add_list unbound.auth_icann.zone_name='root-servers.net.' 73 | add_list unbound.auth_icann.zone_name='iana-servers.net.' 74 | add_list unbound.auth_icann.zone_name='icann-servers.net.' 75 | add_list unbound.auth_icann.zone_name='mcast.net.' 76 | add_list unbound.auth_icann.zone_name='ns.arpa.' 77 | add_list unbound.auth_icann.zone_name='home.arpa.' 78 | add_list unbound.auth_icann.zone_name='resolver.arpa.' 79 | add_list unbound.auth_icann.zone_name='uri.arpa.' 80 | add_list unbound.auth_icann.zone_name='urn.arpa.' 81 | add_list unbound.auth_icann.zone_name='iris.arpa.' 82 | set unbound.auth_icann.enabled='0' 83 | set unbound.auth_icann.fallback='0' 84 | 85 | commit unbound 86 | EOF 87 | 88 | fi 89 | 90 | exit 0 91 | -------------------------------------------------------------------------------- /packages/luci-app-zerotier/root/www/luci-static/resources/view/zerotier.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 'require form'; 3 | 'require view'; 4 | 'require uci'; 5 | 6 | return view.extend({ 7 | render: function() { 8 | var m, s, o; 9 | 10 | // 1. Map the 'zerotier' configuration file 11 | m = new form.Map('zerotier', _('ZeroTier'), 12 | _('ZeroTier creates a virtual network between hosts. Join a network to enable private, encrypted connectivity.')); 13 | 14 | // ----------------------------------------------------------------------- 15 | // Global Settings (Named Section: 'global') 16 | // ----------------------------------------------------------------------- 17 | s = m.section(form.NamedSection, 'global', 'zerotier', _('Global Settings')); 18 | s.anonymous = true; 19 | s.addremove = false; 20 | 21 | // Option: enabled (0/1) 22 | o = s.option(form.Flag, 'enabled', _('Enabled')); 23 | o.default = o.disabled; 24 | o.rmempty = false; 25 | 26 | // Option: port (default 9993) 27 | o = s.option(form.Value, 'port', _('Port'), _('ZeroTier listening port (default 9993). Set to 0 for random.')); 28 | o.datatype = 'port'; 29 | o.placeholder = '9993'; 30 | o.optional = true; 31 | 32 | // Option: secret (Client secret) 33 | o = s.option(form.Value, 'secret', _('Client Secret'), _('Leave blank to generate a secret on first run.')); 34 | o.password = true; 35 | o.optional = true; 36 | 37 | // Advanced paths (collapsible or just optional) 38 | o = s.option(form.Value, 'config_path', _('Persistent Config Path'), _('Directory for persistent configuration (e.g. /etc/zerotier).')); 39 | o.placeholder = '/etc/zerotier'; 40 | o.optional = true; 41 | 42 | o = s.option(form.Flag, 'copy_config_path', _('Copy Config'), _('Copy configuration to memory to avoid flash writes.')); 43 | o.optional = true; 44 | 45 | o = s.option(form.Value, 'local_conf_path', _('Local Config File'), _('Path to local.conf file for advanced options.')); 46 | o.placeholder = '/etc/zerotier.conf'; 47 | o.optional = true; 48 | 49 | 50 | // ----------------------------------------------------------------------- 51 | // Network Configurations (Typed Section: 'network') 52 | // ----------------------------------------------------------------------- 53 | s = m.section(form.TypedSection, 'network', _('ZeroTier Networks'), 54 | _('Join one or more ZeroTier networks by entering their 16-digit Network ID.')); 55 | s.anonymous = true; // We don't need to name the UCI sections (e.g. 'earth') explicitly in the UI 56 | s.addremove = true; // Allow adding/removing networks 57 | 58 | // Option: id (The Network ID) 59 | o = s.option(form.Value, 'id', _('Network ID'), _('16-character Network ID from ZeroTier Central.')); 60 | o.rmempty = false; 61 | o.validate = function(section_id, value) { 62 | if (!value || !value.match(/^[0-9a-fA-F]{16}$/)) { 63 | return _('Must be a valid 16-character Network ID'); 64 | } 65 | return true; 66 | }; 67 | 68 | // Option: allow_managed (Allow ZeroTier to assign IP addresses) 69 | o = s.option(form.Flag, 'allow_managed', _('Auto-Assign IP'), _('Allow ZeroTier to assign managed IP addresses.')); 70 | o.default = o.enabled; 71 | 72 | // Option: allow_global (Allow Global IPs) 73 | o = s.option(form.Flag, 'allow_global', _('Allow Global IPs'), _('Allow setting global/public IP addresses.')); 74 | o.default = o.disabled; 75 | 76 | // Option: allow_default (Allow Default Route) 77 | o = s.option(form.Flag, 'allow_default', _('Allow Default Route'), _('Allow overriding the default route (Full Tunnel).')); 78 | o.default = o.disabled; 79 | 80 | // Option: allow_dns (Allow DNS) 81 | o = s.option(form.Flag, 'allow_dns', _('Allow DNS'), _('Allow accepting DNS configuration from the controller.')); 82 | o.default = o.disabled; 83 | 84 | return m.render(); 85 | } 86 | }); -------------------------------------------------------------------------------- /files/usr/bin/install-it-tools: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Configuration 4 | REPO="CorentinTh/it-tools" 5 | # Using /srv to match the service file paths 6 | STORAGE_DIR="/srv" 7 | IMAGE_FILE="$STORAGE_DIR/it-tools.squashfs" 8 | VERSION_FILE="$STORAGE_DIR/it-tools.version" 9 | WORK_DIR="/tmp/it-tools-install" 10 | 11 | # Setup colors for output 12 | GREEN='\033[0;32m' 13 | YELLOW='\033[1;33m' 14 | RED='\033[0;31m' 15 | NC='\033[0m' # No Color 16 | 17 | log() { 18 | echo "${GREEN}[IT-Tools]${NC} $1" 19 | } 20 | 21 | err() { 22 | echo "${RED}[Error]${NC} $1" 23 | } 24 | 25 | # Check for required dependencies 26 | for cmd in wget jq unzip mksquashfs; do 27 | if ! command -v $cmd >/dev/null; then 28 | err "Missing dependency: $cmd" 29 | echo "Please install it using: opkg update && opkg install $cmd" 30 | # Note: mksquashfs is often in package 'squashfs-tools-mksquashfs' 31 | exit 1 32 | fi 33 | done 34 | 35 | # Ensure storage directory exists 36 | if [ ! -d "$STORAGE_DIR" ]; then 37 | mkdir -p "$STORAGE_DIR" 38 | fi 39 | 40 | log "Checking for updates..." 41 | 42 | # Fetch latest release data using wget 43 | API_URL="https://api.github.com/repos/$REPO/releases/latest" 44 | RELEASE_JSON=$(wget -qO- --no-check-certificate "$API_URL") 45 | 46 | if [ -z "$RELEASE_JSON" ]; then 47 | err "Failed to fetch release info from GitHub." 48 | exit 1 49 | fi 50 | 51 | # Extract version and download URL 52 | LATEST_VERSION=$(echo "$RELEASE_JSON" | jq -r .tag_name) 53 | DOWNLOAD_URL=$(echo "$RELEASE_JSON" | jq -r '.assets[] | select(.name | endswith(".zip")) | select(.name | contains("source") | not) | .browser_download_url' | head -n 1) 54 | 55 | if [ -z "$DOWNLOAD_URL" ] || [ "$DOWNLOAD_URL" = "null" ]; then 56 | err "Could not find a valid zip asset in the latest release." 57 | exit 1 58 | fi 59 | 60 | # Check installed version 61 | CURRENT_VERSION="none" 62 | if [ -f "$VERSION_FILE" ]; then 63 | CURRENT_VERSION=$(cat "$VERSION_FILE") 64 | fi 65 | 66 | # Determine if action is needed 67 | if [ "$LATEST_VERSION" = "$CURRENT_VERSION" ] && [ -f "$IMAGE_FILE" ]; then 68 | log "Already up to date ($CURRENT_VERSION). Exiting." 69 | exit 0 70 | fi 71 | 72 | if [ "$CURRENT_VERSION" = "none" ]; then 73 | log "Installing version $LATEST_VERSION..." 74 | else 75 | log "Updating from $CURRENT_VERSION to $LATEST_VERSION..." 76 | fi 77 | 78 | # Prepare temporary workspace (in RAM) 79 | rm -rf "$WORK_DIR" 80 | mkdir -p "$WORK_DIR" 81 | 82 | # Download 83 | log "Downloading archive..." 84 | wget -qO "$WORK_DIR/it-tools.zip" --no-check-certificate "$DOWNLOAD_URL" 85 | 86 | # Unzip 87 | log "Extracting..." 88 | unzip -q "$WORK_DIR/it-tools.zip" -d "$WORK_DIR/extracted" 89 | 90 | # Find web root (handle potential subfolders in zip) 91 | WEB_ROOT=$(find "$WORK_DIR/extracted" -name "index.html" -exec dirname {} \; | head -n 1) 92 | 93 | if [ -z "$WEB_ROOT" ]; then 94 | err "Could not find index.html in extracted files." 95 | rm -rf "$WORK_DIR" 96 | exit 1 97 | fi 98 | 99 | log "Building SquashFS image (LZ4)..." 100 | 101 | # mksquashfs options for OpenWRT/Static Site: 102 | # -comp lz4: Fast decompression, low CPU usage (ideal for routers) 103 | # -b 256k: Moderate block size (balances compression ratio vs RAM usage during read) 104 | # -no-xattrs: We don't need extended attributes for a web root 105 | # -all-root: Force all files to be owned by root 106 | # -noappend: Overwrite destination file if it exists 107 | if mksquashfs "$WEB_ROOT" "$IMAGE_FILE" -comp lz4 -b 256k -no-xattrs -all-root -noappend -no-progress; then 108 | # Update version file 109 | echo "$LATEST_VERSION" > "$VERSION_FILE" 110 | log "Successfully installed to $IMAGE_FILE" 111 | 112 | # Reload service if it is running to pick up changes immediately 113 | if [ -f "/etc/init.d/mount_it_tools" ] && /etc/init.d/mount_it_tools enabled; then 114 | log "Restarting mount-it-tools service..." 115 | /etc/init.d/mount_it_tools restart 116 | fi 117 | else 118 | err "Failed to create SquashFS image." 119 | rm -rf "$WORK_DIR" 120 | exit 1 121 | fi 122 | 123 | # Cleanup RAM 124 | rm -rf "$WORK_DIR" 125 | log "Done." -------------------------------------------------------------------------------- /files/usr/bin/configure-firewall: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # ANSI Colors 4 | GREEN="\033[0;32m" 5 | RED="\033[0;31m" 6 | YELLOW="\033[1;33m" 7 | NC="\033[0m" # No Color 8 | 9 | show_menu() { 10 | clear 11 | printf "%b=========================================%b\n" "${YELLOW}" "${NC}" 12 | printf " Firewall Configuration Tool \n" 13 | printf "%b=========================================%b\n" "${YELLOW}" "${NC}" 14 | printf "\n" 15 | printf "1. Set Firewall Defaults (Secure)\n" 16 | printf " Re-applies the default policies for firewall.\n" 17 | printf " (Input: REJECT, Output: ACCEPT, Forward: REJECT)\n" 18 | printf " Removes any temporary WAN access rules.\n" 19 | printf "\n" 20 | printf "2. Enable WAN Management Access\n" 21 | printf " Allows SSH, HTTP, HTTPS, SMB, Avahi (Bonjour), btop (via ttyd) and Ping from the WAN side.\n" 22 | printf " Enable Plex Media Server, Aria2 (6980-6999), miniDLNA, from the WAN side.\n" 23 | printf " Useful for upstream management, client discovery, but keeping the firewall enabled.\n" 24 | printf " Firewall will be enabled, and these rules will be enabled (or added if absent).\n" 25 | printf "\n" 26 | printf "3. Disable Firewall Completely (DANGEROUS)\n" 27 | printf " Stops the firewall service and sets all policies to ACCEPT.\n" 28 | printf " Use only if this device is behind another firewall.\n" 29 | printf "\n" 30 | printf "0. Exit\n" 31 | printf "\n" 32 | } 33 | 34 | set_defaults() { 35 | printf "\n%bApplying Firewall Defaults (init.d/z3-firewall base)...%b\n" "${YELLOW}" "${NC}" 36 | 37 | /etc/init.d/firewall enable 38 | 39 | # To work with the default, we can just copy the OpenWRT default firewall config 40 | # into the current config, then run the init.d/z3-firewall and call it a day. 41 | cp /rom/etc/config/firewall /etc/config/firewall 42 | 43 | sh -c /rom/etc/init.d/z3-firewall 44 | 45 | /etc/init.d/firewall restart 46 | printf "%bSecure defaults applied.%b\n" "${GREEN}" "${NC}" 47 | } 48 | 49 | allow_wan_management() { 50 | printf "\n%bEnabling WAN Management Access...%b\n" "${GREEN}" "${NC}" 51 | 52 | /etc/init.d/firewall enable 53 | 54 | # Add the default rules 55 | /usr/bin/add-wan-rules-to-firewall 56 | 57 | # Iterate on each added rule and enable it 58 | RULES=" 59 | allow_ssh_wan 60 | allow_ttyd_management_wan 61 | allow_ttyd_btop_wan 62 | allow_http_management_wan 63 | allow_https_management_wan 64 | allow_avahi_wan 65 | allow_smb_wan 66 | allow_plex_http_wan 67 | allow_plex_discovery_wan 68 | allow_ariang_wan 69 | allow_ariang_bittorrent_wan 70 | allow_minidlna_wan 71 | allow_minidlna_discovery_wan 72 | " 73 | 74 | for rule in $RULES; do 75 | uci set "firewall.$rule.enabled='1'" 76 | done 77 | 78 | uci commit firewall 79 | /etc/init.d/firewall restart 80 | 81 | printf "%bWAN Access Enabled.%b\n" "${GREEN}" "${NC}" 82 | } 83 | 84 | disable_firewall() { 85 | printf "\n%bDisabling Firewall Service...%b\n" "${RED}" "${NC}" 86 | 87 | /etc/init.d/firewall stop 88 | /etc/init.d/firewall disable 89 | 90 | echo "Flushing tables and setting default policies to ACCEPT..." 91 | 92 | if command -v nft >/dev/null; then 93 | nft flush ruleset 94 | nft add table inet filter 95 | nft add chain inet filter input "{ type filter hook input priority 0 ; policy accept ; }" 96 | nft add chain inet filter forward "{ type filter hook forward priority 0 ; policy accept ; }" 97 | nft add chain inet filter output "{ type filter hook output priority 0 ; policy accept ; }" 98 | else 99 | iptables -P INPUT ACCEPT 100 | iptables -P OUTPUT ACCEPT 101 | iptables -P FORWARD ACCEPT 102 | iptables -F 103 | iptables -t nat -F 104 | iptables -t mangle -F 105 | fi 106 | 107 | printf "%bFirewall stopped and flushed. ALL TRAFFIC ALLOWED.%b\n" "${RED}" "${NC}" 108 | } 109 | 110 | while true; do 111 | show_menu 112 | printf "Select an option [0-3]: " 113 | read -r choice 114 | case "$choice" in 115 | 1) set_defaults; printf "Press Enter to continue..."; read -r _ ;; 116 | 2) allow_wan_management; printf "Press Enter to continue..."; read -r _ ;; 117 | 3) disable_firewall; printf "Press Enter to continue..."; read -r _ ;; 118 | 0) exit 0 ;; 119 | *) printf "%bInvalid option.%b\n" "${RED}" "${NC}"; sleep 1 ;; 120 | esac 121 | done -------------------------------------------------------------------------------- /files/etc/uci-defaults/z2-wireless: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # As with the `system` config created on demand, we will also skip this if the config exists. 4 | if /usr/bin/is-default-config wireless && [ ! -f /etc/config/wireless ]; then 5 | 6 | touch /etc/config/wireless 7 | 8 | uci -q batch <<'EOF' 9 | # --- Radio 0 (5GHz) --- 10 | delete wireless.radio0 11 | set wireless.radio0=wifi-device 12 | set wireless.radio0.type='mac80211' 13 | set wireless.radio0.path='platform/soc@0/c000000.wifi' 14 | set wireless.radio0.band='5g' 15 | set wireless.radio0.channel='auto' 16 | set wireless.radio0.htmode='HE80' 17 | set wireless.radio0.country='US' 18 | set wireless.radio0.country_ie='1' 19 | set wireless.radio0.cell_density='0' 20 | set wireless.radio0.txpower='30' 21 | set wireless.radio0.diversity='1' 22 | set wireless.radio0.disabled='0' 23 | # Should disable aggressive Spatial Multiplexing Power Save (SMPS) spam logs (apple devices switching to power-save) 24 | set wireless.radio0.log_level='3' 25 | 26 | # --- Radio 1 (2.4GHz) --- 27 | delete wireless.radio1 28 | set wireless.radio1=wifi-device 29 | set wireless.radio1.type='mac80211' 30 | set wireless.radio1.path='platform/soc@0/c000000.wifi+1' 31 | set wireless.radio1.band='2g' 32 | set wireless.radio1.channel='auto' 33 | set wireless.radio1.htmode='HE20' 34 | set wireless.radio1.country='US' 35 | set wireless.radio1.country_ie='1' 36 | set wireless.radio1.cell_density='0' 37 | set wireless.radio1.txpower='30' 38 | set wireless.radio1.diversity='1' 39 | set wireless.radio1.disabled='0' 40 | set wireless.radio1.noscan='1' 41 | # Should disable aggressive Spatial Multiplexing Power Save (SMPS) spam logs (apple devices switching to power-save) 42 | set wireless.radio0.log_level='3' 43 | 44 | # --- Remove Default OpenWrt Interfaces --- 45 | # These were causing the duplicate "OpenWrt" SSIDs 46 | delete wireless.default_radio0 47 | delete wireless.default_radio1 48 | 49 | # --- Setup Main 5GHz Interface --- 50 | delete wireless.lan_radio0 51 | set wireless.lan_radio0=wifi-iface 52 | set wireless.lan_radio0.device='radio0' 53 | set wireless.lan_radio0.mode='ap' 54 | set wireless.lan_radio0.ssid='OpenWrt_WiFi' 55 | set wireless.lan_radio0.encryption='psk2+ccmp' 56 | set wireless.lan_radio0.network='lan' 57 | set wireless.lan_radio0.ieee80211r='1' 58 | set wireless.lan_radio0.ieee80211w='1' 59 | set wireless.lan_radio0.ieee80211k='1' 60 | set wireless.lan_radio0.ft_psk_generate_local='1' 61 | set wireless.lan_radio0.ft_over_ds='0' 62 | set wireless.lan_radio0.na_mcast_to_ucast='1' 63 | set wireless.lan_radio0.rrm_neighbor_report='1' 64 | set wireless.lan_radio0.rrm_beacon_report='1' 65 | set wireless.lan_radio0.wmm='1' 66 | set wireless.lan_radio0.wnm_sleep_mode='1' 67 | set wireless.lan_radio0.wnm_sleep_mode_no_keys='1' 68 | set wireless.lan_radio0.bss_transition='1' 69 | set wireless.lan_radio0.reassociation_deadline='1000' 70 | set wireless.lan_radio0.proxy_arp='1' 71 | set wireless.lan_radio0.multicast_to_unicast_all='0' 72 | set wireless.lan_radio0.key='password' 73 | 74 | # --- Setup Main 2.4GHz Interface --- 75 | delete wireless.lan_radio1 76 | set wireless.lan_radio1=wifi-iface 77 | set wireless.lan_radio1.device='radio1' 78 | set wireless.lan_radio1.mode='ap' 79 | set wireless.lan_radio1.ssid='OpenWrt_WiFi' 80 | set wireless.lan_radio1.encryption='psk2+ccmp' 81 | set wireless.lan_radio1.network='lan' 82 | set wireless.lan_radio1.ieee80211r='1' 83 | set wireless.lan_radio1.ieee80211w='1' 84 | set wireless.lan_radio1.ieee80211k='1' 85 | set wireless.lan_radio1.ft_psk_generate_local='1' 86 | set wireless.lan_radio1.ft_over_ds='0' 87 | set wireless.lan_radio1.na_mcast_to_ucast='1' 88 | set wireless.lan_radio1.rrm_neighbor_report='1' 89 | set wireless.lan_radio1.rrm_beacon_report='1' 90 | set wireless.lan_radio1.wmm='1' 91 | set wireless.lan_radio1.wnm_sleep_mode='1' 92 | set wireless.lan_radio1.wnm_sleep_mode_no_keys='1' 93 | set wireless.lan_radio1.bss_transition='1' 94 | set wireless.lan_radio1.reassociation_deadline='1000' 95 | set wireless.lan_radio1.proxy_arp='1' 96 | set wireless.lan_radio1.multicast_to_unicast_all='0' 97 | set wireless.lan_radio1.key='password' 98 | 99 | # Ensure both radios use the same group rekey interval 100 | set wireless.lan_radio0.wpa_group_rekey='86400' 101 | set wireless.lan_radio1.wpa_group_rekey='86400' 102 | set wireless.lan_radio1.mobility_domain='1234' 103 | set wireless.lan_radio1.mobility_domain='1234' 104 | 105 | # --- Setup IoT 2.4GHz Interface --- 106 | delete wireless.iot_radio1 107 | set wireless.iot_radio1=wifi-iface 108 | set wireless.iot_radio1.device='radio1' 109 | set wireless.iot_radio1.mode='ap' 110 | set wireless.iot_radio1.ssid='OpenWrt_IoT' 111 | set wireless.iot_radio1.encryption='psk2' 112 | set wireless.iot_radio1.network='IoT' 113 | set wireless.iot_radio1.wmm='1' 114 | set wireless.iot_radio1.wnm_sleep_mode='1' 115 | set wireless.iot_radio1.wnm_sleep_mode_no_keys='1' 116 | set wireless.iot_radio1.macaddr='random' 117 | set wireless.iot_radio1.key='password-iot' 118 | 119 | commit wireless 120 | EOF 121 | 122 | fi 123 | 124 | exit 0 -------------------------------------------------------------------------------- /files/etc/ksmbd/ksmbd.conf.template.example: -------------------------------------------------------------------------------- 1 | # This template is finetuned for spinning hard drives. 2 | # 3 | # Follow this rule for mounting options depending on the device you're connecting: 4 | # 5 | # - Spinning Hard Drives on USB 2.0/3.0: 6 | # 7 | # rw,data=writeback,noatime,barrier=0,commit=60 8 | # 9 | # Boosts write speeds by delaying commits and disabling barriers for fewer I/O interruptions; 10 | # enables faster spin-down and lower CPU usage. For better data integrity, use data=ordered 11 | # and barrier=1, though this may reduce throughput by 10-20%. 12 | # 13 | # - SSD (SATA) Drives on USB 3.0: 14 | # 15 | # rw,data=writeback,noatime 16 | # 17 | # Enhances steady throughput without HDD-specific delays, saturating USB 3.0 for ~100MB/s+ 18 | # transfers. For improved integrity, add data=ordered to ensure metadata consistency, at 19 | # a minor speed cost. 20 | # 21 | # - NVMe Drives on USB 3.0: 22 | # 23 | # rw,data=writeback,lazytime 24 | # 25 | # Reduces flash writes for minimal latency on metadata ops, achieving 200MB/s+ where possible. 26 | # For stronger integrity, use data=journaled, which adds overhead but minimizes corruption 27 | # risks. 28 | # 29 | # If you're copy & pasting these lines, beware of the whitespaces! 30 | 31 | [global] 32 | # --- Identity & Network --- 33 | # 34 | # Standard settings for network discovery and binding; bind interfaces only=yes restricts 35 | # to specified NICs, reducing unnecessary overhead for better router performance across 36 | # all profiles. 37 | # 38 | 39 | netbios name = |NAME| 40 | server string = |DESCRIPTION| 41 | workgroup = |WORKGROUP| 42 | interfaces = |INTERFACES| 43 | bind interfaces only = yes 44 | 45 | 46 | # --- Session Management --- 47 | 48 | # 49 | # The "deadtime" option disconnects inactive clients after 15 mins, freeing RAM and CPU on 50 | # the router for active transfers; ideal for HDD spin-down to maintain high burst speeds. 51 | # 52 | deadtime = 15 53 | 54 | # 55 | # The "ipc" timeout sets a quick 20s reply window for heartbeats, preventing stalled 56 | # connections from blocking performance. 57 | # 58 | ipc timeout = 20 59 | 60 | # 61 | # The "map to guest" allows fallback access on bad credentials, simplifying connections 62 | # without perf impact. 63 | # 64 | map to guest = Bad User 65 | 66 | 67 | # --- Protocol Performance (Crucial for OpenWRT CPU) --- 68 | 69 | # 70 | # Forces SMB3 for efficient opcodes, improving overall throughput by 20-50% over older 71 | # protocols on all storage types. 72 | # 73 | min protocol = SMB3 74 | 75 | # 76 | # Disables signing and encryption to avoid CPU overhead, boosting speeds from ~5MB/s to 77 | # ~30MB/s on routers; security trade-off, but essential for performance. 78 | # 79 | server signing = disabled 80 | 81 | # 82 | # For data integrity in transit, enable signing or encryption if on trusted networks, though 83 | # this halves speeds on low-power CPUs. 84 | # 85 | smb3 encryption = disabled 86 | 87 | 88 | # --- Transport & Buffers --- 89 | 90 | # 91 | # 4MB buffers optimize large file transfers on USB 2.0 by minimizing CPU-USB interruptions, 92 | # suitable for all profiles but especially HDD sequential reads. 93 | # 94 | smb2 max read = 4096K 95 | smb2 max write = 4096K 96 | 97 | # 98 | # For HDD: 1MB trans reduces latency on mechanical seeks, enabling smoother small-file ops. 99 | # For NVMe/SSD: 4MB trans maximizes chunk size for high-throughput transfers, leveraging fast 100 | # storage to hit USB limits. 101 | # 102 | smb2 max trans = 1024K 103 | # smb2 max trans = 4096K 104 | 105 | 106 | # --- KSMBD Specific Optimization --- 107 | 108 | # 109 | # Enables sendfile for zero-copy transfers from USB to network, crucial for USB 3.0 speeds 110 | # (up to 2x gains); benefits SSD/NVMe most by bypassing CPU. 111 | # 112 | use sendfile = yes 113 | 114 | 115 | # --- Asynchronous I/O (AIO) --- 116 | 117 | # 118 | # For HDD: 16KB AIO queues multiple requests to mask seek latencies, preventing network freezes 119 | # and improving responsiveness. 120 | # For NVMe: 4KB threshold handles tiny metadata sync for instant ops, while async offloads data 121 | # for non-blocking throughput. 122 | # 123 | # AIO has no direct integrity impact but pairs well with safe mount options like barrier=1 124 | # if perf is secondary. 125 | # 126 | aio read size = 16384 127 | aio write size = 16384 128 | # aio read size = 4096 129 | # aio write size = 4096 130 | 131 | # --- Caching & Metadata --- 132 | 133 | # 134 | # For NVMe: Disables dir leasing to cut protocol overhead, as the drive's low latency serves requests 135 | # directly for faster file listings. 136 | # For HDD: Keep enabled or rely on "data=writeback" for client-side caching to reduce seeks. 137 | # 138 | # Enabling leasing can enhance integrity via consistent caching but adds minor latency; default is off 139 | # in ksmbd for performance. 140 | # 141 | dir leasing = no -------------------------------------------------------------------------------- /files/usr/lib/unbound/odhcp.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | ############################################################################## 3 | # 4 | # Optimized for asynchronous execution to prevent blocking odhcpd 5 | # 6 | ############################################################################## 7 | 8 | # Load standard OpenWRT functions 9 | . /lib/functions.sh 10 | 11 | # Define the lock file 12 | UB_ODHCPD_LOCK=/var/lock/unbound_odhcpd.lock 13 | 14 | odhcpd_zonedata() { 15 | # Load Unbound defaults (creates variables like UB_VARDIR, UB_CONTROL_CFG) 16 | . /usr/lib/unbound/defaults.sh 17 | 18 | local dhcp_link=$( uci_get unbound.@unbound[0].dhcp_link ) 19 | local dhcp4_slaac6=$( uci_get unbound.@unbound[0].dhcp4_slaac6 ) 20 | local dhcp_domain=$( uci_get unbound.@unbound[0].domain ) 21 | # Dynamically grab the leasefile path. 22 | local dhcp_origin=$( uci_get dhcp.@odhcpd[0].leasefile ) 23 | local exclude_ipv6_ga=$( uci_get unbound.@unbound[0].exclude_ipv6_ga ) 24 | 25 | if [ "$exclude_ipv6_ga" != "0" ] && [ "$exclude_ipv6_ga" != "1" ]; then 26 | logger -t unbound -s "invalid exclude_ipv6_ga value, using default (0)" 27 | exclude_ipv6_ga=0 28 | fi 29 | 30 | # Verify files exist before proceeding 31 | if [ -f "$UB_TOTAL_CONF" ] && [ -f "$dhcp_origin" ] \ 32 | && [ "$dhcp_link" = "odhcpd" ] && [ -n "$dhcp_domain" ] ; then 33 | local longconf dateconf dateoldf 34 | local dns_ls_add=$UB_VARDIR/dhcp_dns.add 35 | local dns_ls_del=$UB_VARDIR/dhcp_dns.del 36 | local dns_ls_new=$UB_VARDIR/dhcp_dns.new 37 | local dns_ls_old=$UB_VARDIR/dhcp_dns.old 38 | local dhcp_ls_new=$UB_VARDIR/dhcp_lease.new 39 | 40 | if [ ! -f $UB_DHCP_CONF ] || [ ! -f $dns_ls_old ] ; then 41 | # no old files laying around 42 | touch $dns_ls_old 43 | sort $dhcp_origin > $dhcp_ls_new 44 | longconf=freshstart 45 | 46 | else 47 | # incremental at high load or full refresh about each 5 minutes 48 | dateconf=$(( $( date +%s ) - $( date -r $UB_DHCP_CONF +%s ) )) 49 | dateoldf=$(( $( date +%s ) - $( date -r $dns_ls_old +%s ) )) 50 | 51 | if [ $dateconf -gt 300 ] ; then 52 | touch $dns_ls_old 53 | sort $dhcp_origin > $dhcp_ls_new 54 | longconf=longtime 55 | 56 | elif [ $dateoldf -gt 1 ] ; then 57 | touch $dns_ls_old 58 | sort $dhcp_origin > $dhcp_ls_new 59 | longconf=increment 60 | 61 | else 62 | # odhcpd is rapidly updating leases a race condition could occur 63 | longconf=skip 64 | fi 65 | fi 66 | 67 | # Execute Logic 68 | case $longconf in 69 | freshstart) 70 | awk -v conffile=$UB_DHCP_CONF -v pipefile=$dns_ls_new \ 71 | -v domain=$dhcp_domain -v bslaac=$dhcp4_slaac6 \ 72 | -v bisolt=0 -v bconf=1 -v exclude_ipv6_ga=$exclude_ipv6_ga \ 73 | -f /usr/lib/unbound/odhcpd.awk $dhcp_ls_new 74 | 75 | cp $dns_ls_new $dns_ls_add 76 | cp $dns_ls_new $dns_ls_old 77 | cat $dns_ls_add | $UB_CONTROL_CFG local_datas 78 | rm -f $dns_ls_new $dns_ls_del $dns_ls_add $dhcp_ls_new 79 | ;; 80 | 81 | longtime) 82 | awk -v conffile=$UB_DHCP_CONF -v pipefile=$dns_ls_new \ 83 | -v domain=$dhcp_domain -v bslaac=$dhcp4_slaac6 \ 84 | -v bisolt=0 -v bconf=1 -v exclude_ipv6_ga=$exclude_ipv6_ga \ 85 | -f /usr/lib/unbound/odhcpd.awk $dhcp_ls_new 86 | 87 | awk '{ print $1 }' $dns_ls_old | sort | uniq > $dns_ls_del 88 | cp $dns_ls_new $dns_ls_add 89 | cp $dns_ls_new $dns_ls_old 90 | cat $dns_ls_del | $UB_CONTROL_CFG local_datas_remove 91 | cat $dns_ls_add | $UB_CONTROL_CFG local_datas 92 | rm -f $dns_ls_new $dns_ls_del $dns_ls_add $dhcp_ls_new 93 | ;; 94 | 95 | increment) 96 | # incremental add and prepare the old list for delete later 97 | # unbound-control can be slow so high DHCP rates cannot run a full list 98 | awk -v conffile=$UB_DHCP_CONF -v pipefile=$dns_ls_new \ 99 | -v domain=$dhcp_domain -v bslaac=$dhcp4_slaac6 \ 100 | -v bisolt=0 -v bconf=0 -v exclude_ipv6_ga=$exclude_ipv6_ga \ 101 | -f /usr/lib/unbound/odhcpd.awk $dhcp_ls_new 102 | 103 | sort $dns_ls_new $dns_ls_old $dns_ls_old | uniq -u > $dns_ls_add 104 | sort $dns_ls_new $dns_ls_old | uniq > $dns_ls_old 105 | cat $dns_ls_add | $UB_CONTROL_CFG local_datas 106 | rm -f $dns_ls_new $dns_ls_del $dns_ls_add $dhcp_ls_new 107 | ;; 108 | *) 109 | # Do nothing 110 | ;; 111 | esac 112 | fi 113 | } 114 | 115 | ############################################################################## 116 | # ASYNC EXECUTION BLOCK 117 | ############################################################################## 118 | # This runs the logic in a background subshell. 119 | # odhcpd will exit this script immediately (exit 0), while the logic 120 | # runs in the background. The flock ensures only one instance runs at a time. 121 | 122 | ( 123 | # Try to obtain a lock on file descriptor 1000. 124 | # If locked, exit this subshell silently (skip update). 125 | flock -x -n 1000 || exit 0 126 | 127 | # Run the main function 128 | odhcpd_zonedata 129 | 130 | ) 1000>$UB_ODHCPD_LOCK & 131 | 132 | # Immediate exit to unblock odhcpd 133 | exit 0 -------------------------------------------------------------------------------- /files/etc/uci-defaults/z3-firewall: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if /usr/bin/is-default-config firewall; then 4 | 5 | touch /etc/config/firewall 6 | 7 | uci -q batch <<'EOF' 8 | delete firewall.@defaults[0] 9 | add firewall defaults 10 | set firewall.@defaults[0]=defaults 11 | set firewall.@defaults[0].input='REJECT' 12 | set firewall.@defaults[0].output='ACCEPT' 13 | set firewall.@defaults[0].forward='REJECT' 14 | set firewall.@defaults[0].syn_flood='1' 15 | 16 | # LAN Zone 17 | delete firewall.@zone[0] 18 | add firewall zone 19 | set firewall.@zone[0]=zone 20 | set firewall.@zone[0].name='lan' 21 | set firewall.@zone[0].input='ACCEPT' 22 | set firewall.@zone[0].output='ACCEPT' 23 | set firewall.@zone[0].forward='ACCEPT' 24 | delete firewall.@zone[0].network 25 | add_list firewall.@zone[0].network='lan' 26 | 27 | delete firewall.@forwarding[0] 28 | add firewall forwarding 29 | set firewall.@forwarding[0]=forwarding 30 | set firewall.@forwarding[0].src='lan' 31 | set firewall.@forwarding[0].dest='wan' 32 | 33 | # IoT Zone 34 | delete firewall.@zone[1] 35 | add firewall zone 36 | set firewall.@zone[1]=zone 37 | set firewall.@zone[1].name='IoT' 38 | set firewall.@zone[1].input='REJECT' 39 | set firewall.@zone[1].output='ACCEPT' 40 | set firewall.@zone[1].forward='REJECT' 41 | delete firewall.@zone[1].network 42 | add_list firewall.@zone[1].network='IoT' 43 | 44 | delete firewall.@forwarding[1] 45 | add firewall forwarding 46 | set firewall.@forwarding[1]=forwarding 47 | set firewall.@forwarding[1].src='IoT' 48 | set firewall.@forwarding[1].dest='wan' 49 | 50 | # WAN Zone 51 | delete firewall.@zone[2] 52 | add firewall zone 53 | set firewall.@zone[2]=zone 54 | set firewall.@zone[2].name='wan' 55 | delete firewall.@zone[2].network 56 | add_list firewall.@zone[2].network='wan' 57 | add_list firewall.@zone[2].network='wan6' 58 | set firewall.@zone[2].input='REJECT' 59 | set firewall.@zone[2].output='ACCEPT' 60 | set firewall.@zone[2].forward='REJECT' 61 | set firewall.@zone[2].masq='1' 62 | set firewall.@zone[2].mtu_fix='1' 63 | 64 | delete firewall.@forwarding[2] 65 | add firewall forwarding 66 | set firewall.@forwarding[2]=forwarding 67 | set firewall.@forwarding[2].src='lan' 68 | set firewall.@forwarding[2].dest='IoT' 69 | 70 | # Tailscale Zone 71 | delete firewall.@zone[3] 72 | add firewall zone 73 | set firewall.@zone[3]=zone 74 | set firewall.@zone[3].name='tailscale' 75 | delete firewall.@zone[3].network 76 | add_list firewall.@zone[3].network='tailscale' 77 | set firewall.@zone[3].input='ACCEPT' 78 | set firewall.@zone[3].output='ACCEPT' 79 | set firewall.@zone[3].forward='REJECT' 80 | set firewall.@zone[3].masq='1' 81 | set firewall.@zone[3].mtu_fix='1' 82 | set firewall.@zone[3].masq6='1' 83 | 84 | delete firewall.@forwarding[3] 85 | add firewall forwarding 86 | set firewall.@forwarding[3].src='tailscale' 87 | set firewall.@forwarding[3].dest='lan' 88 | 89 | delete firewall.@forwarding[4] 90 | add firewall forwarding 91 | set firewall.@forwarding[4].src='tailscale' 92 | set firewall.@forwarding[4].dest='wan' 93 | 94 | delete firewall.@forwarding[5] 95 | add firewall forwarding 96 | set firewall.@forwarding[5].src='lan' 97 | set firewall.@forwarding[5].dest='tailscale' 98 | 99 | # ZeroTier Zone 100 | delete firewall.@zone[4] 101 | add firewall zone 102 | set firewall.@zone[4]=zone 103 | set firewall.@zone[4].name='zerotier' 104 | delete firewall.@zone[4].network 105 | add_list firewall.@zone[4].network='zerotier' 106 | set firewall.@zone[4].input='ACCEPT' 107 | set firewall.@zone[4].output='ACCEPT' 108 | set firewall.@zone[4].forward='ACCEPT' 109 | set firewall.@zone[4].masq='1' 110 | 111 | delete firewall.@forwarding[6] 112 | add firewall forwarding 113 | set firewall.@forwarding[6].src='zerotier' 114 | set firewall.@forwarding[6].dest='lan' 115 | 116 | delete firewall.@forwarding[7] 117 | add firewall forwarding 118 | set firewall.@forwarding[7].src='zerotier' 119 | set firewall.@forwarding[7].dest='wan' 120 | 121 | delete firewall.@forwarding[8] 122 | add firewall forwarding 123 | set firewall.@forwarding[8].src='lan' 124 | set firewall.@forwarding[8].dest='zerotier' 125 | 126 | # DNS redirection for Adblock 127 | delete firewall.@redirect[0] 128 | add firewall redirect 129 | set firewall.@redirect[0]=redirect 130 | set firewall.@redirect[0].name='Force-Adblock-Iot' 131 | set firewall.@redirect[0].src='IoT' 132 | add_list firewall.@redirect[0].proto='tcp' 133 | add_list firewall.@redirect[0].proto='udp' 134 | set firewall.@redirect[0].src_dport='53' 135 | set firewall.@redirect[0].dest_port='53' 136 | set firewall.@redirect[0].target='DNAT' 137 | set firewall.@redirect[0].enabled='0' 138 | 139 | delete firewall.@redirect[1] 140 | add firewall redirect 141 | set firewall.@redirect[1]=redirect 142 | set firewall.@redirect[1].name='Force-Adblock-Lan' 143 | set firewall.@redirect[1].src='lan' 144 | add_list firewall.@redirect[1].proto='tcp' 145 | add_list firewall.@redirect[1].proto='udp' 146 | set firewall.@redirect[1].src_dport='53' 147 | set firewall.@redirect[1].dest_port='53' 148 | set firewall.@redirect[1].target='DNAT' 149 | set firewall.@redirect[1].enabled='0' 150 | 151 | # NSS offloading 152 | delete firewall.qcanssecm 153 | set firewall.qcanssecm=include 154 | set firewall.qcanssecm.type='script' 155 | set firewall.qcanssecm.path='/etc/firewall.d/qca-nss-ecm' 156 | 157 | commit firewall 158 | EOF 159 | 160 | /usr/bin/add-wan-rules-to-firewall 161 | 162 | fi 163 | 164 | exit 0 -------------------------------------------------------------------------------- /files/usr/bin/add-wan-rules-to-firewall: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | uci -q batch <<'EOF' 4 | ### Here onwards are selective rules to open ports to the WAN side (outside) 5 | 6 | # 1. Allow SSH 7 | set firewall.allow_ssh_wan=rule 8 | set firewall.allow_ssh_wan.name='Allow-SSH-WAN' 9 | set firewall.allow_ssh_wan.src='wan' 10 | set firewall.allow_ssh_wan.dest_port='22' 11 | set firewall.allow_ssh_wan.target='ACCEPT' 12 | set firewall.allow_ssh_wan.enabled='0' 13 | uci -q delete firewall.allow_ssh_wan.proto 14 | uci add_list firewall.allow_ssh_wan.proto='tcp' 15 | 16 | # 2. Allow ttyd and btop via ttyd 17 | set firewall.allow_ttyd_management_wan=rule 18 | set firewall.allow_ttyd_management_wan.name='Allow-Ttyd-Management-WAN' 19 | set firewall.allow_ttyd_management_wan.src='wan' 20 | set firewall.allow_ttyd_management_wan.dest_port='7681' 21 | set firewall.allow_ttyd_management_wan.target='ACCEPT' 22 | set firewall.allow_ttyd_management_wan.enabled='0' 23 | uci add_list firewall.allow_ttyd_management_wan.proto='tcp' 24 | 25 | set firewall.allow_ttyd_btop_wan=rule 26 | set firewall.allow_ttyd_btop_wan.name='Allow-Ttyd-Btop-WAN' 27 | set firewall.allow_ttyd_btop_wan.src='wan' 28 | set firewall.allow_ttyd_btop_wan.dest_port='7682' 29 | set firewall.allow_ttyd_btop_wan.target='ACCEPT' 30 | set firewall.allow_ttyd_btop_wan.enabled='0' 31 | uci add_list firewall.allow_ttyd_btop_wan.proto='tcp' 32 | 33 | # 3. Allow HTTP and HTTPS Management access 34 | set firewall.allow_http_management_wan=rule 35 | set firewall.allow_http_management_wan.name='Allow-HTTP-Management-WAN' 36 | set firewall.allow_http_management_wan.src='wan' 37 | set firewall.allow_http_management_wan.dest_port='80' 38 | set firewall.allow_http_management_wan.target='ACCEPT' 39 | set firewall.allow_http_management_wan.enabled='0' 40 | uci -q delete firewall.allow_http_management_wan.proto 41 | uci add_list firewall.allow_http_management_wan.proto='tcp' 42 | 43 | set firewall.allow_https_management_wan=rule 44 | set firewall.allow_https_management_wan.name='Allow-HTTPS-Management-WAN' 45 | set firewall.allow_https_management_wan.src='wan' 46 | set firewall.allow_https_management_wan.dest_port='443' 47 | set firewall.allow_https_management_wan.target='ACCEPT' 48 | set firewall.allow_https_management_wan.enabled='0' 49 | uci -q delete firewall.allow_https_management_wan.proto 50 | uci add_list firewall.allow_https_management_wan.proto='tcp' 51 | 52 | # 4. Allow Avahi (Bonjour) 53 | set firewall.allow_avahi_wan=rule 54 | set firewall.allow_avahi_wan.name='Allow-Avahi-WAN' 55 | set firewall.allow_avahi_wan.src='wan' 56 | set firewall.allow_avahi_wan.dest_port='5353' 57 | set firewall.allow_avahi_wan.target='ACCEPT' 58 | set firewall.allow_avahi_wan.enabled='0' 59 | uci -q delete firewall.allow_avahi_wan.proto 60 | uci add_list firewall.allow_avahi_wan.proto='udp' 61 | 62 | # 5. Allow SMB 63 | set firewall.allow_smb_wan=rule 64 | set firewall.allow_smb_wan.name='Allow-SMB-WAN' 65 | set firewall.allow_smb_wan.src='wan' 66 | set firewall.allow_smb_wan.dest_port='445' 67 | set firewall.allow_smb_wan.target='ACCEPT' 68 | set firewall.allow_smb_wan.enabled='0' 69 | uci -q delete firewall.allow_smb_wan.proto 70 | uci add_list firewall.allow_smb_wan.proto='tcp' 71 | 72 | # 6. Allow Plex Media Server, discovery ports 73 | set firewall.allow_plex_http_wan=rule 74 | set firewall.allow_plex_http_wan.name='Allow-Plex-HTTP-WAN' 75 | set firewall.allow_plex_http_wan.src='wan' 76 | set firewall.allow_plex_http_wan.dest_port='32400' 77 | set firewall.allow_plex_http_wan.target='ACCEPT' 78 | set firewall.allow_plex_http_wan.enabled='0' 79 | uci -q delete firewall.allow_plex_http_wan.proto 80 | uci add_list firewall.allow_plex_http_wan.proto='tcp' 81 | 82 | set firewall.allow_plex_discovery_wan=rule 83 | set firewall.allow_plex_discovery_wan.name='Allow-Plex-Discovery-WAN' 84 | set firewall.allow_plex_discovery_wan.src='wan' 85 | set firewall.allow_plex_discovery_wan.dest_port='32410 32412 32413 32414' 86 | set firewall.allow_plex_discovery_wan.target='ACCEPT' 87 | set firewall.allow_plex_discovery_wan.enabled='0' 88 | uci -q delete firewall.allow_plex_discovery_wan.proto 89 | uci add_list firewall.allow_plex_discovery_wan.proto='udp' 90 | 91 | # 7. Allow AriaNG 92 | set firewall.allow_ariang_wan=rule 93 | set firewall.allow_ariang_wan.name='Allow-AriaNG-WAN' 94 | set firewall.allow_ariang_wan.src='wan' 95 | set firewall.allow_ariang_wan.dest_port='6800' 96 | set firewall.allow_ariang_wan.target='ACCEPT' 97 | set firewall.allow_ariang_wan.enabled='0' 98 | uci -q delete firewall.allow_ariang_wan.proto 99 | uci add_list firewall.allow_ariang_wan.proto='tcp' 100 | uci add_list firewall.allow_ariang_wan.proto='udp' 101 | 102 | set firewall.allow_ariang_bittorrent_wan=rule 103 | set firewall.allow_ariang_bittorrent_wan.name='Allow-AriaNG-BitTorrent-WAN' 104 | set firewall.allow_ariang_bittorrent_wan.src='wan' 105 | set firewall.allow_ariang_bittorrent_wan.dest_port='6881-6999' 106 | set firewall.allow_ariang_bittorrent_wan.target='ACCEPT' 107 | set firewall.allow_ariang_bittorrent_wan.enabled='0' 108 | uci -q delete firewall.allow_ariang_bittorrent_wan.proto 109 | uci add_list firewall.allow_ariang_bittorrent_wan.proto='tcp' 110 | uci add_list firewall.allow_ariang_bittorrent_wan.proto='udp' 111 | 112 | # 8. Allow MiniDLNA, discovery ports 113 | set firewall.allow_minidlna_wan=rule 114 | set firewall.allow_minidlna_wan.name='Allow-MiniDLNA-WAN' 115 | set firewall.allow_minidlna_wan.src='wan' 116 | set firewall.allow_minidlna_wan.dest_port='8200' 117 | set firewall.allow_minidlna_wan.target='ACCEPT' 118 | set firewall.allow_minidlna_wan.enabled='0' 119 | uci -q delete firewall.allow_minidlna_wan.proto 120 | uci add_list firewall.allow_minidlna_wan.proto='tcp' 121 | 122 | set firewall.allow_minidlna_discovery_wan=rule 123 | set firewall.allow_minidlna_discovery_wan.name='Allow-MiniDLNA-Discovery-WAN' 124 | set firewall.allow_minidlna_discovery_wan.src='wan' 125 | set firewall.allow_minidlna_discovery_wan.dest_port='1900' 126 | set firewall.allow_minidlna_discovery_wan.target='ACCEPT' 127 | set firewall.allow_minidlna_discovery_wan.enabled='0' 128 | uci -q delete firewall.allow_minidlna_discovery_wan.proto 129 | uci add_list firewall.allow_minidlna_discovery_wan.proto='udp' 130 | 131 | commit firewall 132 | EOF -------------------------------------------------------------------------------- /files/usr/bin/configure-interface: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # ANSI Colors 4 | GREEN="\033[0;32m" 5 | RED="\033[0;31m" 6 | YELLOW="\033[1;33m" 7 | NC="\033[0m" # No Color 8 | 9 | show_menu() { 10 | clear 11 | printf "%b=========================================%b\n" "${YELLOW}" "${NC}" 12 | printf " Interface Mode Configuration \n" 13 | printf "%b=========================================%b\n" "${YELLOW}" "${NC}" 14 | printf "\n" 15 | printf "1. Router Mode (Factory Default)\n" 16 | printf " Standard operation: NAT, DHCP Server, DNS (Unbound).\n" 17 | printf " LAN IP: 10.0.0.1 (Static)\n" 18 | printf "\n" 19 | printf "2. Managed Switch / Dumb AP Mode\n" 20 | printf " Bridges WAN to LAN. Gets IP from upstream router.\n" 21 | printf " Disables: NAT, Local DHCP, Unbound, Odhcpd, BanIP.\n" 22 | printf " Use this to extend an existing network.\n" 23 | printf "\n" 24 | printf "0. Exit\n" 25 | printf "\n" 26 | } 27 | 28 | set_router_mode() { 29 | printf "\n%bReverting to Router Mode...%b\n" "${YELLOW}" "${NC}" 30 | printf "This will restore Static IP 10.0.0.1 and enable NAT/DHCP/DNS.\n" 31 | 32 | printf "Are you sure? (y/N): " 33 | read -r confirm 34 | if [ "$confirm" != "y" ]; then return; fi 35 | 36 | # 1. Remove WAN from Bridge 37 | # shellcheck disable=SC2046 38 | BR_SECTION=$(uci show network | grep "name='br-lan'" | cut -d'.' -f2) 39 | if [ -n "$BR_SECTION" ]; then 40 | # Quote variable to prevent word splitting 41 | uci del_list "network.${BR_SECTION}.ports=wan" 42 | fi 43 | 44 | # 2. Restore LAN Static IP 45 | uci set network.lan.proto='static' 46 | uci set network.lan.ipaddr='10.0.0.1' 47 | uci set network.lan.netmask='255.255.255.0' 48 | 49 | # 3. Restore WAN Interface 50 | uci set network.wan.proto='dhcp' 51 | uci set network.wan6.proto='dhcpv6' 52 | 53 | # 4. Enable DHCP Server 54 | uci delete dhcp.lan.ignore 55 | 56 | uci commit network 57 | uci commit dhcp 58 | 59 | echo "Re-enabling Unbound and Odhcpd..." 60 | /etc/init.d/unbound enable 61 | /etc/init.d/unbound start 62 | /etc/init.d/odhcpd enable 63 | /etc/init.d/odhcpd start 64 | 65 | echo "Applying changes... IP will be 10.0.0.1" 66 | /etc/init.d/network restart 67 | 68 | # Check firewall status and warn if disabled 69 | if ! /etc/init.d/firewall enabled; then 70 | printf "\n%bWARNING: The firewall is currently disabled.%b\n" "${RED}" "${NC}" 71 | printf "You should enable it manually if desired (e.g., using 'configure-firewall' or '/etc/init.d/firewall enable').\n" 72 | fi 73 | 74 | exit 0 75 | } 76 | 77 | set_ap_mode() { 78 | printf "\n%bConverting to Managed Switch (Dumb AP)...%b\n" "${YELLOW}" "${NC}" 79 | printf "This will:\n" 80 | printf " 1. Bridge WAN port to LAN (br-lan).\n" 81 | printf " 2. Disable WAN routing.\n" 82 | printf " 3. Set LAN to DHCP Client (gets IP from upstream).\n" 83 | printf " 4. Disable local DHCP server, DNS (Unbound), odhcpd and BanIP.\n" 84 | printf " 5. Disable Firewall completely (sets policies to ACCEPT).\n" 85 | printf " 6. Disable Watchcat.\n" 86 | printf "%bWARNING: You will lose connection immediately!%b\n" "${RED}" "${NC}" 87 | printf "%bWARNING: Firewall will be disabled to avoid interference. Configure manually if needed.%b\n" "${YELLOW}" "${NC}" 88 | 89 | printf "Are you sure? (y/N): " 90 | read -r confirm 91 | if [ "$confirm" != "y" ]; then return; fi 92 | 93 | # 1. Add WAN to Bridge 94 | # shellcheck disable=SC2046 95 | BR_SECTION=$(uci show network | grep "name='br-lan'" | cut -d'.' -f2) 96 | if [ -n "$BR_SECTION" ]; then 97 | # Quote variable to prevent word splitting 98 | uci add_list "network.${BR_SECTION}.ports=wan" 99 | else 100 | printf "%bError: Could not find br-lan device.%b\n" "${RED}" "${NC}" 101 | return 102 | fi 103 | 104 | # 2. Configure LAN as DHCP Client 105 | uci set network.lan.proto='dhcp' 106 | 107 | # Remove static IP settings to prevent conflicts 108 | uci delete network.lan.ipaddr 109 | uci delete network.lan.netmask 110 | uci delete network.lan.gateway 111 | uci delete network.lan.dns 112 | 113 | # 3. Disable DHCP Server on LAN 114 | uci set dhcp.lan.ignore='1' 115 | 116 | # 4. Disable WAN Interface logical config, and don't bring it on boot. 117 | uci set network.wan.proto='none' 118 | uci set network.wan.disabled='1' 119 | uci set network.wan.auto='0' 120 | uci set network.wan6.proto='none' 121 | uci set network.wan6.disabled='1' 122 | uci set network.wan6.auto='0' 123 | 124 | uci commit network 125 | uci commit dhcp 126 | 127 | echo "Disabling Watchcat..." 128 | /etc/init.d/watchcat stop 129 | /etc/init.d/watchcat disable 130 | 131 | echo "Disabling Unbound and Odhcpd (Not needed in AP mode)..." 132 | /etc/init.d/unbound stop 133 | /etc/init.d/unbound disable 134 | /etc/init.d/odhcpd stop 135 | /etc/init.d/odhcpd disable 136 | 137 | echo "Disabling BanIP completely..." 138 | /etc/init.d/banip stop 139 | /etc/init.d/banip disable 140 | 141 | echo "Disabling Firewall completely..." 142 | /etc/init.d/firewall stop 143 | /etc/init.d/firewall disable 144 | 145 | # Flush tables and set default policy to ACCEPT (Replicating configure-firewall logic) 146 | if command -v nft >/dev/null; then 147 | echo "Flushing firewall tables (nft)..." 148 | nft flush ruleset 149 | nft add table inet filter 150 | nft add chain inet filter input "{ type filter hook input priority 0 ; policy accept ; }" 151 | nft add chain inet filter forward "{ type filter hook forward priority 0 ; policy accept ; }" 152 | nft add chain inet filter output "{ type filter hook output priority 0 ; policy accept ; }" 153 | else 154 | echo "Flushing firewall tables (iptables)..." 155 | iptables -P INPUT ACCEPT 156 | iptables -P OUTPUT ACCEPT 157 | iptables -P FORWARD ACCEPT 158 | iptables -F 159 | iptables -t nat -F 160 | iptables -t mangle -F 161 | fi 162 | 163 | echo "Applying network changes... Goodbye!" 164 | /etc/init.d/network restart 165 | exit 0 166 | } 167 | 168 | while true; do 169 | show_menu 170 | printf "Select an option [0-2]: " 171 | read -r choice 172 | case "$choice" in 173 | 1) set_router_mode ;; 174 | 2) set_ap_mode ;; 175 | 0) exit 0 ;; 176 | *) printf "%bInvalid option.%b\n" "${RED}" "${NC}"; sleep 1 ;; 177 | esac 178 | done -------------------------------------------------------------------------------- /packages/luci-app-pairdrop/root/www/luci-static/resources/view/pairdrop.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 'require view'; 3 | 'require form'; 4 | 'require uci'; 5 | 'require fs'; 6 | 'require ui'; 7 | 'require rpc'; 8 | 9 | return view.extend({ 10 | // Helper to check installation status 11 | checkInstalled: function() { 12 | return fs.stat('/mnt/sda1/.webapps/pairdrop.sqfs').then(function(res) { 13 | return true; // File exists 14 | }).catch(function() { 15 | return false; // File does not exist or permission denied 16 | }); 17 | }, 18 | 19 | // Helper to check running status (simplified check for mount) 20 | checkRunning: function() { 21 | return fs.exec('/bin/mount').then(function(res) { 22 | if (!res || res.code !== 0) return false; 23 | return res.stdout.indexOf('/www/pairdrop') !== -1; 24 | }).catch(function() { 25 | return false; // Command failed or permission denied 26 | }); 27 | }, 28 | 29 | handleServiceAction: function(action) { 30 | var init = '/etc/init.d/pairdrop'; 31 | var p; 32 | 33 | ui.showModal(_('Processing'), [ 34 | E('p', { 'class': 'spinning' }, _('Executing service action: ' + action + '...')), 35 | E('p', _('This may take a while if downloading files.')) 36 | ]); 37 | 38 | if (action === 'reinstall') { 39 | p = fs.exec(init, ['reinstall']); 40 | } else if (action === 'uninstall') { 41 | p = fs.exec(init, ['uninstall']); 42 | } else { 43 | p = fs.exec(init, [action]); 44 | } 45 | 46 | return p.then(function(res) { 47 | ui.hideModal(); 48 | if (res && res.code !== 0) { 49 | ui.addNotification(null, E('p', _('Action failed') + ': ' + (res.stderr || res.stdout || _('Unknown Error'))), 'error'); 50 | } else { 51 | ui.addNotification(null, E('p', _('Action completed successfully.')), 'success'); 52 | // Refresh page to update button states 53 | window.location.reload(); 54 | } 55 | }).catch(function(e) { 56 | ui.hideModal(); 57 | ui.addNotification(null, E('p', _('Error') + ': ' + e.message), 'error'); 58 | }); 59 | }, 60 | 61 | load: function() { 62 | return Promise.all([ 63 | this.checkInstalled(), 64 | this.checkRunning(), 65 | uci.load('pairdrop').catch(function() { return null; }) 66 | ]); 67 | }, 68 | 69 | render: function(data) { 70 | var isInstalled = data[0]; 71 | var isRunning = data[1]; 72 | 73 | var m, s, o; 74 | 75 | m = new form.Map('pairdrop', _('PairDrop Settings'), _('Configure the PairDrop file sharing service.')); 76 | 77 | s = m.section(form.NamedSection, 'main', 'pairdrop', _('Configuration')); 78 | s.anonymous = true; 79 | 80 | // Enabled Toggle 81 | o = s.option(form.Flag, 'enabled', _('Enable Service')); 82 | o.rmempty = false; 83 | 84 | // Port 85 | o = s.option(form.Value, 'port', _('Port')); 86 | o.datatype = 'port'; 87 | o.default = '3000'; 88 | 89 | // Versions 90 | o = s.option(form.Value, 'node_version', _('Node.js Version')); 91 | o.description = _('Specify Node.js version (e.g., v20.10.0). Leave empty for default.'); 92 | o.placeholder = 'v20.10.0'; 93 | 94 | o = s.option(form.Value, 'pairdrop_version', _('PairDrop Version')); 95 | o.description = _('Specify PairDrop version tag (e.g., v1.10.7). Leave empty for latest.'); 96 | 97 | // --- Actions Section (Merged into main) --- 98 | o = s.option(form.DummyValue, '_divider'); 99 | o.rawhtml = true; 100 | o.default = '

' + _('Service Control') + '

'; 101 | 102 | // Status Display 103 | var statusText = isInstalled ? _('Installed') : _('Not Installed'); 104 | var statusColor = isInstalled ? 'green' : 'red'; 105 | if (isInstalled && isRunning) { 106 | statusText += ' (' + _('Running') + ')'; 107 | } else if (isInstalled) { 108 | statusText += ' (' + _('Stopped') + ')'; 109 | } 110 | 111 | o = s.option(form.DummyValue, '_status', _('Status')); 112 | o.rawhtml = true; 113 | o.default = '' + statusText + ''; 114 | 115 | // Action Buttons 116 | o = s.option(form.DummyValue, '_actions', _('Actions')); 117 | o.rawhtml = true; 118 | o.render = L.bind(function() { 119 | var buttons = []; 120 | 121 | // Install Button (Only if not installed) 122 | if (!isInstalled) { 123 | buttons.push(E('button', { 124 | 'class': 'btn cbi-button cbi-button-action', 125 | 'click': ui.createHandlerFn(this, 'handleServiceAction', 'install') 126 | }, _('Install'))); 127 | } 128 | 129 | // Start Button (Only if installed and not running) 130 | if (isInstalled && !isRunning) { 131 | buttons.push(E('button', { 132 | 'class': 'btn cbi-button cbi-button-apply', 133 | 'click': ui.createHandlerFn(this, 'handleServiceAction', 'start') 134 | }, _('Start'))); 135 | } 136 | 137 | // Stop/Restart (Only if running) 138 | if (isRunning) { 139 | buttons.push(E('button', { 140 | 'class': 'btn cbi-button cbi-button-reset', 141 | 'click': ui.createHandlerFn(this, 'handleServiceAction', 'stop') 142 | }, _('Stop'))); 143 | 144 | buttons.push(E('button', { 145 | 'class': 'btn cbi-button cbi-button-neutral', 146 | 'click': ui.createHandlerFn(this, 'handleServiceAction', 'restart') 147 | }, _('Restart'))); 148 | } 149 | 150 | // Reinstall (Only if installed) 151 | if (isInstalled) { 152 | buttons.push(E('button', { 153 | 'class': 'btn cbi-button cbi-button-negative', 154 | 'style': 'margin-left: 10px;', 155 | 'click': ui.createHandlerFn(this, 'handleServiceAction', 'reinstall') 156 | }, _('Force Reinstall'))); 157 | 158 | // Uninstall (Only if installed) 159 | buttons.push(E('button', { 160 | 'class': 'btn cbi-button cbi-button-negative', 161 | 'click': ui.createHandlerFn(this, 'handleServiceAction', 'uninstall') 162 | }, _('Uninstall'))); 163 | } 164 | 165 | return E('div', { 'class': 'cbi-value-field' }, buttons); 166 | }, this); 167 | 168 | return m.render(); 169 | } 170 | }); -------------------------------------------------------------------------------- /files/usr/bin/configure-guest: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # ANSI Colors 4 | GREEN="\033[0;32m" 5 | RED="\033[0;31m" 6 | YELLOW="\033[1;33m" 7 | NC="\033[0m" # No Color 8 | 9 | show_menu() { 10 | clear 11 | printf "%b=========================================%b\n" "${YELLOW}" "${NC}" 12 | printf " Guest Network Configuration \n" 13 | printf "%b=========================================%b\n" "${YELLOW}" "${NC}" 14 | printf "\n" 15 | printf "1. Remove Guest Network\n" 16 | printf " Removes the 'guest' interface and SSID.\n" 17 | printf "\n" 18 | printf "2. Add/Restore Guest Network\n" 19 | printf " Adds 'guest' interface and 'OpenWrt_Guest' SSID with no GUESTPASSWORD as password.\n" 20 | printf " Clients isolated, no AdBlock but can be enabled via Firewall.\n" 21 | printf "\n" 22 | printf "0. Exit\n" 23 | printf "\n" 24 | } 25 | 26 | remove_guest_network() 27 | { 28 | printf "\n%bRemoving Guest network...%b\n" "${YELLOW}" "${NC}" 29 | 30 | printf "This will remove the 'guest' interface and all SSID attached to that interface.\n" 31 | 32 | printf "Are you sure? (y/N): " 33 | read -r confirm 34 | if [ "$confirm" != "y" ]; then return; fi 35 | 36 | # 1. Remove SSID from guest interface 37 | # shellcheck disable=SC2046 38 | BR_SECTION=$(uci show network | grep "network='guest'" | cut -d'.' -f2) 39 | if [ -n "$BR_SECTION" ]; then 40 | # Quote variable to prevent word splitting 41 | uci del_list "network.${BR_SECTION}.ports=wan" 42 | fi 43 | } 44 | 45 | set_router_mode() { 46 | 47 | printf "This will restore Static IP 10.0.0.1 and enable NAT/DHCP/DNS.\n" 48 | 49 | printf "Are you sure? (y/N): " 50 | read -r confirm 51 | if [ "$confirm" != "y" ]; then return; fi 52 | 53 | # 1. Remove WAN from Bridge 54 | # shellcheck disable=SC2046 55 | BR_SECTION=$(uci show network | grep "name='br-lan'" | cut -d'.' -f2) 56 | if [ -n "$BR_SECTION" ]; then 57 | # Quote variable to prevent word splitting 58 | uci del_list "network.${BR_SECTION}.ports=wan" 59 | fi 60 | 61 | # 2. Restore LAN Static IP 62 | uci set network.lan.proto='static' 63 | uci set network.lan.ipaddr='10.0.0.1' 64 | uci set network.lan.netmask='255.255.255.0' 65 | 66 | # 3. Restore WAN Interface 67 | uci set network.wan.proto='dhcp' 68 | uci set network.wan6.proto='dhcpv6' 69 | 70 | # 4. Enable DHCP Server 71 | uci delete dhcp.lan.ignore 72 | 73 | uci commit network 74 | uci commit dhcp 75 | 76 | echo "Re-enabling Unbound and Odhcpd..." 77 | /etc/init.d/unbound enable 78 | /etc/init.d/unbound start 79 | /etc/init.d/odhcpd enable 80 | /etc/init.d/odhcpd start 81 | 82 | echo "Applying changes... IP will be 10.0.0.1" 83 | /etc/init.d/network restart 84 | 85 | # Check firewall status and warn if disabled 86 | if ! /etc/init.d/firewall enabled; then 87 | printf "\n%bWARNING: The firewall is currently disabled.%b\n" "${RED}" "${NC}" 88 | printf "You should enable it manually if desired (e.g., using 'configure-firewall' or '/etc/init.d/firewall enable').\n" 89 | fi 90 | 91 | exit 0 92 | } 93 | 94 | set_ap_mode() { 95 | printf "\n%bConverting to Managed Switch (Dumb AP)...%b\n" "${YELLOW}" "${NC}" 96 | printf "This will:\n" 97 | printf " 1. Bridge WAN port to LAN (br-lan).\n" 98 | printf " 2. Disable WAN routing.\n" 99 | printf " 3. Set LAN to DHCP Client (gets IP from upstream).\n" 100 | printf " 4. Disable local DHCP server, DNS (Unbound), odhcpd and BanIP.\n" 101 | printf " 5. Disable Firewall completely (sets policies to ACCEPT).\n" 102 | printf " 6. Disable Watchcat.\n" 103 | printf "%bWARNING: You will lose connection immediately!%b\n" "${RED}" "${NC}" 104 | printf "%bWARNING: Firewall will be disabled to avoid interference. Configure manually if needed.%b\n" "${YELLOW}" "${NC}" 105 | 106 | printf "Are you sure? (y/N): " 107 | read -r confirm 108 | if [ "$confirm" != "y" ]; then return; fi 109 | 110 | # 1. Add WAN to Bridge 111 | # shellcheck disable=SC2046 112 | BR_SECTION=$(uci show network | grep "name='br-lan'" | cut -d'.' -f2) 113 | if [ -n "$BR_SECTION" ]; then 114 | # Quote variable to prevent word splitting 115 | uci add_list "network.${BR_SECTION}.ports=wan" 116 | else 117 | printf "%bError: Could not find br-lan device.%b\n" "${RED}" "${NC}" 118 | return 119 | fi 120 | 121 | # 2. Configure LAN as DHCP Client 122 | uci set network.lan.proto='dhcp' 123 | 124 | # Remove static IP settings to prevent conflicts 125 | uci delete network.lan.ipaddr 126 | uci delete network.lan.netmask 127 | uci delete network.lan.gateway 128 | uci delete network.lan.dns 129 | 130 | # 3. Disable DHCP Server on LAN 131 | uci set dhcp.lan.ignore='1' 132 | 133 | # 4. Disable WAN Interface logical config, and don't bring it on boot. 134 | uci set network.wan.proto='none' 135 | uci set network.wan.disabled='1' 136 | uci set network.wan.auto='0' 137 | uci set network.wan6.proto='none' 138 | uci set network.wan6.disabled='1' 139 | uci set network.wan6.auto='0' 140 | 141 | uci commit network 142 | uci commit dhcp 143 | 144 | echo "Disabling Watchcat..." 145 | /etc/init.d/watchcat stop 146 | /etc/init.d/watchcat disable 147 | 148 | echo "Disabling Unbound and Odhcpd (Not needed in AP mode)..." 149 | /etc/init.d/unbound stop 150 | /etc/init.d/unbound disable 151 | /etc/init.d/odhcpd stop 152 | /etc/init.d/odhcpd disable 153 | 154 | echo "Disabling BanIP completely..." 155 | /etc/init.d/banip stop 156 | /etc/init.d/banip disable 157 | 158 | echo "Disabling Firewall completely..." 159 | /etc/init.d/firewall stop 160 | /etc/init.d/firewall disable 161 | 162 | # Flush tables and set default policy to ACCEPT (Replicating configure-firewall logic) 163 | if command -v nft >/dev/null; then 164 | echo "Flushing firewall tables (nft)..." 165 | nft flush ruleset 166 | nft add table inet filter 167 | nft add chain inet filter input "{ type filter hook input priority 0 ; policy accept ; }" 168 | nft add chain inet filter forward "{ type filter hook forward priority 0 ; policy accept ; }" 169 | nft add chain inet filter output "{ type filter hook output priority 0 ; policy accept ; }" 170 | else 171 | echo "Flushing firewall tables (iptables)..." 172 | iptables -P INPUT ACCEPT 173 | iptables -P OUTPUT ACCEPT 174 | iptables -P FORWARD ACCEPT 175 | iptables -F 176 | iptables -t nat -F 177 | iptables -t mangle -F 178 | fi 179 | 180 | echo "Applying network changes... Goodbye!" 181 | /etc/init.d/network restart 182 | exit 0 183 | } 184 | 185 | while true; do 186 | show_menu 187 | printf "Select an option [0-2]: " 188 | read -r choice 189 | case "$choice" in 190 | 1) set_router_mode ;; 191 | 2) set_ap_mode ;; 192 | 0) exit 0 ;; 193 | *) printf "%bInvalid option.%b\n" "${RED}" "${NC}"; sleep 1 ;; 194 | esac 195 | done -------------------------------------------------------------------------------- /packages/luci-app-tailscale/root/www/luci-static/resources/view/tailscale.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 'require view'; 3 | 'require fs'; 4 | 'require ui'; 5 | 'require form'; 6 | 'require network'; 7 | 8 | return view.extend({ 9 | /** 10 | * Diagnostic wrapper to catch errors and pass them to render() 11 | * instead of crashing the view with a red box. 12 | */ 13 | safeExec: function(promise, name) { 14 | return promise.then(function(res) { 15 | return { result: res, error: null, name: name }; 16 | }).catch(function(e) { 17 | console.error('Tailscale View Error [' + name + ']:', e); 18 | var msg = e.message || e; 19 | if (typeof msg === 'string' && msg.includes('Permission denied')) { 20 | msg += ' (Check /usr/share/rpcd/acl.d/ permissions and restart rpcd)'; 21 | } 22 | return { result: null, error: msg, name: name }; 23 | }); 24 | }, 25 | 26 | load: function() { 27 | return Promise.all([ 28 | this.safeExec(fs.exec('/usr/sbin/tailscale', ['status']), 'Tailscale Status'), 29 | this.safeExec(fs.exec('/usr/sbin/tailscale', ['ip', '-4']), 'Tailscale IP'), 30 | this.safeExec(network.getWANNetworks(), 'Detect WAN') 31 | ]); 32 | }, 33 | 34 | render: function(data) { 35 | // Extract wrapped results 36 | var statusRes = data[0]; 37 | var ipRes = data[1]; 38 | var wanRes = data[2]; 39 | 40 | // --- Process Data --- 41 | var statusOutput = (statusRes.result && statusRes.result.stdout) 42 | ? statusRes.result.stdout 43 | : (statusRes.error ? 'Error: ' + statusRes.error : 'Tailscale is stopped or not installed.'); 44 | 45 | var ipOutput = (ipRes.result && ipRes.result.stdout) ? ipRes.result.stdout.trim() : '-'; 46 | 47 | var wanNetworks = (wanRes.result) ? wanRes.result : []; 48 | var wanDevice = 'eth0'; 49 | if (wanNetworks.length > 0) { 50 | var dev = wanNetworks[0].getDevice(); 51 | if (dev) wanDevice = dev.getName(); 52 | } 53 | 54 | var m, s, o; 55 | 56 | m = new form.Map('tailscale', _('Tailscale'), _('Configure the Tailscale coordination server connection.')); 57 | 58 | /* * TAB: Status 59 | */ 60 | s = m.section(form.NamedSection, '_status', 'status', _('Status')); 61 | s.anonymous = true; 62 | s.render = function () { 63 | // Check if we need to authenticate 64 | var authLink = null; 65 | var authRegex = /(https:\/\/login.tailscale.com\/a\/[a-zA-Z0-9]+)/; 66 | var match = statusOutput.match(authRegex); 67 | if (match) { 68 | authLink = match[1]; 69 | } 70 | 71 | return E('div', { 'class': 'cbi-section' }, [ 72 | E('div', { 'class': 'cbi-value' }, [ 73 | E('label', { 'class': 'cbi-value-title' }, _('Tailscale IP')), 74 | E('div', { 'class': 'cbi-value-field' }, ipOutput) 75 | ]), 76 | E('div', { 'class': 'cbi-value' }, [ 77 | E('label', { 'class': 'cbi-value-title' }, _('Status')), 78 | E('div', { 'class': 'cbi-value-field' }, [ 79 | E('pre', {}, statusOutput) 80 | ]) 81 | ]), 82 | authLink ? E('div', { 'class': 'cbi-value' }, [ 83 | E('label', { 'class': 'cbi-value-title' }, _('Auth Required')), 84 | E('div', { 'class': 'cbi-value-field' }, [ 85 | E('a', { 'class': 'btn cbi-button cbi-button-apply', 'href': authLink, 'target': '_blank' }, _('Authenticate Device')) 86 | ]) 87 | ]) : '' 88 | ]); 89 | }; 90 | 91 | /* * TAB: General Settings 92 | */ 93 | s = m.section(form.NamedSection, 'settings', 'settings', _('Settings')); 94 | s.addremove = false; 95 | s.tab('general', _('General Settings')); 96 | s.tab('performance', _('Performance (Kernel 6.6+)')); 97 | s.tab('diagnostics', _('Diagnostics')); 98 | 99 | // -- General Tab -- 100 | o = s.taboption('general', form.Flag, 'enable', _('Enable'), _('Enable the Tailscale daemon.')); 101 | o.rmempty = false; 102 | 103 | o = s.taboption('general', form.Value, 'port', _('Port'), _('UDP port to listen on. Default: 41641')); 104 | o.datatype = 'port'; 105 | o.placeholder = '41641'; 106 | o.rmempty = false; 107 | 108 | o = s.taboption('general', form.ListValue, 'fw_mode', _('Firewall Mode'), _('Firewall configuration mode. OpenWrt 22.03+ usually requires nftables.')); 109 | o.value('nftables', 'nftables'); 110 | o.value('iptables', 'iptables'); 111 | o.default = 'nftables'; 112 | 113 | o = s.taboption('general', form.Value, 'state_file', _('State File'), _('Location of the Tailscale state file.')); 114 | o.default = '/etc/tailscale/tailscaled.state'; 115 | 116 | o = s.taboption('general', form.Flag, 'log_stderr', _('Log to Stderr')); 117 | o.default = '1'; 118 | 119 | // -- Performance Tab -- 120 | o = s.taboption('performance', form.DummyValue, '_perf_info'); 121 | o.rawhtml = true; 122 | o.default = '
' + 123 | _('Optimize throughput on OpenWrt 24.10+ (Kernel 6.6). These settings enable UDP Generic Receive Offload (GRO) on your WAN interface.') + 124 | '
' + _('Detected WAN Device:') + ' ' + wanDevice + '
'; 125 | 126 | o = s.taboption('performance', form.Flag, 'udp_gro_enable', _('Enable UDP GRO Forwarding'), _('Sets rx-udp-gro-forwarding on and rx-gro-list off.')); 127 | o.write = function(section_id, value) { 128 | if (value == '1') { 129 | return Promise.all([ 130 | fs.exec('/usr/sbin/ethtool', ['-K', wanDevice, 'rx-gro-list', 'off']), 131 | fs.exec('/usr/sbin/ethtool', ['-K', wanDevice, 'rx-udp-gro-forwarding', 'on']) 132 | ]); 133 | } else { 134 | return Promise.all([ 135 | fs.exec('/usr/sbin/ethtool', ['-K', wanDevice, 'rx-gro-list', 'on']), 136 | fs.exec('/usr/sbin/ethtool', ['-K', wanDevice, 'rx-udp-gro-forwarding', 'off']) 137 | ]); 138 | } 139 | }; 140 | 141 | // -- Diagnostics Tab -- 142 | o = s.taboption('diagnostics', form.DummyValue, '_diag_log'); 143 | o.rawhtml = true; 144 | 145 | var diagHtml = '
Diagnostic Log:
    '; 146 | data.forEach(function(d) { 147 | var status = d.error ? 'FAILED' : 'OK'; 148 | var detail = d.error ? d.error : 'Success'; 149 | diagHtml += '
  • ' + d.name + ': ' + status + ' (' + detail + ')
  • '; 150 | }); 151 | diagHtml += '
'; 152 | o.default = diagHtml; 153 | 154 | 155 | return m.render(); 156 | }, 157 | 158 | handleSaveApply: function(ev, mode) { 159 | return this.super('handleSaveApply', arguments).then(function() { 160 | return fs.exec('/etc/init.d/tailscale', ['restart']); 161 | }); 162 | } 163 | }); -------------------------------------------------------------------------------- /files/etc/uci-defaults/z2-luci_statistics: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if /usr/bin/is-default-config luci_statistics; then 4 | 5 | touch /etc/config/luci_statistics 6 | 7 | uci -q batch <<'EOF' 8 | delete luci_statistics.collectd 9 | set luci_statistics.collectd=statistics 10 | set luci_statistics.collectd.BaseDir='/var/run/collectd' 11 | set luci_statistics.collectd.PIDFile='/var/run/collectd.pid' 12 | set luci_statistics.collectd.PluginDir='/usr/lib/collectd' 13 | set luci_statistics.collectd.TypesDB='/usr/share/collectd/types.db' 14 | set luci_statistics.collectd.Interval='30' 15 | set luci_statistics.collectd.ReadThreads='2' 16 | 17 | delete luci_statistics.rrdtool 18 | set luci_statistics.rrdtool=statistics 19 | set luci_statistics.rrdtool.default_timespan='1hour' 20 | set luci_statistics.rrdtool.image_width='600' 21 | set luci_statistics.rrdtool.image_height='150' 22 | set luci_statistics.rrdtool.image_path='/tmp/rrdimg' 23 | 24 | delete luci_statistics.collectd_rrdtool 25 | set luci_statistics.collectd_rrdtool=statistics 26 | set luci_statistics.collectd_rrdtool.enable='1' 27 | set luci_statistics.collectd_rrdtool.DataDir='/tmp/rrd' 28 | set luci_statistics.collectd_rrdtool.CreateFiles='1' 29 | set luci_statistics.collectd_rrdtool.CacheTimeout='120' 30 | set luci_statistics.collectd_rrdtool.CacheFlush='900' 31 | set luci_statistics.collectd_rrdtool.WritesPerSecond='50' 32 | set luci_statistics.collectd_rrdtool.RRARows='288' 33 | set luci_statistics.collectd_rrdtool.RRASingle='1' 34 | # Use space-separated values inside single quotes for UCI list 35 | add_list luci_statistics.collectd_rrdtool.RRATimespans='1hour' 36 | add_list luci_statistics.collectd_rrdtool.RRATimespans='1day' 37 | add_list luci_statistics.collectd_rrdtool.RRATimespans='1week' 38 | add_list luci_statistics.collectd_rrdtool.RRATimespans='1month' 39 | add_list luci_statistics.collectd_rrdtool.RRATimespans='1year' 40 | set luci_statistics.collectd_rrdtool.backup='0' 41 | 42 | delete luci_statistics.collectd_csv 43 | set luci_statistics.collectd_csv=statistics 44 | set luci_statistics.collectd_csv.enable='0' 45 | set luci_statistics.collectd_csv.StoreRates='0' 46 | set luci_statistics.collectd_csv.DataDir='/tmp' 47 | 48 | delete luci_statistics.collectd_email 49 | set luci_statistics.collectd_email=statistics 50 | set luci_statistics.collectd_email.enable='0' 51 | 52 | delete luci_statistics.collectd_logfile 53 | set luci_statistics.collectd_logfile=statistics 54 | set luci_statistics.collectd_logfile.enable='0' 55 | 56 | delete luci_statistics.collectd_network 57 | set luci_statistics.collectd_network=statistics 58 | set luci_statistics.collectd_network.enable='0' 59 | 60 | delete luci_statistics.collectd_syslog 61 | set luci_statistics.collectd_syslog=statistics 62 | set luci_statistics.collectd_syslog.enable='0' 63 | 64 | delete luci_statistics.collectd_unixsock 65 | set luci_statistics.collectd_unixsock=statistics 66 | set luci_statistics.collectd_unixsock.enable='0' 67 | 68 | delete luci_statistics.collectd_apcups 69 | set luci_statistics.collectd_apcups=statistics 70 | set luci_statistics.collectd_apcups.enable='0' 71 | 72 | delete luci_statistics.collectd_chrony 73 | set luci_statistics.collectd_chrony=statistics 74 | set luci_statistics.collectd_chrony.enable='0' 75 | 76 | delete luci_statistics.collectd_conntrack 77 | set luci_statistics.collectd_conntrack=statistics 78 | set luci_statistics.collectd_conntrack.enable='0' 79 | 80 | delete luci_statistics.collectd_contextswitch 81 | set luci_statistics.collectd_contextswitch=statistics 82 | set luci_statistics.collectd_contextswitch.enable='0' 83 | 84 | delete luci_statistics.collectd_cpu 85 | set luci_statistics.collectd_cpu=statistics 86 | set luci_statistics.collectd_cpu.enable='1' 87 | set luci_statistics.collectd_cpu.ReportByCpu='1' 88 | set luci_statistics.collectd_cpu.ReportByState='1' 89 | set luci_statistics.collectd_cpu.ShowIdle='0' 90 | set luci_statistics.collectd_cpu.ValuesPercentage='1' 91 | 92 | delete luci_statistics.collectd_cpufreq 93 | set luci_statistics.collectd_cpufreq=statistics 94 | set luci_statistics.collectd_cpufreq.enable='0' 95 | 96 | delete luci_statistics.collectd_curl 97 | set luci_statistics.collectd_curl=statistics 98 | set luci_statistics.collectd_curl.enable='0' 99 | 100 | delete luci_statistics.collectd_df 101 | set luci_statistics.collectd_df=statistics 102 | set luci_statistics.collectd_df.enable='0' 103 | 104 | delete luci_statistics.collectd_dhcpleases 105 | set luci_statistics.collectd_dhcpleases=statistics 106 | set luci_statistics.collectd_dhcpleases.enable='0' 107 | 108 | delete luci_statistics.collectd_disk 109 | set luci_statistics.collectd_disk=statistics 110 | set luci_statistics.collectd_disk.enable='0' 111 | 112 | delete luci_statistics.collectd_dns 113 | set luci_statistics.collectd_dns=statistics 114 | set luci_statistics.collectd_dns.enable='0' 115 | 116 | delete luci_statistics.collectd_entropy 117 | set luci_statistics.collectd_entropy=statistics 118 | set luci_statistics.collectd_entropy.enable='0' 119 | 120 | delete luci_statistics.collectd_exec 121 | set luci_statistics.collectd_exec=statistics 122 | set luci_statistics.collectd_exec.enable='0' 123 | 124 | delete luci_statistics.collectd_interface 125 | set luci_statistics.collectd_interface=statistics 126 | set luci_statistics.collectd_interface.enable='1' 127 | set luci_statistics.collectd_interface.Interfaces='br-lan' 128 | set luci_statistics.collectd_interface.IgnoreSelected='0' 129 | 130 | delete luci_statistics.collectd_ipstatistics 131 | set luci_statistics.collectd_ipstatistics=statistics 132 | set luci_statistics.collectd_ipstatistics.enable='0' 133 | 134 | delete luci_statistics.collectd_iptables 135 | set luci_statistics.collectd_iptables=statistics 136 | set luci_statistics.collectd_iptables.enable='0' 137 | 138 | delete luci_statistics.collectd_irq 139 | set luci_statistics.collectd_irq=statistics 140 | set luci_statistics.collectd_irq.enable='0' 141 | 142 | delete luci_statistics.collectd_iwinfo 143 | set luci_statistics.collectd_iwinfo=statistics 144 | set luci_statistics.collectd_iwinfo.enable='1' 145 | 146 | delete luci_statistics.collectd_load 147 | set luci_statistics.collectd_load=statistics 148 | set luci_statistics.collectd_load.enable='1' 149 | 150 | delete luci_statistics.collectd_memory 151 | set luci_statistics.collectd_memory=statistics 152 | set luci_statistics.collectd_memory.enable='1' 153 | set luci_statistics.collectd_memory.HideFree='0' 154 | set luci_statistics.collectd_memory.ValuesAbsolute='1' 155 | set luci_statistics.collectd_memory.ValuesPercentage='0' 156 | 157 | delete luci_statistics.collectd_netlink 158 | set luci_statistics.collectd_netlink=statistics 159 | set luci_statistics.collectd_netlink.enable='0' 160 | 161 | delete luci_statistics.collectd_nut 162 | set luci_statistics.collectd_nut=statistics 163 | set luci_statistics.collectd_nut.enable='0' 164 | 165 | delete luci_statistics.collectd_olsrd 166 | set luci_statistics.collectd_olsrd=statistics 167 | set luci_statistics.collectd_olsrd.enable='0' 168 | 169 | delete luci_statistics.collectd_openvpn 170 | set luci_statistics.collectd_openvpn=statistics 171 | set luci_statistics.collectd_openvpn.enable='0' 172 | 173 | delete luci_statistics.collectd_ping 174 | set luci_statistics.collectd_ping=statistics 175 | set luci_statistics.collectd_ping.enable='0' 176 | 177 | delete luci_statistics.collectd_processes 178 | set luci_statistics.collectd_processes=statistics 179 | set luci_statistics.collectd_processes.enable='0' 180 | 181 | delete luci_statistics.collectd_sensors 182 | set luci_statistics.collectd_sensors=statistics 183 | set luci_statistics.collectd_sensors.enable='0' 184 | 185 | delete luci_statistics.collectd_snmp6 186 | set luci_statistics.collectd_snmp6=statistics 187 | set luci_statistics.collectd_snmp6.enable='0' 188 | 189 | delete luci_statistics.collectd_splash_leases 190 | set luci_statistics.collectd_splash_leases=statistics 191 | set luci_statistics.collectd_splash_leases.enable='0' 192 | 193 | delete luci_statistics.collectd_tcpconns 194 | set luci_statistics.collectd_tcpconns=statistics 195 | set luci_statistics.collectd_tcpconns.enable='0' 196 | 197 | delete luci_statistics.collectd_thermal 198 | set luci_statistics.collectd_thermal=statistics 199 | set luci_statistics.collectd_thermal.enable='0' 200 | 201 | delete luci_statistics.collectd_uptime 202 | set luci_statistics.collectd_uptime=statistics 203 | set luci_statistics.collectd_uptime.enable='0' 204 | 205 | commit luci_statistics 206 | EOF 207 | 208 | fi 209 | 210 | exit 0 -------------------------------------------------------------------------------- /packages/luci-app-pairdrop/root/etc/init.d/pairdrop: -------------------------------------------------------------------------------- 1 | #!/bin/sh /etc/rc.common 2 | 3 | START=99 4 | STOP=10 5 | 6 | USE_PROCD=1 7 | 8 | NAME=pairdrop 9 | WEBAPPS_DIR="/mnt/sda1/.webapps" 10 | TMP_DIR="$WEBAPPS_DIR/tmp/pairdrop" 11 | SQFS_FILE="$WEBAPPS_DIR/pairdrop.sqfs" 12 | MOUNT_POINT="/www/pairdrop" 13 | CONFIG_MOUNT_POINT="$MOUNT_POINT/pairdrop/public/rtc_config.json" 14 | DYNAMIC_CONFIG="/var/run/pairdrop_rtc_config.json" 15 | # Directory to store Bun cache and global data instead of ~/.bun 16 | BUN_DATA_DIR="$WEBAPPS_DIR/pairdrop/bun" 17 | 18 | DEPENDENCIES="coturn unzip" 19 | 20 | # --- Helper Functions --- 21 | 22 | log_info() { 23 | logger -t pairdrop -p user.info -s "$1" 24 | } 25 | 26 | log_err() { 27 | logger -t pairdrop -p user.err -s "$1" 28 | } 29 | 30 | exec_cmd() { 31 | local cmd="$1" 32 | local err_file="/tmp/pairdrop_cmd.err" 33 | 34 | if ! eval "$cmd" 2> "$err_file"; then 35 | local err_msg=$(cat "$err_file") 36 | log_err "Command failed: $cmd" 37 | [ -n "$err_msg" ] && log_err "Details: $err_msg" 38 | rm -f "$err_file" 39 | return 1 40 | fi 41 | rm -f "$err_file" 42 | return 0 43 | } 44 | 45 | get_arch() { 46 | local arch=$(uname -m) 47 | case "$arch" in 48 | x86_64) echo "x64" ;; 49 | aarch64) echo "aarch64" ;; 50 | *) echo "" ;; 51 | esac 52 | } 53 | 54 | get_latest_github_tag() { 55 | local repo="$1" 56 | wget -qO- "https://api.github.com/repos/$repo/releases/latest" | \ 57 | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/' 58 | } 59 | 60 | generate_rtc_config() { 61 | local target_file="$1" 62 | 63 | local ip4=$(ip -4 addr show br-lan 2>/dev/null | grep -oP '(?<=inet\s)\d+(\.\d+){3}' | head -n 1) 64 | local ip6=$(ip -6 addr show br-lan 2>/dev/null | grep -oP '(?<=inet6\s)[a-f0-9:]+(?=/)' | head -n 1) 65 | 66 | [ -z "$ip4" ] && ip4=$(ifconfig br-lan 2>/dev/null | grep 'inet addr' | cut -d: -f2 | awk '{print $1}') 67 | 68 | log_info "Generating RTC Config. IPv4: ${ip4:-None}, IPv6: ${ip6:-None}" 69 | 70 | echo '{ "sdpSemantics": "unified-plan", "iceServers": [ { "urls": [' > "$target_file" 71 | 72 | local added=0 73 | if [ -n "$ip4" ]; then 74 | echo "\"stun:$ip4:3478\"" >> "$target_file" 75 | added=1 76 | fi 77 | 78 | if [ -n "$ip6" ]; then 79 | [ "$added" -eq 1 ] && echo "," >> "$target_file" 80 | echo "\"stun:[$ip6]:3478\"" >> "$target_file" 81 | fi 82 | 83 | echo '] } ] }' >> "$target_file" 84 | } 85 | 86 | # --- Service Functions --- 87 | 88 | install() { 89 | log_info "Starting installation..." 90 | 91 | if ! mount | grep -q "/mnt/sda1"; then 92 | log_err "/mnt/sda1 is not mounted or not writable." 93 | return 1 94 | fi 95 | 96 | # Load Config 97 | local bun_ver 98 | local pairdrop_ver 99 | config_load pairdrop 100 | config_get bun_ver main bun_version 101 | config_get pairdrop_ver main pairdrop_version 102 | 103 | if [ -z "$bun_ver" ]; then 104 | log_info "Bun version not configured. Will download latest." 105 | else 106 | log_info "Bun version configured: $bun_ver" 107 | fi 108 | 109 | if [ -z "$pairdrop_ver" ]; then 110 | pairdrop_ver=$(get_latest_github_tag "schlagmichdoch/PairDrop") 111 | [ -z "$pairdrop_ver" ] && pairdrop_ver="v1.10.7" 112 | log_info "Pairdrop version not configured. Retrieved latest: $pairdrop_ver" 113 | fi 114 | 115 | local arch=$(get_arch) 116 | if [ -z "$arch" ]; then 117 | log_err "Unsupported architecture $(uname -m). Bun requires x64 or aarch64." 118 | return 1 119 | fi 120 | 121 | # Prepare Tmp 122 | if ! exec_cmd "rm -rf '$TMP_DIR' && mkdir -p '$TMP_DIR/runtime' '$TMP_DIR/pairdrop'"; then 123 | return 1 124 | fi 125 | 126 | # Create Bun Persistent Data Dir (to avoid filling /root/.bun) 127 | if ! exec_cmd "mkdir -p '$BUN_DATA_DIR'"; then 128 | log_err "Failed to create persistent Bun data dir at $BUN_DATA_DIR" 129 | return 1 130 | fi 131 | 132 | # 2. Download & Install Bun (Musl) 133 | local bun_filename="bun-linux-${arch}-musl" 134 | local bun_url="" 135 | 136 | if [ -z "$bun_ver" ]; then 137 | bun_url="https://github.com/oven-sh/bun/releases/latest/download/${bun_filename}.zip" 138 | else 139 | bun_url="https://github.com/oven-sh/bun/releases/download/${bun_ver}/${bun_filename}.zip" 140 | fi 141 | 142 | log_info "Downloading Bun (musl) from $bun_url..." 143 | 144 | local zip_file="$TMP_DIR/bun.zip" 145 | if ! exec_cmd "wget -qO '$zip_file' '$bun_url'"; then 146 | log_err "Bun download failed." 147 | return 1 148 | fi 149 | 150 | log_info "Extracting Bun..." 151 | if ! exec_cmd "unzip -q '$zip_file' -d '$TMP_DIR/bun_extract'"; then 152 | log_err "Bun extraction failed. Ensure 'unzip' is installed." 153 | rm -f "$zip_file" 154 | return 1 155 | fi 156 | rm -f "$zip_file" 157 | 158 | if [ -f "$TMP_DIR/bun_extract/${bun_filename}/bun" ]; then 159 | mv "$TMP_DIR/bun_extract/${bun_filename}/bun" "$TMP_DIR/runtime/bun" 160 | chmod +x "$TMP_DIR/runtime/bun" 161 | else 162 | log_err "Bun binary not found in extracted archive." 163 | return 1 164 | fi 165 | rm -rf "$TMP_DIR/bun_extract" 166 | 167 | # 3. Download & Install PairDrop 168 | local pairdrop_url="https://github.com/schlagmichdoch/PairDrop/archive/refs/tags/${pairdrop_ver}.tar.gz" 169 | log_info "Downloading PairDrop from $pairdrop_url..." 170 | 171 | if ! exec_cmd "wget -qO- '$pairdrop_url' | tar -xz -C '$TMP_DIR/pairdrop' --strip-components=1"; then 172 | log_err "PairDrop download or extraction failed." 173 | return 1 174 | fi 175 | 176 | # 4. Install Dependencies via Bun 177 | log_info "Installing dependencies with Bun..." 178 | 179 | export PATH="$TMP_DIR/runtime:$PATH" 180 | 181 | if ! bun --version >/dev/null 2>&1; then 182 | log_err "ERROR: Downloaded Bun binary failed to execute." 183 | return 1 184 | fi 185 | 186 | # Set HOME to persistent dir so global cache goes to /mnt/sda1/.../.bun 187 | export HOME="$BUN_DATA_DIR" 188 | 189 | # We execute install in the tmp dir, but cache is redirected 190 | if ! exec_cmd "cd '$TMP_DIR/pairdrop' && bun install --production"; then 191 | log_err "bun install failed. Check internet connection." 192 | return 1 193 | fi 194 | 195 | # 5. Create Config 196 | log_info "Creating initial config file..." 197 | generate_rtc_config "$TMP_DIR/pairdrop/public/rtc_config.json" 198 | 199 | # 6. Pack SquashFS 200 | log_info "Packing SquashFS..." 201 | exec_cmd "rm -f '$SQFS_FILE'" 202 | 203 | # We exclude the .bun folder if it accidentally got created locally, 204 | # though with HOME set it should be in BUN_DATA_DIR. 205 | if ! exec_cmd "mksquashfs '$TMP_DIR' '$SQFS_FILE' -comp lz4 -noappend -e .bun -e .cache"; then 206 | log_err "Failed to create SquashFS image." 207 | return 1 208 | fi 209 | 210 | rm -rf "$TMP_DIR" 211 | 212 | log_info "Installation complete. Bun cache located at $BUN_DATA_DIR" 213 | } 214 | 215 | uninstall() { 216 | log_info "Uninstalling Pairdrop..." 217 | 218 | local removed=0 219 | 220 | if [ -f "$SQFS_FILE" ]; then 221 | if exec_cmd "rm '$SQFS_FILE'"; then 222 | log_info "Removed $SQFS_FILE" 223 | removed=1 224 | else 225 | log_err "Failed to remove $SQFS_FILE" 226 | fi 227 | fi 228 | 229 | # Remove the persistent bun cache/data directory 230 | if [ -d "$BUN_DATA_DIR" ]; then 231 | if exec_cmd "rm -rf '$BUN_DATA_DIR'"; then 232 | log_info "Removed Bun data directory: $BUN_DATA_DIR" 233 | removed=1 234 | else 235 | log_err "Failed to remove $BUN_DATA_DIR" 236 | fi 237 | fi 238 | 239 | if [ "$removed" -eq 0 ]; then 240 | log_info "Pairdrop not installed (no files found)." 241 | fi 242 | } 243 | 244 | reinstall() { 245 | uninstall 246 | install 247 | } 248 | 249 | start_service() { 250 | config_load pairdrop 251 | local enabled 252 | local port 253 | config_get_bool enabled main enabled 0 254 | config_get port main port 3000 255 | 256 | if [ "$enabled" -eq 0 ]; then 257 | log_info "Pairdrop is not enabled in the config. Exiting..." 258 | return 0 259 | fi 260 | 261 | if [ ! -f "$SQFS_FILE" ]; then 262 | log_err "Pairdrop squashfs not found. Please install first." 263 | return 1 264 | fi 265 | 266 | if ! mount | grep -q "/mnt/sda1"; then 267 | log_err "/mnt/sda1 not mounted." 268 | return 1 269 | fi 270 | 271 | if ! mount | grep -q "$MOUNT_POINT"; then 272 | exec_cmd "mkdir -p '$MOUNT_POINT'" 273 | if ! exec_cmd "mount -t squashfs -o loop,ro '$SQFS_FILE' '$MOUNT_POINT'"; then 274 | log_err "Failed to mount SquashFS image." 275 | return 1 276 | fi 277 | fi 278 | 279 | log_info "Mounted [$SQFS_FILE] into [$MOUNT_POINT]." 280 | generate_rtc_config "$DYNAMIC_CONFIG" 281 | 282 | if [ -f "$CONFIG_MOUNT_POINT" ]; then 283 | if ! exec_cmd "mount --bind '$DYNAMIC_CONFIG' '$CONFIG_MOUNT_POINT'"; then 284 | log_err "Failed to bind mount dynamic config." 285 | fi 286 | fi 287 | 288 | log_info "Mounted [$DYNAMIC_CONFIG] into [$CONFIG_MOUNT_POINT]." 289 | 290 | if ! pgrep -x "turnserver" >/dev/null; then 291 | if [ -x /etc/init.d/coturn-server ]; then 292 | log_info "Starting coturn-server..." 293 | /etc/init.d/coturn-server start 294 | fi 295 | fi 296 | 297 | log_info "Starting PairDrop with Bun on port $port..." 298 | 299 | procd_open_instance 300 | procd_set_param chdir "$MOUNT_POINT/pairdrop" 301 | # Use absolute path to ensure Bun finds the script regardless of working dir quirks 302 | procd_set_param command "$MOUNT_POINT/runtime/bun" "$MOUNT_POINT/pairdrop/server/index.js" 303 | # Ensure Bun uses the persistent directory for any runtime cache/config by setting HOME 304 | procd_set_param env HOME="$BUN_DATA_DIR" 305 | procd_set_param env PORT="$port" 306 | procd_set_param stdout 1 307 | procd_set_param stderr 1 308 | procd_set_param respawn 309 | procd_close_instance 310 | } 311 | 312 | stop_service() { 313 | log_info "Stopping Pairdrop..." 314 | 315 | local proc_match="$MOUNT_POINT/runtime/bun $MOUNT_POINT/pairdrop/server/index.js" 316 | 317 | # Also try matching without full path in case it was started differently 318 | local proc_match_alt="$MOUNT_POINT/runtime/bun server/index.js" 319 | 320 | if pgrep -f "$proc_match" >/dev/null; then 321 | pkill -f "$proc_match" 322 | elif pgrep -f "$proc_match_alt" >/dev/null; then 323 | pkill -f "$proc_match_alt" 324 | fi 325 | 326 | # Wait loop 327 | local i=0 328 | while pgrep -f "$MOUNT_POINT/runtime/bun" >/dev/null; do 329 | if [ "$i" -ge 10 ]; then 330 | log_err "Process hung. Force killing..." 331 | pkill -9 -f "$MOUNT_POINT/runtime/bun" 332 | break 333 | fi 334 | sleep 1 335 | i=$((i + 1)) 336 | done 337 | 338 | if mount | grep -q "$CONFIG_MOUNT_POINT"; then 339 | if ! umount "$CONFIG_MOUNT_POINT"; then 340 | log_err "Failed to unmount config bind-mount. Force unmounting..." 341 | umount -l "$CONFIG_MOUNT_POINT" 342 | fi 343 | fi 344 | 345 | if mount | grep -q "$MOUNT_POINT"; then 346 | log_info "Unmounting $MOUNT_POINT..." 347 | 348 | if umount "$MOUNT_POINT"; then 349 | log_info "Unmounted successfully." 350 | return 0 351 | fi 352 | 353 | local timeout=5 354 | local count=0 355 | while [ $count -lt $timeout ]; do 356 | sleep 1 357 | if umount "$MOUNT_POINT" 2>/dev/null; then 358 | log_info "Unmounted successfully." 359 | return 0 360 | fi 361 | count=$((count + 1)) 362 | done 363 | 364 | log_err "Timeout reached. Force unmounting..." 365 | if ! umount -l "$MOUNT_POINT"; then 366 | log_err "Failed to force unmount $MOUNT_POINT" 367 | return 1 368 | fi 369 | fi 370 | } 371 | 372 | EXTRA_COMMANDS="install uninstall reinstall" 373 | EXTRA_HELP=" install Download and install Pairdrop (using Bun) 374 | uninstall Remove Pairdrop files 375 | reinstall Reinstall Pairdrop" -------------------------------------------------------------------------------- /files/usr/bin/speedtest-netperf: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # This speed testing script provides a convenient means of on-device network 4 | # performance testing for OpenWrt routers, and subsumes functionality of the 5 | # earlier CeroWrt scripts betterspeedtest.sh and netperfrunner.sh written by 6 | # Rich Brown. 7 | # 8 | # When launched, the script uses netperf to run several upload and download 9 | # streams to an Internet server. This places heavy load on the bottleneck link 10 | # of your network (probably your Internet connection) while measuring the total 11 | # bandwidth of the link during the transfers. Under this network load, the 12 | # script simultaneously measures the latency of pings to see whether the file 13 | # transfers affect the responsiveness of your network. Additionally, the script 14 | # tracks the per-CPU processor usage, as well as the netperf CPU usage used for 15 | # the test. On systems that report CPU frequency scaling, the script can also 16 | # report per-CPU frequencies. 17 | # 18 | # The script operates in two modes of network loading: sequential and 19 | # concurrent. The default sequential mode emulates a web-based speed test by 20 | # first downloading and then uploading network streams, while concurrent mode 21 | # provides a stress test by dowloading and uploading streams simultaneously. 22 | # 23 | # NOTE: The script uses servers and network bandwidth that are provided by 24 | # generous volunteers (not some wealthy "big company"). Feel free to use the 25 | # script to test your SQM configuration or troubleshoot network and latency 26 | # problems. Continuous or high rate use of this script may result in denied 27 | # access. Happy testing! 28 | # 29 | # For more information, consult the online README.md: 30 | # https://github.com/openwrt/packages/blob/master/net/speedtest-netperf/files/README.md 31 | 32 | # Usage: speedtest-netperf [-4 | -6] [ -H netperf-server ] [ -t duration ] [ -p host-to-ping ] [ -n simultaneous-streams ] [ -s | -c ] 33 | 34 | # Options: If options are present: 35 | # 36 | # -H | --host: netperf server name or IP (default netperf.bufferbloat.net) 37 | # Alternate servers are netperf-east (east coast US), 38 | # netperf-west (California), and netperf-eu (Denmark) 39 | # -4 | -6: Enable ipv4 or ipv6 testing (ipv4 is the default) 40 | # -t | --time: Duration of each direction's test - (default - 60 seconds) 41 | # -p | --ping: Host to ping to measure latency (default - gstatic.com) 42 | # -n | --number: Number of simultaneous sessions (default - 5 sessions) 43 | # based on whether concurrent or sequential upload/downloads) 44 | # -s | -c: Sequential or concurrent download/upload (default - sequential) 45 | 46 | # Copyright (c) 2014 - Rich Brown 47 | # Copyright (c) 2018 - Tony Ambardar 48 | # GPLv2 49 | 50 | 51 | # Summarize contents of the ping's output file as min, avg, median, max, etc. 52 | # input parameter ($1) file contains the output of the ping command 53 | 54 | summarize_pings() { 55 | 56 | # Process the ping times, and summarize the results 57 | # grep to keep lines with "time=", and sed to isolate time stamps and sort them 58 | # awk builds an array of those values, prints first & last (which are min, max) 59 | # and computes average. 60 | # If the number of samples is >= 10, also computes median, and 10th and 90th 61 | # percentile readings. 62 | sed 's/^.*time=\([^ ]*\) ms/\1 pingtime/' < $1 | grep -v "PING" | sort -n | awk ' 63 | BEGIN {numdrops=0; numrows=0;} 64 | { 65 | if ( $2 == "pingtime" ) { 66 | numrows += 1; 67 | arr[numrows]=$1; sum+=$1; 68 | } else { 69 | numdrops += 1; 70 | } 71 | } 72 | END { 73 | pc10="-"; pc90="-"; med="-"; 74 | if (numrows>=10) { 75 | ix=int(numrows/10); pc10=arr[ix]; ix=int(numrows*9/10);pc90=arr[ix]; 76 | if (numrows%2==1) med=arr[(numrows+1)/2]; else med=(arr[numrows/2]); 77 | } 78 | pktloss = numdrops>0 ? numdrops/(numdrops+numrows) * 100 : 0; 79 | printf(" Latency: [in msec, %d pings, %4.2f%% packet loss]\n",numdrops+numrows,pktloss) 80 | if (numrows>0) { 81 | fmt="%9s: %7.3f\n" 82 | printf(fmt fmt fmt fmt fmt fmt, "Min",arr[1],"10pct",pc10,"Median",med, 83 | "Avg",sum/numrows,"90pct",pc90,"Max",arr[numrows]) 84 | } 85 | }' 86 | } 87 | 88 | # Summarize the contents of the load file, speedtest process stat file, cpuinfo 89 | # file to show mean/stddev CPU utilization, CPU freq, netperf CPU usage. 90 | # input parameter ($1) file contains CPU load/frequency samples 91 | 92 | summarize_load() { 93 | cat $1 /proc/$$/stat | awk -v SCRIPT_PID=$$ ' 94 | # track CPU frequencies 95 | $1 == "cpufreq" { 96 | sum_freq[$2]+=$3/1000 97 | n_freq_samp[$2]++ 98 | } 99 | # total CPU of speedtest processes 100 | $1 == SCRIPT_PID { 101 | tot=$16+$17 102 | if (init_proc_cpu=="") init_proc_cpu=tot 103 | proc_cpu=tot-init_proc_cpu 104 | } 105 | # track aggregate CPU stats 106 | $1 == "cpu" { 107 | tot=0; for (f=2;f<=8;f++) tot+=$f 108 | if (init_cpu=="") init_cpu=tot 109 | tot_cpu=tot-init_cpu 110 | n_load_samp++ 111 | } 112 | # track per-CPU stats 113 | $1 ~ /cpu[0-9]+/ { 114 | tot=0; for (f=2;f<=8;f++) tot+=$f 115 | usg=tot-($5+$6) 116 | if (init_tot[$1]=="") { 117 | init_tot[$1]=tot 118 | init_usg[$1]=usg 119 | cpus[n_cpus++]=$1 120 | } 121 | if (last_tot[$1]>0) { 122 | sum_usg_2[$1] += ((usg-last_usg[$1])/(tot-last_tot[$1]))^2 123 | } 124 | last_tot[$1]=tot 125 | last_usg[$1]=usg 126 | } 127 | END { 128 | printf(" CPU Load: [in %% busy (avg +/- std dev)") 129 | for (i in sum_freq) if (sum_freq[i]>0) {printf(" @ avg frequency"); break} 130 | if (n_load_samp>0) n_load_samp-- 131 | printf(", %d samples]\n", n_load_samp) 132 | for (i=0;i0) { 135 | avg_usg=(last_tot[c]-init_tot[c]) 136 | avg_usg=avg_usg>0 ? (last_usg[c]-init_usg[c])/avg_usg : 0 137 | std_usg=sum_usg_2[c]/n_load_samp-avg_usg^2 138 | std_usg=std_usg>0 ? sqrt(std_usg) : 0 139 | printf("%9s: %5.1f +/- %4.1f", c, avg_usg*100, std_usg*100) 140 | avg_freq=n_freq_samp[c]>0 ? sum_freq[c]/n_freq_samp[c] : 0 141 | if (avg_freq>0) printf(" @ %4d MHz", avg_freq) 142 | printf("\n") 143 | } 144 | } 145 | printf(" Overhead: [in %% used of total CPU available]\n") 146 | printf("%9s: %5.1f\n", "netperf", tot_cpu>0 ? proc_cpu/tot_cpu*100 : 0) 147 | }' 148 | } 149 | 150 | # Summarize the contents of the speed file to show formatted transfer rate. 151 | # input parameter ($1) indicates transfer direction 152 | # input parameter ($2) file contains speed info from netperf 153 | 154 | summarize_speed() { 155 | printf "%9s: %6.2f Mbps\n" $1 $(awk '{s+=$1} END {print s}' $2) 156 | } 157 | 158 | # Capture process load, then per-CPU load/frequency info at 1-second intervals. 159 | 160 | sample_load() { 161 | local cpus="$(find /sys/devices/system/cpu -name 'cpu[0-9]*' 2>/dev/null)" 162 | local f="cpufreq/scaling_cur_freq" 163 | cat /proc/$$/stat 164 | while : ; do 165 | sleep 1s 166 | grep -E "^cpu[0-9]*" /proc/stat 167 | for c in $cpus; do 168 | [ -r $c/$f ] && echo "cpufreq $(basename $c) $(cat $c/$f)" 169 | done 170 | done 171 | } 172 | 173 | # Print a line of dots as a progress indicator. 174 | 175 | print_dots() { 176 | while : ; do 177 | printf "." 178 | sleep 1s 179 | done 180 | } 181 | 182 | # Start $MAXSESSIONS datastreams between netperf client and server 183 | # netperf writes the sole output value (in Mbps) to stdout when completed 184 | 185 | start_netperf() { 186 | for i in $( seq $MAXSESSIONS ); do 187 | netperf $TESTPROTO -H $TESTHOST -t $1 -l $TESTDUR -v 0 -P 0 >> $2 & 188 | # echo "Starting PID $! params: $TESTPROTO -H $TESTHOST -t $1 -l $TESTDUR -v 0 -P 0 >> $2" 189 | done 190 | } 191 | 192 | # Wait until each of the background netperf processes completes 193 | 194 | wait_netperf() { 195 | # gets a list of PIDs for child processes named 'netperf' 196 | # echo "Process is $$" 197 | # echo $(pgrep -P $$ netperf) 198 | local err=0 199 | for i in $(pgrep -P $$ netperf); do 200 | # echo "Waiting for $i" 201 | wait $i || err=1 202 | done 203 | return $err 204 | } 205 | 206 | # Stop the background netperf processes 207 | 208 | kill_netperf() { 209 | # gets a list of PIDs for child processes named 'netperf' 210 | # echo "Process is $$" 211 | # echo $(pgrep -P $$ netperf) 212 | for i in $(pgrep -P $$ netperf); do 213 | # echo "Stopping $i" 214 | kill -9 $i 215 | wait $i 2>/dev/null 216 | done 217 | } 218 | 219 | # Stop the current sample_load() process 220 | 221 | kill_load() { 222 | # echo "Load: $LOAD_PID" 223 | kill -9 $LOAD_PID 224 | wait $LOAD_PID 2>/dev/null 225 | LOAD_PID=0 226 | } 227 | 228 | # Stop the current print_dots() process 229 | 230 | kill_dots() { 231 | # echo "Dots: $DOTS_PID" 232 | kill -9 $DOTS_PID 233 | wait $DOTS_PID 2>/dev/null 234 | DOTS_PID=0 235 | } 236 | 237 | # Stop the current ping process 238 | 239 | kill_pings() { 240 | # echo "Pings: $PING_PID" 241 | kill -9 $PING_PID 242 | wait $PING_PID 2>/dev/null 243 | PING_PID=0 244 | } 245 | 246 | # Stop the current load, pings and dots, and exit 247 | # ping command catches and handles first Ctrl-C, so you have to hit it again... 248 | 249 | kill_background_and_exit() { 250 | kill_netperf 251 | kill_load 252 | kill_dots 253 | rm -f $DLFILE 254 | rm -f $ULFILE 255 | rm -f $LOADFILE 256 | rm -f $PINGFILE 257 | echo; echo "Stopped" 258 | exit 1 259 | } 260 | 261 | # Measure speed, ping latency and cpu usage of netperf data transfers 262 | # Called with direction parameter: "Download", "Upload", or "Bidirectional" 263 | # The function gets other info from globals and command-line arguments. 264 | 265 | measure_direction() { 266 | 267 | # Create temp files for netperf up/download results 268 | ULFILE=$(mktemp /tmp/netperfUL.XXXXXX) || exit 1 269 | DLFILE=$(mktemp /tmp/netperfDL.XXXXXX) || exit 1 270 | PINGFILE=$(mktemp /tmp/measurepings.XXXXXX) || exit 1 271 | LOADFILE=$(mktemp /tmp/measureload.XXXXXX) || exit 1 272 | # echo $ULFILE $DLFILE $PINGFILE $LOADFILE 273 | 274 | local dir=$1 275 | local spd_test 276 | 277 | # Start dots 278 | print_dots & 279 | DOTS_PID=$! 280 | # echo "Dots PID: $DOTS_PID" 281 | 282 | # Start Ping 283 | if [ $TESTPROTO -eq "-4" ]; then 284 | ping $PINGHOST > $PINGFILE & 285 | else 286 | ping6 $PINGHOST > $PINGFILE & 287 | fi 288 | PING_PID=$! 289 | # echo "Ping PID: $PING_PID" 290 | 291 | # Start CPU load sampling 292 | sample_load > $LOADFILE & 293 | LOAD_PID=$! 294 | # echo "Load PID: $LOAD_PID" 295 | 296 | # Start netperf datastreams between client and server 297 | if [ $dir = "Bidirectional" ]; then 298 | start_netperf TCP_STREAM $ULFILE 299 | start_netperf TCP_MAERTS $DLFILE 300 | else 301 | # Start unidirectional netperf with the proper direction 302 | case $dir in 303 | Download) spd_test="TCP_MAERTS";; 304 | Upload) spd_test="TCP_STREAM";; 305 | esac 306 | start_netperf $spd_test $DLFILE 307 | fi 308 | 309 | # Wait until background netperf processes complete, check errors 310 | if ! wait_netperf; then 311 | echo;echo "WARNING: netperf returned errors. Results may be inaccurate!" 312 | fi 313 | 314 | # When netperf completes, stop the CPU monitor, dots and pings 315 | kill_load 316 | kill_pings 317 | kill_dots 318 | echo 319 | 320 | # Print TCP Download/Upload speed 321 | if [ $dir = "Bidirectional" ]; then 322 | summarize_speed Download $DLFILE 323 | summarize_speed Upload $ULFILE 324 | else 325 | summarize_speed $dir $DLFILE 326 | fi 327 | 328 | # Summarize the ping data 329 | summarize_pings $PINGFILE 330 | 331 | # Summarize the load data 332 | summarize_load $LOADFILE 333 | 334 | # Clean up 335 | rm -f $DLFILE 336 | rm -f $ULFILE 337 | rm -f $PINGFILE 338 | rm -f $LOADFILE 339 | } 340 | 341 | # ------- Start of the main routine -------- 342 | 343 | # set an initial values for defaults 344 | TESTHOST="netperf-west.bufferbloat.net" 345 | TESTDUR="30" 346 | PINGHOST="gstatic.com" 347 | MAXSESSIONS=8 348 | TESTPROTO="-4" 349 | TESTSEQ=1 350 | 351 | # read the options 352 | 353 | # extract options and their arguments into variables. 354 | while [ $# -gt 0 ] 355 | do 356 | case "$1" in 357 | -s|--sequential) TESTSEQ=1 ; shift 1 ;; 358 | -c|--concurrent) TESTSEQ=0 ; shift 1 ;; 359 | -4|-6) TESTPROTO=$1 ; shift 1 ;; 360 | -H|--host) 361 | case "$2" in 362 | "") echo "Missing hostname" ; exit 1 ;; 363 | *) TESTHOST=$2 ; shift 2 ;; 364 | esac ;; 365 | -t|--time) 366 | case "$2" in 367 | "") echo "Missing duration" ; exit 1 ;; 368 | *) TESTDUR=$2 ; shift 2 ;; 369 | esac ;; 370 | -p|--ping) 371 | case "$2" in 372 | "") echo "Missing ping host" ; exit 1 ;; 373 | *) PINGHOST=$2 ; shift 2 ;; 374 | esac ;; 375 | -n|--number) 376 | case "$2" in 377 | "") echo "Missing number of simultaneous streams" ; exit 1 ;; 378 | *) MAXSESSIONS=$2 ; shift 2 ;; 379 | esac ;; 380 | --) shift ; break ;; 381 | *) echo "Usage: speedtest-netperf.sh [ -s | -c ] [-4 | -6] [ -H netperf-server ] [ -t duration ] [ -p host-to-ping ] [ -n simultaneous-sessions ]" ; exit 1 ;; 382 | esac 383 | done 384 | 385 | # Check dependencies 386 | 387 | if ! netperf -V >/dev/null 2>&1; then 388 | echo "Missing netperf program, please install" ; exit 1 389 | fi 390 | 391 | # Start the main test 392 | 393 | DATE=$(date "+%Y-%m-%d %H:%M:%S") 394 | echo "$DATE Starting speedtest for $TESTDUR seconds per transfer session." 395 | echo "Measure speed to $TESTHOST (IPv${TESTPROTO#-}) while pinging $PINGHOST." 396 | echo -n "Download and upload sessions are " 397 | [ "$TESTSEQ " -eq "1" ] && echo -n "sequential," || echo -n "concurrent," 398 | echo " each with $MAXSESSIONS simultaneous streams." 399 | 400 | # Catch a Ctl-C and stop background netperf, CPU stats, pinging and print_dots 401 | trap kill_background_and_exit HUP INT TERM 402 | 403 | if [ $TESTSEQ -eq "1" ]; then 404 | measure_direction "Download" 405 | measure_direction "Upload" 406 | else 407 | measure_direction "Bidirectional" 408 | fi 409 | -------------------------------------------------------------------------------- /files/usr/bin/webapps: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # ============================================================================== 4 | # OpenWRT Static Web Apps Manager 5 | # ============================================================================== 6 | # 7 | # This script manages the installation, update, and removal of static web applications 8 | # stored as SquashFS images on an OpenWRT router. 9 | # 10 | # Features: 11 | # - Centralized configuration and logic. 12 | # - Automates download, SquashFS creation, and Service (init.d) generation. 13 | # - Configures uhttpd automatically with named blocks. 14 | # - Supports: it-tools, bentopdf. 15 | # 16 | # Usage: 17 | # ./webapps.sh install [app_name|all] [--force] [--version ] 18 | # ./webapps.sh update [app_name|all] [--force] [--version ] 19 | # ./webapps.sh remove [app_name] 20 | # ./webapps.sh list 21 | # 22 | # Options: 23 | # --force Reinstall even if the version matches the installed one. 24 | # --version Install a specific release tag instead of the latest. 25 | # 26 | # ============================================================================== 27 | 28 | # --- Configuration --- 29 | 30 | # Directory to store the SquashFS images and version files 31 | STORAGE_DIR="/mnt/sda1/.webapps" 32 | 33 | # Temporary working directory for downloads/extraction 34 | WORK_BASE="/tmp/webapps-work" 35 | 36 | # --- App Definitions --- 37 | # Format: "REPO_USER/REPO_NAME|MOUNT_POINT|FRIENDLY_NAME|PORT|USE_SOURCE" 38 | 39 | get_app_config() { 40 | case "$1" in 41 | it-tools) 42 | echo "sharevb/it-tools|/www/tools|IT-Tools|8080|false" 43 | ;; 44 | bentopdf) 45 | echo "alam00000/bentopdf|/www/bentopdf|BentoPDF|8081|false" 46 | ;; 47 | *) 48 | echo "" 49 | ;; 50 | esac 51 | } 52 | 53 | ALL_APPS="it-tools bentopdf" 54 | 55 | # --- Colors --- 56 | GREEN='\033[0;32m' 57 | YELLOW='\033[1;33m' 58 | RED='\033[0;31m' 59 | BLUE='\033[0;34m' 60 | NC='\033[0m' # No Color 61 | 62 | # --- Logging Functions --- 63 | log() { echo -e "${GREEN}[$APP_NAME]${NC} $1"; } 64 | warn() { echo -e "${YELLOW}[$APP_NAME]${NC} $1"; } 65 | err() { echo -e "${RED}[ERROR]${NC} $1"; } 66 | info() { echo -e "${BLUE}[INFO]${NC} $1"; } 67 | 68 | # --- Dependency Check --- 69 | check_dependencies() { 70 | local missing=0 71 | for cmd in wget jq unzip mksquashfs mount umount uci awk; do 72 | if ! command -v $cmd >/dev/null; then 73 | err "Missing dependency: $cmd" 74 | missing=1 75 | fi 76 | done 77 | if [ $missing -eq 1 ]; then 78 | echo "Please install missing packages (e.g., 'opkg update && opkg install wget-ssl jq unzip squashfs-tools-mksquashfs uci gawk')" 79 | exit 1 80 | fi 81 | } 82 | 83 | # --- Service Generator --- 84 | generate_init_script() { 85 | local service_name="mount_$1" 86 | local mount_point="$2" 87 | local image_file="$3" 88 | local script_path="/etc/init.d/$service_name" 89 | 90 | log "Updating service script: $script_path" 91 | 92 | cat < "$script_path" 93 | #!/bin/sh /etc/rc.common 94 | 95 | START=99 96 | STOP=01 97 | 98 | APP_NAME="$1" 99 | MOUNT_POINT="$mount_point" 100 | IMAGE_FILE="$image_file" 101 | 102 | start() { 103 | if [ ! -f "\$IMAGE_FILE" ]; then 104 | logger -p error -t $service_name "Error: \$IMAGE_FILE not found." 105 | return 1 106 | fi 107 | 108 | if [ ! -d "\$MOUNT_POINT" ]; then 109 | mkdir -p "\$MOUNT_POINT" 110 | fi 111 | 112 | if mount | grep -q "\$MOUNT_POINT"; then 113 | return 0 114 | fi 115 | 116 | logger -p notice -t $service_name "Mounting \$APP_NAME..." 117 | 118 | # Mount loopback with read-only access 119 | if mount -t squashfs -o loop,ro "\$IMAGE_FILE" "\$MOUNT_POINT"; then 120 | logger -p notice -t $service_name "Mounted successfully" 121 | else 122 | logger -p error -t $service_name "Failed to mount" 123 | return 1 124 | fi 125 | } 126 | 127 | stop() { 128 | if mount | grep -q "\$MOUNT_POINT"; then 129 | logger -p notice -t $service_name "Unmounting..." 130 | umount "\$MOUNT_POINT" 131 | fi 132 | } 133 | 134 | restart() { 135 | stop 136 | start 137 | } 138 | EOF 139 | 140 | chmod +x "$script_path" 141 | if ! "$script_path" enabled; then 142 | "$script_path" enable 143 | fi 144 | } 145 | 146 | # --- uhttpd Configuration --- 147 | configure_uhttpd() { 148 | local app_key="$1" 149 | local mount_point="$2" 150 | local port="$3" 151 | 152 | local config_name=$(echo "$app_key" | tr '-' '_') 153 | 154 | log "Configuring uhttpd section '$config_name' on port $port..." 155 | 156 | uci set uhttpd.${config_name}=uhttpd 157 | uci set uhttpd.${config_name}.home="$mount_point" 158 | uci set uhttpd.${config_name}.redirect_https='0' 159 | 160 | uci delete uhttpd.${config_name}.listen_http 2>/dev/null 161 | uci add_list uhttpd.${config_name}.listen_http="0.0.0.0:$port" 162 | uci add_list uhttpd.${config_name}.listen_http="[::]:$port" 163 | 164 | uci commit uhttpd 165 | 166 | if [ -f "/etc/init.d/uhttpd" ]; then 167 | if output=$(/etc/init.d/uhttpd reload 2>&1); then 168 | filtered=$(echo "$output" | grep -v "Skipping invalid Lua prefix" | grep -v "User-defined signal 1") 169 | [ -n "$filtered" ] && echo "$filtered" 170 | log "uhttpd reloaded. Access at http://:$port" 171 | else 172 | warn "uhttpd reload returned an error code. Please check 'logread'." 173 | fi 174 | else 175 | warn "uhttpd service not found." 176 | fi 177 | } 178 | 179 | remove_uhttpd_config() { 180 | local app_key="$1" 181 | local config_name=$(echo "$app_key" | tr '-' '_') 182 | 183 | if uci get uhttpd.${config_name} >/dev/null 2>&1; then 184 | log "Removing uhttpd configuration '$config_name'..." 185 | uci delete uhttpd.${config_name} 186 | uci commit uhttpd 187 | /etc/init.d/uhttpd reload 2>&1 | grep -v "Skipping invalid Lua prefix" 188 | fi 189 | } 190 | 191 | # --- Installation Logic --- 192 | install_app_logic() { 193 | local app_key="$1" 194 | local force_flag="$2" 195 | local target_version="$3" 196 | 197 | local config=$(get_app_config "$app_key") 198 | if [ -z "$config" ]; then 199 | err "Unknown app: $app_key" 200 | return 1 201 | fi 202 | 203 | local repo=$(echo "$config" | cut -d'|' -f1) 204 | local mount_point=$(echo "$config" | cut -d'|' -f2) 205 | APP_NAME=$(echo "$config" | cut -d'|' -f3) 206 | local port=$(echo "$config" | cut -d'|' -f4) 207 | local use_source=$(echo "$config" | cut -d'|' -f5) 208 | 209 | local image_file="$STORAGE_DIR/${app_key}.squashfs" 210 | local version_file="$STORAGE_DIR/${app_key}.version" 211 | local work_dir="$WORK_BASE/$app_key" 212 | 213 | local api_url="" 214 | if [ -n "$target_version" ]; then 215 | log "Checking for specific version: $target_version..." 216 | api_url="https://api.github.com/repos/$repo/releases/tags/$target_version" 217 | else 218 | log "Checking $repo for latest updates..." 219 | api_url="https://api.github.com/repos/$repo/releases/latest" 220 | fi 221 | 222 | local release_json=$(wget -qO- --no-check-certificate "$api_url") 223 | 224 | if [ -z "$release_json" ] || echo "$release_json" | grep -q "Not Found"; then 225 | err "Failed to fetch release info (Version: ${target_version:-latest})." 226 | return 1 227 | fi 228 | 229 | local version_found=$(echo "$release_json" | jq -r .tag_name) 230 | local download_url="" 231 | 232 | if [ "$use_source" = "true" ]; then 233 | log "Configuration requests Source Code archive..." 234 | download_url=$(echo "$release_json" | jq -r '.zipball_url') 235 | else 236 | download_url=$(echo "$release_json" | jq -r '.assets[] | select(.name | endswith(".zip")) | select(.name | contains("source") | not) | .browser_download_url' | head -n 1) 237 | if [ -z "$download_url" ] || [ "$download_url" = "null" ]; then 238 | warn "No pre-built binary zip found. Checking for any zip file..." 239 | download_url=$(echo "$release_json" | jq -r '.assets[] | select(.name | endswith(".zip")) | .browser_download_url' | head -n 1) 240 | fi 241 | fi 242 | 243 | if [ -z "$download_url" ] || [ "$download_url" = "null" ]; then 244 | err "No compatible zip asset found in release $version_found." 245 | return 1 246 | fi 247 | 248 | local current_version="none" 249 | if [ -f "$version_file" ]; then 250 | current_version=$(cat "$version_file") 251 | fi 252 | 253 | # Check if we should skip 254 | if [ "$force_flag" != "true" ] && [ "$version_found" = "$current_version" ] && [ -f "$image_file" ]; then 255 | log "Already up to date ($current_version). Skipping." 256 | return 0 257 | fi 258 | 259 | if [ "$force_flag" = "true" ]; then 260 | log "Force reinstall requested." 261 | fi 262 | 263 | log "Installing $version_found (Current: $current_version)..." 264 | 265 | rm -rf "$work_dir" 266 | mkdir -p "$work_dir" 267 | 268 | log "Downloading from $download_url..." 269 | if ! wget -qO "$work_dir/archive.zip" --no-check-certificate "$download_url"; then 270 | err "Download failed." 271 | rm -rf "$work_dir" 272 | return 1 273 | fi 274 | 275 | log "Extracting..." 276 | if ! unzip -q "$work_dir/archive.zip" -d "$work_dir/extracted"; then 277 | err "Extraction failed." 278 | rm -rf "$work_dir" 279 | return 1 280 | fi 281 | 282 | local index_file=$(find "$work_dir/extracted" -name "index.html" | awk '{ print length, $0 }' | sort -n | cut -d" " -f2- | head -n 1) 283 | 284 | if [ -z "$index_file" ]; then 285 | err "Could not find 'index.html' in the archive." 286 | rm -rf "$work_dir" 287 | return 1 288 | fi 289 | 290 | local web_root=$(dirname "$index_file") 291 | log "Found web root at: $web_root" 292 | 293 | mkdir -p "$STORAGE_DIR" 294 | 295 | log "Building SquashFS image..." 296 | rm -f "$image_file" 297 | 298 | if mksquashfs "$web_root" "$image_file" -comp lz4 -b 256k -no-xattrs -all-root -noappend -no-progress >/dev/null; then 299 | echo "$version_found" > "$version_file" 300 | log "Image created successfully." 301 | 302 | generate_init_script "$app_key" "$mount_point" "$image_file" 303 | 304 | log "Restarting service..." 305 | "/etc/init.d/mount_$app_key" restart 306 | 307 | configure_uhttpd "$app_key" "$mount_point" "$port" 308 | 309 | log "Installation complete!" 310 | else 311 | err "Failed to create SquashFS image." 312 | rm -rf "$work_dir" 313 | return 1 314 | fi 315 | 316 | rm -rf "$work_dir" 317 | } 318 | 319 | # --- Removal Logic --- 320 | remove_app_logic() { 321 | local app_key="$1" 322 | local config=$(get_app_config "$app_key") 323 | 324 | if [ -z "$config" ]; then 325 | err "Unknown app: $app_key" 326 | return 1 327 | fi 328 | 329 | APP_NAME=$(echo "$config" | cut -d'|' -f3) 330 | local service_name="mount_$app_key" 331 | local service_path="/etc/init.d/$service_name" 332 | local image_file="$STORAGE_DIR/${app_key}.squashfs" 333 | local version_file="$STORAGE_DIR/${app_key}.version" 334 | 335 | log "Removing $APP_NAME..." 336 | 337 | if [ -f "$service_path" ]; then 338 | if "$service_path" enabled; then 339 | "$service_path" disable 340 | fi 341 | "$service_path" stop 342 | rm -f "$service_path" 343 | log "Service removed." 344 | fi 345 | 346 | remove_uhttpd_config "$app_key" 347 | 348 | if [ -f "$image_file" ]; then 349 | rm -f "$image_file" 350 | log "SquashFS image removed." 351 | fi 352 | if [ -f "$version_file" ]; then 353 | rm -f "$version_file" 354 | fi 355 | 356 | log "Removal complete." 357 | } 358 | 359 | # --- Main Execution --- 360 | 361 | check_dependencies 362 | 363 | # Argument Parsing 364 | COMMAND="$1" 365 | TARGET="$2" 366 | 367 | # Shift past command and target if they exist 368 | if [ -n "$COMMAND" ]; then shift; fi 369 | if [ -n "$TARGET" ]; then shift; fi 370 | 371 | FORCE="false" 372 | VERSION="" 373 | 374 | # Parse optional flags 375 | while [ $# -gt 0 ]; do 376 | case "$1" in 377 | --force) 378 | FORCE="true" 379 | shift 380 | ;; 381 | --version) 382 | if [ -n "$2" ]; then 383 | VERSION="$2" 384 | shift 2 385 | else 386 | echo "Error: --version requires an argument." 387 | exit 1 388 | fi 389 | ;; 390 | *) 391 | echo "Unknown option: $1" 392 | exit 1 393 | ;; 394 | esac 395 | done 396 | 397 | if [ -z "$COMMAND" ]; then 398 | echo "Usage: $0 {install|update|remove} {app_name|all} [--force] [--version ]" 399 | echo "Available apps: $ALL_APPS" 400 | exit 1 401 | fi 402 | 403 | case "$COMMAND" in 404 | install|update) 405 | if [ -n "$VERSION" ] && [ "$TARGET" = "all" ]; then 406 | echo "Error: Cannot specify --version when installing 'all' apps." 407 | echo "Please install apps individually to specify versions." 408 | exit 1 409 | fi 410 | 411 | if [ "$TARGET" = "all" ]; then 412 | for app in $ALL_APPS; do 413 | install_app_logic "$app" "$FORCE" "" 414 | done 415 | else 416 | install_app_logic "$TARGET" "$FORCE" "$VERSION" 417 | fi 418 | ;; 419 | remove) 420 | if [ -z "$TARGET" ]; then 421 | echo "Please specify an app to remove." 422 | exit 1 423 | fi 424 | remove_app_logic "$TARGET" 425 | ;; 426 | list) 427 | echo "Installed Apps in $STORAGE_DIR:" 428 | ls -1 "$STORAGE_DIR"/*.version 2>/dev/null | while read f; do 429 | app=$(basename "$f" .version) 430 | ver=$(cat "$f") 431 | echo " - $app: $ver" 432 | done 433 | ;; 434 | *) 435 | echo "Unknown command: $COMMAND" 436 | exit 1 437 | ;; 438 | esac -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OpenWRT with NSS and many QoL 2 | 3 | This script builds OpenWRT from scratch for Dynalink DL-WRX36, with Network SubSystem, plus many Quality-of-Life packages and fixes. 4 | 5 | It's based on [jkool702 build](https://github.com/jkool702/openwrt-custom-builds)[²](https://forum.openwrt.org/t/full-featured-custom-build-for-dynalink-dl-wrx36-ax3600/180168), but uses the up-to-date [Agustin Lorenzo NSS Build](https://github.com/AgustinLorenzo/openwrt). It was created mashing up a lot of AI like Claude, Grok, Gemini, Copilot and Mistral, a lot of trial-and-error, and credits. Lot of credits. 6 | 7 | > [!WARNING] 8 | > 9 | > This build **tries** to save your device default configuration, but anyway, **save it before you flash** and then restore it. Some services will be disabled to avoid misconfiguration, and you will need to enable them again in `System → Startup`. Essentially, if `/etc/config/system` and `/etc/config/wireless` exist, these will be kept. 10 | 11 | ## Highlights: 12 | 13 | * **Performance** 14 | * **NSS enabled:** Offloads network to custom hardware, less CPU usage. 15 | * **[CPU Pining](files/etc/init.d/smp_affinity):** CPU0 for generic tasks. Ethernet & Crypto on CPU1. WiFi on CPU2. NSS Queue in CPU1+CPU3. 16 | * **Kernel tweaks:** Specific for Cortex-A53 arch. NEON (SIMD) enabled. CRC32, Crypto (AES/SHA1/SHA2) hardware accelerated. 1000Hz timer. 17 | * **50% more RAM:** It uses ZRAM (LZ4 compression) to minimize flash wear on high memory pressure (like for huge AdBlock lists). 18 | 19 | * **Networking** 20 | * **`10.0.0.0`:** Home Network. With `odhcpd` for superior IPv6 management, `unbound` for private/secure DNS handling. 21 | * **`192.168.0.0`:** IoT Network. No router access, only Internet. Isolated. Home Network can reach it, not viceversa. 22 | * **`LAN4` for IoT:** For IoT Hubs (Nest, Hue Bridge, HomePod, Alexa, etc.). Connected to IoT network. Can be _restored_ to normal LAN if you want (disable this interface and add it to the `lan` interface). 23 | * **[Firewall Rules](files/usr/bin/add-wan-rules-to-firewall):** Included to expose any service of the router by clicking a checkbox on the firewall. 24 | * **[Adblock](https://github.com/openwrt/packages/blob/master/net/adblock/files/README.md)**: Because. With `unbound`, you can with just enabling the firewall rules to intercept DNS connections at port 53, for both `lan` and/or `iot`. 25 | * **[Tailscale](https://tailscale.com/)**: Simple custom private network. With interface and zone. Router acts as exit node. Requires [minor setup](https://openwrt.org/docs/guide-user/services/vpn/tailscale/start). [Comes with custom panel](packages/luci-app-tailscale). 26 | * **[Zerotier](https://www.zerotier.com/)**: Advanced private cloud. Requires [minor setup](https://openwrt.org/docs/guide-user/services/vpn/zerotier). [Comes with custom panel](packages/luci-app-zerotier). 27 | * **Firewall, SQM and Interface setup scripts**: One-shot shell script to [configure your router the first time](#first-boot). 28 | * **[miniDLNA](https://openwrt.org/docs/guide-user/services/media_server/minidlna):** Small DLNA server for simple media sharing. Not needed if you use Plex or its own DLNA server. Requires external storage. 29 | * **[Easy SMB shares](files/etc/ksmbd/ksmbd.conf.template.example):** Robust, easy to use `ksmbd` template to mount your SSD/HDD/NVMe. Hardcoded `SMBUSER:SMBPASSWORD`. 30 | * **[Watchcat](https://openwrt.org/docs/guide-user/advanced/watchcat):** Restarts the WAN interface if Internet down (because some ISP Routers are dumb). 31 | * **[BanIP](https://openwrt.org/docs/guide-user/services/banip):** Want to block an IP, a Country, a DNS-over-HTTPS or a social network? Now you can, but you're on your own for the proper instructions. 32 | 33 | > [!NOTE] 34 | > 35 | > All of these services are disabled by default. You can enable them in `System → Startup`, and/or their own LUCI panel. 36 | 37 | * **Statistics** 38 | * **[NetData](https://github.com/netdata/netdata):** Powerful system data visualizer. [Configured to be lean](files/etc/netdata/netdata.conf). Pinned to CPU0. Disabled by default. 39 | * **[TTYD](https://tsl0922.github.io/ttyd/) + [btop](https://github.com/aristocratos/btop):** Show btop statistics at port `7682` with single unique process (great if you don't want to use netstat) with zero permissions (`nobody:nogroup`). 40 | 41 | * **Webapps** 42 | * **[Plex Media Server](https://plex.tv):** Great Media Server. Comes with LUCI panel. Available at `:32400`. 43 | * **[Aria2](https://aria2.github.io/):** Powerful & simple downloader, with headers and BitTorrent. Comes with [AriaNG](https://github.com/mayswind/AriaNg). Available at `/ariang`. 44 | * **[IT Tools](https://github.com/sharevb/it-tools):** Large toolshed for development and IT. Available at `:8080`. 45 | * **[BentoPDF](https://bentopdf.com/):** Excellent PDF manipulator. Available at `:8081`. 46 | * **[Pairdrop](https://pairdrop.net/):** Bran-dead easy local file sharing. Runs with [bun](https://bun.com/). Available at `:8082`. 47 | 48 | > [!NOTE] 49 | > 50 | > These webapps require external storage to be mounted at `/mnt/sda1` and to be [installed using the `webapps` command](#3-add-webapps). 51 | 52 | ### Build 53 | 54 | * **Unattended:** Interactive part first, build is last step. 55 | * **Offline:** Will work if there is no internet connection after you download everything. 56 | * **TMPFS + CCACHE:** Can build on RAM if 60GB+ available. Auto-installs `ccache` for faster re-builds. 57 | 58 | * **Critical Build Fixes (non-negotiable)** 59 | * **Binutils 2.44:** Injects a specific dependency rule (`all-bfd: all-libsframe`) to prevent race conditions during parallel builds. 60 | * **Kernel Makefile:** Uses a "Nuclear Option" to detect and sanitize corrupt `asm-arch` variables in the kernel Makefiles. 61 | * **Toolchain 3.5+:** Forces `-fPIC` for ZSTD host tools and enforces CMake policy version 3.5+ for those packages that still use the old version (and won't compile). 62 | 63 | * **Packages** 64 | * **Sources:** Integrates `fantastic-packages` and custom configs from `jkool702`, except some useless packages (for this build) 65 | * **Exclusions:** Automatically filters out known unstable packages (e.g., `shadowsocks-rust`, `pcap-dnsproxy`, `stuntman`) to prevent build errors. 66 | 67 | ### Caveats 68 | 69 | * **No remote packages:** Do not try to install packages from the Internet. You will need to compile them yourself and install manually, or you will have _a terrible time_. 70 | * **No attended sysupgrade:** You cannot "upgrade" to newer _generic_ builds. It's disabled. Compile the firmware yourself. 71 | 72 | --- 73 | 74 | ## How to install? 75 | 76 | 1. Create a Debian 13 (or later) container with [Distrobox](https://distrobox.it/), [Podman](https://podman.io/) or Docker. 77 | 78 | ```shell 79 | distrobox create -i debian:13 80 | ``` 81 | 82 | 2. Clone this repository on the directory of your choice inside the container, like `openwrt-build` 83 | 84 | ```shell 85 | git clone --depth 1 --single-branch \ 86 | https://github.com/DarkGhostHunter/OpenWRT-NSS-Full.git \ 87 | openwrt-build 88 | ``` 89 | 90 | 3. Execute the `build.sh` script. 91 | 92 | ```shell 93 | cd openwrt-build && git pull && ./build.sh 94 | ``` 95 | 96 | 4. Grab a cup of coffee and be ready to dive into the never-ending odyssey of building a custom OpenWRT firmware for sake of _pErFoRmAnCe aNd cOnVeNiEnCe_. Firmware and packages will be at `workdir/openwrt/bin/targets/qualcommax/ipq807x/`. 97 | 98 | 5. Follow OpenWRT instructions to [flash your device](https://openwrt.org/toh/dynalink/dl-wrx36), or do the usual _SysUpgrade_ from LUCI or SSH. 99 | 100 | 6. Router will reboot with the freshly flashed firmware. If you want some peace of mind, you can always restart so all changes are applied, but it shouldn't be necessary. 101 | 102 | > [!NOTE] 103 | > 104 | > If your SSH commands output something like `ssh_dispatch_run_fatal: ... error in libcrypto`, you will need to use SSH with some older crypto to connect. Without modifying your system, just run podman/docker and run the command inside it. 105 | > 106 | > ```shell 107 | > podman run -it --rm --network=host \ 108 | > docker.io/alpine:3.19 \ 109 | > /bin/sh -c "apk add --no-cache openssh-client && ssh -o HostKeyAlgorithms=+ssh-rsa -o PubkeyAcceptedKeyTypes=+ssh-rsa admin@192.168.1.1" 110 | > ``` 111 | 112 | --- 113 | 114 | ## First boot 115 | 116 | ### 1. WiFi and Usteer 117 | 118 | You will probably want to change both your WiFi SSID on both antennas into something like "MyOffice". Every time you change them, you will have to get into usteer configuration and point these WiFi SSID to be _steered_. This way, your devices will roam between both antennas depending on signal quality. 119 | 120 | Usteer already has some good-for-anything defaults about minimum signal strength and offset. 121 | 122 | > [!NOTE] 123 | > 124 | > As a rule of thumb, use wider channels for 5GHz if your _Channel Analysis_ (under _Status_) shows very far neighbors with very low noise. Otherwise, stick with a narrower channel for better reach. 125 | 126 | ### 2. Guest Network 127 | 128 | In some places, you may want to offer a "Guest network" so your guest can access the Internet through your router instead of relaying on celular (3G/4G/5G). If that your case, use the `configure-guest` script to create/remove a guest network and SSID. 129 | 130 | ```shell 131 | configure-guest 132 | ``` 133 | 134 | This script will create the "guest" interface, an SSID on the 5GHz antenna, and isolate the interface from other networks. Devices on this interface are isolated from each other. 135 | 136 | ### 3. Add WebApps 137 | 138 | Some handy tools that I always use are [Sharevb's fork](https://github.com/sharevb/it-tools) of [IT Tools](https://it-tools.tech/) and [BentoPDF](https://bentopdf.com/). 139 | 140 | Of course, these won't work without Internet, so I included a small script called `webapps` that downloads the latest versions and makes it available at a hardcoded port by reusing `uhttpd`. Just run `webapps` to install or remove them. 141 | 142 | ```shell 143 | webapps install it-tools 144 | ``` 145 | 146 | The script will download, repack them into SquashFS and mount it into `uhttpd` web server. Also, it will create rules in `uhttpd` to serve them in a hard-coded port. 147 | 148 | > [!NOTE] 149 | > 150 | > Webapps require a mounted external storage at `/mnt/sda1`, since these install at `/mnt/sda1/.webapps`. 151 | 152 | ### 4. Configure your firewall 153 | 154 | The [`configure-firewall`](files/usr/bin/configure-firewall) script disables the firewall entirely, allows OpenWRT management and ports accessible from `wan` (outside), or restores the defaults (enabled). 155 | 156 | If your OpenWRT router sits as a Dumb AP, you will probably want to access the management from the same network, so run this script first. 157 | 158 | ### 5. Configure your Interfaces 159 | 160 | The [`configure-interface`](files/usr/bin/configure-interface) script changes the router between the default Router mode, and the Managed Switch / Dumb AP without firewall. 161 | 162 | If your OpenWRT router won't be the main router, run that script first. Preferably, connect the WAN interface to your upstream router LAN ports, and it should be working immediately. 163 | 164 | ### 4. Speedtest to SQM tuning 165 | 166 | The [`speedtest-to-sqm`](files/usr/bin/speedtest-sqm) script configures SQM rules by _speedtesting_ your Internet. 167 | 168 | If your Internet connection gets unstable (unresponsive browsing, high latency, unstable videocalls) when being saturated, you may be victim of _bufferbloat_. Use this script to add rules to the traffic so prioritize network packets over others. 169 | 170 | You may test [your bufferbloat here](https://www.waveform.com/tools/bufferbloat). Usually asymmetric Internet connections (high download, low download) suffer from this. 171 | 172 | --- 173 | 174 | ## FAQ 175 | 176 | * **Do you plan to add that/this/my/her/his device?** 177 | 178 | No, unless I use it. I only use one and it works great. 179 | 180 | * **My build fails, can you help me?** 181 | 182 | Check the error logs, upload it to an AI and check what fix returns. 183 | 184 | * **The AI says I should edit something inside the `feeds` directory** 185 | 186 | Do not. Asks for a non-invasive fix. Post it here or make it a PR to integrate it to the script. Only put small fixes. 187 | 188 | * **Why `unbound` uses around 250MB of RAM!?** 189 | 190 | Because Adblock loads its source list of banned ip into unbound, hence why it consumes so much RAM. Still, around 50% of RAM left for anything. 191 | 192 | If you're using this router behind a network as a bridge or DNS is handled elsewhere, you may disable Adblock since this would be handled upstream. 193 | 194 | * **Help! My router restarts every 15 minutes or so!** 195 | 196 | It's because [Watchcat](files/etc/uci-defaults/z4-watchcat) default configuration. It will restart the **WAN interface** there is no Internet connection for 15 minutes. 197 | 198 | If you are offline, disable it through the LUCI panel, via SSH (`/etc/init.d/watchcat disable && /etc/init.d/watchcat stop`) or just delete the rules. 199 | 200 | * **Can you include Jellyfin instead of Plex?** 201 | 202 | No, mostly because you will need Docker/Podman or [`chroot`](https://openwrt.org/docs/guide-user/services/chroot), and I'm not familiar with that setup. I would if someone decided to create a small package for it, like [I did for Plex Media Server](packages/luci-app-plexmediaserver). 203 | 204 | * **My router DHCP server dies and I have to resort to use manual DHCP config** 205 | 206 | Remove your DHCP static leases from `etc/config/dhcp` (`Network → DHCP → Leases`) and restart `odhcpd` (`service odhcpd restart`). If it doesn't fail, then **ensure all your static leases have a valid MAC address**. Use `02:xx:xx:xx:xx:xx` as placeholder if necessary. 207 | 208 | * **I'm a heavy VPN user. What can I do for better performance?** 209 | 210 | Get a bigger router? Apart from that, not much. You won't get 1Gbps+ speeds due to [NSS currently not handling this kind of traffic natively](https://github.com/qosmio/openwrt-ipq?tab=readme-ov-file#nss-support-matrix): 211 | 212 | - TLS/DTLS is not available for NSS firmware 11.4-12.5, required for OpenVPN. 213 | - IPSEC is not available for NSS firmware 11.4-12.5, required for WireGuard (Tailscale and Headscale). 214 | - NSS offloading is not compatible with user-space load, like ZeroTier. 215 | 216 | The only resort is to move your VPN processes to the same core handled by NSS to keep them close to the CPU cache for latency. 217 | 218 | Start the `/etc/init.d/smp_affinity_vpn` service to apply the changes, and restart the network. It will move the encryption engine and the processes to CPU3. 219 | 220 | ```shell 221 | /etc/init.d/smp_affinity_vpn start 222 | /etc/init.d/smp_affinity_vpn enable 223 | /etc/init.d/network restart 224 | ``` 225 | 226 | To disable this, just restart the standard `spm_affinity` service to go back to the normal CPU pinning, disable the vpn-related service and restart the network. 227 | 228 | ```shell 229 | /etc/init.d/spm_affinity restart 230 | /etc/init.d/smp_affinity_vpn disable 231 | /etc/init.d/network restart 232 | ``` 233 | 234 | * **Will you keep updated this?** 235 | 236 | Until it hits the next OpenWRT stable release, and then it will stay there until there meaningful performance optimizations for the network stack, which after NSS I don't see meaningful except for VPN (TLS/DTLS, IPSEC). 237 | 238 | So yes, but I don't expect flashing this every month on the Router. --------------------------------------------------------------------------------