15 Commits

Author SHA1 Message Date
Bill Zimmerman
4cf1ca564b Use RaspAP/pi-gen-action@v1.11.0 2025-10-12 11:17:13 +02:00
Bill Zimmerman
29c1e7dda3 Merge branch 'RaspAP:master' into master 2025-10-08 08:41:03 +02:00
Bill Zimmerman
38ac6a12c6 Set pi-gen-repository, bookworm -> trixie
Set repository: RaspAP/pi-gen

Set release: trixie

release -> pi-gen-release: trixie

Update release.yml

Update release.yml

Update release.yml

Update release.yml

Update release.yml

Update release.yml

Update release.yml

Update release.yml

Update release.yml

Update matrix arch w/ base_image

Update w/ workflow_dispatch

Setup QEMU + Docker Buildx

Update release.yml

Define docker-platform in matrix

Set RaspAP/pi-gen-action@v1.12.0

Disable Buildx

Revert release.yml

Update release.yml

Update release.yml
2025-10-07 23:38:36 -07:00
Bill Zimmerman
ead886dffa Merge branch 'RaspAP:master' into master 2025-10-06 08:27:53 +02:00
Bill Zimmerman
c5f9c1593c Update release.yml 2025-08-21 18:59:55 +02:00
Bill Zimmerman
5cd39d6f0d Delete .github/workflows/torrent.yml 2025-08-21 17:03:04 +02:00
Bill Zimmerman
f0ceee0bcd Update release.yml 2025-08-21 17:02:47 +02:00
Bill Zimmerman
9db18fdefd Merge branch 'RaspAP:master' into master 2025-08-21 07:54:30 -07:00
Bill Zimmerman
7868c0d6c1 Create torrent.yml 2025-08-21 16:53:54 +02:00
Bill Zimmerman
e09e0590d7 Merge pull request #1935 from RaspAP/fix/custom-paths
Fix: Update class methods + js handler for custom path locations
2025-08-20 11:48:02 -07:00
Bill Zimmerman
771abe118e Merge pull request #1936 from RaspAP/fix/path-info-handling
Fix: Handle undefined PATH_INFO value
2025-08-20 11:47:35 -07:00
Bill Zimmerman
451c76afe8 Update torrent.yml 2025-08-20 18:58:33 +02:00
Bill Zimmerman
d21b1345bb Update torrent.yml 2025-08-20 18:50:43 +02:00
Bill Zimmerman
eca174a20b Create torrent.yml 2025-08-20 18:47:12 +02:00
billz
de9a3b1fc4 Update class methods + js handler for custom path locations 2025-08-19 17:17:27 -07:00
63 changed files with 2099 additions and 15804 deletions

View File

@@ -52,17 +52,13 @@ body:
attributes:
label: Operating System
options:
- Raspberry Pi OS Lite 64-bit Debian 13 (trixie)
- Raspberry Pi OS Lite 32-bit Debian 13 (trixie)
- Raspberry Pi OS Lite 64-bit Debian 12 (bookworm)
- Raspberry Pi OS Lite 32-bit Debian 12 (bookworm)
- Raspberry Pi OS Desktop 64-bit Debian 12 (bookworm)
- Raspberry Pi OS Lite 64-bit Debian 11 (bullseye)
- Raspberry Pi OS Lite 32-bit Debian 11 (bullseye)
- Kali Linux 2025.3 64-bit
- Kali Linux 2025.3 32-bit
- Armbian 23.11 (jammy)
- Debian 12 (bookworm)
- Raspberry Pi OS (64-bit) Lite Bookworm
- Raspberry Pi OS (32-bit) Lite Bookworm
- Raspberry Pi OS (64-bit) Desktop Bookwom
- Raspberry Pi OS (64-bit) Lite Bullseye
- Raspberry Pi OS (32-bit) Lite Bullseye
- Armbian 23.05 (Suni)
- Debian Bookworm
validations:
required: true
- type: dropdown

View File

@@ -29,7 +29,7 @@ jobs:
- name: Build RaspAP Image
id: build
uses: usimd/pi-gen-action@v1.11.0
uses: RaspAP/pi-gen-action@v1.11.0
with:
release: ${{ matrix.release }}
image-name: "raspap-${{ matrix.release }}-${{ matrix.arch == '32-bit' && 'armhf' || 'arm64' }}-lite-${{ github.event.inputs.tag || github.ref_name }}"
@@ -37,7 +37,7 @@ jobs:
stage-list: stage0 stage1 stage2 ./stage-raspap
verbose-output: true
pi-gen-version: ${{ matrix.pi_gen_version }}
pi-gen-repository: billz/pi-gen
pi-gen-repository: RaspAP/pi-gen
- name: Upload Artifact
uses: svenstaro/upload-release-action@v2

View File

@@ -1,59 +0,0 @@
name: Update OS List for RPi Imager
on:
workflow_dispatch:
release:
types: [published]
jobs:
update-os-list:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Get release information
id: release
run: |
if [ "${{ github.event_name }}" = "release" ]; then
VERSION="${{ github.event.release.tag_name }}"
RELEASE_DATE=$(echo "${{ github.event.release.published_at }}" | cut -d'T' -f1)
else
RESPONSE=$(curl -s "https://api.github.com/repos/${{ github.repository }}/releases/latest")
VERSION=$(echo "$RESPONSE" | jq -r '.tag_name')
RELEASE_DATE=$(echo "$RESPONSE" | jq -r '.published_at' | cut -d'T' -f1)
fi
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "release_date=$RELEASE_DATE" >> $GITHUB_OUTPUT
echo "Found release: $VERSION (published: $RELEASE_DATE)"
- name: Download release assets and generate OS list
env:
VERSION: ${{ steps.release.outputs.version }}
RELEASE_DATE: ${{ steps.release.outputs.release_date }}
GH_TOKEN: ${{ github.token }}
run: |
cd installers
echo "Downloading release assets for $VERSION..."
gh release download "$VERSION" \
--pattern "raspap-trixie-armhf-lite-*.img.zip" \
--pattern "raspap-trixie-arm64-lite-*.img.zip"
chmod +x ./generate_os_list.sh
./generate_os_list.sh
- name: Commit and push changes
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add installers/os-sublist-raspap.json
if git diff --staged --quiet; then
echo "No changes to commit"
else
git commit -m "Update OS list for RPi Imager (${{ steps.release.outputs.version }})"
git push
fi

View File

@@ -1,5 +1,5 @@
![RaspAP Hero](https://i.imgur.com/aNAG3Wa.jpeg)
[![Release 3.5.1](https://img.shields.io/badge/release-v3.5.1-green)](https://github.com/raspap/raspap-webgui/releases) [![Awesome](https://awesome.re/badge.svg)](https://github.com/thibmaek/awesome-raspberry-pi) [![Join Insiders](https://img.shields.io/static/v1?label=Insiders&message=%E2%9D%A4&logo=GitHub&color=ff69b4)](https://github.com/sponsors/RaspAP) [![Build Status](https://app.travis-ci.com/RaspAP/raspap-webgui.svg?branch=master)](https://app.travis-ci.com/RaspAP/raspap-webgui) [![Crowdin](https://badges.crowdin.net/raspap/localized.svg)](https://crowdin.com/project/raspap) [![Twitter URL](https://img.shields.io/twitter/url?label=%40RaspAP&logoColor=%23d8224c&url=https%3A%2F%2Ftwitter.com%2Frasp_ap)](https://twitter.com/rasp_ap) [![Reddit](https://img.shields.io/badge/%2Fr%2FRaspAP-e05d44?style=flat&logo=Reddit&logoColor=white&labelColor=e05d44&color=b14835)](https://reddit.com/r/RaspAP) [![Discord](https://img.shields.io/discord/642436993451819018?color=7289DA&label=Discord&logo=discord&style=flat)](https://discord.gg/KVAsaAR)
[![Release 3.4.3](https://img.shields.io/badge/release-v3.4.3-green)](https://github.com/raspap/raspap-webgui/releases) [![Awesome](https://awesome.re/badge.svg)](https://github.com/thibmaek/awesome-raspberry-pi) [![Join Insiders](https://img.shields.io/static/v1?label=Insiders&message=%E2%9D%A4&logo=GitHub&color=ff69b4)](https://github.com/sponsors/RaspAP) [![Build Status](https://app.travis-ci.com/RaspAP/raspap-webgui.svg?branch=master)](https://app.travis-ci.com/RaspAP/raspap-webgui) [![Crowdin](https://badges.crowdin.net/raspap/localized.svg)](https://crowdin.com/project/raspap) [![Twitter URL](https://img.shields.io/twitter/url?label=%40RaspAP&logoColor=%23d8224c&url=https%3A%2F%2Ftwitter.com%2Frasp_ap)](https://twitter.com/rasp_ap) [![Reddit](https://img.shields.io/badge/%2Fr%2FRaspAP-e05d44?style=flat&logo=Reddit&logoColor=white&labelColor=e05d44&color=b14835)](https://reddit.com/r/RaspAP) [![Discord](https://img.shields.io/discord/642436993451819018?color=7289DA&label=Discord&logo=discord&style=flat)](https://discord.gg/KVAsaAR)
RaspAP is feature-rich wireless router software that _just works_ on many popular [Debian-based devices](#supported-operating-systems), including the Raspberry Pi. Our [custom OS images](#pre-built-image), [Quick installer](#quick-installer) and [Docker container](#docker-support) create a known-good default configuration for all current Raspberry Pis with onboard wireless. A fully responsive, mobile-ready interface gives you control over the relevant services and networking options. Advanced DHCP settings, [WireGuard](https://docs.raspap.com/wireguard/), [Tailscale](https://docs.raspap.com/tailscale/) and [OpenVPN](https://docs.raspap.com/openvpn/) support, [SSL certificates](https://docs.raspap.com/ssl/), [ad blocking](#ad-blocking), security audits, [captive portal integration](https://docs.raspap.com/captive/), themes and [multilingual options](https://docs.raspap.com/translations/) are included.
@@ -41,10 +41,10 @@ RaspAP gives you two different ways to get up and running quickly. The simplest
### Pre-built image
Custom Raspberry Pi OS Lite images with the latest RaspAP are available for [direct download](https://github.com/RaspAP/raspap-webgui/releases/latest). This includes both 32- and 64-bit builds for ARM architectures.
| Operating system | Debian version | Kernel version | RaspAP version | Size |
| ------------ | -------------- | -------------- | -------------- | ---- |
| Raspberry Pi OS (64-bit) Lite | 13 (trixie) | 6.12 | Latest | 826 MB |
| Raspberry Pi OS (32-bit) Lite | 13 (trixie) | 6.12 | Latest | 799 MB |
| Operating system | Debian version | Kernel version | RaspAP version | Size |
| ---------------------| ---------------|-----------------|----------------|-------|
| Raspberry Pi OS (64-bit) Lite | 12 (bookworm) | 6.6 | Latest | 777 MB|
| Raspberry Pi OS (32-bit) Lite | 12 (bookworm) | 6.6 | Latest | 805 MB|
These images are automatically generated with each release of RaspAP. You may choose between an `arm64` or `armhf` (32-bit) based build. Refer to [this resource](https://www.raspberrypi.com/software/operating-systems/) to ensure compatibility with your hardware.
@@ -72,20 +72,19 @@ The Quick installer will respond to several [command line arguments](https://doc
### Initial settings
After completing either of these setup options, the wireless AP network will be configured as follows:
* IP address: `10.3.141.1`
* Username: `admin`
* Password: `secret`
* DHCP range: `10.3.141.50``10.3.141.254`
* SSID: `RaspAP`
* Password: `ChangeMe`
* IP address: 10.3.141.1
* Username: admin
* Password: secret
* DHCP range: 10.3.141.50 — 10.3.141.254
* SSID: `raspi-webgui`
* Password: ChangeMe
It's _strongly recommended_ that your first post-install action is to change the default admin [authentication](https://docs.raspap.com/authentication/) settings. Thereafter, your AP's [basic settings](https://docs.raspap.com/ap-basics/) and many [advanced options](https://docs.raspap.com/ap-basics#advanced-options) are now ready to be modified by RaspAP.
Please [read this](https://docs.raspap.com/issues/) before reporting an issue.
## Join Insiders
[<img src="https://github.com/user-attachments/assets/832f1f0d-517a-4d73-8b62-068cf1a2041d" width="320">](https://github.com/sponsors/RaspAP/)
[![](https://i.imgur.com/eml7k0b.png)](https://github.com/sponsors/RaspAP/)
RaspAP is free software, but powered by _your_ support. If you find RaspAP useful for your personal or commercial projects, [become an Insider](https://github.com/sponsors/RaspAP/) and get early access to [exclusive features](https://docs.raspap.com/insiders/#exclusive-features) in the [Insiders Edition](https://docs.raspap.com/insiders/).
@@ -93,15 +92,19 @@ A tangible side benefit of sponsorship is that **Insiders** are able to help _st
## WireGuard support
![](https://i.imgur.com/5YDv37e.png)
WireGuard® is an extremely simple yet fast and modern VPN that utilizes state-of-the-art cryptography. It aims to be considerably more performant than OpenVPN, and is generally regarded as the most secure, easiest to use, and simplest VPN solution for modern Linux distributions.
WireGuard is included in the pre-built OS and may be optionally installed by the [Quick Installer](https://docs.raspap.com/quick/). Once this is done, you can manage local (server) settings, create a peer configuration and control the `wg-quick` service with RaspAP.
WireGuard may be optionally installed by the [Quick Installer](https://docs.raspap.com/quick/). Once this is done, you can manage local (server) settings, create a peer configuration and control the `wg-quick` service with RaspAP.
Details are [provided here](https://docs.raspap.com/wireguard/).
## OpenVPN support
OpenVPN is included in the pre-built OS and may be optionally installed by the Quick Installer. Once this is done, you can [manage client configurations](https://docs.raspap.com/openvpn/) and the `openvpn-client` service with RaspAP.
![](https://i.imgur.com/ta7tCon.png)
OpenVPN may be optionally installed by the Quick Installer. Once this is done, you can [manage client configurations](https://docs.raspap.com/openvpn/) and the `openvpn-client` service with RaspAP.
To configure an OpenVPN client, upload a valid .ovpn file and, optionally, specify your login credentials. RaspAP will store your client configuration and add firewall rules to forward traffic from OpenVPN's `tun0` interface to your configured wireless interface.
@@ -114,40 +117,39 @@ Several popular VPN providers include a Linux Command Line Interface (CLI) for i
See our [VPN provider documentation](https://docs.raspap.com/providers/) for more information.
## Ad Blocking
This feature uses DNS blacklisting to block requests for ads, trackers and other undesirable hosts. Ad blocking is included in the pre-built OS and may be optionally installed by the [Quick Installer](https://docs.raspap.com/quick/). Thereafter, you may choose between several of the best available [blocklist sources](https://docs.raspap.com/features-core/adblock/#blocklist-sources) to suit your needs.
This feature uses DNS blacklisting to block requests for ads, trackers and other undesirable hosts. To enable ad blocking, simply respond to the prompt during the installation. As a beta release, we encourage testing and feedback from users of RaspAP.
Details are [provided here](https://docs.raspap.com/adblock/).
## Bridged AP
By default RaspAP configures a routed AP for your clients to connect to. A bridged AP configuration is also possible. Select the **Bridged AP mode** toggle under the **Advanced** tab of **Hotspot**, configure a static IP address for the bridge interface, then save and restart the AP.
By default RaspAP configures a routed AP for your clients to connect to. A bridged AP configuration is also possible. Slide the **Bridged AP mode** toggle under the **Advanced** tab of **Configure hotspot**, then save and restart the hotspot.
Details on Bridged AP mode are [provided here](https://docs.raspap.com/bridged/).
**Note:** In bridged mode, all routing capabilities are handled by your upstream router. Because your router assigns IP addresses to your device's hotspot and its clients, you might not be able to reach the RaspAP web interface from the default `10.3.141.1` address. Instead use your RPi's hostname followed by `.local` to access the RaspAP web interface. With Raspbian default settings, this should look like `raspberrypi.local`. Alternate methods are [discussed here](https://www.raspberrypi.org/documentation/remote-access/ip-address.md).
More information on Bridged AP mode is provided [in our documentation](https://docs.raspap.com/bridged/).
## Manual installation
Detailed manual setup instructions are [provided here](https://docs.raspap.com/manual/).
Detailed manual setup instructions are provided [on our documentation site](https://docs.raspap.com/manual/).
## 802.11ac 5GHz support
RaspAP provides an 802.11ac wireless mode option for supported hardware (currently the RPi 3B+, 4, 5 and compatible Orange Pi models) and wireless regulatory domains. See [this](https://docs.raspap.com/ap-basics/#80211ac-5-ghz) for more information.
RaspAP provides an 802.11ac wireless mode option for supported hardware (currently the RPi 3B+/4 and compatible Orange Pi models) and wireless regulatory domains. See [this](https://docs.raspap.com/ap-basics/#80211ac-5-ghz) for more information.
## Supported operating systems
RaspAP was originally made for Raspbian, but now also installs on the following Debian-based distros.
| Distribution | Release | Architecture | Support |
| ------------ | ------- | ------------ | ------- |
| Raspberry Pi OS Lite | 64-bit Debian 13 (trixie) | ARM | Official |
| Raspberry Pi OS Lite | 32-bit Debian 13 (trixie) | ARM | Official |
| Raspberry Pi OS Lite | 64-bit Debian 12 (bookworm) | ARM | Official |
| Raspberry Pi OS Lite | 32-bit Debian 12 (bookworm) | ARM | Official |
| Raspberry Pi OS Desktop | 64-bit Debian 12 (bookworm) | ARM | Official |
| Raspberry Pi OS Lite | 64-bit Debian 11 (bullseye) | ARM | Official |
| Raspberry Pi OS Lite | 32-bit Debian 11 (bullseye) | ARM | Official |
| Kali Linux | 2025.3 | [ARM 64-bit](https://www.kali.org/get-kali/#kali-arm) | Beta |
| Kali Linux | 2025.3 | [ARM 32-bit](https://www.kali.org/get-kali/#kali-arm) | Beta |
| Debian 13 | trixie | [ARM](https://raspi.debian.net/tested-images/) | Beta |
| Debian 12 | bookworm | [ARM](https://raspi.debian.net/tested-images/) | Beta |
| Armbian | 23.11 (jammy) | ARM | Beta |
| Distribution | Release | Architecture | Support |
|---|:---:|:---:|:---:|
| Raspberry Pi OS | (64-bit) Lite Trixie | ARM | Official |
| Raspberry Pi OS | (32-bit) Lite Trixie | ARM | Official |
| Raspberry Pi OS | (64-bit) Lite Bookworm | ARM | Official |
| Raspberry Pi OS | (32-bit) Lite Bookworm | ARM | Official |
| Raspberry Pi OS | (64-bit) Desktop Bookworm | ARM | Official |
| Raspberry Pi OS | (64-bit) Lite Bullseye | ARM | Official |
| Raspberry Pi OS | (32-bit) Lite Bullseye | ARM | Official |
| Armbian | 23.11 (Jammy) | [ARM](https://docs.armbian.com/#supported-socs) | Beta |
| Debian | Bookworm | ARM / x86_64 | Beta |
<img src="https://i.imgur.com/L27nH8f.png" style="width:540px;" />
<img src="https://i.imgur.com/XiAJNKb.png" style="width:480px;" />
You are also encouraged to use RaspAP's community-led [Docker container](#docker-support). Please note that "supported" is not a guarantee. If you are able to improve support for your preferred distro, we encourage you to [actively contribute](#how-to-contribute) to the project.
@@ -165,6 +167,8 @@ curl -sL https://install.raspap.com | bash -s -- --cert
More information on SSL certificates and HTTPS support is available [in our documentation](https://docs.raspap.com/ssl/).
## Docker support
<img src="https://github.com/RaspAP/raspap-webgui/assets/229399/dc40dfc4-e9b8-405f-8ffb-6c5f88482b8e" width="450">
As an alternative to the [Quick installer](#quick-installer), RaspAP may be run in an isolated, portable [Docker container](https://docs.raspap.com/docker/).
See the [RaspAP-docker repo](https://github.com/RaspAP/raspap-docker/) for more information.
@@ -173,9 +177,7 @@ See the [RaspAP-docker repo](https://github.com/RaspAP/raspap-docker/) for more
RaspAP's integrated `PluginManager` provides a framework for developers to create custom plugins. To facilitate this, a `SamplePlugin` [repository](https://github.com/RaspAP/SamplePlugin) is available to get developers started on the right track. If you'd like to develop your own plugin for RaspAP, see the [documentation](https://docs.raspap.com/custom-plugins/) or get started right away by forking the [SamplePlugin](https://github.com/RaspAP/SamplePlugin).
## Multilingual support
RaspAP uses [GNU Gettext](https://www.gnu.org/software/gettext/) to manage multilingual messages. Our pre-built OS includes the `locales-all` package, eliminating the need to manually generate locales.
If you're using the Quick Installer or Manual setup methods, you must configure a corresponding language package for your system. To list languages currently installed on your system, use `locale -a` at the shell prompt. To generate new locales, run `sudo dpkg-reconfigure locales` and select any other desired locales. Details are provided [here](https://docs.raspap.com/translations/).
RaspAP uses [GNU Gettext](https://www.gnu.org/software/gettext/) to manage multilingual messages. In order to use RaspAP with one of our supported translations, you must configure a corresponding language package on your RPi. To list languages currently installed on your system, use `locale -a` at the shell prompt. To generate new locales, run `sudo dpkg-reconfigure locales` and select any other desired locales. Details are provided on our [documentation site](https://docs.raspap.com/translations/).
See this list of [supported languages](https://docs.raspap.com/translations/#supported-languages) that are actively maintained by volunteer translators. If your language is not supported, why not [contribute a translation](https://docs.raspap.com/translations/#contributing-to-a-translation)? Contributors will receive credit as the original translators.

View File

@@ -13,9 +13,7 @@ foreach ($hostapdconfig as $hostapdconfigline) {
continue;
}
$arrLine = explode("=", $hostapdconfigline);
if (count($arrLine) >= 2) {
$arrConfig[$arrLine[0]]=$arrLine[1];
}
$arrConfig[$arrLine[0]]=$arrLine[1];
};
$channel = intval($arrConfig['channel']);
echo json_encode($channel);

View File

@@ -1,8 +1,5 @@
<?php
header("Content-Type: image/svg+xml");
require_once '../../includes/functions.php';
$color = getColorOpt();
$showDevice1 = isset($_GET['device-1']);
$showOut = isset($_GET['out']);
$showDevice2 = isset($_GET['device-2']);
@@ -15,33 +12,33 @@ $showDevice2 = isset($_GET['device-2']);
<?php if ($showDevice2): ?>
<line id="joint-device-2" y1="-0.75" x2="154" y2="-0.75"
transform="matrix(4.37114e-08 1 1 -4.37114e-08 114 297)"
stroke="<?php echo htmlspecialchars($color, ENT_QUOTES, 'UTF-8'); ?>" stroke-width="4"/>
stroke="#008281" stroke-width="4"/>
<?php endif; ?>
<?php if ($showDevice1): ?>
<line id="joint-device-1" style="display: inline;"
y1="-0.75" x2="154" y2="-0.75"
transform="matrix(4.37114e-08 1 1 -4.37114e-08 114 144)"
stroke="<?php echo htmlspecialchars($color, ENT_QUOTES, 'UTF-8'); ?>" stroke-width="4"/>
stroke="#008281" stroke-width="4"/>
<line id="device-1" style="display: inline;"
y1="-0.75" x2="113.231" y2="-0.75"
transform="matrix(1 8.74228e-08 8.74228e-08 -1 114 144)"
stroke="<?php echo htmlspecialchars($color, ENT_QUOTES, 'UTF-8'); ?>" stroke-width="4"/>
stroke="#008281" stroke-width="4"/>
<?php endif; ?>
<?php if ($showOut): ?>
<line id="out" style="display: inline;"
y1="-0.75" x2="113.231" y2="-0.75"
transform="matrix(1 8.74228e-08 8.74228e-08 -1 -0.000305176 297)"
stroke="<?php echo htmlspecialchars($color, ENT_QUOTES, 'UTF-8'); ?>" stroke-width="4"/>
stroke="#008281" stroke-width="4"/>
<?php endif; ?>
<?php if ($showDevice2): ?>
<line id="device-2" style="display: inline;"
y1="-0.75" x2="113.231" y2="-0.75"
transform="matrix(1 8.74228e-08 8.74228e-08 -1 113 450)"
stroke="<?php echo htmlspecialchars($color, ENT_QUOTES, 'UTF-8'); ?>" stroke-width="4"/>
stroke="#008281" stroke-width="4"/>
<?php endif; ?>
</g>
</g>

View File

@@ -12,12 +12,6 @@ if (!isset($_SERVER['HTTP_REFERER'])) {
$hostapd = parse_ini_file(RASPI_HOSTAPD_CONFIG, false, INI_SCANNER_RAW);
// handle parse failure
if ($hostapd === false) {
header('HTTP/1.0 500 Internal Server Error');
exit('Error: Unable to parse hostapd configuration');
}
// assume WPA encryption and get the passphrase
$type = "WPA";
$password = isset($hostapd['wpa_psk']) ? $hostapd['wpa_psk'] : $hostapd['wpa_passphrase'];

View File

@@ -381,15 +381,12 @@ function loadChannelSelect(selected) {
// Map selected hw_mode to available channels
if (hw_mode === 'a') {
selectableChannels = data.filter(item => item.MHz.toString().startsWith('5'));
} else if (hw_mode !== 'ac') {
selectableChannels = data.filter(item => item.MHz.toString().startsWith('24'));
} else if (hw_mode === 'b') {
selectableChannels = data.filter(item => item.MHz.toString().startsWith('24'));
} else if (hw_mode === 'ac') {
selectableChannels = data.filter(item => item.MHz.toString().startsWith('5'));
} else if (hw_mode === 'ax') {
selectableChannels = data.filter(item => item.MHz.toString().startsWith('5'));
} else if (hw_mode === 'be') {
selectableChannels = data.filter(item => item.MHz.toString().startsWith('5'));
} else {
// hw_mode 'b', 'g', or default to 2.4GHz
selectableChannels = data.filter(item => item.MHz.toString().startsWith('24'));
}
// If selected channel doeesn't exist in allowed channels, set default or null (unsupported)

View File

@@ -78,38 +78,6 @@ $(document).on("submit", ".js-dhcp-settings-form", function(e) {
$(".js-add-dhcp-upstream-server").trigger("click");
});
document.addEventListener('DOMContentLoaded', function() {
const bridgeCheckbox = document.getElementById('chxbridgedenable');
const bridgeSection = document.getElementById('bridgeStaticIpSection');
const staticIpInput = document.getElementById('bridgeStaticIp');
const netmaskInput = document.getElementById('bridgeNetmask');
const gatewayInput = document.getElementById('bridgeGateway');
const dnsInput = document.getElementById('bridgeDNS');
const previewIp = document.getElementById('previewStaticIp');
const bridgeInputs = [staticIpInput, netmaskInput, gatewayInput, dnsInput];
// toggle visibility and required fields
bridgeCheckbox.addEventListener('change', function() {
if (this.checked) {
bridgeSection.style.display = 'block';
bridgeInputs.forEach(input => input.setAttribute('required', 'required'));
} else {
bridgeSection.style.display = 'none';
bridgeInputs.forEach(input => input.removeAttribute('required'));
}
});
// auto-populate DNS when gateway loses focus
gatewayInput.addEventListener('blur', function() {
const gatewayVal = this.value.trim();
if (gatewayVal !== '' && dnsInput.value.trim() === '') {
dnsInput.value = gatewayVal;
}
});
});
/**
* mark a form field, e.g. a select box, with the class `.js-field-preset`
* and give it an attribute `data-field-preset-target` with a text field's
@@ -457,36 +425,6 @@ function setDhcpFieldsDisabled() {
$('#txtmetric').prop('disabled', true);
}
document.addEventListener('DOMContentLoaded', function() {
const dhcpCheckbox = document.getElementById('dhcp-iface');
const rangeStart = document.getElementById('txtrangestart');
const rangeEnd = document.getElementById('txtrangeend');
const leaseTime = document.getElementById('txtrangeleasetime');
function updateRequiredFields() {
const isChecked = dhcpCheckbox.checked === true;
if (isChecked) {
rangeStart.setAttribute('required', 'required');
rangeEnd.setAttribute('required', 'required');
leaseTime.setAttribute('required', 'required');
} else {
rangeStart.removeAttribute('required');
rangeEnd.removeAttribute('required');
leaseTime.removeAttribute('required');
rangeStart.classList.remove('is-invalid', 'is-valid');
rangeEnd.classList.remove('is-invalid', 'is-valid');
leaseTime.classList.remove('is-invalid', 'is-valid');
}
}
// set initial state
updateRequiredFields();
setTimeout(updateRequiredFields, 100);
dhcpCheckbox.addEventListener('change', updateRequiredFields);
});
// Static Array method
Array.range = (start, end) => Array.from({length: (end - start)}, (v, k) => k + start);

View File

@@ -23,6 +23,7 @@ define('RASPI_ADBLOCK_CONFIG', RASPI_DNSMASQ_PREFIX.'adblock.conf');
define('RASPI_HOSTAPD_CONFIG', '/etc/hostapd/hostapd.conf');
define('RASPI_DHCPCD_CONFIG', '/etc/dhcpcd.conf');
define('RASPI_DHCPCD_LOG', '/var/log/dnsmasq.log');
define('RASPI_HOSTAPD_LOG', '/tmp/hostapd.log');
define('RASPI_WPA_SUPPLICANT_CONFIG', '/etc/wpa_supplicant/wpa_supplicant.conf');
define('RASPI_HOSTAPD_CTRL_INTERFACE', '/var/run/hostapd');
define('RASPI_WPA_CTRL_INTERFACE', '/var/run/wpa_supplicant');

View File

@@ -14,7 +14,7 @@
"# N",
"ieee80211n=1",
"require_ht=1",
"ht_capab=[MAX-AMSDU-3839][{HT40_DIR}][SHORT-GI-20][SHORT-GI-40][DSSS_CCK-40]",
"ht_capab=[MAX-AMSDU-3839][HT40+][SHORT-GI-20][SHORT-GI-40][DSSS_CCK-40]",
"# AC",
"ieee80211ac=1",
"require_vht=1",
@@ -25,148 +25,6 @@
"vht_oper_centr_freq_seg0_idx={VHT_FREQ_IDX}"
]
},
"ax": {
"settings": [
"# Basic settings",
"hw_mode=a",
"# Enable 802.11n/ac",
"ieee80211d=1",
"ieee80211n=1",
"ieee80211ac=1",
"# Enable 802.11ax",
"ieee80211ax=1",
"# HE 802.11ax capabilities",
"he_su_beamformer=1",
"he_su_beamformee=1",
"he_mu_beamformer=1",
"# BSS color for spatial reuse, value 1-63",
"he_bss_color=1",
"he_oper_chwidth=1",
"# HE/VHT channel widths",
"he_oper_chwidth=1",
"vht_oper_chwidth=1",
"he_oper_centr_freq_seg0_idx={HE_FREQ_IDX}",
"vht_oper_centr_freq_seg0_idx={VHT_FREQ_IDX}",
"# HT 802.11n capabilities",
"ht_capab=[{HT40_DIR}][LDPC][SHORT-GI-20][SHORT-GI-40][TX-STBC][RX-STBC1][MAX-AMSDU-7935]",
"# VHT capabilities 802.11ac",
"vht_capab=[RXLDPC][SHORT-GI-80][TX-STBC-2BY1][RX-STBC-1][MAX-MPDU-11454][MAX-A-MPDU-LEN-EXP7]",
"# WMM/QoS",
"wmm_enabled=1"
]
},
"be": {
"settings": [
"# Basic settings",
"hw_mode=a",
"# Enable 802.11n/ac/ax",
"ieee80211n=1",
"ieee80211ac=1",
"ieee80211ax=1",
"# Maximum MPDU Length of HE 6 GHz band capabilities.",
"# Indicates maximum MPDU length",
"# 0 = 3895 octets",
"# 1 = 7991 octets",
"# 2 = 11454 octets",
"he_6ghz_max_mpdu=2",
"# Maximum A-MPDU Length Exponent of HE 6 GHz band capabilities. Indicates",
"# the maximum length of A-MPDU pre-EOF padding that # the STA can receive.",
"# This field is an integer in the range of 0 to 7. The length defined by",
"# this field is equal to 2 pow -1",
"# octets",
"# 0 = AMPDU length of 8k",
"# 1 = AMPDU length of 16k",
"# 2 = AMPDU length of 32k",
"# 3 = AMPDU length of 65k",
"# 4 = AMPDU length of 131k",
"# 5 = AMPDU length of 262kv",
"# 6 = AMPDU length of 524k",
"# 7 = AMPDU length of 1048k",
"he_6ghz_max_ampdu_len_exp=7",
"# 0 = Indoor AP",
"# 1 = Standard power AP",
"# 2 = Very low power AP",
"# 3 = Indoor enabled AP",
"# 4 = Indoor standard power AP",
"he_6ghz_reg_pwr_type=0",
"# HE beamforming capabilities",
"he_su_beamformer=1",
"he_su_beamformee=1",
"he_mu_beamformer=1",
"he_mu_edca_qos_info_param_count=0",
"he_mu_edca_qos_info_q_ack=0",
"he_mu_edca_qos_info_queue_request=0",
"he_mu_edca_qos_info_txop_request=0",
"he_mu_edca_ac_be_aifsn=8",
"he_mu_edca_ac_be_aci=0",
"he_mu_edca_ac_be_ecwmin=9",
"he_mu_edca_ac_be_ecwmax=10",
"he_mu_edca_ac_be_timer=255",
"he_mu_edca_ac_bk_aifsn=15",
"he_mu_edca_ac_bk_aci=1",
"he_mu_edca_ac_bk_ecwmin=9",
"he_mu_edca_ac_bk_ecwmax=10",
"he_mu_edca_ac_bk_timer=255",
"he_mu_edca_ac_vi_ecwmin=5",
"he_mu_edca_ac_vi_ecwmax=7",
"he_mu_edca_ac_vi_aifsn=5",
"he_mu_edca_ac_vi_aci=2",
"he_mu_edca_ac_vi_timer=255",
"he_mu_edca_ac_vo_aifsn=5",
"he_mu_edca_ac_vo_aci=3",
"he_mu_edca_ac_vo_ecwmin=5",
"he_mu_edca_ac_vo_ecwmax=7",
"he_mu_edca_ac_vo_timer=255",
"# EHT beamforming capabilities",
"eht_su_beamformer=0",
"eht_su_beamformee=0",
"eht_mu_beamformer=0",
"# used by clients to discern the source of interference",
"# each AP in your area needs to use a different number",
"# allowed: 1-63",
"he_bss_color=37",
"# 160 MHz for HE",
"he_oper_chwidth=2",
"he_oper_centr_freq_seg0_idx={HE_FREQ_IDX}",
"# IEEE 802.11be WiFi 7 configuration",
"ieee80211be=1",
"# EHT configuration",
"eht_su_beamformer=1",
"eht_su_beamformee=1",
"eht_mu_beamformer=1",
"# EHT operating channel information; see matching he_* parameters for details.",
"# The field eht_oper_centr_freq_seg0_idx field is used to indicate center",
"# frequency of 40, 80, and 160 MHz bandwidth operation.",
"# In the 6 GHz band, eht_oper_chwidth is ignored and the channel width is",
"# derived from the configured operating class IEEE P802.11be/D1.5,",
"# Annex E.1 - Country information and operating classes.",
"# Channel width 0 = 40 MHz, 1 = 80 Mhz, 2 = 160 Mhz",
"eht_oper_chwidth=2",
"eht_oper_centr_freq_seg0_idx={VHT_FREQ_IDX}",
"# VHT operation parameters",
"vht_oper_chwidth=2",
"vht_oper_centr_freq_seg0_idx={VHT_FREQ_IDX}",
"vht_capab=[MAX-MPDU-11454][RXLDPC][SHORT-GI-80][SHORT-GI-160][TX-STBC-2BY1][RX-STBC-1][SU-BEAMFORMER][SU-BEAMFORMEE][MU-BEAMFORMER][MU-BEAMFORMEE]",
"# WMM configuration",
"wmm_enabled=1",
"wmm_ac_bk_cwmin=4",
"wmm_ac_bk_cwmax=10",
"wmm_ac_bk_aifs=7",
"wmm_ac_bk_txop_limit=0",
"wmm_ac_be_aifs=3",
"wmm_ac_be_cwmin=4",
"wmm_ac_be_cwmax=10",
"wmm_ac_be_txop_limit=0",
"wmm_ac_vi_aifs=2",
"wmm_ac_vi_cwmin=3",
"wmm_ac_vi_cwmax=4",
"wmm_ac_vi_txop_limit=94",
"wmm_ac_vo_aifs=2",
"wmm_ac_vo_cwmin=2",
"wmm_ac_vo_cwmax=3",
"wmm_ac_vo_txop_limit=47"
]
},
"g": {
"settings": [
"hw_mode=g",

152
includes/configure_client.php Normal file → Executable file
View File

@@ -3,7 +3,7 @@
use RaspAP\Networking\Hotspot\WiFiManager;
/**
* WiFi client configuration page handler
*
*
*/
function DisplayWPAConfig()
@@ -16,15 +16,17 @@ function DisplayWPAConfig()
$wifi->knownWifiStations($networks);
$wifi->setKnownStationsWPA($networks);
$clientInterface = $_SESSION['wifi_client_interface'];
$iface = escapeshellarg($_SESSION['wifi_client_interface']);
if (isset($_POST['connect'])) {
$netid = intval($_POST['connect']);
if ($wifi->connectToNetwork($clientInterface, $netid)) {
$status->addMessage('New network selected', 'success');
} else {
$cmd = "sudo wpa_cli -i $iface select_network $netid";
$return = shell_exec($cmd);
sleep(2);
if (trim($return) == "FAIL") {
$status->addMessage('WPA command line client returned failure. Check your adapter.', 'danger');
} else {
$status->addMessage('New network selected', 'success');
}
} elseif (isset($_POST['wpa_reinit'])) {
$status->addMessage('Attempting to reinitialize wpa_supplicant', 'warning');
@@ -32,52 +34,120 @@ function DisplayWPAConfig()
$result = $wifi->reinitializeWPA($force_remove);
} elseif (isset($_POST['client_settings'])) {
$tmp_networks = $networks;
if ($wpa_file = fopen('/tmp/wifidata', 'w')) {
fwrite($wpa_file, 'ctrl_interface=DIR=' . RASPI_WPA_CTRL_INTERFACE . ' GROUP=netdev' . PHP_EOL);
fwrite($wpa_file, 'update_config=1' . PHP_EOL);
foreach (array_keys($_POST) as $post) {
foreach (array_keys($_POST) as $post) {
if (preg_match('/delete(\d+)/', $post, $post_match)) {
$network = $tmp_networks[$_POST['ssid' . $post_match[1]]];
$netid = $network['index'];
$wifi->deleteNetwork($clientInterface, $netid);
unset($tmp_networks[$_POST['ssid' . $post_match[1]]]);
} elseif (preg_match('/disconnect(\d+)/', $post, $post_match)) {
$network = $tmp_networks[$_POST['ssid' . $post_match[1]]];
$netid = $network['index'];
$wifi->disconnectNetwork($clientInterface, $netid);
} elseif (preg_match('/update(\d+)/', $post, $post_match)) {
// NB, multiple protocols are separated with a forward slash ('/')
$protocol = $_POST['protocol' . $post_match[1]] === $wifi::SECURITY_OPEN ? $wifi::SECURITY_OPEN : 'WPA';
$tmp_networks[$_POST['ssid' . $post_match[1]]] = array(
'protocol' => $protocol,
'passphrase' => $_POST['passphrase' . $post_match[1]] ?? '',
'configured' => true
);
if (array_key_exists('priority' . $post_match[1], $_POST)) {
$tmp_networks[$_POST['ssid' . $post_match[1]]]['priority'] = $_POST['priority' . $post_match[1]];
}
$network = $tmp_networks[$_POST['ssid' . $post_match[1]]];
if (preg_match('/delete(\d+)/', $post, $post_match)) {
$network = $tmp_networks[$_POST['ssid' . $post_match[1]]];
$netid = $network['index'];
exec('sudo wpa_cli -i ' . $iface . ' disconnect ' . $netid);
exec('sudo wpa_cli -i ' . $iface . ' remove_network ' . $netid);
unset($tmp_networks[$_POST['ssid' . $post_match[1]]]);
$ssid = $_POST['ssid' . $post_match[1]];
$passphrase = $_POST['passphrase' . $post_match[1]] ?? '';
$netid = $wifi->updateNetwork($clientInterface, $ssid, $passphrase, $protocol);
if ($netid === null) {
$status->addMessage('Unable to add network with WPA command line client', 'warning');
} elseif (preg_match('/update(\d+)/', $post, $post_match)) {
// NB, multiple protocols are separated with a forward slash ('/')
$tmp_networks[$_POST['ssid' . $post_match[1]]] = array(
'protocol' => ( $_POST['protocol' . $post_match[1]] === 'Open' ? 'Open' : 'WPA' ),
'passphrase' => $_POST['passphrase' . $post_match[1]],
'configured' => true
);
if (array_key_exists('priority' . $post_match[1], $_POST)) {
$tmp_networks[$_POST['ssid' . $post_match[1]]]['priority'] = $_POST['priority' . $post_match[1]];
}
$network = $tmp_networks[$_POST['ssid' . $post_match[1]]];
$ssid = escapeshellarg('"'.$_POST['ssid' . $post_match[1]].'"');
$psk = escapeshellarg('"'.$_POST['passphrase' . $post_match[1]].'"');
$netid = trim(shell_exec("sudo wpa_cli -i $iface add_network"));
if (isset($netid)) {
$commands = [
"sudo wpa_cli -i $iface set_network $netid ssid $ssid",
"sudo wpa_cli -i $iface set_network $netid psk $psk",
"sudo wpa_cli -i $iface enable_network $netid"
];
foreach ($commands as $cmd) {
exec($cmd);
}
} else {
$status->addMessage('Unable to add network with WPA command line client', 'warning');
}
}
}
}
$result = $wifi->writeWpaSupplicant($tmp_networks, $clientInterface);
$ok = true;
foreach ($tmp_networks as $ssid => $network) {
if ($network['protocol'] === 'Open') {
fwrite($wpa_file, "network={".PHP_EOL);
fwrite($wpa_file, "\tssid=\"".$ssid."\"".PHP_EOL);
fwrite($wpa_file, "\tkey_mgmt=NONE".PHP_EOL);
fwrite($wpa_file, "\tscan_ssid=1".PHP_EOL);
if (array_key_exists('priority', $network)) {
fwrite($wpa_file, "\tpriority=".$network['priority'].PHP_EOL);
}
fwrite($wpa_file, "}".PHP_EOL);
} else {
if (strlen($network['passphrase']) >=8 && strlen($network['passphrase']) <= 63) {
unset($wpa_passphrase);
unset($line);
exec('wpa_passphrase '. $wifi->ssid2utf8( escapeshellarg($ssid) ) . ' ' . escapeshellarg($network['passphrase']), $wpa_passphrase);
foreach ($wpa_passphrase as $line) {
if (preg_match('/^\s*}\s*$/', $line)) {
if (array_key_exists('priority', $network)) {
fwrite($wpa_file, "\tpriority=".$network['priority'].PHP_EOL);
}
fwrite($wpa_file, $line.PHP_EOL);
} else {
if ( preg_match('/\\\\x[0-9A-Fa-f]{2}/',$ssid) && strpos($line, "ssid=\"") !== false ) {
fwrite($wpa_file, "\tssid=P\"".$ssid."\"".PHP_EOL);
} else {
fwrite($wpa_file, $line.PHP_EOL);
}
}
}
} elseif (strlen($network['passphrase']) == 0 && strlen($network['passkey']) == 64) {
$line = "\tpsk=" . $network['passkey'];
fwrite($wpa_file, "network={".PHP_EOL);
fwrite($wpa_file, "\tssid=\"".$ssid."\"".PHP_EOL);
fwrite($wpa_file, $line.PHP_EOL);
if (array_key_exists('priority', $network)) {
fwrite($wpa_file, "\tpriority=".$network['priority'].PHP_EOL);
}
fwrite($wpa_file, "}".PHP_EOL);
} else {
$status->addMessage('WPA passphrase must be between 8 and 63 characters', 'danger');
$ok = false;
}
}
}
if ($result['success']) {
$status->addMessage($result['message'], 'success');
$networks = $tmp_networks;
if ($ok) {
system('sudo cp /tmp/wifidata ' . RASPI_WPA_SUPPLICANT_CONFIG, $returnval);
if ($returnval == 0) {
exec('sudo wpa_cli -i ' . $_SESSION['wifi_client_interface'] . ' reconfigure', $reconfigure_out, $reconfigure_return);
if ($reconfigure_return == 0) {
$status->addMessage('Wifi settings updated successfully', 'success');
$networks = $tmp_networks;
} else {
$status->addMessage('Wifi settings updated but cannot restart (cannot execute "wpa_cli reconfigure")', 'danger');
}
} else {
$status->addMessage('Wifi settings failed to be updated', 'danger');
}
}
} else {
$status->addMessage($result['message'], 'danger');
$status->addMessage('Failed to update wifi settings', 'danger');
}
}
$ifaceStatus = $wifi->getInterfaceStatus($clientInterface);
$clientInterface = $_SESSION['wifi_client_interface'];
exec('ip a show '.$clientInterface, $stdoutIp);
$stdoutIpAllLinesGlued = implode(" ", $stdoutIp);
$stdoutIpWRepeatedSpaces = preg_replace('/\s\s+/', ' ', $stdoutIpAllLinesGlued);
preg_match('/state (UP|DOWN)/i', $stdoutIpWRepeatedSpaces, $matchesState) || $matchesState[1] = 'unknown';
$ifaceStatus = strtolower($matchesState[1]) ? "up" : "down";
echo renderTemplate("configure_client", compact("status", "clientInterface", "ifaceStatus"));
}

View File

@@ -44,12 +44,6 @@ function DisplayDashboard(&$extraFooterScripts): void
$plugins = $pluginManager->getInstalledPlugins();
$bridgedEnable = getBridgedState();
if ($bridgedEnable) {
$interface = 'br0';
$details = $dashboard->getInterfaceDetails($interface);
$connectionType = 'ethernet';
}
// handle page actions
if (!empty($_POST)) {
$status = $dashboard->handlePageAction($state, $_POST, $status, $interface);

View File

@@ -7,7 +7,7 @@ if (!defined('RASPI_CONFIG')) {
$defaults = [
'RASPI_BRAND_TEXT' => 'RaspAP',
'RASPI_BRAND_TITLE' => RASPI_BRAND_TEXT.' Admin Panel',
'RASPI_VERSION' => '3.5.1',
'RASPI_VERSION' => '3.4.3',
'RASPI_CONFIG_NETWORK' => RASPI_CONFIG.'/networking/defaults.json',
'RASPI_CONFIG_PROVIDERS' => 'config/vpn-providers.json',
'RASPI_CONFIG_API' => RASPI_CONFIG.'/api',
@@ -28,6 +28,7 @@ $defaults = [
'RASPI_HOSTAPD_CONFIG' => '/etc/hostapd/hostapd.conf',
'RASPI_DHCPCD_CONFIG' => '/etc/dhcpcd.conf',
'RASPI_DHCPCD_LOG' => '/var/log/dnsmasq.log',
'RASPI_HOSTAPD_LOG' => '/tmp/hostapd.log',
'RASPI_WPA_SUPPLICANT_CONFIG' => '/etc/wpa_supplicant/wpa_supplicant.conf',
'RASPI_HOSTAPD_CTRL_INTERFACE' => '/var/run/hostapd',
'RASPI_WPA_CTRL_INTERFACE' => '/var/run/wpa_supplicant',

View File

@@ -112,9 +112,6 @@ function getProviderValue($id, $key)
if (!isset($obj['providers']) || !is_array($obj['providers'])) {
return false;
}
if ($id === null || !is_numeric($id)) {
return false;
}
$id--;
if (!isset($obj['providers'][$id]) || !is_array($obj['providers'][$id])) {
return false;
@@ -921,7 +918,7 @@ function renderStatus($hostapd_led, $hostapd_status, $memused_led, $memused, $cp
<img src="app/img/raspAP-logo.php?static=1" class="navbar-logo" width="70" height="70">
</div>
<div class="col ml-2">
<div class="ml-1 sb-status"><?php echo _("Status"); ?></div>
<div class="ml-1 sb-status">Status</div>
<div class="info-item-xs"><span class="icon">
<i class="fas fa-circle hostapd-led <?php echo ($hostapd_led); ?>"></i></span> <?php echo _("Hotspot").' '. _($hostapd_status); ?>
</div>

View File

@@ -3,7 +3,6 @@
use RaspAP\Networking\Hotspot\HostapdManager;
use RaspAP\Networking\Hotspot\HotspotService;
use RaspAP\Networking\Hotspot\WiFiManager;
use RaspAP\Networking\Hotspot\DhcpcdManager;
use RaspAP\Messages\StatusMessage;
use RaspAP\System\Sysinfo;
@@ -16,11 +15,9 @@ $wifi->getWifiInterface();
*/
function DisplayHostAPDConfig()
{
$reg_domain = 'GB';
$hostapd = new HostapdManager();
$hotspot = new HotspotService();
$status = new StatusMessage();
$dhcpcd = new DhcpcdManager();
$system = new Sysinfo();
$operatingSystem = $system->operatingSystem();
@@ -31,24 +28,18 @@ function DisplayHostAPDConfig()
$arr80211w = $hotspot->get80211wOptions();
$languageCode = strtok($_SESSION['locale'], '_');
$countryCodes = getCountryCodes($languageCode);
$reg_domain = $hotspot->getRegDomain();
$interfaces = $hotspot->getInterfaces();
$arrTxPower = getDefaultNetOpts('txpower','dbm');
$managedModeEnabled = false;
try {
$reg_domain = $hotspot->getRegDomain();
} catch (RuntimeException $e) {
error_log('Failed to get regulatory domain: ' . $e->getMessage());
}
if (isset($_POST['interface'])) {
$interface = $_POST['interface'];
} else {
$interface = $_SESSION['ap_interface'];
}
$txpower = $hotspot->getTxPower($interface);
$arrHostapdConf = $hotspot->getHostapdIni();
$logOutput = [];
if (!RASPI_MONITOR_ENABLED) {
if (isset($_POST['StartHotspot']) || isset($_POST['RestartHotspot'])) {
@@ -70,19 +61,7 @@ function DisplayHostAPDConfig()
$status->addMessage($line, 'info');
}
} elseif (isset($_POST['SaveHostAPDSettings'])) {
$result = $hotspot->saveSettings(
$_POST,
$arrSecurity,
$arrEncType,
$arr80211Standard,
$interfaces,
$reg_domain,
$status
);
// reload hostapi.ini
$arrHostapdConf = $hotspot->getHostapdIni();
$hotspot->saveSettings($_POST, $arrSecurity, $arrEncType, $arr80211Standard, $interfaces, $reg_domain, $status);
} elseif (isset($_POST['StopHotspot'])) {
$status->addMessage('Attempting to stop hotspot', 'info');
exec('sudo /bin/systemctl stop hostapd.service', $return);
@@ -117,45 +96,15 @@ function DisplayHostAPDConfig()
error_log('Error: ' . $e->getMessage());
}
// bridge configuration
if (!empty($arrHostapdConf['BridgedEnable']) && (int)$arrHostapdConf['BridgedEnable'] === 1) {
$iface = 'br0';
$bridgeConfig = $dhcpcd->getInterfaceConfig($iface);
if (is_array($bridgeConfig) && !empty($bridgeConfig)) {
$arrConfig['bridgeStaticIP'] = !empty($bridgeConfig['StaticIP'])
? $bridgeConfig['StaticIP']
: '192.168.1.10';
$arrConfig['bridgeNetmask'] = !empty($bridgeConfig['SubnetMask'])
? mask2cidr($bridgeConfig['SubnetMask'])
: '24';
$arrConfig['bridgeGateway'] = !empty($bridgeConfig['StaticRouters'])
? $bridgeConfig['StaticRouters']
: '192.168.1.1';
$arrConfig['bridgeDNS'] = !empty($bridgeConfig['StaticDNS'])
? $bridgeConfig['StaticDNS']
: '192.168.1.1';
}
}
// fetch hostapd logs if enabled
if ((string)$arrHostapdConf['LogEnable'] === "1") {
$logResult = $hotspot->getHostapdLogs(5000);
if ($logResult['success']) {
$joined = implode("\n", $logResult['logs']);
$limited = getLogLimited('', $joined);
$logOutput = explode("\n", $limited);
}
}
// assign disassoc_low_ack boolean if value is set
$arrConfig['disassoc_low_ack_bool'] = isset($arrConfig['disassoc_low_ack']) ? 1 : 0;
$hostapdstatus = $system->hostapdStatus();
$serviceStatus = $hostapdstatus[0] == 0 ? "down" : "up";
// ensure log is writeable
exec('sudo /bin/chmod o+r '.RASPI_HOSTAPD_LOG);
$logdata = getLogLimited(RASPI_HOSTAPD_LOG);
echo renderTemplate(
"hostapd", compact(
"status",
@@ -173,7 +122,7 @@ function DisplayHostAPDConfig()
"arrHostapdConf",
"operatingSystem",
"countryCodes",
"logOutput"
"logdata"
)
);
}

View File

@@ -1,41 +1,125 @@
<?php
/**
* Sets locale information for i18n support, with validation
* Sets locale information for i18n support, with secure input validation.
*
* @see RaspAP\Localization\LocaleManager
* Rudimentary language detection is performed via the browser.
* Accept-Language returns a list of weighted values with a quality (or 'q') parameter.
* A better method would parse the list of preferred languages and match this with
* the languages supported by our platform.
* @see https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4
*
* Uses $validLocales to mitigate OS Command Injection (CWE-78)
* @see Vulnerability Report for JVN #27202136
*/
use RaspAP\Localization\LocaleManager;
// Set locale from POST, if provided and valid
$validLocales = array_keys(getLocales());
if (!empty($_POST['locale']) && in_array($_POST['locale'], $validLocales, true)) {
$_SESSION['locale'] = $_POST['locale'];
}
// Initialize locale manager
$localeManager = new LocaleManager();
$localeManager->initializeAndApply();
// Set locale from browser detection, if not already set
if (empty($_SESSION['locale'])) {
$_SESSION['locale'] = detectBrowserLocale();
}
// Enforce only valid locale values in session
if (!in_array($_SESSION['locale'], $validLocales, true)) {
$_SESSION['locale'] = 'en_GB.UTF-8';
}
// Apply locale settings
putenv("LANG=" . escapeshellarg($_SESSION['locale']));
setlocale(LC_ALL, $_SESSION['locale']);
bindtextdomain(LOCALE_DOMAIN, LOCALE_ROOT);
bind_textdomain_codeset(LOCALE_DOMAIN, 'UTF-8');
textdomain(LOCALE_DOMAIN);
/**
* Get all available locales
*
* @return array Associative array of locale codes to language names
*/
function getLocales(): array
{
static $localeManager = null;
if ($localeManager === null) {
$localeManager = new LocaleManager();
}
return $localeManager->getLocales();
return [
'en_GB.UTF-8' => 'English',
'cs_CZ.UTF-8' => 'Čeština',
'zh_TW.UTF-8' => '正體中文 (Chinese traditional)',
'zh_CN.UTF-8' => '简体中文 (Chinese simplified)',
'da_DK.UTF-8' => 'Dansk',
'de_DE.UTF-8' => 'Deutsch',
'es_MX.UTF-8' => 'Español',
'fi_FI.UTF-8' => 'Finnish',
'fr_FR.UTF-8' => 'Français',
'el_GR.UTF-8' => 'Ελληνικά',
'id_ID.UTF-8' => 'Indonesian',
'it_IT.UTF-8' => 'Italiano',
'ja_JP.UTF-8' => '日本語 (Japanese)',
'ko_KR.UTF-8' => '한국어 (Korean)',
'nl_NL.UTF-8' => 'Nederlands',
'pl_PL.UTF-8' => 'Polskie',
'pt_BR.UTF-8' => 'Português',
'ru_RU.UTF-8' => 'Русский',
'ro_RO.UTF-8' => 'Română',
'sk_SK.UTF-8' => 'Slovenčina',
'sv_SE.UTF-8' => 'Svenska',
'tr_TR.UTF-8' => 'Türkçe',
'vi_VN.UTF-8' => 'Tiếng Việt (Vietnamese)'
];
}
/**
* Detect browser locale from Accept-Language header
*
* @return string Detected locale code
*/
function detectBrowserLocale(): string
{
static $localeManager = null;
if ($localeManager === null) {
$localeManager = new LocaleManager();
if (empty($_SERVER['HTTP_ACCEPT_LANGUAGE']) || strlen($_SERVER['HTTP_ACCEPT_LANGUAGE']) < 2) {
return 'en_GB.UTF-8';
}
$acceptLang = $_SERVER['HTTP_ACCEPT_LANGUAGE'];
$lang = strtolower(substr($acceptLang, 0, 2));
if ($lang === 'zh' && strpos($acceptLang, 'zh-TW') === 0) {
return 'zh_TW.UTF-8';
}
switch ($lang) {
case 'de':
return 'de_DE.UTF-8';
case 'fr':
return 'fr_FR.UTF-8';
case 'it':
return 'it_IT.UTF-8';
case 'pt':
return 'pt_BR.UTF-8';
case 'sv':
return 'sv_SE.UTF-8';
case 'nl':
return 'nl_NL.UTF-8';
case 'zh':
return 'zh_CN.UTF-8';
case 'cs':
return 'cs_CZ.UTF-8';
case 'ru':
return 'ru_RU.UTF-8';
case 'es':
return 'es_MX.UTF-8';
case 'fi':
return 'fi_FI.UTF-8';
case 'da':
return 'da_DK.UTF-8';
case 'tr':
return 'tr_TR.UTF-8';
case 'id':
return 'id_ID.UTF-8';
case 'ko':
return 'ko_KR.UTF-8';
case 'ja':
return 'ja_JP.UTF-8';
case 'vi':
return 'vi_VN.UTF-8';
case 'el':
return 'el_GR.UTF-8';
case 'pl':
return 'pl_PL.UTF-8';
case 'sk':
return 'sk_SK.UTF-8';
default:
return 'en_GB.UTF-8';
}
return $localeManager->detectBrowserLocale();
}

View File

@@ -7,14 +7,14 @@
* Enables use of simple web interface rather than SSH to control WiFi and related services on the Raspberry Pi.
* Recommended distribution is Raspberry Pi OS (64-bit) Lite. Specific instructions to install the supported software are
* in the README and original post by @SirLagz. For a quick run through, the packages required for the WebGUI are:
* lighttpd (version 1.4.79 installed via apt)
* php-fpm (version 8.4.11 installed via apt)
* lighttpd (version 1.4.69 installed via apt)
* php-cgi (version 8.2.28 installed via apt)
* along with their supporting packages, php8.2 will also need to be enabled.
*
* @author Lawrence Yau <sirlagz@gmail.com>
* @author Bill Zimmerman <billzimmerman@gmail.com>
* @license GNU General Public License, version 3 (GPL-3.0)
* @version 3.5.1
* @version 3.4.3
* @link https://github.com/RaspAP/raspap-webgui/
* @link https://raspap.com/
* @see http://sirlagz.net/2013/02/08/raspap-webgui/

142
installers/common.sh Normal file → Executable file
View File

@@ -34,16 +34,7 @@ if [ "$insiders" == 1 ]; then
repo="RaspAP/raspap-insiders"
branch=${RASPAP_INSIDERS_LATEST}
fi
#Use ssh IF $ssh is set AND $username and $acctoken IS NOT set
if [ -n "$username" ] && [ -n "$acctoken" ]; then
git_source_url="https://${username}:${acctoken}@github.com/$repo"
ssh=0
elif [ "$ssh" == 1 ]; then
git_source_url="git@github.com:$repo"
else
git_source_url="https://github.com/$repo"
fi
git_source_url="https://github.com/$repo"
webroot_dir="/var/www/html"
# NOTE: all the below functions are overloadable for system-specific installs
@@ -158,7 +149,7 @@ function _get_linux_distro() {
# Sets php package option based on Linux version, abort if unsupported distro
function _set_php_package() {
case $RELEASE in
13|2025.*) # Debian 13 trixie, Kali Linux 2025
13) # Debian 13 trixie
php_package="php8.4-fpm"
phpiniconf="/etc/php/8.4/fpm/php.ini" ;;
23.05|12*) # Debian 12 & Armbian 23.05
@@ -246,7 +237,7 @@ function _install_dependencies() {
else
echo "${php_package} will be installed from the main deb sources list"
fi
if [ ${OS,,} = "debian" ] || [ ${OS,,} = "ubuntu" ] || [ ${OS,,} = "kali" ]; then
if [ ${OS,,} = "debian" ] || [ ${OS,,} = "ubuntu" ]; then
dhcpcd_package="dhcpcd5"
iw_package="iw"
rsync_package="rsync"
@@ -286,11 +277,10 @@ function _install_dependencies() {
sudo apt-get install -y lighttpd git hostapd dnsmasq iptables-persistent $php_package $dhcpcd_package $iw_package $rsync_package $network_tools $ifconfig_package vnstat qrencode jq isoquery || _install_status 1 "Unable to install dependencies"
if [[ "$php_package" == *"-fpm" ]]; then
_install_log "Enabling lighttpd fastcgi-php-fpm module for $php_package"
sudo lighty-enable-mod fastcgi-php-fpm 2>&1 | grep -qE "already enabled" || \
_install_status 1 "Unable to enable fastcgi-php-fpm module"
sudo systemctl restart $php_package.service || _install_status 1 "Unable to restart $php_package.service"
install_log "Enabling lighttpd fastcgi-php-fpm module for $php_package"
sudo lighty-enable-mod fastcgi-php-fpm || install_status 1 "Unable to enable fastcgi-php-fpm module"
fi
_install_status 0
}
@@ -325,6 +315,9 @@ function _create_hostapd_scripts() {
_install_log "Creating hostapd logging & control scripts"
sudo mkdir $raspap_dir/hostapd || _install_status 1 "Unable to create directory '$raspap_dir/hostapd'"
# Copy logging shell scripts
sudo cp "$webroot_dir/installers/"enablelog.sh "$raspap_dir/hostapd" || _install_status 1 "Unable to move logging scripts"
sudo cp "$webroot_dir/installers/"disablelog.sh "$raspap_dir/hostapd" || _install_status 1 "Unable to move logging scripts"
# Copy service control shell scripts
sudo cp "$webroot_dir/installers/"servicestart.sh "$raspap_dir/hostapd" || _install_status 1 "Unable to move service control scripts"
# Change ownership and permissions of hostapd control scripts
@@ -424,28 +417,7 @@ function _prompt_install_feature() {
else
$function
fi
elif [ "$opt" == "pv_option" ]; then
local opt_value=${!opt:-0}
if [ "$opt_value" == 0 ]; then
echo "(Skipped)"
else
local valid_ids=($(jq -r '.providers[].id' "$webroot_dir/config/vpn-providers.json"))
local found=0
for id in "${valid_ids[@]}"; do
if [ "$id" == "$opt_value" ]; then
found=1
break
fi
done
if [ $found == 1 ]; then
echo -e
$function
else
_install_status 1 "Invalid VPN provider ID $opt_value - (Skipped)"
fi
fi
elif [ "${!opt}" == 1 ]; then
echo -e
$function
else
echo "(Skipped)"
@@ -526,7 +498,6 @@ function _install_provider() {
if [ "$answer" != "${answer#[0]}" ]; then
_install_status 0 "(Skipped)"
skip=true
break
elif [[ "$answer" =~ ^[0-9]+$ ]] && [[ -n ${options[$answer]+abc} ]]; then
break
@@ -535,25 +506,25 @@ function _install_provider() {
fi
done
fi
if ! [ "$skip" ]; then
selected="${options[$answer]}"
echo "Configuring support for ${selected%%|*}"
bin_path=${selected#*|}
if ! grep -q "$bin_path" "$webroot_dir/installers/raspap.sudoers"; then
echo "Adding $bin_path to raspap.sudoers"
echo "www-data ALL=(ALL) NOPASSWD:$bin_path *" | sudo tee -a "$webroot_dir/installers/raspap.sudoers" > /dev/null || _install_status 1 "Unable to modify raspap.sudoers"
fi
echo "Enabling administration option for ${selected%%|*}"
sudo sed -i "s/\('RASPI_VPN_PROVIDER_ENABLED', \)false/\1true/g" "$webroot_dir/includes/config.php" || _install_status 1 "Unable to modify config.php"
echo "Adding VPN provider to $raspap_dir/provider.ini"
if [ ! -f "$raspap_dir/provider.ini" ]; then
sudo touch "$raspap_dir/provider.ini"
echo "providerID = $answer" | sudo tee "$raspap_dir/provider.ini" > /dev/null || _install_status 1 "Unable to create $raspap_dir/provider.ini"
elif ! grep -q "providerID = $answer" "$raspap_dir/provider.ini"; then
echo "providerID = $answer" | sudo tee "$raspap_dir/provider.ini" > /dev/null || _install_status 1 "Unable to write to $raspap_dir/provider.ini"
fi
fi
selected="${options[$answer]}"
echo "Configuring support for ${selected%%|*}"
bin_path=${selected#*|}
if ! grep -q "$bin_path" "$webroot_dir/installers/raspap.sudoers"; then
echo "Adding $bin_path to raspap.sudoers"
echo "www-data ALL=(ALL) NOPASSWD:$bin_path *" | sudo tee -a "$webroot_dir/installers/raspap.sudoers" > /dev/null || _install_status 1 "Unable to modify raspap.sudoers"
fi
echo "Enabling administration option for ${selected%%|*}"
sudo sed -i "s/\('RASPI_VPN_PROVIDER_ENABLED', \)false/\1true/g" "$webroot_dir/includes/config.php" || _install_status 1 "Unable to modify config.php"
echo "Adding VPN provider to $raspap_dir/provider.ini"
if [ ! -f "$raspap_dir/provider.ini" ]; then
sudo touch "$raspap_dir/provider.ini"
echo "providerID = $answer" | sudo tee "$raspap_dir/provider.ini" > /dev/null || _install_status 1 "Unable to create $raspap_dir/provider.ini"
elif ! grep -q "providerID = $answer" "$raspap_dir/provider.ini"; then
echo "providerID = $answer" | sudo tee "$raspap_dir/provider.ini" > /dev/null || _install_status 1 "Unable to write to $raspap_dir/provider.ini"
fi
_install_status 0
}
@@ -566,13 +537,6 @@ function _install_wireguard() {
fi
echo "Installing wireguard from apt"
sudo apt-get install -y wireguard $wg_dep || _install_status 1 "Unable to install wireguard"
# create shim for resolvconf under debian 13
if { ! command -v resolvconf >/dev/null || [ "$RELEASE" == 13 ]; } then
echo "Applying resolvconf shim to prevent DNS conflicts"
sudo ln -sf /usr/bin/true /usr/local/bin/resolvconf || _install_status 1 "Failed to apply resolvconf shim"
fi
echo "Enabling wg-quick@wg0"
sudo systemctl enable wg-quick@wg0 || _install_status 1 "Failed to enable wg-quick service"
echo "Enabling WireGuard management option"
@@ -641,16 +605,22 @@ function _download_latest_files() {
source_dir="/tmp/raspap-webgui"
if [ -d "$source_dir" ]; then
echo "Temporary download destination $source_dir exists. Removing..."
rm -rf "$source_dir"
rm -r "$source_dir"
fi
if [ "$insiders" == 1 ] && [ "$ssh" != 1 ] && [[ -z "$username" || -z "$acctoken" ]]; then
_install_status 3
_install_status 0 "Insiders please read this: https://docs.raspap.com/insiders/#authentication"
if [ "$repo" == "RaspAP/raspap-insiders" ]; then
if [ -n "$username" ] && [ -n "$acctoken" ]; then
insiders_source_url="https://${username}:${acctoken}@github.com/$repo"
git clone --branch $branch --depth 1 --recurse-submodules -c advice.detachedHead=false $insiders_source_url $source_dir || clone=false
git -C $source_dir submodule update --remote plugins || clone=false
else
_install_status 3
echo "Insiders please read this: https://docs.raspap.com/insiders/#authentication"
fi
fi
if [ -z "$insiders_source_url" ]; then
git clone --branch $branch --depth 1 --recurse-submodules -c advice.detachedHead=false $git_source_url $source_dir || clone=false
git -C $source_dir submodule update --remote plugins || clone=false
fi
git clone --branch $branch --depth 1 --recurse-submodules -c advice.detachedHead=false $git_source_url $source_dir || clone=false
git -C $source_dir submodule update --remote plugins || clone=false
if [ "$clone" = false ]; then
_install_status 1 "Unable to download files from GitHub"
echo "The installer cannot continue." >&2
@@ -660,36 +630,18 @@ function _download_latest_files() {
if [ -d "$webroot_dir" ] && [ "$update" == 0 ]; then
sudo mv $webroot_dir "$webroot_dir.`date +%F-%R`" || _install_status 1 "Unable to move existing webroot directory"
elif [ "$upgrade" == 1 ] || [ "$update" == 1 ]; then
# Preserve user plugins temporarily
if [ -d "$webroot_dir/plugins" ]; then
sudo cp -r "$webroot_dir/plugins" "/tmp/raspap-user-plugins"
fi
sudo find "$webroot_dir" -mindepth 1 \
! -path "${webroot_dir}/ajax/system/sys_read_logfile.php" \
! -path "${webroot_dir}/plugins" \
! -path "${webroot_dir}/plugins/*" \
-delete 2>/dev/null
# Remove plugins to permit clean rsync
sudo rm -rf "$webroot_dir/plugins"
exclude='--exclude=ajax/system/sys_read_logfile.php'
shopt -s extglob
sudo find "$webroot_dir" ! -path "${webroot_dir}/ajax/system/sys_read_logfile.php" -delete 2>/dev/null
fi
_install_log "Installing application to $webroot_dir"
sudo rsync -av $exclude "$source_dir"/ "$webroot_dir"/ >/dev/null 2>&1 || _install_status 1 "Unable to install files to $webroot_dir"
# Restore user plugins after rsync
if [ "$upgrade" == 1 ] || [ "$update" == 1 ]; then
if [ -d "/tmp/raspap-user-plugins" ]; then
sudo find /tmp/raspap-user-plugins -mindepth 1 -maxdepth 1 -type d -exec cp -r {} "$webroot_dir/plugins/" \; 2>/dev/null
sudo rm -rf "/tmp/raspap-user-plugins"
fi
fi
if [ "$update" == 1 ]; then
_install_log "Applying existing configuration to ${webroot_dir}/includes"
sudo mv /tmp/config.php $webroot_dir/includes || _install_status 1 "Unable to move config.php to ${webroot_dir}/includes"
if [ -f /tmp/raspap.auth ]; then
_install_log "Applying existing authentication file to ${raspap_dir}"
sudo mv /tmp/raspap.auth $raspap_dir || _install_status 1 "Unable to restore authentification credentials file to ${raspap_dir}"
@@ -857,8 +809,8 @@ function _configure_networking() {
"-A POSTROUTING -s 192.168.50.0/24 ! -d 192.168.50.0/24 -j MASQUERADE"
)
for rule in "${rules[@]}"; do
if sudo grep -- "$rule" $rulesv4 > /dev/null; then
echo "Rule already exists: ${rule}"
if grep -- "$rule" $rulesv4 > /dev/null; then
echo "Rule already exits: ${rule}"
else
rule=$(sed -e 's/^\(-A POSTROUTING\)/-t nat \1/' <<< $rule)
echo "Adding rule: ${rule}"

3
installers/disablelog.sh Executable file
View File

@@ -0,0 +1,3 @@
#!/bin/bash
/bin/sed -i 's|DAEMON_OPTS=" -f /tmp/hostapd.log"|#DAEMON_OPTS=""|' /etc/default/hostapd

3
installers/enablelog.sh Executable file
View File

@@ -0,0 +1,3 @@
#!/bin/bash
/bin/sed -i 's|#DAEMON_OPTS=""|DAEMON_OPTS=" -f /tmp/hostapd.log"|' /etc/default/hostapd
touch /tmp/hostapd.log

View File

@@ -1,173 +0,0 @@
#!/bin/bash
#
# RaspAP OS Subitem Generator for RPi Imager 2.x
# Author: @billz <billzimmerman@gmail.com>
# Author URI: https://github.com/billz
# Project URI: https://github.com/RaspAP/
# License: GNU General Public License v3.0
# License URI: https://github.com/RaspAP/raspap-webgui/blob/master/LICENSE
# Exit on error
set -o errexit
# Exit on error inside functions
set -o errtrace
# Set defaults
NAME="RaspAP"
WEBSITE="https://raspap.com/"
ICON="https://raspap.com/assets/images/raspAP-logo.svg"
DESCRIPTION="The easiest, full-featured wireless router for Debian-based devices."
REPO="RaspAP/raspap-webgui"
# Fetch latest release from GitHub (adapted from RaspAP installer)
_get_release() {
local response
local host="api.github.com"
echo "Fetching latest release from GitHub..." >&2
response=$(curl -s "https://$host/repos/$REPO/releases/latest")
if echo "$response" | grep -q 'API rate limit exceeded'; then
echo "Error: GitHub API rate limit exceeded. Try again later or use a GitHub token." >&2
exit 1
fi
VERSION=$(echo "$response" | grep -o '"tag_name": *"[^"]*"' | sed 's/"tag_name": *"\(.*\)"/\1/')
if [ -z "$VERSION" ]; then
echo "Error: Failed to fetch latest release. Check network connectivity." >&2
exit 1
fi
local published_at=$(echo "$response" | grep -o '"published_at": *"[^"]*"' | sed 's/"published_at": *"\(.*\)"/\1/')
RELEASE_DATE=$(echo "$published_at" | cut -d'T' -f1)
echo "Found release: $VERSION (published: $RELEASE_DATE)" >&2
}
# Fetch latest release info (if not provided via environment variables)
if [ -z "$VERSION" ] || [ -z "$RELEASE_DATE" ]; then
_get_release
else
echo "Using VERSION=$VERSION and RELEASE_DATE=$RELEASE_DATE from environment" >&2
fi
BASE_URL="https://github.com/RaspAP/raspap-webgui/releases/download/${VERSION}"
_get_size() {
if [ -f "$1" ]; then
if [[ "$OSTYPE" == "darwin"* ]]; then
stat -f %z "$1" 2>/dev/null
else
stat -c %s "$1" 2>/dev/null
fi
else
echo "0"
fi
}
_get_sha256() {
if [ -f "$1" ]; then
if command -v sha256sum &> /dev/null; then
sha256sum "$1" | awk '{print $1}'
else
shasum -a 256 "$1" | awk '{print $1}'
fi
else
echo ""
fi
}
# Process armhf (32-bit)
ARMHF_ZIP="raspap-trixie-armhf-lite-${VERSION}.img.zip"
if [ -f "$ARMHF_ZIP" ]; then
ARMHF_IMG=$(ls *-raspap-trixie-armhf-lite-${VERSION}.img 2>/dev/null | head -1)
if [ -z "$ARMHF_IMG" ]; then
echo "Extracting $ARMHF_ZIP..."
unzip -o "$ARMHF_ZIP"
ARMHF_IMG=$(ls *-raspap-trixie-armhf-lite-${VERSION}.img 2>/dev/null | head -1)
fi
else
ARMHF_IMG=""
fi
ARMHF_EXTRACT_SIZE=$(_get_size "$ARMHF_IMG")
ARMHF_EXTRACT_SHA=$(_get_sha256 "$ARMHF_IMG")
ARMHF_DOWNLOAD_SIZE=$(_get_size "$ARMHF_ZIP")
ARMHF_DOWNLOAD_SHA=$(_get_sha256 "$ARMHF_ZIP")
# Process arm64 (64-bit)
ARM64_ZIP="raspap-trixie-arm64-lite-${VERSION}.img.zip"
if [ -f "$ARM64_ZIP" ]; then
ARM64_IMG=$(ls *-raspap-trixie-arm64-lite-${VERSION}.img 2>/dev/null | head -1)
if [ -z "$ARM64_IMG" ]; then
echo "Extracting $ARM64_ZIP..."
unzip -o "$ARM64_ZIP"
ARM64_IMG=$(ls *-raspap-trixie-arm64-lite-${VERSION}.img 2>/dev/null | head -1)
fi
else
ARM64_IMG=""
fi
ARM64_EXTRACT_SIZE=$(_get_size "$ARM64_IMG")
ARM64_EXTRACT_SHA=$(_get_sha256 "$ARM64_IMG")
ARM64_DOWNLOAD_SIZE=$(_get_size "$ARM64_ZIP")
ARM64_DOWNLOAD_SHA=$(_get_sha256 "$ARM64_ZIP")
# Generate JSON
cat > os-sublist-raspap.json << EOF
{
"name": "${NAME}",
"description": "${DESCRIPTION}",
"icon": "${ICON}",
"random": false,
"subitems": [
{
"name": "${NAME} 32-bit (armhf)",
"description": "${DESCRIPTION}",
"icon": "${ICON}",
"url": "${BASE_URL}/${ARMHF_ZIP}",
"extract_size": ${ARMHF_EXTRACT_SIZE},
"extract_sha256": "${ARMHF_EXTRACT_SHA}",
"image_download_size": ${ARMHF_DOWNLOAD_SIZE},
"image_download_sha256": "${ARMHF_DOWNLOAD_SHA}",
"release_date": "${RELEASE_DATE}",
"init_format": "systemd",
"devices": [
"pi5-32bit",
"pi4-32bit",
"pi3-32bit",
"pi2-32bit"
],
"capabilities": []
},
{
"name": "${NAME} 64-bit (arm64)",
"description": "${DESCRIPTION}",
"icon": "${ICON}",
"url": "${BASE_URL}/${ARM64_ZIP}",
"extract_size": ${ARM64_EXTRACT_SIZE},
"extract_sha256": "${ARM64_EXTRACT_SHA}",
"image_download_size": ${ARM64_DOWNLOAD_SIZE},
"image_download_sha256": "${ARM64_DOWNLOAD_SHA}",
"release_date": "${RELEASE_DATE}",
"init_format": "systemd",
"devices": [
"pi5-64bit",
"pi4-64bit",
"pi3-64bit"
],
"capabilities": []
}
]
}
EOF
echo "Generated os-sublist-raspap.json"
cat os-sublist-raspap.json

View File

@@ -41,21 +41,6 @@ case "$action" in
echo "OK"
;;
"deb")
[ $# -lt 1 ] && { echo "Usage: $0 deb <deb_file>"; exit 1; }
deb_file="$1"
if [ ! -f "$deb_file" ]; then
echo "Error: File not found: $deb_file"
exit 1
fi
echo "Installing .deb package: $deb_file"
dpkg -i "$deb_file"
echo "OK"
;;
"user")
[ $# -lt 2 ] && { echo "Usage: $0 user <username> <password>."; exit 1; }

View File

@@ -9,16 +9,14 @@ www-data ALL=(ALL) NOPASSWD:/sbin/wpa_supplicant -i [a-zA-Z0-9]* -c /etc/wpa_sup
www-data ALL=(ALL) NOPASSWD:/bin/rm /var/run/wpa_supplicant/[a-zA-Z0-9]*
www-data ALL=(ALL) NOPASSWD:/sbin/wpa_cli -i [a-zA-Z0-9]* scan_results
www-data ALL=(ALL) NOPASSWD:/sbin/wpa_cli -i [a-zA-Z0-9]* scan
www-data ALL=(ALL) NOPASSWD:/sbin/wpa_cli -i [a-zA-Z0-9]* status
www-data ALL=(ALL) NOPASSWD:/sbin/wpa_cli -i [a-zA-Z0-9]* reconfigure
www-data ALL=(ALL) NOPASSWD:/sbin/wpa_cli -i [a-zA-Z0-9]* add_network
www-data ALL=(ALL) NOPASSWD:/sbin/wpa_cli -i [a-zA-Z0-9]* list_networks
www-data ALL=(ALL) NOPASSWD:/sbin/wpa_cli -i [a-zA-Z0-9]* enable_network [0-9]
www-data ALL=(ALL) NOPASSWD:/sbin/wpa_cli -i [a-zA-Z0-9]* disconnect [0-9]
www-data ALL=(ALL) NOPASSWD:/sbin/wpa_cli -i enable_network [0-9]
www-data ALL=(ALL) NOPASSWD:/sbin/wpa_cli -i disconnect [0-9]
www-data ALL=(ALL) NOPASSWD:/sbin/wpa_cli -i [a-zA-Z0-9]* select_network [0-9]
www-data ALL=(ALL) NOPASSWD:/sbin/wpa_cli -i [a-zA-Z0-9]* set_network [0-9] *
www-data ALL=(ALL) NOPASSWD:/sbin/wpa_cli -i [a-zA-Z0-9]* remove_network [0-9]
www-data ALL=(ALL) NOPASSWD:/bin/rm -f /var/run/wpa_supplicant/wlan[0-9]
www-data ALL=(ALL) NOPASSWD:/bin/cp /tmp/hostapddata /etc/hostapd/hostapd.conf
www-data ALL=(ALL) NOPASSWD:/bin/systemctl start hostapd.service
www-data ALL=(ALL) NOPASSWD:/bin/systemctl stop hostapd.service
@@ -49,7 +47,6 @@ www-data ALL=(ALL) NOPASSWD:/sbin/ip -s a f label wl*
www-data ALL=(ALL) NOPASSWD:/sbin/ifup *
www-data ALL=(ALL) NOPASSWD:/sbin/ifdown *
www-data ALL=(ALL) NOPASSWD:/sbin/iw dev*
www-data ALL=(ALL) NOPASSWD:/sbin/iw reg set*
www-data ALL=(ALL) NOPASSWD:/bin/cp /etc/raspap/networking/dhcpcd.conf /etc/dhcpcd.conf
www-data ALL=(ALL) NOPASSWD:/etc/raspap/hostapd/enablelog.sh
www-data ALL=(ALL) NOPASSWD:/etc/raspap/hostapd/disablelog.sh
@@ -93,4 +90,3 @@ www-data ALL=(ALL) NOPASSWD:/etc/raspap/plugins/plugin_helper.sh
www-data ALL=(ALL) NOPASSWD:/bin/systemctl start raspap-network-activity@*.service
www-data ALL=(ALL) NOPASSWD:/bin/systemctl stop raspap-network-activity@*.service
www-data ALL=(ALL) NOPASSWD:/bin/cp /tmp/wpa_conf_* /etc/wpa_supplicant/wpa_supplicant.conf
www-data ALL=(ALL) NOPASSWD:/usr/bin/journalctl -u hostapd.service *

View File

@@ -49,7 +49,6 @@ OPTIONS:
-b, --branch <name> Overrides the default git branch (latest release)
-t, --token <accesstoken> Specify a GitHub token to access a private repository
-n, --name <username> Specify a GitHub username to access a private repository
-x, --use-ssh Use ssh instead of https for git
-u, --upgrade Upgrades an existing installation to the latest release version
-d, --update Updates an existing installation to the latest release version
-p, --path <path> Used with -d, --update, sets the existing install path
@@ -57,7 +56,7 @@ OPTIONS:
-m, --minwrite Configures a microSD card for minimum write operation
-k, --check <flag> Sets the connectivity check flag (default is 1=perform check)
-v, --version Outputs release info and exits
-z, --uninstall Loads and executes the uninstaller
-n, --uninstall Loads and executes the uninstaller
-h, --help Outputs usage notes and exits
Examples:
@@ -106,9 +105,7 @@ function _parse_params() {
restapi_option=1
adblock_option=1
wg_option=1
pv_option=0
insiders=0
ssh=0
minwrite=0
acctoken=""
path=""
@@ -168,9 +165,6 @@ function _parse_params() {
-m|--minwrite)
minwrite=1
;;
-x|--use-ssh)
ssh=1
;;
-t|--token)
acctoken="$2"
shift
@@ -193,7 +187,7 @@ function _parse_params() {
-v|--version)
_version
;;
-z|--uninstall)
-n|--uninstall)
uninstall=1
;;
-*|--*)

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

@@ -10,7 +10,7 @@ msgstr ""
"Project-Id-Version: 1.2.1\n"
"Report-Msgid-Bugs-To: Bill Zimmerman <billzimmerman@gmail.com>\n"
"POT-Creation-Date: 2017-10-19 08:56+0000\n"
"PO-Revision-Date: 2025-10-25 08:35+0000\n"
"PO-Revision-Date: 2022-01-05 20:35+0000\n"
"Last-Translator: Bill Zimmerman <billzimmerman@gmail.com>\n"
"Language-Team: \n"
"Language: en_US\n"
@@ -34,15 +34,9 @@ msgstr "Dashboard"
msgid "WiFi client"
msgstr "WiFi client"
msgid "Status"
msgstr "Status"
msgid "Hotspot"
msgstr "Hotspot"
msgid "Logging"
msgstr "Logging"
msgid "Mem Use"
msgstr "Mem Use"
@@ -420,6 +414,9 @@ msgstr "Signal quality"
msgid "WAN IP"
msgstr "WAN IP"
msgid "Web-GUI"
msgstr "Web-GUI"
msgid "Signal strength"
msgstr "Signal strength"
@@ -446,24 +443,6 @@ msgstr "Thanks for being an Insider"
msgid "DHCP server settings"
msgstr "DHCP server settings"
msgid "Default gateway"
msgstr "Default gateway"
msgid "Install a default route for this interface"
msgstr "Install a default route for this interface"
msgid "Static IP options"
msgstr "Static IP options"
msgid "DHCP options"
msgstr "DHCP options"
msgid "Enable this option if you want RaspAP to assign IP addresses to clients on the selected interface. A static IP address is required for this option."
msgstr "Enable this option if you want RaspAP to assign IP addresses to clients on the selected interface. A static IP address is required for this option."
msgid "Enable this only if you want your device to use this interface as its primary route to the internet."
msgstr "Enable this only if you want your device to use this interface as its primary route to the internet."
msgid "Client list"
msgstr "Client list"
@@ -602,8 +581,11 @@ msgstr "This option adds <code>dhcp-host</code> entries to the dnsmasq configura
msgid "This toggles the <code>gateway</code>/<code>nogateway</code> option for this interface in the dhcpcd.conf file."
msgstr "This toggles the <code>gateway</code>/<code>nogateway</code> option for this interface in the dhcpcd.conf file."
msgid "This toggles the <code>nohook wpa_supplicant</code> option for this interface in the dhcpcd.conf file."
msgstr "This toggles the <code>nohook wpa_supplicant</code> option for this interface in the dhcpcd.conf file."
msgid "This toggles the <code>nohook wpa_supplicant</code> option for this interface in the DHCPCD configuration."
msgstr "This toggles the <code>nohook wpa_supplicant</code> option for this interface in the DHCPCD configuration."
msgid "Enable this only if you want your device to use this interface as its primary route to the internet."
msgstr "Enable this only if you want your device to use this interface as its primary route to the internet."
msgid "Disable wpa_supplicant dhcp hook for this interface"
msgstr "Disable wpa_supplicant dhcp hook for this interface"
@@ -744,27 +726,6 @@ msgstr "Enable logging"
msgid "Logfile output"
msgstr "Logfile output"
msgid "Log level"
msgstr "Log level"
msgid "Higher levels reduce log verbosity. Informational is recommended."
msgstr "Higher levels reduce log verbosity. Informational is recommended."
msgid "Verbose debugging"
msgstr "Verbose debugging"
msgid "Debugging"
msgstr "Debugging"
msgid "Informational"
msgstr "Informational"
msgid "Notification"
msgstr "Notification"
msgid "Warning"
msgstr "Warning"
msgid "WiFi client AP mode"
msgstr "WiFi client AP mode"
@@ -774,9 +735,6 @@ msgstr "Bridged AP mode"
msgid "WiFi repeater mode"
msgstr "WiFi repeater mode"
msgid "Dual band AP mode"
msgstr "Dual band AP mode"
msgid "Hide SSID in broadcast"
msgstr "Hide SSID in broadcast"
@@ -879,75 +837,6 @@ msgstr "Parameter hiddenSSID contains invalid configuration value."
msgid "Parameter hiddenSSID is not a number."
msgstr "Parameter hiddenSSID is not a number."
msgid "Bridge interface configuration"
msgstr "Bridge interface configuration"
msgid "Configure a static IP address for the <code>br0</code> interface to maintain connectivity during bridge mode activation."
msgstr "Configure a static IP address for the <code>br0</code> interface to maintain connectivity during bridge mode activation."
msgid "Static IP Address"
msgstr "Static IP Address"
msgid "Netmask / CIDR"
msgstr "Netmask / CIDR"
msgid "Example: 192.168.1.100"
msgstr "Example: 192.168.1.100"
msgid "CIDR notation (e.g., 24 for 255.255.255.0)"
msgstr "CIDR notation (e.g., 24 for 255.255.255.0)"
msgid "Gateway"
msgstr "Gateway"
msgid "Your router's IP address"
msgstr "Your router's IP address"
msgid "Usually same as gateway"
msgstr "Usually same as gateway"
msgid "Bridge static IP address must be a valid IPv4 address"
msgstr "Bridge static IP address must be a valid IPv4 address"
msgid "Bridge netmask must be a number between 1 and 32"
msgstr "Bridge netmask must be a number between 1 and 32"
msgid "Bridge netmask is required when using static IP"
msgstr "Bridge netmask is required when using static IP"
msgid "Bridge gateway must be a valid IPv4 address"
msgstr "Bridge gateway must be a valid IPv4 address"
msgid "Bridge gateway is required when using static IP"
msgstr "Bridge gateway is required when using static IP"
msgid "Bridge DNS server must be a valid IPv4 address"
msgstr "Bridge DNS server must be a valid IPv4 address"
msgid "Bridge DNS server is required when using static IP"
msgstr "Bridge DNS server is required when using static IP"
msgid "Bridge static IP and gateway must be in the same subnet"
msgstr "Bridge static IP and gateway must be in the same subnet"
msgid "Please enter a valid IPv4 address"
msgstr "Please enter a valid IPv4 address"
msgid "Please enter a valid netmask"
msgstr "Please enter a valid netmask"
msgid "DHCP configuration for br0 enabled"
msgstr "DHCP configuration for br0 enabled"
msgid "Unable to save WiFi hotspot settings due to validation errors"
msgstr "Unable to save WiFi hotspot settings due to validation errors"
msgid "Enable AP isolation"
msgstr "Enable AP isolation"
msgid "Blocks wireless clients from seeing or connecting to each other. Recommended for guest networks and public access points."
msgstr "Blocks wireless clients from seeing or connecting to each other. Recommended for guest networks and public access points."
#: includes/networking.php
msgid "Summary"
msgstr "Summary"
@@ -967,11 +856,8 @@ msgstr "Alternate DNS Server"
msgid "Adapter IP Address Settings"
msgstr "Adapter IP Address Settings"
msgid "Enable fallback to static option"
msgstr "Enable fallback to static option"
msgid "Enable this option to configure a static profile and fall back to it when DHCP lease fails."
msgstr "Enable this option to configure a static profile and fall back to it when DHCP lease fails."
msgid "Enable Fallback to Static Option"
msgstr "Enable Fallback to Static Option"
msgid "Static IP"
msgstr "Static IP"
@@ -1000,14 +886,14 @@ msgstr "Devices"
msgid "Diagnostics"
msgstr "Diagnostics"
msgid "Network devices"
msgstr "Network devices"
msgid "Properties of network devices"
msgstr "Properties of network devices"
msgid "Device"
msgstr "Device"
msgid "MAC address"
msgstr "MAC address"
msgid "MAC"
msgstr "MAC"
msgid "USB vid/pid"
msgstr "USB vid/pid"
@@ -1021,11 +907,11 @@ msgstr "Fixed name"
msgid "Change"
msgstr "Change"
msgid "Mobile data settings"
msgstr "Mobile data settings"
msgid "Settings for Mobile Data Devices"
msgstr "Settings for Mobile Data Devices"
msgid "SIM card PIN number"
msgstr "SIM card PIN number"
msgid "PIN of SIM card"
msgstr "PIN of SIM card"
msgid "APN Settings (Modem device ppp0)"
msgstr "APN Settings (Modem device ppp0)"
@@ -1184,9 +1070,6 @@ msgstr "Storage Used"
msgid "CPU Load"
msgstr "CPU Load"
msgid "CPU Temp"
msgstr "CPU Temp"
msgid "Reboot"
msgstr "Reboot"
@@ -1373,32 +1256,11 @@ msgstr "Installed"
msgid "Alert messages"
msgstr "Alert messages"
msgid "Alert close timeout (milliseconds)"
msgstr "Alert close timeout (milliseconds)"
msgid "Automatically close alerts after a specified timeout"
msgstr "Automatically close alerts after a specified timeout"
msgid "To <a href=\"%s\" target=\"_blank\">inspect adapters</a> attached to this device, click or tap the button below."
msgstr "To <a href=\"%s\" target=\"_blank\">inspect adapters</a> attached to this device, click or tap the button below."
msgid "The adapter inspection tool returns details about external WLAN devices including drivers, supported modes and so on."
msgstr "The adapter inspection tool returns details about external WLAN devices including drivers, supported modes and so on."
msgid "Choose a network interface to inspect"
msgstr "Choose a network interface to inspect"
msgid "Select an interface..."
msgstr "Select an interface..."
msgid "Adapter health check"
msgstr "Adapter health check"
msgid "Inspect adapters"
msgstr "Inspect adapters"
msgid "Theme"
msgstr "Theme"
msgid "Alert close timeout (milliseconds)"
msgstr "Alert close timeout (milliseconds)"
#: includes/data_usage.php
msgid "Data usage"
@@ -2107,27 +1969,6 @@ msgstr "Update complete"
msgid "An error occurred. Check the log at <code>/tmp/raspap_install.log</code>"
msgstr "An error occurred. Check the log at <code>/tmp/raspap_install.log</code>"
msgid "RaspAP is a co-creation of %1$s and %2$s with the contributions of our %3$s and %4$s. Learn more about joining the project as a %5$s, %6$s or %7$s with immediate access to %8$s available to %9$s."
msgstr "RaspAP is a co-creation of %1$s and %2$s with the contributions of our %3$s and %4$s. Learn more about joining the project as a %5$s, %6$s or %7$s with immediate access to %8$s available to %9$s."
msgid "developer community"
msgstr "developer community"
msgid "language translators"
msgstr "language translators"
msgid "code contributor"
msgstr "code contributor"
msgid "translator"
msgstr "translator"
msgid "financial sponsor"
msgstr "financial sponsor"
msgid "exclusive features"
msgstr "exclusive features"
#: includes/exceptions.php
msgid "RaspAP Exception"
@@ -2357,30 +2198,12 @@ msgstr "Configure exit node"
msgid "The device <code>%s</code> is connected to your tailnet with the address <code>%s</code>."
msgstr "The device <code>%s</code> is connected to your tailnet with the address <code>%s</code>."
msgid "By default, Tailscale only routes traffic between the devices on which it's been installed. You can also route all your public internet traffic by configuring a device on your network as a <strong>exit node</strong>"
msgstr "By default, Tailscale only routes traffic between the devices on which it's been installed. You can also route all your public internet traffic by configuring a device on your network as a <strong>exit node</strong>"
msgid "By default, Tailscale only routes traffic between the devices on which it's been installed. By configuring <code>%s</code> as an <strong>exit node</strong>, your public internet traffic will be routed through this device"
msgstr "By default, Tailscale only routes traffic between the devices on which it's been installed. By configuring <code>%s</code> as an <strong>exit node</strong>, your public internet traffic will be routed through this device"
msgid "When you route all traffic through an exit node, you're effectively using default routes (0.0.0.0/0, ::/0), similar to how you would if you were using a typical VPN."
msgstr "When you route all traffic through an exit node, you're effectively using default routes (0.0.0.0/0, ::/0), similar to how you would if you were using a typical VPN."
msgid "You have the option of configuring this device as an exit node, or using another exit node in your tailnet."
msgstr "You have the option of configuring this device as an exit node, or using another exit node in your tailnet."
msgid "Select an existing exit node on your tailnet"
msgstr "Select an existing exit node on your tailnet"
msgid "This is a typical configuration if you're using this device as a VPN travel router, for example."
msgstr "This is a typical configuration if you're using this device as a VPN travel router, for example."
msgid "Configure this device as a new exit node"
msgstr "Configure this device as a new exit node"
msgid "By configuring this device as an exit node, public internet traffic from devices connected in your tailnet will be routed through it."
msgstr "By configuring this device as an exit node, public internet traffic from devices connected in your tailnet will be routed through it."
msgid "For security reasons, you must opt in to enable exit node functionality. The first step is to advertise <code>%s</code> as an exit node in your tailnet. In the next step, you'll allow this device to be an exit node."
msgstr "For security reasons, you must opt in to enable exit node functionality. The first step is to advertise <code>%s</code> as an exit node in your tailnet. In the next step, you'll allow this device to be an exit node."
msgid "Advertise <code>%s</code> as an exit node"
msgstr "Advertise <code>%s</code> as an exit node"
@@ -2396,370 +2219,12 @@ msgstr "Recommended for Tailscale exit nodes with Linux 6.2 or later kernels, th
msgid "This option enables transport layer offloads for better performance."
msgstr "This option enables transport layer offloads for better performance."
msgid "Select an exit node"
msgstr "Select an exit node"
msgid "To use <code>%s</code> as a VPN gateway, configure Tailscale to use an exit node. Tailscale's suggested node is indicated with a star."
msgstr "To use <code>%s</code> as a VPN gateway, configure Tailscale to use an exit node. Tailscale's suggested node is indicated with a star."
msgid "Advertise a <strong>subnet route</strong> for the active <code>%s</code> AP interface"
msgstr "Advertise a <strong>subnet route</strong> for the active <code>%s</code> AP interface"
msgid "Subnet routes let you extend your Tailscale network (known as a tailnet) to include devices that don't or can't run the Tailscale client."
msgstr "Subnet routes let you extend your Tailscale network (known as a tailnet) to include devices that don't or can't run the Tailscale client."
msgid "A subnet route acts as a gateway between your tailnet and a physical subnet. The subnet of the active AP interface is preconfigured below; edit if necessary."
msgstr "A subnet route acts as a gateway between your tailnet and a physical subnet. The subnet of the active AP interface is preconfigured below; edit if necessary."
msgid "Route LAN traffic through the exit node."
msgstr "Route LAN traffic through the exit node."
msgid "This will direct all LAN traffic to go through your exit node only."
msgstr "This will direct all LAN traffic to go through your exit node only."
msgid "Choose <strong>Next</strong> to configure <code>%s</code> to use the selected exit node with these options."
msgstr "Choose <strong>Next</strong> to configure <code>%s</code> to use the selected exit node with these options."
msgid "No exit nodes found on your tailnet. Choose <strong>Back</strong> to continue."
msgstr "No exit nodes found on your tailnet. Choose <strong>Back</strong> to continue."
msgid "Using exit node"
msgstr "Using exit node"
msgid "The device <code>%s</code> is configured to use exit node <code>%s</code>. It has the Tailscale MagicDNS address <code>%s</code>."
msgstr "The device <code>%s</code> is configured to use exit node <code>%s</code>. It has the Tailscale MagicDNS address <code>%s</code>."
msgid "Choose <strong>Save settings</strong> to continue."
msgstr "Choose <strong>Save settings</strong> to continue."
msgid "Choose <strong>Next</strong> to continue."
msgstr "Choose <strong>Next</strong> to continue."
msgid "Tailnet status"
msgstr "Tailnet status"
msgid "Current <code>tailnet</code> status is displayed below."
msgstr "Current <code>tailnet</code> status is displayed below."
msgid "Use Tailscale DNS settings (default)."
msgstr "Use Tailscale DNS settings (default)."
msgid "Uncheck to use local DNS. This sets <code>--accept-dns=false</code>."
msgstr "Uncheck to use local DNS. This sets <code>--accept-dns=false</code>."
msgid "Do not use Tailscale subnets (default on Linux)."
msgstr "Do not use Tailscale subnets (default on Linux)."
msgid "If subnet routes exist for your tailnet, you can route your device's traffic to a subnet router. Enabling this sets <code>--accept-routes=true</code>."
msgstr "If subnet routes exist for your tailnet, you can route your device's traffic to a subnet router. Enabling this sets <code>--accept-routes=true</code>."
msgid "If keys expire for a device, connections to/from the given endpoint will stop working."
msgstr "If keys expire for a device, connections to/from the given endpoint will stop working."
msgid "This option uses <code>--force-reauth</code> to renew the keys for this device."
msgstr "This option uses <code>--force-reauth</code> to renew the keys for this device."
#: wireshark plugin
msgid "Start capture"
msgstr "Start capture"
msgid "Stop capture"
msgstr "Stop capture"
msgid "Capture files"
msgstr "Capture files"
msgid "Capture interface"
msgstr "Capture interface"
msgid "Output file"
msgstr "Output file"
msgid "Path where capture file will be saved (.pcap format)"
msgstr "Path where capture file will be saved (.pcap format)"
msgid "File will be saved with .pcap extension"
msgstr "File will be saved with .pcap extension"
msgid "Capture filter (BPF syntax)"
msgstr "Capture filter (BPF syntax)"
msgid "Berkeley Packet Filter syntax. Leave empty to capture all traffic."
msgstr "Berkeley Packet Filter syntax. Leave empty to capture all traffic."
msgid "Examples: <code>port 80</code>, <code>host 192.168.1.1</code>, <code>tcp and not port 22</code>"
msgstr "Examples: <code>port 80</code>, <code>host 192.168.1.1</code>, <code>tcp and not port 22</code>"
msgid "Capture limits"
msgstr "Capture limits"
msgid "Packet count limit"
msgstr "Packet count limit"
msgid "Stop capture after this many packets. Leave empty for unlimited."
msgstr "Stop capture after this many packets. Leave empty for unlimited."
msgid "Duration limit (seconds)"
msgstr "Duration limit (seconds)"
msgid "Stop capture after this many seconds. Leave empty for unlimited."
msgstr "Stop capture after this many seconds. Leave empty for unlimited."
msgid "Ring buffer settings"
msgstr "Ring buffer settings"
msgid "File size (KB)"
msgstr "File size (KB)"
msgid "Create new file when this size is reached. Leave empty to disable."
msgstr "Create new file when this size is reached. Leave empty to disable."
msgid "10000 = 10 MB per file"
msgstr "10000 = 10 MB per file"
msgid "Number of files"
msgstr "Number of files"
msgid "Maximum number of ring buffer files to keep. Oldest files are deleted."
msgstr "Maximum number of ring buffer files to keep. Oldest files are deleted."
msgid "Advanced options"
msgstr "Advanced options"
msgid "Snapshot length (bytes)"
msgstr "Snapshot length (bytes)"
msgid "Limit the amount of data captured per packet. Leave empty for full packets."
msgstr "Limit the amount of data captured per packet. Leave empty for full packets."
msgid "96 bytes captures headers only, reduces file size"
msgstr "96 bytes captures headers only, reduces file size"
msgid "Promiscuous mode"
msgstr "Promiscuous mode"
msgid "Capture all packets on the network segment, not just those destined for this interface"
msgstr "Capture all packets on the network segment, not just those destined for this interface"
msgid "Quick filter presets"
msgstr "Quick filter presets"
msgid "Capture files generated by <code>tshark</code> are displayed below."
msgstr "Capture files generated by <code>tshark</code> are displayed below."
msgid "No capture files found in /tmp directory"
msgstr "No capture files found in /tmp directory"
msgid "Filename"
msgstr "Filename"
msgid "Size"
msgstr "Size"
msgid "Modified"
msgstr "Modified"
msgid "Actions"
msgstr "Actions"
msgid "Download file"
msgstr "Download file"
msgid "Delete file"
msgstr "Delete file"
msgid "Confirm deletion"
msgstr "Confirm deletion"
msgid "Are you sure you want to delete this file?"
msgstr "Are you sure you want to delete this file?"
msgid "All Traffic"
msgstr "All Traffic"
msgid "HTTP/HTTPS"
msgstr "HTTP/HTTPS"
msgid "ICMP (Ping)"
msgstr "ICMP (Ping)"
msgid "SSH"
msgstr "SSH"
msgid "Exclude SSH"
msgstr "Exclude SSH"
msgid "A Wireshark (TShark) CLI packet capture for RaspAP"
msgstr "A Wireshark (TShark) CLI packet capture for RaspAP"
msgid "Information provided by tshark"
msgstr "Information provided by tshark"
msgid "Total: %d file(s), %s"
msgstr "Total: %d file(s), %s"
#: captive portal plugin
msgid "Captive portal"
msgstr "Captive portal"
msgid "Gateway interface"
msgstr "Gateway interface"
msgid "Gateway name"
msgstr "Gateway name"
msgid "Gateway address"
msgstr "Gateway address"
msgid "Gateway port"
msgstr "Gateway port"
msgid "Defaults to the active AP interface, typically <code>wlan0</code>"
msgstr "Defaults to the active AP interface, typically <code>wlan0</code>"
msgid "Auto-detected from gateway interface if not specified"
msgstr "Auto-detected from gateway interface if not specified"
msgid "Start portal"
msgstr "Start portal"
msgid "Stop portal"
msgstr "Stop portal"
msgid "Information provided by nodogsplash"
msgstr "Information provided by nodogsplash"
msgid "Stop portal service"
msgstr "Stop portal service"
msgid "Start portal service"
msgstr "Start portal service"
msgid "Changing the portal service will momentarily disrupt client traffic. Choose <strong>Proceed</strong> to continue."
msgstr "Changing the portal service will momentarily disrupt client traffic. Choose <strong>Proceed</strong> to continue."
msgid "Interface to be managed by the portal"
msgstr "Interface to be managed by the portal"
msgid "Name of your gateway (available as \\$gatewayname variable)"
msgstr "Name of your gateway (available as \\$gatewayname variable)"
msgid "IP address of the router. Leave empty for auto-detection"
msgstr "IP address of the router. Leave empty for auto-detection"
msgid "Port for Nodogsplash HTTP server"
msgstr "Port for Nodogsplash HTTP server"
msgid "Maximum clients"
msgstr "Maximum clients"
msgid "Session timeout (minutes)"
msgstr "Session timeout (minutes)"
msgid "Pre-auth idle timeout (minutes)"
msgstr "Pre-auth idle timeout (minutes)"
msgid "Does not include users on the trusted MAC list"
msgstr "Does not include users on the trusted MAC list"
msgid "Auth idle timeout (minutes)"
msgstr "Auth idle timeout (minutes)"
msgid "Check interval (seconds)"
msgstr "Check interval (seconds)"
msgid "MAC address control"
msgstr "MAC address control"
msgid "MAC mechanism"
msgstr "MAC mechanism"
msgid "Blocked MAC list"
msgstr "Blocked MAC list"
msgid "Trusted MAC list"
msgstr "Trusted MAC list"
msgid "These devices are not subject to authentication or firewall rules"
msgstr "These devices are not subject to authentication or firewall rules"
msgid "Maximum number of concurrent authenticated users"
msgstr "Maximum number of concurrent authenticated users"
msgid "Default session length in minutes. 0 = unlimited"
msgstr "Default session length in minutes. 0 = unlimited"
msgid "Time before unauthenticated idle users are removed"
msgstr "Time before unauthenticated idle users are removed"
msgid "Time before authenticated idle users are deauthenticated"
msgstr "Time before authenticated idle users are deauthenticated"
msgid "How often to check client timeouts"
msgstr "How often to check client timeouts"
msgid "Block: blocklisted MACs are blocked. Allow: only allowlisted MACs are allowed"
msgstr "Block: blocklisted MACs are blocked. Allow: only allowlisted MACs are allowed"
msgid "Example: <code>00:11:22:33:44:55,AA:BB:CC:DD:EE:FF</code>"
msgstr "Example: <code>00:11:22:33:44:55,AA:BB:CC:DD:EE:FF</code>"
msgid "Comma-separated MAC addresses that bypass authentication entirely"
msgstr "Comma-separated MAC addresses that bypass authentication entirely"
msgid "Block (blocklist mode)"
msgstr "Block (blocklist mode)"
msgid "Allow (allowlist mode)"
msgstr "Allow (allowlist mode)"
msgid "Gateway IP range"
msgstr "Gateway IP range"
msgid "Default: 0.0.0.0/0 (all addresses)"
msgstr "Default: 0.0.0.0/0 (all addresses)"
msgid "Debug level"
msgstr "Debug level"
msgid "Firewall settings"
msgstr "Firewall settings"
msgid "Allow all traffic for authenticated users"
msgstr "Allow all traffic for authenticated users"
msgid "Allow DNS for pre-authenticated users"
msgstr "Allow DNS for pre-authenticated users"
msgid "IP range to manage in CIDR notation. Leave empty for all addresses"
msgstr "IP range to manage in CIDR notation. Leave empty for all addresses"
msgid "Amount of logging detail reported by the nodogsplash.service"
msgstr "Amount of logging detail reported by the nodogsplash.service"
msgid "0 - Errors only"
msgstr "0 - Errors only"
msgid "1 - Errors, warnings, infos"
msgstr "1 - Errors, warnings, infos"
msgid "2 - Errors, warnings, infos, verbose"
msgstr "2 - Errors, warnings, infos, verbose"
msgid "3 - Errors, warnings, infos, verbose, debug"
msgstr "3 - Errors, warnings, infos, verbose, debug"
msgid "When enabled, authenticated users have unrestricted access"
msgstr "When enabled, authenticated users have unrestricted access"
msgid "Required for clients to resolve domain names before authentication"
msgstr "Required for clients to resolve domain names before authentication"
msgid "Portal status"
msgstr "Portal status"
msgid "Current <code>nodogsplash</code> status is displayed below."
msgstr "Current <code>nodogsplash</code> status is displayed below."

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Submodule plugins updated: 9236df6139...b8e51de448

View File

@@ -79,13 +79,9 @@ class HTTPAuth
*/
public function logout(): void
{
$locale = $_SESSION['locale'] ?? 'en_GB.UTF-8'; // save locale
session_regenerate_id(true); // generate a new session id
session_unset(); // unset all session variables
session_destroy(); // destroy the session
session_start();
$_SESSION['locale'] = $locale;
setcookie('locale', $locale, time() + (86400 * 30), '/', '', false, true);
$basePath = rtrim(dirname($_SERVER['SCRIPT_NAME']), '/');
$redirectUrl = $_SERVER['REQUEST_URI'];
if (strpos($redirectUrl, '/login') === false) {

View File

@@ -1,258 +0,0 @@
<?php
/**
* Locale Manager class
*
* @description Manages locale/i18n functionality for RaspAP
* @author Bill Zimmerman <billzimmerman@gmail.com>
* @license https://github.com/raspap/raspap-webgui/blob/master/LICENSE
*/
namespace RaspAP\Localization;
class LocaleManager
{
private string $locale;
private const DEFAULT_LOCALE = 'en_GB.UTF-8';
private const COOKIE_LIFETIME = 86400 * 30; // 30 days
/**
* Constructor
*/
public function __construct()
{
$this->locale = self::DEFAULT_LOCALE;
}
/**
* Get all available locales
*
* @return array Associative array of locale codes to language names
*/
public function getLocales(): array
{
return [
'en_GB.UTF-8' => 'English',
'cs_CZ.UTF-8' => 'Čeština',
'zh_TW.UTF-8' => '正體中文 (Chinese traditional)',
'zh_CN.UTF-8' => '简体中文 (Chinese simplified)',
'da_DK.UTF-8' => 'Dansk',
'de_DE.UTF-8' => 'Deutsch',
'es_MX.UTF-8' => 'Español',
'fi_FI.UTF-8' => 'Finnish',
'fr_FR.UTF-8' => 'Français',
'el_GR.UTF-8' => 'Ελληνικά',
'id_ID.UTF-8' => 'Indonesian',
'it_IT.UTF-8' => 'Italiano',
'ja_JP.UTF-8' => '日本語 (Japanese)',
'ko_KR.UTF-8' => '한국어 (Korean)',
'nl_NL.UTF-8' => 'Nederlands',
'pl_PL.UTF-8' => 'Polskie',
'pt_BR.UTF-8' => 'Português',
'ru_RU.UTF-8' => 'Русский',
'ro_RO.UTF-8' => 'Română',
'sk_SK.UTF-8' => 'Slovenčina',
'sv_SE.UTF-8' => 'Svenska',
'tr_TR.UTF-8' => 'Türkçe',
'vi_VN.UTF-8' => 'Tiếng Việt (Vietnamese)'
];
}
/**
* Get valid locale codes
*
* @return array Array of valid locale codes
*/
public function getValidLocales(): array
{
return array_keys($this->getLocales());
}
/**
* Detect browser locale from Accept-Language header
*
* Language detection is performed via the browser.
* Accept-Language returns a list of weighted values with a quality (or 'q') parameter.
* A better method would parse the list of preferred languages and match this with
* the languages supported by our platform.
*
* @see https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4
* @return string Detected locale code
*/
public function detectBrowserLocale(): string
{
if (empty($_SERVER['HTTP_ACCEPT_LANGUAGE']) || strlen($_SERVER['HTTP_ACCEPT_LANGUAGE']) < 2) {
return self::DEFAULT_LOCALE;
}
$acceptLang = $_SERVER['HTTP_ACCEPT_LANGUAGE'];
$lang = strtolower(substr($acceptLang, 0, 2));
if ($lang === 'zh' && strpos($acceptLang, 'zh-TW') === 0) {
return 'zh_TW.UTF-8';
}
switch ($lang) {
case 'de':
return 'de_DE.UTF-8';
case 'fr':
return 'fr_FR.UTF-8';
case 'it':
return 'it_IT.UTF-8';
case 'pt':
return 'pt_BR.UTF-8';
case 'sv':
return 'sv_SE.UTF-8';
case 'nl':
return 'nl_NL.UTF-8';
case 'zh':
return 'zh_CN.UTF-8';
case 'cs':
return 'cs_CZ.UTF-8';
case 'ru':
return 'ru_RU.UTF-8';
case 'es':
return 'es_MX.UTF-8';
case 'fi':
return 'fi_FI.UTF-8';
case 'da':
return 'da_DK.UTF-8';
case 'tr':
return 'tr_TR.UTF-8';
case 'id':
return 'id_ID.UTF-8';
case 'ko':
return 'ko_KR.UTF-8';
case 'ja':
return 'ja_JP.UTF-8';
case 'vi':
return 'vi_VN.UTF-8';
case 'el':
return 'el_GR.UTF-8';
case 'pl':
return 'pl_PL.UTF-8';
case 'sk':
return 'sk_SK.UTF-8';
default:
return self::DEFAULT_LOCALE;
}
}
/**
* Validate if a locale code is valid
*
* Uses $validLocales to mitigate OS Command Injection (CWE-78)
* @see Vulnerability Report for JVN #27202136
*
* @param string $locale Locale code to validate
* @return bool True if valid, false otherwise
*/
public function isValidLocale(string $locale): bool
{
return in_array($locale, $this->getValidLocales(), true);
}
/**
* Set the current locale
*
* @param string $locale Locale code to set
* @return bool true if locale was set, false if invalid
*/
public function setLocale(string $locale): bool
{
if (!$this->isValidLocale($locale)) {
return false;
}
$this->locale = $locale;
return true;
}
/**
* Get the current locale
*
* @return string Current locale code
*/
public function getCurrentLocale(): string
{
return $this->locale;
}
/**
* Initialize locale
*
* @return void
*/
public function initialize(): void
{
$validLocales = $this->getValidLocales();
// Set locale from POST, if provided and valid
if (!empty($_POST['locale']) && in_array($_POST['locale'], $validLocales, true)) {
$_SESSION['locale'] = $_POST['locale'];
$this->setLocaleCookie($_POST['locale']);
}
// Set locale from cookie or browser detection, if not already set in session
if (empty($_SESSION['locale'])) {
if (isset($_COOKIE['locale']) && in_array($_COOKIE['locale'], $validLocales, true)) {
$_SESSION['locale'] = $_COOKIE['locale'];
} else {
$_SESSION['locale'] = $this->detectBrowserLocale();
$this->setLocaleCookie($_SESSION['locale']);
}
}
// Enforce only valid locale values in session
if (!in_array($_SESSION['locale'], $validLocales, true)) {
$_SESSION['locale'] = self::DEFAULT_LOCALE;
$this->setLocaleCookie($_SESSION['locale']);
}
// Update internal state
$this->locale = $_SESSION['locale'];
}
/**
* Apply locale settings to the system
*
* Sets environment variables and configures gettext
*
* @return void
*/
public function applyLocale(): void
{
putenv("LANG=" . escapeshellarg($this->locale));
setlocale(LC_ALL, $this->locale);
// Use global constants if defined, otherwise use defaults
$localeDomain = defined('LOCALE_DOMAIN') ? LOCALE_DOMAIN : 'messages';
$localeRoot = defined('LOCALE_ROOT') ? LOCALE_ROOT : 'locale';
bindtextdomain($localeDomain, $localeRoot);
bind_textdomain_codeset($localeDomain, 'UTF-8');
textdomain($localeDomain);
}
/**
* Set locale cookie
*
* @param string $locale Locale code to store in cookie
* @return void
*/
private function setLocaleCookie(string $locale): void
{
setcookie('locale', $locale, time() + self::COOKIE_LIFETIME, '/', '', false, true);
}
/**
* Initialize and apply locale in one call
*
* @return void
*/
public function initializeAndApply(): void
{
$this->initialize();
$this->applyLocale();
}
}

View File

@@ -28,7 +28,7 @@ class DhcpcdManager
* @param bool $wifiAPEnable
* @param bool $dualAPEnable
* @param StatusMessage $status
* @return string
* @return string
*/
public function buildConfig(
string $ap_iface,
@@ -36,7 +36,6 @@ class DhcpcdManager
bool $repeaterEnable,
bool $wifiAPEnable,
bool $dualAPEnable,
?array $bridgeConfig = null,
StatusMessage $status
): bool
{
@@ -62,14 +61,9 @@ class DhcpcdManager
if ($bridgedEnable) {
$config = array_keys(getDefaultNetOpts('dhcp', 'options'));
$config[] = '';
$config[] = '# RaspAP br0 configuration';
$config[] = 'denyinterfaces eth0 wlan0';
$config[] = 'interface br0';
$config[] = 'static ip_address='.$bridgeConfig['staticIp'] . '/'. $bridgeConfig['netmask'];
$config[] = 'static routers='.$bridgeConfig['gateway'];
$config[] = 'static domain_name_server='.$bridgeConfig['dns'];
$config[] = PHP_EOL;
} elseif ($repeaterEnable) {
$config = [
'# RaspAP ' . $ap_iface . ' configuration',
@@ -111,17 +105,12 @@ class DhcpcdManager
$domain_name_server
);
}
$dhcp_cfg = file_get_contents(SELF::CONF_DEFAULT);
$skip_dhcp = false;
if (preg_match('/wlan[3-9]\d*|wlan[1-9]\d+/', $ap_iface)) {
$skip_dhcp = true;
} elseif ($bridgedEnable == 1) {
$dhcp_cfg = join(PHP_EOL, $config);
$status->addMessage('DHCP configuration for br0 enabled', 'success');
} elseif ($wifiAPEnable == 1) {
} elseif ($bridgedEnable == 1 || $wifiAPEnable == 1) {
$dhcp_cfg = join(PHP_EOL, $config);
$status->addMessage(sprintf(_('DHCP configuration for %s enabled.'), $ap_iface), 'success');
} elseif (!preg_match('/^interface\s'.$ap_iface.'$/m', $dhcp_cfg)) {
@@ -135,11 +124,7 @@ class DhcpcdManager
$dhcp_cfg = $this->removeIface($dhcp_cfg,'br0');
$dhcp_cfg = $this->removeIface($dhcp_cfg,'uap0');
if (!strpos($dhcp_cfg, 'metric')) {
$pattern = '/^#\sRaspAP\s' . preg_quote($ap_iface, '/') . '\sconfiguration\n' .
'(?:.*\n)*?' .
'(?:\n)*' .
'(?=#\sRaspAP\s|\z)/m';
$dhcp_cfg = preg_replace($pattern, $config . "\n\n", $dhcp_cfg, 1);
$dhcp_cfg = preg_replace('/^#\sRaspAP\s'.$ap_iface.'\s.*?(?=(?:\s*^\s*$|\s*nogateway))/ms', $config, $dhcp_cfg, 1);
} else {
$metrics = true;
}
@@ -199,11 +184,7 @@ class DhcpcdManager
$status->addMessage('DHCP configuration for '.$iface.' added.', 'success');
} else {
$cfg = join(PHP_EOL, $cfg);
$pattern = '/^#\sRaspAP\s' . preg_quote($iface, '/') . '\sconfiguration\n' .
'(?:.*\n)*?' .
'(?:\n)*' .
'(?=#\sRaspAP\s|\z)/m';
$dhcp_cfg = preg_replace($pattern, $cfg . "\n\n", $dhcp_cfg, 1);
$dhcp_cfg = preg_replace('/^#\sRaspAP\s'.$iface.'\s.*?(?=\s*^\s*$)/ms', $cfg, $dhcp_cfg, 1);
}
return $dhcp_cfg;
@@ -286,8 +267,8 @@ class DhcpcdManager
*/
public function remove(string $iface, StatusMessage $status): bool
{
$configFile = SELF::CONF_DEFAULT;
$tempFile = SELF::CONF_TMP;
$configFile = SELF::CONF_DEFAULT;
$tempFile = SELF::CONF_TMP;
$dhcp_cfg = file_get_contents($configFile);
$modified_cfg = preg_replace('/^#\sRaspAP\s'.$iface.'\s.*?(?=\s*^\s*$)([\s]+)/ms', '', $dhcp_cfg, 1);
@@ -296,7 +277,7 @@ class DhcpcdManager
$cmd = sprintf('sudo cp %s %s', escapeshellarg($tempFile), escapeshellarg($configFile));
exec($cmd, $output, $result);
if ($result == 0) {
$status->addMessage('DHCP configuration for '.$iface.' removed', 'success');
return true;
@@ -362,29 +343,18 @@ class DhcpcdManager
];
// merge existing settings with updates
$processed_keys = [];
foreach ($existing_config as $line) {
$matched = false;
foreach ($static_settings as $key => $value) {
if (strpos($line, $key) === 0) {
$config[] = "$key=$value";
$matched = true;
$processed_keys[] = $key;
unset($static_settings[$key]);
break;
}
}
if (!$matched && !preg_match('/^interface/', $line)) {
$is_duplicate = false;
foreach ($processed_keys as $processed_key) {
if (strpos($line, $processed_key) === 0) {
$is_duplicate = true;
break;
}
}
if (!$is_duplicate && !in_array($line, $config, true)) {
$config[] = $line;
}
$config[] = $line;
}
}

View File

@@ -102,11 +102,8 @@ class HostapdManager
if (!empty($config['ieee80211ac']) && strval($config['ieee80211ac']) === '1') {
$selected = 'ac';
}
if (!empty($config['ieee80211ax']) && strval($config['ieee80211ax']) === '1') {
$selected = 'ax';
}
if (!empty($config['ieee80211be']) && strval($config['ieee80211be']) === '1') {
$selected = 'be';
if (!empty($config['ieee80211w']) && strval($config['ieee80211w']) === '2') {
$selected = 'w';
}
return $selected;
@@ -154,7 +151,6 @@ class HostapdManager
$config[] = 'auth_algs=1';
$wpa = $params['wpa'];
$wpa_numeric = $wpa;
$wpa_key_mgmt = 'WPA-PSK';
if ($wpa == 4) {
@@ -189,42 +185,20 @@ class HostapdManager
$config[] = 'ssid=' . $params['ssid'];
$config[] = 'channel=' . $params['channel'];
// choose VHT segment index (fallback only if required)
$vht_freq_idx = ($params['channel'] < RASPI_5GHZ_CHANNEL_MIN) ? 42 : 155;
$hwMode = isset($params['hw_mode']) ? $params['hw_mode'] : '';
// validate channel width for 802.11ax/be
if (in_array($hwMode, ['ax', 'be'])) {
// for 6GHz band (channels 1-233) wider bandwidths are available
$is6GHz = ($params['channel'] >= 1 && $params['channel'] <= 233);
// for 802.11be, 320 MHz only available on 6GHz
if ($hwMode === 'be' && !$is6GHz && isset($params['eht_oper_chwidth']) && $params['eht_oper_chwidth'] == 4) {
// reset to 160 MHz if 320 MHz requested on non-6GHz
$params['eht_oper_chwidth'] = 2;
}
}
// fetch settings for selected mode
$modeSettings = getDefaultNetOpts('hostapd', 'modes', $hwMode);
$settings = $modeSettings[$hwMode]['settings'] ?? [];
// extract channel width from settings to calculate center frequency
$chwidth = $this->extractChannelWidth($settings, $hwMode);
// calculate center frequency indices based on channel + width
$vht_freq_idx = $this->calculateCenterFreqIndex((int)$params['channel'], $chwidth);
$he_freq_idx = $vht_freq_idx; // For most cases, HE and VHT use the same center frequency
// calculate HT40 direction based on channel and width
$ht40_dir = $this->calculateHT40Direction((int)$params['channel'], $chwidth);
if (!empty($settings)) {
foreach ($settings as $line) {
if (!is_string($line)) {
continue;
}
$replaced = str_replace('{VHT_FREQ_IDX}', (string) $vht_freq_idx ?? '', $line);
$replaced = str_replace('{HE_FREQ_IDX}', (string) $he_freq_idx ?? '', $replaced);
$replaced = str_replace('{HT40_DIR}', (string) $ht40_dir ?? '', $replaced);
$replaced = str_replace('{VHT_FREQ_IDX}', (string) $vht_freq_idx ?? '',$line);
$config[] = $replaced;
}
}
@@ -235,9 +209,9 @@ class HostapdManager
}
// bridge handling
if (!empty($params['bridgeName'])) {
if (!empty($params['bridge'])) {
$config[] = 'interface=' . $params['interface'];
$config[] = 'bridge=' . $params['bridgeName'];
$config[] = 'bridge=' . $params['bridge'];
} else {
$config[] = 'interface=' . $params['interface'];
}
@@ -251,12 +225,6 @@ class HostapdManager
$config[] = 'max_num_sta=' . (int)$params['max_num_sta'];
}
// add logging configuration if enabled
if (!empty($params['log_enable'])) {
$config[] = 'logger_syslog=-1';
$config[] = 'logger_syslog_level=0';
}
// optional additional user config
$config[] = $this->parseUserHostapdCfg();
@@ -357,6 +325,17 @@ class HostapdManager
return [$apIface, $cliIface, $sessionIface];
}
/**
* Enables or disables hostapd logging
*
* @param int $logEnable
*/
private function handleLogState(int $logEnable): void
{
$script = $logEnable === 1 ? 'enablelog.sh' : 'disablelog.sh';
exec('sudo ' . RASPI_CONFIG . '/hostapd/' . $script);
}
/**
* Parses optional /etc/hostapd/hostapd.conf.users file
*
@@ -436,6 +415,8 @@ class HostapdManager
*/
public function persistHostapdIni(array $states, string $apIface, string $cliIface, array $previousIni = []): bool
{
$this->applyLogState($states['LogEnable']);
// compose new ini payload
$cfg = [
'WifiInterface' => $apIface,
@@ -454,6 +435,17 @@ class HostapdManager
return write_php_ini($cfg, RASPI_CONFIG . '/hostapd.ini');
}
/**
* Enables or disables hostapd logging
*
* @param int $logEnable 1 = enable, 0 = disable
*/
private function applyLogState(int $logEnable): void
{
$script = $logEnable === 1 ? 'enablelog.sh' : 'disablelog.sh';
exec('sudo ' . RASPI_CONFIG . '/hostapd/' . $script);
}
/**
* Returns a count of hostapd-<interface>.conf files
*
@@ -465,241 +457,5 @@ class HostapdManager
return is_array($configs) ? count($configs) : 0;
}
/**
* Extracts channel width from mode settings
*
* @param array $settings mode settings array
* @param string $hwMode hardware mode (ac, ax, be)
* @return int channel width in MHz (20, 40, 80, 160, 320)
*/
private function extractChannelWidth(array $settings, string $hwMode): int
{
$chwidthParam = '';
// determine parameter based on mode
if ($hwMode === 'ac') {
$chwidthParam = 'vht_oper_chwidth';
} elseif ($hwMode === 'ax') {
$chwidthParam = 'he_oper_chwidth';
} elseif ($hwMode === 'be') {
$chwidthParam = 'eht_oper_chwidth';
} else {
return 20; // 20 MHz default for other modes
}
// parse settings to find channel width
foreach ($settings as $line) {
if (!is_string($line)) {
continue;
}
// skip comments
if (strpos(trim($line), '#') === 0) {
continue;
}
if (strpos($line, $chwidthParam . '=') !== false) {
$parts = explode('=', $line, 2);
if (count($parts) === 2) {
// extract numeric value
$value = trim($parts[1]);
// remove any inline comments
$value = preg_replace('/\s*#.*$/', '', $value);
$chwidthCode = (int) $value;
// convert hostapd encoding to MHz based on mode
if ($hwMode === 'be') {
// EHT uses: 0=20, 1=40, 2=80, 3=160, 4=320
switch ($chwidthCode) {
case 0: return 20;
case 1: return 40;
case 2: return 80;
case 3: return 160;
case 4: return 320;
default: return 20;
}
} else {
// VHT/HE uses: 0=20/40, 1=80, 2=160, 3=80+80
switch ($chwidthCode) {
case 0: return 40;
case 1: return 80;
case 2: return 160;
case 3: return 160; // 80+80 treated as 160
default: return 20;
}
}
}
}
}
return 20; // default to 20 MHz channel if not found
}
/**
* Calculates center frequency segment 0 index for given channel and width
*
* @param int $channel primary channel number
* @param int $chwidthMHz channel width in MHz (20, 40, 80, 160, 320)
* @return int center frequency segment 0 index
*/
private function calculateCenterFreqIndex(int $channel, int $chwidthMHz): int
{
// determine band based on channel number
$is24GHz = ($channel >= 1 && $channel <= 14);
$is5GHz = ($channel >= 36 && $channel <= 177);
$is6GHz = ($channel >= 1 && $channel <= 233 && !$is24GHz); // 6 GHz uses 1-233
// 20 MHz - center is always primary channel
if ($chwidthMHz <= 20) {
return $channel;
}
// 2.4 GHz band
if ($is24GHz) {
if ($chwidthMHz == 40) {
// for 2.4 GHz, typically use HT40+ (center is primary + 2)
// channels 1-7 use HT40+, channels 8-13 use HT40-
return ($channel <= 7) ? $channel + 2 : $channel - 2;
}
// wider bandwidths not supported on 2.4 GHz
return $channel;
}
// 5 GHz band
if ($is5GHz) {
if ($chwidthMHz == 40) {
// HT40+ configuration: center = primary + 2
// adjust for upper/lower position in 40 MHz pair
if (in_array($channel, [36, 44, 52, 60, 100, 108, 116, 124, 132, 140, 149, 157, 165, 173])) {
return $channel + 2;
} else {
return $channel - 2;
}
}
if ($chwidthMHz == 80) {
// map channel to 80 MHz center frequency
if ($channel >= 36 && $channel <= 48) return 42;
if ($channel >= 52 && $channel <= 64) return 58;
if ($channel >= 100 && $channel <= 112) return 106;
if ($channel >= 116 && $channel <= 128) return 122;
if ($channel >= 132 && $channel <= 144) return 138;
if ($channel >= 149 && $channel <= 161) return 155;
if ($channel >= 165 && $channel <= 177) return 171;
}
if ($chwidthMHz == 160) {
// map channel to 160 MHz center frequency
if ($channel >= 36 && $channel <= 64) return 50;
if ($channel >= 100 && $channel <= 128) return 114;
// channels 149-177 don't support 160 MHz in most regions
if ($channel >= 149 && $channel <= 177) return 163;
}
}
// 6 GHz band (UNII-5 through UNII-8)
if ($is6GHz && !$is24GHz) {
// 6 GHz uses different channel numbering: 1, 5, 9, 13, ... (every 4)
if ($chwidthMHz == 40) {
// center is at the midpoint between two 20 MHz channels
return $channel + 2;
}
if ($chwidthMHz == 80) {
// calculate 80 MHz center
$blockStart = (int)(($channel - 1) / 16) * 16 + 1;
return $blockStart + 6;
}
if ($chwidthMHz == 160) {
// calculate 160 MHz center
$blockStart = (int)(($channel - 1) / 32) * 32 + 1;
return $blockStart + 14;
}
if ($chwidthMHz == 320) {
// calculate 320 MHz center
$blockStart = (int)(($channel - 1) / 64) * 64 + 1;
return $blockStart + 30;
}
}
// fallback: return primary channel
return $channel;
}
/**
* Calculates HT40 direction (+ or -) based on channel and bandwidth
*
* @param int $channel primary channel number
* @param int $chwidthMHz channel width in MHz
* @return string HT40 direction: "HT40+" or "HT40-" or "" for 20MHz
*/
private function calculateHT40Direction(int $channel, int $chwidthMHz): string
{
// only applicable for 40 MHz and wider on 5 GHz
if ($chwidthMHz < 40) {
return '';
}
$is24GHz = ($channel >= 1 && $channel <= 14);
$is5GHz = ($channel >= 36 && $channel <= 177);
// 2.4 GHz band
if ($is24GHz) {
// channels 1-7 use HT40+, channels 8-13 use HT40-
return ($channel <= 7) ? 'HT40+' : 'HT40-';
}
// 5 GHz band
if ($is5GHz) {
if ($chwidthMHz == 40) {
// for pure 40 MHz mode
if (in_array($channel, [36, 44, 52, 60, 100, 108, 116, 124, 132, 140, 149, 157, 165, 173])) {
return 'HT40+';
} else {
return 'HT40-';
}
}
if ($chwidthMHz >= 80) {
// for 80 MHz and wider, determine based on position within the 80 MHz block
// lower half of 80 MHz block uses HT40+, upper half uses HT40-
// determine which 80 MHz block this channel belongs to
if ($channel >= 36 && $channel <= 48) {
// block: 36, 40, 44, 48 (center 42)
return ($channel <= 40) ? 'HT40+' : 'HT40-';
}
if ($channel >= 52 && $channel <= 64) {
// block: 52, 56, 60, 64 (center 58)
return ($channel <= 56) ? 'HT40+' : 'HT40-';
}
if ($channel >= 100 && $channel <= 112) {
// block: 100, 104, 108, 112 (center 106)
return ($channel <= 104) ? 'HT40+' : 'HT40-';
}
if ($channel >= 116 && $channel <= 128) {
// block: 116, 120, 124, 128 (center 122)
return ($channel <= 120) ? 'HT40+' : 'HT40-';
}
if ($channel >= 132 && $channel <= 144) {
// block: 132, 136, 140, 144 (center 138)
return ($channel <= 136) ? 'HT40+' : 'HT40-';
}
if ($channel >= 149 && $channel <= 161) {
// block: 149, 153, 157, 161 (center 155)
return ($channel <= 153) ? 'HT40+' : 'HT40-';
}
if ($channel >= 165 && $channel <= 177) {
// block: 165, 169, 173, 177 (center 171)
return ($channel <= 169) ? 'HT40+' : 'HT40-';
}
}
}
// default fallback
return 'HT40+';
}
}

View File

@@ -32,9 +32,7 @@ class HotspotService
'b' => '802.11b - 2.4 GHz',
'g' => '802.11g - 2.4 GHz',
'n' => '802.11n - 2.4/5 GHz',
'ac' => '802.11ac - 5 GHz',
'ax' => '802.11ax - 2.4/5/6 GHz',
'be' => '802.11be - 2.4/5/6 GHz'
'ac' => '802.11ac - 5 GHz'
];
// encryption types
@@ -44,21 +42,6 @@ class HotspotService
'TKIP CCMP' => 'TKIP+CCMP'
];
// 802.11ax (Wi-Fi 6) channel widths
private const HE_CHANNEL_WIDTHS = [
0 => '20/40 MHz',
1 => '80 MHz',
2 => '160 MHz'
];
// 802.11be (Wi-Fi 7) channel widths
private const EHT_CHANNEL_WIDTHS = [
0 => '20 MHz',
1 => '40 MHz',
2 => '80 MHz',
3 => '160 MHz',
4 => '320 MHz (6 GHz only)'
];
public function __construct()
{
@@ -84,23 +67,7 @@ class HotspotService
}
/**
* Returns 802.11ax (Wi-Fi 6) channel widths
*/
public static function getHeChannelWidths(): array
{
return self::HE_CHANNEL_WIDTHS;
}
/**
* Returns 802.11be (Wi-Fi 7) channel widths
*/
public static function getEhtChannelWidths(): array
{
return self::EHT_CHANNEL_WIDTHS;
}
/**
* Returns translated security modes
* Returns translated security modes.
*/
public static function getSecurityModes(): array
{
@@ -127,6 +94,7 @@ class HotspotService
];
}
/**
* Validates user input + saves configs for hostapd, dnsmasq & dhcp
*
@@ -168,91 +136,65 @@ class HotspotService
// validate config from post data
$validated = $this->hostapd->validate($post_data, $wpa_array, $enc_types, $modes, $interfaces, $reg_domain, $status);
if ($validated === false) {
$status->addMessage('Unable to save WiFi hotspot settings due to validation errors', 'danger');
return false;
}
try {
// normalize state flags
$validated['interface'] = $apIface;
$validated["bridgeName"] = !empty($states["BridgedEnable"]) ? "br0" : null;
$validated['bridge'] = !empty($states['BridgedEnable']);
$validated['apsta'] = !empty($states['WifiAPEnable']);
$validated['repeater'] = !empty($states['RepeaterEnable']);
$validated['dualmode'] = !empty($states['DualAPEnable']);
$validated['txpower'] = $post_data['txpower'];
// add 802.11ax/be specific parameters if present
if (in_array($validated['hw_mode'], ['ax', 'be'])) {
if ($validated['wpa'] < 4 && $validated['hw_mode'] === 'be') {
$status->addMessage('Note: WiFi 7 works best with WPA3 security', 'info');
}
}
// hostapd
$config = $this->hostapd->buildConfig($validated, $status);
$this->hostapd->saveConfig($config, $dualAPEnable, $validated['interface']);
$this->maybeSetRegDomain($post_data['country_code'], $status);
$status->addMessage('WiFi hotspot settings saved.', 'success');
// dnsmasq
if ($validated !== false) {
try {
$syscfg = $this->dnsmasq->getConfig($validated['interface'] ?? RASPI_WIFI_AP_INTERFACE);
} catch (\RuntimeException $e) {
error_log('Error: ' . $e->getMessage());
}
// normalize state flags
$validated['interface'] = $apIface;
$validated['bridge'] = !empty($states['BridgedEnable']);
$validated['apsta'] = !empty($states['WifiAPEnable']);
$validated['repeater'] = !empty($states['RepeaterEnable']);
$validated['dualmode'] = !empty($states['DualAPEnable']);
$validated['txpower'] = $post_data['txpower'];
try {
$dnsmasqConfig = $this->dnsmasq->buildConfig(
$syscfg,
$validated['interface'],
$validated['apsta'],
$validated['bridge']
);
$this->dnsmasq->saveConfig($dnsmasqConfig, $validated['interface'], $status);
} catch (\RuntimeException $e) {
error_log('Error: ' . $e->getMessage());
}
// hostapd
$config = $this->hostapd->buildConfig($validated, $status);
$this->hostapd->saveConfig($config, $dualAPEnable, $validated['interface']);
$this->maybeSetRegDomain($post_data['country_code'], $status);
// dhcpcd
// pass bridge configuration if available
try {
$bridgeConfig = null;
if ($validated['bridge'] && !empty($validated['bridgeStaticIp'])) {
$bridgeConfig = [
'staticIp' => $validated['bridgeStaticIp'],
'netmask' => $validated['bridgeNetmask'],
'gateway' => $validated['bridgeGateway'],
'dns' => $validated['bridgeDNS']
];
$status->addMessage('WiFi hotspot settings saved.', 'success');
// dnsmasq
try {
$syscfg = $this->dnsmasq->getConfig($validated['interface'] ?? RASPI_WIFI_AP_INTERFACE);
} catch (\RuntimeException $e) {
error_log('Error: ' . $e->getMessage());
}
$return = $this->dhcpcd->buildConfig(
$validated['interface'],
$validated['bridge'],
$validated['repeater'],
$validated['apsta'],
$validated['dualmode'],
$bridgeConfig,
$status,
);
} catch (\RuntimeException $e) {
error_log('Error: ' . $e->getMessage());
$status->addMessage('Error configuring DHCP: ' . $e->getMessage(), 'danger');
return false;
}
try {
$dnsmasqConfig = $this->dnsmasq->buildConfig(
$syscfg,
$validated['interface'],
$validated['apsta'],
$validated['bridge']
);
$this->dnsmasq->saveConfig($dnsmasqConfig, $validated['interface'], $status);
} catch (\RuntimeException $e) {
error_log('Error: ' . $e->getMessage());
}
} catch (\Throwable $e) {
error_log(sprintf(
"Error: %s in %s on line %d\nStack trace:\n%s",
$e->getMessage(),
$e->getFile(),
$e->getLine(),
$e->getTraceAsString()
));
$status->addMessage('Unable to save WiFi hotspot settings', 'danger');
// dhcpcd
try {
$return = $this->dhcpcd->buildConfig(
$validated['interface'],
$validated['bridge'],
$validated['repeater'],
$validated['apsta'],
$validated['dualmode'],
$status,
);
} catch (\RuntimeException $e) {
error_log('Error: ' . $e->getMessage());
}
} catch (\Throwable $e) {
error_log(sprintf(
"Error: %s in %s on line %d\nStack trace:\n%s",
$e->getMessage(),
$e->getFile(),
$e->getLine(),
$e->getTraceAsString()
));
$status->addMessage('Unable to save WiFi hotspot settings', 'danger');
}
}
return true;
@@ -334,22 +276,10 @@ class HotspotService
* Gets the current regulatory domain
*
* @return string
* @throws RuntimeException if unable to determine regulatory domain
*/
public function getRegDomain(): string
{
$domain = shell_exec("iw reg get | grep -o 'country [A-Z]\{2\}' | awk 'NR==1{print $2}'");
if ($domain === null) {
throw new \RuntimeException('Failed to execute regulatory domain command');
}
$domain = trim($domain);
if (empty($domain)) {
throw new \RuntimeException('Unable to determine regulatory domain');
}
return $domain;
}
@@ -390,36 +320,6 @@ class HotspotService
return array_values($interfaces);
}
/**
* Retrieves hostapd service logs from systemd journal
*
* @param int $lines number of log lines to retrieve (default: 100, max: 1000)
* @param bool $follow return command for real-time following (tbd)
* @return array ['success' => bool, 'logs' => array, 'command' => string]
*/
public function getHostapdLogs(int $lines = 100, bool $follow = false): array
{
// sanitize and limit line count
$lines = max(1, min(1000, $lines));
if ($follow) {
return [
'success' => true,
'logs' => [],
'command' => 'journalctl -u hostapd.service -f --no-pager'
];
}
$cmd = sprintf('sudo journalctl -u hostapd.service -n %d --no-pager 2>&1', $lines);
exec($cmd, $output, $status);
return [
'success' => $status === 0,
'logs' => $output,
'line_count' => count($output)
];
}
/**
* Starts services for given interface
*

View File

@@ -16,15 +16,6 @@ use RaspAP\Messages\StatusMessage;
class HostapdValidator
{
// Valid channel widths for 802.11ax (HE)
private const HE_VALID_CHWIDTHS = [0, 1, 2]; // 20/40, 80, 160 MHz
// Valid channel widths for 802.11be (EHT)
private const EHT_VALID_CHWIDTHS = [0, 1, 2, 3, 4]; // 20, 40, 80, 160, 320 MHz
// 6 GHz channel range (US)
private const CHANNEL_6GHZ_MIN = 1;
private const CHANNEL_6GHZ_MAX = 233;
/**
* Validates full hostapd parameter set
@@ -73,16 +64,6 @@ class HostapdValidator
$goodInput = false;
}
// validate 802.11ax specific parameters
if ($post['hw_mode'] === 'ax' && !$this->validateAxParams($post, $status)) {
$goodInput = false;
}
// validate 802.11be specific parameters
if ($post['hw_mode'] === 'be' && !$this->validateBeParams($post, $status)) {
$goodInput = false;
}
// validate SSID
if (empty($post['ssid']) || strlen($post['ssid']) > 32) {
$status->addMessage('SSID must be between 1 and 32 characters', 'danger');
@@ -136,66 +117,6 @@ class HostapdValidator
$post['max_num_sta'] = $post['max_num_sta'] > 2007 ? 2007 : $post['max_num_sta'];
$post['max_num_sta'] = $post['max_num_sta'] < 1 ? null : $post['max_num_sta'];
// validate bridged mode static IP configuration
$bridgedEnable = !empty($post['bridgedEnable']);
$bridgeStaticIp = trim($post['bridgeStaticIp'] ?? '');
$bridgeNetmask = trim($post['bridgeNetmask'] ?? '');
$bridgeGateway = trim($post['bridgeGateway'] ?? '');
$bridgeDNS = trim($post['bridgeDNS'] ?? '');
if ($bridgedEnable) {
// validate static IP address
if (!filter_var($bridgeStaticIp, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
$status->addMessage('Bridge static IP address must be a valid IPv4 address', 'danger');
$goodInput = false;
}
// validate netmask (CIDR notation)
if (!empty($bridgeNetmask)) {
if (!ctype_digit($bridgeNetmask) || (int)$bridgeNetmask < 1 || (int)$bridgeNetmask > 32) {
$status->addMessage('Bridge netmask must be a number between 1 and 32', 'danger');
$goodInput = false;
}
} else {
$status->addMessage('Bridge netmask is required when using static IP', 'danger');
$goodInput = false;
}
// validate gateway
if (!empty($bridgeGateway)) {
if (!filter_var($bridgeGateway, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
$status->addMessage('Bridge gateway must be a valid IPv4 address', 'danger');
$goodInput = false;
}
} else {
$status->addMessage('Bridge gateway is required when using static IP', 'danger');
$goodInput = false;
}
// validate DNS server
if (!empty($bridgeDNS)) {
if (!filter_var($bridgeDNS, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
$status->addMessage('Bridge DNS server must be a valid IPv4 address', 'danger');
$goodInput = false;
}
} else {
$status->addMessage('Bridge DNS server is required when using static IP', 'danger');
$goodInput = false;
}
// validate that static IP and gateway are in the same subnet
if ($goodInput && !empty($bridgeStaticIp) && !empty($bridgeGateway) && !empty($bridgeNetmask)) {
$ipLong = ip2long($bridgeStaticIp);
$gatewayLong = ip2long($bridgeGateway);
$mask = -1 << (32 - (int)$bridgeNetmask);
if (($ipLong & $mask) !== ($gatewayLong & $mask)) {
$status->addMessage('Bridge static IP and gateway must be in the same subnet', 'danger');
$goodInput = false;
}
}
}
if (!$goodInput) {
return false;
}
@@ -215,101 +136,9 @@ class HostapdValidator
'max_num_sta' => $post['max_num_sta'],
'beacon_interval' => $post['beacon_interval'] ?? null,
'disassoc_low_ack' => $post['disassoc_low_ackEnable'] ?? null,
'bridge' => ($post['bridgedEnable'] ?? false) ? 'br0' : null,
'bridgeStaticIp' => ($post['bridgeStaticIp']),
'bridgeNetmask' => ($post['bridgeNetmask']),
'bridgeGateway' => ($post['bridgeGateway']),
'bridgeDNS' => ($post['bridgeDNS']),
'he_oper_chwidth' => $post['he_oper_chwidth'] ?? null, // 802.11ax parameters
'he_bss_color' => $post['he_bss_color'] ?? null, // 802.11be parameters
'eht_oper_chwidth' => $post['eht_oper_chwidth'] ?? null
'bridge' => ($post['bridgedEnable'] ?? false) ? 'br0' : null
];
}
/**
* Validates 802.11ax (Wi-Fi 6) specific parameters
*
* @param array $post
* @param StatusMessage $status
* @return bool
*/
private function validateAxParams(array $post, StatusMessage $status): bool
{
$valid = true;
// Validate HE channel width
if (isset($post['he_oper_chwidth'])) {
$chwidth = (int)$post['he_oper_chwidth'];
if (!in_array($chwidth, self::HE_VALID_CHWIDTHS, true)) {
$status->addMessage('Invalid 802.11ax channel width. Must be 0 (20/40 MHz), 1 (80 MHz), or 2 (160 MHz)', 'danger');
$valid = false;
}
}
// Validate BSS color (1-63)
if (isset($post['he_bss_color'])) {
$bssColor = (int)$post['he_bss_color'];
if ($bssColor < 1 || $bssColor > 63) {
$status->addMessage('802.11ax BSS color must be between 1 and 63', 'danger');
$valid = false;
}
}
return $valid;
}
/**
* Validates 802.11be (Wi-Fi 7) specific parameters
*
* @param array $post
* @param StatusMessage $status
* @return bool
*/
private function validateBeParams(array $post, StatusMessage $status): bool
{
$valid = true;
$channel = (int)$post['channel'];
// Validate EHT channel width
if (isset($post['eht_oper_chwidth'])) {
$chwidth = (int)$post['eht_oper_chwidth'];
if (!in_array($chwidth, self::EHT_VALID_CHWIDTHS, true)) {
$status->addMessage('Invalid 802.11be channel width. Must be 0-4 (20, 40, 80, 160, or 320 MHz)', 'danger');
$valid = false;
}
// 320 MHz only valid on 6 GHz band
if ($chwidth === 4) {
if ($channel < self::CHANNEL_6GHZ_MIN || $channel > self::CHANNEL_6GHZ_MAX) {
$status->addMessage('802.11be 320 MHz channel width is only available on 6 GHz band (channels 1-233)', 'danger');
$valid = false;
}
}
}
// Validate BSS color (same as 802.11ax, inherited)
if (isset($post['he_bss_color'])) {
$bssColor = (int)$post['he_bss_color'];
if ($bssColor < 1 || $bssColor > 63) {
$status->addMessage('BSS color must be between 1 and 63', 'danger');
$valid = false;
}
}
return $valid;
}
/**
* Checks if channel is in 6GHz band
*
* @param int $channel
* @return bool
*/
private function is6GHzChannel(int $channel): bool
{
return $channel >= self::CHANNEL_6GHZ_MIN && $channel <= self::CHANNEL_6GHZ_MAX;
}
}

View File

@@ -16,15 +16,16 @@ class WiFiManager
private const MIN_RSSI = -100;
private const MAX_RSSI = -55;
const SECURITY_OPEN = 'OPEN';
public function knownWifiStations(&$networks)
{
// find currently configured networks
exec(' sudo cat ' . RASPI_WPA_SUPPLICANT_CONFIG, $known_return);
$index = 0;
foreach ($known_return as $line) {
if (preg_match('/network\s*=/', $line)) {
$network = array('visible' => false, 'configured' => true, 'connected' => false, 'index' => null);
++$index;
} elseif (isset($network) && $network !== null) {
if (preg_match('/^\s*}\s*$/', $line)) {
$networks[$ssid] = $network;
@@ -36,7 +37,8 @@ class WiFiManager
$ssid = trim($lineArr[1], '"');
$ssid = str_replace('P"','',$ssid);
$network['ssid'] = $ssid;
$network['index'] = $this->getNetworkIdBySSID($ssid);
$index = $this->getNetworkIdBySSID($ssid);
$network['index'] = $index;
break;
case 'psk':
$network['passkey'] = trim($lineArr[1]);
@@ -49,7 +51,7 @@ class WiFiManager
break;
case 'key_mgmt':
if (! array_key_exists('passphrase', $network) && $lineArr[1] === 'NONE') {
$network['protocol'] = self::SECURITY_OPEN;
$network['protocol'] = 'Open';
}
break;
case 'priority':
@@ -82,27 +84,24 @@ class WiFiManager
$cacheKey,
function () use ($iface) {
$stdout = shell_exec("sudo iw dev $iface scan");
sleep(1);
if ($stdout === null) {
return [];
}
return preg_split("/\n/", $stdout);
}
);
// determine the next index that follows the indexes of the known networks
// exclude the AP from nearby networks
exec('sed -rn "s/ssid=(.*)\s*$/\1/p" ' . escapeshellarg(RASPI_HOSTAPD_CONFIG), $ap_ssid);
$ap_ssid = $ap_ssid[0] ?? '';
$index = 0;
if (!empty($networks)) {
foreach ($networks as $network) {
if (isset($network['index']) && is_numeric($network['index']) && ($network['index'] > $index)) {
$index = (int)$network['index'];
}
$lastnet = end($networks);
if (isset($lastnet['index'])) {
$index = $lastnet['index'] + 1;
}
}
$index++;
$current = [];
$commitCurrent = function () use (&$current, &$networks, &$index) {
$commitCurrent = function () use (&$current, &$networks, &$index, $ap_ssid) {
if (empty($current['ssid'])) {
return;
}
@@ -110,7 +109,7 @@ class WiFiManager
$ssid = $current['ssid'];
// unprintable 7bit ASCII control codes, delete or quotes -> ignore network
if (preg_match('/[\x00-\x1f\x7f\'`\´"]/', $ssid)) {
if ($ssid === $ap_ssid || preg_match('/[\x00-\x1f\x7f\'`\´"]/', $ssid)) {
return;
}
@@ -128,7 +127,7 @@ class WiFiManager
$networks[$ssid] = [
'ssid' => $ssid,
'configured' => false,
'protocol' => $current['security'] ?? self::SECURITY_OPEN,
'protocol' => $current['security'] ?? 'OPEN',
'channel' => $channel,
'passphrase' => '',
'visible' => true,
@@ -136,13 +135,9 @@ class WiFiManager
'RSSI' => $rssi,
'index' => $index
];
$index++; // increment for next new network
++$index;
}
};
if (is_string($scan_results)) {
$scan_results = explode("\n", trim($scan_results));
}
foreach ($scan_results as $line) {
$line = trim($line);
@@ -154,7 +149,7 @@ class WiFiManager
'ssid' => '',
'signal' => null,
'freq' => null,
'security' => self::SECURITY_OPEN
'security' => 'OPEN'
];
continue;
}
@@ -177,50 +172,20 @@ class WiFiManager
}
$commitCurrent();
}
/**
* Check if networks are connected via wpa_cli status
* NB: iwconfig shows the last associated SSID even when connection is inactive
*
*/
public function connectedWifiStations(&$networks)
{
$wpa_state = null;
$connected_ssid = null;
$iface = $_SESSION['wifi_client_interface'];
$cmd = "sudo wpa_cli -i $iface status";
$status_output = shell_exec($cmd);
if ($status_output === null || empty($status_output)) {
error_log("WiFiManager::connectedWifiStations: wpa_cli command failed or returned no output");
return;
}
$lines = explode("\n", trim($status_output));
foreach ($lines as $line) {
$line = trim($line);
if (preg_match('/^wpa_state=(.+)$/', $line, $matches)) {
$wpa_state = trim($matches[1]);
}
if (preg_match('/^ssid=(.+)$/', $line, $matches)) {
$connected_ssid = trim($matches[1]);
}
}
if ($wpa_state === 'COMPLETED' && !empty($connected_ssid)) {
$ssid = hexSequence2lower($connected_ssid);
// check if this SSID exists in networks array
if (array_key_exists($ssid, $networks)) {
exec('iwconfig ' .$_SESSION['wifi_client_interface'], $iwconfig_return);
foreach ($iwconfig_return as $line) {
if (preg_match('/ESSID:\"([^"]+)\"/i', $line, $iwconfig_ssid)) {
$ssid=hexSequence2lower($iwconfig_ssid[1]);
$networks[$ssid]['connected'] = true;
} else {
error_log("WiFiManager::connectedWifiStations: SSID '$ssid' not found. SSIDs: " . implode(', ', array_keys($networks)));
//$check=detectCaptivePortal($_SESSION['wifi_client_interface']);
$networks[$ssid]["portal-url"]=$check["URL"];
}
// captive portal detection
// $check = detectCaptivePortal($iface);
// if (isset($check["URL"])) {
// $networks[$ssid]["portal-url"] = $check["URL"];
// }
}
}
@@ -285,11 +250,8 @@ class WiFiManager
{
$iface = $_SESSION['wifi_client_interface'];
if ($force == true) {
$cmd = "sudo rm -f /var/run/wpa_supplicant/" . $iface;
$cmd = "sudo /sbin/wpa_supplicant -i $unescapedIface -c /etc/wpa_supplicant/wpa_supplicant.conf -B 2>&1";
$result = shell_exec($cmd);
$cmd = "sudo /sbin/wpa_supplicant -i $iface -c /etc/wpa_supplicant/wpa_supplicant.conf -B 2>&1";
$result = shell_exec($cmd);
sleep(2);
}
$cmd = "sudo wpa_cli -i $iface reconfigure";
$result = shell_exec($cmd);
@@ -458,7 +420,7 @@ class WiFiManager
"sudo wpa_cli -i $iface set_network $netid ssid $ssid",
];
if ($protocol === self::SECURITY_OPEN) {
if (strtolower($protocol) === 'open') {
$commands[] = "sudo wpa_cli -i $iface set_network $netid key_mgmt NONE";
} else {
$commands[] = "sudo wpa_cli -i $iface set_network $netid psk $psk";
@@ -480,12 +442,12 @@ class WiFiManager
error_log("Successfully added network: {$network['ssid']}");
}
/**
/*
* Parses wpa_cli list_networks output and returns the id
* of a corresponding network SSID
*
* @param string $ssid
* @return integer id or null
* @return integer id
*/
public function getNetworkIdBySSID($ssid) {
$iface = escapeshellarg($_SESSION['wifi_client_interface']);
@@ -493,11 +455,10 @@ class WiFiManager
$output = [];
exec($cmd, $output);
array_shift($output);
foreach ($output as $line) {
$columns = preg_split('/\t/', $line);
if (count($columns) >= 2 && trim($columns[1]) === trim($ssid)) {
return (int)$columns[0]; // return network ID
if (count($columns) >= 4 && trim($columns[1]) === trim($ssid)) {
return $columns[0]; // return network ID
}
}
return null;
@@ -552,211 +513,5 @@ CONF;
}
}
/**
* Gets the operational status of a network interface
*
* @param string $interface network interface name
* @return string returns up, down, or unknown
*/
public function getInterfaceStatus(string $interface): string
{
exec('ip a show ' . escapeshellarg($interface), $output);
$outputGlued = implode(" ", $output);
$outputNormalized = preg_replace('/\s\s+/', ' ', $outputGlued);
if (preg_match('/state (UP|DOWN)/i', $outputNormalized, $matches)) {
return strtolower($matches[1]);
}
return 'unknown';
}
/**
* Connects to a network using wpa_cli
*
* @param string $interface network interface name
* @param int $netid network ID to connect to
* @return bool true on success, false on failure
*/
public function connectToNetwork(string $interface, int $netid): bool
{
$iface = escapeshellarg($interface);
$cmd = "sudo wpa_cli -i $iface select_network $netid";
$selectResult = shell_exec($cmd);
if ($selectResult === null || trim($selectResult) === "FAIL") {
return false;
}
sleep(3);
$cmd = "sudo wpa_cli -i $iface reassociate";
$reassociateResult = shell_exec($cmd);
if ($reassociateResult !== null) {
$trimmed = trim($reassociateResult);
if ($trimmed === "FAIL") {
return false;
}
}
return true;
}
/**
* Deletes a network from wpa_cli
*
* @param string $interface network interface name
* @param int $netid network ID to delete
* @return void
*/
public function deleteNetwork(string $interface, int $netid): void
{
$iface = escapeshellarg($interface);
exec("sudo wpa_cli -i $iface disconnect $netid");
exec("sudo wpa_cli -i $iface remove_network $netid");
}
/**
* Disconnects from a network using wpa_cli
*
* @param string $interface network interface name
* @param int $netid network ID to disconnect from
* @return void
*/
public function disconnectNetwork(string $interface, int $netid): void
{
$iface = escapeshellarg($interface);
exec("sudo wpa_cli -i $iface disconnect $netid");
exec("sudo wpa_cli -i $iface remove_network $netid");
sleep(2);
}
/**
* Updates/adds a network via wpa_cli
*
* @param string $interface network interface name
* @param string $ssid network SSID
* @param string $passphrase network passphrase
* @param string $protocol security protocol (OPEN or WPA)
* @return int|null network ID on success, null on failure
*/
public function updateNetwork(string $interface, string $ssid, string $passphrase, string $protocol = 'WPA'): ?int
{
$iface = escapeshellarg($interface);
$escapedSsid = escapeshellarg('"' . $ssid . '"');
$netid = shell_exec("sudo wpa_cli -i $iface add_network");
if ($netid === null || !is_numeric(trim($netid))) {
return null;
}
$netid = trim($netid);
$commands = [
"sudo wpa_cli -i $iface set_network $netid ssid $escapedSsid"
];
if ($protocol === self::SECURITY_OPEN) {
$commands[] = "sudo wpa_cli -i $iface set_network $netid key_mgmt NONE";
} else {
$escapedPsk = escapeshellarg('"' . $passphrase . '"');
$commands[] = "sudo wpa_cli -i $iface set_network $netid psk $escapedPsk";
}
$commands[] = "sudo wpa_cli -i $iface enable_network $netid";
foreach ($commands as $cmd) {
exec($cmd);
}
return (int)$netid;
}
/**
* Writes a wpa_supplicant configuration and applies it
*
* @param array $networks array of network configurations
* @param string $interface the network interface name
* @return array Array with 'success' (bool) and 'message' (string)
*/
public function writeWpaSupplicant(array $networks, string $interface): array
{
$wpa_file = fopen('/tmp/wifidata', 'w');
if (!$wpa_file) {
return ['success' => false, 'message' => 'Failed to update wifi settings'];
}
fwrite($wpa_file, 'ctrl_interface=DIR=' . RASPI_WPA_CTRL_INTERFACE . ' GROUP=netdev' . PHP_EOL);
fwrite($wpa_file, 'update_config=1' . PHP_EOL);
$ok = true;
foreach ($networks as $ssid => $network) {
if ($network['protocol'] === self::SECURITY_OPEN) {
fwrite($wpa_file, "network={".PHP_EOL);
fwrite($wpa_file, "\tssid=\"".$ssid."\"".PHP_EOL);
fwrite($wpa_file, "\tkey_mgmt=NONE".PHP_EOL);
fwrite($wpa_file, "\tscan_ssid=1".PHP_EOL);
if (array_key_exists('priority', $network)) {
fwrite($wpa_file, "\tpriority=".$network['priority'].PHP_EOL);
}
fwrite($wpa_file, "}".PHP_EOL);
} else {
if (strlen($network['passphrase']) >= 8 && strlen($network['passphrase']) <= 63) {
unset($wpa_passphrase);
unset($line);
exec('wpa_passphrase '. $this->ssid2utf8(escapeshellarg($ssid)) . ' ' . escapeshellarg($network['passphrase']), $wpa_passphrase);
foreach ($wpa_passphrase as $line) {
if (preg_match('/^\s*}\s*$/', $line)) {
if (array_key_exists('priority', $network)) {
fwrite($wpa_file, "\tpriority=".$network['priority'].PHP_EOL);
}
fwrite($wpa_file, $line.PHP_EOL);
} else {
if (preg_match('/\\\\x[0-9A-Fa-f]{2}/', $ssid) && strpos($line, "ssid=\"") !== false) {
fwrite($wpa_file, "\tssid=P\"".$ssid."\"".PHP_EOL);
} else {
fwrite($wpa_file, $line.PHP_EOL);
}
}
}
} elseif (strlen($network['passphrase']) == 0 && strlen($network['passkey']) == 64) {
$line = "\tpsk=" . $network['passkey'];
fwrite($wpa_file, "network={".PHP_EOL);
fwrite($wpa_file, "\tssid=\"".$ssid."\"".PHP_EOL);
fwrite($wpa_file, $line.PHP_EOL);
if (array_key_exists('priority', $network)) {
fwrite($wpa_file, "\tpriority=".$network['priority'].PHP_EOL);
}
fwrite($wpa_file, "}".PHP_EOL);
} else {
$ok = false;
fclose($wpa_file);
return ['success' => false, 'message' => 'WPA passphrase must be between 8 and 63 characters'];
}
}
}
fclose($wpa_file);
if ($ok) {
system('sudo cp /tmp/wifidata ' . RASPI_WPA_SUPPLICANT_CONFIG, $returnval);
if ($returnval == 0) {
exec('sudo wpa_cli -i ' . escapeshellarg($interface) . ' reconfigure', $reconfigure_out, $reconfigure_return);
if ($reconfigure_return == 0) {
return ['success' => true, 'message' => 'Wifi settings updated successfully'];
} else {
return ['success' => false, 'message' => 'Wifi settings updated but cannot restart (cannot execute "wpa_cli reconfigure")'];
}
} else {
return ['success' => false, 'message' => 'Wifi settings failed to be updated'];
}
}
return ['success' => false, 'message' => 'Unknown error'];
}
}

View File

@@ -197,10 +197,6 @@ class PluginInstaller
$this->installDependencies($manifest['dependencies']);
$rollbackStack[] = 'uninstallDependencies';
}
if (!empty($manifest['dpkgs'])) {
$this->installDebianPackages($manifest['dpkgs'], $pluginDir);
$rollbackStack[] = 'uninstallDebianPackages';
}
if (!empty($manifest['user_nonprivileged'])) {
$this->createUser($manifest['user_nonprivileged']);
$rollbackStack[] = 'deleteUser';
@@ -289,54 +285,6 @@ class PluginInstaller
}
}
/**
* Installs a Debian package (.deb) for the current architecture
*
* @param array $dpkgs
* @param string $pluginDir
* @return void
* @throws \Exception
*/
private function installDebianPackages(array $dpkgs, string $pluginDir): void
{
$arch = trim(shell_exec('dpkg --print-architecture'));
if (empty($arch)) {
throw new \Exception('Unable to detect system architecture');
}
// match .deb file for current arch
$debFile = null;
foreach ($dpkgs as $pkg) {
if (strpos($pkg, $arch) !== false) {
$debFile = $pkg;
break;
}
}
if (!$debFile) {
throw new \Exception("No matching .deb package found for architecture: $arch");
}
$debPath = realpath(rtrim($pluginDir, '/') . '/dpkgs/' . $debFile);
if ($debPath === false || !is_file($debPath)) {
throw new \Exception("Debian package not found: $debFile");
}
// invoke the plugin-helper
$cmd = sprintf(
'sudo %s deb %s 2>&1',
escapeshellcmd($this->helperScriptPath), escapeshellarg($debPath)
);
$return = shell_exec($cmd);
// check for success
if (stripos($return, 'ok') === false) {
throw new \Exception("Plugin helper failed to install .deb package: $debFile\nOutput: $return");
}
error_log("Installed Debian package: $debFile for arch $arch");
}
/**
* Creates a non-priviledged Linux user
*
@@ -369,7 +317,7 @@ class PluginInstaller
$source = escapeshellarg($pluginDir . DIRECTORY_SEPARATOR . $config['source']);
$destination = $config['destination'];
if (strncmp($destination, '/', 1) !== 0) {
if (!str_starts_with($destination, '/')) {
$destination = $this->rootPath . '/' . ltrim($destination, '/');
}
$destination = escapeshellarg($destination);

View File

@@ -15,20 +15,11 @@
</div>
</div>
<div class="col-md-8">
<div class="mt-3">
<?php echo sprintf(
_('RaspAP is a co-creation of %1$s and %2$s with the contributions of our %3$s and %4$s. Learn more about joining the project as a %5$s, %6$s or %7$s with immediate access to %8$s available to %9$s.'),
'<a href="https://github.com/billz">billz</a>',
'<a href="https://github.com/sirlagz">SirLagz</a>',
'<a href="https://github.com/raspap/raspap-webgui/graphs/contributors">' . _('developer community') . '</a>',
'<a href="https://crowdin.com/project/raspap">' . _('language translators') . '</a>',
'<a href="https://docs.raspap.com/#get-involved">' . _('code contributor') . '</a>',
'<a href="https://docs.raspap.com/translations/">' . _('translator') . '</a>',
'<a href="https://github.com/sponsors/RaspAP">' . _('financial sponsor') . '</a>',
'<a href="https://docs.raspap.com/insiders/#whats-in-it-for-me">' . _('exclusive features') . '</a>',
'<strong>' . _('Insiders') . '</strong>'
); ?>
</div>
<div class="mt-3">RaspAP is a co-creation of <a href="https://github.com/billz">billz</a> and <a href="https://github.com/sirlagz">SirLagz</a>
with the contributions of our <a href="https://github.com/raspap/raspap-webgui/graphs/contributors">developer community</a>
and <a href="https://crowdin.com/project/raspap">language translators</a>.
Learn more about joining the project as a <a href="https://docs.raspap.com/#get-involved">code contributor</a>,
<a href="https://docs.raspap.com/translations/">translator</a> or <a href="https://github.com/sponsors/RaspAP">financial sponsor</a> with immediate access to <a href="https://docs.raspap.com/insiders/#whats-in-it-for-me">exclusive features</a> available to <strong>Insiders</strong>.</div>
<div class="mt-3 project-links">
<div class="row">
<div class="col-6">GitHub <i class="fa-brands fa-github"></i> <a href="https://github.com/RaspAP/" target="_blank" rel="noopener">RaspAP</a></div>

View File

@@ -1,5 +1,5 @@
<div class="tab-pane fade" id="client-list">
<h4 class="mt-3 mb-3"><?php echo _("Client list"); ?></h4>
<h4 class="mt-3 mb-3">Client list</h4>
<div class="row">
<div class="col-lg-12">
<div class="card mb-3">

View File

@@ -1,8 +1,8 @@
<div class="tab-pane active" id="server-settings">
<h4 class="mt-3"><?php echo _("DHCP server settings"); ?></h4>
<h4 class="mt-3">DHCP server settings</h4>
<div class="row">
<div class="mb-3 col-md-6">
<label for="code"><?php echo _("Interface"); ?></label>
<label for="code">Interface</label>
<?php SelectorOptions('interface', $interfaces, $ap_iface, 'cbxdhcpiface', 'loadInterfaceDHCPSelect'); ?>
</div>
</div>
@@ -33,7 +33,7 @@
</div>
</div>
<h5 class="mt-1"><?php echo _("Static IP options"); ?></h5>
<h5 class="mt-1">Static IP options</h5>
<div class="row">
<div class="mb-3 col-md-6" required>
<label for="code"><?php echo _("IP Address"); ?></label>
@@ -90,7 +90,7 @@
</div>
</div>
<h5 class="mt-1"><?php echo _("DHCP options"); ?></h5>
<h5 class="mt-1">DHCP options</h5>
<div class="row">
<div class="mb-3 col-md-6">
<div class="input-group">
@@ -107,32 +107,34 @@
<div class="row">
<div class="mb-3 col-md-6">
<label for="txtrangestart"><?php echo _("Starting IP Address"); ?></label>
<label for="code"><?php echo _("Starting IP Address"); ?></label>
<input type="text" class="form-control ip_address" id="txtrangestart" name="RangeStart" maxlength="15" />
<div class="invalid-feedback">
<?php echo _("Please provide a valid Starting IP Address."); ?>
</div>
</div>
</div>
<div class="row">
<div class="mb-3 col-md-6">
<label for="txtrangeend"><?php echo _("Ending IP Address"); ?></label>
<label for="code"><?php echo _("Ending IP Address"); ?></label>
<input type="text" class="form-control ip_address" id="txtrangeend" name="RangeEnd" maxlength="15" />
<div class="invalid-feedback">
<?php echo _("Please provide a valid Ending IP Address."); ?>
</div>
</div>
</div>
<div class="row">
<div class="mb-3 col-xs-3 col-sm-3">
<label for="txtrangeleasetime"><?php echo _("Lease Time"); ?></label>
<label for="code"><?php echo _("Lease Time"); ?></label>
<input type="text" class="form-control" id="txtrangeleasetime" name="RangeLeaseTime" />
<div class="invalid-feedback">
<?php echo _("Please provide a valid Lease Time."); ?>
</div>
</div>
<div class="col-xs-3 col-sm-3">
<label for="cbxrangeleasetimeunits"><?php echo _("Interval"); ?></label>
<label for="code"><?php echo _("Interval"); ?></label>
<select id="cbxrangeleasetimeunits" name="RangeLeaseTimeUnits" class="form-select" >
<option value="m"><?php echo _("Minute(s)"); ?></option>
<option value="h"><?php echo _("Hour(s)"); ?></option>

View File

@@ -10,76 +10,6 @@
</div>
</div>
</div>
<!-- static IP settings -->
<div class="row" id="bridgeStaticIpSection" style="display: <?php echo $arrHostapdConf['BridgedEnable'] == 1 ? 'block' : 'none' ?>;">
<div class="col-md-12 mb-3">
<div class="card">
<div class="card-body">
<h6 class="card-title"><?php echo _("Bridge interface configuration"); ?></h6>
<p class="text-muted small mb-3">
<?php echo _("Configure a static IP address for the <code>br0</code> interface to maintain connectivity during bridge mode activation."); ?>
</p>
<div class="row g-3">
<div class="col-md-6">
<label for="bridgeStaticIp" class="form-label"><?php echo _("Static IP Address"); ?></label>
<div class="input-group has-validation">
<input type="text" class="form-control ip_address" id="bridgeStaticIp" name="bridgeStaticIp"
value="<?php echo htmlspecialchars($arrConfig['bridgeStaticIP'] ?? '', ENT_QUOTES); ?>"
placeholder="192.168.1.100" />
<div class="invalid-feedback">
<?php echo _("Please enter a valid IPv4 address"); ?>
</div>
</div>
<div class="form-text"><?php echo _("Example: 192.168.1.100"); ?></div>
</div>
<div class="col-md-6">
<label for="bridgeNetmask" class="form-label"><?php echo _("Netmask / CIDR"); ?></label>
<div class="input-group has-validation">
<input type="text" class="form-control" id="bridgeNetmask" name="bridgeNetmask"
value="<?php echo htmlspecialchars($arrConfig['bridgeNetmask'] ?? '24', ENT_QUOTES); ?>"
placeholder="24" />
<div class="invalid-feedback">
<?php echo _("Please enter a valid netmask"); ?>
</div>
</div>
<div class="form-text"><?php echo _("CIDR notation (e.g., 24 for 255.255.255.0)"); ?></div>
</div>
<div class="col-md-6">
<label for="bridgeGateway" class="form-label"><?php echo _("Gateway"); ?></label>
<div class="input-group has-validation">
<input type="text" class="form-control ip_address" id="bridgeGateway" name="bridgeGateway"
value="<?php echo htmlspecialchars($arrConfig['bridgeGateway'] ?? '', ENT_QUOTES); ?>"
placeholder="192.168.1.1" />
<div class="invalid-feedback">
<?php echo _("Please enter a valid IPv4 address"); ?>
</div>
</div>
<div class="form-text"><?php echo _("Your router's IP address"); ?></div>
</div>
<div class="col-md-6">
<label for="bridgeDNS" class="form-label"><?php echo _("DNS Server"); ?></label>
<div class="input-group has-validation">
<input type="text" class="form-control ip_address" id="bridgeDNS" name="bridgeDNS"
value="<?php echo htmlspecialchars($arrConfig['bridgeDNS'] ?? '', ENT_QUOTES); ?>"
placeholder="192.168.1.1" />
<div class="invalid-feedback">
<?php echo _("Please enter a valid IPv4 address"); ?>
</div>
</div>
<div class="form-text"><?php echo _("Usually same as gateway"); ?></div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6 mb-2">
<div class="form-check form-switch">

View File

@@ -18,8 +18,9 @@
<div class="row">
<div class="mb-3 col-md-6">
<label for="cbxhwmode"><?php echo _("Wireless Mode") ;?></label>
<i class="fas fa-question-circle text-muted" data-bs-toggle="tooltip" data-bs-placement="auto" title="<?php echo _("The 802.11ac 5 GHz option is disabled until a compatible wireless regulatory domain is set."); ?>"></i>
<?php SelectorOptions('hw_mode', $arr80211Standard, $arrConfig['selected_hw_mode'], 'cbxhwmode', 'getChannel'); ?>
</div>
</div>
</div>
<div class="row">
<div class="mb-3 col-md-6">

View File

@@ -14,7 +14,7 @@
<div class="mb-3 col-md-8 mt-2">
<?php
if ($arrHostapdConf['LogEnable'] == 1) {
echo '<textarea class="logoutput text-secondary" id="hostapd-log">'.htmlspecialchars(implode("\n", $logOutput), ENT_QUOTES).'</textarea>';
echo '<textarea class="logoutput text-secondary" id="hostapd-log">'.htmlspecialchars($logdata, ENT_QUOTES).'</textarea>';
} else {
echo '<textarea class="logoutput my-3"></textarea>';
}

View File

@@ -12,7 +12,7 @@
</div>
<div class="mb-3">
<label for="cbx80211w"><?php echo _("802.11w"); ?></label>
<i class="fas fa-question-circle text-muted" data-bs-toggle="tooltip" data-bs-placement="auto" title="<?php echo _("802.11w extends strong cryptographic protection to a select set of robust management frames, including Deauthentication, Disassociation and certain categories of Action Management frames. Collectively, this is known as Management Frame Protection (MFP)."); ?>"></i>
<i class="fas fa-question-circle text-muted" data-bs-toggle="tooltip" data-bs-placement="auto" title="802.11w extends strong cryptographic protection to a select set of robust management frames, including Deauthentication, Disassociation and certain categories of Action Management frames. Collectively, this is known as Management Frame Protection (MFP)."></i>
<?php SelectorOptions('80211w', $arr80211w, $arrConfig['ieee80211w'] ?? 0, 'cbx80211w'); ?>
</div>

View File

@@ -1,11 +1,10 @@
<?php ob_start() ?>
<?php ob_start() ?>
<?php if (!RASPI_MONITOR_ENABLED) : ?>
<input type="submit" class="btn btn-outline btn-primary" name="SaveOpenVPNSettings" value="<?php echo _("Save settings"); ?>" />
<input type="submit" class="btn btn-outline btn-primary" name="SaveOpenVPNSettings" value="Save settings" />
<?php if ($openvpnstatus[0] == 0) {
echo '<input type="submit" class="btn btn-success" name="StartOpenVPN" value="' . _("Start OpenVPN") . '" />' , PHP_EOL;
echo '<input type="submit" class="btn btn-success" name="StartOpenVPN" value="Start OpenVPN" />' , PHP_EOL;
} else {
echo '<input type="submit" class="btn btn-warning" name="StopOpenVPN" value="' . _("Stop OpenVPN") . '" />' , PHP_EOL;
echo '<input type="submit" class="btn btn-warning" name="StopOpenVPN" value="Stop OpenVPN" />' , PHP_EOL;
}
?>
<?php endif ?>

View File

@@ -3,25 +3,6 @@
use RaspAP\Networking\Hotspot\WiFiManager;
$wifi = new WiFiManager();
// fix: re-apply locale settings in this template context
if (!empty($_SESSION['locale'])) {
putenv("LANG=" . $_SESSION['locale']);
setlocale(LC_ALL, $_SESSION['locale']);
bindtextdomain('messages', realpath(__DIR__ . '/../../locale'));
bind_textdomain_codeset('messages', 'UTF-8');
textdomain('messages');
}
// set defaults
$network = $network ?? [];
$network['ssid'] = $network['ssid'] ?? '';
$network['ssidutf8'] = $network['ssidutf8'] ?? $network['ssid'];
$network['configured'] = $network['configured'] ?? false;
$network['connected'] = $network['connected'] ?? false;
$network['visible'] = $network['visible'] ?? false;
$network['channel'] = $network['channel'] ?? '';
$network['protocol'] = $network['protocol'] ?? $wifi::SECURITY_OPEN;
$network['passphrase'] = $network['passphrase'] ?? '';
?>
<div class="card">
<div class="card-body">
@@ -29,7 +10,7 @@ $network['passphrase'] = $network['passphrase'] ?? '';
<?php if (strlen($network['ssid']) == 0) {
$network['ssid'] = "(unknown)";
} ?>
<h5 class="card-title"><i class="fas fa-wifi me-2"></i><?php echo htmlspecialchars($network['ssidutf8'], ENT_QUOTES); ?></h5>
<h5 class="card-title"><i class="fas fa-wifi me-2"></i><?php echo htmlspecialchars($network['ssidutf8'], ENT_QUOTES); ?></h5>
<div class="info-item-wifi"><?php echo _("Status"); ?></div>
<div>
<?php if ($network['configured']) { ?>
@@ -76,7 +57,7 @@ $network['passphrase'] = $network['passphrase'] ?? '';
<div class="mb-3">
<div class="info-item-wifi mb-2"><?php echo _("Passphrase"); ?></div>
<div class="input-group">
<?php if ($network['protocol'] === $wifi::SECURITY_OPEN) { ?>
<?php if ($network['protocol'] === 'Open') { ?>
<input type="password" disabled class="form-control" aria-describedby="passphrase" name="passphrase<?php echo $index ?>" value="" />
<?php } else { ?>
<input type="password" class="form-control" aria-describedby="passphrase" name="passphrase<?php echo $index ?>" value="<?php echo htmlspecialchars($network['passphrase']); ?>" data-bs-target="#update<?php echo $index ?>" data-colors="#ffd0d0,#d0ffd0">
@@ -85,18 +66,14 @@ $network['passphrase'] = $network['passphrase'] ?? '';
</div>
</div>
<div class="btn-group btn-block d-flex">
<?php if ($network['configured']) { ?>
<input type="submit" class="btn btn-warning" value="<?php echo _("Update"); ?>" id="update<?php echo $index ?>" name="update<?php echo $index ?>"<?php echo ($network['protocol'] === 'Open' ? ' disabled' : '')?> data-bs-toggle="modal" data-bs-target="#configureClientModal" />
<?php if ($network['connected']) { ?>
<button type="submit" class="btn btn-info" value="<?php echo $index?>" name="disconnect<?php echo $index ?>"><?php echo _("Disconnect"); ?></button>
<?php } else { ?>
<button type="submit" class="btn btn-info" value="<?php echo $index?>" name="connect"><?php echo _("Connect"); ?></button>
<?php } ?>
<?php } else { ?>
<input type="submit" class="btn btn-info" value="<?php echo _("Add"); ?>" id="update<?php echo $index ?>" name="update<?php echo $index ?>" data-bs-toggle="modal" data-bs-target="#configureClientModal" />
<?php } ?>
<input type="submit" class="btn btn-danger" value="<?php echo _("Delete"); ?>" name="delete<?php echo $index ?>"<?php echo ($network['configured'] ? '' : ' disabled')?> data-bs-toggle="modal" data-bs-target="#configureClientModal" />
</div><!-- /.btn-group -->
<div class="btn-group btn-block d-flex">
<?php if ($network['configured']) { ?>
<input type="submit" class="btn btn-warning" value="<?php echo _("Update"); ?>" id="update<?php echo $index ?>" name="update<?php echo $index ?>"<?php echo ($network['protocol'] === 'Open' ? ' disabled' : '')?> data-bs-toggle="modal" data-bs-target="#configureClientModal" />
<button type="submit" class="btn btn-info" value="<?php echo $index?>" name="connect"><?php echo _("Connect"); ?></button>
<?php } else { ?>
<input type="submit" class="btn btn-info" value="<?php echo _("Add"); ?>" id="update<?php echo $index ?>" name="update<?php echo $index ?>" data-bs-toggle="modal" data-bs-target="#configureClientModal" />
<?php } ?>
<input type="submit" class="btn btn-danger" value="<?php echo _("Delete"); ?>" name="delete<?php echo $index ?>"<?php echo ($network['configured'] ? '' : ' disabled')?> data-bs-toggle="modal" data-bs-target="#configureClientModal" />
</div><!-- /.btn-group -->
</div><!-- /.card-body -->
</div><!-- /.card -->