2025-05-21_1
This commit is contained in:
parent
d5c4d93b54
commit
5c0511db27
215
README.md
215
README.md
@ -1,33 +1,67 @@
|
||||
# ip2nginx
|
||||
|
||||
**ip2nginx** is a lightweight and secure system for dynamically updating NGINX reverse proxy configurations based on IP address changes reported by remote systems (e.g., pfSense). It is particularly useful in scenarios where IP addresses change dynamically and need to be synchronized with a web server’s reverse proxy configuration.
|
||||
[](https://www.paypal.com/donate/?hosted_button_id=JNFS79EFEM7C6)
|
||||
|
||||
<!-- TOC -->
|
||||
## 📚 Table of Contents
|
||||
|
||||
- [💡 Project Overview](#-project-overview)
|
||||
- [⚙️ Features](#️-features)
|
||||
- [📁 Project Structure](#-project-structure)
|
||||
- [🚀 Update Process: update.php](#-update-process-updatephp)
|
||||
- [🚀 Update Process: updater.php](#-update-process-updaterphp)
|
||||
- [✅ Example Entry in meta.json](#-example-entry-in-metajson)
|
||||
- [✅ Example Entry in token.json](#-example-entry-in-tokenjson)
|
||||
- [🛡 Security](#-security)
|
||||
- [🔄 Usage Example from pfSense](#-usage-example-from-pfsense)
|
||||
- [📅 Cron Setup with run.sh](#-cron-setup-with-runsh)
|
||||
- [✅ Dependencies](#-dependencies)
|
||||
- [📜 License](#-license)
|
||||
- [🤝 Author](#-author)
|
||||
<!-- /TOC -->
|
||||
|
||||
|
||||
|
||||
**ip2nginx** is a lightweight and secure system for dynamically updating NGINX reverse proxy configurations based on public IP address changes, typically reported by edge devices like **pfSense**. It ensures that NGINX always routes traffic through the correct IP, even in dynamic environments.
|
||||
|
||||
**Current Version:** `0.0.1`
|
||||
|
||||
---
|
||||
|
||||
## ⚙️ Features
|
||||
|
||||
- Accepts remote updates via update.php (must be present on the server).
|
||||
- Logs all changes to log.json.
|
||||
- Access is controlled using tokens stored in token.json.
|
||||
- Protects against abuse by blocking IPs after repeated failed attempts, using blocklist.json and failures.json.
|
||||
- The core function is to locate a specific location block inside the domain’s nginx.conf and update only the proxy_pass value — without overwriting the entire file.
|
||||
- Automatically reloads NGINX using nginx -t && systemctl reload nginx.
|
||||
- Accepts remote updates via `update.php` using **token-authenticated** requests.
|
||||
- Supports both `POST` and `GET`, though **POST is preferred** to avoid token caching.
|
||||
- Updates only the `proxy_pass` line in the relevant `location` block of `nginx.conf`.
|
||||
- Automatically marks entries in `meta.json` as `"changed": 1` when input changes.
|
||||
- Logs all changes to `log.json` with timestamps.
|
||||
- Automatically reloads NGINX: `nginx -t && systemctl reload nginx` (requires root).
|
||||
- Built-in abuse protection: failed requests tracked and blocked.
|
||||
- `.htaccess` ensures that only `update.php` is externally accessible.
|
||||
|
||||
---
|
||||
|
||||
## 📁 Project Structure
|
||||
|
||||
```
|
||||
ip2nginx/
|
||||
├── .htaccess # Restricts access, routes all requests to updater
|
||||
├── updater.php # Applies proxy_pass updates to nginx.conf
|
||||
├── update.php # Processes and applies external IP update requests
|
||||
├── run.sh # CLI wrapper script for triggering update
|
||||
├── index.php # Default entry point and fallback error handling
|
||||
├── data/ # Data storage directory
|
||||
│ ├── meta.json # Central metadata file for all domains
|
||||
│ ├── token.json # Authorized tokens for each client
|
||||
│ ├── log.json # Log of updates and timestamps
|
||||
│ ├── blocklist.json # IPs blocked after repeated failed attempts
|
||||
│ └── failures.json # Failed authentication tracking
|
||||
├── index.php # Shared configuration and fallback error handler
|
||||
├── update.php # Receives incoming remote IP update requests
|
||||
├── updater.php # CLI-only: applies changes to nginx.conf if marked
|
||||
├── run.sh # Wrapper script for cron automation
|
||||
├── check_env.php # Environment validator and bootstrapper
|
||||
├── .htaccess # Blocks unauthorized access, routes traffic
|
||||
├── data/
|
||||
│ ├── meta.json # Stores current configuration state per domain
|
||||
│ ├── token.json # Stores allowed tokens (auth)
|
||||
│ ├── log.json # Stores audit log of changes
|
||||
│ ├── blocklist.json # Temporarily blocked IPs (48h ban)
|
||||
│ └── failures.json # Tracks failed attempts per IP
|
||||
```
|
||||
|
||||
## ✅ Example Entry in `token.json`
|
||||
---
|
||||
|
||||
## ✅ Example: `token.json`
|
||||
|
||||
```json
|
||||
{
|
||||
@ -36,8 +70,9 @@ ip2nginx/
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ Example Entry in `meta.json`
|
||||
## ✅ Example: `meta.json`
|
||||
|
||||
```json
|
||||
{
|
||||
@ -53,84 +88,73 @@ ip2nginx/
|
||||
}
|
||||
```
|
||||
|
||||
## 🚀 Remote update Process: `update.php`
|
||||
---
|
||||
|
||||
The API supports both GET and POST requests, though POST is recommended to prevent token caching by proxies or browsers.
|
||||
## 🌐 Remote Update API: `update.php`
|
||||
|
||||
Supports **POST** (preferred) and **GET** methods.
|
||||
|
||||
| Parameter | Required | Description |
|
||||
|------------|----------|-----------------------------------------------------------------------------|
|
||||
| `name` | ✅ | Identifier for the configuration entry (e.g., `domain1.to.com`) |
|
||||
| `token` | ✅ | Secret token assigned to the client for authentication |
|
||||
| `ip` | ❌ | New public IP address (auto-detected if omitted) |
|
||||
| `domain` | ❌ | Target domain name to connect to (e.g., `domain.from.com`) |
|
||||
| | | (default: IP address) |
|
||||
| `port` | ❌ | Proxy port (default: `443` for HTTPS, `80` for HTTP) |
|
||||
| `protocol` | ❌ | Protocol to use: `http` or `https` (default: `http`) |
|
||||
| `location` | ❌ | Specific `location` block in NGINX to update (default: `/`) |
|
||||
| `name` | ✅ | Identifier (e.g. `domain1.to.com`) |
|
||||
| `token` | ✅ | Secret token assigned for this name |
|
||||
| `ip` | ❌ | New public IP (default: auto-detected from request) |
|
||||
| `domain` | ❌ | Backend domain to proxy to (default: same as IP) |
|
||||
| `port` | ❌ | Port number (default: 443 for https, 80 for http) |
|
||||
| `protocol` | ❌ | One of `http` or `https` (default: `http`) |
|
||||
| `location` | ❌ | NGINX location block path to update (default: `/`) |
|
||||
|
||||
If the domain, IP, port, or protocol differ from the previously saved values, the entry in `meta.json` is automatically marked with `"changed": 1`.
|
||||
Any change in `ip`, `domain`, `port`, or `protocol` triggers `"changed": 1` in `meta.json`.
|
||||
|
||||
If **any parameter is received via GET**, then `ip` and `domain` will be overridden with the **client’s real IP** for security.
|
||||
|
||||
## 🚀 Local update Process: `updater.php`
|
||||
---
|
||||
|
||||
This script is meant to be run via CLI (e.g. cronjob). It will:
|
||||
## 🧩 Update Process: `updater.php`
|
||||
|
||||
To apply updates made via `update.php`:
|
||||
|
||||
1. Load all entries from `meta.json`
|
||||
2. Process only those marked as `"changed": 1`
|
||||
3. Locate the virtual host config: `/var/www/vhosts/system/<domain>/conf/nginx.conf`
|
||||
4. Replace only the `proxy_pass` inside the matching `location` block
|
||||
5. Validate and reload Nginx (`sudo nginx -t && sudo systemctl reload nginx`)
|
||||
6. Reset the `"changed"` flag in `meta.json`
|
||||
2. Check for entries marked `"changed": 1`
|
||||
3. Find `/var/www/vhosts/system/<domain>/conf/nginx.conf`
|
||||
4. Modify the appropriate `location` block’s `proxy_pass` directive only
|
||||
5. Validate and reload NGINX
|
||||
6. Reset `"changed": 0`
|
||||
|
||||
## ⏱ Cronjob Setup: Running `run.sh` Every 5 Minutes
|
||||
---
|
||||
|
||||
To automatically apply updates to `nginx.conf` when new data is received via `update.php`, you should schedule the `run.sh` script to run regularly. This ensures that any entries marked as `"changed": 1` in `meta.json` are processed and deployed without manual intervention.
|
||||
## 🛠 `check_env.php`: Environment Setup
|
||||
|
||||
### 🔧 Recommended Cron Configuration
|
||||
This CLI script validates:
|
||||
|
||||
To run the update script every 5 or 10 minutes as **root** (required for writing to NGINX config files and reloading the server):
|
||||
- Config files and permissions
|
||||
- JSON structure of each config file
|
||||
- Auto-creates missing files (with defaults)
|
||||
- Token file includes example if missing
|
||||
|
||||
1. Open the root user’s crontab:
|
||||
---
|
||||
|
||||
## ⏱ Cron Setup: `run.sh`
|
||||
|
||||
To automate updates, add `run.sh` to your crontab as root:
|
||||
|
||||
```sh
|
||||
sudo crontab -e
|
||||
```
|
||||
|
||||
2. Add the following line:
|
||||
Then add:
|
||||
|
||||
```cron
|
||||
*/5 * * * * /path/to/ip2nginx/run.sh
|
||||
```
|
||||
|
||||
Replace `/path/to/ip2nginx/` with the actual path where your `run.sh` script is located.
|
||||
This ensures automatic application of proxy changes to NGINX config and reloads.
|
||||
|
||||
### ✅ Why It Must Run as Root
|
||||
---
|
||||
|
||||
The updater modifies system-level files, typically located in:
|
||||
## 🔄 pfSense Shell Example
|
||||
|
||||
```
|
||||
/var/www/vhosts/system/<domain>/conf/nginx.conf
|
||||
```
|
||||
|
||||
These files usually require **root** privileges for writing. Additionally, reloading NGINX via:
|
||||
|
||||
```sh
|
||||
sudo nginx -t && sudo systemctl reload nginx
|
||||
```
|
||||
|
||||
also requires elevated permissions. If `run.sh` is not run as root, the updates will silently fail or be skipped due to insufficient access rights.
|
||||
|
||||
## 🛡 Security
|
||||
|
||||
- `.htaccess` blocks direct access to internal logic.
|
||||
- Only HTTPS connections should be used.
|
||||
- Only `update.php` is directly accessible. All other access is routed through `index.php`.
|
||||
- All access is name and token-authenticated
|
||||
- IP addresses are blocked for 48 hours after 3 failed attempts
|
||||
- API responses are intentionally generic to prevent brute-force probing
|
||||
|
||||
## 🔄 Usage Example from pfSense
|
||||
You can create a cron task in pfSense or use a shell script to send updated IP info to the server:
|
||||
Add the following script to `pfSense` via `System > Advanced > Cron`:
|
||||
|
||||
```sh
|
||||
#!/bin/sh
|
||||
@ -140,31 +164,34 @@ NAME="domain1.to.com"
|
||||
DOMAIN="domain.from.com"
|
||||
TOKEN="YOUR_SECRET_TOKEN"
|
||||
|
||||
curl -s -X POST "$SERVER/update.php" \
|
||||
-d "name=$NAME" \
|
||||
-d "domain=$DOMAIN" \
|
||||
-d "protocol=https" \
|
||||
-d "port=443" \
|
||||
-d "token=$TOKEN"
|
||||
curl -s -X POST "$SERVER/update.php" -d "name=$NAME" -d "domain=$DOMAIN" -d "protocol=https" -d "port=443" -d "token=$TOKEN"
|
||||
```
|
||||
|
||||
OR
|
||||
|
||||
curl -s -X POST "https://your-server.com/ip2nginx/update.php" "name=domain1.to.com" -d "token=SECRET_TOKEN_8v73jDKsdLzAq9DkeUz1" -d "protocol=https"
|
||||
|
||||
Add this script to pfSense via `System > Advanced > Cron` and run it every 5 or 10 minutes.
|
||||
|
||||
## ✅ Dependencies
|
||||
|
||||
- PHP 7.4+
|
||||
- `nginx` installed with permissions to reload via `systemctl`
|
||||
- `curl` for shell scripts (on client side)
|
||||
- Token-based access configuration in `token.json`
|
||||
|
||||
## 📜 License
|
||||
|
||||
MIT or similar — free to use, modify, and deploy.
|
||||
|
||||
---
|
||||
|
||||
Created by **SAFE-CAP / https://safe-cap.com / Alexander Schiemann**
|
||||
## 🔒 Security Highlights
|
||||
|
||||
- `.htaccess` denies access to all files except `update.php`.
|
||||
- Only HTTPS connections should be used.
|
||||
- All tokens are stored securely and verified per name.
|
||||
- After 3 failed attempts, IP is banned for 48 hours.
|
||||
- Generic error messages avoid leaking details to attackers.
|
||||
|
||||
---
|
||||
|
||||
## ✅ Requirements
|
||||
|
||||
- PHP 7.4 or newer
|
||||
- NGINX with reload access (`sudo systemctl reload nginx`)
|
||||
- `curl` on the client side
|
||||
- Token definitions in `token.json`
|
||||
|
||||
---
|
||||
|
||||
## 📜 License
|
||||
|
||||
MIT (or similar): Open-source, free for use and modification.
|
||||
|
||||
---
|
||||
|
||||
**Maintained by [SAFE-CAP / Alexander Schiemann](https://safe-cap.com)**
|
||||
49
index.php
49
index.php
@ -126,6 +126,53 @@ function getQuery(string $name, mixed $def): mixed {
|
||||
return isEmpty($val) ? $def : $val;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves multiple request parameters from POST or GET with fallback to default.
|
||||
* Key matching is case-insensitive.
|
||||
*
|
||||
* @param array $params Array of parameters in the format:
|
||||
* [
|
||||
* 'KeyName' => defaultValue,
|
||||
* ...
|
||||
* ]
|
||||
* @return array Associative array:
|
||||
* [
|
||||
* 'KeyName' => [
|
||||
* 'value' => mixed,
|
||||
* 'source' => 'post' | 'get' | 'default'
|
||||
* ],
|
||||
* ...
|
||||
* ]
|
||||
*/
|
||||
function getQueryMulti(array $params): array {
|
||||
$result = [];
|
||||
|
||||
// Lowercase copies of GET and POST for case-insensitive matching
|
||||
$post = array_change_key_case($_POST, CASE_LOWER);
|
||||
$get = array_change_key_case($_GET, CASE_LOWER);
|
||||
|
||||
foreach ($params as $key => $default) {
|
||||
$keyLower = strtolower($key);
|
||||
$value = $default;
|
||||
$source = 'default';
|
||||
|
||||
if (isset($post[$keyLower]) && !isEmpty($post[$keyLower])) {
|
||||
$value = $post[$keyLower];
|
||||
$source = 'post';
|
||||
} elseif (isset($get[$keyLower]) && !isEmpty($get[$keyLower])) {
|
||||
$value = $get[$keyLower];
|
||||
$source = 'get';
|
||||
}
|
||||
|
||||
$result[$key] = [
|
||||
'value' => $value,
|
||||
'source' => $source
|
||||
];
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a given value is considered "empty".
|
||||
* - Returns true for null, empty strings, empty arrays, 0, "0", etc. (like PHP's `empty()`)
|
||||
@ -230,5 +277,3 @@ function saveJson(string $filename, ?array $val): bool {
|
||||
|
||||
return $success;
|
||||
}
|
||||
|
||||
|
||||
|
||||
137
update.php
137
update.php
@ -10,55 +10,91 @@ include('index.php');
|
||||
*/
|
||||
main();
|
||||
|
||||
/**
|
||||
* Main entry point for processing update requests.
|
||||
*
|
||||
* This function:
|
||||
* - Validates environment and access permissions.
|
||||
* - Retrieves input parameters from POST/GET using getQueryMulti().
|
||||
* - Applies security logic: if any input came from GET, assumes a potential attack and resets IP/domain.
|
||||
* - Validates required fields and token.
|
||||
* - Triggers metadata update and logging if valid.
|
||||
*/
|
||||
function main(): void {
|
||||
// Ensure the data directory exists and is writable
|
||||
if (!ensureDirectoryExists(DATA_DIR)) {
|
||||
error_deny();
|
||||
}
|
||||
|
||||
// Get client IP address
|
||||
// Get the real client IP address
|
||||
$clientIp = $_SERVER['REMOTE_ADDR'] ?? 'unknown';
|
||||
|
||||
// Deny access if the client IP is currently blocked
|
||||
// Deny access if the client IP is blocked
|
||||
if (isBlockedIP($clientIp)) {
|
||||
error_deny();
|
||||
}
|
||||
|
||||
// Retrieve parameters from GET or POST
|
||||
$name = getQuery('name', null);
|
||||
$domain = getQuery('domain', $clientIp);
|
||||
$ip = getQuery('ip', $clientIp);
|
||||
$token = getQuery('token', null);
|
||||
$port = getQuery('port', 80);
|
||||
$protocol = getQuery('protocol', 'http');
|
||||
// Retrieve all expected input parameters using case-insensitive matching
|
||||
$query = getQueryMulti([
|
||||
'name' => null,
|
||||
'token' => null,
|
||||
'ip' => $clientIp,
|
||||
'domain' => $clientIp, // fallback to IP if domain not given
|
||||
'protocol' => 'http',
|
||||
'port' => null,
|
||||
]);
|
||||
|
||||
// Load the authorized tokens
|
||||
// Check if any parameter was received via GET (possible tampering)
|
||||
$usedGET = false;
|
||||
foreach ($query as $key => $meta) {
|
||||
if ($meta['source'] === 'get') {
|
||||
$usedGET = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Extract values
|
||||
$name = $query['name']['value'];
|
||||
$domain = $query['domain']['value'];
|
||||
$ip = $query['ip']['value'];
|
||||
$token = $query['token']['value'];
|
||||
$port = $query['port']['value'];
|
||||
$protocol = $query['protocol']['value'];
|
||||
|
||||
// If GET was used, override domain and IP with server-detected IP for safety
|
||||
if ($usedGET) {
|
||||
$domain = $clientIp;
|
||||
$ip = $clientIp;
|
||||
}
|
||||
|
||||
// Load token list
|
||||
$tokens = loadTokens();
|
||||
|
||||
// Validate required input and IP address format
|
||||
// Basic validation
|
||||
if (isEmpty($name) || isEmpty($token) || !filter_var($ip, FILTER_VALIDATE_IP)) {
|
||||
error_deny();
|
||||
}
|
||||
|
||||
// Check if token is valid for the given name
|
||||
// Token mismatch
|
||||
if (!array_key_exists($name, $tokens) || $tokens[$name] !== $token) {
|
||||
recordFailure($clientIp);
|
||||
error_deny();
|
||||
}
|
||||
|
||||
// Ensure the protocol is either http or https
|
||||
// Validate protocol
|
||||
if (!in_array($protocol, ['http', 'https'])) {
|
||||
error_deny();
|
||||
}
|
||||
|
||||
// If port is not set, use default based on protocol
|
||||
if ($port === null || $port === '') {
|
||||
$port = ($protocol === 'https') ? '443' : '80';
|
||||
} elseif (!preg_match('/^\d{2,5}$/', $port)) {
|
||||
// Normalize port if empty or invalid
|
||||
if (isEmpty($port)) {
|
||||
$port = ($protocol === 'https') ? 443 : 80;
|
||||
} elseif (!preg_match('/^\d{2,5}$/', (string)$port)) {
|
||||
error_deny();
|
||||
}
|
||||
$port = intval($port);
|
||||
|
||||
// Update metadata and log the change
|
||||
// All good: update metadata and log
|
||||
updateMeta($name, $domain, $ip, $port, $protocol);
|
||||
}
|
||||
|
||||
@ -75,25 +111,27 @@ function loadTokens(): array {
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the meta.json file with new connection data for a given name (domain),
|
||||
* and logs the update if changes are detected.
|
||||
* Updates the meta.json file with new connection data for the given entry name
|
||||
* (typically a domain alias). If any relevant fields (IP, port, protocol, domain)
|
||||
* differ from previously stored values, the metadata entry is updated, flagged with
|
||||
* `"changed": 1`, timestamped, and logged.
|
||||
*
|
||||
* If any field (IP, port, protocol, domain) differs from the stored data,
|
||||
* the entry is updated and flagged with "changed": 1. The location field is
|
||||
* preserved if it already exists and is not empty.
|
||||
* The `location` field is preserved from existing data if it's set and non-empty,
|
||||
* otherwise it defaults to DEF_LOCATION.
|
||||
*
|
||||
* @param string $name The identifier name (usually domain alias)
|
||||
* @param string $domain The target domain for proxy_pass
|
||||
* @param string $ip The updated IP address
|
||||
* @param int $port The proxy port to use
|
||||
* @param string $protocol Protocol to use ('http' or 'https')
|
||||
* @return void
|
||||
* @param string $name Logical identifier of the entry (e.g., domain alias)
|
||||
* @param string $domain Target domain for use in proxy_pass
|
||||
* @param string $ip New or updated public IP address
|
||||
* @param int $port Port number for proxy (typically 80 or 443)
|
||||
* @param string $protocol Protocol to use for proxy (either 'http' or 'https')
|
||||
* @return bool True if the meta file was changed and saved; false otherwise
|
||||
*/
|
||||
function updateMeta(string $name, string $domain, string $ip, int $port, string $protocol): void {
|
||||
$meta = loadJson(METAFILE);
|
||||
$resp = "No changes for $name\n";
|
||||
function updateMeta(string $name, string $domain, string $ip, int $port, string $protocol): bool {
|
||||
$meta = loadJson(METAFILE); // Load current meta.json contents
|
||||
$resp = "No changes for $name\n"; // Default response if no changes
|
||||
$result = false; // Flag to track if update occurred
|
||||
|
||||
// New data coming in from client
|
||||
// New metadata values from the client
|
||||
$incoming = [
|
||||
'domain' => $domain,
|
||||
'ip' => $ip,
|
||||
@ -101,37 +139,33 @@ function updateMeta(string $name, string $domain, string $ip, int $port, string
|
||||
'protocol' => $protocol,
|
||||
];
|
||||
|
||||
// Existing data for this entry, if any
|
||||
// Previously stored values for this entry
|
||||
$existing = $meta[$name] ?? [];
|
||||
|
||||
// If any changes detected — update
|
||||
// Check if any field has changed
|
||||
if (array_diff_assoc($incoming, $existing)) {
|
||||
$incoming['location'] = DEF_LOCATION; // Default location
|
||||
$incoming['time'] = date('c'); // Timestamp
|
||||
$incoming['changed'] = 1; // Flag as changed
|
||||
$incoming['location'] = DEF_LOCATION; // Default location path
|
||||
$incoming['time'] = date('c'); // Timestamp in ISO 8601 format
|
||||
|
||||
// Preserve existing location if defined and not empty
|
||||
if (array_key_exists('location', $existing)) {
|
||||
if (!isEmpty($existing['location'])) {
|
||||
// Preserve existing location if set and not empty
|
||||
if (array_key_exists('location', $existing) && !isEmpty($existing['location'])) {
|
||||
$incoming['location'] = $existing['location'];
|
||||
}
|
||||
}
|
||||
|
||||
// Save updated meta
|
||||
$meta[$name] = $incoming;
|
||||
$incoming['changed'] = 1; // Mark for processing in updater
|
||||
logChange($name, $incoming); // Log the update
|
||||
|
||||
saveJson (METAFILE, $meta);
|
||||
$meta[$name] = $incoming; // Update in-memory data
|
||||
$result = saveJson(METAFILE, $meta); // Write back to meta.json
|
||||
|
||||
// Log the change
|
||||
logChange ($name, $incoming);
|
||||
$resp = "Updated: [$protocol://$ip:$port] for $name\n";
|
||||
}
|
||||
|
||||
// Always respond with 200 OK and a message
|
||||
http_response_code(200);
|
||||
http_response_code(200); // Always return success
|
||||
echo $resp;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a given IP address is currently blocked,
|
||||
@ -148,7 +182,6 @@ function isBlockedIP(string $ip): bool {
|
||||
return isset($blocklist[$ip]) && time() < $blocklist[$ip];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Ensures that the specified directory (or its parent) exists.
|
||||
*
|
||||
@ -172,7 +205,6 @@ function ensureDirectoryExists(string $path, bool $create = true): bool {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Appends a structured log entry to the log file.
|
||||
*
|
||||
@ -224,4 +256,3 @@ function recordFailure(string $ip): void {
|
||||
// Save updated failure log
|
||||
saveJson (FAILURESFILE, $failures);
|
||||
}
|
||||
|
||||
|
||||
97
updater.php
97
updater.php
@ -61,7 +61,6 @@ function main(): void {
|
||||
echo "All applicable domains processed.\n";
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Generic processor for a location block in NGINX config lines.
|
||||
* Applies a callback to each line inside the matched location block.
|
||||
@ -107,58 +106,108 @@ function processLocationBlock(array $lines, string $path, ?string $modifier, cal
|
||||
return $lines;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a specific `location` block from an array of NGINX configuration lines.
|
||||
*
|
||||
* This function uses a helper (`processLocationBlock`) to identify a `location` block
|
||||
* by its path and optional modifier (e.g., `~`, `=`, `^~`), and removes the entire block
|
||||
* including its contents from the provided config lines.
|
||||
*
|
||||
* @param array &$lines NGINX config lines.
|
||||
* @param string $path The location path to match (e.g., "/api", "\.php$").
|
||||
* @param string|null $modifier Optional location modifier (e.g. `~`, `=`, `^~`). If null, matches unmodified location.
|
||||
* @return array A new array of config lines with the matched block removed.
|
||||
*/
|
||||
function removeLocationBlock(array $lines, string $path, ?string $modifier = null): array {
|
||||
$result = []; // Final result array for cleaned config lines
|
||||
$skipBlock = false; // Flag indicating whether we are inside a block to remove
|
||||
$buffer = []; // Temporary buffer for skipped lines (optional, unused here)
|
||||
|
||||
function removeLocationBlock(array &$lines, string $path, ?string $modifier = null): array {
|
||||
$result = [];
|
||||
$skipBlock = false;
|
||||
$buffer = [];
|
||||
|
||||
// Process the location block using a callback to mark lines for deletion
|
||||
$lines = processLocationBlock($lines, $path, $modifier, function(array &$lines, int $i, string $phase) use (&$skipBlock, &$buffer) {
|
||||
if ($phase === 'start') {
|
||||
$skipBlock = true;
|
||||
$skipBlock = true; // Begin removing lines at block start
|
||||
}
|
||||
if ($skipBlock) {
|
||||
$lines[$i] = null; // Mark for removal
|
||||
$lines[$i] = null; // Mark current line for removal
|
||||
}
|
||||
if ($phase === 'end') {
|
||||
$skipBlock = false;
|
||||
$skipBlock = false; // Stop skipping lines at block end
|
||||
}
|
||||
});
|
||||
|
||||
// Filter out null lines
|
||||
// Remove all lines marked as null (deleted) and reindex
|
||||
foreach ($lines as $line) {
|
||||
if (!isEmpty($line)) $result[] = $line;
|
||||
if (!isEmpty($line)) {
|
||||
$result[] = $line;
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates one or more directives (e.g. proxy_pass) inside a given location block.
|
||||
* Updates one or more NGINX directives inside a specific location block.
|
||||
* Also optionally appends the directive if it's not found in the block.
|
||||
*
|
||||
* @param array $lines Array of nginx configuration lines.
|
||||
* @param string $locationPath The location path to match (e.g. "/api/").
|
||||
* @param string|null $modifier Optional modifier (e.g. "~", "^~").
|
||||
* @param array $replacements List of replacements: [['name' => 'proxy_pass', 'val' => 'http://...'], ...]
|
||||
* @return array Modified nginx config lines.
|
||||
* @param array $lines NGINX config lines as an array
|
||||
* @param string $locationPath The location path to match (e.g. "/api/")
|
||||
* @param string|null $modifier Modifier for location (e.g. "~", "^~") or null for plain
|
||||
* @param array $replacements Array of directive replacements in the format:
|
||||
* [
|
||||
* [
|
||||
* 'name' => 'directive_name',
|
||||
* 'val' => 'replacement value',
|
||||
* 'append' => true|false (optional)
|
||||
* ],
|
||||
* ...
|
||||
* ]
|
||||
* @return array Modified lines with replacements and optional appends
|
||||
*/
|
||||
function updateLocationBlock(array $lines, string $locationPath, ?string $modifier, array $replacements): array {
|
||||
return processLocationBlock($lines, $locationPath, $modifier, function(array &$lines, int $i, string $phase) use ($replacements) {
|
||||
$seenDirectives = []; // Tracks which directives were matched and replaced
|
||||
|
||||
return processLocationBlock($lines, $locationPath, $modifier, function(array &$lines, int $i, string $phase) use ($replacements, &$seenDirectives) {
|
||||
// This block runs for each line inside the matching location block
|
||||
|
||||
if ($phase === 'inside') {
|
||||
foreach ($replacements as $rep) {
|
||||
// Validate required fields
|
||||
if (!isset($rep['name'], $rep['val'])) continue;
|
||||
|
||||
$name = preg_quote($rep['name'], '/');
|
||||
$name = preg_quote($rep['name'], '/'); // Escape directive name for regex
|
||||
$val = $rep['val'];
|
||||
if (preg_match("/$name\\s+.+;/", $lines[$i])) {
|
||||
$lines[$i] = preg_replace("/$name\\s+.+;/", "$name $val;", $lines[$i]);
|
||||
|
||||
// If line contains the directive, replace its value
|
||||
if (preg_match("/^\\s*{$name}\\s+.+;/", $lines[$i])) {
|
||||
$lines[$i] = preg_replace("/^\\s*{$name}\\s+.+;/", "{$rep['name']} {$val};", $lines[$i]);
|
||||
$seenDirectives[$rep['name']] = true; // Mark directive as seen
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// At the end of the location block, insert any missing directives marked for appending
|
||||
if ($phase === 'end') {
|
||||
$inserts = [];
|
||||
|
||||
foreach ($replacements as $rep) {
|
||||
$append = $rep['append'] ?? false;
|
||||
if (!isset($rep['name'], $rep['val']) || !$append) continue;
|
||||
|
||||
// If directive wasn't seen inside the block, add it now
|
||||
if (empty($seenDirectives[$rep['name']])) {
|
||||
$inserts[] = " {$rep['name']} {$rep['val']};"; // Indentation is preserved
|
||||
}
|
||||
}
|
||||
|
||||
// Insert new lines right before the closing brace of the location block
|
||||
if (!empty($inserts)) {
|
||||
array_splice($lines, $i, 0, $inserts);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Processes a single domain configuration from meta.json.
|
||||
*
|
||||
@ -216,7 +265,9 @@ function processDomain(string $name, array $meta): bool {
|
||||
|
||||
// Apply transformation inside matching location block
|
||||
$lines = updateLocationBlock($lines, $meta['location'], null, [['name' => 'proxy_pass', 'val' => $proxyTarget]]);
|
||||
// remove from nginx php обработчик так как он приводит к обработке локально, а не на удаленном сервере
|
||||
|
||||
// Remove the PHP handler location block from nginx config.
|
||||
// This prevents local PHP processing and ensures the request is forwarded to the remote upstream server.
|
||||
$lines = removeLocationBlock($lines, '~', '.*\.php.*');
|
||||
|
||||
$modified = implode("\n", $lines);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user