diff --git a/src/RaspAP/Networking/Hotspot/DnsmasqManager.php b/src/RaspAP/Networking/Hotspot/DnsmasqManager.php new file mode 100644 index 00000000..46f121a1 --- /dev/null +++ b/src/RaspAP/Networking/Hotspot/DnsmasqManager.php @@ -0,0 +1,89 @@ + after save + * @return bool + * @throws \RuntimeException + */ + public function saveConfig(string $config, bool $dualMode, string $iface, bool $restart = false): bool + { + $configFile = $this->resolveConfigPath($iface, $dualMode); + //$configFile = self::CONF_DEFAULT; + $tempFile = self::CONF_TMP; + + + if (file_put_contents($tempFile, $config) === false) { + throw new \RuntimeException("Failed to write temp hostapd config"); + } + + exec(sprintf('sudo cp %s %s', escapeshellarg($tempFile), escapeshellarg($configFile)), $o, $status); + if ($status !== 0) { + throw new \RuntimeException("Failed to apply new hostapd config"); + } + + if ($restart) { + $this->restartService($iface); + } + + return true; + } + + /** + * Sets transmit power for an interface + * + * @param string $iface + * @param int|string $dbm + * @return bool + */ + public function setTxPower(string $iface, $dbm): bool + { + return false; + } + + /** + * Sets regulatory domain + * + * @param string $countryCode + * @return bool + */ + public function setRegDomain(string $countryCode): bool + { + return false; + } + + /** + * Parses optional /etc/hostapd/hostapd.conf.users file + * + * @return string $tmp + */ + function parseUserHostapdCfg() + { + if (file_exists(CONF_DEFAULT . '.users')) { + exec('cat '. CONF_DEFAULT . '.users', $hostapdconfigusers); + foreach ($hostapdconfigusers as $hostapdconfigusersline) { + if (strlen($hostapdconfigusersline) === 0) { + continue; + } + if ($hostapdconfigusersline[0] != "#") { + $arrLine = explode("=", $hostapdconfigusersline); + $tmp.= $arrLine[0]."=".$arrLine[1].PHP_EOL;; + } + } + return $tmp; + } + } + + /** + * Determines the hostapd config file for a given interface + * + * @param string $iface + * @param bool $dualMode + * @return string + */ + private function resolveConfigPath(string $iface, bool $dualMode): string + { + if ($dualMode) { + return SELF::CONF_PATH_PREFIX . $iface . '.conf'; + } + // primary interface uses the canonical config path + return self::CONF_DEFAULT; + } + + /** + * Restarts hostapd systemd instance + * + * @param string $iface + * @throws \RuntimeException + */ + private function restartService(string $iface): void + { + // sanitize + if (!preg_match('/^[A-Za-z0-9_-]+$/', $iface)) { + throw new \RuntimeException("Invalid interface name: $iface"); + } + + // use instance unit (preferred) if available + $cmds = [ + sprintf('sudo systemctl restart hostapd@%s', $iface), + // fallback to singleton service + 'sudo systemctl restart hostapd.service' + ]; + + foreach ($cmds as $cmd) { + exec($cmd, $out, $rc); + if ($rc === 0) { + return; + } + } + + throw new \RuntimeException("Failed to restart hostapd (tried instance + fallback)."); + } + +} + +