├── .gitignore ├── requirements.txt ├── docs ├── build-module-artifacts1.png ├── build-module-artifacts2.png ├── build-toolchain-cache1.png ├── build-toolchain-cache2.png ├── build-module-artifacts-snapshot.png └── build-toolchain-cache-snapshot.png ├── luci-proto-amneziawg ├── root │ └── usr │ │ └── share │ │ ├── luci │ │ └── menu.d │ │ │ └── luci-proto-amneziawg.json │ │ └── rpcd │ │ ├── acl.d │ │ └── luci-amneziawg.json │ │ └── ucode │ │ └── luci.amneziawg ├── Makefile └── htdocs │ └── luci-static │ └── resources │ ├── view │ └── amneziawg │ │ └── status.js │ ├── icons │ └── amneziawg.svg │ └── protocol │ └── amneziawg.js ├── scripts ├── apk-make-index.sh ├── ipkg-make-index.sh └── generate_target_matrix.py ├── amneziawg-tools ├── Makefile └── files │ ├── amneziawg_watchdog │ └── amneziawg.sh ├── kmod-amneziawg └── Makefile ├── .github └── workflows │ ├── build-toolchain-cache.yml │ ├── create-release.yml │ ├── multibuild-module-artifacts.yml │ ├── create-release-from-multibuild.yml │ └── build-module-artifacts.yml ├── README.md └── Makefile /.gitignore: -------------------------------------------------------------------------------- 1 | .feed/ 2 | .idea/ 3 | .vscode/ 4 | .envrc 5 | attic/ 6 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | beautifulsoup4>=4.12.3 2 | aiohttp>=3.11.11 3 | -------------------------------------------------------------------------------- /docs/build-module-artifacts1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/defanator/amneziawg-openwrt/HEAD/docs/build-module-artifacts1.png -------------------------------------------------------------------------------- /docs/build-module-artifacts2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/defanator/amneziawg-openwrt/HEAD/docs/build-module-artifacts2.png -------------------------------------------------------------------------------- /docs/build-toolchain-cache1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/defanator/amneziawg-openwrt/HEAD/docs/build-toolchain-cache1.png -------------------------------------------------------------------------------- /docs/build-toolchain-cache2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/defanator/amneziawg-openwrt/HEAD/docs/build-toolchain-cache2.png -------------------------------------------------------------------------------- /docs/build-module-artifacts-snapshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/defanator/amneziawg-openwrt/HEAD/docs/build-module-artifacts-snapshot.png -------------------------------------------------------------------------------- /docs/build-toolchain-cache-snapshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/defanator/amneziawg-openwrt/HEAD/docs/build-toolchain-cache-snapshot.png -------------------------------------------------------------------------------- /luci-proto-amneziawg/root/usr/share/luci/menu.d/luci-proto-amneziawg.json: -------------------------------------------------------------------------------- 1 | { 2 | "admin/status/amneziawg": { 3 | "title": "AmneziaWG", 4 | "order": 92, 5 | "action": { 6 | "type": "view", 7 | "path": "amneziawg/status" 8 | }, 9 | "depends": { 10 | "acl": [ "luci-proto-amneziawg" ], 11 | "uci": { "network": true } 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /luci-proto-amneziawg/root/usr/share/rpcd/acl.d/luci-amneziawg.json: -------------------------------------------------------------------------------- 1 | { 2 | "luci-proto-amneziawg": { 3 | "description": "Grant access to LuCI AmneziaWG procedures", 4 | "read": { 5 | "ubus": { 6 | "luci.amneziawg": [ 7 | "getAwgInstances" 8 | ] 9 | }, 10 | "uci": [ "ddns", "system", "network" ] 11 | }, 12 | "write": { 13 | "ubus": { 14 | "luci.amneziawg": [ 15 | "generateKeyPair", 16 | "getPublicAndPrivateKeyFromPrivate", 17 | "generatePsk" 18 | ] 19 | } 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /luci-proto-amneziawg/Makefile: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (C) 2016 Dan Luedtke 3 | # 4 | # This is free software, licensed under the Apache License, Version 2.0 . 5 | # 6 | 7 | include $(TOPDIR)/rules.mk 8 | 9 | LUCI_TITLE:=AmneziaWG Web UI 10 | LUCI_DESCRIPTION:=Provides Web UI for AmneziaWG 11 | LUCI_DEPENDS:=+luci-base +amneziawg-tools +ucode +luci-lib-uqr +resolveip 12 | LUCI_PKGARCH:=all 13 | PKGARCH:=all 14 | 15 | PKG_MAINTAINER:=Amnezia Admin 16 | PKG_PROVIDES:=luci-proto-amneziawg 17 | PKG_LICENSE:=Apache-2.0 18 | 19 | include $(TOPDIR)/feeds/luci/luci.mk 20 | 21 | # call BuildPackage - OpenWrt buildroot signature 22 | -------------------------------------------------------------------------------- /scripts/apk-make-index.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # apk v3 repository index control tool 3 | # TODO: add signing + verifying with signatures 4 | 5 | set -euxo pipefail 6 | 7 | usage() { 8 | echo "usage: $(basename "$0") {create,verify,dump} pkg_path" >&2 9 | exit 1 10 | } 11 | 12 | if [ $# -lt 2 ]; then 13 | usage 14 | fi 15 | 16 | ACTION="$1" 17 | PKG_PATH="$2" 18 | 19 | if [ ! -d "${PKG_PATH}" ]; then 20 | echo "${PKG_PATH}: directory not found" >&2 21 | exit 1 22 | fi 23 | 24 | case "${ACTION}" in 25 | create) 26 | echo "Creating packages.adb index in ${PKG_PATH}" >&2 27 | ( cd "${PKG_PATH}" && ${APK} mkndx --allow-untrusted --output packages.adb -- *.apk ) 28 | ;; 29 | verify) 30 | echo "Verifying packages.adb in ${PKG_PATH}" >&2 31 | ${APK} verify --allow-untrusted "${PKG_PATH}/packages.adb" 32 | ;; 33 | dump) 34 | echo "Dumping packages.adb in ${PKG_PATH}" >&2 35 | ${APK} adbdump "${PKG_PATH}/packages.adb" | tee "${PKG_PATH}/packages.adb.txt" 36 | ;; 37 | *) 38 | echo "unknown action: ${ACTION}" >&2 39 | usage 40 | esac 41 | -------------------------------------------------------------------------------- /scripts/ipkg-make-index.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # based on https://github.com/lede-project/source/blob/master/scripts/ipkg-make-index.sh 3 | set -e 4 | 5 | OS="$(uname -s | tr '[:upper:]' '[:lower:]')" 6 | 7 | case "${OS}" in 8 | linux) 9 | SIZE_CMD="stat -L -c%s" 10 | ;; 11 | darwin) 12 | SIZE_CMD="stat -L -f%z" 13 | ;; 14 | *) 15 | echo "unknown platform: ${OS}" >&2 16 | exit 1 17 | esac 18 | 19 | pkg_dir=$1 20 | 21 | if [ -z $pkg_dir ] || [ ! -d $pkg_dir ]; then 22 | echo "Usage: ipkg-make-index " >&2 23 | exit 1 24 | fi 25 | 26 | empty=1 27 | 28 | for pkg in `find $pkg_dir -name '*.ipk' | sort`; do 29 | empty= 30 | name="${pkg##*/}" 31 | name="${name%%_*}" 32 | [[ "$name" = "kernel" ]] && continue 33 | [[ "$name" = "libc" ]] && continue 34 | echo "Generating index for package $pkg" >&2 35 | file_size=$(${SIZE_CMD} $pkg) 36 | sha256sum=$(shasum -a 256 $pkg | awk '{print $1}') 37 | # Take pains to make variable value sed-safe 38 | sed_safe_pkg=`echo $pkg | sed -e 's/^\.\///g' -e 's/\\//\\\\\\//g'` 39 | tar -xzOf $pkg ./control.tar.gz | tar xzOf - ./control | sed -e "s/^Description:/Filename: $sed_safe_pkg\\ 40 | Size: $file_size\\ 41 | SHA256sum: $sha256sum\\ 42 | Description:/" 43 | echo "" 44 | done 45 | [ -n "$empty" ] && echo 46 | exit 0 47 | -------------------------------------------------------------------------------- /amneziawg-tools/Makefile: -------------------------------------------------------------------------------- 1 | include $(TOPDIR)/rules.mk 2 | 3 | PKG_NAME:=amneziawg-tools 4 | PKG_VERSION:=1.0.20250903 5 | PKG_RELEASE:=1 6 | 7 | PKG_SOURCE_PROTO:=git 8 | PKG_SOURCE_URL:=https://github.com/amnezia-vpn/amneziawg-tools.git 9 | PKG_SOURCE_VERSION:=v$(PKG_VERSION) 10 | 11 | PKG_MAINTAINER:=Amnezia Admin 12 | PKG_LICENSE:=GPL-2.0 13 | PKG_LICENSE_FILES:=COPYING 14 | 15 | PKG_BUILD_DIR=$(BUILD_DIR)/$(PKG_NAME)-$(PKG_VERSION) 16 | 17 | include $(INCLUDE_DIR)/package.mk 18 | 19 | MAKE_PATH:=src 20 | MAKE_VARS += PLATFORM=linux 21 | 22 | define Package/amneziawg-tools 23 | CATEGORY:=Network 24 | SUBMENU:=VPN 25 | URL:=https://amnezia.org/ 26 | MAINTAINER:=Amnezia Admin 27 | TITLE:=AmneziaWG userspace control program (awg) 28 | DEPENDS:= \ 29 | +@BUSYBOX_CONFIG_IP \ 30 | +@BUSYBOX_CONFIG_FEATURE_IP_LINK 31 | endef 32 | 33 | define Package/amneziawg-tools/description 34 | Amnezia VPN — simple and free app to run a self-hosted VPN with 35 | high privacy requirements. 36 | 37 | This package provides the userspace control program for AmneziaWG, 38 | `awg`, a netifd protocol helper, and a re-resolve watchdog script. 39 | endef 40 | 41 | define Package/amneziawg-tools/install 42 | $(INSTALL_DIR) $(1)/usr/bin/ 43 | $(INSTALL_BIN) $(PKG_BUILD_DIR)/src/wg $(1)/usr/bin/awg 44 | $(INSTALL_BIN) ./files/amneziawg_watchdog $(1)/usr/bin/ 45 | $(INSTALL_DIR) $(1)/lib/netifd/proto/ 46 | $(INSTALL_BIN) ./files/amneziawg.sh $(1)/lib/netifd/proto/ 47 | endef 48 | 49 | $(eval $(call BuildPackage,amneziawg-tools)) 50 | -------------------------------------------------------------------------------- /kmod-amneziawg/Makefile: -------------------------------------------------------------------------------- 1 | include $(TOPDIR)/rules.mk 2 | 3 | include $(INCLUDE_DIR)/kernel.mk 4 | 5 | PKG_NAME:=kmod-amneziawg 6 | PKG_VERSION:=1.0.20251104 7 | PKG_RELEASE:=1 8 | 9 | PKG_SOURCE_PROTO:=git 10 | PKG_SOURCE_URL:=https://github.com/amnezia-vpn/amneziawg-linux-kernel-module.git 11 | # commit 866b0abe820d5a9c115fe3e4221132ecf8e39b94 (HEAD -> master, tag: v1.0.20251104, upstream/master, upstream/HEAD, origin/master, origin/HEAD) 12 | # Author: Yaroslav Gurov 13 | # Date: Sat Oct 25 13:13:24 2025 +0000 14 | PKG_SOURCE_VERSION:=866b0abe820d5a9c115fe3e4221132ecf8e39b94 15 | PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)-$(PKG_VERSION) 16 | MAKE_PATH:=src 17 | 18 | include $(INCLUDE_DIR)/package.mk 19 | 20 | define KernelPackage/amneziawg 21 | SECTION:=kernel 22 | CATEGORY:=Kernel Modules 23 | SUBMENU:=Network Support 24 | URL:=https://amnezia.org/ 25 | MAINTAINER:=Amnezia Admin 26 | TITLE:=AmneziaWG Kernel Module 27 | FILES:=$(PKG_BUILD_DIR)/$(MAKE_PATH)/amneziawg.ko 28 | DEPENDS:= \ 29 | +kmod-udptunnel4 \ 30 | +kmod-udptunnel6 \ 31 | +kmod-crypto-lib-chacha20poly1305 \ 32 | +kmod-crypto-lib-curve25519 33 | endef 34 | 35 | define Build/Prepare 36 | $(call Build/Prepare/Default) 37 | mkdir -p $(PKG_BUILD_DIR)/$(MAKE_PATH)/kernel 38 | $(CP) $(LINUX_DIR)/* $(PKG_BUILD_DIR)/$(MAKE_PATH)/kernel/ 39 | endef 40 | 41 | define Build/Compile 42 | $(MAKE_VARS) $(MAKE) -C "$(LINUX_DIR)" \ 43 | $(KERNEL_MAKE_FLAGS) \ 44 | M="$(PKG_BUILD_DIR)/$(MAKE_PATH)" \ 45 | EXTRA_CFLAGS="$(BUILDFLAGS)" \ 46 | WIREGUARD_VERSION="$(WIREGUARD_VERSION)" \ 47 | modules 48 | endef 49 | 50 | $(eval $(call KernelPackage,amneziawg)) 51 | -------------------------------------------------------------------------------- /amneziawg-tools/files/amneziawg_watchdog: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # SPDX-License-Identifier: GPL-2.0 3 | # 4 | # Copyright (C) 2018 Aleksandr V. Piskunov . 5 | # Copyright (C) 2015-2018 Jason A. Donenfeld . All Rights Reserved. 6 | # 7 | # This watchdog script tries to re-resolve hostnames for inactive WireGuard peers. 8 | # Use it for peers with a frequently changing dynamic IP. 9 | # persistent_keepalive must be set, recommended value is 25 seconds. 10 | # 11 | # Run this script from cron every minute: 12 | # echo '* * * * * /usr/bin/wireguard_watchdog' >> /etc/crontabs/root 13 | 14 | # shellcheck disable=SC1091,SC3043 15 | 16 | . /lib/functions.sh 17 | 18 | check_peer_activity() { 19 | local cfg="$1" 20 | local iface="$2" 21 | local disabled 22 | local public_key 23 | local endpoint_host 24 | local endpoint_port 25 | local persistent_keepalive 26 | local last_handshake 27 | local idle_seconds 28 | 29 | config_get_bool disabled "${cfg}" "disabled" 0 30 | config_get public_key "${cfg}" "public_key" 31 | config_get endpoint_host "${cfg}" "endpoint_host" 32 | config_get endpoint_port "${cfg}" "endpoint_port" 33 | 34 | if [ "${disabled}" -eq 1 ]; then 35 | # skip disabled peers 36 | return 0 37 | fi 38 | 39 | persistent_keepalive=$(awg show "${iface}" persistent-keepalive | grep "${public_key}" | awk '{print $2}') 40 | 41 | # only process peers with endpoints and keepalive set 42 | [ -z "${endpoint_host}" ] && return 0; 43 | if [ -z "${persistent_keepalive}" ] || [ "${persistent_keepalive}" = "off" ]; then return 0; fi 44 | 45 | # skip IP addresses 46 | # check taken from packages/net/ddns-scripts/files/dynamic_dns_functions.sh 47 | local IPV4_REGEX="[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}" 48 | local IPV6_REGEX="\(\([0-9A-Fa-f]\{1,4\}:\)\{1,\}\)\(\([0-9A-Fa-f]\{1,4\}\)\{0,1\}\)\(\(:[0-9A-Fa-f]\{1,4\}\)\{1,\}\)" 49 | local IPV4 IPV6 50 | IPV4="$(echo "${endpoint_host}" | grep -m 1 -o "$IPV4_REGEX$")" # do not detect ip in 0.0.0.0.example.com 51 | IPV6="$(echo "${endpoint_host}" | grep -m 1 -o "$IPV6_REGEX")" 52 | [ -n "${IPV4}${IPV6}" ] && return 0; 53 | 54 | # re-resolve endpoint hostname if not responding for too long 55 | last_handshake=$(awg show "${iface}" latest-handshakes | grep "${public_key}" | awk '{print $2}') 56 | [ -z "${last_handshake}" ] && return 0; 57 | idle_seconds=$(($(date +%s)-last_handshake)) 58 | [ ${idle_seconds} -lt 150 ] && return 0; 59 | logger -t "amneziawg_monitor" "${iface} endpoint ${endpoint_host}:${endpoint_port} is not responding for ${idle_seconds} seconds, trying to re-resolve hostname" 60 | awg set "${iface}" peer "${public_key}" endpoint "${endpoint_host}:${endpoint_port}" 61 | } 62 | 63 | # query ubus for all active wireguard interfaces 64 | awg_ifaces=$(ubus -S call network.interface dump | jsonfilter -e '@.interface[@.up=true]' | jsonfilter -a -e '@[@.proto="amneziawg"].interface' | tr "\n" " ") 65 | 66 | # check every peer in every active wireguard interface 67 | config_load network 68 | for iface in $awg_ifaces; do 69 | config_foreach check_peer_activity "amneziawg_${iface}" "${iface}" 70 | done 71 | -------------------------------------------------------------------------------- /luci-proto-amneziawg/root/usr/share/rpcd/ucode/luci.amneziawg: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Jo-Philipp Wich 2 | // Licensed to the public under the Apache License 2.0. 3 | 4 | 'use strict'; 5 | 6 | import { cursor } from 'uci'; 7 | import { popen } from 'fs'; 8 | 9 | 10 | function shellquote(s) { 11 | return `'${replace(s ?? '', "'", "'\\''")}'`; 12 | } 13 | 14 | function command(cmd) { 15 | return trim(popen(cmd)?.read?.('all')); 16 | } 17 | 18 | function checkPeerHost(configHost, configPort, wgHost) { 19 | const ips = popen(`resolveip ${shellquote(configHost)} 2>/dev/null`); 20 | const hostIp = replace(wgHost, /\[|\]/g, ""); 21 | if (ips) { 22 | for (let line = ips.read('line'); length(line); line = ips.read('line')) { 23 | const ip = rtrim(line, '\n'); 24 | if (configPort && (ip + ":" + configPort == hostIp)) { 25 | return true; 26 | } else if (ip == substr(hostIp, 0, rindex(hostIp, ":"))) { 27 | return true; 28 | } 29 | } 30 | } 31 | return false; 32 | } 33 | 34 | const methods = { 35 | generatePsk: { 36 | call: function() { 37 | return { psk: command('awg genpsk 2>/dev/null') }; 38 | } 39 | }, 40 | 41 | generateKeyPair: { 42 | call: function() { 43 | const priv = command('awg genkey 2>/dev/null'); 44 | const pub = command(`echo ${shellquote(priv)} | awg pubkey 2>/dev/null`); 45 | 46 | return { keys: { priv, pub } }; 47 | } 48 | }, 49 | 50 | getPublicAndPrivateKeyFromPrivate: { 51 | args: { privkey: "privkey" }, 52 | call: function(req) { 53 | const priv = req.args?.privkey; 54 | const pub = command(`echo ${shellquote(priv)} | awg pubkey 2>/dev/null`); 55 | 56 | return { keys: { priv, pub } }; 57 | } 58 | }, 59 | 60 | getAwgInstances: { 61 | call: function() { 62 | const data = {}; 63 | let last_device; 64 | let qr_pubkey = {}; 65 | 66 | const uci = cursor(); 67 | const wg_dump = popen("awg show all dump 2>/dev/null"); 68 | 69 | if (wg_dump) { 70 | uci.load("network"); 71 | 72 | for (let line = wg_dump.read('line'); length(line); line = wg_dump.read('line')) { 73 | const record = split(rtrim(line, '\n'), '\t'); 74 | 75 | if (last_device != record[0]) { 76 | last_device = record[0]; 77 | data[last_device] = { 78 | name: last_device, 79 | public_key: record[2], 80 | listen_port: record[3], 81 | fwmark: record[4], 82 | peers: [] 83 | }; 84 | 85 | if (!length(record[2]) || record[2] == '(none)') 86 | qr_pubkey[last_device] = ''; 87 | else 88 | qr_pubkey[last_device] = `PublicKey = ${record[2]}`; 89 | } 90 | else { 91 | let peer_name; 92 | let peer_name_legacy; 93 | 94 | uci.foreach('network', `amneziawg_${last_device}`, (s) => { 95 | if (!s.disabled && s.public_key == record[1] && (!s.endpoint_host || checkPeerHost(s.endpoint_host, s.endpoint_port, record[3]))) 96 | peer_name = s.description; 97 | if (s.public_key == record[1]) 98 | peer_name_legacy = s.description; 99 | }); 100 | 101 | if (!peer_name) peer_name = peer_name_legacy; 102 | 103 | const peer = { 104 | name: peer_name, 105 | public_key: record[1], 106 | endpoint: record[3], 107 | allowed_ips: [], 108 | latest_handshake: record[5], 109 | transfer_rx: record[6], 110 | transfer_tx: record[7], 111 | persistent_keepalive: record[8] 112 | }; 113 | 114 | if (record[3] != '(none)' && length(record[4])) 115 | push(peer.allowed_ips, ...split(record[4], ',')); 116 | 117 | push(data[last_device].peers, peer); 118 | } 119 | } 120 | } 121 | 122 | return data; 123 | } 124 | } 125 | }; 126 | 127 | return { 'luci.amneziawg': methods }; 128 | -------------------------------------------------------------------------------- /.github/workflows/build-toolchain-cache.yml: -------------------------------------------------------------------------------- 1 | name: build toolchain cache 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | openwrt_version: 7 | description: "OpenWrt version" 8 | type: string 9 | required: true 10 | default: "23.05.3" 11 | openwrt_arch: 12 | description: "OpenWrt arch" 13 | type: string 14 | required: true 15 | default: "mips_24kc" 16 | openwrt_target: 17 | description: "OpenWrt target" 18 | type: string 19 | required: true 20 | default: "ath79" 21 | openwrt_subtarget: 22 | description: "OpenWrt subtarget" 23 | type: string 24 | required: true 25 | default: "generic" 26 | openwrt_vermagic: 27 | description: "OpenWrt vermagic" 28 | type: string 29 | required: true 30 | default: "auto" 31 | openwrt_snapshot_ref: 32 | description: "OpenWrt snapshot git ref" 33 | type: string 34 | required: false 35 | default: "main" 36 | 37 | jobs: 38 | build: 39 | name: "v${{ matrix.build_env.tag }} - ${{ matrix.build_env.pkgarch }} :: ${{ matrix.build_env.target }}/${{ matrix.build_env.subtarget }} openwrt build" 40 | runs-on: ubuntu-22.04 41 | strategy: 42 | matrix: 43 | build_env: 44 | - tag: ${{ inputs.openwrt_version || vars.DEFAULT_OPENWRT_VERSION }} 45 | pkgarch: ${{ inputs.openwrt_arch || vars.DEFAULT_OPENWRT_ARCH }} 46 | target: ${{ inputs.openwrt_target || vars.DEFAULT_OPENWRT_TARGET }} 47 | subtarget: ${{ inputs.openwrt_subtarget || vars.DEFAULT_OPENWRT_SUBTARGET }} 48 | vermagic: ${{ inputs.openwrt_vermagic || vars.DEFAULT_OPENWRT_VERMAGIC }} 49 | snapshot_ref: ${{ inputs.openwrt_snapshot_ref || 'main' }} 50 | 51 | env: 52 | OPENWRT_RELEASE: ${{ matrix.build_env.tag }} 53 | OPENWRT_ARCH: ${{ matrix.build_env.pkgarch }} 54 | OPENWRT_TARGET: ${{ matrix.build_env.target }} 55 | OPENWRT_SUBTARGET: ${{ matrix.build_env.subtarget }} 56 | OPENWRT_VERMAGIC: ${{ matrix.build_env.vermagic }} 57 | OPENWRT_SNAPSHOT_REF: ${{ matrix.build_env.snapshot_ref }} 58 | 59 | steps: 60 | - name: checkout amneziawg-openwrt 61 | uses: actions/checkout@v4 62 | with: 63 | path: amneziawg-openwrt 64 | fetch-depth: 0 65 | 66 | - name: checkout openwrt (release) 67 | uses: actions/checkout@v4 68 | if: ${{ matrix.build_env.tag != 'snapshot' }} 69 | with: 70 | path: openwrt 71 | repository: openwrt/openwrt 72 | ref: v${{ matrix.build_env.tag }} 73 | fetch-depth: 0 74 | 75 | - name: checkout openwrt (snapshot) 76 | uses: actions/checkout@v4 77 | if: ${{ matrix.build_env.tag == 'snapshot' }} 78 | with: 79 | path: openwrt 80 | repository: openwrt/openwrt 81 | ref: ${{ matrix.build_env.snapshot_ref }} 82 | fetch-depth: 0 83 | 84 | - name: building toolchain and kernel 85 | id: build-toolchain-kernel 86 | run: | 87 | set -x 88 | cd amneziawg-openwrt 89 | make show-env 90 | time -p make build-toolchain 91 | time -p make build-kernel 92 | make purge-circular-symlinks 93 | 94 | - name: save toolchain and kernel cache 95 | id: cache-tools-kernel-save 96 | uses: actions/cache/save@v4 97 | with: 98 | key: ${{ runner.os }}-openwrt-cache-toolchain-kernel-${{ matrix.build_env.tag }}-${{ matrix.build_env.pkgarch }}-${{ matrix.build_env.target }}-${{ matrix.build_env.subtarget }} 99 | path: | 100 | openwrt/.config 101 | openwrt/.config.old 102 | openwrt/feeds.conf 103 | openwrt/bin/** 104 | openwrt/build_dir/** 105 | openwrt/dl/** 106 | openwrt/feeds/** 107 | openwrt/package/** 108 | openwrt/staging_dir/** 109 | openwrt/tmp/** 110 | -------------------------------------------------------------------------------- /.github/workflows/create-release.yml: -------------------------------------------------------------------------------- 1 | # obsoleted workflow to create release from single target build; 2 | # can still be used to update existing releases with new targets 3 | 4 | name: create release 5 | 6 | #on: 7 | # push: 8 | # tags: 9 | # - "v*.*.*" 10 | 11 | on: 12 | workflow_dispatch: 13 | inputs: 14 | openwrt_version: 15 | description: "OpenWrt version" 16 | type: string 17 | required: true 18 | default: "23.05.3" 19 | openwrt_arch: 20 | description: "OpenWrt arch" 21 | type: string 22 | required: true 23 | default: "mips_24kc" 24 | openwrt_target: 25 | description: "OpenWrt target" 26 | type: string 27 | required: true 28 | default: "ath79" 29 | openwrt_subtarget: 30 | description: "OpenWrt subtarget" 31 | type: string 32 | required: true 33 | default: "generic" 34 | openwrt_vermagic: 35 | description: "OpenWrt vermagic" 36 | type: string 37 | required: true 38 | default: "auto" 39 | 40 | jobs: 41 | build: 42 | name: "v${{ matrix.build_env.tag }} - ${{ matrix.build_env.pkgarch }} :: ${{ matrix.build_env.target }}/${{ matrix.build_env.subtarget }} amneziawg release" 43 | runs-on: ubuntu-22.04 44 | strategy: 45 | matrix: 46 | build_env: 47 | - tag: ${{ inputs.openwrt_version || vars.DEFAULT_OPENWRT_VERSION }} 48 | pkgarch: ${{ inputs.openwrt_arch || vars.DEFAULT_OPENWRT_ARCH }} 49 | target: ${{ inputs.openwrt_target || vars.DEFAULT_OPENWRT_TARGET }} 50 | subtarget: ${{ inputs.openwrt_subtarget || vars.DEFAULT_OPENWRT_SUBTARGET }} 51 | vermagic: ${{ inputs.openwrt_vermagic || vars.DEFAULT_OPENWRT_VERMAGIC }} 52 | 53 | env: 54 | OPENWRT_RELEASE: ${{ matrix.build_env.tag }} 55 | OPENWRT_ARCH: ${{ matrix.build_env.pkgarch }} 56 | OPENWRT_TARGET: ${{ matrix.build_env.target }} 57 | OPENWRT_SUBTARGET: ${{ matrix.build_env.subtarget }} 58 | OPENWRT_VERMAGIC: ${{ matrix.build_env.vermagic }} 59 | 60 | steps: 61 | - name: checkout amneziawg-openwrt 62 | uses: actions/checkout@v4 63 | with: 64 | path: amneziawg-openwrt 65 | fetch-depth: 0 66 | 67 | - name: check amneziawg-openwrt release readiness 68 | id: check-amneziawg-release 69 | run: | 70 | set -x 71 | cd amneziawg-openwrt 72 | make show-env 73 | make check-release 74 | 75 | - name: checkout openwrt 76 | uses: actions/checkout@v4 77 | with: 78 | path: openwrt 79 | repository: openwrt/openwrt 80 | ref: v${{ matrix.build_env.tag }} 81 | fetch-depth: 0 82 | 83 | - name: checkout usign 84 | uses: actions/checkout@v4 85 | with: 86 | path: usign 87 | repository: openwrt/usign 88 | fetch-depth: 0 89 | 90 | - name: build usign 91 | id: build-usign 92 | run: | 93 | set -x 94 | cd usign 95 | mkdir build 96 | cd build 97 | cmake .. 98 | make -j 99 | echo "$(pwd)" >> $GITHUB_PATH 100 | 101 | - name: restore cached tools and kernel 102 | id: cache-tools-kernel-restore 103 | uses: actions/cache/restore@v4 104 | with: 105 | fail-on-cache-miss: true 106 | key: ${{ runner.os }}-openwrt-cache-toolchain-kernel-${{ matrix.build_env.tag }}-${{ matrix.build_env.pkgarch }}-${{ matrix.build_env.target }}-${{ matrix.build_env.subtarget }} 107 | restore-keys: | 108 | ${{ runner.os }}-openwrt-cache-tools-kernel-${{ matrix.build_env.pkgarch }}- 109 | path: | 110 | openwrt/.config 111 | openwrt/.config.old 112 | openwrt/feeds.conf 113 | openwrt/bin/** 114 | openwrt/build_dir/** 115 | openwrt/dl/** 116 | openwrt/feeds/** 117 | openwrt/package/** 118 | openwrt/staging_dir/** 119 | openwrt/tmp/** 120 | 121 | - name: build amneziawg 122 | id: build-amneziawg 123 | run: | 124 | set -x 125 | cd amneziawg-openwrt 126 | make show-env 127 | time -p make build-amneziawg 128 | cat << EOF >release.sec 129 | ${{ secrets.RELEASE_FEED_SEC_KEY }} 130 | EOF 131 | cat << EOF >release.pub 132 | ${{ secrets.RELEASE_FEED_PUB_KEY }} 133 | EOF 134 | make prepare-artifacts 135 | FEED_SEC_KEY=$(pwd)/release.sec FEED_PUB_KEY=$(pwd)/release.pub make prepare-release 136 | cp release.pub ../awgrelease/amneziawg-public.key 137 | 138 | - name: release 139 | uses: softprops/action-gh-release@v2 140 | with: 141 | files: | 142 | awgrelease/*.tar.gz 143 | awgrelease/amneziawg-public.key 144 | -------------------------------------------------------------------------------- /.github/workflows/multibuild-module-artifacts.yml: -------------------------------------------------------------------------------- 1 | name: build module artifacts for multiple targets 2 | 3 | on: 4 | schedule: 5 | - cron: '30 3 * * 2,6' 6 | pull_request: 7 | branches: 8 | - master 9 | workflow_dispatch: 10 | inputs: 11 | openwrt_versions: 12 | description: "OpenWrt version(s)" 13 | type: string 14 | required: true 15 | default: "24.10.2 23.05.5 23.05.3" 16 | 17 | jobs: 18 | generate-target-matrix: 19 | runs-on: ubuntu-22.04 20 | outputs: 21 | build-matrix: ${{ steps.generate-target-matrix.outputs.BUILD_MATRIX }} 22 | env: 23 | OPENWRT_RELEASES: ${{ inputs.openwrt_versions || vars.DEFAULT_OPENWRT_VERSIONS }} 24 | steps: 25 | - name: checkout amneziawg-openwrt 26 | uses: actions/checkout@v4 27 | with: 28 | fetch-depth: 0 29 | 30 | - name: prepare virtualenv 31 | id: prepare-virtualenv 32 | run: | 33 | make show-env 34 | make venv 35 | 36 | - name: generate target matrix 37 | id: generate-target-matrix 38 | run: | 39 | make generate-target-matrix >> $GITHUB_OUTPUT 40 | 41 | build: 42 | name: "v${{ matrix.build_env.tag }} - ${{ matrix.build_env.pkgarch }} :: ${{ matrix.build_env.target }}/${{ matrix.build_env.subtarget }} amneziawg build" 43 | runs-on: ubuntu-22.04 44 | needs: generate-target-matrix 45 | strategy: 46 | fail-fast: false 47 | matrix: 48 | build_env: ${{ fromJson(needs.generate-target-matrix.outputs.build-matrix) }} 49 | 50 | env: 51 | OPENWRT_RELEASE: ${{ matrix.build_env.tag }} 52 | OPENWRT_ARCH: ${{ matrix.build_env.pkgarch }} 53 | OPENWRT_TARGET: ${{ matrix.build_env.target }} 54 | OPENWRT_SUBTARGET: ${{ matrix.build_env.subtarget }} 55 | OPENWRT_VERMAGIC: ${{ matrix.build_env.vermagic }} 56 | 57 | steps: 58 | - name: checkout amneziawg-openwrt 59 | uses: actions/checkout@v4 60 | with: 61 | path: amneziawg-openwrt 62 | fetch-depth: 0 63 | 64 | - name: checkout openwrt 65 | uses: actions/checkout@v4 66 | with: 67 | path: openwrt 68 | repository: openwrt/openwrt 69 | ref: v${{ matrix.build_env.tag }} 70 | fetch-depth: 0 71 | 72 | - name: checkout usign 73 | uses: actions/checkout@v4 74 | with: 75 | path: usign 76 | repository: openwrt/usign 77 | fetch-depth: 0 78 | 79 | - name: build usign 80 | id: build-usign 81 | run: | 82 | set -x 83 | cd usign 84 | mkdir build 85 | cd build 86 | cmake .. 87 | make -j 88 | echo "$(pwd)" >> $GITHUB_PATH 89 | 90 | - name: restore cached tools and kernel 91 | id: cache-tools-kernel-restore 92 | uses: actions/cache/restore@v4 93 | with: 94 | fail-on-cache-miss: true 95 | key: ${{ runner.os }}-openwrt-cache-toolchain-kernel-${{ matrix.build_env.tag }}-${{ matrix.build_env.pkgarch }}-${{ matrix.build_env.target }}-${{ matrix.build_env.subtarget }} 96 | restore-keys: | 97 | ${{ runner.os }}-openwrt-cache-tools-kernel-${{ matrix.build_env.pkgarch }}- 98 | path: | 99 | openwrt/.config 100 | openwrt/.config.old 101 | openwrt/feeds.conf 102 | openwrt/bin/** 103 | openwrt/build_dir/** 104 | openwrt/dl/** 105 | openwrt/feeds/** 106 | openwrt/package/** 107 | openwrt/staging_dir/** 108 | openwrt/tmp/** 109 | 110 | - name: build amneziawg 111 | id: build-amneziawg 112 | run: | 113 | set -x 114 | cd amneziawg-openwrt 115 | make show-env 116 | make export-env >> $GITHUB_OUTPUT 117 | time -p make build-amneziawg 118 | make prepare-artifacts 119 | 120 | - name: create feed archive 121 | id: create-feed-archive 122 | run: | 123 | set -x 124 | cd amneziawg-openwrt 125 | usign -G -s test.sec -p test.pub 126 | FEED_SEC_KEY=$(pwd)/test.sec FEED_PUB_KEY=$(pwd)/test.pub make create-feed-archive 127 | cp test.sec ../awgrelease/test.sec-${{ steps.build-amneziawg.outputs.version_str }}-openwrt-${{ matrix.build_env.tag }}-${{ matrix.build_env.pkgarch}}-${{ matrix.build_env.target}}-${{ matrix.build_env.subtarget}} 128 | cp test.pub ../awgrelease/test.pub-${{ steps.build-amneziawg.outputs.version_str }}-openwrt-${{ matrix.build_env.tag }}-${{ matrix.build_env.pkgarch}}-${{ matrix.build_env.target}}-${{ matrix.build_env.subtarget}} 129 | 130 | - name: upload artifacts 131 | if: ${{ always() }} 132 | uses: actions/upload-artifact@v4 133 | with: 134 | name: amneziawg-${{ steps.build-amneziawg.outputs.version_str }}-openwrt-${{ matrix.build_env.tag }}-${{ matrix.build_env.pkgarch}}-${{ matrix.build_env.target}}-${{ matrix.build_env.subtarget}} 135 | path: | 136 | awgrelease/*.gz 137 | awgrelease/test.pub-* 138 | awgrelease/test.sec-* 139 | -------------------------------------------------------------------------------- /luci-proto-amneziawg/htdocs/luci-static/resources/view/amneziawg/status.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 'require view'; 3 | 'require rpc'; 4 | 'require poll'; 5 | 'require dom'; 6 | 'require ui'; 7 | 8 | 9 | var callgetAwgInstances = rpc.declare({ 10 | object: 'luci.amneziawg', 11 | method: 'getAwgInstances' 12 | }); 13 | 14 | function timestampToStr(timestamp) { 15 | if (timestamp < 1) 16 | return _('Never', 'No AmneziaWG peer handshake yet'); 17 | 18 | var seconds = (Date.now() / 1000) - timestamp; 19 | var ago; 20 | 21 | if (seconds < 60) 22 | ago = _('%ds ago').format(seconds); 23 | else if (seconds < 3600) 24 | ago = _('%dm ago').format(seconds / 60); 25 | else if (seconds < 86401) 26 | ago = _('%dh ago').format(seconds / 3600); 27 | else 28 | ago = _('over a day ago'); 29 | 30 | return (new Date(timestamp * 1000)).toUTCString() + ' (' + ago + ')'; 31 | } 32 | 33 | function handleInterfaceDetails(iface) { 34 | ui.showModal(_('Instance Details'), [ 35 | ui.itemlist(E([]), [ 36 | _('Name'), iface.name, 37 | _('Public Key'), E('code', [ iface.public_key ]), 38 | _('Listen Port'), iface.listen_port, 39 | _('Firewall Mark'), iface.fwmark != 'off' ? iface.fwmark : E('em', _('none')) 40 | ]), 41 | E('div', { 'class': 'right' }, [ 42 | E('button', { 43 | 'class': 'btn cbi-button', 44 | 'click': ui.hideModal 45 | }, [ _('Dismiss') ]) 46 | ]) 47 | ]); 48 | } 49 | 50 | function handlePeerDetails(peer) { 51 | ui.showModal(_('Peer Details'), [ 52 | ui.itemlist(E([]), [ 53 | _('Description'), peer.name, 54 | _('Public Key'), E('code', [ peer.public_key ]), 55 | _('Endpoint'), peer.endpoint, 56 | _('Allowed IPs'), (Array.isArray(peer.allowed_ips) && peer.allowed_ips.length) ? peer.allowed_ips.join(', ') : E('em', _('none')), 57 | _('Received Data'), '%1024mB'.format(peer.transfer_rx), 58 | _('Transmitted Data'), '%1024mB'.format(peer.transfer_tx), 59 | _('Latest Handshake'), timestampToStr(+peer.latest_handshake), 60 | _('Keep-Alive'), (peer.persistent_keepalive != 'off') ? _('every %ds', 'AmneziaWG keep alive interval').format(+peer.persistent_keepalive) : E('em', _('none')), 61 | ]), 62 | E('div', { 'class': 'right' }, [ 63 | E('button', { 64 | 'class': 'btn cbi-button', 65 | 'click': ui.hideModal 66 | }, [ _('Dismiss') ]) 67 | ]) 68 | ]); 69 | } 70 | 71 | function renderPeerTable(instanceName, peers) { 72 | var t = new L.ui.Table( 73 | [ 74 | _('Peer'), 75 | _('Endpoint'), 76 | _('Data Received'), 77 | _('Data Transmitted'), 78 | _('Latest Handshake') 79 | ], 80 | { 81 | id: 'peers-' + instanceName 82 | }, 83 | E('em', [ 84 | _('No peers connected') 85 | ]) 86 | ); 87 | 88 | t.update(peers.map(function(peer) { 89 | return [ 90 | [ 91 | peer.name || '', 92 | E('div', { 93 | 'style': 'cursor:pointer', 94 | 'click': ui.createHandlerFn(this, handlePeerDetails, peer) 95 | }, [ 96 | E('p', [ 97 | peer.name ? E('span', [ peer.name ]) : E('em', [ _('Untitled peer') ]) 98 | ]), 99 | E('span', { 100 | 'class': 'ifacebadge hide-sm', 101 | 'data-tooltip': _('Public key: %h', 'Tooltip displaying full AmneziaWG peer public key').format(peer.public_key) 102 | }, [ 103 | E('code', [ peer.public_key.replace(/^(.{5}).+(.{6})$/, '$1…$2') ]) 104 | ]) 105 | ]) 106 | ], 107 | peer.endpoint, 108 | [ +peer.transfer_rx, '%1024mB'.format(+peer.transfer_rx) ], 109 | [ +peer.transfer_tx, '%1024mB'.format(+peer.transfer_tx) ], 110 | [ +peer.latest_handshake, timestampToStr(+peer.latest_handshake) ] 111 | ]; 112 | })); 113 | 114 | return t.render(); 115 | } 116 | 117 | return view.extend({ 118 | renderIfaces: function(ifaces) { 119 | var res = [ 120 | E('h2', [ _('AmneziaWG Status') ]) 121 | ]; 122 | 123 | for (var instanceName in ifaces) { 124 | res.push( 125 | E('h3', [ _('Instance "%h"', 'AmneziaWG instance heading').format(instanceName) ]), 126 | E('p', { 127 | 'style': 'cursor:pointer', 128 | 'click': ui.createHandlerFn(this, handleInterfaceDetails, ifaces[instanceName]) 129 | }, [ 130 | E('span', { 'class': 'ifacebadge' }, [ 131 | E('img', { 'src': L.resource('icons', 'amneziawg.svg') }), 132 | '\xa0', 133 | instanceName 134 | ]), 135 | E('span', { 'style': 'opacity:.8' }, [ 136 | ' · ', 137 | _('Port %d', 'AmneziaWG listen port').format(ifaces[instanceName].listen_port), 138 | ' · ', 139 | E('code', { 'click': '' }, [ ifaces[instanceName].public_key ]) 140 | ]) 141 | ]), 142 | renderPeerTable(instanceName, ifaces[instanceName].peers) 143 | ); 144 | } 145 | 146 | if (res.length == 1) 147 | res.push(E('p', { 'class': 'center', 'style': 'margin-top:5em' }, [ 148 | E('em', [ _('No AmneziaWG interfaces configured.') ]) 149 | ])); 150 | 151 | return E([], res); 152 | }, 153 | 154 | render: function() { 155 | poll.add(L.bind(function () { 156 | return callgetAwgInstances().then(L.bind(function(ifaces) { 157 | dom.content( 158 | document.querySelector('#view'), 159 | this.renderIfaces(ifaces) 160 | ); 161 | }, this)); 162 | }, this), 5); 163 | 164 | return E([], [ 165 | E('h2', [ _('AmneziaWG Status') ]), 166 | E('p', { 'class': 'center', 'style': 'margin-top:5em' }, [ 167 | E('em', [ _('Loading data…') ]) 168 | ]) 169 | ]); 170 | }, 171 | 172 | handleReset: null, 173 | handleSaveApply: null, 174 | handleSave: null 175 | }); 176 | -------------------------------------------------------------------------------- /.github/workflows/create-release-from-multibuild.yml: -------------------------------------------------------------------------------- 1 | # default workflow for creating release from multi-target builds 2 | 3 | name: create release from multibuild 4 | 5 | on: 6 | push: 7 | tags: 8 | - "v*.*.*" 9 | workflow_dispatch: 10 | inputs: 11 | openwrt_versions: 12 | description: "OpenWrt version(s)" 13 | type: string 14 | required: true 15 | default: "24.10.2 23.05.5 23.05.3" 16 | 17 | jobs: 18 | generate-target-matrix: 19 | runs-on: ubuntu-22.04 20 | outputs: 21 | build-matrix: ${{ steps.generate-target-matrix.outputs.BUILD_MATRIX }} 22 | env: 23 | OPENWRT_RELEASES: ${{ inputs.openwrt_versions || vars.DEFAULT_OPENWRT_VERSIONS }} 24 | steps: 25 | - name: checkout amneziawg-openwrt 26 | uses: actions/checkout@v4 27 | with: 28 | fetch-depth: 0 29 | 30 | - name: prepare virtualenv 31 | id: prepare-virtualenv 32 | run: | 33 | make show-env 34 | make venv 35 | 36 | - name: generate target matrix 37 | id: generate-target-matrix 38 | run: | 39 | make generate-target-matrix >> $GITHUB_OUTPUT 40 | 41 | build: 42 | name: "v${{ matrix.build_env.tag }} - ${{ matrix.build_env.pkgarch }} :: ${{ matrix.build_env.target }}/${{ matrix.build_env.subtarget }} amneziawg release" 43 | runs-on: ubuntu-22.04 44 | needs: generate-target-matrix 45 | strategy: 46 | matrix: 47 | build_env: ${{ fromJson(needs.generate-target-matrix.outputs.build-matrix) }} 48 | 49 | env: 50 | OPENWRT_RELEASE: ${{ matrix.build_env.tag }} 51 | OPENWRT_ARCH: ${{ matrix.build_env.pkgarch }} 52 | OPENWRT_TARGET: ${{ matrix.build_env.target }} 53 | OPENWRT_SUBTARGET: ${{ matrix.build_env.subtarget }} 54 | OPENWRT_VERMAGIC: ${{ matrix.build_env.vermagic }} 55 | 56 | steps: 57 | - name: checkout amneziawg-openwrt 58 | uses: actions/checkout@v4 59 | with: 60 | path: amneziawg-openwrt 61 | fetch-depth: 0 62 | 63 | - name: check amneziawg-openwrt release readiness 64 | id: check-amneziawg-release 65 | run: | 66 | set -x 67 | cd amneziawg-openwrt 68 | make show-env 69 | make check-release 70 | 71 | - name: checkout openwrt 72 | uses: actions/checkout@v4 73 | with: 74 | path: openwrt 75 | repository: openwrt/openwrt 76 | ref: v${{ matrix.build_env.tag }} 77 | fetch-depth: 0 78 | 79 | - name: checkout usign 80 | uses: actions/checkout@v4 81 | with: 82 | path: usign 83 | repository: openwrt/usign 84 | fetch-depth: 0 85 | 86 | - name: build usign 87 | id: build-usign 88 | run: | 89 | set -x 90 | cd usign 91 | mkdir build 92 | cd build 93 | cmake .. 94 | make -j 95 | echo "$(pwd)" >> $GITHUB_PATH 96 | 97 | - name: restore cached tools and kernel 98 | id: cache-tools-kernel-restore 99 | uses: actions/cache/restore@v4 100 | with: 101 | fail-on-cache-miss: true 102 | key: ${{ runner.os }}-openwrt-cache-toolchain-kernel-${{ matrix.build_env.tag }}-${{ matrix.build_env.pkgarch }}-${{ matrix.build_env.target }}-${{ matrix.build_env.subtarget }} 103 | restore-keys: | 104 | ${{ runner.os }}-openwrt-cache-tools-kernel-${{ matrix.build_env.pkgarch }}- 105 | path: | 106 | openwrt/.config 107 | openwrt/.config.old 108 | openwrt/feeds.conf 109 | openwrt/bin/** 110 | openwrt/build_dir/** 111 | openwrt/dl/** 112 | openwrt/feeds/** 113 | openwrt/package/** 114 | openwrt/staging_dir/** 115 | openwrt/tmp/** 116 | 117 | - name: build amneziawg 118 | id: build-amneziawg 119 | run: | 120 | set -x 121 | cd amneziawg-openwrt 122 | make show-env 123 | time -p make build-amneziawg 124 | cat << EOF >release.sec 125 | ${{ secrets.RELEASE_FEED_SEC_KEY }} 126 | EOF 127 | cat << EOF >release.pub 128 | ${{ secrets.RELEASE_FEED_PUB_KEY }} 129 | EOF 130 | make prepare-artifacts 131 | FEED_SEC_KEY=$(pwd)/release.sec FEED_PUB_KEY=$(pwd)/release.pub make prepare-release 132 | cp release.pub ../awgrelease/amneziawg-public.key 133 | 134 | - name: upload artifacts 135 | if: ${{ always() }} 136 | uses: actions/upload-artifact@v4 137 | with: 138 | name: amneziawg-${{ steps.build-amneziawg.outputs.version_str }}-openwrt-${{ matrix.build_env.tag }}-${{ matrix.build_env.pkgarch}}-${{ matrix.build_env.target}}-${{ matrix.build_env.subtarget}} 139 | path: awgrelease/* 140 | 141 | create-release: 142 | name: create-release 143 | runs-on: ubuntu-22.04 144 | needs: build 145 | steps: 146 | - name: download artifacts 147 | uses: actions/download-artifact@v4 148 | with: 149 | path: awgrelease/ 150 | merge-multiple: true 151 | 152 | - name: show artifacts 153 | run: ls -Rl awgrelease/ 154 | 155 | - name: create release 156 | uses: softprops/action-gh-release@v2 157 | with: 158 | files: | 159 | awgrelease/*.tar.gz 160 | awgrelease/amneziawg-public.key 161 | -------------------------------------------------------------------------------- /.github/workflows/build-module-artifacts.yml: -------------------------------------------------------------------------------- 1 | name: build module artifacts 2 | 3 | #on: 4 | # schedule: 5 | # - cron: '30 3 * * 2,6' 6 | # pull_request: 7 | # branches: 8 | # - master 9 | 10 | on: 11 | workflow_dispatch: 12 | inputs: 13 | openwrt_version: 14 | description: "OpenWrt version" 15 | type: string 16 | required: true 17 | default: "23.05.3" 18 | openwrt_arch: 19 | description: "OpenWrt arch" 20 | type: string 21 | required: true 22 | default: "mips_24kc" 23 | openwrt_target: 24 | description: "OpenWrt target" 25 | type: string 26 | required: true 27 | default: "ath79" 28 | openwrt_subtarget: 29 | description: "OpenWrt subtarget" 30 | type: string 31 | required: true 32 | default: "generic" 33 | openwrt_vermagic: 34 | description: "OpenWrt vermagic" 35 | type: string 36 | required: true 37 | default: "auto" 38 | openwrt_snapshot_ref: 39 | description: "OpenWrt snapshot git ref" 40 | type: string 41 | required: false 42 | default: "main" 43 | 44 | jobs: 45 | build: 46 | name: "v${{ matrix.build_env.tag }} - ${{ matrix.build_env.pkgarch }} :: ${{ matrix.build_env.target }}/${{ matrix.build_env.subtarget }} amneziawg build" 47 | runs-on: ubuntu-22.04 48 | strategy: 49 | matrix: 50 | build_env: 51 | - tag: ${{ inputs.openwrt_version || vars.DEFAULT_OPENWRT_VERSION }} 52 | pkgarch: ${{ inputs.openwrt_arch || vars.DEFAULT_OPENWRT_ARCH }} 53 | target: ${{ inputs.openwrt_target || vars.DEFAULT_OPENWRT_TARGET }} 54 | subtarget: ${{ inputs.openwrt_subtarget || vars.DEFAULT_OPENWRT_SUBTARGET }} 55 | vermagic: ${{ inputs.openwrt_vermagic || vars.DEFAULT_OPENWRT_VERMAGIC }} 56 | snapshot_ref: ${{ inputs.openwrt_snapshot_ref || 'main' }} 57 | 58 | env: 59 | OPENWRT_RELEASE: ${{ matrix.build_env.tag }} 60 | OPENWRT_ARCH: ${{ matrix.build_env.pkgarch }} 61 | OPENWRT_TARGET: ${{ matrix.build_env.target }} 62 | OPENWRT_SUBTARGET: ${{ matrix.build_env.subtarget }} 63 | OPENWRT_VERMAGIC: ${{ matrix.build_env.vermagic }} 64 | OPENWRT_SNAPSHOT_REF: ${{ matrix.build_env.snapshot_ref }} 65 | 66 | steps: 67 | - name: checkout amneziawg-openwrt 68 | uses: actions/checkout@v4 69 | with: 70 | path: amneziawg-openwrt 71 | fetch-depth: 0 72 | 73 | - name: checkout openwrt (release) 74 | uses: actions/checkout@v4 75 | if: ${{ matrix.build_env.tag != 'snapshot' }} 76 | with: 77 | path: openwrt 78 | repository: openwrt/openwrt 79 | ref: v${{ matrix.build_env.tag }} 80 | fetch-depth: 0 81 | 82 | - name: checkout openwrt (snapshot) 83 | uses: actions/checkout@v4 84 | if: ${{ matrix.build_env.tag == 'snapshot' }} 85 | with: 86 | path: openwrt 87 | repository: openwrt/openwrt 88 | ref: ${{ matrix.build_env.snapshot_ref }} 89 | fetch-depth: 0 90 | 91 | - name: checkout usign 92 | uses: actions/checkout@v4 93 | with: 94 | path: usign 95 | repository: openwrt/usign 96 | fetch-depth: 0 97 | 98 | - name: build usign 99 | id: build-usign 100 | run: | 101 | set -x 102 | cd usign 103 | mkdir build 104 | cd build 105 | cmake .. 106 | make -j 107 | echo "$(pwd)" >> $GITHUB_PATH 108 | 109 | - name: restore cached tools and kernel 110 | id: cache-tools-kernel-restore 111 | uses: actions/cache/restore@v4 112 | with: 113 | fail-on-cache-miss: true 114 | key: ${{ runner.os }}-openwrt-cache-toolchain-kernel-${{ matrix.build_env.tag }}-${{ matrix.build_env.pkgarch }}-${{ matrix.build_env.target }}-${{ matrix.build_env.subtarget }} 115 | restore-keys: | 116 | ${{ runner.os }}-openwrt-cache-tools-kernel-${{ matrix.build_env.pkgarch }}- 117 | path: | 118 | openwrt/.config 119 | openwrt/.config.old 120 | openwrt/feeds.conf 121 | openwrt/bin/** 122 | openwrt/build_dir/** 123 | openwrt/dl/** 124 | openwrt/feeds/** 125 | openwrt/package/** 126 | openwrt/staging_dir/** 127 | openwrt/tmp/** 128 | 129 | - name: build amneziawg 130 | id: build-amneziawg 131 | run: | 132 | set -x 133 | cd amneziawg-openwrt 134 | make show-env 135 | make export-env >> $GITHUB_OUTPUT 136 | time -p make build-amneziawg 137 | make prepare-artifacts 138 | 139 | - name: create feed archive 140 | id: create-feed-archive 141 | run: | 142 | set -x 143 | cd amneziawg-openwrt 144 | usign -G -s test.sec -p test.pub 145 | FEED_SEC_KEY=$(pwd)/test.sec FEED_PUB_KEY=$(pwd)/test.pub make create-feed-archive 146 | cp test.sec ../awgrelease/test.sec-${{ steps.build-amneziawg.outputs.version_str }}-openwrt-${{ matrix.build_env.tag }}-${{ matrix.build_env.pkgarch}}-${{ matrix.build_env.target}}-${{ matrix.build_env.subtarget}} 147 | cp test.pub ../awgrelease/test.pub-${{ steps.build-amneziawg.outputs.version_str }}-openwrt-${{ matrix.build_env.tag }}-${{ matrix.build_env.pkgarch}}-${{ matrix.build_env.target}}-${{ matrix.build_env.subtarget}} 148 | 149 | - name: upload artifacts 150 | if: ${{ always() }} 151 | uses: actions/upload-artifact@v4 152 | with: 153 | name: amneziawg-${{ steps.build-amneziawg.outputs.version_str }}-openwrt-${{ matrix.build_env.tag }}-${{ matrix.build_env.pkgarch}}-${{ matrix.build_env.target}}-${{ matrix.build_env.subtarget}} 154 | path: | 155 | awgrelease/*.gz 156 | awgrelease/test.pub-* 157 | awgrelease/test.sec-* 158 | -------------------------------------------------------------------------------- /scripts/generate_target_matrix.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # python/async refactored version of https://github.com/Slava-Shchipunov/awg-openwrt/blob/master/index.js 4 | 5 | import sys 6 | import os 7 | import re 8 | import argparse 9 | import logging 10 | import json 11 | import asyncio 12 | import aiohttp 13 | from bs4 import BeautifulSoup 14 | 15 | logger = logging.getLogger(os.path.basename(__file__)) 16 | 17 | # filtered targets for release builds 18 | TARGETS_TO_BUILD = ["ath79"] 19 | #SUBTARGETS_TO_BUILD = ["generic", "nand"] 20 | SUBTARGETS_TO_BUILD = ["generic"] 21 | 22 | # filtered targets for snapshot builds 23 | SNAPSHOT_TARGETS_TO_BUILD = ["ath79"] 24 | SNAPSHOT_SUBTARGETS_TO_BUILD = ["generic", "nand"] 25 | 26 | 27 | class OpenWrtBuildInfoFetcher: 28 | def __init__(self, version): 29 | self._session = None 30 | self.url = "https://downloads.openwrt.org/" 31 | self.version = version.lower() 32 | 33 | if self.version == "snapshot": 34 | self.base_uri = "/snapshots/targets/" 35 | else: 36 | self.base_uri = f"/releases/{version}/targets/" 37 | 38 | self.targets = {} 39 | 40 | def __str__(self): 41 | return f"{self.__class__.__name__} ({self.url})" 42 | 43 | async def __aenter__(self): 44 | self._session = aiohttp.ClientSession(base_url=self.url) 45 | return self 46 | 47 | async def __aexit__(self, *err): 48 | await self._session.close() 49 | self._session = None 50 | 51 | async def get(self, url): 52 | async with self._session.get( 53 | os.path.join(self.base_uri, url.lstrip("/")) 54 | ) as response: 55 | response.raise_for_status() 56 | if response.status != 200: 57 | logger.error("error fetching %s: %d", url, response.status) 58 | raise Exception(f"Error fetching {url}") 59 | return await response.text() 60 | 61 | async def get_targets(self): 62 | logger.info("fetching targets") 63 | 64 | r = await self.get("/") 65 | s = BeautifulSoup(r, "html.parser") 66 | 67 | for element in s.select("table tr td.n a"): 68 | name = element.get("href") 69 | if name and name.endswith("/"): 70 | if len(TARGETS_TO_BUILD) > 0 and name[:-1] not in TARGETS_TO_BUILD: 71 | continue 72 | self.targets[name[:-1]] = {} 73 | 74 | async def get_subtargets(self): 75 | logger.info("fetching subtargets") 76 | 77 | _jobs = [] 78 | for target in self.targets: 79 | _jobs.append({"target": target, "url": f"{target}/"}) 80 | 81 | res = await asyncio.gather(*(self.get(_job["url"]) for _job in _jobs)) 82 | 83 | for i, _ in enumerate(_jobs): 84 | target = _jobs[i]["target"] 85 | s = BeautifulSoup(res[i], "html.parser") 86 | 87 | for element in s.select("table tr td.n a"): 88 | name = element.get("href") 89 | if name and name.endswith("/"): 90 | if ( 91 | len(SUBTARGETS_TO_BUILD) > 0 92 | and name[:-1] not in SUBTARGETS_TO_BUILD 93 | ): 94 | continue 95 | self.targets[target][name[:-1]] = { 96 | "vermagic": None, 97 | "pkgarch": None, 98 | } 99 | 100 | async def get_details(self): 101 | logger.info("fetching details") 102 | 103 | _jobs = [] 104 | for target, subtargets in self.targets.items(): 105 | for subtarget in subtargets: 106 | _jobs.append( 107 | { 108 | "target": target, 109 | "subtarget": subtarget, 110 | "url": f"{target}/{subtarget}/packages/", 111 | } 112 | ) 113 | 114 | res = await asyncio.gather(*(self.get(_job["url"]) for _job in _jobs)) 115 | 116 | logger.info("parsing details") 117 | 118 | for i, _ in enumerate(_jobs): 119 | target = _jobs[i]["target"] 120 | subtarget = _jobs[i]["subtarget"] 121 | 122 | # BeautifulSoup solution (commented below) takes a while, so use plain regex here 123 | packages = re.findall(r'href="(kernel[_-].*[ia]pk)"', res[i]) 124 | logger.debug("kernel packages found: %s", packages) 125 | for package in packages: 126 | logger.debug("%s/%s: found kernel: %s", target, subtarget, package) 127 | 128 | # regular (release) builds 129 | m = re.match( 130 | r"kernel_\d+\.\d+\.\d+(?:-\d+)?[-~]([a-f0-9]+)(?:-r\d+)?_([a-zA-Z0-9_-]+)\.ipk$", 131 | package, 132 | ) 133 | if m: 134 | self.targets[target][subtarget]["vermagic"] = m.group(1) 135 | self.targets[target][subtarget]["pkgarch"] = m.group(2) 136 | break 137 | 138 | # snapshot builds 139 | m = re.match( 140 | r"kernel-\d+\.\d+\.\d+(?:-\d+)?[-~]([a-f0-9]+)(?:-r\d+)\.apk$", 141 | package, 142 | ) 143 | if m: 144 | self.targets[target][subtarget]["vermagic"] = m.group(1) 145 | # TODO: figure out how to populate this correctly 146 | self.targets[target][subtarget]["pkgarch"] = "none" 147 | break 148 | 149 | # s = BeautifulSoup(res[i], 'html.parser') 150 | # for element in s.select('a'): 151 | # name = element.get('href') 152 | # if name and name.startswith('kernel_'): 153 | # logger.info("%s/%s: parsing %s", target, subtarget, element) 154 | # m = re.match(r'kernel_\d+\.\d+\.\d+(?:-\d+)?[-~]([a-f0-9]+)(?:-r\d+)?_([a-zA-Z0-9_-]+)\.ipk$', name) 155 | # if m: 156 | # self.targets[target][subtarget]["vermagic"] = m.group(1) 157 | # self.targets[target][subtarget]["pkgarch"] = m.group(2) 158 | # break 159 | 160 | 161 | async def main(): 162 | parser = argparse.ArgumentParser( 163 | description="Generate build matrix for amneziawg-openwrt GitHub CI" 164 | ) 165 | parser.add_argument( 166 | "version", 167 | help="OpenWrt version (use SNAPSHOT for building against snapshots)", 168 | nargs="+", 169 | ) 170 | parser.add_argument( 171 | "--verbose", action="store_true", default=False, help="enable logging" 172 | ) 173 | args = parser.parse_args() 174 | 175 | if args.verbose: 176 | logging.basicConfig( 177 | level=logging.DEBUG, 178 | format="%(asctime)s [%(levelname)s] %(name)s: %(message)s", 179 | ) 180 | 181 | logger.info("started") 182 | job_config = [] 183 | 184 | versions = set() 185 | for version in args.version: 186 | if version.lower() in versions: 187 | logger.warning("duplicate version ignored: %s", version) 188 | continue 189 | versions.add(version.lower()) 190 | 191 | try: 192 | for version in versions: 193 | async with OpenWrtBuildInfoFetcher(version=version) as of: 194 | await of.get_targets() 195 | await of.get_subtargets() 196 | await of.get_details() 197 | 198 | for target, subtargets in of.targets.items(): 199 | for subtarget in subtargets: 200 | job_config.append( 201 | { 202 | "tag": version, 203 | "target": target, 204 | "subtarget": subtarget, 205 | "vermagic": of.targets[target][subtarget]["vermagic"], 206 | "pkgarch": of.targets[target][subtarget]["pkgarch"], 207 | } 208 | ) 209 | 210 | print(json.dumps(job_config, separators=(",", ":"))) 211 | except Exception as exc: 212 | logger.error("%s", str(exc)) 213 | return 1 214 | 215 | logger.info("stopped") 216 | 217 | return 0 218 | 219 | 220 | if __name__ == "__main__": 221 | sys.exit(asyncio.run(main())) 222 | -------------------------------------------------------------------------------- /amneziawg-tools/files/amneziawg.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Copyright 2016-2017 Dan Luedtke 3 | # Licensed to the public under the Apache License 2.0. 4 | 5 | # shellcheck disable=SC1091,SC3003,SC3043 6 | 7 | AWG=/usr/bin/awg 8 | if [ ! -x $AWG ]; then 9 | logger -t "amneziawg" "error: missing amneziawg-tools (${AWG})" 10 | exit 0 11 | fi 12 | 13 | [ -n "$INCLUDE_ONLY" ] || { 14 | . /lib/functions.sh 15 | . ../netifd-proto.sh 16 | init_proto "$@" 17 | } 18 | 19 | proto_amneziawg_init_config() { 20 | proto_config_add_string "private_key" 21 | proto_config_add_int "listen_port" 22 | proto_config_add_int "mtu" 23 | proto_config_add_string "fwmark" 24 | proto_config_add_int "awg_jc" 25 | proto_config_add_int "awg_jmin" 26 | proto_config_add_int "awg_jmax" 27 | proto_config_add_int "awg_s1" 28 | proto_config_add_int "awg_s2" 29 | proto_config_add_int "awg_s3" 30 | proto_config_add_int "awg_s4" 31 | proto_config_add_string "awg_h1" 32 | proto_config_add_string "awg_h2" 33 | proto_config_add_string "awg_h3" 34 | proto_config_add_string "awg_h4" 35 | proto_config_add_string "awg_i1" 36 | proto_config_add_string "awg_i2" 37 | proto_config_add_string "awg_i3" 38 | proto_config_add_string "awg_i4" 39 | proto_config_add_string "awg_i5" 40 | # shellcheck disable=SC2034 41 | available=1 42 | # shellcheck disable=SC2034 43 | no_proto_task=1 44 | } 45 | 46 | proto_amneziawg_is_kernel_mode() { 47 | if [ ! -e /sys/module/amneziawg ]; then 48 | modprobe amneziawg >/dev/null 2>&1 || true 49 | 50 | if [ -e /sys/module/amneziawg ]; then 51 | return 0 52 | else 53 | if ! command -v "${WG_QUICK_USERSPACE_IMPLEMENTATION:-amneziawg-go}" >/dev/null; then 54 | ret=$? 55 | echo "Please install either kernel module (kmod-amneziawg package) or user-space implementation in /usr/bin/amneziawg-go." 56 | exit $ret 57 | else 58 | return 1 59 | fi 60 | fi 61 | else 62 | return 0 63 | fi 64 | } 65 | 66 | proto_amneziawg_setup_peer() { 67 | local peer_config="$1" 68 | 69 | local disabled 70 | local public_key 71 | local preshared_key 72 | local allowed_ips 73 | local route_allowed_ips 74 | local endpoint_host 75 | local endpoint_port 76 | local persistent_keepalive 77 | 78 | config_get_bool disabled "${peer_config}" "disabled" 0 79 | config_get public_key "${peer_config}" "public_key" 80 | config_get preshared_key "${peer_config}" "preshared_key" 81 | config_get allowed_ips "${peer_config}" "allowed_ips" 82 | config_get_bool route_allowed_ips "${peer_config}" "route_allowed_ips" 0 83 | config_get endpoint_host "${peer_config}" "endpoint_host" 84 | config_get endpoint_port "${peer_config}" "endpoint_port" 85 | config_get persistent_keepalive "${peer_config}" "persistent_keepalive" 86 | 87 | if [ "${disabled}" -eq 1 ]; then 88 | # skip disabled peers 89 | return 0 90 | fi 91 | 92 | if [ -z "$public_key" ]; then 93 | echo "Skipping peer config $peer_config because public key is not defined." 94 | return 0 95 | fi 96 | 97 | echo "[Peer]" >> "${awg_cfg}" 98 | echo "PublicKey=${public_key}" >> "${awg_cfg}" 99 | if [ "${preshared_key}" ]; then 100 | echo "PresharedKey=${preshared_key}" >> "${awg_cfg}" 101 | fi 102 | for allowed_ip in ${allowed_ips}; do 103 | echo "AllowedIPs=${allowed_ip}" >> "${awg_cfg}" 104 | done 105 | if [ "${endpoint_host}" ]; then 106 | case "${endpoint_host}" in 107 | *:*) 108 | endpoint="[${endpoint_host}]" 109 | ;; 110 | *) 111 | endpoint="${endpoint_host}" 112 | ;; 113 | esac 114 | if [ "${endpoint_port}" ]; then 115 | endpoint="${endpoint}:${endpoint_port}" 116 | else 117 | endpoint="${endpoint}:51820" 118 | fi 119 | echo "Endpoint=${endpoint}" >> "${awg_cfg}" 120 | fi 121 | if [ "${persistent_keepalive}" ]; then 122 | echo "PersistentKeepalive=${persistent_keepalive}" >> "${awg_cfg}" 123 | fi 124 | 125 | if [ "${route_allowed_ips}" -ne 0 ]; then 126 | for allowed_ip in ${allowed_ips}; do 127 | case "${allowed_ip}" in 128 | *:*/*) 129 | proto_add_ipv6_route "${allowed_ip%%/*}" "${allowed_ip##*/}" 130 | ;; 131 | *.*/*) 132 | proto_add_ipv4_route "${allowed_ip%%/*}" "${allowed_ip##*/}" 133 | ;; 134 | *:*) 135 | proto_add_ipv6_route "${allowed_ip%%/*}" "128" 136 | ;; 137 | *.*) 138 | proto_add_ipv4_route "${allowed_ip%%/*}" "32" 139 | ;; 140 | esac 141 | done 142 | fi 143 | } 144 | 145 | ensure_key_is_generated() { 146 | local private_key 147 | private_key="$(uci get network."$1".private_key)" 148 | 149 | if [ "$private_key" = "generate" ]; then 150 | local ucitmp 151 | oldmask="$(umask)" 152 | umask 077 153 | ucitmp="$(mktemp -d)" 154 | private_key="$("${AWG}" genkey)" 155 | uci -q -t "$ucitmp" set network."$1".private_key="$private_key" && \ 156 | uci -q -t "$ucitmp" commit network 157 | rm -rf "$ucitmp" 158 | umask "$oldmask" 159 | fi 160 | } 161 | 162 | proto_amneziawg_setup() { 163 | local config="$1" 164 | local awg_dir="/tmp/amneziawg" 165 | local awg_cfg="${awg_dir}/${config}" 166 | 167 | local private_key 168 | local listen_port 169 | local addresses 170 | local mtu 171 | local fwmark 172 | local ip6prefix 173 | local nohostroute 174 | local tunlink 175 | 176 | # Amnezia WG specific parameters 177 | local awg_jc 178 | local awg_jmin 179 | local awg_jmax 180 | local awg_s1 181 | local awg_s2 182 | local awg_s3 183 | local awg_s4 184 | local awg_h1 185 | local awg_h2 186 | local awg_h3 187 | local awg_h4 188 | local awg_i1 189 | local awg_i2 190 | local awg_i3 191 | local awg_i4 192 | local awg_i5 193 | 194 | ensure_key_is_generated "${config}" 195 | 196 | config_load network 197 | config_get private_key "${config}" "private_key" 198 | config_get listen_port "${config}" "listen_port" 199 | config_get addresses "${config}" "addresses" 200 | config_get mtu "${config}" "mtu" 201 | config_get fwmark "${config}" "fwmark" 202 | config_get ip6prefix "${config}" "ip6prefix" 203 | config_get nohostroute "${config}" "nohostroute" 204 | config_get tunlink "${config}" "tunlink" 205 | 206 | config_get awg_jc "${config}" "awg_jc" 207 | config_get awg_jmin "${config}" "awg_jmin" 208 | config_get awg_jmax "${config}" "awg_jmax" 209 | config_get awg_s1 "${config}" "awg_s1" 210 | config_get awg_s2 "${config}" "awg_s2" 211 | config_get awg_s3 "${config}" "awg_s3" 212 | config_get awg_s4 "${config}" "awg_s4" 213 | config_get awg_h1 "${config}" "awg_h1" 214 | config_get awg_h2 "${config}" "awg_h2" 215 | config_get awg_h3 "${config}" "awg_h3" 216 | config_get awg_h4 "${config}" "awg_h4" 217 | config_get awg_i1 "${config}" "awg_i1" 218 | config_get awg_i2 "${config}" "awg_i2" 219 | config_get awg_i3 "${config}" "awg_i3" 220 | config_get awg_i4 "${config}" "awg_i4" 221 | config_get awg_i5 "${config}" "awg_i5" 222 | 223 | if proto_amneziawg_is_kernel_mode; then 224 | logger -t "amneziawg" "info: using kernel-space kmod-amneziawg for ${AWG}" 225 | ip link del dev "${config}" 2>/dev/null 226 | ip link add dev "${config}" type amneziawg 227 | else 228 | logger -t "amneziawg" "info: using user-space amneziawg-go for ${AWG}" 229 | rm -f "/var/run/amneziawg/${config}.sock" 230 | amneziawg-go "${config}" 231 | fi 232 | 233 | if [ "${mtu}" ]; then 234 | ip link set mtu "${mtu}" dev "${config}" 235 | fi 236 | 237 | proto_init_update "${config}" 1 238 | 239 | umask 077 240 | mkdir -p "${awg_dir}" 241 | echo "[Interface]" > "${awg_cfg}" 242 | echo "PrivateKey=${private_key}" >> "${awg_cfg}" 243 | if [ "${listen_port}" ]; then 244 | echo "ListenPort=${listen_port}" >> "${awg_cfg}" 245 | fi 246 | if [ "${fwmark}" ]; then 247 | echo "FwMark=${fwmark}" >> "${awg_cfg}" 248 | fi 249 | # AmneziaWG parameters 250 | if [ "${awg_jc}" ]; then 251 | echo "Jc=${awg_jc}" >> "${awg_cfg}" 252 | fi 253 | if [ "${awg_jmin}" ]; then 254 | echo "Jmin=${awg_jmin}" >> "${awg_cfg}" 255 | fi 256 | if [ "${awg_jmax}" ]; then 257 | echo "Jmax=${awg_jmax}" >> "${awg_cfg}" 258 | fi 259 | if [ "${awg_s1}" ]; then 260 | echo "S1=${awg_s1}" >> "${awg_cfg}" 261 | fi 262 | if [ "${awg_s2}" ]; then 263 | echo "S2=${awg_s2}" >> "${awg_cfg}" 264 | fi 265 | if [ "${awg_s3}" ]; then 266 | echo "S3=${awg_s3}" >> "${awg_cfg}" 267 | fi 268 | if [ "${awg_s4}" ]; then 269 | echo "S4=${awg_s4}" >> "${awg_cfg}" 270 | fi 271 | if [ "${awg_h1}" ]; then 272 | echo "H1=${awg_h1}" >> "${awg_cfg}" 273 | fi 274 | if [ "${awg_h2}" ]; then 275 | echo "H2=${awg_h2}" >> "${awg_cfg}" 276 | fi 277 | if [ "${awg_h3}" ]; then 278 | echo "H3=${awg_h3}" >> "${awg_cfg}" 279 | fi 280 | if [ "${awg_h4}" ]; then 281 | echo "H4=${awg_h4}" >> "${awg_cfg}" 282 | fi 283 | if [ "${awg_i1}" ]; then 284 | echo "I1=${awg_i1}" >> "${awg_cfg}" 285 | fi 286 | if [ "${awg_i2}" ]; then 287 | echo "I2=${awg_i2}" >> "${awg_cfg}" 288 | fi 289 | if [ "${awg_i3}" ]; then 290 | echo "I3=${awg_i3}" >> "${awg_cfg}" 291 | fi 292 | if [ "${awg_i4}" ]; then 293 | echo "I4=${awg_i4}" >> "${awg_cfg}" 294 | fi 295 | if [ "${awg_i5}" ]; then 296 | echo "I5=${awg_i5}" >> "${awg_cfg}" 297 | fi 298 | config_foreach proto_amneziawg_setup_peer "amneziawg_${config}" 299 | 300 | # Apply configuration file 301 | ${AWG} setconf "${config}" "${awg_cfg}" 302 | AWG_RETURN=$? 303 | 304 | rm -f "${awg_cfg}" 305 | 306 | if [ ${AWG_RETURN} -ne 0 ]; then 307 | sleep 5 308 | proto_setup_failed "${config}" 309 | exit 1 310 | fi 311 | 312 | for address in ${addresses}; do 313 | case "${address}" in 314 | *:*/*) 315 | proto_add_ipv6_address "${address%%/*}" "${address##*/}" 316 | ;; 317 | *.*/*) 318 | proto_add_ipv4_address "${address%%/*}" "${address##*/}" 319 | ;; 320 | *:*) 321 | proto_add_ipv6_address "${address%%/*}" "128" 322 | ;; 323 | *.*) 324 | proto_add_ipv4_address "${address%%/*}" "32" 325 | ;; 326 | esac 327 | done 328 | 329 | for prefix in ${ip6prefix}; do 330 | proto_add_ipv6_prefix "$prefix" 331 | done 332 | 333 | # endpoint dependency 334 | if [ "${nohostroute}" != "1" ]; then 335 | # shellcheck disable=SC2034 336 | ${AWG} show "${config}" endpoints | \ 337 | sed -E 's/\[?([0-9.:a-f]+)\]?:([0-9]+)/\1 \2/' | \ 338 | while IFS=$'\t ' read -r key address port; do 339 | [ -n "${port}" ] || continue 340 | proto_add_host_dependency "${config}" "${address}" "${tunlink}" 341 | done 342 | fi 343 | 344 | proto_send_update "${config}" 345 | } 346 | 347 | proto_amneziawg_teardown() { 348 | local config="$1" 349 | if proto_amneziawg_is_kernel_mode; then 350 | ip link del dev "${config}" >/dev/null 2>&1 351 | else 352 | rm -f "/var/run/amneziawg/${config}.sock" 353 | fi 354 | } 355 | 356 | [ -n "$INCLUDE_ONLY" ] || { 357 | add_protocol amneziawg 358 | } 359 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # amneziawg-openwrt 2 | 3 | This repo is a fork of original [amneziawg-openwrt](https://github.com/amnezia-vpn/amneziawg-openwrt) one with a few extra things intended to help with creating reproducible builds of the AmneziaWG package artifacts for different versions and architectures supported by OpenWrt. 4 | 5 | The idea is to keep it up to date with the upstream for all the general sources. All the extra features are basically combined into a top-level [Makefile](Makefile) and a set of GitHub action [workflows](.github/workflows/). 6 | 7 | ## OpenWRT package feed 8 | 9 | Custom OpenWRT package feed with binary packages built using pipelines of this repository is available [here](https://etaoin.shrdlu.club/openwrt-amneziawg/). 10 | 11 | Please note that it does include quite limited number of targets. 12 | If your target is not available, you may want to [open an issue](https://github.com/defanator/amneziawg-openwrt/issues), or refer to alternative builds available out there such as [https://github.com/Slava-Shchipunov/awg-openwrt](https://github.com/Slava-Shchipunov/awg-openwrt). 13 | 14 | ## Added features 15 | 16 | 1. [Makefile](Makefile) providing a number of targets: 17 | ``` 18 | % make 19 | 20 | Targets: 21 | help Show help message (list targets) 22 | show-env Show environment details 23 | export-env Export environment 24 | github-build-cache Run GitHub workflow to create OpenWrt toolchain and kernel cache (use WORKFLOW_REF to specify branch/tag) 25 | github-build-artifacts Run GitHub workflow to build amneziawg OpenWrt packages (use WORKFLOW_REF to specify branch/tag) 26 | build-toolchain Build OpenWrt toolchain 27 | build-kernel Build OpenWrt kernel 28 | build-amneziawg Build amneziawg-openwrt kernel module and packages 29 | prepare-artifacts Save amneziawg-openwrt artifacts from regular builds 30 | check-release Verify that everything is in place for tagged release 31 | prepare-release Save amneziawg-openwrt artifacts from tagged release 32 | ``` 33 | It is heavily used by GitHub actions (see below), but it also can be used directly on host platforms to prepare an environment for building OpenWrt packages, including kernel modules. 34 | 35 | 2. GitHub action workflows, in particular: 36 | - [build-toolchain-cache](.github/workflows/build-toolchain-cache.yml) - configures, compiles, and saves all the necessary OpenWrt tools required for building supplementary packages to GitHub cache; 37 | - [build-module-artifacts](.github/workflows/build-module-artifacts.yml) - builds AmneziaWG packages (.ipk) with the help of a tools created and saved in the GitHub cache by [build-toolchain-cache](.github/workflows/build-toolchain-cache.yml); 38 | - [create-release](.github/workflows/create-release.yml) - publishes GitHub release from a given tag. 39 | 40 | ## Creating OpenWrt toolchain and kernel cache 41 | 42 | Note that this process could take about 2 hours on a [default GitHub hosted runner](https://docs.github.com/en/actions/using-github-hosted-runners/using-github-hosted-runners/about-github-hosted-runners#standard-github-hosted-runners-for-public-repositories). For using GitHub actions powered methods, you will need to create your own fork of this repository first. 43 | 44 | ### With GitHub actions from UI 45 | 46 | Go to the Actions menu, select "build toolchain cache" workflow on the left, click on the "Run workflow" drop-down on the right. Enter all the details for target OpenWrt release and architecture, and press "Run workflow" button to initiate the job: 47 | 48 | ![build-toolchain-cache screenshot](docs/build-toolchain-cache1.png) 49 | 50 | ### With GitHub actions from CLI 51 | 52 | Use the `github-build-cache` target (do not forget to set your own build details through `OPENWRT_*` environment variables): 53 | ``` 54 | % OPENWRT_RELEASE=23.05.4 OPENWRT_ARCH=mips_24kc OPENWRT_TARGET=ath79 OPENWRT_SUBTARGET=generic make github-build-cache 55 | ``` 56 | 57 | ### Verifying created caches 58 | 59 | Go to the Actions menu and select Management - Caches on the left: 60 | 61 | ![toolchain caches screenshot](docs/build-toolchain-cache2.png) 62 | 63 | Refer to the workflow logs to figure out what went wrong in case if a desired cache has not been created during the run. 64 | 65 | ### Manually on a host VM/instance 66 | 67 | Run this: 68 | ``` 69 | % mkdir -p ${HOME}/openwrt-playground 70 | % cd ${HOME}/openwrt-playground 71 | % git clone https://github.com/defanator/amneziawg-openwrt.git 72 | % cd amneziawg-openwrt 73 | % OPENWRT_RELEASE=23.05.4 OPENWRT_ARCH=mips_24kc OPENWRT_TARGET=ath79 OPENWRT_SUBTARGET=generic make build-toolchain build-kernel 74 | ``` 75 | 76 | Do not forget to set your own build details. Once make invocation is complete, toolchain and kernel should be available in `${HOME}/openwrt-playground/openwrt`. 77 | 78 | ## Building AmneziaWG packages 79 | 80 | Note that toolchain cache must exist in order for these steps to complete (in case if GitHub actions are used). Building AmneziaWG packages should not take longer than 10 minutes if corresponding toolchain/kernel cache is available. 81 | 82 | ### With GitHub actions from UI 83 | 84 | Go to the Actions menu, select "build module artifacts" workflow on the left, click on the "Run workflow" drop-down on the right. Enter all the details for target OpenWrt release and architecture, and press "Run workflow" button to initiate the job: 85 | 86 | ![build-module-artifacts screenshot](docs/build-module-artifacts1.png) 87 | 88 | ### With GitHub actions from CLI 89 | 90 | Use the `github-build-artifacts` target (do not forget to set your own build details through `OPENWRT_*` environment variables): 91 | ``` 92 | % OPENWRT_RELEASE=23.05.4 OPENWRT_ARCH=mips_24kc OPENWRT_TARGET=ath79 OPENWRT_SUBTARGET=generic make github-build-artifacts 93 | ``` 94 | 95 | ### Locating the artifacts 96 | 97 | Go to the workflow summary page and look for "Artifacts produced during runtime" section in the bottom: 98 | 99 | ![build-module-artifacts results screenshot](docs/build-module-artifacts2.png) 100 | 101 | ### Manually on a host VM/instance 102 | 103 | Make sure you completed toolchain and kernel builds as described above, then run this: 104 | ``` 105 | % cd ${HOME}/openwrt-playground/amneziawg-openwrt 106 | % OPENWRT_RELEASE=23.05.4 OPENWRT_ARCH=mips_24kc OPENWRT_TARGET=ath79 OPENWRT_SUBTARGET=generic make build-amneziawg prepare-artifacts 107 | ``` 108 | 109 | Do not forget to set your own build details. Once make invocation is complete, packages should be available in `${HOME}/openwrt-playground/awgrelease`. 110 | 111 | ## Building AmneziaWG packages for OpenWrt snapshot 112 | 113 | This section describes how existing workflows can be used to build AmneziaWG packages for OpenWrt [snapshot](https://openwrt.org/releases/snapshot) (ongoing development branch). 114 | 115 | ### Building package artifacts for OpenWrt snapshot: general steps 116 | 117 | These steps must be performed in a given order: 118 | 119 | 1. Run the [build toolchain cache](.github/workflows/build-toolchain-cache.yml) workflow with the following inputs modified: 120 | 121 | 1. "OpenWrt version" must be set to `snapshot`, 122 | 2. "OpenWrt snapshot git ref" must be set either to `main` (default [upstream branch](https://github.com/openwrt/openwrt/branches)), or to a particular commit ref from [upstream history](https://github.com/openwrt/openwrt/commits/main/). 123 | 124 | Example: 125 | ![build-toolchain-cache for snapshot screenshot](docs/build-toolchain-cache-snapshot.png) 126 | 127 | 2. Run the [build module artifacts](.github/workflows/build-module-artifacts.yml) workflow with the following inputs modified: 128 | 129 | 1. "OpenWrt version" must be set to `snapshot`, 130 | 2. "OpenWrt snapshot git ref" must be set to the same value as in the 1st step of building toolchain cache. Note that if you were using branch name while building toolchain cache, it's worth to use particular commit SHA from that job in this step (commit SHA can be found in job log for the "checkout openwrt (snapshot)" step - see e.g. [this one](https://github.com/defanator/amneziawg-openwrt/actions/runs/13474586977/job/37652166474#step:4:165) for the reference): this way the building environment for module/packages should be in consistent shape with previous one where toolchain/kernel were first created. 131 | 132 | Example: 133 | ![build-module-artifacts for snapshot screenshot](docs/build-module-artifacts-snapshot.png) 134 | 135 | 3. Collect the artifacts - e.g. for [this run](https://github.com/defanator/amneziawg-openwrt/actions/runs/13480463738) you would get the following data: 136 | ``` 137 | snapshot/ath79/generic/amneziawg-tools-1.0.20241018-r1.apk 138 | snapshot/ath79/generic/kmod-amneziawg-6.6.78.1.0.20241112-r1.apk 139 | snapshot/ath79/generic/luci-proto-amneziawg-0.0.1-r1.apk 140 | snapshot/ath79/generic/packages.adb 141 | snapshot/ath79/generic/packages.adb.txt 142 | ``` 143 | 144 | (Note that OpenWrt has migrated from `opkg` to `apk` (Alpine Package Keeper) package manager in snapshots.) 145 | 146 | ### Debugging issues related to building for OpenWrt snapshot 147 | 148 | Under the hood the workflows run the same set of Makefile targets to achieve the goal, so in case of any issues you may refer to workflow logs and try to reproduce a failure in a manual build (follow the above guidance on how to build toolchain, kernel, and packages on VMs). 149 | 150 | ## Creating tagged releases 151 | 152 | In order to create new release, a SEMVER tag in a form of `vX.Y.Z` must be created and pushed to the repository. 153 | Corresponding [release workflow](.github/workflows/create-release.yml) will be automatically triggered. 154 | 155 | ## Creating opkg feed 156 | 157 | The following Makefile targets are available to operate with OpenWrt [package feeds](https://openwrt.org/docs/guide-developer/feeds): 158 | - `make create-feed`: create new feed from previously built artifacts, 159 | - `make verify-feed`: verify feed metadata and signatures. 160 | 161 | Every tagged release should produce archive(s) with opkg feed for each unique combination of `OPENWRT_RELEASE`, `OPENWRT_ARCH`, `OPENWRT_TARGET`, and `OPENWRT_SUBTARGET`. 162 | Such archives can be used directly to extract `.ipk` packages for manual installation on target platforms, or be combined into a single feed which can be hosted and accessed externally (TODO: add an example of how to configure `/etc/opkg/customfeeds.conf`). 163 | 164 | ## Miscellaneous 165 | 166 | This tooling uses automated way of obtaining vermagic of a given kernel based on required build parameters (`OPENWRT_RELEASE`, `OPENWRT_ARCH`, `OPENWRT_TARGET`, `OPENWRT_SUBTARGET`); `make show-env` could be quite handy while debugging various related stuff: 167 | 168 | ``` 169 | % make show-env | grep -- "^OPENWRT" 170 | OPENWRT_RELEASE 23.05.3 171 | OPENWRT_ARCH mips_24kc 172 | OPENWRT_TARGET ath79 173 | OPENWRT_SUBTARGET generic 174 | OPENWRT_VERMAGIC 34a8cffa541c94af8232fe9af7a1f5ba 175 | OPENWRT_BASE_URL https://downloads.openwrt.org/releases/23.05.3/targets/ath79/generic 176 | OPENWRT_MANIFEST https://downloads.openwrt.org/releases/23.05.3/targets/ath79/generic/openwrt-23.05.3-ath79-generic.manifest 177 | 178 | % OPENWRT_RELEASE=22.03.4 OPENWRT_SUBTARGET=nand make show-env | grep -- "^OPENWRT" 179 | OPENWRT_RELEASE 22.03.4 180 | OPENWRT_ARCH mips_24kc 181 | OPENWRT_TARGET ath79 182 | OPENWRT_SUBTARGET nand 183 | OPENWRT_VERMAGIC 5c9be91b90bda5403fe3a7c4e8ddb26f 184 | OPENWRT_BASE_URL https://downloads.openwrt.org/releases/22.03.4/targets/ath79/nand 185 | OPENWRT_MANIFEST https://downloads.openwrt.org/releases/22.03.4/targets/ath79/nand/openwrt-22.03.4-ath79-nand.manifest 186 | ``` 187 | 188 | Output of `make show-env` should be present in GitHub action workflow logs as well. 189 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env make -f 2 | 3 | SELF := $(abspath $(lastword $(MAKEFILE_LIST))) 4 | TOPDIR := $(realpath $(dir $(abspath $(lastword $(MAKEFILE_LIST))))) 5 | UPPERDIR := $(realpath $(TOPDIR)/../) 6 | 7 | OPENWRT_SRCDIR ?= $(UPPERDIR)/openwrt 8 | AMNEZIAWG_SRCDIR ?= $(TOPDIR) 9 | AMNEZIAWG_DSTDIR ?= $(UPPERDIR)/awgrelease 10 | 11 | OPENWRT_RELEASE ?= 23.05.3 12 | OPENWRT_ARCH ?= mips_24kc 13 | OPENWRT_TARGET ?= ath79 14 | OPENWRT_SUBTARGET ?= generic 15 | OPENWRT_VERMAGIC ?= auto 16 | OPENWRT_SNAPSHOT_REF ?= main 17 | 18 | # for generate-target-matrix 19 | OPENWRT_RELEASES ?= $(OPENWRT_RELEASE) 20 | 21 | GITHUB_SHA ?= $(shell git rev-parse --short HEAD) 22 | VERSION_STR ?= $(shell git describe --tags --long --dirty) 23 | POSTFIX := $(VERSION_STR)_v$(OPENWRT_RELEASE)_$(OPENWRT_ARCH)_$(OPENWRT_TARGET)_$(OPENWRT_SUBTARGET) 24 | FEED_NAME := amneziawg-opkg-feed-$(VERSION_STR)-openwrt-$(OPENWRT_RELEASE)-$(OPENWRT_ARCH)-$(OPENWRT_TARGET)-$(OPENWRT_SUBTARGET) 25 | 26 | WORKFLOW_REF ?= $(shell git rev-parse --abbrev-ref HEAD) 27 | 28 | ifneq ($(OPENWRT_RELEASE),snapshot) 29 | OPENWRT_ROOT_URL ?= https://downloads.openwrt.org/releases 30 | OPENWRT_BASE_URL ?= $(OPENWRT_ROOT_URL)/$(OPENWRT_RELEASE)/targets/$(OPENWRT_TARGET)/$(OPENWRT_SUBTARGET) 31 | OPENWRT_MANIFEST ?= $(OPENWRT_BASE_URL)/openwrt-$(OPENWRT_RELEASE)-$(OPENWRT_TARGET)-$(OPENWRT_SUBTARGET).manifest 32 | OPENWRT_PKG_EXT := .ipk 33 | else 34 | OPENWRT_ROOT_URL ?= https://downloads.openwrt.org/snapshots 35 | OPENWRT_BASE_URL ?= $(OPENWRT_ROOT_URL)/targets/$(OPENWRT_TARGET)/$(OPENWRT_SUBTARGET) 36 | OPENWRT_MANIFEST ?= $(OPENWRT_BASE_URL)/openwrt-$(OPENWRT_TARGET)-$(OPENWRT_SUBTARGET).manifest 37 | OPENWRT_PKG_EXT := .apk 38 | endif 39 | 40 | NPROC ?= $(shell getconf _NPROCESSORS_ONLN) 41 | 42 | ifndef OPENWRT_VERMAGIC 43 | _NEED_VERMAGIC=1 44 | endif 45 | 46 | ifeq ($(OPENWRT_VERMAGIC), auto) 47 | _NEED_VERMAGIC=1 48 | endif 49 | 50 | OPENWRT_RELEASE_NUM := $(shell echo $(OPENWRT_RELEASE) | awk -F. '{printf "%02d%02d%02d", $$1, $$2, $$3}') 51 | 52 | ifeq ($(_NEED_VERMAGIC), 1) 53 | ifeq ($(OPENWRT_RELEASE), snapshot) 54 | OPENWRT_VERMAGIC := $(shell curl -fs $(OPENWRT_MANIFEST) | grep -- "^kernel" | sed -e "s,.*\~,," | cut -d '-' -f 1) 55 | else 56 | ifeq ($(shell [ $(OPENWRT_RELEASE_NUM) -ge 240000 ] && echo true || echo false), true) 57 | OPENWRT_VERMAGIC := $(shell curl -fs $(OPENWRT_MANIFEST) | grep -- "^kernel" | sed -e "s,.*\~,," | cut -d '-' -f 1) 58 | else 59 | OPENWRT_VERMAGIC := $(shell curl -fs $(OPENWRT_MANIFEST) | grep -- "^kernel" | sed -e "s,.*\-,,") 60 | endif 61 | endif 62 | endif 63 | 64 | ifndef USIGN 65 | ifneq ($(shell usign 2>&1 | grep -i -- "usage: usign"),) 66 | USIGN = usign 67 | endif 68 | endif 69 | USIGN ?= $(error usign not found) 70 | 71 | FEED_PATH ?= $(TOPDIR)/.feed 72 | FEED_SEC_KEY ?= $(error FEED_SEC_KEY unset) 73 | FEED_PUB_KEY ?= $(error FEED_PUB_KEY unset) 74 | 75 | help: ## Show help message (list targets) 76 | @awk 'BEGIN {FS = ":.*##"; printf "\nTargets:\n"} /^[$$()% 0-9a-zA-Z_-]+:.*?##/ {printf " \033[36m%-22s\033[0m %s\n", $$1, $$2}' $(SELF) 77 | 78 | SHOW_ENV_VARS = \ 79 | SHELL \ 80 | SELF \ 81 | TOPDIR \ 82 | UPPERDIR \ 83 | OPENWRT_SRCDIR \ 84 | AMNEZIAWG_SRCDIR \ 85 | AMNEZIAWG_DSTDIR \ 86 | GITHUB_SHA \ 87 | VERSION_STR \ 88 | POSTFIX \ 89 | FEED_NAME \ 90 | GITHUB_REF_TYPE \ 91 | GITHUB_REF_NAME \ 92 | WORKFLOW_REF \ 93 | OPENWRT_RELEASE \ 94 | OPENWRT_RELEASE_NUM \ 95 | OPENWRT_ARCH \ 96 | OPENWRT_TARGET \ 97 | OPENWRT_SUBTARGET \ 98 | OPENWRT_VERMAGIC \ 99 | OPENWRT_SNAPSHOT_REF \ 100 | OPENWRT_BASE_URL \ 101 | OPENWRT_MANIFEST \ 102 | OPENWRT_PKG_EXT \ 103 | NPROC 104 | 105 | show-var-%: 106 | @{ \ 107 | escaped_v="$(subst ",\",$($*))" ; \ 108 | if [ -n "$$escaped_v" ]; then v="$$escaped_v"; else v="(undefined)"; fi; \ 109 | printf "%-21s %s\n" "$*" "$$v"; \ 110 | } 111 | 112 | show-env: $(addprefix show-var-, $(SHOW_ENV_VARS)) ## Show environment details 113 | 114 | export-var-%: 115 | @{ \ 116 | escaped_v="$(subst ",\",$($*))" ; \ 117 | if [ -n "$$escaped_v" ]; then v="$$escaped_v"; else v="(undefined)"; fi; \ 118 | printf "%s=%s\n" "$*" "$$v"; \ 119 | } 120 | 121 | export-env: $(addprefix export-var-, $(SHOW_ENV_VARS)) ## Export environment 122 | 123 | .venv: 124 | python3 -m venv $(TOPDIR)/.venv 125 | $(TOPDIR)/.venv/bin/python3 -m pip install -r $(TOPDIR)/requirements.txt 126 | 127 | venv: .venv ## Create virtualenv 128 | 129 | .PHONY: generate-target-matrix 130 | generate-target-matrix: .venv ## Generate target matrix of build environments for GitHub CI 131 | @printf "BUILD_MATRIX=%s" "$$($(TOPDIR)/.venv/bin/python3 $(TOPDIR)/scripts/generate_target_matrix.py $(OPENWRT_RELEASES))" 132 | 133 | .PHONY: github-build-cache 134 | github-build-cache: ## Run GitHub workflow to create OpenWrt toolchain and kernel cache (use WORKFLOW_REF to specify branch/tag) 135 | @{ \ 136 | set -ex ; \ 137 | gh workflow run build-toolchain-cache.yml \ 138 | --ref $(WORKFLOW_REF) \ 139 | -f openwrt_version=$(OPENWRT_RELEASE) \ 140 | -f openwrt_arch=$(OPENWRT_ARCH) \ 141 | -f openwrt_target=$(OPENWRT_TARGET) \ 142 | -f openwrt_subtarget=$(OPENWRT_SUBTARGET) \ 143 | -f openwrt_vermagic=$(OPENWRT_VERMAGIC) ; \ 144 | } 145 | 146 | .PHONY: github-build-artifacts 147 | github-build-artifacts: ## Run GitHub workflow to build amneziawg OpenWrt packages (use WORKFLOW_REF to specify branch/tag) 148 | @{ \ 149 | set -ex ; \ 150 | gh workflow run build-module-artifacts.yml \ 151 | --ref $(WORKFLOW_REF) \ 152 | -f openwrt_version=$(OPENWRT_RELEASE) \ 153 | -f openwrt_arch=$(OPENWRT_ARCH) \ 154 | -f openwrt_target=$(OPENWRT_TARGET) \ 155 | -f openwrt_subtarget=$(OPENWRT_SUBTARGET) \ 156 | -f openwrt_vermagic=$(OPENWRT_VERMAGIC) ; \ 157 | } 158 | 159 | $(OPENWRT_SRCDIR): 160 | @{ \ 161 | set -eux ; \ 162 | git clone https://github.com/openwrt/openwrt.git $@ ; \ 163 | if [ "$(OPENWRT_RELEASE)" != "snapshot" ]; then \ 164 | cd $@ ; \ 165 | git checkout v$(OPENWRT_RELEASE) ; \ 166 | else \ 167 | cd $@ ; \ 168 | git checkout $(OPENWRT_SNAPSHOT_REF) ; \ 169 | fi ; \ 170 | } 171 | 172 | $(OPENWRT_SRCDIR)/feeds.conf: | $(OPENWRT_SRCDIR) 173 | @{ \ 174 | set -ex ; \ 175 | curl -fsL $(OPENWRT_BASE_URL)/feeds.buildinfo | tee $@ ; \ 176 | } 177 | 178 | $(OPENWRT_SRCDIR)/.config: | $(OPENWRT_SRCDIR) 179 | @{ \ 180 | set -ex ; \ 181 | curl -fsL $(OPENWRT_BASE_URL)/config.buildinfo > $@ ; \ 182 | echo "CONFIG_PACKAGE_kmod-crypto-lib-chacha20=m" >> $@ ; \ 183 | echo "CONFIG_PACKAGE_kmod-crypto-lib-chacha20poly1305=m" >> $@ ; \ 184 | echo "CONFIG_PACKAGE_kmod-crypto-chacha20poly1305=m" >> $@ ; \ 185 | } 186 | 187 | .PHONY: build-toolchain 188 | build-toolchain: $(OPENWRT_SRCDIR)/feeds.conf $(OPENWRT_SRCDIR)/.config ## Build OpenWrt toolchain 189 | @{ \ 190 | set -ex ; \ 191 | cd $(OPENWRT_SRCDIR) ; \ 192 | time -p ./scripts/feeds update ; \ 193 | time -p ./scripts/feeds install -a ; \ 194 | time -p make defconfig ; \ 195 | time -p make tools/install -i -j $(NPROC) ; \ 196 | time -p make toolchain/install -i -j $(NPROC) ; \ 197 | } 198 | 199 | .PHONY: build-kernel 200 | build-kernel: $(OPENWRT_SRCDIR)/feeds.conf $(OPENWRT_SRCDIR)/.config ## Build OpenWrt kernel 201 | @{ \ 202 | set -ex ; \ 203 | cd $(OPENWRT_SRCDIR) ; \ 204 | time -p make defconfig ; \ 205 | time -p make V=s target/linux/compile -i -j $(NPROC) ; \ 206 | VERMAGIC=$$(cat ./build_dir/target-$(OPENWRT_ARCH)*/linux-$(OPENWRT_TARGET)_$(OPENWRT_SUBTARGET)/linux-*/.vermagic) ; \ 207 | echo "Vermagic: $${VERMAGIC}" ; \ 208 | if [ "$${VERMAGIC}" != "$(OPENWRT_VERMAGIC)" ]; then \ 209 | echo "Vermagic mismatch: $${VERMAGIC}, expected $(OPENWRT_VERMAGIC)" ; \ 210 | exit 1 ; \ 211 | fi ; \ 212 | } 213 | 214 | # TODO: this should not be required but actions/cache/save@v4 could not handle circular symlinks with error like this: 215 | # Warning: ELOOP: too many symbolic links encountered, stat '/home/runner/work/amneziawg-openwrt/amneziawg-openwrt/openwrt/staging_dir/toolchain-mips_24kc_gcc-11.2.0_musl/initial/lib/lib' 216 | # Warning: Cache save failed. 217 | .PHONY: purge-circular-symlinks 218 | purge-circular-symlinks: 219 | @{ \ 220 | set -ex ; \ 221 | cd $(OPENWRT_SRCDIR) ; \ 222 | export LC_ALL=C ; \ 223 | for deadlink in $$(find . -follow -type l -printf "" 2>&1 | sed -e "s/find: '\(.*\)': Too many levels of symbolic links.*/\1/"); do \ 224 | echo "deleting dead link: $${deadlink}" ; \ 225 | rm -f "$${deadlink}" ; \ 226 | done ; \ 227 | } 228 | 229 | .PHONY: build-amneziawg 230 | build-amneziawg: ## Build amneziawg-openwrt kernel module and packages 231 | @{ \ 232 | set -ex ; \ 233 | cd $(OPENWRT_SRCDIR) ; \ 234 | VERMAGIC=$$(cat ./build_dir/target-$(OPENWRT_ARCH)*/linux-$(OPENWRT_TARGET)_$(OPENWRT_SUBTARGET)/linux-*/.vermagic) ; \ 235 | echo "Vermagic: $${VERMAGIC}" ; \ 236 | if [ "$${VERMAGIC}" != "$(OPENWRT_VERMAGIC)" ]; then \ 237 | echo "Vermagic mismatch: $${VERMAGIC}, expected $(OPENWRT_VERMAGIC)" ; \ 238 | exit 1 ; \ 239 | fi ; \ 240 | echo "src-git awgopenwrt $(AMNEZIAWG_SRCDIR)^$(GITHUB_SHA)" > feeds.conf ; \ 241 | ./scripts/feeds update ; \ 242 | ./scripts/feeds install -a ; \ 243 | mv .config.old .config ; \ 244 | echo "CONFIG_PACKAGE_kmod-amneziawg=m" >> .config ; \ 245 | echo "CONFIG_PACKAGE_amneziawg-tools=y" >> .config ; \ 246 | echo "CONFIG_PACKAGE_luci-proto-amneziawg=y" >> .config ; \ 247 | make defconfig ; \ 248 | make V=s package/kmod-amneziawg/clean ; \ 249 | make V=s package/kmod-amneziawg/download ; \ 250 | make V=s package/kmod-amneziawg/prepare ; \ 251 | make V=s package/kmod-amneziawg/compile ; \ 252 | make V=s package/luci-proto-amneziawg/clean ; \ 253 | make V=s package/luci-proto-amneziawg/download ; \ 254 | make V=s package/luci-proto-amneziawg/prepare ; \ 255 | make V=s package/luci-proto-amneziawg/compile ; \ 256 | make V=s package/amneziawg-tools/clean ; \ 257 | make V=s package/amneziawg-tools/download ; \ 258 | make V=s package/amneziawg-tools/prepare ; \ 259 | make V=s package/amneziawg-tools/compile ; \ 260 | } 261 | 262 | .PHONY: prepare-artifacts 263 | prepare-artifacts: ## Save amneziawg-openwrt artifacts from regular builds 264 | @{ \ 265 | set -ex ; \ 266 | cd $(OPENWRT_SRCDIR) ; \ 267 | mkdir -p $(AMNEZIAWG_DSTDIR)/$(OPENWRT_RELEASE)/$(OPENWRT_TARGET)/$(OPENWRT_SUBTARGET) ; \ 268 | cp bin/packages/$(OPENWRT_ARCH)/awgopenwrt/amneziawg-tools*$(OPENWRT_PKG_EXT) $(AMNEZIAWG_DSTDIR)/$(OPENWRT_RELEASE)/$(OPENWRT_TARGET)/$(OPENWRT_SUBTARGET)/ ; \ 269 | cp bin/packages/$(OPENWRT_ARCH)/awgopenwrt/luci-proto-amneziawg*$(OPENWRT_PKG_EXT) $(AMNEZIAWG_DSTDIR)/$(OPENWRT_RELEASE)/$(OPENWRT_TARGET)/$(OPENWRT_SUBTARGET)/ ; \ 270 | cp bin/targets/$(OPENWRT_TARGET)/$(OPENWRT_SUBTARGET)/packages/kmod-amneziawg*$(OPENWRT_PKG_EXT) $(AMNEZIAWG_DSTDIR)/$(OPENWRT_RELEASE)/$(OPENWRT_TARGET)/$(OPENWRT_SUBTARGET)/ ; \ 271 | } 272 | 273 | .PHONY: check-release 274 | check-release: ## Verify that everything is in place for tagged release 275 | @{ \ 276 | set -eux ; \ 277 | echo "checking for release" ; \ 278 | if [ "$${GITHUB_REF_TYPE}" != "tag" ]; then \ 279 | echo "ERROR: unsupported GITHUB_REF_TYPE: $${GITHUB_REF_TYPE}" >&2 ; \ 280 | exit 1 ; \ 281 | fi ; \ 282 | if ! echo "$${GITHUB_REF_NAME}" | grep -q -E '^v[0-9]+(\.[0-9]+){2}$$'; then \ 283 | echo "ERROR: tag $${GITHUB_REF_NAME} is NOT a valid semver" >&2 ; \ 284 | exit 1 ; \ 285 | fi ; \ 286 | num_extra_commits="$$(git rev-list "$${GITHUB_REF_NAME}..HEAD" --count)" ; \ 287 | if [ "$${num_extra_commits}" -gt 0 ]; then \ 288 | echo "ERROR: $${num_extra_commits} extra commit(s) detected" >&2 ; \ 289 | exit 1 ; \ 290 | fi ; \ 291 | } 292 | 293 | .PHONY: create-feed-archive 294 | create-feed-archive: ## Create archive of a package feed 295 | @{ \ 296 | set -eux ; \ 297 | cd $(OPENWRT_SRCDIR) ; \ 298 | mkdir -p $(AMNEZIAWG_DSTDIR) ; \ 299 | FEED_PATH="$(AMNEZIAWG_DSTDIR)/$(FEED_NAME)" $(MAKE) -f $(SELF) create-feed ; \ 300 | FEED_PATH="$(AMNEZIAWG_DSTDIR)/$(FEED_NAME)" $(MAKE) -f $(SELF) verify-feed ; \ 301 | tar -C $(AMNEZIAWG_DSTDIR)/$(FEED_NAME) -czvf $(AMNEZIAWG_DSTDIR)/$(FEED_NAME).tar.gz $(OPENWRT_RELEASE)/ ; \ 302 | } 303 | 304 | .PHONY: prepare-release 305 | prepare-release: check-release create-feed-archive ## Save amneziawg-openwrt artifacts from tagged release 306 | 307 | $(FEED_PATH): 308 | mkdir -p $@ 309 | 310 | .PHONY: create-feed-ipk 311 | create-feed-ipk: | $(FEED_PATH) 312 | @{ \ 313 | set -eux ; \ 314 | target_path=$(FEED_PATH)/$(OPENWRT_RELEASE)/$(OPENWRT_TARGET)/$(OPENWRT_SUBTARGET) ; \ 315 | mkdir -p $${target_path} ; \ 316 | for pkg in $$(find $(AMNEZIAWG_DSTDIR)/ -type f -name "*$(OPENWRT_PKG_EXT)"); do \ 317 | cp $${pkg} $${target_path}/ ; \ 318 | done ; \ 319 | ( cd $${target_path} && $(TOPDIR)/scripts/ipkg-make-index.sh . >Packages && $(USIGN) -S -m Packages -s $(FEED_SEC_KEY) -x Packages.sig && gzip -fk Packages ) ; \ 320 | cat $${target_path}/Packages ; \ 321 | } 322 | 323 | .PHONY: verify-feed-ipk 324 | verify-feed-ipk: | $(FEED_PATH) 325 | @{ \ 326 | set -eux ; \ 327 | target_path=$(FEED_PATH)/$(OPENWRT_RELEASE)/$(OPENWRT_TARGET)/$(OPENWRT_SUBTARGET) ; \ 328 | cat $${target_path}/Packages ; \ 329 | find $${target_path}/ -type f | sort ; \ 330 | $(USIGN) -V -m $${target_path}/Packages -p $(FEED_PUB_KEY) ; \ 331 | ( cd $${target_path} && gunzip -fk Packages.gz ) ; \ 332 | $(USIGN) -V -m $${target_path}/Packages -p $(FEED_PUB_KEY) ; \ 333 | } 334 | 335 | .PHONY: create-feed-apk 336 | create-feed-apk: 337 | @{ \ 338 | set -eux ; \ 339 | export APK="$(OPENWRT_SRCDIR)/staging_dir/host/bin/apk" ; \ 340 | $${APK} --version ; \ 341 | target_path=$(FEED_PATH)/$(OPENWRT_RELEASE)/$(OPENWRT_TARGET)/$(OPENWRT_SUBTARGET) ; \ 342 | mkdir -p $${target_path} ; \ 343 | for pkg in $$(find $(AMNEZIAWG_DSTDIR)/ -type f -name "*$(OPENWRT_PKG_EXT)"); do \ 344 | cp $${pkg} $${target_path}/ ; \ 345 | done ; \ 346 | $(TOPDIR)/scripts/apk-make-index.sh create "$${target_path}" ; \ 347 | $(TOPDIR)/scripts/apk-make-index.sh dump "$${target_path}" ; \ 348 | } 349 | 350 | .PHONY: verify-feed-apk 351 | verify-feed-apk: 352 | @{ \ 353 | set -eux ; \ 354 | export APK="$(OPENWRT_SRCDIR)/staging_dir/host/bin/apk" ; \ 355 | $${APK} --version ; \ 356 | target_path=$(FEED_PATH)/$(OPENWRT_RELEASE)/$(OPENWRT_TARGET)/$(OPENWRT_SUBTARGET) ; \ 357 | $(TOPDIR)/scripts/apk-make-index.sh dump "$${target_path}" ; \ 358 | $(TOPDIR)/scripts/apk-make-index.sh verify "$${target_path}" ; \ 359 | } 360 | 361 | ifneq ($(OPENWRT_RELEASE),snapshot) 362 | .PHONY: create-feed 363 | create-feed: create-feed-ipk ## Create package feed 364 | 365 | .PHONY: verify-feed 366 | verify-feed: verify-feed-ipk ## Verify package feed 367 | else 368 | .PHONY: create-feed 369 | create-feed: create-feed-apk 370 | 371 | .PHONY: verify-feed 372 | verify-feed: verify-feed-apk 373 | endif 374 | -------------------------------------------------------------------------------- /luci-proto-amneziawg/htdocs/luci-static/resources/icons/amneziawg.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 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 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | -------------------------------------------------------------------------------- /luci-proto-amneziawg/htdocs/luci-static/resources/protocol/amneziawg.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 'require fs'; 3 | 'require ui'; 4 | 'require dom'; 5 | 'require uci'; 6 | 'require rpc'; 7 | 'require form'; 8 | 'require network'; 9 | 'require validation'; 10 | 'require uqr'; 11 | 12 | var generateKey = rpc.declare({ 13 | object: 'luci.amneziawg', 14 | method: 'generateKeyPair', 15 | expect: { keys: {} } 16 | }); 17 | 18 | var getPublicAndPrivateKeyFromPrivate = rpc.declare({ 19 | object: 'luci.amneziawg', 20 | method: 'getPublicAndPrivateKeyFromPrivate', 21 | params: ['privkey'], 22 | expect: { keys: {} } 23 | }); 24 | 25 | var generatePsk = rpc.declare({ 26 | object: 'luci.amneziawg', 27 | method: 'generatePsk', 28 | expect: { psk: '' } 29 | }); 30 | 31 | var qrIcon = ''; 32 | 33 | function validateBase64(section_id, value) { 34 | if (value.length == 0) 35 | return true; 36 | 37 | if (value.length != 44 || !value.match(/^(?:[A-Za-z0-9+\/]{4})*(?:[A-Za-z0-9+\/]{2}==|[A-Za-z0-9+\/]{3}=)?$/)) 38 | return _('Invalid Base64 key string'); 39 | 40 | if (value[43] != "=" ) 41 | return _('Invalid Base64 key string'); 42 | 43 | return true; 44 | } 45 | 46 | var stubValidator = { 47 | factory: validation, 48 | apply: function(type, value, args) { 49 | if (value != null) 50 | this.value = value; 51 | 52 | return validation.types[type].apply(this, args); 53 | }, 54 | assert: function(condition) { 55 | return !!condition; 56 | } 57 | }; 58 | 59 | function generateDescription(name, texts) { 60 | return E('li', { 'style': 'color: inherit;' }, [ 61 | E('span', name), 62 | E('ul', texts.map(function (text) { 63 | return E('li', { 'style': 'color: inherit;' }, text); 64 | })) 65 | ]); 66 | } 67 | 68 | function buildSVGQRCode(data, code) { 69 | // pixel size larger than 4 clips right and bottom edges of complex configs 70 | const options = { 71 | pixelSize: 4, 72 | whiteColor: 'white', 73 | blackColor: 'black' 74 | }; 75 | const svg = uqr.renderSVG(data, options); 76 | code.style.opacity = ''; 77 | dom.content(code, Object.assign(E(svg), { style: 'width:100%;height:auto' })); 78 | } 79 | 80 | var cbiKeyPairGenerate = form.DummyValue.extend({ 81 | cfgvalue: function(section_id, value) { 82 | return E('button', { 83 | 'class': 'btn', 84 | 'click': ui.createHandlerFn(this, function(section_id, ev) { 85 | var prv = this.section.getUIElement(section_id, 'private_key'), 86 | pub = this.section.getUIElement(section_id, 'public_key'), 87 | map = this.map; 88 | 89 | return generateKey().then(function(keypair) { 90 | prv.setValue(keypair.priv); 91 | pub.setValue(keypair.pub); 92 | map.save(null, true); 93 | }); 94 | }, section_id) 95 | }, [ _('Generate new key pair') ]); 96 | } 97 | }); 98 | 99 | function handleWindowDragDropIgnore(ev) { 100 | ev.preventDefault() 101 | } 102 | 103 | return network.registerProtocol('amneziawg', { 104 | getI18n: function() { 105 | return _('AmneziaWG VPN'); 106 | }, 107 | 108 | getIfname: function() { 109 | return this._ubus('l3_device') || this.sid; 110 | }, 111 | 112 | getPackageName: function() { 113 | return 'amneziawg-tools'; 114 | }, 115 | 116 | isFloating: function() { 117 | return true; 118 | }, 119 | 120 | isVirtual: function() { 121 | return true; 122 | }, 123 | 124 | getDevices: function() { 125 | return null; 126 | }, 127 | 128 | containsDevice: function(ifname) { 129 | return (network.getIfnameOf(ifname) == this.getIfname()); 130 | }, 131 | 132 | renderFormOptions: function(s) { 133 | var o, ss, ss2; 134 | 135 | // -- general --------------------------------------------------------------------- 136 | 137 | o = s.taboption('general', form.Value, 'private_key', _('Private Key'), _('Required. Base64-encoded private key for this interface.')); 138 | o.password = true; 139 | o.validate = validateBase64; 140 | o.rmempty = false; 141 | 142 | var serverName = this.getIfname(); 143 | 144 | o = s.taboption('general', form.Value, 'public_key', _('Public Key'), _('Base64-encoded public key of this interface for sharing.')); 145 | o.rmempty = false; 146 | o.write = function() {/* write nothing */}; 147 | 148 | o.load = function(section_id) { 149 | var privKey = s.formvalue(section_id, 'private_key') || uci.get('network', section_id, 'private_key'); 150 | 151 | return getPublicAndPrivateKeyFromPrivate(privKey).then( 152 | function(keypair) { 153 | return keypair.pub || ''; 154 | }, 155 | function(error) { 156 | return _('Error getting PublicKey'); 157 | }, this) 158 | }; 159 | 160 | s.taboption('general', cbiKeyPairGenerate, '_gen_server_keypair', ' '); 161 | 162 | o = s.taboption('general', form.Value, 'listen_port', _('Listen Port'), _('Optional. UDP port used for outgoing and incoming packets.')); 163 | o.datatype = 'port'; 164 | o.placeholder = _('random'); 165 | o.optional = true; 166 | 167 | o = s.taboption('general', form.DynamicList, 'addresses', _('IP Addresses'), _('Recommended. IP addresses of the AmneziaWG interface.')); 168 | o.datatype = 'ipaddr'; 169 | o.optional = true; 170 | 171 | o = s.taboption('general', form.Flag, 'nohostroute', _('No Host Routes'), _('Optional. Do not create host routes to peers.')); 172 | o.optional = true; 173 | 174 | o = s.taboption('general', form.Button, '_import', _('Import configuration'), _('Imports settings from an existing AmneziaWG configuration file')); 175 | o.inputtitle = _('Load configuration…'); 176 | o.onclick = function() { 177 | return ss.handleConfigImport('full'); 178 | }; 179 | 180 | // -- advanced -------------------------------------------------------------------- 181 | 182 | o = s.taboption('advanced', form.Value, 'mtu', _('MTU'), _('Optional. Maximum Transmission Unit of tunnel interface.')); 183 | o.datatype = 'range(0,8940)'; 184 | o.placeholder = '1420'; 185 | o.optional = true; 186 | 187 | o = s.taboption('advanced', form.Value, 'fwmark', _('Firewall Mark'), _('Optional. 32-bit mark for packets during firewall processing. Enter value in hex, starting with 0x.')); 188 | o.optional = true; 189 | o.validate = function(section_id, value) { 190 | if (value.length > 0 && !value.match(/^0x[a-fA-F0-9]{1,8}$/)) 191 | return _('Invalid hexadecimal value'); 192 | 193 | return true; 194 | }; 195 | 196 | // AmneziaWG 197 | 198 | try { 199 | s.tab('amneziawg', _('AmneziaWG Settings'), _('Further information about AmneziaWG interfaces and peers at amnezia.org.')); 200 | } 201 | catch(e) {} 202 | 203 | o = s.taboption('amneziawg', form.Value, 'awg_jc', _('Jc'), _('Junk packet count.')); 204 | o.datatype = 'uinteger'; 205 | o.placeholder = '0'; 206 | o.optional = true; 207 | 208 | o = s.taboption('amneziawg', form.Value, 'awg_jmin', _('Jmin'), _('Junk packet minimum size.')); 209 | o.datatype = 'uinteger'; 210 | o.placeholder = '0'; 211 | o.optional = true; 212 | 213 | o = s.taboption('amneziawg', form.Value, 'awg_jmax', _('Jmax'), _('Junk packet maximum size.')); 214 | o.datatype = 'uinteger'; 215 | o.placeholder = '0'; 216 | o.optional = true; 217 | 218 | o = s.taboption('amneziawg', form.Value, 'awg_s1', _('S1'), _('Handshake initiation packet junk header size.')); 219 | o.datatype = 'uinteger'; 220 | o.placeholder = '0'; 221 | o.optional = true; 222 | 223 | o = s.taboption('amneziawg', form.Value, 'awg_s2', _('S2'), _('Handshake response packet junk header size.')); 224 | o.datatype = 'uinteger'; 225 | o.placeholder = '0'; 226 | o.optional = true; 227 | 228 | o = s.taboption('amneziawg', form.Value, 'awg_s3', _('S3'), _('Cookie reply packet junk header size.')); 229 | o.datatype = 'uinteger'; 230 | o.placeholder = '0'; 231 | o.optional = true; 232 | 233 | o = s.taboption('amneziawg', form.Value, 'awg_s4', _('S4'), _('Transport packet junk header size.')); 234 | o.datatype = 'uinteger'; 235 | o.placeholder = '0'; 236 | o.optional = true; 237 | 238 | o = s.taboption('amneziawg', form.Value, 'awg_h1', _('H1'), _('Handshake initiation packet type header.')); 239 | o.datatype = 'string'; 240 | o.placeholder = '1'; 241 | o.optional = true; 242 | 243 | o = s.taboption('amneziawg', form.Value, 'awg_h2', _('H2'), _('Handshake response packet type header.')); 244 | o.datatype = 'string'; 245 | o.placeholder = '2'; 246 | o.optional = true; 247 | 248 | o = s.taboption('amneziawg', form.Value, 'awg_h3', _('H3'), _('Handshake cookie packet type header.')); 249 | o.datatype = 'string'; 250 | o.placeholder = '3'; 251 | o.optional = true; 252 | 253 | o = s.taboption('amneziawg', form.Value, 'awg_h4', _('H4'), _('Transport packet type header.')); 254 | o.datatype = 'string'; 255 | o.placeholder = '4'; 256 | o.optional = true; 257 | 258 | o = s.taboption('amneziawg', form.Value, 'awg_i1', _('I1'), _('First special junk packet signature.')); 259 | o.datatype = 'string'; 260 | o.optional = true; 261 | 262 | o = s.taboption('amneziawg', form.Value, 'awg_i2', _('I2'), _('Second special junk packet signature.')); 263 | o.datatype = 'string'; 264 | o.optional = true; 265 | 266 | o = s.taboption('amneziawg', form.Value, 'awg_i3', _('I3'), _('Third special junk packet signature.')); 267 | o.datatype = 'string'; 268 | o.optional = true; 269 | 270 | o = s.taboption('amneziawg', form.Value, 'awg_i4', _('I4'), _('Fourth special junk packet signature.')); 271 | o.datatype = 'string'; 272 | o.optional = true; 273 | 274 | o = s.taboption('amneziawg', form.Value, 'awg_i5', _('I5'), _('Fifth special junk packet signature.')); 275 | o.datatype = 'string'; 276 | o.optional = true; 277 | 278 | // -- peers ----------------------------------------------------------------------- 279 | 280 | try { 281 | s.tab('peers', _('Peers'), _('Further information about AmneziaWG interfaces and peers at amnezia.org.')); 282 | } 283 | catch(e) {} 284 | 285 | o = s.taboption('peers', form.SectionValue, '_peers', form.GridSection, 'amneziawg_%s'.format(s.section)); 286 | o.depends('proto', 'amneziawg'); 287 | 288 | ss = o.subsection; 289 | ss.anonymous = true; 290 | ss.addremove = true; 291 | ss.addbtntitle = _('Add peer'); 292 | ss.nodescriptions = true; 293 | ss.modaltitle = _('Edit peer'); 294 | ss.sortable = true; 295 | 296 | ss.handleDragConfig = function(ev) { 297 | ev.stopPropagation(); 298 | ev.preventDefault(); 299 | ev.dataTransfer.dropEffect = 'copy'; 300 | }; 301 | 302 | ss.handleDropConfig = function(mode, ev) { 303 | var file = ev.dataTransfer.files[0], 304 | nodes = ev.currentTarget, 305 | input = nodes.querySelector('textarea'), 306 | reader = new FileReader(); 307 | 308 | if (file) { 309 | reader.onload = function(rev) { 310 | input.value = rev.target.result.trim(); 311 | ss.handleApplyConfig(mode, nodes, file.name, ev); 312 | }; 313 | 314 | reader.readAsText(file); 315 | } 316 | 317 | ev.stopPropagation(); 318 | ev.preventDefault(); 319 | }; 320 | 321 | ss.parseConfig = function(data) { 322 | var lines = String(data).split(/(\r?\n)+/), 323 | section = null, 324 | config = { peers: [] }, 325 | s; 326 | 327 | for (var i = 0; i < lines.length; i++) { 328 | var line = lines[i].replace(/#.*$/, '').trim(); 329 | 330 | if (line.match(/^\[(\w+)\]$/)) { 331 | section = RegExp.$1.toLowerCase(); 332 | 333 | if (section == 'peer') 334 | config.peers.push(s = {}); 335 | else 336 | s = config; 337 | } 338 | else if (section && line.match(/^(\w+)\s*=\s*(.+)$/)) { 339 | var key = RegExp.$1, 340 | val = RegExp.$2.trim(); 341 | 342 | if (val.length) 343 | s[section + '_' + key.toLowerCase()] = val; 344 | } 345 | } 346 | 347 | if (config.interface_address) { 348 | config.interface_address = config.interface_address.split(/[, ]+/); 349 | 350 | for (var i = 0; i < config.interface_address.length; i++) 351 | if (!stubValidator.apply('ipaddr', config.interface_address[i])) 352 | return _('Address setting is invalid'); 353 | } 354 | 355 | if (config.interface_dns) { 356 | config.interface_dns = config.interface_dns.split(/[, ]+/); 357 | 358 | for (var i = 0; i < config.interface_dns.length; i++) 359 | if (!stubValidator.apply('ipaddr', config.interface_dns[i], ['nomask'])) 360 | return _('DNS setting is invalid'); 361 | } 362 | 363 | if (!config.interface_privatekey || validateBase64(null, config.interface_privatekey) !== true) 364 | return _('PrivateKey setting is missing or invalid'); 365 | 366 | if (!stubValidator.apply('port', config.interface_listenport || '0')) 367 | return _('ListenPort setting is invalid'); 368 | 369 | for (var i = 0; i < config.peers.length; i++) { 370 | var pconf = config.peers[i]; 371 | 372 | if (pconf.peer_publickey != null && validateBase64(null, pconf.peer_publickey) !== true) 373 | return _('PublicKey setting is invalid'); 374 | 375 | if (pconf.peer_presharedkey != null && validateBase64(null, pconf.peer_presharedkey) !== true) 376 | return _('PresharedKey setting is invalid'); 377 | 378 | if (pconf.peer_allowedips) { 379 | pconf.peer_allowedips = pconf.peer_allowedips.split(/[, ]+/); 380 | 381 | for (var j = 0; j < pconf.peer_allowedips.length; j++) 382 | if (!stubValidator.apply('ipaddr', pconf.peer_allowedips[j])) 383 | return _('AllowedIPs setting is invalid'); 384 | } 385 | else { 386 | pconf.peer_allowedips = [ '0.0.0.0/0', '::/0' ]; 387 | } 388 | 389 | if (pconf.peer_endpoint) { 390 | var host_port = pconf.peer_endpoint.match(/^\[([a-fA-F0-9:]+)\]:(\d+)$/) || pconf.peer_endpoint.match(/^(.+):(\d+)$/); 391 | 392 | if (!host_port || !stubValidator.apply('host', host_port[1]) || !stubValidator.apply('port', host_port[2])) 393 | return _('Endpoint setting is invalid'); 394 | 395 | pconf.peer_endpoint = [ host_port[1], host_port[2] ]; 396 | } 397 | 398 | if (pconf.peer_persistentkeepalive == 'off' || pconf.peer_persistentkeepalive == '0') 399 | delete pconf.peer_persistentkeepalive; 400 | 401 | if (!stubValidator.apply('port', pconf.peer_persistentkeepalive || '0')) 402 | return _('PersistentKeepAlive setting is invalid'); 403 | } 404 | 405 | return config; 406 | }; 407 | 408 | ss.handleApplyConfig = function(mode, nodes, comment, ev) { 409 | var input = nodes.querySelector('textarea').value, 410 | error = nodes.querySelector('.alert-message'), 411 | cancel = nodes.nextElementSibling.querySelector('.btn'), 412 | config = this.parseConfig(input); 413 | 414 | if (typeof(config) == 'string') { 415 | error.firstChild.data = _('Cannot parse configuration: %s').format(config); 416 | error.style.display = 'block'; 417 | return; 418 | } 419 | 420 | if (mode == 'full') { 421 | var prv = s.formvalue(s.section, 'private_key'); 422 | 423 | if (prv && prv != config.interface_privatekey && !confirm(_('Overwrite the current settings with the imported configuration?'))) 424 | return; 425 | 426 | return getPublicAndPrivateKeyFromPrivate(config.interface_privatekey).then(function(keypair) { 427 | s.getOption('private_key').getUIElement(s.section).setValue(keypair.priv); 428 | s.getOption('public_key').getUIElement(s.section).setValue(keypair.pub); 429 | s.getOption('listen_port').getUIElement(s.section).setValue(config.interface_listenport || ''); 430 | s.getOption('addresses').getUIElement(s.section).setValue(config.interface_address); 431 | s.getOption('awg_jc').getUIElement(s.section).setValue(config.interface_jc || ''); 432 | s.getOption('awg_jmin').getUIElement(s.section).setValue(config.interface_jmin || ''); 433 | s.getOption('awg_jmax').getUIElement(s.section).setValue(config.interface_jmax || ''); 434 | s.getOption('awg_s1').getUIElement(s.section).setValue(config.interface_s1 || ''); 435 | s.getOption('awg_s2').getUIElement(s.section).setValue(config.interface_s2 || ''); 436 | s.getOption('awg_s3').getUIElement(s.section).setValue(config.interface_s3 || ''); 437 | s.getOption('awg_s4').getUIElement(s.section).setValue(config.interface_s4 || ''); 438 | s.getOption('awg_h1').getUIElement(s.section).setValue(config.interface_h1 || ''); 439 | s.getOption('awg_h2').getUIElement(s.section).setValue(config.interface_h2 || ''); 440 | s.getOption('awg_h3').getUIElement(s.section).setValue(config.interface_h3 || ''); 441 | s.getOption('awg_h4').getUIElement(s.section).setValue(config.interface_h4 || ''); 442 | s.getOption('awg_i1').getUIElement(s.section).setValue(config.interface_i1 || ''); 443 | s.getOption('awg_i2').getUIElement(s.section).setValue(config.interface_i2 || ''); 444 | s.getOption('awg_i3').getUIElement(s.section).setValue(config.interface_i3 || ''); 445 | s.getOption('awg_i4').getUIElement(s.section).setValue(config.interface_i4 || ''); 446 | s.getOption('awg_i5').getUIElement(s.section).setValue(config.interface_i5 || ''); 447 | 448 | if (config.interface_dns) 449 | s.getOption('dns').getUIElement(s.section).setValue(config.interface_dns); 450 | 451 | for (var i = 0; i < config.peers.length; i++) { 452 | var pconf = config.peers[i]; 453 | var sid = uci.add('network', 'amneziawg_' + s.section); 454 | 455 | uci.sections('network', 'amneziawg_' + s.section, function(peer) { 456 | if (peer.public_key == pconf.peer_publickey) 457 | uci.remove('network', peer['.name']); 458 | }); 459 | 460 | uci.set('network', sid, 'description', comment || _('Imported peer configuration')); 461 | uci.set('network', sid, 'public_key', pconf.peer_publickey); 462 | uci.set('network', sid, 'preshared_key', pconf.peer_presharedkey); 463 | uci.set('network', sid, 'allowed_ips', pconf.peer_allowedips); 464 | uci.set('network', sid, 'persistent_keepalive', pconf.peer_persistentkeepalive); 465 | 466 | if (pconf.peer_endpoint) { 467 | uci.set('network', sid, 'endpoint_host', pconf.peer_endpoint[0]); 468 | uci.set('network', sid, 'endpoint_port', pconf.peer_endpoint[1]); 469 | } 470 | } 471 | 472 | return s.map.save(null, true); 473 | }).then(function() { 474 | cancel.click(); 475 | }); 476 | } 477 | else { 478 | return getPublicAndPrivateKeyFromPrivate(config.interface_privatekey).then(function(keypair) { 479 | var sid = uci.add('network', 'amneziawg_' + s.section); 480 | var pub = s.formvalue(s.section, 'public_key'); 481 | 482 | uci.sections('network', 'amneziawg_' + s.section, function(peer) { 483 | if (peer.public_key == keypair.pub) 484 | uci.remove('network', peer['.name']); 485 | }); 486 | 487 | uci.set('network', sid, 'description', comment || _('Imported peer configuration')); 488 | uci.set('network', sid, 'public_key', keypair.pub); 489 | uci.set('network', sid, 'private_key', keypair.priv); 490 | 491 | for (var i = 0; i < config.peers.length; i++) { 492 | var pconf = config.peers[i]; 493 | 494 | if (pconf.peer_publickey == pub) { 495 | uci.set('network', sid, 'preshared_key', pconf.peer_presharedkey); 496 | uci.set('network', sid, 'allowed_ips', pconf.peer_allowedips); 497 | uci.set('network', sid, 'persistent_keepalive', pconf.peer_persistentkeepalive); 498 | break; 499 | } 500 | } 501 | 502 | return s.map.save(null, true); 503 | }).then(function() { 504 | cancel.click(); 505 | }); 506 | } 507 | }; 508 | 509 | ss.handleConfigImport = function(mode) { 510 | var mapNode = ss.getActiveModalMap(), 511 | headNode = mapNode.parentNode.querySelector('h4'), 512 | parent = this.map; 513 | 514 | var nodes = E('div', { 515 | 'dragover': this.handleDragConfig, 516 | 'drop': this.handleDropConfig.bind(this, mode) 517 | }, [ 518 | E([], (mode == 'full') ? [ 519 | E('p', _('Drag or paste a valid *.conf file below to configure the local AmneziaWG interface.')) 520 | ] : [ 521 | E('p', _('Paste or drag a AmneziaWG configuration (commonly wg0.conf) from another system below to create a matching peer entry allowing that system to connect to the local AmneziaWG interface.')), 522 | E('p', _('To configure fully the local AmneziaWG interface from an existing (e.g. provider supplied) configuration file, use the configuration import instead.')) 523 | ]), 524 | E('p', [ 525 | E('textarea', { 526 | 'placeholder': (mode == 'full') 527 | ? _('Paste or drag supplied AmneziaWG configuration file…') 528 | : _('Paste or drag AmneziaWG peer configuration (wg0.conf) file…'), 529 | 'style': 'height:5em;width:100%; white-space:pre' 530 | }) 531 | ]), 532 | E('div', { 533 | 'class': 'alert-message', 534 | 'style': 'display:none' 535 | }, ['']) 536 | ]); 537 | 538 | var cancelFn = function() { 539 | nodes.parentNode.removeChild(nodes.nextSibling); 540 | nodes.parentNode.removeChild(nodes); 541 | mapNode.classList.remove('hidden'); 542 | mapNode.nextSibling.classList.remove('hidden'); 543 | headNode.removeChild(headNode.lastChild); 544 | window.removeEventListener('dragover', handleWindowDragDropIgnore); 545 | window.removeEventListener('drop', handleWindowDragDropIgnore); 546 | }; 547 | 548 | var a = nodes.querySelector('a.full-import'); 549 | 550 | if (a) { 551 | a.addEventListener('click', ui.createHandlerFn(this, function(mode) { 552 | cancelFn(); 553 | this.handleConfigImport('full'); 554 | })); 555 | } 556 | 557 | mapNode.classList.add('hidden'); 558 | mapNode.nextElementSibling.classList.add('hidden'); 559 | 560 | headNode.appendChild(E('span', [ ' » ', (mode == 'full') ? _('Import configuration') : _('Import as peer') ])); 561 | mapNode.parentNode.appendChild(E([], [ 562 | nodes, 563 | E('div', { 564 | 'class': 'right' 565 | }, [ 566 | E('button', { 567 | 'class': 'btn', 568 | 'click': cancelFn 569 | }, [ _('Cancel') ]), 570 | ' ', 571 | E('button', { 572 | 'class': 'btn primary', 573 | 'click': ui.createHandlerFn(this, 'handleApplyConfig', mode, nodes, null) 574 | }, [ _('Import settings') ]) 575 | ]) 576 | ])); 577 | 578 | window.addEventListener('dragover', handleWindowDragDropIgnore); 579 | window.addEventListener('drop', handleWindowDragDropIgnore); 580 | }; 581 | 582 | ss.renderSectionAdd = function(/* ... */) { 583 | var nodes = this.super('renderSectionAdd', arguments); 584 | 585 | nodes.appendChild(E('button', { 586 | 'class': 'btn', 587 | 'click': ui.createHandlerFn(this, 'handleConfigImport', 'peer') 588 | }, [ _('Import configuration as peer…') ])); 589 | 590 | return nodes; 591 | }; 592 | 593 | ss.renderSectionPlaceholder = function() { 594 | return E('em', _('No peers defined yet.')); 595 | }; 596 | 597 | o = ss.option(form.Flag, 'disabled', _('Disabled'), _('Enable / Disable peer. Restart amneziawg interface to apply changes.')); 598 | o.editable = true; 599 | o.optional = true; 600 | o.width = '5%'; 601 | 602 | o = ss.option(form.Value, 'description', _('Description'), _('Optional. Description of peer.')); 603 | o.placeholder = 'My Peer'; 604 | o.datatype = 'string'; 605 | o.optional = true; 606 | o.width = '30%'; 607 | o.textvalue = function(section_id) { 608 | var dis = ss.getOption('disabled'), 609 | pub = ss.getOption('public_key'), 610 | prv = ss.getOption('private_key'), 611 | psk = ss.getOption('preshared_key'), 612 | name = this.cfgvalue(section_id), 613 | key = pub.cfgvalue(section_id); 614 | 615 | var desc = [ 616 | E('p', [ 617 | name ? E('span', [ name ]) : E('em', [ _('Untitled peer') ]) 618 | ]) 619 | ]; 620 | 621 | if (dis.cfgvalue(section_id) == '1') 622 | desc.push(E('span', { 623 | 'class': 'ifacebadge', 624 | 'data-tooltip': _('AmneziaWG peer is disabled') 625 | }, [ 626 | E('em', [ _('Disabled', 'Label indicating that AmneziaWG peer is disabled') ]) 627 | ]), ' '); 628 | 629 | if (!key || !pub.isValid(section_id)) { 630 | desc.push(E('span', { 631 | 'class': 'ifacebadge', 632 | 'data-tooltip': _('Public key is missing') 633 | }, [ 634 | E('em', [ _('Key missing', 'Label indicating that AmneziaWG peer lacks public key') ]) 635 | ])); 636 | } 637 | else { 638 | desc.push( 639 | E('span', { 640 | 'class': 'ifacebadge', 641 | 'data-tooltip': _('Public key: %h', 'Tooltip displaying full AmneziaWG peer public key').format(key) 642 | }, [ 643 | E('code', [ key.replace(/^(.{5}).+(.{6})$/, '$1…$2') ]) 644 | ]), 645 | ' ', 646 | (prv.cfgvalue(section_id) && prv.isValid(section_id)) 647 | ? E('span', { 648 | 'class': 'ifacebadge', 649 | 'data-tooltip': _('Private key present') 650 | }, [ _('Private', 'Label indicating that AmneziaWG peer private key is stored') ]) : '', 651 | ' ', 652 | (psk.cfgvalue(section_id) && psk.isValid(section_id)) 653 | ? E('span', { 654 | 'class': 'ifacebadge', 655 | 'data-tooltip': _('Preshared key in use') 656 | }, [ _('PSK', 'Label indicating that AmneziaWG peer uses a PSK') ]) : '' 657 | ); 658 | } 659 | 660 | return E([], desc); 661 | }; 662 | 663 | function handleKeyChange(ev, section_id, value) { 664 | var prv = this.section.getUIElement(section_id, 'private_key'), 665 | btn = this.map.findElement('.btn.qr-code'); 666 | 667 | btn.disabled = (!prv.isValid() || !prv.getValue()); 668 | } 669 | 670 | o = ss.option(form.Value, 'public_key', _('Public Key'), _('Required. Public key of the AmneziaWG peer.')); 671 | o.modalonly = true; 672 | o.validate = validateBase64; 673 | o.onchange = handleKeyChange; 674 | 675 | o = ss.option(form.Value, 'private_key', _('Private Key'), _('Optional. Private key of the AmneziaWG peer. The key is not required for establishing a connection but allows generating a peer configuration or QR code if available. It can be removed after the configuration has been exported.')); 676 | o.modalonly = true; 677 | o.validate = validateBase64; 678 | o.onchange = handleKeyChange; 679 | o.password = true; 680 | 681 | o = ss.option(cbiKeyPairGenerate, '_gen_peer_keypair', ' '); 682 | o.modalonly = true; 683 | 684 | o = ss.option(form.Value, 'preshared_key', _('Preshared Key'), _('Optional. Base64-encoded preshared key. Adds in an additional layer of symmetric-key cryptography for post-quantum resistance.')); 685 | o.modalonly = true; 686 | o.validate = validateBase64; 687 | o.password = true; 688 | 689 | o = ss.option(form.DummyValue, '_gen_psk', ' '); 690 | o.modalonly = true; 691 | o.cfgvalue = function(section_id, value) { 692 | return E('button', { 693 | 'class': 'btn', 694 | 'click': ui.createHandlerFn(this, function(section_id, ev) { 695 | var psk = this.section.getUIElement(section_id, 'preshared_key'), 696 | map = this.map; 697 | 698 | return generatePsk().then(function(key) { 699 | psk.setValue(key); 700 | map.save(null, true); 701 | }); 702 | }, section_id) 703 | }, [ _('Generate preshared key') ]); 704 | }; 705 | 706 | o = ss.option(form.DynamicList, 'allowed_ips', _('Allowed IPs'), _("Optional. IP addresses and prefixes that this peer is allowed to use inside the tunnel. Usually the peer's tunnel IP addresses and the networks the peer routes through the tunnel.")); 707 | o.datatype = 'ipaddr'; 708 | o.textvalue = function(section_id) { 709 | var ips = L.toArray(this.cfgvalue(section_id)), 710 | list = []; 711 | 712 | for (var i = 0; i < ips.length; i++) { 713 | if (i > 7) { 714 | list.push(E('em', { 715 | 'class': 'ifacebadge cbi-tooltip-container' 716 | }, [ 717 | _('+ %d more', 'Label indicating further amount of allowed ips').format(ips.length - i), 718 | E('span', { 719 | 'class': 'cbi-tooltip' 720 | }, [ 721 | E('ul', ips.map(function(ip) { 722 | return E('li', [ 723 | E('span', { 'class': 'ifacebadge' }, [ ip ]) 724 | ]); 725 | })) 726 | ]) 727 | ])); 728 | 729 | break; 730 | } 731 | 732 | list.push(E('span', { 'class': 'ifacebadge' }, [ ips[i] ])); 733 | } 734 | 735 | if (!list.length) 736 | list.push('*'); 737 | 738 | return E('span', { 'style': 'display:inline-flex;flex-wrap:wrap;gap:.125em' }, list); 739 | }; 740 | 741 | o = ss.option(form.Flag, 'route_allowed_ips', _('Route Allowed IPs'), _('Optional. Create routes for Allowed IPs for this peer.')); 742 | o.modalonly = true; 743 | 744 | o = ss.option(form.Value, 'endpoint_host', _('Endpoint Host'), _('Optional. Host of peer. Names are resolved prior to bringing up the interface.')); 745 | o.placeholder = 'vpn.example.com'; 746 | o.datatype = 'host'; 747 | o.textvalue = function(section_id) { 748 | var host = this.cfgvalue(section_id), 749 | port = this.section.cfgvalue(section_id, 'endpoint_port'); 750 | 751 | return (host && port) 752 | ? '%h:%d'.format(host, port) 753 | : (host 754 | ? '%h:*'.format(host) 755 | : (port 756 | ? '*:%d'.format(port) 757 | : '*')); 758 | }; 759 | 760 | o = ss.option(form.Value, 'endpoint_port', _('Endpoint Port'), _('Optional. Port of peer.')); 761 | o.modalonly = true; 762 | o.placeholder = '51820'; 763 | o.datatype = 'port'; 764 | 765 | o = ss.option(form.Value, 'persistent_keepalive', _('Persistent Keep Alive'), _('Optional. Seconds between keep alive messages. Default is 0 (disabled). Recommended value if this device is behind a NAT is 25.')); 766 | o.modalonly = true; 767 | o.datatype = 'range(0,65535)'; 768 | o.placeholder = '0'; 769 | 770 | 771 | 772 | o = ss.option(form.DummyValue, '_keyops', _('Configuration Export'), 773 | _('Generates a configuration suitable for import on a AmneziaWG peer')); 774 | 775 | o.modalonly = true; 776 | 777 | o.createPeerConfig = function(section_id, endpoint, ips, eips, dns) { 778 | var pub = s.formvalue(s.section, 'public_key'), 779 | port = s.formvalue(s.section, 'listen_port') || '51820', 780 | jc = s.formvalue(s.section, 'awg_jc'), 781 | jmin = s.formvalue(s.section, 'awg_jmin'), 782 | jmax = s.formvalue(s.section, 'awg_jmax'), 783 | s1 = s.formvalue(s.section, 'awg_s1'), 784 | s2 = s.formvalue(s.section, 'awg_s2'), 785 | s3 = s.formvalue(s.section, 'awg_s3'), 786 | s4 = s.formvalue(s.section, 'awg_s4'), 787 | h1 = s.formvalue(s.section, 'awg_h1'), 788 | h2 = s.formvalue(s.section, 'awg_h2'), 789 | h3 = s.formvalue(s.section, 'awg_h3'), 790 | h4 = s.formvalue(s.section, 'awg_h4'), 791 | i1 = s.formvalue(s.section, 'awg_i1'), 792 | i2 = s.formvalue(s.section, 'awg_i2'), 793 | i3 = s.formvalue(s.section, 'awg_i3'), 794 | i4 = s.formvalue(s.section, 'awg_i4'), 795 | i5 = s.formvalue(s.section, 'awg_i5'), 796 | prv = this.section.formvalue(section_id, 'private_key'), 797 | psk = this.section.formvalue(section_id, 'preshared_key'), 798 | eport = this.section.formvalue(section_id, 'endpoint_port'), 799 | keep = this.section.formvalue(section_id, 'persistent_keepalive'); 800 | 801 | // If endpoint is IPv6 we must escape it with [] 802 | if (endpoint.indexOf(':') > 0) { 803 | endpoint = '['+endpoint+']'; 804 | } 805 | 806 | return [ 807 | '[Interface]', 808 | 'PrivateKey = ' + prv, 809 | eips && eips.length ? 'Address = ' + eips.join(', ') : '# Address not defined', 810 | eport ? 'ListenPort = ' + eport : '# ListenPort not defined', 811 | dns && dns.length ? 'DNS = ' + dns.join(', ') : '# DNS not defined', 812 | jc ? 'Jc = ' + jc : '# Jc not defined', 813 | jmin ? 'Jmin = ' + jmin : '# Jmin not defined', 814 | jmax ? 'Jmax = ' + jmax : '# Jmax not defined', 815 | s1 ? 'S1 = ' + s1 : '# S1 not defined', 816 | s2 ? 'S2 = ' + s2 : '# S2 not defined', 817 | s3 ? 'S3 = ' + s3 : '# S3 not defined', 818 | s4 ? 'S4 = ' + s4 : '# S4 not defined', 819 | h1 ? 'H1 = ' + h1 : '# H1 not defined', 820 | h2 ? 'H2 = ' + h2 : '# H2 not defined', 821 | h3 ? 'H3 = ' + h3 : '# H3 not defined', 822 | h4 ? 'H4 = ' + h4 : '# H4 not defined', 823 | i1 ? 'I1 = ' + i1 : '# I1 not defined', 824 | i2 ? 'I2 = ' + i2 : '# I2 not defined', 825 | i3 ? 'I3 = ' + i3 : '# I3 not defined', 826 | i4 ? 'I4 = ' + i4 : '# I4 not defined', 827 | i5 ? 'I5 = ' + i5 : '# I5 not defined', 828 | '', 829 | '[Peer]', 830 | 'PublicKey = ' + pub, 831 | psk ? 'PresharedKey = ' + psk : '# PresharedKey not used', 832 | ips && ips.length ? 'AllowedIPs = ' + ips.join(', ') : '# AllowedIPs not defined', 833 | endpoint ? 'Endpoint = ' + endpoint + ':' + port : '# Endpoint not defined', 834 | keep ? 'PersistentKeepAlive = ' + keep : '# PersistentKeepAlive not defined' 835 | ].join('\n'); 836 | }; 837 | 838 | o.handleGenerateQR = function(section_id, ev) { 839 | var mapNode = ss.getActiveModalMap(), 840 | headNode = mapNode.parentNode.querySelector('h4'), 841 | configGenerator = this.createPeerConfig.bind(this, section_id), 842 | parent = this.map, 843 | eips = this.section.formvalue(section_id, 'allowed_ips'); 844 | 845 | return Promise.all([ 846 | network.getWANNetworks(), 847 | network.getWAN6Networks(), 848 | network.getNetwork('lan'), 849 | L.resolveDefault(uci.load('ddns')), 850 | L.resolveDefault(uci.load('system')), 851 | parent.save(null, true) 852 | ]).then(function(data) { 853 | var hostnames = []; 854 | 855 | uci.sections('ddns', 'service', function(s) { 856 | if (typeof(s?.lookup_host) == 'string' && s?.enabled == '1') 857 | hostnames.push(s.lookup_host); 858 | }); 859 | 860 | uci.sections('system', 'system', function(s) { 861 | if (typeof(s?.hostname) == 'string' && s?.hostname?.indexOf('.') > 0) 862 | hostnames.push(s.hostname); 863 | }); 864 | 865 | for (var i = 0; i < data[0].length; i++) 866 | hostnames.push.apply(hostnames, data[0][i].getIPAddrs().map(function(ip) { return ip.split('/')[0] })); 867 | 868 | for (var i = 0; i < data[1].length; i++) 869 | hostnames.push.apply(hostnames, data[1][i].getIP6Addrs().map(function(ip) { return ip.split('/')[0] })); 870 | 871 | var ips = [ '0.0.0.0/0', '::/0' ]; 872 | 873 | var dns = []; 874 | 875 | var lan = data[2]; 876 | if (lan) { 877 | var lanIp = lan.getIPAddr(); 878 | if (lanIp) { 879 | dns.unshift(lanIp) 880 | } 881 | } 882 | 883 | var qrm, qrs, qro; 884 | 885 | qrm = new form.JSONMap({ config: { endpoint: hostnames[0], allowed_ips: ips, addresses: eips, dns_servers: dns } }, null, _('The generated configuration can be imported into a WireGuard client application to set up a connection towards this device.')); 886 | qrm.parent = parent; 887 | 888 | qrs = qrm.section(form.NamedSection, 'config'); 889 | 890 | function handleConfigChange(ev, section_id, value) { 891 | var code = this.map.findElement('.qr-code'), 892 | conf = this.map.findElement('.client-config'), 893 | endpoint = this.section.getUIElement(section_id, 'endpoint'), 894 | ips = this.section.getUIElement(section_id, 'allowed_ips'); 895 | eips = this.section.getUIElement(section_id, 'addresses'); 896 | dns = this.section.getUIElement(section_id, 'dns_servers'); 897 | 898 | if (this.isValid(section_id)) { 899 | conf.firstChild.data = configGenerator(endpoint.getValue(), ips.getValue(), eips.getValue(), dns.getValue()); 900 | code.style.opacity = '.5'; 901 | 902 | buildSVGQRCode(conf.firstChild.data, code); 903 | } 904 | }; 905 | 906 | qro = qrs.option(form.Value, 'endpoint', _('Connection endpoint'), _('The public hostname or IP address of this system the peer should connect to. This usually is a static public IP address, a static hostname or a DDNS domain.')); 907 | qro.datatype = 'or(ipaddr,hostname)'; 908 | hostnames.forEach(function(hostname) { qro.value(hostname) }); 909 | qro.onchange = handleConfigChange; 910 | 911 | qro = qrs.option(form.DynamicList, 'allowed_ips', _('Allowed IPs'), _('IP addresses that are allowed inside the tunnel. The peer will accept tunnelled packets with source IP addresses matching this list and route back packets with matching destination IP.')); 912 | qro.datatype = 'ipaddr'; 913 | qro.default = ips; 914 | ips.forEach(function(ip) { qro.value(ip) }); 915 | qro.onchange = handleConfigChange; 916 | 917 | qro = qrs.option(form.DynamicList, 'dns_servers', _('DNS Servers'), _('DNS servers for the remote clients using this tunnel to your openwrt device. Some wireguard clients require this to be set.')); 918 | qro.datatype = 'ipaddr'; 919 | qro.default = dns; 920 | qro.onchange = handleConfigChange; 921 | 922 | qro = qrs.option(form.DynamicList, 'addresses', _('Addresses'), _('IP addresses for the peer to use inside the tunnel. Some clients require this setting.')); 923 | qro.datatype = 'ipaddr'; 924 | qro.default = eips; 925 | eips.forEach(function(eip) { qro.value(eip) }); 926 | qro.onchange = handleConfigChange; 927 | 928 | qro = qrs.option(form.DummyValue, 'output'); 929 | qro.renderWidget = function() { 930 | var peer_config = configGenerator(hostnames[0], ips, eips, dns); 931 | 932 | var node = E('div', { 933 | 'style': 'display:flex;flex-wrap:wrap;align-items:center;gap:.5em;width:100%' 934 | }, [ 935 | E('div', { 936 | 'class': 'qr-code', 937 | 'style': 'width:320px;flex:0 1 320px;text-align:center' 938 | }, [ 939 | E('em', { 'class': 'spinning' }, [ _('Generating QR code…') ]) 940 | ]), 941 | E('pre', { 942 | 'class': 'client-config', 943 | 'style': 'flex:1;white-space:pre;overflow:auto', 944 | 'click': function(ev) { 945 | var sel = window.getSelection(), 946 | range = document.createRange(); 947 | 948 | range.selectNodeContents(ev.currentTarget); 949 | 950 | sel.removeAllRanges(); 951 | sel.addRange(range); 952 | } 953 | }, [ peer_config ]) 954 | ]); 955 | 956 | buildSVGQRCode(peer_config, node.firstChild); 957 | 958 | return node; 959 | }; 960 | 961 | return qrm.render().then(function(nodes) { 962 | mapNode.classList.add('hidden'); 963 | mapNode.nextElementSibling.classList.add('hidden'); 964 | 965 | headNode.appendChild(E('span', [ ' » ', _('Generate configuration') ])); 966 | mapNode.parentNode.appendChild(E([], [ 967 | nodes, 968 | E('div', { 969 | 'class': 'right' 970 | }, [ 971 | E('button', { 972 | 'class': 'btn', 973 | 'click': function() { 974 | nodes.parentNode.removeChild(nodes.nextSibling); 975 | nodes.parentNode.removeChild(nodes); 976 | mapNode.classList.remove('hidden'); 977 | mapNode.nextSibling.classList.remove('hidden'); 978 | headNode.removeChild(headNode.lastChild); 979 | } 980 | }, [ _('Back to peer configuration') ]) 981 | ]) 982 | ])); 983 | 984 | if (!s.formvalue(s.section, 'listen_port')) { 985 | nodes.appendChild(E('div', { 'class': 'alert-message' }, [ 986 | E('p', [ 987 | _('No fixed interface listening port defined, peers might not be able to initiate connections to this AmneziaWG instance!') 988 | ]) 989 | ])); 990 | } 991 | }); 992 | }); 993 | }; 994 | 995 | o.cfgvalue = function(section_id, value) { 996 | var privkey = this.section.cfgvalue(section_id, 'private_key'); 997 | 998 | return E('button', { 999 | 'class': 'btn qr-code', 1000 | 'style': 'display:inline-flex;align-items:center;gap:.5em', 1001 | 'click': ui.createHandlerFn(this, 'handleGenerateQR', section_id), 1002 | 'disabled': privkey ? null : '' 1003 | }, [ 1004 | Object.assign(E(qrIcon), { style: 'width:22px;height:22px' }), 1005 | _('Generate configuration…') 1006 | ]); 1007 | }; 1008 | }, 1009 | 1010 | deleteConfiguration: function() { 1011 | uci.sections('network', 'amneziawg_%s'.format(this.sid), function(s) { 1012 | uci.remove('network', s['.name']); 1013 | }); 1014 | } 1015 | }); 1016 | --------------------------------------------------------------------------------