"
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.
--------------------------------------------------------------------------------