├── .github └── workflows │ └── build-firmware.yaml ├── .gitignore ├── README.md ├── builds └── version-tp-link_archer_c7_v5.txt ├── config-generator ├── index.js └── package.json ├── server ├── Iran_server │ └── Iran_server_setup.sh └── chisel_config_manager │ ├── chisele_config_manager.js │ ├── nginx │ ├── confd.conf │ └── moduled.conf │ └── package.json ├── src ├── .gitignore ├── build.bash └── files │ ├── etc │ ├── config │ │ ├── firewall │ │ ├── network.d │ │ │ └── tplink_archer-c7-v5.conf │ │ ├── pbr │ │ ├── routro │ │ ├── rpcd │ │ ├── tinyproxy │ │ ├── uhttpd │ │ └── users │ ├── crontabs │ │ └── root │ ├── dropbear │ │ └── authorized_keys │ ├── hotplug.d │ │ └── iface │ │ │ └── 99-custom │ ├── init.d │ │ ├── chisel │ │ ├── outlineGate │ │ └── wifipass │ └── rc.local │ ├── usr │ ├── bin │ │ ├── binauth.sh │ │ ├── check_chisel.sh │ │ ├── dragon.sh │ │ ├── manage_mac_access.sh │ │ ├── proxymaster.sh │ │ ├── router_updater.sh │ │ ├── set_admin_pass.sh │ │ ├── shservice.lua │ │ ├── wg_prepare_config.sh │ │ ├── wg_scripts.sh │ │ └── wrong_wifi_pass_checker.sh │ └── share │ │ ├── rpcd │ │ └── acl.d │ │ │ └── ubus-routro.json │ │ └── ubus │ │ └── dragon.json │ └── www │ ├── .DS_Store │ ├── cgi-bin │ ├── api │ └── dashboard.lua │ ├── dashboard │ ├── citylink.html │ ├── dashboard.html │ ├── ethernet.html │ ├── firmware.html │ ├── guest.html │ ├── index.html │ ├── management.html │ ├── outline.html │ ├── proxy.html │ ├── scripts │ │ ├── common │ │ │ ├── bootstrap.bundle.min.js │ │ │ ├── heartBeat.js │ │ │ ├── loading.js │ │ │ └── ubus.js │ │ └── page-specific │ │ │ ├── citylink.js │ │ │ ├── dashboard.js │ │ │ ├── ethernet.js │ │ │ ├── firmware.js │ │ │ ├── guest.js │ │ │ ├── index.js │ │ │ ├── management.js │ │ │ ├── outline.js │ │ │ ├── proxy.js │ │ │ ├── settings.js │ │ │ ├── vpn.js │ │ │ └── wifi.js │ ├── settings.html │ ├── styles │ │ ├── bootstrap.min.css │ │ ├── index.css │ │ ├── management.css │ │ └── style.css │ ├── vpn.html │ └── wifi.html │ ├── favicon.ico │ ├── nlink-dashboard │ └── index.html │ └── portal │ ├── auth.lua │ ├── bootstrap.min.css │ ├── failed.html │ ├── images │ ├── nocensored.jpg │ └── splash.jpg │ ├── splash.css │ ├── splash.html │ ├── status.html │ └── success.html └── utils ├── IR_IP_LIST └── ip_script.bash /.github/workflows/build-firmware.yaml: -------------------------------------------------------------------------------- 1 | name: Build the Neighbor Link Firmware 2 | 3 | on: 4 | push: 5 | release: 6 | types: [published] 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-22.04 11 | permissions: 12 | contents: write 13 | strategy: 14 | fail-fast: false 15 | matrix: 16 | profile: 17 | - "tplink_archer-c7-v5" 18 | - "tplink_archer-c7-v2" 19 | - "tplink_archer-a7-v5" 20 | - "glinet_gl-mt300a" 21 | - "tplink_archer-ax23-v1" 22 | 23 | steps: 24 | - name: Checkout Code 25 | uses: actions/checkout@v4 26 | 27 | - name: Set up Build Environment 28 | run: | 29 | sudo apt-get update -y 30 | sudo apt-get install -y \ 31 | curl build-essential libncurses-dev zlib1g-dev \ 32 | gawk git gettext libssl-dev xsltproc rsync unzip \ 33 | python3 python3-distutils jq 34 | 35 | - name: Build the Firmware 36 | if: github.event_name != 'release' 37 | run: | 38 | cd src 39 | bash build.bash ${GITHUB_SHA::6} ${{ matrix.profile }} 40 | 41 | - name: Upload Build Artifacts 42 | uses: actions/upload-artifact@v4 43 | if: github.event_name != 'release' 44 | with: 45 | name: ${{ matrix.profile }}_${{ github.sha }} 46 | path: src/build/* 47 | 48 | - name: Build the Firmware (Release) 49 | if: github.event_name == 'release' 50 | run: | 51 | cd src 52 | bash build.bash ${{ github.event.release.tag_name }} ${{ matrix.profile }} 53 | 54 | - name: Attach Artifacts to Release 55 | uses: svenstaro/upload-release-action@v2 56 | if: github.event_name == 'release' 57 | with: 58 | repo_token: ${{ secrets.GITHUB_TOKEN }} 59 | file: src/build/* 60 | tag: ${{ github.ref }} 61 | file_glob: true 62 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.tar.xz 2 | *.bin 3 | ramips/ 4 | TODO 5 | src/openwrt-imagebuilder* -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NeighborLink 2 | 3 | ## [Installation Guide](https://github.com/nasnet-community/solutions/tree/main/neighbor-link) 4 | 5 | نیبرلینک سیستمی است که به افرادی که در شعاع پوشش یک دستگاه استارلینک و اکستندرهای آن قرار دارند، اجازه می‌دهد حتی در شرایطی که اینترنت داخلی و بین‌المللی یک کشور به‌طور کامل قطع شده است، به اینترنت امن و پرسرعت دسترسی پیدا کنند. 6 | 7 | نیبرلینک یک سیستم‌عامل OpenWRT شخصی‌سازی‌شده است که به مدیر سیستم امکان می‌دهد اینترنت استارلینک را با کاربران دیگر، مثلاً همسایه‌ها، به اشتراک بگذارد. این سیستم در حال حاضر امکاناتی مانند مدیریت کاربران، تفکیک مسیر (Split Tunneling) و مخفی‌سازی آی‌پی با استفاده از وی‌پی‌ان را فراهم می‌کند. در آینده، قابلیت‌های دیگری مانند لیست سفید (Whitelisting)، لیست سیاه (Blacklisting) و مدیریت ترافیک به نیبرلینک افزوده خواهد شد. 8 | 9 | سیتی لینک (CityLink) یکی از گزینه‌های نیبرلینک است که با استفاده از آن می‌توان از راه دور و با استفاده از اینترنت داخلی یک کشور، به‌صورت امن و ناشناس به استارلینک متصل شد. در حال حاضر، می‌توان با پروکسی‌هایی مانند FoxyProxy و Potasto و همچنین با استفاده از Outline، از سیتی لینک استفاده کرد. 10 | 11 | NeighborLink enables individuals in close proximity to a Starlink terminal to retain secure internet access, demonstrating its effectiveness in circumventing widespread internet restrictions. 12 | The core advantage of NeighborLink is providing resilient local internet access, designed to be impervious to nationwide shutdowns. 13 | 14 | In scenarios where the government enforces a complete internet blackout, this system ensures that individuals with local access, for example, residents within a building, maintain uninterrupted internet connectivity. This is done by bypassing the Starlink’s proprietary router to use a generic off-the-shelf router and install customized OpenWRT OS on the router to share the Starlink’s internet bandwidth. 15 | 16 | Key developments including a user management dashboard, split tunneling and VPN technology have been implemented, with future enhancements planned for whitelisting, blacklisting, and traffic management. 17 | 18 | CityLink is a feature to enable remote use of Starlink via the domestic internet. In essence, CityLink is composed of two separate methods: 19 | Proxy (such as FoxyProxy and Potatso) 20 | Outline. 21 | Both the administrator and users can utilize either or both methods according to their needs. 22 | The CityLink solution was designed with an understanding of, and in response to, the dual structure of Iran's internet - differences in domestic and international internet censorship. 23 | 24 | The solution includes integrating a cloud server, empowering users to create personal servers, and implementing firewalls to conceal satellite connections. An assessment of Iranian internet providers favored fiber internet and point-to-point connections for their performance and security. 25 | -------------------------------------------------------------------------------- /builds/version-tp-link_archer_c7_v5.txt: -------------------------------------------------------------------------------- 1 | new_version=0.0.267 2 | firmwareUrl=https://s3-firmware-releases.s3.us-west-1.amazonaws.com/v0.0.267-openwrt-23.05.2-ath79-generic-tplink_archer-c7-v5-squashfs-sysupgrade.bin 3 | fimwareUrlFactory=https://s3-firmware-releases.s3.us-west-1.amazonaws.com/v0.0.267-openwrt-23.05.2-ath79-generic-tplink_archer-c7-v5-squashfs-factory.bin 4 | -------------------------------------------------------------------------------- /config-generator/index.js: -------------------------------------------------------------------------------- 1 | const http = require('http'); 2 | const fs = require('fs').promises; // Use promises-based file operations 3 | const path = require('path'); 4 | const url = require('url'); 5 | const queryString = require('querystring'); 6 | 7 | // Define the folder where configuration files are stored 8 | const configFolder = path.join(__dirname, 'config'); 9 | 10 | // Create an HTTP server 11 | const server = http.createServer(async (req, res) => { 12 | try { 13 | const urlData = url.parse(req.url); // Parse the URL 14 | const queryParams = queryString.parse(urlData.query); // Parse the query string using querystring 15 | const queryId = queryParams["id"]; // Extract the 'id' from the query string 16 | 17 | // Validate the 'id' format (MD5 hash of a MAC address) 18 | if (!isValidId(queryId)) { 19 | sendResponse(res, 400, 'Invalid ID.'); 20 | return; 21 | } 22 | 23 | // Path for the 'id.conf' file 24 | const configFile = path.join(configFolder, `${queryId}.conf`); 25 | 26 | // Check if 'id.conf' exists 27 | if (await fileExists(configFile)) { 28 | // Read and return the content of the 'id.conf' file 29 | const data = await fs.readFile(configFile, 'utf8'); 30 | sendResponse(res, 200, data, 'text/plain'); 31 | } else { 32 | // Find the first 'peerx.conf' file 33 | const peerConfigFile = await findFirstPeerConfig(); 34 | if (!peerConfigFile) { 35 | sendResponse(res, 404, 'No peer configuration found.'); 36 | return; 37 | } 38 | 39 | // Read and return the content of the 'peerx.conf' file 40 | const data = await fs.readFile(peerConfigFile, 'utf8'); 41 | 42 | // Rename 'peerx.conf' to 'id.conf' 43 | const newConfigFile = path.join(configFolder, `${queryId}.conf`); 44 | await fs.rename(peerConfigFile, newConfigFile); 45 | 46 | // Return the file data 47 | sendResponse(res, 200, data, 'text/plain'); 48 | } 49 | } catch (err) { 50 | console.error(err); 51 | sendResponse(res, 500, 'Internal Server Error.'); 52 | } 53 | }); 54 | 55 | // Start the server on port 8080 56 | server.listen(8080, () => { 57 | console.log('Server listening on port 8080'); 58 | }); 59 | 60 | /** 61 | * Validate the 'id' format (MD5 hash of a MAC address). 62 | * @param {string} id 63 | * @returns {boolean} Returns true if valid, otherwise false. 64 | */ 65 | function isValidId(id) { 66 | return /^[a-fA-F0-9]{32}$/.test(id); 67 | } 68 | 69 | /** 70 | * Check if a file exists. 71 | * @param {string} filePath Path to the file. 72 | * @returns {Promise} Returns true if the file exists, otherwise false. 73 | */ 74 | async function fileExists(filePath) { 75 | try { 76 | await fs.access(filePath); 77 | return true; 78 | } catch { 79 | return false; 80 | } 81 | } 82 | 83 | /** 84 | * Find the first 'peerx.conf' file in the config folder. 85 | * @returns {Promise} The path of the first 'peerx.conf' file, or null if none is found. 86 | */ 87 | async function findFirstPeerConfig() { 88 | try { 89 | const files = await fs.readdir(configFolder); 90 | for (const file of files) { 91 | if (file.startsWith('peer') && file.endsWith('.conf')) { 92 | return path.join(configFolder, file); 93 | } 94 | } 95 | return null; 96 | } catch (err) { 97 | console.error('Error reading config folder:', err); 98 | return null; 99 | } 100 | } 101 | 102 | /** 103 | * Send an HTTP response. 104 | * @param {http.ServerResponse} res HTTP response object. 105 | * @param {number} statusCode HTTP status code. 106 | * @param {string} message Response message to be sent. 107 | * @param {string} [contentType='text/plain'] Optional content type. 108 | */ 109 | function sendResponse(res, statusCode, message, contentType = 'text/plain') { 110 | res.statusCode = statusCode; 111 | res.setHeader('Content-Type', contentType); 112 | res.end(message); 113 | } 114 | -------------------------------------------------------------------------------- /config-generator/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "returnpeer", 3 | "version": "1.0.0", 4 | "description": "Return the wireguard peer.conf from s3", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC" 11 | } 12 | -------------------------------------------------------------------------------- /server/Iran_server/Iran_server_setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Check if the script is running as root or with sudo 4 | if [[ ! $(id -u) -eq 0 && ! $(id -g) -eq 0 ]]; then 5 | echo "This script requires root or sudo privileges." 6 | exit 1 7 | fi 8 | 9 | # Check if /etc/os-release file exists 10 | if [[ -f /etc/os-release ]]; then 11 | # Source the os-release file to get the OS name and version 12 | . /etc/os-release 13 | 14 | # Check if the operating system is not Ubuntu 15 | if [[ "$ID" != "ubuntu" ]]; then 16 | echo "This script is only supported on Ubuntu systems." 17 | exit 1 18 | fi 19 | 20 | # Check if the Ubuntu version is below 20.04 21 | if [[ "$(printf '%s\n' "20.04" "$VERSION_ID" | sort -V | head -n1)" != "20.04" ]]; then 22 | echo "This script requires Ubuntu 20.04 or later." 23 | exit 1 24 | fi 25 | 26 | else 27 | # If /etc/os-release does not exist, prompt the user 28 | echo "/etc/os-release file not found. Are you running Ubuntu 20.04 or later? (yes/no)" 29 | read -r confirmation 30 | if [[ "$confirmation" != "yes" ]]; then 31 | echo "This script is only supported on Ubuntu 20.04 or later." 32 | exit 1 33 | fi 34 | fi 35 | 36 | 37 | # Default values 38 | DEFAULT_CHISEL_VERSION="1.10.0" 39 | DEFAULT_CHISEL_PORT=8080 40 | 41 | 42 | # User options 43 | CHOOSE_DEPLOY=1 44 | CHOOSE_RETRIEVE=2 45 | CHOOSE_STOP_DELETE=3 46 | CHOOSE_LIST_SERVICES=4 47 | 48 | # Function to validate client key 49 | validate_client_key() { 50 | if [[ ! -z "$CLIENT_KEY" && "${#CLIENT_KEY}" -gt 8 ]]; then 51 | return 0 52 | else 53 | echo "Invalid client key. It should be a non-empty string longer than 16 characters." 54 | return 1 55 | fi 56 | } 57 | 58 | # Function to check domain existence 59 | check_domain_existence() { 60 | if [[ ! -z "$DOMAIN" ]]; then 61 | host "$DOMAIN" > /dev/null 2>&1 62 | if [[ $? -eq 0 ]]; then 63 | return 0 64 | else 65 | echo "Domain '$DOMAIN' does not exist." 66 | return 1 67 | fi 68 | fi 69 | } 70 | 71 | # Function to generate the string 72 | generate_string() { 73 | if [[ -z "$DOMAIN" ]]; then 74 | SERVER_IP=$(hostname -I | awk '{print $1}') 75 | else 76 | SERVER_IP="$DOMAIN" 77 | fi 78 | 79 | echo "InRe:$CLIENT_KEY:$PASSWORD:$DEFAULT_CHISEL_PORT:$EXT_PORT1:$INT_PORT1:$EXT_PORT2:$INT_PORT2@$SERVER_IP" 80 | } 81 | 82 | # Function to generate a random number within a specified range, excluding a given number 83 | function generate_random_number() { 84 | local min=$1 85 | local max=$2 86 | local exclude=$3 87 | local random_number 88 | 89 | while true; do 90 | random_number=$((RANDOM % (max - min + 1) + min)) 91 | if [[ $random_number -ne $exclude ]]; then 92 | break 93 | fi 94 | done 95 | 96 | echo "$random_number" 97 | } 98 | 99 | 100 | # Function to deploy the service 101 | deploy_service() { 102 | 103 | CLIENT_KEY=$(openssl rand -hex 12) 104 | 105 | echo "Please enter the domain associated with this server. This is an optional step, but it is required if you plan to use the Proxy service." 106 | read DOMAIN 107 | 108 | validate_client_key 109 | 110 | if [[ $? -eq 0 ]]; then 111 | 112 | # Install required packages 113 | apt update 114 | apt install jq openssl -y 115 | check_domain_existence 116 | 117 | if [[ $? -eq 0 ]]; then 118 | apt install squid snapd nginx jq curl apache2-utils -y 119 | snap install certbot --classic 120 | fi 121 | 122 | # Download and configure chisel 123 | wget https://github.com/jpillora/chisel/releases/download/v${DEFAULT_CHISEL_VERSION}/chisel_${DEFAULT_CHISEL_VERSION}_linux_amd64.gz 124 | if [[ $? -ne 0 ]]; then 125 | echo "Error in downloadin the service from github, If github blocked in your country please use proxy" 126 | exit 1; 127 | fi 128 | gunzip chisel_${DEFAULT_CHISEL_VERSION}_linux_amd64.gz 129 | sudo mkdir -p /etc/chisel/clients 130 | mv chisel_${DEFAULT_CHISEL_VERSION}_linux_amd64 /etc/chisel/chisel_v${DEFAULT_CHISEL_VERSION} 131 | chmod +x /etc/chisel/chisel_v${DEFAULT_CHISEL_VERSION} 132 | 133 | # Create chisel user file 134 | PASSWORD=$(openssl rand -hex 19) 135 | echo "{ \"$CLIENT_KEY:$PASSWORD\" : [ \"\" ] }" > /etc/chisel/users.json 136 | 137 | # Create systemd file for chisel 138 | printf "%s\n"\ 139 | "[Unit]" \ 140 | "Description=Chisel Server $DEFAULT_CHISEL_VERSION" \ 141 | ""\ 142 | "[Service]" \ 143 | "User=root" \ 144 | "Type=simple" \ 145 | "ExecStart=/etc/chisel/chisel_v${DEFAULT_CHISEL_VERSION} server -p $DEFAULT_CHISEL_PORT --reverse --authfile /etc/chisel/users.json" \ 146 | "Restart=on-failure" \ 147 | "RestartSec=2s" \ 148 | ""\ 149 | "[Install]" \ 150 | "WantedBy=multi-user.target" > /etc/systemd/system/chisel.service 151 | 152 | systemctl enable chisel.service 153 | systemctl start chisel.service 154 | 155 | 156 | EXT_PORT1=$(generate_random_number 1024 48151 $DEFAULT_CHISEL_PORT) 157 | EXT_PORT2=$(generate_random_number 1024 48151 $DEFAULT_CHISEL_PORT) 158 | while [[ $EXT_PORT2 -eq $EXT_PORT1 ]];do 159 | EXT_PORT2=$(generate_random_number 1024 48151 $DEFAULT_CHISEL_PORT) 160 | done 161 | 162 | # Open ports 163 | ufw allow $DEFAULT_CHISEL_PORT/tcp 164 | ufw allow 22/tcp 165 | ufw allow 80/tcp 166 | ufw allow 443/tcp 167 | ufw allow $EXT_PORT1/tcp 168 | ufw allow $EXT_PORT2/tcp 169 | ufw enable 170 | 171 | 172 | INT_PORT1=$(generate_random_number 48152 49151 $DEFAULT_CHISEL_PORT) 173 | INT_PORT2=$(generate_random_number 48152 49151 $INT_PORT1) 174 | 175 | 176 | 177 | if [[ ! -z "$DOMAIN" ]]; then 178 | 179 | # Create nginx config 180 | printf "%s\n"\ 181 | "server {" \ 182 | " server_name $DOMAIN;" \ 183 | " root /var/www/example.com;" \ 184 | " index index.html;" \ 185 | " location / {"\ 186 | " error_page 404 /404.html;"\ 187 | " }" \ 188 | " listen [::]:80;"\ 189 | " listen 80;"\ 190 | "}" > /etc/nginx/conf.d/chisel_ssl.conf 191 | 192 | # Obtain SSL certificate 193 | certbot --nginx -d "$DOMAIN" -m info@${DOMAIN} --agree-tos --non-interactive 194 | 195 | # Todo : Setup Squid with split tunneling and parent proxy 196 | echo ".ir" > /etc/squid/domains.txt 197 | wget https://github.com/bootmortis/iran-hosted-domains/releases/download/202409020032/domains.txt 198 | grep -v "\.ir$" domains.txt > domains.txt.2 199 | sed 's/^././' domains.txt.2 >> /etc/squid/domains.txt 200 | 201 | htpasswd -bc /etc/squid/.squid_users ${CLIENT_KEY} ${PASSWORD} 202 | 203 | printf "%s\n"\ 204 | "https_port $EXT_PORT1 tls-cert=/etc/letsencrypt/live/$DOMAIN/fullchain.pem tls-key=/etc/letsencrypt/live/$DOMAIN/privkey.pem"\ 205 | "auth_param basic program /usr/lib/squid/basic_ncsa_auth /etc/squid/.squid_users"\ 206 | "auth_param basic children 5"\ 207 | "auth_param basic realm Proxy Authentication Required"\ 208 | "auth_param basic credentialsttl 2 hours"\ 209 | "auth_param basic casesensitive on"\ 210 | "acl auth_users proxy_auth REQUIRED"\ 211 | "http_access allow auth_users"\ 212 | "http_access deny all"\ 213 | "cache_peer 127.0.0.1 parent $INT_PORT1 0 name=P-1 round-robin no-query weight=5 login=$CLIENT_KEY:$PASSWORD"\ 214 | "acl ir_domain dstdomain '/etc/squid/domains.txt'"\ 215 | "cache_peer_access P-1 allow ir_domain"\ 216 | "never_direct allow !ir_domain"\ 217 | "visible_hostname 'HidenLayer'" > /etc/squid/squid.conf 218 | 219 | systemctl restart squid 220 | 221 | fi 222 | 223 | # Generate and display string 224 | generate_string 225 | echo "" 226 | echo "" 227 | echo "Copy the following string into the CityLink dashboard:" 228 | echo "" 229 | echo -e "\033[0;32m$(generate_string)\033[0m" 230 | 231 | echo "" 232 | echo "" 233 | 234 | # Save string to file 235 | echo "$(generate_string)" > /etc/chisel/clients/client.str 236 | fi 237 | 238 | exit 0 239 | } 240 | 241 | # Function to retrieve active service config 242 | retrieve_service_config() { 243 | # Implement logic to retrieve active service config 244 | echo "Retrieving active service config..." 245 | KEYSTRING=$(cat /etc/chisel/clients/client.str) 246 | 247 | echo "" 248 | echo "" 249 | echo "Copy the following string into the CityLink dashboard:" 250 | echo "" 251 | echo -e "\033[0;32m$KEYSTRING\033[0m" 252 | 253 | echo "" 254 | echo "" 255 | 256 | exit 0; 257 | } 258 | 259 | # Function to stop and delete the service 260 | stop_delete_service() { 261 | # Implement logic to stop and delete the service 262 | echo "Stopping and deleting the service..." 263 | } 264 | 265 | # Function to show the list of available services 266 | list_services() { 267 | # Implement logic to show the list of available services 268 | echo "Listing available services..." 269 | } 270 | 271 | # Main script logic 272 | while true; do 273 | echo "Choose an option:" 274 | echo "1. Deploy the new service" 275 | echo "2. Retrieve the active service config" 276 | echo "5. Exit" 277 | read CHOICE 278 | 279 | case "$CHOICE" in 280 | 1) deploy_service ;; 281 | 2) retrieve_service_config ;; 282 | 5) exit ;; 283 | *) echo "Invalid choice. Please select a number from 1, 2 and 5." ;; 284 | esac 285 | done -------------------------------------------------------------------------------- /server/chisel_config_manager/chisele_config_manager.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const fs = require('fs'); 3 | const app = express(); 4 | const PORT = 19119; 5 | 6 | // File paths as variables 7 | const userFilePath = '/etc/chisel/users.json'; 8 | const portFilePath = './assignedport.json'; 9 | const o_portFilePath = './o_assignedport.json'; 10 | 11 | const portRange=19120 12 | const portCount=41 13 | 14 | const o_portRange=52120 15 | const o_portCount=41 16 | 17 | app.get('/assign', (req, res) => { 18 | const token = req.query.token; 19 | if (!token) { 20 | return res.json({ code: 0, port: null }); 21 | } 22 | 23 | const users = JSON.parse(fs.readFileSync(userFilePath, 'utf8')); 24 | const assignedPorts = JSON.parse(fs.readFileSync(portFilePath, 'utf8')); 25 | const o_assignedPorts = JSON.parse(fs.readFileSync(o_portFilePath, 'utf8')); 26 | 27 | // Reverse the token to get the password 28 | const password = token.split('').reverse().join(''); 29 | users[`${token}:${password}`] = [""]; 30 | 31 | // Write to user.json 32 | fs.writeFileSync(userFilePath, JSON.stringify(users, null, 2)); 33 | 34 | let port = assignedPorts[token]; 35 | if (!port) { 36 | // Find a random port that's not already assigned 37 | const availablePorts = Array.from({ length: portCount }, (_, i) => i + portRange) 38 | .filter(p => !Object.values(assignedPorts).includes(p)); 39 | 40 | if (availablePorts.length === 0) { 41 | return res.json({ code: -1, port: null }); 42 | } 43 | 44 | port = availablePorts[Math.floor(Math.random() * availablePorts.length)]; 45 | assignedPorts[token] = port; 46 | 47 | // Write to assignedport.json 48 | fs.writeFileSync(portFilePath, JSON.stringify(assignedPorts, null, 2)); 49 | } 50 | 51 | let o_port = o_assignedPorts[token]; 52 | if (!o_port) { 53 | // Find a random port that's not already assigned 54 | const availablePorts = Array.from({ length: o_portCount }, (_, i) => i + o_portRange) 55 | .filter(p => !Object.values(o_assignedPorts).includes(p)); 56 | 57 | if (availablePorts.length === 0) { 58 | return res.json({ code: -1, port: null, outlineport:null }); 59 | } 60 | 61 | o_port = availablePorts[Math.floor(Math.random() * availablePorts.length)]; 62 | o_assignedPorts[token] = o_port; 63 | 64 | // Write to assignedport.json 65 | fs.writeFileSync(o_portFilePath, JSON.stringify(o_assignedPorts, null, 2)); 66 | } 67 | 68 | res.json({ code: 1, port: port, outlineport: o_port }); 69 | }); 70 | 71 | app.listen(PORT, () => { 72 | console.log(`Server running on port ${PORT}`); 73 | }); 74 | 75 | 76 | 77 | // Function to check and create file with empty JSON object 78 | function checkAndCreateFile(filePath) { 79 | if (!fs.existsSync(filePath)) { 80 | fs.writeFileSync(filePath, '{}', 'utf8'); 81 | console.log(`Created file: ${filePath}`); 82 | } else { 83 | console.log(`File already exists: ${filePath}`); 84 | } 85 | } 86 | 87 | // Check and create files 88 | checkAndCreateFile(userFilePath); 89 | checkAndCreateFile(portFilePath); 90 | checkAndCreateFile(o_portFilePath); -------------------------------------------------------------------------------- /server/chisel_config_manager/nginx/confd.conf: -------------------------------------------------------------------------------- 1 | server { 2 | 3 | server_name server_name.domain.com; 4 | 5 | root /var/www/example.com; 6 | index index.html; 7 | 8 | location / { 9 | proxy_pass http://127.0.0.1:19119; 10 | proxy_http_version 1.1; 11 | proxy_set_header Upgrade $http_upgrade; 12 | proxy_set_header Connection 'upgrade'; 13 | proxy_set_header Host $host; 14 | error_page 404 /404.html; 15 | } 16 | 17 | listen 80; 18 | listen [::]:80; 19 | } -------------------------------------------------------------------------------- /server/chisel_config_manager/nginx/moduled.conf: -------------------------------------------------------------------------------- 1 | stream{ 2 | 3 | # Define a map block to map source ports to destination ports 4 | map $server_port $backend_port { 5 | ~^192([0-9])$ 1912$1; 6 | ~^193([0-9])$ 1913$1; 7 | ~^194([0-9])$ 1914$1; 8 | ~^195([0-9])$ 1915$1; 9 | ~^196([0-9])$ 1916$1; 10 | } 11 | 12 | 13 | # SSL configuration for the server 14 | server { 15 | listen 1920-1960 ; 16 | #ssl_certificate /etc/letsencrypt/live/chiselmaster.wachi.ir/fullchain.pem; 17 | #ssl_certificate_key /etc/letsencrypt/live/chiselmaster.wachi.ir/privkey.pem; 18 | #ssl_protocols TLSv1.2 TLSv1.3; 19 | #ssl_ciphers HIGH:!aNULL:!MD5; 20 | 21 | # Use the mapped port for proxy_pass 22 | proxy_pass 127.0.0.1:$backend_port; 23 | } 24 | } -------------------------------------------------------------------------------- /server/chisel_config_manager/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chisel-config-manager", 3 | "version": "1.1.2", 4 | "description": "A configuration manager for Chisel.", 5 | "main": "chisel_config_manager.js", 6 | "scripts": { 7 | "start": "node chisel_config_manager.js" 8 | }, 9 | "author": "routro", 10 | "license": "ISC", 11 | "dependencies": { 12 | "express": "^4.17.1" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/.gitignore: -------------------------------------------------------------------------------- 1 | version-*.txt -------------------------------------------------------------------------------- /src/build.bash: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # This script is designed to run on Ubuntu systems with AMD64 architecture. 4 | # Please ensure you are running this script on a compatible system. 5 | 6 | set -e 7 | 8 | #Openwrt Version 9 | wrt_version="23.05.2" 10 | 11 | #Target Device 12 | # Profile Arch Chip 13 | targets=( "tplink_archer-c7-v5 ath79 generic" \ 14 | "tplink_archer-c7-v2 ath79 generic" \ 15 | "tplink_archer-a7-v5 ath79 generic" \ 16 | "glinet_gl-mt300a ramips mt7620" \ 17 | "tplink_archer-ax23-v1 ramips mt7621" 18 | ) 19 | 20 | declare -A target_info 21 | for entry in "${targets[@]}"; do 22 | target_info["$(echo "$entry" | awk '{print $1}')"]="$(echo "$entry" | cut -d' ' -f2-)" 23 | done 24 | 25 | # Excluded packages 26 | EXCLUDE_PACKAGES='-dnsmasq -wpad-basic-mbedtls' 27 | 28 | # Included Packages 29 | INCLUDE_PACKAGES='curl dnsmasq-full luci luci-base iwinfo wireguard-tools kmod-nft-core kmod-nft-fib kmod-nft-nat kmod-nft-offload mtd ubus ubusd rpcd rpcd-mod-file rpcd-mod-iwinfo uci uhttpd uhttpd-mod-ubus gnupg tinyproxy jq coreutils-stat coreutils-nohup lua luasocket uhttpd-mod-lua coreutils-base64 wpad-openssl pbr kmod-br-netfilter kmod-ipt-physdev iptables-mod-physdev' 30 | 31 | FILES="../files" 32 | 33 | BUILD_DIR="build" 34 | rm -rf $BUILD_DIR 35 | mkdir -p $BUILD_DIR 36 | 37 | # The new release version should be transfered as a first variable 38 | if [ -n "$1" ]; then 39 | release_version=$1 40 | else 41 | release_version="0.0.0" 42 | fi 43 | 44 | if [ -n "$2" ]; then 45 | profiles=$2 46 | else 47 | profiles="${!target_info[*]}" 48 | fi 49 | 50 | for profile in $profiles; do 51 | 52 | IFS=' ' read -r cpu_arch chipset <<< "${target_info[$profile]}" 53 | 54 | PATH_PART="$wrt_version-$cpu_arch-$chipset" 55 | 56 | download_url="https://archive.openwrt.org/releases/$wrt_version/targets/$cpu_arch/$chipset/openwrt-imagebuilder-$PATH_PART.Linux-x86_64.tar.xz" 57 | 58 | rm -rf openwrt-imagebuilder-* 59 | curl -fsSL "$download_url" -O 60 | tar -J -x -f openwrt-imagebuilder-"$PATH_PART".Linux-x86_64.tar.xz 2>/dev/null > /dev/null 61 | 62 | sed -i "s/option version .*/option version '$release_version'/" "files/etc/config/routro" 63 | sed -i "s/option profile .*/option profile '$profile'/" "files/etc/config/routro" 64 | 65 | # Check and copy profile-specific network config if it exists 66 | if [ -f "files/etc/config/network.d/$profile.conf" ]; then 67 | cp "files/etc/config/network.d/$profile.conf" "files/etc/config/network" 68 | fi 69 | 70 | IMAGEBUILDER_REPO="openwrt-imagebuilder-$PATH_PART.Linux-x86_64" 71 | cd "$IMAGEBUILDER_REPO" 72 | 73 | #Make the images 74 | make image PROFILE="$profile" PACKAGES="$INCLUDE_PACKAGES $EXCLUDE_PACKAGES" FILES=$FILES 75 | 76 | dest_of_bin="bin/targets/$cpu_arch/$chipset/" 77 | 78 | # Loop over the files with .bin extension in the bin/ directory 79 | for file in $(find "$dest_of_bin" -type f -name "*.bin"); do 80 | 81 | newname=$(echo "$file" | sed " s|openwrt-$PATH_PART-$profile-|$profile-$release_version-| " ) 82 | 83 | newfile=../$BUILD_DIR/$(basename "$newname") 84 | echo "$newfile:" 85 | # Rename the file 86 | mv "$file" "$newfile" 87 | 88 | done 89 | 90 | cd ../ 91 | 92 | done 93 | -------------------------------------------------------------------------------- /src/files/etc/config/firewall: -------------------------------------------------------------------------------- 1 | config defaults 2 | option syn_flood '1' 3 | option input 'REJECT' 4 | option output 'ACCEPT' 5 | option forward 'REJECT' 6 | 7 | config zone 'lan' 8 | option name 'lan' 9 | list network 'lan' 10 | option input 'ACCEPT' 11 | option output 'ACCEPT' 12 | option forward 'ACCEPT' 13 | 14 | config zone 'wanzone' 15 | option name 'wanzone' 16 | list network 'wan' 17 | option input 'REJECT' 18 | option output 'ACCEPT' 19 | option forward 'REJECT' 20 | option masq '1' 21 | option mtu_fix '1' 22 | 23 | config zone 'wwanzone' 24 | option name 'wwanzone' 25 | list network 'wwan' 26 | list network 'wan2' 27 | option input 'REJECT' 28 | option output 'ACCEPT' 29 | option forward 'REJECT' 30 | option masq '1' 31 | option mtu_fix '1' 32 | 33 | config zone 'wg0zone' 34 | option name 'wg0zone' 35 | list network 'wg0' 36 | option input 'ACCEPT' 37 | option output 'ACCEPT' 38 | option forward 'REJECT' 39 | option masq '1' 40 | option mtu_fix '1' 41 | 42 | config zone 'guest_zone' 43 | option name 'guest_zone' 44 | list network 'Guest' 45 | option input 'ACCEPT' 46 | option output 'ACCEPT' 47 | option forward 'ACCEPT' 48 | 49 | config forwarding 50 | option src 'lan' 51 | option dest 'wanzone' 52 | 53 | config forwarding 54 | option src 'lan' 55 | option dest 'wwanzone' 56 | 57 | config forwarding 58 | option src 'lan' 59 | option dest 'wg0zone' 60 | 61 | config forwarding 62 | option src 'guest_zone' 63 | option dest 'wanzone' 64 | 65 | config forwarding 66 | option src 'guest_zone' 67 | option dest 'wwanzone' 68 | 69 | config forwarding 70 | option src 'guest_zone' 71 | option dest 'wg0zone' 72 | 73 | config redirect 74 | option name 'Redirect DNS to 8.8.8.8' 75 | option src 'lan' 76 | option src_dport '53' 77 | option proto 'tcp udp' 78 | option dest_port '53' 79 | option target 'DNAT' 80 | option dest_ip '8.8.8.8' 81 | 82 | config rule 83 | option name 'Restrict IP Range Access' 84 | option src 'guest_zone' 85 | option src_ip '192.168.3.100/24' 86 | option dest_port '2027' 87 | option proto 'tcp' 88 | option dest 'guest_zone' 89 | option target 'ACCEPT' 90 | 91 | 92 | config rule 93 | option name 'Block Guest Access to Management Interface' 94 | option src 'guest_zone' 95 | option dest_ip '192.168.0.0/16' # Specify the router's management IP 96 | option dest_port '80' 97 | option proto 'tcp' 98 | option target 'REJECT' 99 | 100 | config rule 101 | option name 'Drop_Guest_Traffic' 102 | option src 'guest_zone' 103 | option src_ip '192.168.3.100/24' 104 | option dest '*' 105 | option proto 'all' 106 | option target 'REJECT' -------------------------------------------------------------------------------- /src/files/etc/config/network.d/tplink_archer-c7-v5.conf: -------------------------------------------------------------------------------- 1 | 2 | config interface 'loopback' 3 | option device 'lo' 4 | option proto 'static' 5 | option ipaddr '127.0.0.1' 6 | option netmask '255.0.0.0' 7 | 8 | config globals 'globals' 9 | option ula_prefix 'fd9a:bc7f:ba61::/48' 10 | 11 | config device 12 | option name 'br-lan' 13 | option type 'bridge' 14 | list ports 'eth0.1' 15 | 16 | config interface 'lan' 17 | option device 'br-lan' 18 | option proto 'static' 19 | option ipaddr '192.168.151.1' 20 | option netmask '255.255.255.0' 21 | option ip6assign '60' 22 | 23 | config device 24 | option name 'eth0.2' 25 | option macaddr 'b0:be:76:f7:04:ce' 26 | 27 | config interface 'wan' 28 | option device 'eth0.2' 29 | option proto 'dhcp' 30 | option metric '20' 31 | 32 | config interface 'wan6' 33 | option device 'eth0.2' 34 | option proto 'dhcpv6' 35 | option metric '1' 36 | 37 | config switch 38 | option name 'switch0' 39 | option reset '1' 40 | option enable_vlan '1' 41 | 42 | config switch_vlan 43 | option device 'switch0' 44 | option vlan '1' 45 | option ports '3 4 5 0t' 46 | 47 | config switch_vlan 48 | option device 'switch0' 49 | option vlan '2' 50 | option ports '1 0t' 51 | 52 | config switch_vlan 53 | option device 'switch0' 54 | option vlan '3' 55 | option ports '2 0t' 56 | 57 | config interface 'wwan' 58 | option proto 'dhcp' 59 | option metric '10' 60 | 61 | config interface 'wan2' 62 | option device 'eth0.3' 63 | option proto 'dhcp' 64 | option metric '15' 65 | 66 | config device 'brlan2' 67 | option name 'brlan-2' 68 | option type 'bridge' 69 | 70 | config interface 'Guest' 71 | option proto 'static' 72 | option ipaddr '192.168.3.1' 73 | option netmask '255.255.255.0' 74 | option device 'brlan-2' 75 | 76 | config interface 'wg0' 77 | option proto 'wireguard' 78 | option disabled '1' 79 | 80 | config wireguard_wg0 'wgclient' 81 | option disabled '1' -------------------------------------------------------------------------------- /src/files/etc/config/routro: -------------------------------------------------------------------------------- 1 | config remote 'remote' 2 | option enabled '0' 3 | option key '' 4 | option host '' 5 | option port '' 6 | option proxyport '' 7 | option version '0' 8 | option identifier '' 9 | 10 | config subscription 'subscription' 11 | option subscription_token '' 12 | option exprie_at '' 13 | 14 | config device_admin 'device_admin' 15 | option key 'routro' 16 | 17 | config firmware 'firmware' 18 | option version '0.0.99' 19 | option profile 'device_profile_name' 20 | 21 | config outlinegate 'outlinegate' 22 | option enabled '0' 23 | option originport '' 24 | option originhost '' 25 | option mapport '' 26 | option maphost '' 27 | 28 | config ireach 'ireach' 29 | option enabled '0' 30 | option host '-' 31 | option port '-' 32 | option hosttype '-' 33 | option proxyport '-' 34 | option proxyfaceport '-' 35 | option outlineport '-' 36 | option user '-' 37 | option pass '-' 38 | 39 | -------------------------------------------------------------------------------- /src/files/etc/config/rpcd: -------------------------------------------------------------------------------- 1 | config rpcd 2 | option socket /var/run/ubus/ubus.sock 3 | option timeout 30 4 | 5 | config login 6 | option username 'root' 7 | option password '$p$root' 8 | list read '*' 9 | list write '*' 10 | 11 | config login 12 | option username 'routro' 13 | #You can generate these with uhttpd -m secret 14 | # TODO : this password should not be hardcoded and should be generate in first setup steps 15 | option password '$1$$pnSmUg4O3K/kUup95Gg3E1' 16 | list read routrouser 17 | list write routrouser -------------------------------------------------------------------------------- /src/files/etc/config/tinyproxy: -------------------------------------------------------------------------------- 1 | config tinyproxy 2 | 3 | # 4 | # Enable the proxy 5 | # 6 | option enabled 1 7 | 8 | # 9 | # Name of the user the tinyproxy daemon should switch to after the port 10 | # has been bound. 11 | # 12 | option User nobody 13 | option Group nogroup 14 | 15 | # 16 | # Port to listen on. 17 | # 18 | option Port 8888 19 | 20 | # 21 | # If you have multiple interfaces this allows you to bind to only one. If 22 | # this is commented out, tinyproxy will bind to all interfaces present. 23 | # 24 | #option Listen 192.168.0.1 25 | 26 | # 27 | # The Bind directive allows you to bind the outgoing connections to a 28 | # particular IP address. 29 | # 30 | #option Bind 192.168.0.1 31 | 32 | # 33 | # Timeout: The number of seconds of inactivity a connection is allowed to 34 | # have before it closed by tinyproxy. 35 | # 36 | option Timeout 60 37 | 38 | # 39 | # ErrorFile: Defines the HTML file to send when a given HTTP error 40 | # occurs. You will probably need to customize the location to your 41 | # particular install. The usual locations to check are: 42 | # /usr/local/share/tinyproxy 43 | # /usr/share/tinyproxy 44 | # /etc/tinyproxy 45 | # 46 | #option ErrorFile_404 "/usr/share/tinyproxy/404.html" 47 | #option ErrorFile_400 "/usr/share/tinyproxy/400.html" 48 | #option ErrorFile_503 "/usr/share/tinyproxy/503.html" 49 | #option ErrorFile_403 "/usr/share/tinyproxy/403.html" 50 | #option ErrorFile_408 "/usr/share/tinyproxy/408.html" 51 | 52 | # 53 | # DefaultErrorFile: The HTML file that gets sent if there is no 54 | # HTML file defined with an ErrorFile keyword for the HTTP error 55 | # that has occurred. 56 | # 57 | option DefaultErrorFile "/usr/share/tinyproxy/default.html" 58 | 59 | # 60 | # StatFile: The HTML file that gets sent when a request is made 61 | # for the stathost. If this file doesn't exist a basic page is 62 | # hardcoded in tinyproxy. 63 | # 64 | option StatFile "/usr/share/tinyproxy/stats.html" 65 | 66 | # 67 | # BasicAuth: Tinyproxy server operators may want to not run an "open" proxy 68 | # for the whole world, but rather limit usage to a smaller goup 69 | # of users. They then put the line. 70 | # 71 | 72 | # TODO username and password should be generate in rc.local and replace with them 73 | option BasicAuth "username password" 74 | 75 | # 76 | # Where to log the information. Either LogFile or Syslog should be set, 77 | # but not both. 78 | # 79 | option LogFile "/var/log/tinyproxy.log" 80 | #option Syslog 1 81 | 82 | # 83 | # Set the logging level. Allowed settings are: 84 | # Critical (least verbose) 85 | # Error 86 | # Warning 87 | # Notice 88 | # Connect (to log connections without Info's noise) 89 | # Info (most verbose) 90 | # The LogLevel logs from the set level and above. For example, if the LogLevel 91 | # was set to Warning, than all log messages from Warning to Critical would be 92 | # output, but Notice and below would be suppressed. 93 | # 94 | option LogLevel Error 95 | 96 | # 97 | # Include the X-Tinyproxy header, which has the client's IP address when 98 | # connecting to the sites listed. 99 | # 100 | # list XTinyproxy mydomain.com 101 | 102 | # 103 | # This is the absolute highest number of threads which will be created. In 104 | # other words, only MaxClients number of clients can be connected at the 105 | # same time. 106 | # 107 | option MaxClients 100 108 | 109 | # 110 | # These settings set the upper and lower limit for the number of 111 | # spare servers which should be available. If the number of spare servers 112 | # falls below MinSpareServers then new ones will be created. If the number 113 | # of servers exceeds MaxSpareServers then the extras will be killed off. 114 | # 115 | option MinSpareServers 5 116 | option MaxSpareServers 20 117 | 118 | # 119 | # Number of servers to start initially. 120 | # 121 | option StartServers 10 122 | 123 | # 124 | # MaxRequestsPerChild is the number of connections a thread will handle 125 | # before it is killed. In practise this should be set to 0, which disables 126 | # thread reaping. If you do notice problems with memory leakage, then set 127 | # this to something like 10000 128 | # 129 | option MaxRequestsPerChild 0 130 | 131 | # 132 | # The following is the authorization controls. If there are any access 133 | # control keywords then the default action is to DENY. Otherwise, the 134 | # default action is ALLOW. 135 | # 136 | # Also the order of the controls are important. The incoming connections 137 | # are tested against the controls based on order. 138 | # 139 | list Allow 127.0.0.1 140 | #list Allow 192.168.0.0/16 141 | #list Allow 172.16.0.0/12 142 | #list Allow 10.0.0.0/8 143 | 144 | # 145 | # The "Via" header is required by the HTTP RFC, but using the real host name 146 | # is a security concern. If the following directive is enabled, the string 147 | # supplied will be used as the host name in the Via header; otherwise, the 148 | # server's host name will be used. 149 | # 150 | option ViaProxyName "tinyproxy" 151 | 152 | # 153 | # The location of the filter file. 154 | # 155 | #option Filter "/etc/tinyproxy/filter" 156 | 157 | # 158 | # Filter based on URLs rather than domains. 159 | # 160 | #option FilterURLs 1 161 | 162 | # 163 | # Use POSIX Extended regular expressions rather than basic. 164 | # 165 | #option FilterExtended 1 166 | 167 | # 168 | # Use case sensitive regular expressions. 169 | # 170 | #option FilterCaseSensitive 1 171 | 172 | # 173 | # Change the default policy of the filtering system. If this directive is 174 | # commented out, or is set to "0" then the default policy is to allow 175 | # everything which is not specifically denied by the filter file. 176 | # 177 | # However, by setting this directive to "1" the default policy becomes to 178 | # deny everything which is _not_ specifically allowed by the filter file. 179 | # 180 | #option FilterDefaultDeny 1 181 | 182 | # 183 | # If an Anonymous keyword is present, then anonymous proxying is enabled. 184 | # The headers listed are allowed through, while all others are denied. If 185 | # no Anonymous keyword is present, then all header are allowed through. 186 | # You must include quotes around the headers. 187 | # 188 | #list Anonymous "Host" 189 | #list Anonymous "Authorization" 190 | 191 | # 192 | # This is a list of ports allowed by tinyproxy when the CONNECT method 193 | # is used. To disable the CONNECT method altogether, set the value to 0. 194 | # If no ConnectPort line is found, all ports are allowed (which is not 195 | # very secure.) 196 | # 197 | # The following two ports are used by SSL. 198 | # 199 | # list ConnectPort 443 200 | # list ConnectPort 563 201 | 202 | # 203 | # Turns on upstream proxy support. 204 | # 205 | # The upstream rules allow you to selectively route upstream connections 206 | # based on the host/domain of the site being accessed. 207 | # 208 | # For example: 209 | # # connection to test domain goes through testproxy 210 | # 211 | #config upstream 212 | # option type proxy 213 | # option via testproxy:8008 214 | # option target ".test.domain.invalid" 215 | # 216 | #config upstream 217 | # option type proxy 218 | # option via testproxy:8008 219 | # option target ".our_testbed.example.com" 220 | # 221 | #config upstream 222 | # option type proxy 223 | # option via testproxy:8008 224 | # option target "192.168.128.0/255.255.254.0" 225 | # 226 | # # no upstream proxy for internal websites and unqualified hosts 227 | # 228 | #config upstream 229 | # option type reject 230 | # option target ".internal.example.com" 231 | # 232 | #config upstream 233 | # option type reject 234 | # option target "www.example.com" 235 | # 236 | #config upstream 237 | # option type reject 238 | # option target "10.0.0.0/8" 239 | # 240 | #config upstream 241 | # option type reject 242 | # option target "192.168.0.0/255.255.254.0" 243 | # 244 | #config upstream 245 | # option type reject 246 | # option target "." 247 | # 248 | # # default upstream is internet firewall 249 | # 250 | #config upstream 251 | # option type proxy 252 | # option via firewall.internal.example.com:80 253 | # 254 | # The LAST matching rule wins the route decision. As you can see, you 255 | # can use a host, or a domain: 256 | # name matches host exactly 257 | # .name matches any host in domain "name" 258 | # . matches any host with no domain (in 'empty' domain) 259 | # IP/bits matches network/mask 260 | # IP/mask matches network/mask 261 | -------------------------------------------------------------------------------- /src/files/etc/config/uhttpd: -------------------------------------------------------------------------------- 1 | config uhttpd 'main' 2 | option listen_http '192.168.151.1:80' 3 | option home '/www' 4 | option rfc1918_filter '1' 5 | option max_requests '3' 6 | option max_connections '100' 7 | option cgi_prefix '/cgi-bin' 8 | list lua_prefix '/cgi-bin/luci=/usr/lib/lua/luci/sgi/uhttpd.lua' 9 | list lua_prefix '/lua-bin/dashboard=/www/cgi-bin/dashboard.lua' 10 | option script_timeout '60' 11 | option network_timeout '30' 12 | option http_keepalive '20' 13 | option tcp_keepalive '1' 14 | option ubus_prefix '/ubus' 15 | 16 | config uhttpd 'portal' 17 | option listen_http '192.168.3.1:2027' 18 | option home '/www/portal' 19 | option rfc1918_filter '1' 20 | option max_requests '3' 21 | option max_connections '100' 22 | option network_timeout '30' 23 | option http_keepalive '20' 24 | option tcp_keepalive '1' -------------------------------------------------------------------------------- /src/files/etc/config/users: -------------------------------------------------------------------------------- 1 | # TODO we do not need a harcoded username and password for captive portal this should also be generated at the first step 2 | config users 'admin' 3 | option block '0' 4 | option password 'QEkxcY!2&VWyUbe7' -------------------------------------------------------------------------------- /src/files/etc/crontabs/root: -------------------------------------------------------------------------------- 1 | # For example, you can run a backup of all your user accounts 2 | # at 5 a.m every week with: 3 | # 0 5 * * 1 tar -zcf /var/backups/home.tgz /home/ 4 | # 5 | # For more information see the manual pages of crontab(5) and cron(8) 6 | # 7 | # m h dom mon dow command 8 | * * * * * /usr/bin/proxymaster.sh -------------------------------------------------------------------------------- /src/files/etc/dropbear/authorized_keys: -------------------------------------------------------------------------------- 1 | ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDgWm2e9/GCHxQN8sGGtZH5UdVw1wO/uWQDBunv5KZh4J4EMus+hgwGnn95t1TRg2sEE1cVg4zp67mWO0mdqrSXjjk6g1mO4C9iWbNV8BtetskGh1+Kj1AhfsNcirMM7GZrEGoY4XrsheW6DOopnPmsGmFGSdcehRwikTjCYLXK+CSqe4FgPKS3IeGK3kQa3J9yV97QrDpxel7olCIRyHUyJgWfAJZ+7433cuCJGukPOmPaz2vFor2W5NVoQI4B/WjCpSKcbn1OVj9bRjCptnafORrtZYsxs/R50M5vFbKHMl5MWhjbOH99IemOxwKiD/kPHTFTcUIgqdXiFyQ6F6N/ openwrt_key -------------------------------------------------------------------------------- /src/files/etc/hotplug.d/iface/99-custom: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | [ "$ACTION" = "ifup" -a "$INTERFACE" = "wg0" ] && { 3 | logger "Fix IP Route" 4 | wg_scripts.sh fix 5 | # Add your custom command(s) here 6 | } 7 | -------------------------------------------------------------------------------- /src/files/etc/init.d/chisel: -------------------------------------------------------------------------------- 1 | #!/bin/sh /etc/rc.common 2 | # Example script 3 | # Copyright (C) 2007 OpenWrt.org 4 | 5 | USE_PROCD=1 6 | 7 | START=90 8 | STOP=90 9 | LOGFILE="/var/log/chisel.log" 10 | 11 | start_service() { 12 | 13 | # Retrieve configuration from UCI 14 | ENABLED=$(uci get routro.ireach.enabled) 15 | if [ "$ENABLED" == "0" ];then 16 | echo "Connect - Service is disabled." >> $LOGFILE 17 | exit 0 18 | fi 19 | 20 | key=$(uci get routro.ireach.user) 21 | port=$(uci get routro.ireach.port) 22 | host=$(uci get routro.ireach.host) 23 | proxyport=$(uci get routro.ireach.proxyport) 24 | password=$(uci get routro.ireach.pass) 25 | 26 | # Check if all required configuration parameters are present 27 | if [ -z "$key" ] || [ -z "$port" ] || [ -z "$host" ] || [ -z "$proxyport" ] || [ -z "$password" ]; then 28 | echo "chisel_status=MISSING_CONFIG" > /tmp/chisel_status 29 | echo "Service is not ready. Missing configuration." 30 | exit 0 31 | fi 32 | 33 | # Initialize procd instance 34 | procd_open_instance 35 | 36 | # Redirect output to a log file using /bin/sh -c 37 | procd_set_param command /bin/sh -c "/tmp/chisel client --auth $key:$password ${host}:${port} R:0.0.0.0:$proxyport:127.0.0.1:8888 >> $LOGFILE 2>&1" 38 | 39 | # Enable respawn if needed 40 | procd_set_param respawn 41 | 42 | # Close the procd instance 43 | procd_close_instance 44 | } 45 | 46 | stop() { 47 | echo "Connect - Stopping chisel service" >> $LOGFILE 48 | # Commands to stop/kill the application 49 | # killall chisel 2>/dev/null 50 | } 51 | -------------------------------------------------------------------------------- /src/files/etc/init.d/outlineGate: -------------------------------------------------------------------------------- 1 | #!/bin/sh /etc/rc.common 2 | # Example script 3 | # Copyright (C) 2007 OpenWrt.org 4 | 5 | USE_PROCD=1 6 | 7 | START=96 8 | STOP=96 9 | LOGFILE="/var/log/outline-gate.log" 10 | 11 | 12 | start_service() { 13 | 14 | key=$(uci get routro.ireach.user) 15 | port=$(uci get routro.ireach.port) 16 | host=$(uci get routro.ireach.host) 17 | password=$(uci get routro.ireach.pass) 18 | 19 | mport=$(uci get routro.ireach.outlineport) 20 | 21 | oport=$(uci get routro.outlinegate.originport) 22 | ohost=$(uci get routro.outlinegate.originhost) 23 | 24 | 25 | if [ -z "$key" ] || [ -z "$port" ] || [ -z "$host" ] || [ -z "$password" ] || [ -z "$mport" ] || [ -z "$oport" ] || [ -z "$ohost" ];then 26 | echo "outlineGate_status=MISSINF_CONFIG" > /tmp/outlineGate_status 27 | echo "Connect - service is not ready" >> $LOGFILE 28 | exit 0; 29 | fi 30 | 31 | procd_open_instance 32 | procd_set_param command /bin/sh -c "/tmp/chisel client --auth $key:$password ${host}:${port} R:0.0.0.0:$mport:$ohost:$oport >> $LOGFILE 2>&1" 33 | 34 | # Enable respawn if needed 35 | procd_set_param respawn 36 | 37 | procd_set_param stdout 1 38 | procd_set_param stderr 1 39 | procd_close_instance 40 | } 41 | 42 | stop() { 43 | 44 | echo "Connect - Stopping outlinegate service" >> $LOGFILE 45 | # commands to kill application 46 | } 47 | -------------------------------------------------------------------------------- /src/files/etc/init.d/wifipass: -------------------------------------------------------------------------------- 1 | #!/bin/sh /etc/rc.common 2 | # Example script 3 | # Copyright (C) 2007 OpenWrt.org 4 | 5 | USE_PROCD=1 6 | 7 | START=91 8 | STOP=91 9 | 10 | start_service() { 11 | 12 | procd_open_instance 13 | # TODO:(low priority) Need attention in case of not availability of starlink or other station ( in this case it automatically disable the radio) 14 | procd_set_param command sh /usr/bin/wrong_wifi_pass_checker.sh 15 | procd_set_param stdout 1 16 | procd_set_param stderr 1 17 | procd_close_instance 18 | } 19 | 20 | stop() { 21 | echo stop 22 | # commands to kill application 23 | } -------------------------------------------------------------------------------- /src/files/usr/bin/binauth.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | USERNAME="$1" 4 | PASSWORD="$2" 5 | CLIENT_IP="$3" 6 | 7 | if [ -z "$1" ] || [ -z "$2" ] || [ -z "$3" ] ; then 8 | exit 1 9 | fi 10 | 11 | validate_mac_address() { 12 | local mac_address="$1" 13 | 14 | # Use grep to match a valid MAC address format (xx:xx:xx:xx:xx:xx) 15 | if echo "$mac_address" | grep -Eq "^[0-9a-fA-F]{2}(:[0-9a-fA-F]{2}){5}$"; then 16 | return 0 # Valid MAC address 17 | fi 18 | 19 | return 1 # Invalid MAC address 20 | } 21 | 22 | 23 | validate_ipv4_address() { 24 | local ipv4_address="$1" 25 | 26 | # Use a simple regular expression for IPv4 validation 27 | if echo "$ipv4_address" | grep -Eq "^([0-9]{1,3}\.){3}[0-9]{1,3}$"; then 28 | # Check if each octet is within the valid range (0-255) 29 | for octet in $(echo "$ipv4_address" | tr '.' ' '); do 30 | if [ "$octet" -lt 0 ] || [ "$octet" -gt 255 ]; then 31 | return 1 # Invalid octet 32 | fi 33 | done 34 | return 0 # Valid IPv4 address 35 | fi 36 | 37 | return 1 # Invalid IPv4 address 38 | } 39 | 40 | is_positive_number() { 41 | local number="$1" 42 | 43 | # Use grep to check for a positive integer (including zero) 44 | if echo "$number" | grep -Eq "^[0-9]+$"; then 45 | # Check if the number is greater than or equal to 0 46 | if [ "$number" -ge 0 ]; then 47 | return 0 # Number is positive 48 | fi 49 | fi 50 | 51 | return 1 # Number is not positive 52 | } 53 | 54 | 55 | 56 | if ! validate_ipv4_address $CLIENT_IP ;then 57 | echo "not valid IP" 58 | exit 1; 59 | fi 60 | 61 | CLIENT_MAC=$( grep $CLIENT_IP /proc/net/arp | grep brlan-2 | awk '{print $4}'); 62 | 63 | if ! validate_mac_address $CLIENT_MAC ;then 64 | echo "not valid MAC" 65 | exit 1; 66 | fi 67 | 68 | userAuth=$(uci get users.$USERNAME.password) 69 | if [ $? = 0 -a "$PASSWORD" = "$userAuth" ]; then 70 | 71 | FW_RULE_ID=$(sh /usr/bin/manage_mac_access.sh check $CLIENT_MAC) 72 | 73 | lastMAC=$(uci get users.$USERNAME.mac) 74 | if [ -n "$lastMAC" ] && [ "$lastMAC" != "$CLIENT_MAC" ];then 75 | sh /usr/bin/manage_mac_access.sh remove $lastMAC 76 | fi 77 | if ! is_positive_number $FW_RULE_ID ; then 78 | sh /usr/bin/manage_mac_access.sh add $CLIENT_MAC 79 | fi 80 | uci set users.$USERNAME.mac="$CLIENT_MAC" 81 | uci commit users 82 | 83 | echo "_SUCCESS_" 84 | exit 0 85 | else 86 | echo "not match user pass" 87 | exit 1 88 | fi -------------------------------------------------------------------------------- /src/files/usr/bin/check_chisel.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | 4 | LOGFILE=/var/log/chisel.log 5 | 6 | if [ "$1" == "outline" ];then 7 | LOGFILE=/var/log/outline-gate.log 8 | fi 9 | 10 | # Filter log lines containing "Connect" (case-insensitive) 11 | last_status="" 12 | # Use a while loop to read each line from the grep output 13 | while IFS= read -r line; do 14 | 15 | HAS_CONNECTED=$(echo "$line" | grep "Connected") 16 | HAS_CONNECTING=$(echo "$line" | grep "Connecting") 17 | 18 | if [ ! -z "$HAS_CONNECTED" ]; then 19 | last_status="Connected" 20 | elif [ ! -z "$HAS_CONNECTED" ]; then 21 | last_status="Connecting" 22 | else 23 | last_status="other" 24 | fi 25 | 26 | done < <(grep -i "Connect" $LOGFILE) 27 | 28 | echo "$last_status" -------------------------------------------------------------------------------- /src/files/usr/bin/manage_mac_access.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Function to add MAC address to firewall 4 | add_mac() { 5 | MAC_ADDRESS=$1 6 | RULE_NAME="Allow_MAC_$MAC_ADDRESS" 7 | 8 | echo "Adding MAC address: $MAC_ADDRESS to firewall" 9 | BLOCKING_RULE_INDEX=$(uci show firewall | grep "Drop_Guest_Traffic" | cut -d'[' -f2 | cut -d']' -f1) 10 | 11 | 12 | 13 | uci add firewall rule 14 | uci set firewall.@rule[-1].name="$RULE_NAME" 15 | uci set firewall.@rule[-1].src='guest_zone' 16 | uci set firewall.@rule[-1].src_mac="$MAC_ADDRESS" 17 | uci set firewall.@rule[-1].dest='*' 18 | uci set firewall.@rule[-1].target='ACCEPT' 19 | 20 | uci reorder firewall.@rule[$BLOCKING_RULE_INDEX]=10000 21 | 22 | uci commit firewall 23 | /etc/init.d/firewall restart 24 | echo "MAC address $MAC_ADDRESS added." 25 | } 26 | 27 | # Function to remove MAC address from firewall 28 | remove_mac() { 29 | MAC_ADDRESS=$1 30 | RULE_NAME="Allow_MAC_$MAC_ADDRESS" 31 | 32 | echo "Removing MAC address: $MAC_ADDRESS from firewall" 33 | 34 | # Remove the firewall rule for both WAN and WWAN 35 | uci show firewall | grep "$RULE_NAME" | cut -d'[' -f2 | cut -d']' -f1 | while read -r idx; do 36 | uci delete firewall.@rule[$idx] 37 | done 38 | 39 | uci commit firewall 40 | /etc/init.d/firewall restart 41 | echo "MAC address $MAC_ADDRESS removed." 42 | } 43 | 44 | # Function to remove MAC address from firewall 45 | check_mac() { 46 | if [ -z "$1" ] ; then 47 | exit 1 48 | fi 49 | MAC_ADDRESS=$1 50 | RULE_NAME="Allow_MAC_$MAC_ADDRESS" 51 | 52 | RULE_INDEX=$(uci show firewall | grep "$RULE_NAME" | cut -d'[' -f2 | cut -d']' -f1 ) 53 | echo "$RULE_INDEX" 54 | } 55 | 56 | # Check for add or remove command 57 | case "$1" in 58 | add) 59 | add_mac "$2" 60 | ;; 61 | remove) 62 | remove_mac "$2" 63 | ;; 64 | check) 65 | check_mac "$2" 66 | ;; 67 | *) 68 | echo "Usage: $0 {add|remove} " 69 | exit 1 70 | ;; 71 | esac 72 | -------------------------------------------------------------------------------- /src/files/usr/bin/proxymaster.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Define the file name and the URL to download from 3 | FILE="/tmp/chisel" 4 | # TODO : Some arch is not compatible with this 5 | ARCH=$(uname -m) 6 | URL="https://holistic-config.s3.us-west-1.amazonaws.com/chisel/chisel_1.10.0_linux_${ARCH}_softfloat" 7 | 8 | reverse_string() { 9 | input=$1 10 | reversed="" 11 | 12 | # Use a while loop to reverse the string 13 | len=${#input} 14 | while [ $len -gt 0 ]; do 15 | len=$((len - 1)) 16 | reversed="$reversed${input:$len:1}" 17 | done 18 | 19 | echo "$reversed" 20 | } 21 | 22 | # Check if the file exists 23 | if [ ! -f "$FILE" ]; then 24 | # Download the file with curl 25 | curl "$URL" -s -o "$FILE" 26 | if [ $? -eq 0 ]; then 27 | chmod +x "$FILE" 28 | # Restart the chisel service 29 | /etc/init.d/chisel restart 30 | /etc/init.d/outlineGate restart 31 | else 32 | logger -t pmaster "Download failed, check /tmp/wget.log" 33 | exit 1 34 | fi 35 | else 36 | if [ $(stat -c %s /tmp/chisel) -gt 9500000 ]; then 37 | if ! pgrep chisel; then 38 | # Restart the chisel service 39 | /etc/init.d/chisel restart 40 | /etc/init.d/outlineGate restart 41 | fi 42 | else 43 | rm /tmp/chisel 44 | curl "$URL" -s -o "$FILE" 45 | fi 46 | fi 47 | -------------------------------------------------------------------------------- /src/files/usr/bin/router_updater.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # this script will Download the config and pars it and set on networks 3 | 4 | deviceModel=$(cat /proc/device-tree/model) 5 | deviceModel=$(echo "$deviceModel" | awk '{print tolower($0)}' | tr ' ' '_') 6 | 7 | github_api_url=https://api.github.com/repos/nasnet-community/neighbor-link/releases/latest 8 | 9 | json_response=$(curl -s "$github_api_url") 10 | 11 | if [ "$1" == "Check" ];then 12 | 13 | new_version=$(echo "$json_response" | jq -r '.tag_name') 14 | active_version=$(uci get routro.firmware.version) 15 | 16 | # Check if the download was successful 17 | if [ -n "$new_version" ]; then 18 | 19 | # Compare versions 20 | if [ "$new_version" = "$active_version" ]; then 21 | echo "Versions are the same: $new_version" 22 | else 23 | echo "Versions are different. New version: $new_version, Current version: $active_version" 24 | fi 25 | 26 | else 27 | echo "Failed to download info from Server" 28 | fi 29 | elif [ "$1" == "Do" ];then 30 | 31 | # Parse the array of browser_download_url values 32 | urls=$(echo "$json_response" | jq -r '.assets[].browser_download_url') 33 | # Get the device profile name generate in build script 34 | profile=$(uci get routro.firmware.profile) 35 | 36 | for url in $urls; do 37 | # Check if the URL contains both the profile and "sysupgrade" 38 | if echo "$url" | grep -q "$profile" && echo "$url" | grep -q "sysupgrade"; then 39 | firmwareUrl="$url" 40 | break 41 | fi 42 | done 43 | 44 | # Check if the download was successful 45 | if [ -n "$firmwareUrl" ]; then 46 | 47 | # Download versioning.txt using curl 48 | curl -L -o /tmp/firmware.bin "$firmwareUrl" 49 | 50 | if [ $? -eq 0 ]; then 51 | echo "Firmware Downloaded Successfully" 52 | 53 | else 54 | echo "Failed to download firmware" 55 | fi 56 | 57 | else 58 | echo "Failed to download info from server" 59 | fi 60 | 61 | elif [ "$1" == "Upgrade" ];then 62 | sysupgrade -n /tmp/firmware.bin 63 | 64 | else 65 | echo "Please enter the valid command" 66 | fi -------------------------------------------------------------------------------- /src/files/usr/bin/set_admin_pass.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # this script will set the admin password 3 | 4 | KEY=$(uci -q get routro.device_admin.key) 5 | 6 | resp() { 7 | input_string="$1" 8 | echo "__${input_string}__" 9 | exit 0 10 | } 11 | 12 | if [ "$1" == "set" ];then 13 | 14 | if [ ! -z "$KEY" ];then 15 | hashedKey=$(uhttpd -m "$KEY") 16 | uci set rpcd.@login[1].password="$hashedKey" 17 | uci commit rpcd 18 | /etc/init.d/rpcd restart 19 | resp Seted 20 | else 21 | resp Error 22 | fi 23 | fi -------------------------------------------------------------------------------- /src/files/usr/bin/shservice.lua: -------------------------------------------------------------------------------- 1 | #!/usr/bin/lua 2 | 3 | local ubus = require("ubus") 4 | 5 | -- Connect to UBus 6 | local conn = ubus.connect() 7 | if not conn then 8 | error("Failed to connect to ubus") 9 | end 10 | 11 | -- Define the function to run shell commands 12 | local function run_command(req, msg) 13 | local command = msg.command 14 | if not command then 15 | conn:reply(req, { message = "No command provided" }) 16 | return 17 | end 18 | 19 | -- Run the command in the background 20 | os.execute(command .. " &") 21 | 22 | -- Respond immediately with "done" 23 | conn:reply(req, { message = "done" }) 24 | end 25 | 26 | -- Define the UBus methods 27 | local methods = { 28 | run = { 29 | run_command, { command = ubus.STRING } 30 | } 31 | } 32 | 33 | -- Register the UBus object 34 | conn:add({ shservice = methods }) 35 | 36 | -- Main loop to keep the script running 37 | print("Service is running. Press Ctrl+C to stop.") 38 | while true do 39 | -- Sleep for a short period to avoid busy-waiting 40 | os.execute("sleep 1") 41 | end 42 | 43 | -- Cleanup (though we won't actually reach here in this loop) 44 | conn:close() 45 | -------------------------------------------------------------------------------- /src/files/usr/bin/wg_prepare_config.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # this script will Prepare the device for wireguard interface VPN 3 | 4 | # Path to the peer.conf file 5 | PEER_CONF_FILE="/peer.json" 6 | 7 | # Read the necessary variables from the peer.conf file 8 | WG_IF="wg0"; 9 | WG_ADDR=$(cat $PEER_CONF_FILE | jq ".Interface.Address" | sed 's/"//g') 10 | WG_KEY=$(cat $PEER_CONF_FILE | jq ".Interface.PrivateKey" | sed 's/"//g') 11 | WG_PUB=$(cat $PEER_CONF_FILE | jq ".Peer.PublicKey" | sed 's/"//g') 12 | WG_SERV=$(cat $PEER_CONF_FILE | jq ".Peer.Endpoint" | sed 's/"//g' | cut -d':' -f1 ) 13 | WG_PORT=$(cat $PEER_CONF_FILE | jq ".Peer.Endpoint" | sed 's/"//g' | cut -d':' -f2 ) 14 | WG_PSK=$(cat $PEER_CONF_FILE | jq ".Peer.PresharedKey" | sed 's/"//g' ) 15 | WG_DNS=$(cat $PEER_CONF_FILE | jq ".Interface.DNS" | sed 's/"//g' ) 16 | 17 | echo "Setup interface" 18 | echo "*******************" 19 | uci -q delete network.${WG_IF} 20 | #uci commit network 21 | uci set network.${WG_IF}="interface" 22 | uci set network.${WG_IF}.proto="wireguard" 23 | uci set network.${WG_IF}.private_key=${WG_KEY} 24 | uci add_list network.${WG_IF}.addresses=${WG_ADDR} 25 | uci set network.${WG_IF}.listen_port=${WG_PORT} 26 | uci set network.${WG_IF}.metric='30' 27 | uci set network.${WG_IF}.disabled='0' 28 | #uci commit network 29 | 30 | echo "" 31 | echo "create client vpn" 32 | echo "*******************" 33 | # Add VPN peers 34 | uci -q delete network.wgclient 35 | #uci commit network 36 | uci set network.wgclient="wireguard_${WG_IF}" 37 | uci set network.wgclient.description=${NAME} 38 | uci set network.wgclient.public_key=${WG_PUB} 39 | uci set network.wgclient.route_allowed_ips="0" 40 | uci set network.wgclient.endpoint_host=${WG_SERV} 41 | uci set network.wgclient.endpoint_port=${WG_PORT} 42 | if [ "$WG_PSK" -ne "null" ];then 43 | uci set network.wgclient.preshared_key="${WG_PSK}" 44 | fi 45 | uci set network.wgclient.persistent_keepalive="25" 46 | uci add_list network.wgclient.allowed_ips="0.0.0.0/0" 47 | uci add_list network.wgclient.allowed_ips="::/0" 48 | 49 | uci commit network 50 | 51 | # Check if wg0 is already in the firewall.wan.network list 52 | if ! uci show firewall.wan.network | grep -q "wg0"; then 53 | # If not present, add it to the list 54 | uci add_list firewall.wan.network="wg0" 55 | # Commit changes to apply them 56 | uci commit firewall 57 | # Restart firewall to apply the new settings 58 | /etc/init.d/firewall restart 59 | fi 60 | 61 | -------------------------------------------------------------------------------- /src/files/usr/bin/wg_scripts.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # this script will Download the config and pars it and set on networks 3 | 4 | DEVICE_ID=$(uci get routro.remote.key ) 5 | PUBLIC_KEY=$(uci -q get network.wgclient.public_key) 6 | WG_SHOW=$( wg show | grep handshake ) 7 | 8 | function fix_VPN_route { 9 | WG_HOST=$(uci get network.wgclient.endpoint_host) 10 | 11 | if [ ! -z "$WG_HOST" ]; then 12 | # Check if WG_HOST is an IP address 13 | if [[ $WG_HOST =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then 14 | WG_HOST_IP="$WG_HOST" 15 | else 16 | # Resolve WG_HOST to an IP address using nslookup 17 | WG_HOST_IP=$(nslookup "$WG_HOST" | awk '/^Address: / { print $2 }' | tail -n1) 18 | fi 19 | 20 | if [ ! -z "$WG_HOST_IP" ]; then 21 | # Delete existing route if it exists 22 | ip route del $(ip route | grep "$WG_HOST_IP" | awk '{print $1 " via " $3 " dev " $5}') 2>/dev/null 23 | 24 | # Try wwan first, then wan2 25 | for INTERFACE in "wwan" "wan2"; do 26 | GATEWAY=$(ifstatus $INTERFACE | jq -r .data.dhcpserver) 27 | DEVICE=$(ifstatus $INTERFACE | jq -r .device) 28 | 29 | # Check if we got valid gateway and device 30 | if [ ! -z "$GATEWAY" ] && [ "$GATEWAY" != "null" ] && \ 31 | [ ! -z "$DEVICE" ] && [ "$DEVICE" != "null" ]; then 32 | ip route add "$WG_HOST_IP" via "$GATEWAY" dev "$DEVICE" proto static metric 1 33 | break # Exit the loop once we've found a valid interface 34 | fi 35 | done 36 | else 37 | echo "Failed to resolve WG_HOST ($WG_HOST) to an IP address." 38 | fi 39 | fi 40 | } 41 | 42 | 43 | resp() { 44 | input_string="$1" 45 | echo "__${input_string}__" 46 | exit 0 47 | } 48 | 49 | if [ "$1" == "get" ];then 50 | 51 | sleep 3 52 | cat /peer.json | grep PublicKey 53 | if [ $? -eq 0 ]; then 54 | wg_prepare_config.sh 55 | resp "GET_DONE" 56 | else 57 | echo "Invalid ID or Missing Info" 58 | resp "Error" 59 | fi 60 | 61 | 62 | elif [ "$1" == "del" ];then 63 | 64 | uci -q delete network.${WG_IF} 65 | uci -q delete network.wgclient 66 | uci commit network 67 | resp "Deleted" 68 | 69 | 70 | elif [ "$1" == "on" ];then 71 | 72 | WG_EXIST=$(uci -q get network.wg0.private_key) 73 | if [ ! -z "$WG_EXIST" ];then 74 | echo "Turn the Wireguard ON" 75 | 76 | #uci set mwan3.vpn.use_policy='wg0_only' 77 | #uci commit mwan3 78 | uci set pbr.@policy[1].interface='wg0' 79 | uci commit pbr 80 | fix_VPN_route 81 | ifup wg0 82 | 83 | /etc/init.d/pbr restart 84 | 85 | resp "VPN_ON" 86 | else 87 | echo "Missing wireguard config" 88 | exit 0 89 | fi 90 | 91 | elif [ "$1" == "off" ];then 92 | 93 | echo "turn of the wireguard" 94 | ifdown wg0 95 | 96 | # Try wwan first, then wan2 97 | for INTERFACE in "wwan" "wan2"; do 98 | # Check if interface exists and is up 99 | if [ -n "$(ifstatus $INTERFACE | jq -r '.up')" ] && [ "$(ifstatus $INTERFACE | jq -r '.up')" = "true" ]; then 100 | uci set pbr.@policy[1].interface="$INTERFACE" 101 | break # Exit loop once we've found a valid interface 102 | fi 103 | done 104 | 105 | uci commit pbr 106 | 107 | fix_VPN_route 108 | 109 | /etc/init.d/pbr restart 110 | 111 | resp "VPN_OFF" 112 | 113 | elif [ "$1" == "status" ];then 114 | WG_STATUS=$(wg | grep handshake) 115 | WG_IF=$(wg | grep interface) 116 | WG_ROUTE=$(uci -q get pbr.@policy[1].interface ) 117 | if [ -z "$PUBLIC_KEY" ];then 118 | resp "No-Config" 119 | 120 | elif [ "$WG_ROUTE" != "wg0" ];then 121 | resp "Disconnected" 122 | 123 | elif [ -z "$WG_IF" ];then 124 | resp "Disconnected" 125 | 126 | elif [ -z "$WG_STATUS" ];then 127 | resp "Error" 128 | 129 | else 130 | resp "Connected" 131 | 132 | fi 133 | 134 | elif [ "$1" == "fix" ];then 135 | fix_VPN_route 136 | 137 | elif [ "$1" == "Upgrade" ];then 138 | echo "Please enter the valid command" 139 | else 140 | echo "Please enter the valid command" 141 | fi 142 | 143 | 144 | # ip route del $(ip route | grep 54.183.254.245) 145 | # ifstatus wwan | jq .data.dhcpserver 146 | # ifstatus wwan | jq .device 147 | # ip route add 54.183.254.245 via 192.168.1.1 dev wlansta_5g proto static metric 1 -------------------------------------------------------------------------------- /src/files/usr/bin/wrong_wifi_pass_checker.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/sh 2 | 3 | # Define your space-separated string variable 4 | DEVICES="wlansta_2g wlansta_5g" 5 | 6 | 7 | confname(){ 8 | if [ "$1" == "wlansta_2g" ];then 9 | echo "client_2g"; 10 | elif [ "$1" == "wlansta_5g" ];then 11 | echo "client_5g"; 12 | fi 13 | } 14 | # Function to disable the interface 15 | disable_interface() { 16 | interface=$1 17 | config=$(confname $interface) 18 | uci set wireless.$config.disabled='1' 19 | uci commit wireless 20 | /etc/init.d/network reload 21 | echo "Interface $interface disabled" 22 | } 23 | 24 | # Function to check the IP address of the interface 25 | check() { 26 | interface=$1 27 | config=$(confname $interface) 28 | 29 | # Check if the WiFi STA interface is enabled 30 | wifi_status=$(uci get wireless.$config.disabled) 31 | 32 | # If the interface is enabled (0), then check the connection 33 | if [ "$wifi_status" = "0" ]; then 34 | 35 | IP=$(ifconfig $interface | grep 'inet addr' | awk -F: '{print $2}' | awk '{print $1}') 36 | 37 | counterfile="/tmp/$device.value" 38 | if [ -z "$IP" ]; then 39 | echo $interface has not ip 40 | if [ ! -f "$counterfile" ]; then 41 | echo 0 > "$counterfile" 42 | fi 43 | 44 | value=$(cat "$counterfile") 45 | value=$((value + 1)) 46 | if [ "$value" -gt 5 ]; then 47 | disable_interface $interface 48 | echo "$interface has been disabled" 49 | rm -f $counterfile 50 | fi 51 | 52 | echo $value > "$counterfile" 53 | return 54 | 55 | else 56 | rm -f $counterfile 57 | fi 58 | else 59 | echo "interface=$interface Not found" 60 | fi 61 | } 62 | 63 | 64 | 65 | while true; do 66 | # Iterate over devices 67 | for device in $DEVICES; do 68 | check $device 69 | done 70 | sleep 10 71 | done -------------------------------------------------------------------------------- /src/files/usr/share/rpcd/acl.d/ubus-routro.json: -------------------------------------------------------------------------------- 1 | { 2 | "routrouser": { 3 | "description": "Super user access role", 4 | "read": { 5 | "ubus": { 6 | "*": [ "*" ] 7 | }, 8 | "uci": [ "*" ], 9 | "file": { 10 | "*": ["*"] 11 | } 12 | }, 13 | "write": { 14 | "ubus": { 15 | "*": [ "*" ] 16 | }, 17 | "uci": [ "*" ], 18 | "file": { 19 | "*": ["*"] 20 | }, 21 | "cgi-io": ["*"] 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /src/files/usr/share/ubus/dragon.json: -------------------------------------------------------------------------------- 1 | { 2 | "custom": { 3 | "custom_script": { 4 | "method": "run_script", 5 | "parameters": {}, 6 | "returns": { 7 | "type": "string" 8 | } 9 | } 10 | } 11 | } -------------------------------------------------------------------------------- /src/files/www/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasnet-community/neighbor-link/78c0f225689d7e479f7b7887899b26fa00ce04ad/src/files/www/.DS_Store -------------------------------------------------------------------------------- /src/files/www/cgi-bin/api: -------------------------------------------------------------------------------- 1 | #!/usr/bin/lua 2 | 3 | -- Utility to decode URL-encoded parameters 4 | local function url_decode(str) 5 | str = string.gsub(str, '+', ' ') 6 | str = string.gsub(str, '%%(%x%x)', function(h) return string.char(tonumber(h, 16)) end) 7 | return str 8 | end 9 | 10 | local function log_message(msg) 11 | local log_file = io.open("/tmp/lua_script.log", "a") 12 | log_file:write(os.date("%Y-%m-%d %H:%M:%S") .. " - " .. msg .. "\n") 13 | log_file:close() 14 | end 15 | 16 | -- Function to quote a parameter for safe shell usage 17 | local function quote_param(param) 18 | return "'" .. string.gsub(param, "'", "'\\''") .. "'" 19 | end 20 | 21 | local function execute_command(cmd, params) 22 | log_message("Executing command: " .. cmd) 23 | if cmd ~= "dragon.sh" then 24 | return "Error: Unauthorized command", 1 25 | end 26 | 27 | local quoted_params = {} 28 | for _, param in ipairs(params) do 29 | table.insert(quoted_params, quote_param(param)) 30 | end 31 | 32 | local full_cmd = cmd .. " " .. table.concat(quoted_params, " ") 33 | log_message("Full command: " .. full_cmd) 34 | 35 | local result = io.popen(full_cmd .. " 2>&1"):read("*a") 36 | local exit_code = io.popen("echo $?"):read("*n") 37 | 38 | log_message("Command result: " .. (result or "")) 39 | log_message("Exit code: " .. (exit_code or -1)) 40 | 41 | return result, exit_code 42 | end 43 | 44 | local function read_body() 45 | local content_length = tonumber(os.getenv("CONTENT_LENGTH")) 46 | if content_length then 47 | log_message("Content length: " .. content_length) 48 | local body = io.read(content_length) 49 | log_message("Body: " .. body) 50 | return body 51 | end 52 | log_message("No content length provided") 53 | return nil 54 | end 55 | 56 | local function parse_params(body) 57 | local params = {} 58 | for k, v in string.gmatch(body, "([^&=]+)=([^&=]+)") do 59 | params[url_decode(k)] = url_decode(v) 60 | end 61 | log_message("Parsed params: " .. table.concat(params, ", ")) 62 | return params 63 | end 64 | 65 | -- Simple JSON encoding function 66 | local function json_encode(tbl) 67 | local result = {} 68 | for k, v in pairs(tbl) do 69 | if type(v) == "string" then 70 | v = string.gsub(v, '"', '\\"') 71 | table.insert(result, string.format('"%s":"%s"', k, v)) 72 | else 73 | table.insert(result, string.format('"%s":%s', k, tostring(v))) 74 | end 75 | end 76 | return "{" .. table.concat(result, ",") .. "}" 77 | end 78 | 79 | print("Content-Type: application/json\n") 80 | 81 | local body = read_body() 82 | local response = {} 83 | 84 | if body then 85 | local params = parse_params(body) 86 | if params.cmd then 87 | local cmd = params.cmd 88 | local param_str = params.params 89 | local param_list = {} 90 | if param_str then 91 | for param in param_str:gmatch("[^%s]+") do 92 | table.insert(param_list, param) 93 | end 94 | end 95 | local output, exit_code = execute_command(cmd, param_list) 96 | response.output = output 97 | response.exit_code = exit_code 98 | else 99 | log_message("Error: No command provided") 100 | response.error = "Error: No command provided" 101 | response.exit_code = 1 102 | end 103 | else 104 | log_message("Error: No body provided") 105 | response.error = "Error: No body provided" 106 | response.exit_code = 1 107 | end 108 | 109 | print(json_encode(response)) -------------------------------------------------------------------------------- /src/files/www/cgi-bin/dashboard.lua: -------------------------------------------------------------------------------- 1 | #!/usr/bin/lua 2 | 3 | function handle_request(env) 4 | 5 | 6 | local content_types = { 7 | html = "text/html", 8 | css = "text/css", 9 | js = "application/javascript", 10 | json = "application/json", 11 | png = "image/png", 12 | jpg = "image/jpeg", 13 | jpeg = "image/jpeg", 14 | gif = "image/gif" 15 | } 16 | 17 | 18 | local path = tostring(env.REQUEST_URI) 19 | -- Remove query string and hashtag from the path 20 | path = path:gsub("%?.*$", ""):gsub("#.*$", "") 21 | 22 | -- Extract everything after /dashboard/ instead of just the last segment 23 | local relative_path = path:match("/dashboard/(.*)$") or "" 24 | local file_path = "/www/dashboard/" .. (relative_path ~= "" and relative_path or "index.html") 25 | 26 | -- Get file extension for content type (using cleaned path) 27 | local ext = path:match("%.([^%.]+)$") 28 | local content_type = content_types[ext] or "text/html" 29 | 30 | -- Try to open and read the file 31 | local file = io.open(file_path, "r") 32 | if file then 33 | -- Output headers 34 | print("Status: 200 OK") 35 | print("Content-Type: " .. content_type) 36 | print("Cache-Control: no-store, no-cache, must-revalidate, max-age=0") 37 | print("Pragma: no-cache") 38 | print("Expires: 0") 39 | print("") 40 | 41 | -- Read and output file content 42 | local content = file:read("*all") 43 | file:close() 44 | print(content) 45 | else 46 | -- File not found - return 404 47 | print("Status: 404 Not Found") 48 | print("Content-Type: text/html") 49 | print("Cache-Control: no-store, no-cache, must-revalidate, max-age=0") 50 | print("Pragma: no-cache") 51 | print("Expires: 0") 52 | print("") 53 | print("

404 - File not found

") 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /src/files/www/dashboard/ethernet.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Step 2 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 |
21 | 31 |
32 |
Step 2: Connect NeighborLink to Iran Internet
33 |
34 | 44 |
45 | 46 | 47 |
48 | 49 | 50 | 60 | 61 | 62 | 79 |
80 | 81 |
82 |
83 | 93 |
94 |
95 |
96 |
97 | Status: Disconnected 98 |
99 |
100 | _ 101 |
102 |
103 | 104 |
105 |
106 | 107 |
108 |
109 | Loading... 110 |
111 |
112 |
113 | Loading... 114 |
115 |
116 |
117 | 118 |
119 | 123 |
124 | 125 |
126 | 127 | 128 | -------------------------------------------------------------------------------- /src/files/www/dashboard/firmware.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Firmware 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 |
19 | 27 |
28 |

Firmware Upgrade

29 |
30 |
31 | 32 | 33 |
34 | 35 | 36 | 42 | 43 | 44 | 51 |
52 | 53 |
54 |
55 |
56 | 63 | 64 | 71 |
72 |
73 | 74 |
75 | 78 |
79 |
80 | 81 |
82 |
83 | Loading... 84 |
85 |
86 |
87 | Loading... 88 |
89 |
90 |
91 | 92 |
93 | 96 |
97 | 98 |
99 | 100 | 101 | -------------------------------------------------------------------------------- /src/files/www/dashboard/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Login 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 |
15 |
16 | 20 | 21 |
22 |
23 |

Authentication

24 |
25 |
26 |
27 | 28 | 29 |
30 |
31 | 32 |
33 |
34 |
35 | 36 |
37 |
38 |
39 | 40 | -------------------------------------------------------------------------------- /src/files/www/dashboard/management.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Dashboard 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |
14 |
15 | 16 | 20 | 21 | Back to Dashboard 22 | 23 |

User Management

24 |
25 |
26 |
27 | 28 | 29 |
30 |
31 | 32 | 33 |
34 | 35 | 36 |
37 |
38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 |
#UsernameActions
49 | 50 |
51 |
52 |
53 |
54 |
55 | Loading... 56 |
57 |
58 | 59 | 76 | 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /src/files/www/dashboard/outline.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Step 5 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 |
19 | 27 |
28 |
Setup Outline Relay
29 |
30 | 31 |
32 | 33 | 34 |
35 | 36 | 37 | 58 | 59 | 60 | 86 |
87 | 88 | 89 |
90 |

Outline:

91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 | 99 |
100 |
101 |
102 | 103 | 110 | 111 |
Past original accesskey here
112 |
113 | 114 | 115 |
116 |   117 | 118 |
119 | 120 |
121 |
122 |
123 | Loading... 124 |
125 |
126 |
127 | Loading... 128 |
129 |
130 |
131 | 132 |
133 | 136 |
137 | 138 | 139 | -------------------------------------------------------------------------------- /src/files/www/dashboard/proxy.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Step 5 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 |
19 | 27 |
28 |
HTTPS Proxy Config
29 |
30 | 31 |
32 | 33 | 34 |
35 | 36 | 37 | 60 | 61 | 62 | 86 |
87 | 88 | 89 |

Proxy Info:

90 |
91 |
92 |
93 | 94 | 95 | 96 | 97 | 100 | 101 | 102 | 103 | 106 | 107 | 108 | 109 | 112 | 113 | 114 | 115 | 118 | 119 | 120 |
Host: 98 | 99 |
Port: 104 | 105 |
Username: 110 | 111 |
Password: 116 | 117 |
121 |
122 |
123 | 124 |
125 |
126 |
127 | Loading... 128 |
129 |
130 |
131 | Loading... 132 |
133 |
134 |
135 | 136 |
137 | 140 |
141 | 142 | 143 | -------------------------------------------------------------------------------- /src/files/www/dashboard/scripts/common/heartBeat.js: -------------------------------------------------------------------------------- 1 | const toastLiveExample = document.getElementById('liveToast') 2 | const toastBootstrap = bootstrap.Toast.getOrCreateInstance(toastLiveExample) 3 | var toastShowCount = 0; 4 | var isSet=false 5 | function heartBeat() { 6 | const Device_ID=["uci", "get", {"config":"routro"}]; 7 | ubus_call(Device_ID,function(chunk){ 8 | if(chunk[0] !== 0) { 9 | toastBootstrap.show() 10 | toastShowCount++ 11 | } 12 | else if(chunk[1]?.values?.firmware?.version){ 13 | if(isSet==false){ 14 | const versionSeen = localStorage.getItem("opr-version-seen"); 15 | const currentVersion = chunk[1]?.values?.firmware?.version 16 | if(currentVersion != versionSeen){ 17 | localStorage.setItem("opr-dashboard-seen","false") 18 | } 19 | localStorage.setItem("opr-version-seen",chunk[1]?.values?.firmware?.version); 20 | isSet=true; 21 | } 22 | } 23 | if(toastShowCount >= 3) { 24 | window.location.href = "index.html" 25 | } 26 | }, true); 27 | } 28 | 29 | setInterval(() => { 30 | heartBeat() 31 | }, 5000); -------------------------------------------------------------------------------- /src/files/www/dashboard/scripts/common/loading.js: -------------------------------------------------------------------------------- 1 | function loading(show,message,errorStyle) { 2 | 3 | document.getElementById('overlay').style.display = show ? 'flex' : 'none'; 4 | var message_element = document.getElementById('overlay_message'); 5 | 6 | if(errorStyle) 7 | { 8 | message_element.classList.add("text-danger") 9 | message_element.classList.remove("text-light") 10 | } 11 | else{ 12 | message_element.classList.remove("text-danger") 13 | message_element.classList.add("text-light") 14 | } 15 | if (message){ 16 | message_element.textContent = message; 17 | }else{ 18 | message_element.textContent = "Loading ..."; 19 | } 20 | } 21 | 22 | 23 | function removeSubstring ( mainStr , substringToRemove ){ 24 | var mainString = mainStr 25 | // Check if the mainString ends with the substringToRemove 26 | if (mainString.endsWith(substringToRemove)) { 27 | // Get the length of the substringToRemove 28 | let substringLength = substringToRemove.length; 29 | // Remove the substring from the end of the mainString using slice() 30 | mainString = mainString.slice(0, -substringLength); 31 | } 32 | 33 | return mainString 34 | } 35 | 36 | document.addEventListener("DOMContentLoaded", function() { 37 | const urlParams = new URLSearchParams(window.location.search); 38 | if (urlParams.has('ref') && urlParams.get('ref') === 'dashboard') { 39 | const previousButtonText = document.getElementById('back-btn-text'); 40 | previousButtonText.textContent = 'Back to Dashboard'; 41 | const previousButtonhref = document.getElementById('back-btn-href'); 42 | previousButtonhref.href = 'dashboard.html'; 43 | } 44 | }) 45 | 46 | function addCustomAlert(title, message, visibilityTime) { 47 | const alertContainer = document.getElementById('alertContainer'); 48 | 49 | const alertDiv = document.createElement('div'); 50 | alertDiv.className = 'alert alert-warning alert-dismissible fade show'; 51 | alertDiv.role = 'alert'; 52 | 53 | // Title on its own line 54 | const strongText = document.createElement('strong'); 55 | strongText.innerText = title; 56 | strongText.style.display = 'block'; // Make title a block element to appear on a new line 57 | 58 | // Message on its own line 59 | const alertMessage = document.createElement('span'); 60 | alertMessage.innerText = message; 61 | alertMessage.style.display = 'block'; // Make message a block element to appear on a new line 62 | alertMessage.style.marginTop = '5px'; // Optional: Adds some spacing between title and message 63 | 64 | // Container for close button and countdown text 65 | const buttonContainer = document.createElement('div'); 66 | buttonContainer.style.display = 'flex'; 67 | buttonContainer.style.flexDirection = 'column'; 68 | buttonContainer.style.alignItems = 'flex-end'; // Aligns items to the right 69 | 70 | const closeButton = document.createElement('button'); 71 | closeButton.type = 'button'; 72 | closeButton.className = 'btn-close'; 73 | closeButton.setAttribute('data-bs-dismiss', 'alert'); 74 | closeButton.setAttribute('aria-label', 'Close'); 75 | 76 | const countdownSpan = document.createElement('span'); 77 | countdownSpan.style.fontSize = '0.8em'; // Smaller font size for the countdown 78 | countdownSpan.style.marginTop = '5px'; // Adds a little space between button and countdown 79 | let countdown = Math.floor((visibilityTime || 20000) / 1000); // Convert visibility time to seconds 80 | countdownSpan.innerText = `(${countdown})`; 81 | 82 | buttonContainer.appendChild(closeButton); 83 | buttonContainer.appendChild(countdownSpan); 84 | 85 | alertDiv.appendChild(strongText); // Append title 86 | alertDiv.appendChild(alertMessage); // Append message 87 | alertDiv.appendChild(buttonContainer); // Add the button container to the alert 88 | 89 | alertContainer.appendChild(alertDiv); 90 | 91 | // Update the countdown every second 92 | const interval = setInterval(() => { 93 | countdown--; 94 | countdownSpan.innerText = `( ${countdown} )`; 95 | 96 | if (countdown <= 0) { 97 | clearInterval(interval); // Stop the interval when countdown is 0 98 | } 99 | }, 1000); 100 | 101 | // Remove the alert after the specified visibility time 102 | setTimeout(() => { 103 | alertDiv.classList.remove('show'); // Hides the alert 104 | alertDiv.classList.add('fade'); // Optional: adds fade-out effect 105 | setTimeout(() => alertDiv.remove(), 500); // Removes it after fade out 106 | }, visibilityTime || 20000); 107 | } 108 | 109 | 110 | 111 | 112 | // Language Switching 113 | const englishButton = document.querySelector('.en-btn'); 114 | const persianButtons = document.querySelectorAll('.fa-btn'); 115 | 116 | function switchLanguage(showEnglish) { 117 | const englishElements = document.querySelectorAll('.english-text'); 118 | const persianElements = document.querySelectorAll('.farsi-text'); 119 | 120 | englishElements.forEach(el => { 121 | el.style.display = showEnglish ? 'block' : 'none'; 122 | }); 123 | 124 | persianElements.forEach(el => { 125 | el.style.display = showEnglish ? 'none' : 'block'; 126 | }); 127 | } 128 | 129 | if( englishButton && persianButtons){ 130 | // English button click handler 131 | englishButton.addEventListener('click', () => { 132 | switchLanguage(true); 133 | }); 134 | 135 | // Persian buttons click handler 136 | persianButtons.forEach(btn => { 137 | btn.addEventListener('click', () => { 138 | switchLanguage(false); 139 | }); 140 | }); 141 | 142 | // Initialize with Persian text by default 143 | switchLanguage(false); 144 | } -------------------------------------------------------------------------------- /src/files/www/dashboard/scripts/common/ubus.js: -------------------------------------------------------------------------------- 1 | var ubus_call_id = 0; 2 | var token=localStorage.getItem("routro_token"); 3 | 4 | function ubus_call(params,cb){ 5 | 6 | ubus_call_id++; 7 | var body={ 8 | "jsonrpc": "2.0", 9 | "id": ubus_call_id, 10 | "method": "call", 11 | "params":[ 12 | token 13 | ] 14 | } 15 | for (param of params){ 16 | body["params"].push(param); 17 | } 18 | 19 | // create XMLHttpRequest object 20 | const xhr = new XMLHttpRequest(); 21 | xhr.open("POST", "http://192.168.151.1/ubus"); 22 | xhr.setRequestHeader("Content-Type", "application/json"); 23 | xhr.send(JSON.stringify(body)); 24 | 25 | xhr.onload = function() { 26 | if (xhr.status === 200) { 27 | //parse JSON datax`x 28 | var resBody = JSON.parse(xhr.responseText); 29 | 30 | if (resBody["result"]){ 31 | cb(resBody["result"]); 32 | } 33 | 34 | else{ 35 | //console.log(resBody) 36 | if(resBody["error"]?.["code"] == -32002 || resBody["error"]?.["code"] == -37200 ){ 37 | window.location.href = "index.html"; 38 | } 39 | } 40 | 41 | } 42 | else { 43 | console.log("XHR was failed") 44 | } 45 | } 46 | xhr.onerror = function() { 47 | loading(false); 48 | console.log("Network error occurred") 49 | cb([-1]) 50 | } 51 | } 52 | 53 | async function async_ubus_call(ubus_command){ 54 | return new Promise((resolve,reject) =>{ 55 | try { 56 | ubus_call(ubus_command, function(chunk) { 57 | resolve(chunk); 58 | }) 59 | } catch (error) { 60 | reject(error); 61 | } 62 | }) 63 | } 64 | 65 | 66 | function lua_call(command, params, cb) { 67 | 68 | // URL-encode the body 69 | const urlEncodedBody = `cmd=${encodeURIComponent(command)}¶ms=${encodeURIComponent(params)}`; 70 | 71 | // create XMLHttpRequest object 72 | const xhr = new XMLHttpRequest(); 73 | xhr.open("POST", "http://192.168.151.1/cgi-bin/api"); 74 | xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); 75 | xhr.send(urlEncodedBody); 76 | 77 | xhr.onload = function() { 78 | if (xhr.status === 200) { 79 | // Extract the text between <___RESPONSE___> and <___END___> 80 | const responseText = xhr.responseText; 81 | const startTag = "<___RESPONSE___>"; 82 | const endTag = "<___END___>"; 83 | const startIndex = responseText.indexOf(startTag); 84 | const endIndex = responseText.indexOf(endTag); 85 | 86 | if (startIndex !== -1 && endIndex !== -1 && startIndex < endIndex) { 87 | const extractedText = responseText.substring(startIndex + startTag.length, endIndex); 88 | cb(extractedText.trim()); 89 | } else { 90 | cb("error"); 91 | } 92 | } else { 93 | console.log("XHR failed with status: " + xhr.status); 94 | cb("error"); 95 | } 96 | } 97 | 98 | xhr.onerror = function() { 99 | console.log("Network error occurred"); 100 | cb("error"); 101 | } 102 | } 103 | 104 | async function async_lua_call(cmd,params){ 105 | return new Promise((resolve,reject) =>{ 106 | try { 107 | lua_call(cmd,params, function(chunk) { 108 | resolve(chunk); 109 | }) 110 | } catch (error) { 111 | reject(error); 112 | } 113 | }) 114 | } 115 | 116 | function ipapi_call(cb) { 117 | 118 | const xhr = new XMLHttpRequest(); 119 | xhr.open("GET", "http://ip-api.com/json"); 120 | xhr.send(); 121 | 122 | xhr.onload = function() { 123 | if (xhr.status === 200) { 124 | var resBody = JSON.parse(xhr.responseText); 125 | cb(resBody) 126 | } else { 127 | cb("error"); 128 | } 129 | } 130 | 131 | xhr.onerror = function() { 132 | cb("error"); 133 | } 134 | } 135 | 136 | async function async_ipapi_call(){ 137 | return new Promise((resolve,reject) =>{ 138 | try { 139 | ipapi_call(function(chunk) { 140 | resolve(chunk); 141 | }) 142 | } catch (error) { 143 | reject(error); 144 | } 145 | }) 146 | } -------------------------------------------------------------------------------- /src/files/www/dashboard/scripts/page-specific/citylink.js: -------------------------------------------------------------------------------- 1 | const connectButton = document.getElementById("connect-btn") 2 | const disconnectButton = document.getElementById("disconnect-btn") 3 | const connectionString = document.getElementById("connection-string") 4 | 5 | connectButton.onclick = async function(e){ 6 | showLoadingConnectButton(true) 7 | var params = validateAndParse(connectionString.value) 8 | if( params == false ) 9 | { 10 | addCustomAlert("Invalid String","Please make sure you copy the correct string, It should start with InR and end with @domain or @server_ip",10000) 11 | showLoadingConnectButton(false) 12 | return; 13 | } 14 | 15 | var result=await async_lua_call("dragon.sh","infinite-reach-connect "+params["SERVER_IP"]+" "+params["DEFAULT_CHISEL_PORT"]+" "+params["INT_PORT1"]+" "+params["EXT_PORT2"]+" "+params["EXT_PORT1"]+" "+params["SERVER_IP_TYPE"]+" "+params["CLIENT_KEY"]+" "+params["PASSWORD"]) 16 | var resultSplit = result.split(" "); 17 | if ( resultSplit[0] == "running" && resultSplit[1] == "Connected" ){ 18 | changeStatus(true,"Server://"+params["SERVER_IP"]) 19 | // connectButton.classList.add("btn-success") 20 | } 21 | else{ 22 | addCustomAlert("Somthing Went Wrong","Please try Agaign",6000) 23 | } 24 | showLoadingConnectButton(false) 25 | } 26 | 27 | disconnectButton.onclick = async function(e){ 28 | loading(true,"Disconnecting...",true); 29 | var result=await async_lua_call("dragon.sh","infinite-reach-disconnect") 30 | var resultSplit = result.split(" "); 31 | if ( resultSplit[0] == "inactive"){ 32 | changeStatus(false,"") 33 | loading(false); 34 | return; 35 | } 36 | addCustomAlert("Something Went Wrong", "Please make sure your iran internet is working and try agaign",5000) 37 | loading(false); 38 | 39 | } 40 | 41 | getStatus_iReach(); 42 | async function getStatus_iReach() { 43 | loading(true,"Getting Status..."); 44 | var result=await async_lua_call("dragon.sh","infinite-reach-status") 45 | var resultSplit = result.split(" "); 46 | if ( resultSplit[2] == "running" && resultSplit[3] == "Connected"){ 47 | changeStatus(true,"Server://"+resultSplit[4]) 48 | loading(false) 49 | return 50 | } 51 | changeStatus(false); 52 | loading(false); 53 | } 54 | 55 | const connectionStatus = document.getElementById("connection-status") 56 | function changeStatus(theStatus,underText) { 57 | const textBox = connectionStatus.getElementsByTagName("strong")[0] 58 | const ssidBox = connectionStatus.getElementsByTagName("strong")[1] 59 | if(theStatus){ 60 | textBox.textContent = "Status: Connected" 61 | ssidBox.textContent = underText 62 | connectionStatus.classList.remove("alert-danger"); 63 | connectionStatus.classList.add("alert-success"); 64 | disconnectButton.classList.remove("d-none") 65 | document.getElementById("config-cards").classList.remove("d-none") 66 | document.getElementById("connection-section").classList.add("d-none") 67 | } 68 | else{ 69 | textBox.textContent = "Status: Disconnected" 70 | ssidBox.textContent = "" 71 | connectionStatus.classList.remove("alert-success"); 72 | connectionStatus.classList.add("alert-danger"); 73 | disconnectButton.classList.add("d-none"); 74 | document.getElementById("config-cards").classList.add("d-none") 75 | document.getElementById("connection-section").classList.remove("d-none") 76 | } 77 | 78 | } 79 | 80 | 81 | 82 | const showLoadingConnectButton = (show) => { 83 | document.querySelector('#connect-btn > span').style.display = show ? 'none' : 'inline'; 84 | document.querySelector('#connect-btn > div').style.display = show ? 'inline-block' : 'none'; 85 | } 86 | /** 87 | * Validate and parse the input string. 88 | * @param {string} input - The input string to validate. 89 | * @returns {Object|boolean} - Returns a JSON object with key-value pairs if valid, otherwise false. 90 | */ 91 | function validateAndParse(input) { 92 | // Define regex for the input format 93 | const regex = /^InRe:([^:]+):([^:]+):(\d+):(\d+):(\d+):(\d+):(\d+)@([a-zA-Z0-9.-]+)$/; 94 | 95 | // Test if input matches the format 96 | const match = input.match(regex); 97 | if (!match) return false; 98 | 99 | // Extract variables from the match 100 | const [ 101 | , // Full match is ignored 102 | CLIENT_KEY, PASSWORD, DEFAULT_CHISEL_PORT, EXT_PORT1, INT_PORT1, EXT_PORT2, INT_PORT2, SERVER_IP 103 | ] = match; 104 | 105 | // Convert port values to numbers 106 | const ports = [DEFAULT_CHISEL_PORT, EXT_PORT1, INT_PORT1, EXT_PORT2, INT_PORT2].map(Number); 107 | 108 | // Check if all port values are numbers 109 | if (ports.some(isNaN)) return false; 110 | 111 | // Determine if SERVER_IP is a domain, IPv4, or IPv6 112 | let serverIpType = null; 113 | if (isIPv4(SERVER_IP)) { 114 | serverIpType = "IPv4"; 115 | } else if (isIPv6(SERVER_IP)) { 116 | serverIpType = "IPv6"; 117 | } else if (isDomain(SERVER_IP)) { 118 | serverIpType = "domain"; 119 | } else { 120 | return false; // Invalid SERVER_IP format 121 | } 122 | 123 | // Return the JSON object with the extracted values 124 | return { 125 | CLIENT_KEY, 126 | PASSWORD, 127 | DEFAULT_CHISEL_PORT: ports[0], 128 | EXT_PORT1: ports[1], 129 | INT_PORT1: ports[2], 130 | EXT_PORT2: ports[3], 131 | INT_PORT2: ports[4], 132 | SERVER_IP, 133 | SERVER_IP_TYPE: serverIpType 134 | }; 135 | } 136 | 137 | /** 138 | * Check if the input is a valid IPv4 address. 139 | * @param {string} ip - The input to check. 140 | * @returns {boolean} - True if valid IPv4, false otherwise. 141 | */ 142 | function isIPv4(ip) { 143 | const ipv4Regex = /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/; 144 | return ipv4Regex.test(ip); 145 | } 146 | 147 | /** 148 | * Check if the input is a valid IPv6 address. 149 | * @param {string} ip - The input to check. 150 | * @returns {boolean} - True if valid IPv6, false otherwise. 151 | */ 152 | function isIPv6(ip) { 153 | const ipv6Regex = /^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:))$/; 154 | return ipv6Regex.test(ip); 155 | } 156 | 157 | /** 158 | * Check if the input is a valid domain name. 159 | * @param {string} domain - The input to check. 160 | * @returns {boolean} - True if valid domain, false otherwise. 161 | */ 162 | function isDomain(domain) { 163 | const domainRegex = /^([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$/; 164 | return domainRegex.test(domain); 165 | } 166 | 167 | -------------------------------------------------------------------------------- /src/files/www/dashboard/scripts/page-specific/dashboard.js: -------------------------------------------------------------------------------- 1 | // const vpnOn = document.getElementById("vpn-on") 2 | // const vpnOff = document.getElementById("vpn-off") 3 | const vpnStauts = document.getElementById("connection-status") 4 | const vpnShield = document.getElementById("vpn-shield") 5 | const ipAddress = document.getElementById("ip-address") 6 | const internetProvider = document.getElementById("internet-provider") 7 | 8 | const btnStarlink = document.getElementById('starlink-btn') 9 | const btnIran = document.getElementById('iran-btn') 10 | const btnSettings = document.getElementById('settings-btn') 11 | const btnVPN = document.getElementById('vpn-btn') 12 | const btnGuest = document.getElementById('guest-btn') 13 | const btnReach = document.getElementById('reach-btn') 14 | const btnFrimware = document.getElementById('frimware-btn') 15 | 16 | btnStarlink.onclick=function (e){ 17 | window.location.href = "wifi.html?ref=dashboard"; 18 | } 19 | btnIran.onclick=function (e){ 20 | window.location.href = "ethernet.html?ref=dashboard"; 21 | } 22 | btnSettings.onclick=function (e){ 23 | window.location.href = "settings.html?ref=dashboard"; 24 | } 25 | btnVPN.onclick=function (e){ 26 | window.location.href = "vpn.html?ref=dashboard"; 27 | } 28 | btnGuest.onclick=function (e){ 29 | window.location.href = "guest.html?ref=dashboard"; 30 | } 31 | btnReach.onclick=function (e){ 32 | window.location.href = "citylink.html?ref=dashboard"; 33 | } 34 | btnFrimware.onclick=function (e){ 35 | window.location.href = "firmware.html?ref=dashboard"; 36 | } 37 | 38 | 39 | // document.addEventListener("DOMContentLoaded", function() { 40 | // // Expected values 41 | // const expectedDashboardSeen = "true"; 42 | // // Retrieve values from localStorage 43 | // const dashboardSeen = localStorage.getItem("opr-dashboard-seen"); 44 | 45 | // // Check if the values match the expected values 46 | // if (dashboardSeen !== expectedDashboardSeen ) { 47 | // // Redirect to the desired page if values do not match 48 | // window.location.href = "wifi.html"; 49 | // } 50 | // }); 51 | 52 | function showWarning(status){ 53 | if(status=="show"){ 54 | document.getElementById("warning-part").classList.remove('d-none'); 55 | }else{ 56 | document.getElementById("warning-part").classList.add('d-none'); 57 | } 58 | } 59 | 60 | function changeVPNstatus(status) { 61 | if (status == "connect") { 62 | vpnShield.setAttribute ('fill' ,'blue') 63 | vpnStauts.textContent = "Connected" 64 | showWarning("hide") 65 | //vpnOn.checked=true 66 | } 67 | if (status == "disconnect") { 68 | vpnShield.setAttribute ('fill' ,'red') 69 | vpnStauts.textContent = "Disconnected" 70 | showWarning("show") 71 | //vpnOff.checked=true 72 | } 73 | if (status == "connecting") { 74 | vpnShield.setAttribute ('fill' ,'yellow') 75 | vpnStauts.textContent = "Connecting..." 76 | showWarning("show") 77 | //vpnOff.checked=true 78 | } 79 | 80 | } 81 | function setIpInfo(ip,provider) { 82 | ipAddress.textContent=ip 83 | internetProvider.textContent=provider 84 | } 85 | 86 | function updateStarlink(status) { 87 | if (status == "connect") { 88 | btnStarlink.classList.add('btn-success') 89 | btnStarlink.classList.remove('btn-danger') 90 | } 91 | if (status == "disconnect") { 92 | btnStarlink.classList.add('btn-danger') 93 | btnStarlink.classList.remove('btn-success') 94 | } 95 | } 96 | function updateIran(status) { 97 | if (status == "connect") { 98 | btnIran.classList.add('btn-success') 99 | btnIran.classList.remove('btn-danger') 100 | } 101 | if (status == "disconnect") { 102 | btnIran.classList.add('btn-danger') 103 | btnIran.classList.remove('btn-success') 104 | } 105 | } 106 | function updateGuest(status) { 107 | if (status == "enable") { 108 | btnGuest.classList.add('btn-success') 109 | btnGuest.classList.remove('btn-danger') 110 | } 111 | if (status == "disable") { 112 | btnGuest.classList.add('btn-danger') 113 | btnGuest.classList.remove('btn-success') 114 | } 115 | } 116 | 117 | function updateReach(status) { 118 | if (status == "disconnect") { 119 | btnReach.classList.add('btn-danger') 120 | btnReach.classList.remove('btn-suncess') 121 | btnReach.classList.remove('btn-warning') 122 | } 123 | if (status == "disable") { 124 | btnReach.classList.add('btn-warning') 125 | btnReach.classList.remove('btn-success') 126 | btnReach.classList.remove('btn-danger') 127 | } 128 | if (status == "enable") { 129 | btnReach.classList.add('btn-success') 130 | btnReach.classList.remove('btn-danger') 131 | btnReach.classList.remove('btn-warning') 132 | } 133 | } 134 | 135 | function updateVpn(status) { 136 | if (status == "disconnected") { 137 | btnVPN.classList.add('btn-danger') 138 | btnVPN.classList.remove('btn-suncess') 139 | btnVPN.classList.remove('btn-warning') 140 | } 141 | if (status == "noconfig") { 142 | btnVPN.classList.add('btn-warning') 143 | btnVPN.classList.remove('btn-success') 144 | btnVPN.classList.remove('btn-danger') 145 | } 146 | if (status == "connected") { 147 | btnVPN.classList.add('btn-success') 148 | btnVPN.classList.remove('btn-danger') 149 | btnVPN.classList.remove('btn-warning') 150 | } 151 | } 152 | 153 | // vpnOn.onclick=async function(){ 154 | // loading(true,"Connecting to the VPN") 155 | // var vpn=await async_lua_call("dragon.sh","vpn-on") 156 | // console.log(vpn) 157 | // readVpnStatus() 158 | // } 159 | // vpnOff.onclick=async function(){ 160 | // loading(true,"Disconnecting from the VPN, Your starlink ip will not be transparent") 161 | // var vpn=await async_lua_call("dragon.sh","vpn-off") 162 | // console.log(vpn) 163 | // readVpnStatus() 164 | // } 165 | 166 | async function ipapi(){ 167 | //var jsonString= await async_lua_call("dragon.sh","ip-api") 168 | //var unescapedString = jsonString.replace(/\\"/g, '"'); 169 | //var jsonObject = JSON.parse(unescapedString); 170 | //setIpInfo(jsonObject["query"],jsonObject["country"],jsonObject["isp"]) 171 | var jsonString= await async_ipapi_call() 172 | setIpInfo(jsonString["query"],jsonString["isp"]) 173 | return jsonString 174 | } 175 | 176 | 177 | 178 | readVpnStatus() 179 | async function readVpnStatus(){ 180 | loading(true,"Getting vpn status") 181 | const VPN_STAT=["file","exec",{"command":"wg_scripts.sh","params":[ "status" ]}]; 182 | var response=await async_ubus_call(VPN_STAT) 183 | // Extract the 'stdout' from the response 184 | const stdout = response[1].stdout; 185 | // Check if 'Connected' is present in the 'stdout' 186 | if (stdout.includes('__Connected__')) { 187 | changeVPNstatus("connect") 188 | updateVpn("connected") 189 | 190 | } else if (stdout.includes('__Disconnected__')) { 191 | changeVPNstatus("disconnect") 192 | updateVpn("disconnected") 193 | } else if (stdout.includes('__Error__')) { 194 | changeVPNstatus("connecting") 195 | updateVpn("noconfig") 196 | }else if (stdout.includes('__No-Config__')) { 197 | changeVPNstatus("disconnect") 198 | updateVpn("noconfig") 199 | }else { 200 | changeVPNstatus("connecting") 201 | updateVpn("disconnected") 202 | } 203 | await ipapi() 204 | loading(false) 205 | } 206 | netdump() 207 | function netdump(){ 208 | loading(true) 209 | const NET_DUMP=["network.interface","dump",{}] 210 | wanInterface=""; 211 | ubus_call(NET_DUMP,function(chunk){ 212 | if(chunk[0]==0){ 213 | 214 | InterfaceInfo=chunk[1].interface; 215 | InterfaceInfo.forEach(element => { 216 | if (element.interface == "wwan") { 217 | if(element.up){ 218 | updateStarlink("connect") 219 | }else{ 220 | updateStarlink("disconnect") 221 | } 222 | } 223 | if (element.interface == "wan") { 224 | if(element.up){ 225 | updateIran("connect") 226 | }else{ 227 | updateIran("disconnect") 228 | } 229 | } 230 | if (element.interface == "Guest") { 231 | if(element.up){ 232 | updateGuest("enable") 233 | }else{ 234 | updateGuest("disable") 235 | } 236 | } 237 | }); 238 | } 239 | loading(false) 240 | }); 241 | } 242 | 243 | getConfig(); 244 | async function getConfig(){ 245 | var rawConfig = await async_lua_call("dragon.sh","infinite-reach-status") 246 | var splitedConfig = rawConfig.split(" ") 247 | if( splitedConfig[2] == "running" && splitedConfig[3]== "Connected"){ 248 | updateReach("enable") 249 | }else{ 250 | updateReach("disable") 251 | } 252 | } 253 | 254 | document.getElementById('en-btn').addEventListener('click', function() { 255 | document.getElementById('english-alert').classList.remove('d-none'); 256 | document.getElementById('farsi-alert').classList.add('d-none'); 257 | }); 258 | 259 | document.getElementById('fa-btn').addEventListener('click', function() { 260 | document.getElementById('english-alert').classList.add('d-none'); 261 | document.getElementById('farsi-alert').classList.remove('d-none'); 262 | }); -------------------------------------------------------------------------------- /src/files/www/dashboard/scripts/page-specific/ethernet.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | const checkButton = document.getElementById('check-eth-btn'); 4 | var testflag=true 5 | checkButton.onclick = () =>{ 6 | netdump(); 7 | } 8 | 9 | const connectionStatus = document.getElementById("connection-status") 10 | function changeStatus(theStatus,SSID) { 11 | const textBox = connectionStatus.getElementsByTagName("strong")[0] 12 | const ssidBox = connectionStatus.getElementsByTagName("strong")[1] 13 | if(theStatus){ 14 | textBox.textContent = "Status: Connected" 15 | ssidBox.textContent = SSID 16 | connectionStatus.classList.remove("alert-danger"); 17 | connectionStatus.classList.add("alert-success"); 18 | } 19 | else{ 20 | textBox.textContent = "Status: Disconnected" 21 | ssidBox.textContent = "" 22 | connectionStatus.classList.remove("alert-success"); 23 | connectionStatus.classList.add("alert-danger"); 24 | } 25 | 26 | } 27 | 28 | 29 | netdump(); 30 | function netdump(){ 31 | loading(true) 32 | const NET_DUMP=["network.interface","dump",{}] 33 | wanInterface=""; 34 | ubus_call(NET_DUMP,function(chunk){ 35 | if(chunk[0]==0){ 36 | 37 | InterfaceInfo=chunk[1].interface; 38 | InterfaceInfo.forEach(element => { 39 | if (element.interface == "wan") { 40 | wanInterface=element 41 | console.log(element); 42 | } 43 | }); 44 | } 45 | if(wanInterface.up ){ 46 | console.log("wanInterface") 47 | changeStatus(wanInterface.up,wanInterface['ipv4-address'][0].address) 48 | } 49 | 50 | loading(false) 51 | 52 | }); 53 | } -------------------------------------------------------------------------------- /src/files/www/dashboard/scripts/page-specific/firmware.js: -------------------------------------------------------------------------------- 1 | var checkUpdateButton = document.getElementById('check-update'); 2 | var doUpdateButton = document.getElementById("do-update") 3 | 4 | checkUpdateButton.onclick = function(e){ 5 | loading(true,"Checking for update") 6 | const UPDATE_CHECK=["file","exec",{"command":"router_updater.sh","params":[ "Check" ]}]; 7 | ubus_call(UPDATE_CHECK,function(chunk){ 8 | loading(false) 9 | if(chunk.length > 1){ 10 | let responseTexMessage = chunk[1]["stdout"] 11 | document.getElementById("update-notif").textContent = responseTexMessage 12 | 13 | if ( responseTexMessage.includes("different") ){ 14 | doUpdateButton.classList.add('d-inline-block') 15 | doUpdateButton.classList.remove('d-none') 16 | } 17 | else{ 18 | doUpdateButton.classList.remove('d-inline-block') 19 | doUpdateButton.classList.add('d-none') 20 | } 21 | } 22 | else{ 23 | document.getElementById("update-notif").textContent = "Connection failed" 24 | } 25 | 26 | }) 27 | } 28 | 29 | doUpdateButton.onclick = function(e){ 30 | const UPDATE_DO=["file","exec",{"command":"router_updater.sh","params":[ "Do" ]}]; 31 | const SYSUPGRADE_N=["file","exec",{"command":"router_updater.sh","params":[ "Upgrade" ]}]; 32 | loading(true,"Preparing for upgrade") 33 | ubus_call(UPDATE_DO,function(chunk){ 34 | let responseTexMessage = chunk[1]["stdout"] 35 | document.getElementById("update-notif").textContent = responseTexMessage 36 | 37 | if ( responseTexMessage.includes("Successfully") ){ 38 | document.getElementById("update-notif").textContent = "Firmware Downloaded" 39 | loading(true,"Upgrade firmware... > Please wait for 10 Minutes, Then reconncet to router and reload this page") 40 | ubus_call(SYSUPGRADE_N,function(chunk){ 41 | console.log( chunk ) 42 | }) 43 | } 44 | else{ 45 | document.getElementById("update-notif").textContent = "Firmware Download Failed" 46 | loading(false) 47 | } 48 | 49 | }) 50 | } 51 | 52 | -------------------------------------------------------------------------------- /src/files/www/dashboard/scripts/page-specific/guest.js: -------------------------------------------------------------------------------- 1 | var guestWifiEnable = document.getElementById('guest-wifi-enable'); 2 | var guestWifiEnableLabel = document.getElementById('guest-wifi-enable-label'); 3 | var guestSsid = document.getElementById('guest-ssid'); 4 | var guestPassword = document.getElementById('guest-password'); 5 | var guestUpdate = document.getElementById('guest-update'); 6 | var userManagement = document.getElementById('user-management'); 7 | const otherParts = document.getElementById('other-parts') 8 | const connectedDevice = document.getElementById('guest-connected-devices') 9 | const totalSend = document.getElementById('guest-connected-total-send') 10 | const totalReceive = document.getElementById('guest-connected-total-receive') 11 | const connectedClientsList = document.getElementById('connected-clients-list'); 12 | 13 | function validateSSID(ssid) { 14 | // Allowed characters: alphanumeric, space, and special characters ! . _ - () 15 | const ssidRegex = /^[a-zA-Z0-9 !._\-()]+$/; 16 | return ssidRegex.test(ssid); 17 | } 18 | 19 | function validatePassword(password) { 20 | // Allowed characters: any printable ASCII character 21 | const passwordRegex = /^[\x20-\x7E]+$/; // ASCII range for printable characters (space to ~) 22 | return passwordRegex.test(password); 23 | } 24 | 25 | guestUpdate.onclick = async function(e){ 26 | var newSSID = guestSsid.value; 27 | var newPASS = guestPassword.value; 28 | if( !validateSSID(newSSID) ){ 29 | addCustomAlert("Error!","SSID has not acceptable charachter") 30 | return 31 | } 32 | if( !validatePassword(newPASS) ){ 33 | addCustomAlert("Error!","Password has not acceptable charachter") 34 | return 35 | } 36 | loading(true,"Set Guest Wifi Info") 37 | await async_lua_call("dragon.sh","guest-set "+newSSID+" "+newPASS) 38 | await wifiInfo() 39 | } 40 | 41 | function updateTable(count,send,receive) { 42 | connectedDevice.textContent = count; 43 | totalSend.textContent = send 44 | totalReceive.textContent = receive 45 | } 46 | 47 | function addConnectedClient(mac,total) { 48 | 49 | var tr = document.createElement('tr'); 50 | var td1 = document.createElement('td'); 51 | td1.textContent = mac; 52 | var td2 = document.createElement('td'); 53 | td2.textContent = parseInt(total); 54 | 55 | tr.append(td1, td2); 56 | connectedClientsList.append(tr); 57 | } 58 | function clearClient(){ 59 | connectedClientsList.innerHTML=''; 60 | } 61 | 62 | 63 | guestWifiEnable.onclick = async function(e){ 64 | 65 | setStatus(guestWifiEnable.checked) 66 | if(guestWifiEnable.checked){ 67 | loading(true,"Enabling Guest wifi") 68 | await async_lua_call("dragon.sh","guest-on") 69 | await wifiInfo(); 70 | }else{ 71 | loading(true,"Disabling Guest wifi") 72 | await async_lua_call("dragon.sh","guest-off") 73 | await wifiInfo(); 74 | } 75 | 76 | } 77 | function setStatus(status) { 78 | if(status){ 79 | guestWifiEnableLabel.textContent = "Enable"; 80 | guestWifiEnable.checked = true; 81 | otherParts.classList.add('d-block'); 82 | otherParts.classList.remove('d-none'); 83 | }else{ 84 | guestWifiEnableLabel.textContent = "Disable"; 85 | guestWifiEnable.checked = false; 86 | otherParts.classList.remove('d-block'); 87 | otherParts.classList.add('d-none'); 88 | } 89 | } 90 | 91 | userManagement.onclick = function(e){ 92 | window.location.href = './management.html'; 93 | } 94 | 95 | //netdump(); 96 | function netdump(){ 97 | loading(true) 98 | const NET_DUMP=["network.interface","dump",{}] 99 | wanInterface=""; 100 | ubus_call(NET_DUMP,function(chunk){ 101 | if(chunk[0]==0){ 102 | 103 | InterfaceInfo=chunk[1].interface; 104 | InterfaceInfo.forEach(element => { 105 | if (element.interface == "wwan") { 106 | wanInterface=element 107 | console.log(element); 108 | } 109 | }); 110 | } 111 | if(wanInterface.up ){ 112 | console.log("wanInterface") 113 | changeStatus(wanInterface.up,wanInterface['ipv4-address'][0].address) 114 | } 115 | 116 | loading(false) 117 | 118 | }); 119 | } 120 | 121 | wifiInfo() 122 | async function wifiInfo(){ 123 | loading(true) 124 | const WIFI_INFO=["uci", "get", {"config":"wireless"}]; 125 | var info = await async_ubus_call(WIFI_INFO); 126 | var device_wifi_info = info[1].values 127 | var guest_wifi_2g = device_wifi_info["guest_2g"] 128 | 129 | if(guest_wifi_2g && guest_wifi_2g.disabled == "0"){ 130 | setStatus(true); 131 | guestSsid.value = removeSubstring(guest_wifi_2g["ssid"],"-2g") 132 | guestPassword.value = guest_wifi_2g["key"] 133 | loading(false) 134 | return 135 | } 136 | 137 | var guest_wifi_5g = device_wifi_info["guest_5g"] 138 | if(guest_wifi_5g && guest_wifi_5g.disabled == "0"){ 139 | setStatus(true); 140 | guestSsid.value = removeSubstring(guest_wifi_5g["ssid"],"-5g") 141 | guestPassword.value = guest_wifi_5g["key"] 142 | loading(false) 143 | return 144 | } 145 | 146 | setStatus(false); 147 | loading(false) 148 | return 149 | } 150 | 151 | function addCustomAlert(title, message) { 152 | const alertContainer = document.getElementById('alertContainer'); 153 | 154 | const alertDiv = document.createElement('div'); 155 | alertDiv.className = 'alert alert-warning alert-dismissible fade show'; 156 | alertDiv.role = 'alert'; 157 | 158 | const strongText = document.createElement('strong'); 159 | strongText.innerText = title; 160 | 161 | const alertMessage = document.createTextNode(' ' + message); 162 | 163 | const closeButton = document.createElement('button'); 164 | closeButton.type = 'button'; 165 | closeButton.className = 'btn-close'; 166 | closeButton.setAttribute('data-bs-dismiss', 'alert'); 167 | closeButton.setAttribute('aria-label', 'Close'); 168 | 169 | alertDiv.appendChild(strongText); 170 | alertDiv.appendChild(alertMessage); 171 | alertDiv.appendChild(closeButton); 172 | 173 | alertContainer.appendChild(alertDiv); 174 | } 175 | 176 | 177 | async function guestClient() { 178 | const GUEST_CLIENT=["hostapd.wlanguest_2g","get_clients",{}]; 179 | const GUEST_CLIENT_5=["hostapd.wlanguest_5g","get_clients",{}]; 180 | var chunk = await async_ubus_call(GUEST_CLIENT); 181 | var clients = chunk[1]?.clients; 182 | chunk = await async_ubus_call(GUEST_CLIENT_5) 183 | var clients_5g = chunk[1]?.clients; 184 | 185 | var totalSend = 0; 186 | var totalReceive = 0; 187 | var totalClients = 0; 188 | clearClient(); 189 | for (var [key, value] of Object.entries(clients)) { 190 | var sendBytes = value.bytes.rx 191 | var reseiveBytes = value.bytes.tx 192 | totalSend += sendBytes; 193 | totalReceive += reseiveBytes; 194 | totalClients++; 195 | addConnectedClient(key,parseInt((reseiveBytes + sendBytes) / (1024 * 1024))) 196 | } 197 | for (var [key, value] of Object.entries(clients_5g)) { 198 | var sendBytes = value.bytes.rx 199 | var reseiveBytes = value.bytes.tx 200 | totalSend += sendBytes; 201 | totalReceive += reseiveBytes; 202 | totalClients++; 203 | addConnectedClient(key,parseInt((reseiveBytes + sendBytes) / (1024 * 1024))) 204 | } 205 | updateTable(totalClients,parseInt(totalSend / (1024 * 1024)) + ' MB', parseInt(totalReceive / (1024 * 1024)) + ' MB') 206 | //document.getElementById('guest-connected-total-send').textContent = parseInt(totalSend / (1024 * 1024)) + ' MB'; 207 | //document.getElementById('guest-connected-total-receive').textContent = parseInt(totalReceive / (1024 * 1024)) + ' MB'; 208 | // text_deviceID.value=deviceInfo["remote"]["key"]; 209 | // input_admin_pass.value=deviceInfo["device_admin"]["key"]; 210 | 211 | } 212 | setInterval(() => { 213 | guestClient() 214 | }, 6000); -------------------------------------------------------------------------------- /src/files/www/dashboard/scripts/page-specific/index.js: -------------------------------------------------------------------------------- 1 | // let login_button=document.getElementById("button_loginBUTTON") 2 | // let login_titlte=document.getElementById("text_loginTITLE") 3 | let login_password_input=document.getElementById("password"); 4 | //let login_form=document.getElementsByClassName("login-form")[0]; 5 | var loginForm = document.getElementById('login-form'); 6 | 7 | loginForm.addEventListener('submit', loginfunction); 8 | 9 | //login_form.addEventListener('submit',loginfunction); 10 | // login_button.onclick=function(){ 11 | // loginfunction(); 12 | // } 13 | 14 | function loginfunction(e){ 15 | e.preventDefault(); 16 | let password=login_password_input.value; 17 | console.log(password); 18 | login(password); 19 | } 20 | 21 | // notif handling 22 | var notifElement = document.getElementById('notif'); 23 | var notifMessageContainer = document.getElementById('notif-message'); 24 | var closeNotif = document.getElementById('close-notif'); 25 | closeNotif.addEventListener('click', function() { notif(false); }); 26 | 27 | function notif(show, type, text) { 28 | if(show) { 29 | notifElement.removeAttribute('class'); 30 | notifElement.classList.add("alert", "alert-"+type); 31 | notifMessageContainer.textContent = text; 32 | notifElement.style.display = "flex"; 33 | } else { 34 | notifElement.style.display = "none"; 35 | } 36 | } 37 | 38 | 39 | 40 | 41 | function login(password){ 42 | // login_titlte.style.color="#111111" 43 | // login_titlte.textContent="Try to Login" 44 | // open a POST request 45 | let body={ 46 | "jsonrpc": "2.0", 47 | "id": 1, 48 | "method": "call", 49 | "params":[ 50 | "00000000000000000000000000000000", 51 | "session", 52 | "login", 53 | { 54 | "username":"routro", 55 | "password":password 56 | } 57 | ] 58 | } 59 | 60 | // create XMLHttpRequest object 61 | const xhr = new XMLHttpRequest(); 62 | xhr.open("POST", "http://192.168.151.1/ubus"); 63 | xhr.setRequestHeader("Content-Type", "application/json"); 64 | xhr.send(JSON.stringify(body)); 65 | 66 | xhr.onload = function() { 67 | if (xhr.status === 200) { 68 | //parse JSON datax`x 69 | var data = JSON.parse(xhr.responseText) 70 | if( data["result"] ){ 71 | if (data["result"][0]==0){ 72 | let token=data["result"][1]["ubus_rpc_session"]; 73 | if ( token.length > 10 ){ 74 | console.log(token); 75 | localStorage.setItem("routro_token",token); 76 | login_password_input.value=""; 77 | window.location.href = "./dashboard.html"; 78 | } 79 | }else{ 80 | notif(true, 'danger', "Incorrect password!"); 81 | // login_titlte.style.color="#EA6333" //Red 82 | // login_titlte.textContent="Password incorrect"; 83 | } 84 | } 85 | else{ 86 | notif(true, 'warning', "Something Went Wrong!"); 87 | // login_titlte.style.color="#EA9A33" //Orange 88 | // login_titlte.textContent="Something Went Wrong"; 89 | } 90 | console.log(data) 91 | } else { 92 | console.log("Login was failed"); 93 | notif(true, 'danger', "Login was failed!"); 94 | // login_titlte.style.color="#EA9A33" //Orange 95 | // login_titlte.textContent="Something Went Wrong"; 96 | } 97 | } 98 | 99 | xhr.onerror = function() { 100 | console.log("Network error occurred"); 101 | notif(true, 'danger', "Network error occurred!"); 102 | // login_titlte.style.color="#EA9A33" //Orange 103 | // login_titlte.textContent="Something Went Wrong"; 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/files/www/dashboard/scripts/page-specific/management.js: -------------------------------------------------------------------------------- 1 | 2 | // JSON object for users 3 | let users = [ 4 | { 5 | username: 'loading ...', 6 | password: 'loading ...', 7 | } 8 | ] 9 | let fetch_users= [] 10 | var ubus_call_id=1 11 | var token=localStorage.getItem("routro_token"); 12 | 13 | function ubus_call(params,cb){ 14 | 15 | ubus_call_id++; 16 | var body={ 17 | "jsonrpc": "2.0", 18 | "id": ubus_call_id, 19 | "method": "call", 20 | "params":[ 21 | token 22 | ] 23 | } 24 | for (param of params){ 25 | body["params"].push(param); 26 | } 27 | 28 | // create XMLHttpRequest object 29 | const xhr = new XMLHttpRequest(); 30 | xhr.open("POST", "http://192.168.151.1/ubus"); 31 | xhr.setRequestHeader("Content-Type", "application/json"); 32 | xhr.send(JSON.stringify(body)); 33 | 34 | xhr.onload = function() { 35 | if (xhr.status === 200) { 36 | //parse JSON datax`x 37 | var resBody = JSON.parse(xhr.responseText); 38 | 39 | if (resBody["result"]){ 40 | cb(resBody["result"]); 41 | } 42 | 43 | else{ 44 | console.log(resBody) 45 | if(resBody["error"]?.["code"] == -32002){ 46 | window.location.href = "index.html"; 47 | } 48 | } 49 | } 50 | else { 51 | console.log("XHR was failed") 52 | } 53 | } 54 | xhr.onerror = function() { 55 | loading(false); 56 | console.log("Network error occurred") 57 | } 58 | } 59 | 60 | 61 | function transformValuesToObject(values) { 62 | const users = []; 63 | 64 | for (const key in values) { 65 | const user = { 66 | username: key, 67 | password: values[key].password, // Assuming the password is the same as the username 68 | }; 69 | 70 | users.push(user); 71 | } 72 | 73 | return users; 74 | } 75 | 76 | // const inputValues = { 77 | // "captive": { 78 | // ".anonymous": false, 79 | // ".type": "users", 80 | // ".name": "captive", 81 | // ".index": 0, 82 | // "username": "password", 83 | // "block": true 84 | // }, 85 | // "captive2": { 86 | // ".anonymous": false, 87 | // ".type": "users", 88 | // ".name": "captive", 89 | // ".index": 0, 90 | // "username": "password2" 91 | // } 92 | // }; 93 | 94 | 95 | // Function to render the user list 96 | function renderUserList() { 97 | const userList = document.getElementById('userList'); 98 | userList.innerHTML = ''; 99 | users.forEach((user, index) => { 100 | const row = document.createElement('tr'); 101 | row.innerHTML = ` 102 | ${index+1} 103 | ${user.username} 104 | 105 | 106 | 107 | 108 | `; 109 | userList.appendChild(row); 110 | }); 111 | } 112 | 113 | // Function to edit a user 114 | function editUser(username) { 115 | // Find the user by username 116 | const user = users.find(u => u.username === username); 117 | if (user) { 118 | // Populate the form fields with user data 119 | document.getElementById('newUsername').value = user.username; 120 | document.getElementById('newPassword').value = user.password; 121 | document.getElementById('modify-user-btn').value = "Update User"; 122 | } 123 | } 124 | 125 | // Function to delete a user 126 | function deleteUser(btn) { 127 | const username = btn.getAttribute('data-username'); 128 | // Find the user index by username 129 | const userIndex = users.findIndex(u => u.username === username); 130 | if (userIndex !== -1) { 131 | // Remove the user from the array 132 | users.splice(userIndex, 1); 133 | // Update the user list 134 | renderUserList(); 135 | } 136 | SaveIN(); 137 | myModal.hide(); 138 | } 139 | 140 | const myModal = new bootstrap.Modal(document.getElementById('confirmModal')); 141 | function deleteUserModal(username) { 142 | document.getElementById('modal-username').innerText = username; 143 | document.getElementById('modal-delete-btn').setAttribute('data-username', username); 144 | myModal.show(); 145 | } 146 | 147 | // Function to add a user 148 | function addUser() { 149 | const newUsername = document.getElementById('newUsername').value; 150 | const newPassword = document.getElementById('newPassword').value; 151 | 152 | // Check for duplicate username 153 | const existingUser = users.find(u => u.username === newUsername); 154 | if (existingUser) { 155 | // Replace the existing user's data 156 | existingUser.password = newPassword; 157 | } else { 158 | // Add the new user 159 | users.push({ 160 | username: newUsername, 161 | password: newPassword 162 | }); 163 | } 164 | 165 | // Update the user list 166 | // renderUserList(); 167 | // Clear the input fields 168 | document.getElementById('newUsername').value = ''; 169 | document.getElementById('newPassword').value = ''; 170 | document.getElementById('modify-user-btn').value = "Add User"; 171 | SaveIN(); 172 | } 173 | 174 | function resetForm() { 175 | document.getElementById('modify-user-btn').value = "Add User"; 176 | } 177 | 178 | const UCI_GET_USERS=["uci", "get", {"config":"users"}] 179 | const UCI_SET_USERS=["uci", "set", {"config":"users"}] 180 | const UCI_ADD_USERS=["uci", "add", {"config":"users"}] 181 | const UCI_DEL_USERS=["uci", "delete", {"config":"users"}] 182 | const UCI_COMMIT=["uci", "commit", {"config":"---"}]; 183 | 184 | // Function to read data 185 | function reloadData() { 186 | ubus_call(UCI_GET_USERS,function(chunk){ 187 | console.log(chunk[1]); 188 | if(chunk[1]){ 189 | users = transformValuesToObject(chunk[1]["values"]); 190 | fetch_users = Object.assign([],users); 191 | renderUserList(); 192 | console.log(users); 193 | } 194 | }) 195 | } 196 | 197 | function SaveIN(){ 198 | users.forEach(element => { 199 | 200 | var new_param = []; 201 | 202 | var params = { 203 | "values" : { 204 | password: element.password } 205 | } 206 | 207 | if ( isUsernameInArray(element.username,fetch_users) ){ 208 | params["section"] = element.username; 209 | new_param = UCI_SET_USERS; 210 | }else{ 211 | params["type"] = "users"; 212 | params["name"] = element.username; 213 | new_param = UCI_ADD_USERS; 214 | } 215 | 216 | new_param[2] = Object.assign(new_param[2],params); 217 | 218 | ubus_call(new_param,function(chunk){ 219 | if(chunk[0] == 0){ 220 | var commit_param=UCI_COMMIT; 221 | commit_param[2]["config"]=new_param[2]["config"]; 222 | ubus_call(commit_param,function(chunk2){ 223 | console.log(chunk2) 224 | }) 225 | } 226 | }) 227 | 228 | }); 229 | 230 | fetch_users.forEach(element => { 231 | if(!isUsernameInArray(element.username,users)){ 232 | var new_param = UCI_DEL_USERS; 233 | var params = { 234 | "section":element.username 235 | } 236 | new_param[2] = Object.assign(new_param[2],params); 237 | ubus_call(new_param,function(chunk){ 238 | if(chunk[0] == 0){ 239 | var commit_param=UCI_COMMIT; 240 | commit_param[2]["config"]=new_param[2]["config"]; 241 | ubus_call(commit_param,function(chunk2){ 242 | console.log(chunk2) 243 | }) 244 | } 245 | }) 246 | 247 | } 248 | }) 249 | 250 | renderUserList(); 251 | } 252 | function isUsernameInArray(usernameToCheck, arrayToSearch) { 253 | return arrayToSearch.some(item => item.username === usernameToCheck); 254 | } 255 | // Initial rendering of user list 256 | reloadData() 257 | //renderUserList(); -------------------------------------------------------------------------------- /src/files/www/dashboard/scripts/page-specific/outline.js: -------------------------------------------------------------------------------- 1 | 2 | infoSection = document.getElementById("info-section") 3 | iHost = document.getElementById("i-host") 4 | iPort = document.getElementById("i-port") 5 | iUsername = document.getElementById("i-username") 6 | iPassword = document.getElementById("i-password") 7 | regenButton = document.getElementById("regen-pass") 8 | 9 | function updateTable(user,pass,host,port) { 10 | iHost.textContent = host; 11 | iPort.textContent = port 12 | iUsername.textContent = user 13 | iPassword.textContent = pass 14 | } 15 | 16 | function parseOutlineAccessKey(accessKey) { 17 | const regex = /^ss:\/\/[^@]+@([^:]+):(\d+)/; 18 | const match = accessKey.match(regex); 19 | 20 | if (!match) { 21 | return { error: 'Invalid access key format' }; 22 | } 23 | 24 | const hash = accessKey.split("@")[0]; 25 | const host = match[1]; 26 | const port = match[2]; 27 | 28 | const isIp = /^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$/.test(host); 29 | const isDomain = /^[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/.test(host); 30 | 31 | if (!isIp && !isDomain) { 32 | return { error: 'Host is neither a valid IP address nor a domain' }; 33 | } 34 | 35 | return { 36 | hash, 37 | host, 38 | port, 39 | type: isIp ? 'IP' : 'Domain', 40 | }; 41 | } 42 | 43 | 44 | const convertButton = document.getElementById("outline-convert") 45 | const accessKey = document.getElementById("access-key") 46 | const outlineOrigin = document.getElementById("outline-origin") 47 | const outlineMap = document.getElementById("outline-map") 48 | const convertedAccessKey = document.getElementById("out-access-key") 49 | 50 | 51 | convertButton.onclick = async function(e){ 52 | loading(true,"Connect & Convert ...") 53 | var parsedKey=parseOutlineAccessKey(accessKey.value) 54 | if( parsedKey["error"] ) 55 | { 56 | addCustomAlert("access-key is not valid",parsedKey["error"]) 57 | loading(false) 58 | return; 59 | } 60 | var outlineServerIP=parsedKey["host"] 61 | if( parsedKey["type"] == "Domain" ){ 62 | outlineServerIP=await domainToIP(parsedKey["host"]) 63 | if ( outlineServerIP == null ){ 64 | addCustomAlert("Invarlid outline server","The domain is not resolve to any ipv4") 65 | loading(false) 66 | return; 67 | } 68 | } 69 | 70 | var rawinfo = await async_lua_call("dragon.sh","ireach-outline-set "+outlineServerIP+" "+parsedKey["port"]) 71 | var parsedInfo= rawinfo.split(" ") 72 | if( parsedInfo[0] != "running" || parsedInfo[1] != "Connected" ){ 73 | addCustomAlert("Something went wrong","Please try again later",4000) 74 | loading(false) 75 | return 76 | } 77 | var convertedKeyString = parsedKey["hash"]+"@"+parsedInfo[2]+":"+parsedInfo[3]; 78 | await async_lua_call("dragon.sh","ireach-outline-write "+btoa(convertedKeyString) ) 79 | convertedAccessKey.textContent = convertedKeyString+"/?outline=1#🚀 Free Internet" 80 | loading(false) 81 | } 82 | 83 | getOutline() 84 | async function getOutline(){ 85 | loading(true,"Getting info") 86 | var rawinfo = await async_lua_call("dragon.sh","ireach-outline-get") 87 | 88 | var [status, connection, savedKey] = rawinfo.split(' '); 89 | if (status != "running" || connection !="Connected" ) { 90 | outlineOrigin.textContent = "Service Is not running" 91 | outlineOrigin.classList.add("text-danger") 92 | outlineOrigin.classList.remove("text-success") 93 | } 94 | else{ 95 | outlineOrigin.textContent = "Service is Running, Share the converted key with anyone you want" 96 | outlineOrigin.classList.add("text-success") 97 | outlineOrigin.classList.remove("text-danger") 98 | convertedAccessKey.textContent = atob(savedKey)+"/?outline=1#🚀 Free Internet" 99 | } 100 | loading(false) 101 | return rawinfo.split(' '); 102 | } 103 | 104 | 105 | async function domainToIP(domain) { 106 | try { 107 | const response = await fetch(`https://dns.google/resolve?name=${domain}&type=A`); 108 | const data = await response.json(); 109 | 110 | if (data.Answer && data.Answer.length > 0) { 111 | return data.Answer[0].data; // Returns the first A record (IPv4 address) 112 | } else { 113 | // Return null if no IP address is found 114 | return null; 115 | } 116 | } catch (error) { 117 | console.error("Error fetching IP:", error.message); 118 | return null; // Return null if there was an error during the fetch or parsing 119 | } 120 | } -------------------------------------------------------------------------------- /src/files/www/dashboard/scripts/page-specific/proxy.js: -------------------------------------------------------------------------------- 1 | 2 | iHost = document.getElementById("i-host") 3 | iPort = document.getElementById("i-port") 4 | iUsername = document.getElementById("i-username") 5 | iPassword = document.getElementById("i-password") 6 | 7 | function updateTable(user,pass,host,port) { 8 | iHost.textContent = host; 9 | iPort.textContent = port 10 | iUsername.textContent = user 11 | iPassword.textContent = pass 12 | } 13 | 14 | getProxyConfig(); 15 | async function getProxyConfig(){ 16 | loading(true,"Getting info...") 17 | var rawConfig = await async_lua_call("dragon.sh","ireach-proxy-get") 18 | var status=rawConfig.split(' ')[0] 19 | if( status == "running"){ 20 | //setStatus(true); 21 | var [state,isdomain,user,pass,host,port] = rawConfig.split(' '); 22 | var proxyHost="https://"+host; 23 | if( isdomain != "domain" ){ 24 | proxyHost="http://"+host; 25 | } 26 | updateTable(user,pass,proxyHost,port) 27 | }else{ 28 | updateTable("no-Config","no-Config","no-Config","no-Config") 29 | //setStatus(false); 30 | } 31 | loading(false); 32 | } 33 | -------------------------------------------------------------------------------- /src/files/www/dashboard/scripts/page-specific/settings.js: -------------------------------------------------------------------------------- 1 | var guestSsid = document.getElementById('wifi-ssid'); 2 | var guestPassword = document.getElementById('wifi-password'); 3 | var guestUpdate = document.getElementById('wifi-update'); 4 | 5 | 6 | function validateSSID(ssid) { 7 | // Allowed characters: alphanumeric, space, and special characters ! . _ - () 8 | const ssidRegex = /^[a-zA-Z0-9 !._\-()]+$/; 9 | return ssidRegex.test(ssid); 10 | } 11 | 12 | function validatePassword(password) { 13 | // Allowed characters: any printable ASCII character 14 | const passwordRegex = /^[\x20-\x7E]+$/; // ASCII range for printable characters (space to ~) 15 | return passwordRegex.test(password); 16 | } 17 | 18 | guestUpdate.onclick = async function(e){ 19 | var newSSID = guestSsid.value; 20 | var newPASS = guestPassword.value; 21 | if( !validateSSID(newSSID) ){ 22 | addCustomAlert("Error!","SSID has not acceptable charachter",5000) 23 | return 24 | } 25 | if( !validatePassword(newPASS) ){ 26 | addCustomAlert("Error!","Password has not acceptable charachter",5000) 27 | return 28 | } 29 | if( newPASS.length < 8 ){ 30 | addCustomAlert("Error!","Password must be at leaset 8 charachters",5000) 31 | return 32 | } 33 | 34 | loading(true,"Set Wifi Info") 35 | await async_lua_call("dragon.sh","wifi-set "+newSSID+" "+newPASS) 36 | await wifiInfo() 37 | } 38 | 39 | 40 | wifiInfo() 41 | async function wifiInfo(){ 42 | loading(true) 43 | const WIFI_INFO=["uci", "get", {"config":"wireless"}]; 44 | var info = await async_ubus_call(WIFI_INFO); 45 | var device_wifi_info = info[1].values 46 | var guest_wifi_2g = device_wifi_info["default_radio1"] 47 | 48 | if(guest_wifi_2g && guest_wifi_2g.disabled == "0"){ 49 | guestSsid.value = removeSubstring(guest_wifi_2g["ssid"],"-2g") 50 | guestPassword.value = guest_wifi_2g["key"] 51 | loading(false) 52 | return 53 | } 54 | 55 | var guest_wifi_5g = device_wifi_info["default_radio0"] 56 | if(guest_wifi_5g && guest_wifi_5g.disabled == "0"){ 57 | guestSsid.value = removeSubstring(guest_wifi_5g["ssid"],"-5g") 58 | guestPassword.value = guest_wifi_5g["key"] 59 | loading(false) 60 | return 61 | } 62 | 63 | loading(false) 64 | return 65 | } -------------------------------------------------------------------------------- /src/files/www/dashboard/scripts/page-specific/vpn.js: -------------------------------------------------------------------------------- 1 | const configPart = document.getElementById("config-part") 2 | const getConfig = document.getElementById("get-config") 3 | const changeConfig = document.getElementById("change-config") 4 | const configStatus = document.getElementById("config-status") 5 | const vpnPart = document.getElementById("vpn-part") 6 | const vpnOn = document.getElementById("vpn-on") 7 | const vpnOff = document.getElementById("vpn-off") 8 | const vpnStauts = document.getElementById("connection-status") 9 | const vpnShield = document.getElementById("vpn-shield") 10 | const ipAddress = document.getElementById("ip-address") 11 | const ipCountry = document.getElementById("ip-country") 12 | const internetProvider = document.getElementById("internet-provider") 13 | 14 | function changeVPNstatus(status) { 15 | if (status == "connect") { 16 | vpnShield.setAttribute ('fill' ,'blue') 17 | vpnStauts.textContent = "Connected" 18 | vpnOn.checked=true 19 | showWarning("hide") 20 | } 21 | if (status == "disconnect") { 22 | vpnShield.setAttribute ('fill' ,'red') 23 | vpnStauts.textContent = "Disconnected" 24 | vpnOff.checked=true 25 | showWarning("show") 26 | } 27 | if (status == "connecting") { 28 | vpnShield.setAttribute ('fill' ,'yellow') 29 | vpnStauts.textContent = "Connecting..." 30 | vpnOff.checked=true 31 | showWarning("show") 32 | } 33 | 34 | } 35 | function setIpInfo(ip,country,provider) { 36 | ipAddress.textContent=ip 37 | internetProvider.textContent=provider 38 | ipCountry.textContent=country 39 | } 40 | 41 | function configView(hasconfig){ 42 | if(hasconfig){ 43 | configPart.classList.add("d-none") 44 | configPart.classList.remove("d-flex") 45 | 46 | vpnPart.classList.add("d-flex") 47 | vpnPart.classList.remove("d-none") 48 | } 49 | else{ 50 | configPart.classList.add("d-flex") 51 | configPart.classList.remove("d-none") 52 | 53 | vpnPart.classList.add("d-none") 54 | vpnPart.classList.remove("d-flex") 55 | } 56 | } 57 | 58 | getConfig.onclick=async function(){ 59 | loading(true,"Parse & Save config ...") 60 | parseConfig(); 61 | const VPN_SCR=["file","exec",{"command":"wg_scripts.sh","params":[ "get" ]}]; 62 | configStatus.textContent = "Download Config ..." 63 | const response = await async_ubus_call(VPN_SCR) 64 | const stdout = response[1].stdout; 65 | if(stdout.includes("__GET_DONE__")){ 66 | configView(true) 67 | } 68 | else if(stdout.includes("__Error__")){ 69 | configStatus.textContent = "Something Went wrong, please check you internet and try agaign" 70 | } 71 | loading(false) 72 | } 73 | 74 | changeConfig.onclick=async function(){ 75 | loading(true, "Removing the config ...") 76 | const VPN_SCR=["file","exec",{"command":"wg_scripts.sh","params":[ "del" ]}]; 77 | const response = await async_ubus_call(VPN_SCR) 78 | const stdout = response[1].stdout; 79 | if(stdout.includes("Deleted")){ 80 | configView(false); 81 | await async_lua_call("dragon.sh","vpn-off") 82 | readVpnStatus() 83 | } 84 | else if(stdout.includes("__Error__")){ 85 | configStatus.textContent = "Something Went wrong, please check you internet and try agaign" 86 | } 87 | loading(false) 88 | } 89 | 90 | vpnOn.onclick=async function(){ 91 | loading(true,"Connecting to the VPN") 92 | var vpn=await async_lua_call("dragon.sh","vpn-on") 93 | console.log(vpn) 94 | readVpnStatus() 95 | } 96 | vpnOff.onclick=async function(){ 97 | loading(true,"Disconnecting from the VPN, Your starlink ip will not be transparent") 98 | var vpn=await async_lua_call("dragon.sh","vpn-off") 99 | console.log(vpn) 100 | readVpnStatus() 101 | } 102 | 103 | netdump(); 104 | function netdump(){ 105 | loading(true) 106 | const NET_DUMP=["network.interface","dump",{}] 107 | wanInterface=""; 108 | ubus_call(NET_DUMP,function(chunk){ 109 | if(chunk[0]==0){ 110 | 111 | InterfaceInfo=chunk[1].interface; 112 | InterfaceInfo.forEach(element => { 113 | if (element.interface == "wg0") { 114 | wanInterface=element 115 | console.log(element); 116 | } 117 | }); 118 | } 119 | if(wanInterface.up == false ){ 120 | console.log("wanInterface") 121 | //changeStatus(wanInterface.up,wanInterface['ipv4-address'][0].address) 122 | } 123 | 124 | loading(false) 125 | 126 | }); 127 | } 128 | 129 | async function ipapi(){ 130 | //var jsonString= await async_lua_call("dragon.sh","ip-api") 131 | //var unescapedString = jsonString.replace(/\\"/g, '"'); 132 | //var jsonObject = JSON.parse(unescapedString); 133 | //setIpInfo(jsonObject["query"],jsonObject["country"],jsonObject["isp"]) 134 | var jsonString= await async_ipapi_call() 135 | setIpInfo(jsonString["query"],jsonString["country"],jsonString["isp"]) 136 | return jsonString 137 | } 138 | 139 | 140 | 141 | readVpnStatus() 142 | async function readVpnStatus(){ 143 | loading(true,"Getting vpn status") 144 | const VPN_STAT=["file","exec",{"command":"wg_scripts.sh","params":[ "status" ]}]; 145 | var response=await async_ubus_call(VPN_STAT) 146 | configView(true) 147 | // Extract the 'stdout' from the response 148 | const stdout = response[1].stdout; 149 | // Check if 'Connected' is present in the 'stdout' 150 | if (stdout.includes('__Connected__')) { 151 | changeVPNstatus("connect") 152 | } else if (stdout.includes('__Disconnected__')) { 153 | changeVPNstatus("disconnect") 154 | } else if (stdout.includes('__Error__')) { 155 | changeVPNstatus("connecting") 156 | }else if (stdout.includes('__No-Config__')) { 157 | configView(false) 158 | }else { 159 | changeVPNstatus("connecting") 160 | } 161 | await ipapi() 162 | loading(false) 163 | } 164 | 165 | function parseConfig() { 166 | const textarea = document.getElementById('wireguard-config'); 167 | const configText = textarea.value; 168 | 169 | const lines = configText.trim().split('\n'); 170 | const configObject = {}; 171 | 172 | let currentSection = null; 173 | 174 | lines.forEach(line => { 175 | line = line.trim(); 176 | 177 | if(line.startsWith('#')){ 178 | // the line is command 179 | console.log(line); 180 | } 181 | else if(line.startsWith('[') && line.endsWith(']')) { 182 | // New section 183 | currentSection = line.slice(1, -1).trim(); 184 | configObject[currentSection] = {}; 185 | } else if (line.includes('=')) { 186 | // Key-value pair with improved handling for '=' in values 187 | const indexOfEqual = line.indexOf('='); 188 | const key = line.substring(0, indexOfEqual).trim(); 189 | const value = line.substring(indexOfEqual + 1).trim(); 190 | 191 | if (currentSection) { 192 | configObject[currentSection][key] = value; 193 | } else { 194 | configObject[key] = value; 195 | } 196 | } 197 | }); 198 | 199 | setWireguardConfig(configObject); 200 | console.log( configObject ); 201 | console.log( btoa(JSON.stringify(configObject) ) ); 202 | } 203 | 204 | async function setWireguardConfig(config){ 205 | const VPN_CONFIG=["file","exec",{"command":"dragon.sh","params":[ "wireguard-set-conf", btoa(JSON.stringify(config) ) ]}]; 206 | res = await async_ubus_call(VPN_CONFIG) 207 | } 208 | 209 | function showWarning(status){ 210 | if(status=="show"){ 211 | document.getElementById("warning-part").classList.remove('d-none'); 212 | }else{ 213 | document.getElementById("warning-part").classList.add('d-none'); 214 | } 215 | } 216 | 217 | document.getElementById('en-btn').addEventListener('click', function() { 218 | document.getElementById('english-alert').classList.remove('d-none'); 219 | document.getElementById('farsi-alert').classList.add('d-none'); 220 | }); 221 | 222 | document.getElementById('fa-btn').addEventListener('click', function() { 223 | document.getElementById('english-alert').classList.add('d-none'); 224 | document.getElementById('farsi-alert').classList.remove('d-none'); 225 | }); 226 | -------------------------------------------------------------------------------- /src/files/www/dashboard/scripts/page-specific/wifi.js: -------------------------------------------------------------------------------- 1 | var connectionBand="2 or 5" 2 | var connectionEncryption = "WPA3 WPA2 PSK" 3 | 4 | function cloneAndUpdateWifiElement(ssid, band, security) { 5 | // Select the element to be cloned 6 | const originalElement = document.getElementById('wifi-element'); 7 | 8 | // Clone the element 9 | const clonedElement = originalElement.cloneNode(true); 10 | 11 | // Update the SSID 12 | const ssidElement = clonedElement.getElementsByClassName('ssid-span')[0]; 13 | ssidElement.textContent = ssid; 14 | 15 | // Update the band 16 | const bandElement = clonedElement.getElementsByClassName('band-span')[0]; 17 | bandElement.textContent = band; 18 | 19 | // Update the band 20 | const encElement = clonedElement.getElementsByClassName('encryption-span')[0]; 21 | encElement.textContent = security; 22 | 23 | // Update the security icon visibility 24 | const lockIcon = clonedElement.getElementsByClassName('wifi-lock-icon')[0]; 25 | console.log(security) 26 | if (security == "none") { 27 | lockIcon.classList.add("d-none"); 28 | console.log("none added") 29 | } 30 | clonedElement.classList.remove("d-none"); 31 | clonedElement.classList.add("d-flex") 32 | // Return the updated element 33 | return clonedElement; 34 | } 35 | 36 | const wifiContainer = document.getElementById('elements-container'); 37 | function clearElements() { 38 | wifiContainer.innerHTML = ''; 39 | } 40 | function addWifiElement(SSiD,band,encryption){ 41 | const newElement = cloneAndUpdateWifiElement(SSiD,band+" Ghz", encryption); 42 | newElement.onclick = () => wificlick(SSiD,band, encryption); 43 | wifiContainer.appendChild(newElement); // Append the new element to the body for demonstration 44 | } 45 | function wificlick(SSiD,band,encryption){ 46 | connectionElement.classList.add("d-flex"); 47 | connectionElement.classList.remove("d-none") 48 | passInput.value="" 49 | if(SSiD == "Hiden Network"){ 50 | ssidPart.classList.add("d-block"); 51 | ssidPart.classList.remove("d-none"); 52 | ssidInput.value="" 53 | } 54 | else{ 55 | ssidPart.classList.remove("d-block"); 56 | ssidPart.classList.add("d-none"); 57 | ssidInput.value=SSiD 58 | } 59 | connectionBand=band 60 | connectionEncryption=encryption; 61 | } 62 | 63 | 64 | const scanButton = document.getElementById("scan-wifi-btn") 65 | scanButton.onclick=async function(){ 66 | loading(true,"Scanning the Wi-Fi. This will take some time.") 67 | clearElements(); 68 | for (let index = 0; index < 2; index++) { 69 | const WIFI_SCAN=["iwinfo", "scan", {"device":"radio"+index}]; 70 | var wifi_scan_raw = await async_ubus_call(WIFI_SCAN); 71 | if( wifi_scan_raw[0] == 0 ) 72 | { 73 | var available_wifi_list=wifi_scan_raw[1].results; 74 | for (var i = 0; i< available_wifi_list.length; i++){ 75 | addWifiElement(available_wifi_list[i]["ssid"] || "Hiden Network", available_wifi_list[i]["band"] ,getEncryptionType( available_wifi_list[i] )) 76 | } 77 | } 78 | } 79 | loading(false) 80 | } 81 | 82 | 83 | const ssidInput = document.getElementById("connection-ssid"); 84 | const ssidPart = document.getElementById("ssid-part"); 85 | const passInput = document.getElementById("connection-password"); 86 | const cancelButton=document.getElementById("cancel"); 87 | const ConnectButton=document.getElementById("connect"); 88 | const connectionElement = document.getElementById("connection-span") 89 | 90 | 91 | cancelButton.onclick=function(){ 92 | connectionElement.classList.remove("d-flex"); 93 | connectionElement.classList.add("d-none"); 94 | } 95 | 96 | async function check_wifi_status(){ 97 | var status= await async_lua_call("dragon.sh", "iwstat "+connectionBand) 98 | return status 99 | } 100 | function checkWifiLoop() { 101 | let intervalId = setInterval(async () => { 102 | try { 103 | let status = await check_wifi_status(); 104 | if (status === "Connected") { 105 | clearInterval(intervalId); 106 | console.log("Wi-Fi is connected."); 107 | changeStatus(true,ssidInput.value) 108 | connectionElement.classList.remove("d-flex"); 109 | connectionElement.classList.add("d-none"); 110 | clearElements(); 111 | loading(false); 112 | } 113 | else if (status === "Disabled") { 114 | clearInterval(intervalId); 115 | console.log("Wi-Fi is disabled."); 116 | loading(false); 117 | addCustomAlert("Error!","The password might be incorrect.") 118 | } else { 119 | console.log("Checking Wi-Fi status..."); 120 | } 121 | } catch (error) { 122 | console.error("Error checking Wi-Fi status:", error); 123 | } 124 | }, 5000); // 5000 milliseconds = 5 seconds 125 | } 126 | ConnectButton.onclick=function(){ 127 | 128 | const ssid = ssidInput.value.trim(); 129 | const password = passInput.value.trim(); 130 | 131 | if (ssid === "" || password === "") { 132 | addCustomAlert("Error: ","SSID and Password cannot be empty"); 133 | return; 134 | } 135 | if (password.length < 6 ) { 136 | addCustomAlert("Error: ","Password is too short"); 137 | return; 138 | } 139 | var b64ssid=btoa(ssid) 140 | var b64pass=btoa(password) 141 | const param = "iwset " + b64ssid + " " + b64pass + " " + connectionBand + " " + connectionEncryption 142 | lua_call("dragon.sh", param,function(chunk){ 143 | console.log(chunk); 144 | loading(false); 145 | if(chunk == "Done"){ 146 | loading(true,"Establishing the connection will take about 20 seconds. If you were using Wi-Fi, you might be disconnected now.") 147 | checkWifiLoop() 148 | } 149 | else{ 150 | addCustomAlert("Something Went Wrong!","Try again") 151 | } 152 | 153 | }) 154 | 155 | } 156 | 157 | 158 | function addCustomAlert(title, message) { 159 | const alertContainer = document.getElementById('alertContainer'); 160 | 161 | const alertDiv = document.createElement('div'); 162 | alertDiv.className = 'alert alert-warning alert-dismissible fade show'; 163 | alertDiv.role = 'alert'; 164 | 165 | const strongText = document.createElement('strong'); 166 | strongText.innerText = title; 167 | 168 | const alertMessage = document.createTextNode(' ' + message); 169 | 170 | const closeButton = document.createElement('button'); 171 | closeButton.type = 'button'; 172 | closeButton.className = 'btn-close'; 173 | closeButton.setAttribute('data-bs-dismiss', 'alert'); 174 | closeButton.setAttribute('aria-label', 'Close'); 175 | 176 | alertDiv.appendChild(strongText); 177 | alertDiv.appendChild(alertMessage); 178 | alertDiv.appendChild(closeButton); 179 | 180 | alertContainer.appendChild(alertDiv); 181 | } 182 | 183 | const connectionStatus = document.getElementById("connection-status") 184 | function changeStatus(theStatus,SSID) { 185 | const textBox = connectionStatus.getElementsByTagName("strong")[0] 186 | const ssidBox = connectionStatus.getElementsByTagName("strong")[1] 187 | if(theStatus){ 188 | textBox.textContent = "Status: Connected" 189 | ssidBox.textContent = SSID 190 | connectionStatus.classList.remove("alert-danger"); 191 | connectionStatus.classList.add("alert-success"); 192 | } 193 | else{ 194 | textBox.textContent = "Status: Disconnected" 195 | ssidBox.textContent = "" 196 | connectionStatus.classList.remove("alert-success"); 197 | connectionStatus.classList.add("alert-danger"); 198 | } 199 | 200 | } 201 | 202 | netdump(); 203 | function netdump(){ 204 | loading(true) 205 | const NET_DUMP=["network.interface","dump",{}] 206 | wanInterface=""; 207 | ubus_call(NET_DUMP,function(chunk){ 208 | if(chunk[0]==0){ 209 | 210 | InterfaceInfo=chunk[1].interface; 211 | InterfaceInfo.forEach(element => { 212 | if (element.interface == "wwan") { 213 | wanInterface=element 214 | console.log(element); 215 | } 216 | }); 217 | } 218 | if(wanInterface.up ){ 219 | console.log("wanInterface") 220 | changeStatus(wanInterface.up,wanInterface['ipv4-address'][0].address) 221 | } 222 | 223 | loading(false) 224 | 225 | }); 226 | } 227 | 228 | function getEncryptionType(accessPoint) { 229 | if (!accessPoint.encryption || !accessPoint.encryption.enabled) { 230 | return "none"; // No encryption 231 | } 232 | 233 | const wpaVersions = accessPoint.encryption.wpa || []; 234 | const authMethods = accessPoint.encryption.authentication || []; 235 | const ciphers = accessPoint.encryption.ciphers || []; 236 | 237 | // Check for WPA3 (SAE) 238 | if (wpaVersions.includes(3) && authMethods.includes("sae")) { 239 | if (wpaVersions.includes(2) && authMethods.includes("psk")) { 240 | return "sae-mixed"; // WPA3/WPA2 mixed mode 241 | } 242 | return "sae"; // WPA3 (SAE) only 243 | } 244 | 245 | // Check for WPA2-PSK 246 | if (wpaVersions.includes(2) && authMethods.includes("psk")) { 247 | if (ciphers.includes("ccmp")) { 248 | return "psk2"; // WPA2 with AES (CCMP) 249 | } else if (ciphers.includes("tkip")) { 250 | return "psk2-tkip"; // WPA2 with TKIP (less secure) 251 | } 252 | } 253 | 254 | // Check for WPA-PSK 255 | if (wpaVersions.includes(1) && authMethods.includes("psk")) { 256 | if (ciphers.includes("ccmp")) { 257 | return "psk"; // WPA with AES 258 | } else if (ciphers.includes("tkip")) { 259 | return "psk-tkip"; // WPA with TKIP 260 | } 261 | } 262 | 263 | // Check for WEP encryption 264 | if (authMethods.includes("wep")) { 265 | return "wep"; // WEP encryption 266 | } 267 | 268 | // Return "unknown" for any unrecognized encryption types 269 | return "unknown"; 270 | } -------------------------------------------------------------------------------- /src/files/www/dashboard/settings.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Step 4 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 |
21 | 31 |
32 |
Device Settings
33 |
34 |
35 | Dashboard 36 | 37 |
38 |
39 | 40 | 41 |
42 |

Config

43 |
44 |
45 |
46 |
47 |
48 |
49 | 50 | 51 |
Choose your device wifi name
52 |
53 |
54 | 55 | 56 |
Chose secure password
57 |
58 |
59 | 60 |
61 |
62 | 66 | 67 | 68 |
69 | 70 |
71 |
72 |
73 | Loading... 74 |
75 |
76 |
77 | Loading... 78 |
79 |
80 |
81 | 82 |
83 | 87 |
88 | 89 | 90 | -------------------------------------------------------------------------------- /src/files/www/dashboard/styles/index.css: -------------------------------------------------------------------------------- 1 | .login-container { 2 | width: 100%; 3 | display: flex; 4 | overflow: auto; 5 | min-height: 100vh; 6 | align-items: center; 7 | flex-direction: column; 8 | justify-content: center; 9 | } 10 | .login-login-form { 11 | width: 566px; 12 | height: 317px; 13 | display: flex; 14 | align-self: center; 15 | margin-top: var(--dl-space-space-twounits); 16 | align-items: flex-start; 17 | flex-direction: column; 18 | justify-content: center; 19 | background-color: #FFFFFF; 20 | } 21 | .login-title { 22 | flex: 0 0 auto; 23 | width: auto; 24 | height: auto; 25 | display: flex; 26 | align-self: flex-start; 27 | margin-top: 0px; 28 | justify-content: center; 29 | } 30 | .login-icon { 31 | fill: #7A3FDB; 32 | width: 68px; 33 | height: 51px; 34 | } 35 | .login-text { 36 | color: rgb(122, 63, 219); 37 | font-size: 40px; 38 | align-self: flex-start; 39 | font-style: normal; 40 | font-family: Roboto; 41 | font-weight: 700; 42 | } 43 | .login-form { 44 | width: 100%; 45 | height: 273px; 46 | display: flex; 47 | position: relative; 48 | align-items: flex-start; 49 | border-radius: 14px; 50 | flex-direction: column; 51 | background-color: #F3F3F3; 52 | } 53 | .login-text1 { 54 | color: rgb(50, 53, 57); 55 | font-size: 24px; 56 | font-style: normal; 57 | margin-top: var(--dl-space-space-twounits); 58 | font-weight: 700; 59 | line-height: 1; 60 | margin-left: var(--dl-space-space-twounits); 61 | } 62 | .login-line { 63 | width: 498px; 64 | height: 5px; 65 | display: flex; 66 | align-self: center; 67 | margin-top: 10px; 68 | align-items: center; 69 | margin-left: 0px; 70 | border-color: rgba(120, 120, 120, 0.4); 71 | border-style: solid; 72 | border-width: 2px; 73 | margin-right: 0px; 74 | padding-left: 0px; 75 | margin-bottom: 20px; 76 | padding-right: 0px; 77 | flex-direction: column; 78 | border-top-width: 3px; 79 | border-left-width: 0px; 80 | border-right-width: 0px; 81 | border-bottom-width: 0px; 82 | } 83 | .login-element { 84 | width: 85%; 85 | height: 108px; 86 | display: flex; 87 | align-self: center; 88 | align-items: flex-start; 89 | padding-top: var(--dl-space-space-unit); 90 | flex-direction: column; 91 | padding-bottom: var(--dl-space-space-unit); 92 | } 93 | .login-container1 { 94 | flex: 0 0 auto; 95 | width: auto; 96 | height: auto; 97 | display: flex; 98 | align-items: flex-start; 99 | justify-content: flex-start; 100 | } 101 | .login-title1 { 102 | font-size: 22px; 103 | font-family: Roboto; 104 | margin-bottom: var(--dl-space-space-halfunit); 105 | } 106 | .login-textinput { 107 | flex: 1; 108 | width: 100%; 109 | height: 55px; 110 | font-size: 20px; 111 | text-align: left; 112 | font-family: Roboto; 113 | line-height: 1; 114 | padding-top: 5px; 115 | border-style: hidden; 116 | border-radius: var(--dl-radius-radius-radius8); 117 | padding-bottom: 5px; 118 | } 119 | .login-button { 120 | color: var(--dl-color-gray-white); 121 | width: 155px; 122 | font-size: 20px; 123 | align-self: center; 124 | text-align: center; 125 | transition: 0.3s; 126 | font-family: Roboto; 127 | border-style: hidden; 128 | border-radius: 25px; 129 | background-color: #7a3fdb; 130 | } 131 | .login-button:hover { 132 | cursor: pointer; 133 | } 134 | .login-text2 { 135 | font-style: normal; 136 | font-weight: 700; 137 | } 138 | .login-text5 { 139 | display: none; 140 | font-size: 14px; 141 | margin-left: 48px; 142 | text-decoration: underline; 143 | } 144 | @media(max-width: 479px) { 145 | .login-login-form { 146 | width: 450px; 147 | } 148 | .login-line { 149 | width: 80%; 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /src/files/www/dashboard/styles/management.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: Arial, sans-serif; 3 | background-color: #f2f2f2; 4 | margin: 0; 5 | padding: 20px; 6 | display: flex; 7 | flex-direction: column; 8 | } 9 | 10 | h1 { 11 | background-color: #333; 12 | color: #fff; 13 | padding: 20px; 14 | text-align: center; 15 | } 16 | 17 | h2 { 18 | margin-top: 20px; 19 | } 20 | 21 | table { 22 | width: 80%; 23 | margin: 20px auto; 24 | border-collapse: collapse; 25 | box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); 26 | } 27 | 28 | th, td { 29 | padding: 10px; 30 | text-align: center; 31 | } 32 | 33 | th { 34 | background-color: #333; 35 | color: #fff; 36 | } 37 | 38 | tr:nth-child(even) { 39 | background-color: #f2f2f2; 40 | } 41 | 42 | tr:nth-child(odd) { 43 | background-color: #ddd; 44 | } 45 | 46 | button { 47 | background-color: #333; 48 | color: #fff; 49 | border: none; 50 | padding: 5px 10px; 51 | cursor: pointer; 52 | margin-right: 5px; 53 | } 54 | 55 | button:hover { 56 | background-color: #555; 57 | } 58 | 59 | form { 60 | width: 80%; 61 | margin: 20px auto; 62 | background-color: #fff; 63 | padding: 20px; 64 | box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); 65 | } 66 | 67 | label, input, select { 68 | display: block; 69 | margin: 10px 0; 70 | } 71 | 72 | input[type="text"], input[type="password"] { 73 | width: 95%; 74 | padding: 10px; 75 | } 76 | 77 | input[type="radio"] { 78 | margin: 5px 5px 5px 0; 79 | } 80 | 81 | select { 82 | width: 100%; 83 | padding: 10px; 84 | } 85 | 86 | button[type="submit"] { 87 | background-color: #333; 88 | color: #fff; 89 | border: none; 90 | padding: 10px 20px; 91 | cursor: pointer; 92 | } 93 | 94 | button[type="CTA"] { 95 | background-color: #0e572f; 96 | color: #fff; 97 | border: none; 98 | padding: 10px 20px; 99 | cursor: pointer; 100 | margin-right: 10px; 101 | } 102 | 103 | button[type="submit"]:hover { 104 | background-color: #555; 105 | } 106 | 107 | .blocking { 108 | display: flex; 109 | flex-direction: row; 110 | align-items: center; 111 | } 112 | 113 | .something { 114 | display: flex; 115 | flex-direction: row; 116 | flex-wrap: nowrap; 117 | justify-content: center; 118 | align-items: center; 119 | } -------------------------------------------------------------------------------- /src/files/www/dashboard/styles/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding-top: 32px; 3 | } 4 | 5 | 6 | #notif { 7 | display: none; 8 | justify-content: space-between; 9 | } 10 | 11 | #overlay { 12 | position: fixed; 13 | top: 0; 14 | right: 0; 15 | bottom: 0; 16 | left: 0; 17 | background: rgba(0,0,0,0.8); 18 | z-index: 100; 19 | display: none; 20 | align-items: center; 21 | justify-content: center; 22 | } 23 | 24 | #vpn-status-flash { 25 | visibility: hidden; 26 | } 27 | 28 | .scroll-table { 29 | height: 124px; 30 | overflow: scroll; 31 | } 32 | .scroll-table td { 33 | padding-top: 5px; 34 | padding-bottom: 5px; 35 | } 36 | 37 | .wifi-element-row { 38 | border: 1px solid white; 39 | border-radius: 5px; 40 | margin-top: 10px; 41 | padding: 10px; 42 | cursor: pointer; 43 | transition: background-color 0.3s; 44 | } 45 | .wifi-element-row:hover { 46 | background-color: #ffffff8e; 47 | color: black; 48 | } 49 | 50 | :dir(rtl) { 51 | font-family: 'Vazirmatn', sans-serif; 52 | } -------------------------------------------------------------------------------- /src/files/www/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasnet-community/neighbor-link/78c0f225689d7e479f7b7887899b26fa00ce04ad/src/files/www/favicon.ico -------------------------------------------------------------------------------- /src/files/www/nlink-dashboard/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 19 | 20 | 21 | Dashboard - Neighbor Link Interface 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/files/www/portal/auth.lua: -------------------------------------------------------------------------------- 1 | #!/usr/bin/lua 2 | 3 | -- Function to parse the query string without urllib 4 | function parse_query_string(query) 5 | local params = {} 6 | for k, v in string.gmatch(query, "([^&=?]+)=([^&=?]+)") do 7 | params[k] = v 8 | end 9 | return params 10 | end 11 | 12 | -- Function to serialize a table (stringify) 13 | function table_to_string(tbl, indent) 14 | local str = "" 15 | local indent = indent or "" 16 | 17 | for k, v in pairs(tbl) do 18 | str = str .. indent .. tostring(k) .. ": " 19 | 20 | if type(v) == "table" then 21 | str = str .. "\n" .. table_to_string(v, indent .. " ") 22 | else 23 | str = str .. tostring(v) .. "\n" 24 | end 25 | end 26 | 27 | return str 28 | end 29 | 30 | -- Function to log data to a file 31 | function log_to_file(data) 32 | local file = io.open("/www/portal/env_log.txt", "a") -- Open the file in append mode 33 | if file then 34 | file:write(data .. "\n") -- Write the data to the file 35 | file:close() -- Close the file 36 | else 37 | print("Error opening file for logging") 38 | end 39 | end 40 | 41 | -- Define the handle_request function required by uhttpd 42 | function handle_request(env) 43 | -- Log the entire env table to the file 44 | local env_string = table_to_string(env) 45 | log_to_file(env_string) -- Log the environment variables to a file 46 | 47 | -- Get the query string from the environment variable 48 | local query_string = env.QUERY_STRING or "" 49 | local params = parse_query_string(query_string) 50 | 51 | -- Retrieve username and password from query parameters 52 | local username = params["username"] or "" 53 | local password = params["password"] or "" 54 | 55 | -- Get the client's IP address 56 | local client_ip = env.REMOTE_ADDR or "" 57 | 58 | -- Example command to get MAC address (if needed) 59 | -- local client_mac = "" -- You can retrieve MAC using ARP or similar methods 60 | 61 | -- Prepare the command to execute binauth.sh 62 | local command = string.format("/usr/bin/binauth.sh %s %s %s", username, password, client_ip) 63 | 64 | -- Execute the command 65 | local handle = io.popen(command) 66 | local result = handle:read("*a") 67 | local success = handle:close() 68 | 69 | -- Redirect based on the result of the command 70 | if string.find(result, "_SUCCESS_") then 71 | -- Redirect to /success 72 | print("Status: 302 Found") 73 | print("Location: /success.html") 74 | print() -- End of headers 75 | else 76 | -- Redirect to /failed 77 | print("Status: 302 Found") 78 | print("Location: /failed.html") 79 | print() -- End of headers 80 | end 81 | end 82 | -------------------------------------------------------------------------------- /src/files/www/portal/failed.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Authentication Failed 7 | 74 | 75 | 76 | 77 |
78 |
⚠️
79 |

Authentication Failed

80 |

Please wait 10 seconds before trying again.

81 | Return to Splash Page 82 |
83 | 84 | 100 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /src/files/www/portal/images/nocensored.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasnet-community/neighbor-link/78c0f225689d7e479f7b7887899b26fa00ce04ad/src/files/www/portal/images/nocensored.jpg -------------------------------------------------------------------------------- /src/files/www/portal/images/splash.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasnet-community/neighbor-link/78c0f225689d7e479f7b7887899b26fa00ce04ad/src/files/www/portal/images/splash.jpg -------------------------------------------------------------------------------- /src/files/www/portal/splash.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: lightgrey; 3 | color: black; 4 | margin-left: 5%; 5 | margin-right: 5%; 6 | text-align: left; 7 | } 8 | 9 | hr { 10 | display:block; 11 | margin-top:0.5em; 12 | margin-bottom:0.5em; 13 | margin-left:auto; 14 | margin-right:auto; 15 | border-style:inset; 16 | border-width:5px; 17 | } 18 | 19 | .offset { 20 | background: rgba(300, 300, 300, 0.6); 21 | margin-left:auto; 22 | margin-right:auto; 23 | max-width:600px; 24 | min-width:200px; 25 | padding: 5px; 26 | } 27 | 28 | .insert 29 | { 30 | background: rgba(350, 350, 350, 0.7); 31 | border: 2px solid #aaa; 32 | border-radius: 4px; 33 | min-width:200px; 34 | max-width:100%; 35 | padding: 5px; 36 | } 37 | 38 | img { 39 | width: 40%; 40 | max-width: 180px; 41 | margin-left: 0%; 42 | margin-right: 5%; 43 | } 44 | 45 | input[type=text], input[type=email], input[type=number] { 46 | color: black; 47 | background: white; 48 | margin-left: 0%; 49 | margin-right: 5%; 50 | text-align: left; 51 | font-size: 1.0em; 52 | line-height: 2.0em; 53 | font-weight: bold; 54 | border: 3px; 55 | border-style: inset; 56 | } 57 | 58 | input[type=submit] { 59 | color: white; 60 | background: green; 61 | margin-left: 0%; 62 | margin-right: 5%; 63 | text-align: left; 64 | font-size: 1.0em; 65 | line-height: 2.5em; 66 | font-weight: bold; 67 | border: 3px; 68 | border-style: inset; 69 | } 70 | 71 | med-blue { 72 | font-size: 1.2em; 73 | color: blue; 74 | font-weight: bold; 75 | font-style: normal; 76 | } 77 | 78 | big-red { 79 | font-size: 1.5em; 80 | color: red; 81 | font-weight: bold; 82 | } 83 | 84 | italic-black { 85 | font-size: 1.0em; 86 | color: black; 87 | font-weight: bold; 88 | font-style: italic; 89 | } 90 | 91 | copy-right { 92 | font-size: 0.7em; 93 | color: darkgrey; 94 | font-weight: bold; 95 | font-style:italic; 96 | } 97 | 98 | normal-black { 99 | font-size: 1.0em; 100 | color: black; 101 | font-weight: bold; 102 | font-style: normal; 103 | } 104 | 105 | -------------------------------------------------------------------------------- /src/files/www/portal/splash.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | $gatewayname Hotspot Gateway. 16 | 17 | 65 | 66 | 67 | 68 | 69 |
70 |
71 |
72 |
73 |

Welcome!

74 |

For access to the Internet, please Enter Username and Password, then click Continue.

75 |

Please be mindful to use the internet with fair usage practices, as it is a shared resource.

76 |
77 | 78 | 79 |
80 | 81 | 82 |
83 |
84 | 85 | 86 |
87 | 88 |
89 |
90 |
91 |
92 |
93 | 94 | 109 | 110 | 111 | -------------------------------------------------------------------------------- /src/files/www/portal/status.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | $gatewayname Hotspot Gateway Status 15 | 16 | 24 | 25 | 26 | 27 | 28 |
29 | $gatewayname Hotspot Gateway. 30 |
31 |
32 | You are already logged in and have access to the Internet. 33 |
34 |

You are already logged in and have access to the Internet.

35 |
36 |

You can use your Browser, Email and other network Apps as you normally would.

37 | 38 |
39 | Copyright © The Nodogsplash Contributors 2004-2019.
This software is released under the GNU GPL license.
40 | 41 |
42 | 43 | 44 | -------------------------------------------------------------------------------- /src/files/www/portal/success.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Authentication Successful 7 | 60 | 61 | 62 | 63 |
64 |
🎉
65 |

Authentication Successful!

66 |

You're all set! Now close this page and enjoy free internet.

67 |
68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /utils/ip_script.bash: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Check if the input file is provided 4 | if [ -z "$1" ]; then 5 | echo "Usage: $0 " 6 | exit 1 7 | fi 8 | 9 | input_file="$1" 10 | output_file="output.csv" 11 | 12 | # Initialize variables 13 | counter=0 14 | line_buffer="" 15 | index=0 16 | 17 | # Process the input file 18 | while IFS= read -r line 19 | do 20 | # Add the line to the buffer 21 | if [ $counter -eq 0 ]; then 22 | line_buffer="$line" 23 | else 24 | line_buffer="$line_buffer,$line" 25 | fi 26 | 27 | # Increment the counter 28 | counter=$((counter + 1)) 29 | 30 | # If 100 lines are read, write to output file 31 | if [ $counter -eq 60 ]; then 32 | 33 | index=$((index + 1)) 34 | echo "config rule 'ir_ip$index'" >> "$output_file" 35 | echo " option dest_ip '$line_buffer'" >> "$output_file" 36 | echo " option use_policy 'wan_only'" >> "$output_file" 37 | echo " option family 'ipv4'" >> "$output_file" 38 | echo "" >> "$output_file" 39 | # Reset counter and buffer 40 | counter=0 41 | line_buffer="" 42 | 43 | fi 44 | done < "$input_file" 45 | 46 | # Write any remaining lines in the buffer to the output file 47 | if [ $counter -ne 0 ]; then 48 | index=$((index + 1)) 49 | echo "config rule 'ir_ip$index'" >> "$output_file" 50 | echo " option dest_ip '$line_buffer'" >> "$output_file" 51 | echo " option use_policy 'wan_only'" >> "$output_file" 52 | echo " option family 'ipv4'" >> "$output_file" 53 | echo "" >> "$output_file" 54 | fi 55 | 56 | echo "Processing complete. Output written to $output_file." 57 | --------------------------------------------------------------------------------