feat: implement AIVPN host binary fallback and update installation script for prebuilt binary
This commit is contained in:
@@ -811,7 +811,7 @@ class InstallProtocolManager
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$wrapper = "bash <<'EOS'\nset -euo pipefail\n" . $exportLines . $script . "\nEOS";
|
$wrapper = "bash <<'EOS'\nset -eo pipefail\n" . $exportLines . $script . "\nEOS";
|
||||||
Logger::appendInstall($server->getId(), strtoupper($phase) . ' phase: executing remote script');
|
Logger::appendInstall($server->getId(), strtoupper($phase) . ' phase: executing remote script');
|
||||||
$output = $server->executeCommand($wrapper, true);
|
$output = $server->executeCommand($wrapper, true);
|
||||||
Logger::appendInstall($server->getId(), strtoupper($phase) . ' phase: output size ' . strlen((string) $output) . ' bytes');
|
Logger::appendInstall($server->getId(), strtoupper($phase) . ' phase: output size ' . strlen((string) $output) . ' bytes');
|
||||||
@@ -1600,6 +1600,10 @@ class InstallProtocolManager
|
|||||||
$stmt2 = $pdo->prepare('INSERT INTO server_protocols (server_id, protocol_id, config_data, applied_at, created_at) VALUES (?, ?, ?, NOW(), NOW()) ON DUPLICATE KEY UPDATE config_data = VALUES(config_data), applied_at = NOW()');
|
$stmt2 = $pdo->prepare('INSERT INTO server_protocols (server_id, protocol_id, config_data, applied_at, created_at) VALUES (?, ?, ?, NOW(), NOW()) ON DUPLICATE KEY UPDATE config_data = VALUES(config_data), applied_at = NOW()');
|
||||||
$stmt2->execute([$serverId, $pid, json_encode($config)]);
|
$stmt2->execute([$serverId, $pid, json_encode($config)]);
|
||||||
}
|
}
|
||||||
|
// Save vpn_port to vpn_servers table for shell protocols (like AIVPN)
|
||||||
|
if ($port !== null && $port > 0) {
|
||||||
|
self::markServerActive($serverId, null, ['vpn_port' => $port]);
|
||||||
|
}
|
||||||
return $res;
|
return $res;
|
||||||
} catch (Throwable $e) {
|
} catch (Throwable $e) {
|
||||||
$message = (string) $e->getMessage();
|
$message = (string) $e->getMessage();
|
||||||
@@ -1661,12 +1665,41 @@ class InstallProtocolManager
|
|||||||
$serverPort = 443;
|
$serverPort = 443;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Use full path to aivpn-server binary as per official Dockerfile
|
||||||
|
// The binary is installed to /usr/local/bin/aivpn-server in the container
|
||||||
|
$binaryCmd = '/usr/local/bin/aivpn-server';
|
||||||
|
|
||||||
|
// Verify the binary exists, fallback to other locations if needed
|
||||||
|
$checkCmd = sprintf('docker exec -i %s test -f %s && echo "found" || echo "not found"',
|
||||||
|
escapeshellarg($containerName),
|
||||||
|
escapeshellarg($binaryCmd));
|
||||||
|
$checkResult = (string) $server->executeCommand($checkCmd, true);
|
||||||
|
if (strpos($checkResult, 'found') === false) {
|
||||||
|
// Try alternative locations
|
||||||
|
$fallbacks = [
|
||||||
|
'aivpn-server', // In PATH
|
||||||
|
'/usr/bin/aivpn-server',
|
||||||
|
'/opt/aivpn/aivpn-server',
|
||||||
|
'/app/aivpn-server',
|
||||||
|
];
|
||||||
|
foreach ($fallbacks as $loc) {
|
||||||
|
$checkCmd = sprintf('docker exec -i %s test -f %s && echo "found" || echo "not found"',
|
||||||
|
escapeshellarg($containerName),
|
||||||
|
escapeshellarg($loc));
|
||||||
|
$checkResult = (string) $server->executeCommand($checkCmd, true);
|
||||||
|
if (strpos($checkResult, 'found') !== false) {
|
||||||
|
$binaryCmd = $loc;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$cmdParts = [
|
$cmdParts = [
|
||||||
'docker',
|
'docker',
|
||||||
'exec',
|
'exec',
|
||||||
'-i',
|
'-i',
|
||||||
escapeshellarg($containerName),
|
escapeshellarg($containerName),
|
||||||
'aivpn-server',
|
$binaryCmd,
|
||||||
'--add-client',
|
'--add-client',
|
||||||
escapeshellarg($clientName),
|
escapeshellarg($clientName),
|
||||||
'--key-file',
|
'--key-file',
|
||||||
@@ -1682,11 +1715,39 @@ class InstallProtocolManager
|
|||||||
|
|
||||||
$cmd = implode(' ', $cmdParts);
|
$cmd = implode(' ', $cmdParts);
|
||||||
Logger::appendInstall($server->getId(), 'Adding AIVPN client via builtin add_client: ' . $clientName . ' in ' . $containerName);
|
Logger::appendInstall($server->getId(), 'Adding AIVPN client via builtin add_client: ' . $clientName . ' in ' . $containerName);
|
||||||
|
|
||||||
|
try {
|
||||||
$output = (string) $server->executeCommand($cmd, true);
|
$output = (string) $server->executeCommand($cmd, true);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
// Container may be restarting or unavailable - try host binary fallback
|
||||||
|
Logger::appendInstall($server->getId(), 'AIVPN add_client docker exec failed (container may be restarting): ' . $e->getMessage());
|
||||||
|
$hostResult = self::runAivpnAddClientViaHostBinary($server, $clientName, $serverHost, $serverPort);
|
||||||
|
if ($hostResult !== null) {
|
||||||
|
return $hostResult;
|
||||||
|
}
|
||||||
|
return ['success' => true, 'connection_key' => '', 'connection_uri' => ''];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if docker exec returned an error (container not running, etc.)
|
||||||
|
$trimmedOutput = trim($output);
|
||||||
|
if ($trimmedOutput === '' ||
|
||||||
|
stripos($trimmedOutput, 'Error response from daemon') !== false ||
|
||||||
|
stripos($trimmedOutput, 'is restarting') !== false ||
|
||||||
|
stripos($trimmedOutput, 'No such container') !== false ||
|
||||||
|
stripos($trimmedOutput, 'executable file not found') !== false) {
|
||||||
|
// Container unavailable - try host binary fallback
|
||||||
|
Logger::appendInstall($server->getId(), 'AIVPN add_client container unavailable, trying host binary fallback');
|
||||||
|
$hostResult = self::runAivpnAddClientViaHostBinary($server, $clientName, $serverHost, $serverPort);
|
||||||
|
if ($hostResult !== null) {
|
||||||
|
return $hostResult;
|
||||||
|
}
|
||||||
|
return ['success' => true, 'connection_key' => '', 'connection_uri' => ''];
|
||||||
|
}
|
||||||
|
|
||||||
$parsed = self::parseAivpnAddClientOutput($output);
|
$parsed = self::parseAivpnAddClientOutput($output);
|
||||||
|
|
||||||
if (empty($parsed['connection_uri']) && empty($parsed['connection_key'])) {
|
if (empty($parsed['connection_uri']) && empty($parsed['connection_key'])) {
|
||||||
$head = substr(str_replace(["\r", "\n"], ' ', trim($output)), 0, 220);
|
$head = substr(str_replace(["\r", "\n"], ' ', $trimmedOutput), 0, 220);
|
||||||
throw new Exception('AIVPN add_client succeeded but no connection key found in output: ' . $head);
|
throw new Exception('AIVPN add_client succeeded but no connection key found in output: ' . $head);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1734,6 +1795,90 @@ class InstallProtocolManager
|
|||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static function runAivpnAddClientViaHostBinary(VpnServer $server, string $clientName, string $serverHost, int $serverPort): ?array
|
||||||
|
{
|
||||||
|
$hostBinaryPaths = [
|
||||||
|
'/opt/amnezia/aivpn/aivpn-server-linux-x86_64',
|
||||||
|
'/opt/amnezia/aivpn/aivpn-server',
|
||||||
|
'/usr/local/bin/aivpn-server',
|
||||||
|
'/usr/bin/aivpn-server',
|
||||||
|
];
|
||||||
|
|
||||||
|
$binaryPath = null;
|
||||||
|
foreach ($hostBinaryPaths as $path) {
|
||||||
|
try {
|
||||||
|
$check = (string) $server->executeCommand('test -f ' . escapeshellarg($path) . ' && echo "found" || echo "not_found"', true);
|
||||||
|
if (trim($check) === 'found') {
|
||||||
|
$binaryPath = $path;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} catch (Exception $e) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($binaryPath === null) {
|
||||||
|
Logger::appendInstall($server->getId(), 'AIVPN host binary not found for fallback');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$cmdParts = [
|
||||||
|
escapeshellarg($binaryPath),
|
||||||
|
'--add-client',
|
||||||
|
escapeshellarg($clientName),
|
||||||
|
'--key-file',
|
||||||
|
escapeshellarg('/etc/aivpn/server.key'),
|
||||||
|
'--clients-db',
|
||||||
|
escapeshellarg('/etc/aivpn/clients.json'),
|
||||||
|
];
|
||||||
|
|
||||||
|
if ($serverHost !== '') {
|
||||||
|
$cmdParts[] = '--server-ip';
|
||||||
|
$cmdParts[] = escapeshellarg($serverHost . ':' . $serverPort);
|
||||||
|
}
|
||||||
|
|
||||||
|
$cmd = implode(' ', $cmdParts);
|
||||||
|
Logger::appendInstall($server->getId(), 'AIVPN add_client fallback via host binary: ' . $clientName);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$output = (string) $server->executeCommand($cmd, true);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
Logger::appendInstall($server->getId(), 'AIVPN host binary fallback failed: ' . $e->getMessage());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$trimmedOutput = trim($output);
|
||||||
|
if ($trimmedOutput === '' ||
|
||||||
|
stripos($trimmedOutput, 'Failed to add client') !== false ||
|
||||||
|
stripos($trimmedOutput, 'error') !== false) {
|
||||||
|
Logger::appendInstall($server->getId(), 'AIVPN host binary fallback returned error: ' . substr($trimmedOutput, 0, 200));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$parsed = self::parseAivpnAddClientOutput($output);
|
||||||
|
if (empty($parsed['connection_uri']) && empty($parsed['connection_key'])) {
|
||||||
|
Logger::appendInstall($server->getId(), 'AIVPN host binary fallback produced no connection key');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = ['success' => true];
|
||||||
|
if (!empty($parsed['connection_uri'])) {
|
||||||
|
$result['connection_uri'] = $parsed['connection_uri'];
|
||||||
|
}
|
||||||
|
if (!empty($parsed['connection_key'])) {
|
||||||
|
$result['connection_key'] = $parsed['connection_key'];
|
||||||
|
}
|
||||||
|
if (!empty($parsed['client_ip'])) {
|
||||||
|
$result['client_ip'] = $parsed['client_ip'];
|
||||||
|
}
|
||||||
|
if (!empty($parsed['client_id'])) {
|
||||||
|
$result['client_id'] = $parsed['client_id'];
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger::appendInstall($server->getId(), 'AIVPN host binary fallback succeeded for ' . $clientName);
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
private static function runBuiltinXrayAddClient(VpnServer $server, array $options): array
|
private static function runBuiltinXrayAddClient(VpnServer $server, array $options): array
|
||||||
{
|
{
|
||||||
$clientId = $options['client_id'] ?? null;
|
$clientId = $options['client_id'] ?? null;
|
||||||
|
|||||||
+66
-11
@@ -467,20 +467,70 @@ class VpnClient
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($slug === 'aivpn' && empty($vars['connection_key'])) {
|
if ($slug === 'aivpn' && empty($vars['connection_key'])) {
|
||||||
|
// Fallback: try to run host binary directly when container is unavailable
|
||||||
try {
|
try {
|
||||||
$rawKey = trim((string) $server->executeCommand('cat /etc/aivpn/server.key 2>/dev/null', true));
|
$hostBinaryPaths = [
|
||||||
if ($rawKey !== '' && !empty($vars['client_ip']) && !empty($vars['server_host']) && !empty($vars['server_port'])) {
|
'/opt/amnezia/aivpn/aivpn-server-linux-x86_64',
|
||||||
$payload = [
|
'/opt/amnezia/aivpn/aivpn-server',
|
||||||
'i' => (string) $vars['client_ip'],
|
'/usr/local/bin/aivpn-server',
|
||||||
'k' => $rawKey,
|
'/usr/bin/aivpn-server',
|
||||||
'p' => '',
|
|
||||||
's' => (string) $vars['server_host'] . ':' . (string) $vars['server_port'],
|
|
||||||
];
|
];
|
||||||
$json = (string) json_encode($payload, JSON_UNESCAPED_SLASHES);
|
$binaryPath = null;
|
||||||
$vars['connection_key'] = rtrim(strtr(base64_encode($json), '+/', '-_'), '=');
|
foreach ($hostBinaryPaths as $path) {
|
||||||
|
try {
|
||||||
|
$check = trim((string) $server->executeCommand('test -f ' . escapeshellarg($path) . ' && echo "found" || echo "not_found"', true));
|
||||||
|
if ($check === 'found') {
|
||||||
|
$binaryPath = $path;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
// Keep empty: final template output will expose a missing key.
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($binaryPath !== null) {
|
||||||
|
$serverHost = !empty($vars['server_host']) ? (string) $vars['server_host'] : ($serverData['host'] ?? '');
|
||||||
|
$serverPort = !empty($vars['server_port']) ? (int) $vars['server_port'] : (int) ($serverData['vpn_port'] ?? 443);
|
||||||
|
if ($serverHost === '') {
|
||||||
|
$serverHost = $serverData['host'] ?? '';
|
||||||
|
}
|
||||||
|
if ($serverPort <= 0) {
|
||||||
|
$serverPort = 443;
|
||||||
|
}
|
||||||
|
|
||||||
|
$cmdParts = [
|
||||||
|
escapeshellarg($binaryPath),
|
||||||
|
'--add-client',
|
||||||
|
escapeshellarg($loginFinal),
|
||||||
|
'--key-file',
|
||||||
|
escapeshellarg('/etc/aivpn/server.key'),
|
||||||
|
'--clients-db',
|
||||||
|
escapeshellarg('/etc/aivpn/clients.json'),
|
||||||
|
];
|
||||||
|
if ($serverHost !== '') {
|
||||||
|
$cmdParts[] = '--server-ip';
|
||||||
|
$cmdParts[] = escapeshellarg($serverHost . ':' . $serverPort);
|
||||||
|
}
|
||||||
|
$cmd = implode(' ', $cmdParts);
|
||||||
|
$output = (string) $server->executeCommand($cmd, true);
|
||||||
|
$trimmed = trim($output);
|
||||||
|
if ($trimmed !== '' && stripos($trimmed, 'Failed to add client') === false) {
|
||||||
|
if (preg_match('/(aivpn:\/\/[A-Za-z0-9_\-+=\/]+)/', $trimmed, $m)) {
|
||||||
|
$uri = trim((string) $m[1]);
|
||||||
|
$vars['connection_uri'] = $uri;
|
||||||
|
if (stripos($uri, 'aivpn://') === 0) {
|
||||||
|
$vars['connection_key'] = substr($uri, strlen('aivpn://'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (preg_match('/\bVPN\s*IP:\s*([0-9.]+)/i', $trimmed, $m)) {
|
||||||
|
$vars['client_ip'] = trim((string) $m[1]);
|
||||||
|
$clientIP = $vars['client_ip'];
|
||||||
|
}
|
||||||
|
error_log('AIVPN host binary fallback succeeded, connection_key length: ' . strlen($vars['connection_key'] ?? ''));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception $e) {
|
||||||
|
error_log('AIVPN host binary fallback failed: ' . $e->getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -488,7 +538,12 @@ class VpnClient
|
|||||||
$vars['connection_key'] = self::normalizeAivpnConnectionKey((string) $vars['connection_key']);
|
$vars['connection_key'] = self::normalizeAivpnConnectionKey((string) $vars['connection_key']);
|
||||||
}
|
}
|
||||||
|
|
||||||
$config = $protoRow ? ProtocolService::generateProtocolOutput($protoRow, $vars) : '';
|
if ($protoRow) {
|
||||||
|
require_once __DIR__ . '/ProtocolService.php';
|
||||||
|
$config = ProtocolService::generateProtocolOutput($protoRow, $vars);
|
||||||
|
} else {
|
||||||
|
$config = '';
|
||||||
|
}
|
||||||
|
|
||||||
// Prepare last_config_json for QR code generation if config is JSON (XRay)
|
// Prepare last_config_json for QR code generation if config is JSON (XRay)
|
||||||
if ($config !== '' && ($decoded = json_decode($config)) !== null) {
|
if ($config !== '' && ($decoded = json_decode($config)) !== null) {
|
||||||
|
|||||||
@@ -0,0 +1,233 @@
|
|||||||
|
-- =====================================================================
|
||||||
|
-- Migration 069: Fix AIVPN installation - use prebuilt binary via Dockerfile.prebuilt
|
||||||
|
-- Instead of pulling from registry, build locally from prebuilt binary
|
||||||
|
-- Based on: https://github.com/infosave2007/aivpn/blob/master/README_RU.md
|
||||||
|
-- =====================================================================
|
||||||
|
|
||||||
|
UPDATE protocols
|
||||||
|
SET install_script = '#!/bin/bash
|
||||||
|
set -eo pipefail
|
||||||
|
|
||||||
|
# Use exported variables from panel (SERVER_PORT, SERVER_CONTAINER) or defaults
|
||||||
|
CONTAINER_NAME="${SERVER_CONTAINER:-aivpn-server}"
|
||||||
|
VPN_PORT="${SERVER_PORT:-443}"
|
||||||
|
CONFIG_DIR="/etc/aivpn"
|
||||||
|
|
||||||
|
# Install Docker if not available
|
||||||
|
if ! command -v docker &> /dev/null; then
|
||||||
|
apt-get update -qq
|
||||||
|
apt-get install -y -qq apt-transport-https ca-certificates curl gnupg lsb-release >/dev/null 2>&1
|
||||||
|
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
|
||||||
|
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" > /etc/apt/sources.list.d/docker.list
|
||||||
|
apt-get update -qq && apt-get install -y -qq docker-ce docker-ce-cli containerd.io >/dev/null 2>&1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Install git, iptables, curl if not available
|
||||||
|
if ! command -v git &> /dev/null || ! command -v iptables &> /dev/null || ! command -v curl &> /dev/null; then
|
||||||
|
apt-get update -qq
|
||||||
|
if ! command -v git &> /dev/null; then
|
||||||
|
apt-get install -y -qq git >/dev/null 2>&1
|
||||||
|
fi
|
||||||
|
if ! command -v iptables &> /dev/null; then
|
||||||
|
apt-get install -y -qq iptables >/dev/null 2>&1
|
||||||
|
fi
|
||||||
|
if ! command -v curl &> /dev/null; then
|
||||||
|
apt-get install -y -qq curl >/dev/null 2>&1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p "$CONFIG_DIR"
|
||||||
|
|
||||||
|
# Enable IP forwarding
|
||||||
|
sysctl -w net.ipv4.ip_forward=1 2>/dev/null || true
|
||||||
|
|
||||||
|
# Generate server key if not exists
|
||||||
|
if [ ! -f "$CONFIG_DIR/server.key" ]; then
|
||||||
|
openssl rand 32 > "$CONFIG_DIR/server.key"
|
||||||
|
chmod 600 "$CONFIG_DIR/server.key"
|
||||||
|
echo "Generated new AIVPN server key"
|
||||||
|
else
|
||||||
|
echo "Using existing AIVPN server key"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Setup NAT - find default interface
|
||||||
|
DEFAULT_IFACE=""
|
||||||
|
if command -v ip >/dev/null 2>&1; then
|
||||||
|
DEFAULT_IFACE=$(ip route show default 2>/dev/null | grep default | head -1 | tr -s " " | cut -d" " -f5)
|
||||||
|
elif command -v route >/dev/null 2>&1; then
|
||||||
|
DEFAULT_IFACE=$(route -n 2>/dev/null | grep "^0\.0\.0\.0" | head -1 | tr -s " " | cut -d" " -f8)
|
||||||
|
elif [ -d /sys/class/net ]; then
|
||||||
|
# Fallback: try common interface names
|
||||||
|
for iface in eth0 ens3 enp0s3 wlan0; do
|
||||||
|
if [ -d "/sys/class/net/$iface" ]; then
|
||||||
|
DEFAULT_IFACE=$iface
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "$DEFAULT_IFACE" ]; then
|
||||||
|
iptables -t nat -C POSTROUTING -s 10.0.0.0/24 -o "$DEFAULT_IFACE" -j MASQUERADE 2>/dev/null || \
|
||||||
|
iptables -t nat -A POSTROUTING -s 10.0.0.0/24 -o "$DEFAULT_IFACE" -j MASQUERADE
|
||||||
|
else
|
||||||
|
echo "WARNING: Could not determine default network interface, skipping NAT setup"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Get external IP
|
||||||
|
EXTERNAL_IP=$(curl -s -4 ifconfig.me 2>/dev/null || curl -s -4 icanhazip.com 2>/dev/null || echo "YOUR_SERVER_IP")
|
||||||
|
|
||||||
|
# Clone AIVPN repository
|
||||||
|
AIVPN_DIR="/opt/amnezia/aivpn"
|
||||||
|
if [ ! -d "$AIVPN_DIR" ]; then
|
||||||
|
echo "Cloning AIVPN repository..."
|
||||||
|
git clone --depth=1 https://github.com/infosave2007/aivpn.git "$AIVPN_DIR"
|
||||||
|
else
|
||||||
|
echo "AIVPN repository already exists"
|
||||||
|
fi
|
||||||
|
|
||||||
|
cd "$AIVPN_DIR"
|
||||||
|
|
||||||
|
# Download prebuilt binary directly from repository
|
||||||
|
echo "Downloading prebuilt AIVPN server binary..."
|
||||||
|
DOWNLOAD_URL="https://github.com/infosave2007/aivpn/blob/master/releases/aivpn-server-linux-x86_64?raw=true"
|
||||||
|
|
||||||
|
if [ -z "$DOWNLOAD_URL" ]; then
|
||||||
|
echo "ERROR: Could not find download URL for aivpn-server-linux-x86_64"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Downloading from: $DOWNLOAD_URL"
|
||||||
|
curl -L -o aivpn-server-linux-x86_64 "$DOWNLOAD_URL"
|
||||||
|
|
||||||
|
if [ ! -f "./aivpn-server-linux-x86_64" ]; then
|
||||||
|
echo "ERROR: Binary download failed"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
chmod +x ./aivpn-server-linux-x86_64
|
||||||
|
echo "Binary downloaded successfully: $(ls -lh aivpn-server-linux-x86_64)"
|
||||||
|
|
||||||
|
# Check /dev/net/tun exists
|
||||||
|
if [ ! -c /dev/net/tun ]; then
|
||||||
|
echo "Creating /dev/net/tun..."
|
||||||
|
mkdir -p /dev/net
|
||||||
|
mknod /dev/net/tun c 10 200
|
||||||
|
chmod 666 /dev/net/tun
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Remove existing container
|
||||||
|
docker rm -f "$CONTAINER_NAME" >/dev/null 2>&1 || true
|
||||||
|
|
||||||
|
# Go back to AIVPN root directory
|
||||||
|
cd /opt/amnezia/aivpn
|
||||||
|
|
||||||
|
# Create docker-entrypoint.sh as a separate file (to avoid Docker build variable expansion issues)
|
||||||
|
cat > docker-entrypoint.sh << ''ENTRYPOINT_EOF''
|
||||||
|
#!/bin/sh
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
mkdir -p /etc/aivpn /var/lib/aivpn/masks
|
||||||
|
|
||||||
|
# Seed preset masks on first run
|
||||||
|
PRESET_DIR="/usr/share/aivpn/preset-masks"
|
||||||
|
if [ -d "$PRESET_DIR" ]; then
|
||||||
|
for f in "$PRESET_DIR"/*.json; do
|
||||||
|
[ -f "$f" ] || continue
|
||||||
|
base="$(basename "$f")"
|
||||||
|
if [ ! -f "/var/lib/aivpn/masks/$base" ]; then
|
||||||
|
cp "$f" "/var/lib/aivpn/masks/$base"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
exec /usr/local/bin/aivpn-server "$@"
|
||||||
|
ENTRYPOINT_EOF
|
||||||
|
chmod +x docker-entrypoint.sh
|
||||||
|
|
||||||
|
# Always create/update Dockerfile.prebuilt
|
||||||
|
cat > Dockerfile.prebuilt << ''DOCKERFILE''
|
||||||
|
FROM ubuntu:24.04
|
||||||
|
|
||||||
|
# Install runtime dependencies (single line to avoid backslash issues)
|
||||||
|
RUN apt-get update && apt-get install -y ca-certificates iptables iproute2 netcat-openbsd python3 bc libstdc++6 && rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy prebuilt Linux server binary from releases/
|
||||||
|
COPY aivpn-server-linux-x86_64 /usr/local/bin/aivpn-server
|
||||||
|
RUN chmod +x /usr/local/bin/aivpn-server
|
||||||
|
|
||||||
|
# Create config directory, masks directory, and TUN device node (single line to avoid backslash issues)
|
||||||
|
RUN mkdir -p /etc/aivpn /var/lib/aivpn/masks /var/lib/aivpn/bootstrap /usr/share/aivpn/preset-masks /dev/net && mknod /dev/net/tun c 10 200 2>/dev/null || true && chmod 666 /dev/net/tun
|
||||||
|
|
||||||
|
# Copy mask assets to preset directory
|
||||||
|
COPY mask-assets/*.json /usr/share/aivpn/preset-masks/
|
||||||
|
|
||||||
|
# Copy prebuilt entrypoint script
|
||||||
|
COPY docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh
|
||||||
|
RUN chmod +x /usr/local/bin/docker-entrypoint.sh
|
||||||
|
|
||||||
|
ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"]
|
||||||
|
CMD ["--listen", "0.0.0.0:443", "--key-file", "/etc/aivpn/server.key", "--clients-db", "/etc/aivpn/clients.json"]
|
||||||
|
DOCKERFILE
|
||||||
|
|
||||||
|
# Build Docker image using Dockerfile.prebuilt
|
||||||
|
echo "Building AIVPN Docker image from prebuilt binary..."
|
||||||
|
docker build -t aivpn-server:local -f Dockerfile.prebuilt .
|
||||||
|
|
||||||
|
# Run AIVPN container
|
||||||
|
echo "Running AIVPN container..."
|
||||||
|
RUN_OUTPUT=$(docker run -d --name "$CONTAINER_NAME" --restart always --cap-add=NET_ADMIN --device /dev/net/tun --network host -v "$CONFIG_DIR:/etc/aivpn" -v /var/lib/aivpn/masks:/var/lib/aivpn/masks aivpn-server:local --listen "0.0.0.0:${VPN_PORT}" --key-file /etc/aivpn/server.key --clients-db /etc/aivpn/clients.json 2>&1)
|
||||||
|
RUN_EXIT=$?
|
||||||
|
echo "docker run exit code: $RUN_EXIT"
|
||||||
|
echo "docker run output: $RUN_OUTPUT"
|
||||||
|
|
||||||
|
sleep 3
|
||||||
|
|
||||||
|
# Check container exists
|
||||||
|
echo "Checking container..."
|
||||||
|
CONTAINER_EXISTS=$(docker ps -a --filter "name=$CONTAINER_NAME" --format {{.Names}} 2>/dev/null)
|
||||||
|
echo "Container exists: $CONTAINER_EXISTS"
|
||||||
|
|
||||||
|
if [ -z "$CONTAINER_EXISTS" ]; then
|
||||||
|
echo "ERROR: Container was not created"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check container status
|
||||||
|
STATUS=$(docker inspect --format={{.State.Status}} "$CONTAINER_NAME" 2>/dev/null || echo "")
|
||||||
|
if [ -z "$STATUS" ]; then
|
||||||
|
STATUS="unknown"
|
||||||
|
fi
|
||||||
|
echo "Container status: $STATUS"
|
||||||
|
|
||||||
|
if [ "$STATUS" != "running" ]; then
|
||||||
|
echo "ERROR: AIVPN container is not running (status: $STATUS)"
|
||||||
|
echo "=== Container logs ==="
|
||||||
|
docker logs "$CONTAINER_NAME" 2>&1 || echo "No logs available"
|
||||||
|
echo "=== Container inspect ==="
|
||||||
|
docker inspect "$CONTAINER_NAME" 2>&1 || echo "Container not found"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "AIVPN installed successfully"
|
||||||
|
# Output variables for the web panel parser
|
||||||
|
KEY_B64=$(base64 -w 0 "$CONFIG_DIR/server.key" 2>/dev/null || base64 "$CONFIG_DIR/server.key")
|
||||||
|
echo "Variable: connection_key=$KEY_B64"
|
||||||
|
echo "Variable: server_host=$EXTERNAL_IP"
|
||||||
|
echo "Variable: server_port=$VPN_PORT"
|
||||||
|
echo "Variable: config_dir=$CONFIG_DIR"',
|
||||||
|
definition = JSON_OBJECT(
|
||||||
|
'engine', 'shell',
|
||||||
|
'metadata', JSON_OBJECT(
|
||||||
|
'container_name', 'aivpn-server',
|
||||||
|
'port_range', JSON_ARRAY(443, 443),
|
||||||
|
'config_dir', '/etc/aivpn',
|
||||||
|
'vpn_subnet', '10.0.0.0/24',
|
||||||
|
'requires_docker_build', true,
|
||||||
|
'git_repo', 'https://github.com/infosave2007/aivpn.git',
|
||||||
|
'build_method', 'dockerfile_prebuilt'
|
||||||
|
)
|
||||||
|
),
|
||||||
|
updated_at = NOW()
|
||||||
|
WHERE slug = 'aivpn';
|
||||||
Reference in New Issue
Block a user