Rebrand: PaladinVPN -> ReVoltVPN — all references, package names, Android namespace

This commit is contained in:
esefxdz committed 2026-07-24 21:03:18 +03:00
1 parent aaf9887f49
commit 3531671d3d
26 files changed
+75 -274

No files matched your search

+3 -2
View File
@@ -62,6 +62,9 @@ local_packages/*/.dart_tool/
# Crashlytics cache
**/android/app/crashlytics-build.properties
# Shell scripts (dev only, not for public)
server_picture.sh
# Secrets
android/key.properties
android/admob.properties
@@ -75,5 +78,3 @@ server full picture.txt
technical_debt_*.md
server_full_picture_2.txt
hivemind_backup.py
AGENTS.md
00-FULL-REPORT.md
+2 -2
View File
@@ -1,6 +1,6 @@
Privacy Policy
Last updated: July 2025
PaladinVPN is a free VPN service built and operated by a solo developer.
ReVoltVPN is a free VPN service built and operated by a solo developer.
What we collect
We collect the minimum required to operate the service:
@@ -14,7 +14,7 @@ We do not log your internet traffic or browsing history.
We do not sell or share any data with third parties.
Ads & Consent
PaladinVPN uses Google AdMob to display rewarded video ads. On first launch, users in the European Economic Area (EEA) and United Kingdom are shown a consent dialog (provided by Google's User Messaging Platform) that lets them choose whether to allow personalized ads. This consent choice is stored on your device and can be changed at any time by reinstalling the app. Google may collect data as part of ad delivery. Please refer to Google's Privacy Policy for details.
ReVoltVPN uses Google AdMob to display rewarded video ads. On first launch, users in the European Economic Area (EEA) and United Kingdom are shown a consent dialog (provided by Google's User Messaging Platform) that lets them choose whether to allow personalized ads. This consent choice is stored on your device and can be changed at any time by reinstalling the app. Google may collect data as part of ad delivery. Please refer to Google's Privacy Policy for details.
Your traffic
Your internet traffic is routed through our server located in Finland. We do not inspect, log, or store your traffic.
Contact
+5 -5
View File
@@ -1,16 +1,16 @@
---
<p align="center">
<img src="screenshots/feature_graphic.png" alt="PaladinVPN banner" width="100%"/>
<img src="screenshots/feature_graphic.png" alt="ReVoltVPN banner" width="100%"/>
</p>
# PaladinVPN
# ReVoltVPN
A free VPN app I built in my spare time as a student with basically no prior experience.
<p align="center">
<img src="screenshots/hero_screenshot.png" alt="PaladinVPN splash screen" width="32%"/>
<img src="screenshots/hero_screenshot.png" alt="ReVoltVPN splash screen" width="32%"/>
&nbsp;&nbsp;&nbsp;
<img src="screenshots/styled_idle.png" alt="PaladinVPN connected — 60 min session active" width="32%"/>
<img src="screenshots/styled_idle.png" alt="ReVoltVPN connected — 60 min session active" width="32%"/>
</p>
There are hundreds of free VPN apps on the Play Store. Most of them are fine honestly — some are genuinely good. But a lot of them are vague about how they work, who runs them, and what happens to your traffic. I wanted to build something where the answer to all of those questions is just... public.
@@ -58,6 +58,6 @@ You can read the server code yourself, it's in this repo.
---
*PaladinVPN is not affiliated with any VPN company.*
*ReVoltVPN is not affiliated with any VPN company.*
---
+1 -1
View File
@@ -1,4 +1,4 @@
# Xray Configuration Reference — PaladinVPN
# Xray Configuration Reference — ReVoltVPN
> Minimal production config with annotated explanations and optional upgrades.
> File lives at `/usr/local/etc/xray/config.json` on the Hetzner box.
+2 -2
View File
@@ -7,7 +7,7 @@ plugins {
}
android {
namespace = "com.paladinvpn.app"
namespace = "com.revoltvpn.app"
compileSdk = flutter.compileSdkVersion
ndkVersion = flutter.ndkVersion
@@ -34,7 +34,7 @@ android {
}
defaultConfig {
applicationId = "com.paladinvpn.app"
applicationId = "com.revoltvpn.app"
minSdk = flutter.minSdkVersion
targetSdk = flutter.targetSdkVersion
versionCode = flutter.versionCode
+1 -1
View File
@@ -14,7 +14,7 @@
</queries>
<application
android:label="Paladin VPN"
android:label="REVOLT VPN"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher"
android:usesCleartextTraffic="false"
@@ -1,4 +1,4 @@
package com.paladinvpn.app
package com.revoltvpn.app
import io.flutter.embedding.android.FlutterActivity
+5 -5
View File
@@ -1,10 +1,10 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:paladinvpn/logic/vpn_connection.dart';
import 'package:paladinvpn/logic/session_timer.dart';
import 'package:paladinvpn/logic/ad_manager.dart';
import 'package:paladinvpn/components/online_dot.dart';
import 'package:paladinvpn/components/watch_ad_button.dart';
import 'package:revoltvpn/logic/vpn_connection.dart';
import 'package:revoltvpn/logic/session_timer.dart';
import 'package:revoltvpn/logic/ad_manager.dart';
import 'package:revoltvpn/components/online_dot.dart';
import 'package:revoltvpn/components/watch_ad_button.dart';
/// BottomBar is the bottom utility panel that surfaces server connectivity status
/// and offers the user an option to support the server when the VPN tunnel is active.
+1 -1
View File
@@ -1,6 +1,6 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:paladinvpn/logic/session_timer.dart';
import 'package:revoltvpn/logic/session_timer.dart';
/// ClockDisplay renders inside the ConnectButton circle when the VPN is active.
///
+6 -6
View File
@@ -1,9 +1,9 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:provider/provider.dart';
import 'package:paladinvpn/logic/vpn_connection.dart';
import 'package:paladinvpn/logic/session_timer.dart';
import 'package:paladinvpn/components/clock_display.dart';
import 'package:revoltvpn/logic/vpn_connection.dart';
import 'package:revoltvpn/logic/session_timer.dart';
import 'package:revoltvpn/components/clock_display.dart';
class ConnectButton extends StatefulWidget {
const ConnectButton({super.key});
@@ -108,13 +108,13 @@ class _ConnectButtonState extends State<ConnectButton>
title: const Text('VPN Permission Required',
style: TextStyle(color: Colors.white)),
content: const Text(
'PaladinVPN needs permission to set up a VPN connection.\n\n'
'ReVoltVPN needs permission to set up a VPN connection.\n\n'
'Since the permission was denied once, Android won\'t ask again. '
'To grant it manually:\n\n'
'1. Open your device Settings\n'
'2. Go to Apps → PaladinVPN\n'
'2. Go to Apps → ReVoltVPN\n'
'3. Tap "VPN" and enable it\n'
'4. Return to PaladinVPN and tap connect',
'4. Return to ReVoltVPN and tap connect',
style: TextStyle(color: Color(0xFFA0AEC0)),
),
actions: [
+1 -1
View File
@@ -72,7 +72,7 @@ class GdprButton extends StatelessWidget {
void _fallbackToPrivacyPolicy() async {
final Uri url = Uri.parse(
'https://github.com/esefxdz/PaladinVPN/blob/main/PRIVACY_POLICY.md',
'https://github.com/esefxdz/ReVoltVPN/blob/main/PRIVACY_POLICY.md',
);
if (!await launchUrl(url, mode: LaunchMode.externalApplication)) {
debugPrint('Could not launch $url');
+3 -3
View File
@@ -1,13 +1,13 @@
import 'package:flutter/material.dart';
/// Header displays the Paladin brand logo, title, and subtitle.
/// Header displays the ReVolt brand logo, title, and subtitle.
///
/// It represents the top branding portion of the main dashboard and is designed
/// with clean proportions, consistent typography, and a modern cyberpunk-inspired color scheme.
class Header extends StatelessWidget {
const Header({super.key});
// Cyber-paladin cyan glow accent color used as a fallback for the shield icon
// cyber-revolt cyan glow accent color used as a fallback for the shield icon
static const Color _cyanGlow = Color(0xFF00E5FF);
@override
@@ -30,7 +30,7 @@ class Header extends StatelessWidget {
// Brand primary name
const Text(
'Paladin',
'ReVolt',
style: TextStyle(
fontSize: 22,
fontWeight: FontWeight.w800,
+2 -2
View File
@@ -5,7 +5,7 @@ class InfoButton extends StatelessWidget {
const InfoButton({super.key});
Future<void> _launchGitHub() async {
final Uri url = Uri.parse('https://github.com/esefxdz/PaladinVPN');
final Uri url = Uri.parse('https://github.com/esefxdz/ReVoltVPN');
if (!await launchUrl(url, mode: LaunchMode.externalApplication)) {
debugPrint('Could not launch $url');
}
@@ -16,7 +16,7 @@ class InfoButton extends StatelessWidget {
return IconButton(
icon: const Icon(Icons.help_outline, color: Colors.white, size: 28),
onPressed: _launchGitHub,
tooltip: 'About PaladinVPN',
tooltip: 'About ReVoltVPN',
splashRadius: 24,
);
}
+3 -3
View File
@@ -46,7 +46,7 @@ class VpnNotificationManager {
// the old channel was created without 'ongoing: true', users would be
// stuck with a swipeable notification forever.
const androidDetails = AndroidNotificationDetails(
'paladin_vpn_status_v2',
'revolt_vpn_status_v2',
'VPN Status',
channelDescription: 'Shown while VPN is active — non-dismissable',
importance: Importance.low,
@@ -72,7 +72,7 @@ class VpnNotificationManager {
try {
await _plugin.show(
888, // Fixed ID — always updates the same notification
'Paladin VPN Active',
'REVOLT VPN Active',
'Time: $timeLeft • $speedKbps',
notificationDetails,
);
@@ -84,7 +84,7 @@ class VpnNotificationManager {
await init();
await _plugin.show(
888,
'Paladin VPN Active',
'REVOLT VPN Active',
'Time: $timeLeft • $speedKbps',
notificationDetails,
);
+1 -1
View File
@@ -5,7 +5,7 @@ class PrivacyPolicyButton extends StatelessWidget {
const PrivacyPolicyButton({super.key});
Future<void> _launchPrivacyPolicy() async {
final Uri url = Uri.parse('https://github.com/esefxdz/PaladinVPN/blob/main/PRIVACY_POLICY.md');
final Uri url = Uri.parse('https://github.com/esefxdz/ReVoltVPN/blob/main/PRIVACY_POLICY.md');
if (!await launchUrl(url, mode: LaunchMode.externalApplication)) {
debugPrint('Could not launch $url');
}
+1 -1
View File
@@ -1,6 +1,6 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:paladinvpn/logic/vpn_connection.dart';
import 'package:revoltvpn/logic/vpn_connection.dart';
/// StatusText displays the status of the VPN tunnel.
///
+2 -2
View File
@@ -1,7 +1,7 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:paladinvpn/logic/session_timer.dart';
import 'package:paladinvpn/logic/ad_manager.dart';
import 'package:revoltvpn/logic/session_timer.dart';
import 'package:revoltvpn/logic/ad_manager.dart';
/// WatchAdButton is a voluntary option allowing the user to watch a rewarded video ad
/// and extend their active VPN tunnel session by 30 minutes as a reward.
+3 -3
View File
@@ -3,9 +3,9 @@ import 'dart:convert';
import 'dart:math';
import 'package:flutter/foundation.dart';
import 'package:google_mobile_ads/google_mobile_ads.dart';
import 'package:paladinvpn/logic/crypto_service.dart';
import 'package:paladinvpn/logic/hivemind_service.dart';
import 'package:paladinvpn/logic/app_config.dart';
import 'package:revoltvpn/logic/crypto_service.dart';
import 'package:revoltvpn/logic/hivemind_service.dart';
import 'package:revoltvpn/logic/app_config.dart';
/// AdManager manages the lifecycle of rewarded video ads.
///
+3 -3
View File
@@ -3,8 +3,8 @@ import 'dart:convert';
import 'dart:math';
import 'package:flutter/foundation.dart';
import 'package:http/http.dart' as http;
import 'package:paladinvpn/logic/app_config.dart';
import 'package:paladinvpn/logic/crypto_service.dart';
import 'package:revoltvpn/logic/app_config.dart';
import 'package:revoltvpn/logic/crypto_service.dart';
class HivemindService {
/// Nonce generated at the start of a connect cycle.
@@ -76,7 +76,7 @@ class HivemindService {
'&security=${AppConfig.vlessSecurity}'
'&type=${AppConfig.vlessType}'
'&flow=${AppConfig.vlessFlow}'
'#PaladinVPN';
'#ReVoltVPN';
_expectedNonce = null; // consumed successfully
return vlessUrl;
+4 -4
View File
@@ -2,10 +2,10 @@ import 'dart:async';
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:http/http.dart' as http;
import 'package:paladinvpn/logic/app_config.dart';
import 'package:paladinvpn/logic/vpn_connection.dart';
import 'package:paladinvpn/logic/crypto_service.dart';
import 'package:paladinvpn/components/notification.dart';
import 'package:revoltvpn/logic/app_config.dart';
import 'package:revoltvpn/logic/vpn_connection.dart';
import 'package:revoltvpn/logic/crypto_service.dart';
import 'package:revoltvpn/components/notification.dart';
/// SessionTimer is the client-side session authority for the VPN tunnel.
///
+3 -3
View File
@@ -5,7 +5,7 @@
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter_vless/flutter_vless.dart';
import 'package:paladinvpn/logic/hivemind_service.dart';
import 'package:revoltvpn/logic/hivemind_service.dart';
// ── VPN States ────────────────────────────────────────────────────────────────
enum VpnStatus {
@@ -50,7 +50,7 @@ class VpnConnection extends ChangeNotifier {
try {
await _vless.initializeVless(
providerBundleIdentifier: 'com.paladinvpn.app',
providerBundleIdentifier: 'com.revoltvpn.app',
);
_initialized = true;
} catch (e) {
@@ -148,7 +148,7 @@ class VpnConnection extends ChangeNotifier {
final config = parsed.getFullConfiguration();
await _vless.startVless(
remark: parsed.remark.isNotEmpty ? parsed.remark : 'PaladinVPN',
remark: parsed.remark.isNotEmpty ? parsed.remark : 'ReVoltVPN',
config: config,
);
+10 -10
View File
@@ -3,13 +3,13 @@ import 'package:flutter/services.dart';
import 'package:provider/provider.dart';
import 'package:google_mobile_ads/google_mobile_ads.dart';
import 'package:paladinvpn/logic/vpn_connection.dart';
import 'package:paladinvpn/logic/session_timer.dart';
import 'package:paladinvpn/logic/ad_manager.dart';
import 'package:paladinvpn/logic/consent_manager.dart';
import 'package:paladinvpn/screens/intro.dart';
import 'package:revoltvpn/logic/vpn_connection.dart';
import 'package:revoltvpn/logic/session_timer.dart';
import 'package:revoltvpn/logic/ad_manager.dart';
import 'package:revoltvpn/logic/consent_manager.dart';
import 'package:revoltvpn/screens/intro.dart';
/// The entry point for the Paladin VPN application.
/// The entry point for the REVOLT VPN application.
void main() async {
// Ensure that the Flutter engine is fully initialized before using platform channels
// (e.g., for SystemChrome or MobileAds).
@@ -39,12 +39,12 @@ void main() async {
await MobileAds.instance.initialize();
// Inflate the root widget
runApp(const PaladinApp());
runApp(const ReVoltApp());
}
/// The root application widget wrapping the app in necessary state providers and themes.
class PaladinApp extends StatelessWidget {
const PaladinApp({super.key});
class ReVoltApp extends StatelessWidget {
const ReVoltApp({super.key});
@override
Widget build(BuildContext context) {
@@ -69,7 +69,7 @@ class PaladinApp extends StatelessWidget {
ChangeNotifierProvider(create: (_) => AdManager()),
],
child: MaterialApp(
title: 'Paladin VPN',
title: 'ReVolt VPN',
debugShowCheckedModeBanner: false,
theme: _buildDarkTheme(),
home: const IntroScreen(),
+2 -2
View File
@@ -1,6 +1,6 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:paladinvpn/screens/main_screen.dart';
import 'package:revoltvpn/screens/main_screen.dart';
class IntroScreen extends StatefulWidget {
const IntroScreen({super.key});
@@ -77,7 +77,7 @@ class _IntroScreenState extends State<IntroScreen> with WidgetsBindingObserver {
),
const SizedBox(height: 30),
const Text(
'PALADIN VPN',
'REVOLT VPN',
style: TextStyle(
color: Colors.white,
fontSize: 28,
+8 -8
View File
@@ -1,13 +1,13 @@
import 'package:flutter/material.dart';
import 'package:paladinvpn/components/header.dart';
import 'package:paladinvpn/components/status_text.dart';
import 'package:paladinvpn/components/connect_button.dart';
import 'package:paladinvpn/components/bottom_bar.dart';
import 'package:paladinvpn/components/info_button.dart';
import 'package:paladinvpn/components/privacy_policy_button.dart';
import 'package:paladinvpn/components/gdpr_button.dart';
import 'package:revoltvpn/components/header.dart';
import 'package:revoltvpn/components/status_text.dart';
import 'package:revoltvpn/components/connect_button.dart';
import 'package:revoltvpn/components/bottom_bar.dart';
import 'package:revoltvpn/components/info_button.dart';
import 'package:revoltvpn/components/privacy_policy_button.dart';
import 'package:revoltvpn/components/gdpr_button.dart';
/// MainScreen serves as the primary home screen for Paladin VPN.
/// MainScreen serves as the primary home screen for REVOLT VPN.
///
/// It provides a dark mode, cyberpunk-inspired visual layout displaying:
/// - Logo and Branding at the top (`Header`)
+2 -2
View File
@@ -1,5 +1,5 @@
name: paladinvpn
description: Paladin VPN — Secure VLESS tunnel for Android
name: revoltvpn
description: ReVolt VPN — Secure VLESS tunnel for Android
# The following line prevents the package from being accidentally published to
# pub.dev using `pub publish`. This is preferred for private packages.
-200
View File
@@ -1,200 +0,0 @@
#!/bin/bash
# PaladinVPN — Full Server Picture (VLESS era)
# Copy-paste this entire block into your SSH terminal on the Hetzner Debian box.
# Read-only — makes no changes to the system.
# Run as root or with sudo.
bash <<'REPORT'
echo "============================================================"
echo " PaladinVPN — Server State Report"
echo " $(date '+%Y-%m-%d %H:%M:%S %Z')"
echo "============================================================"
# ── 1. System ────────────────────────────────────────────────
echo ""; echo "=== SYSTEM ==="
echo "Hostname : $(hostname)"
echo "Uptime : $(uptime -p)"
echo "Debian : $(cat /etc/debian_version 2>/dev/null || echo 'N/A')"
echo "Kernel : $(uname -r)"
echo "Arch : $(uname -m)"
echo "Load : $(uptime | awk -F'load average:' '{print $2}' | xargs)"
echo ""
echo "CPU:"
echo "$(lscpu | grep -E 'Model name|CPU\(s\)|Thread' | sed 's/^/ /')" 2>/dev/null
echo ""
echo "Memory:"
free -h | grep -E '^Mem:|^Swap:' | sed 's/^/ /'
echo ""
echo "Disk:"
df -h / | tail -1 | awk '{print " Root: " $3 "/" $2 " (" $5 ")"}'
# ── 2. Network Interfaces ────────────────────────────────────
echo ""; echo "=== NETWORK INTERFACES ==="
ip -br addr 2>/dev/null || ifconfig -a 2>/dev/null
echo ""
echo "Routes:"
ip route show 2>/dev/null | grep -v '^fe80' || route -n
echo ""
echo "DNS:"
cat /etc/resolv.conf 2>/dev/null | grep -v '^#' | grep -v '^$' | sed 's/^/ /'
# ── 3. Open Ports ────────────────────────────────────────────
echo ""; echo "=== LISTENING PORTS ==="
ss -tlnp 2>/dev/null | head -30 || netstat -tlnp 2>/dev/null | head -30
# ── 4. IPv6 Status ───────────────────────────────────────────
echo ""; echo "=== IPv6 ==="
echo "Forwarding:"
sysctl net.ipv6.conf.all.forwarding 2>/dev/null
echo ""
echo "IPv6 routes:"
ip -6 route show 2>/dev/null | head -10
echo ""
echo "IPv6 NAT (ip6tables):"
ip6tables -t nat -L POSTROUTING -n 2>/dev/null | head -10 || echo " ip6tables not available"
# ── 5. Firewall (iptables) ───────────────────────────────────
echo ""; echo "=== IPTABLES NAT ==="
iptables -t nat -L POSTROUTING -n -v 2>/dev/null | head -20 || echo " iptables not available"
# ── 6. Services ──────────────────────────────────────────────
echo ""; echo "=== SERVICES ==="
for svc in hivemind xray nginx docker certbot; do
if systemctl is-enabled "$svc" &>/dev/null; then
printf " %-12s : %s (enabled)\n" "$svc" "$(systemctl is-active "$svc")"
elif systemctl is-active "$svc" &>/dev/null; then
printf " %-12s : %s (not enabled)\n" "$svc" "$(systemctl is-active "$svc")"
else
printf " %-12s : not found\n" "$svc"
fi
done
echo ""
echo "Certbot timer:"
systemctl list-timers --all 2>/dev/null | grep -i certbot || echo " No certbot timer found"
# ── 7. Xray ──────────────────────────────────────────────────
echo ""; echo "=== XRAY ==="
if command -v xray &>/dev/null; then
echo "Binary : $(which xray)"
echo "Version : $(xray version 2>&1 | head -1)"
echo "Status : $(systemctl is-active xray 2>/dev/null || echo 'N/A')"
echo ""
echo "Config file exists?"
if [ -f /usr/local/etc/xray/config.json ]; then
echo " YES — $(wc -c < /usr/local/etc/xray/config.json) bytes"
echo " Clients: $(python3 -c "import json; c=json.load(open('/usr/local/etc/xray/config.json')); print(len(c['inbounds'][0]['settings']['clients']))" 2>/dev/null || echo 'ERROR reading config')"
else
echo " NO — /usr/local/etc/xray/config.json not found"
fi
echo ""
echo "Stats API:"
curl -s --max-time 3 http://127.0.0.1:10085/stats/query -d '{"pattern":"","reset":false}' 2>/dev/null | python3 -m json.tool 2>/dev/null | head -10 || echo " Stats API unreachable"
echo ""
echo "Last 5 Xray log lines:"
journalctl -u xray --no-pager -n 5 2>/dev/null | sed 's/^/ /' || echo " No journal entries"
else
echo " Xray NOT INSTALLED"
fi
# ── 8. Hivemind ──────────────────────────────────────────────
echo ""; echo "=== HIVEMIND ==="
echo "Status : $(systemctl is-active hivemind 2>/dev/null || echo 'N/A')"
echo "Config : $(systemctl cat hivemind 2>/dev/null | grep -i execstart | sed 's/^/ /')"
echo ""
echo "Active sessions:"
python3 -c "
import sys; sys.path.insert(0, '/root')
try:
from server_hivemind_5_0 import active_sessions, sessions_lock
with sessions_lock:
count = len(active_sessions)
throttled = sum(1 for s in active_sessions.values() if s.get('is_throttled'))
print(f' Total: {count} Throttled: {throttled}')
except Exception as e:
print(f' Could not read sessions: {e}')
" 2>/dev/null || echo " Could not import hivemind module"
echo ""
echo "Hivemind script:"
if [ -f /root/server_hivemind_5_0.py ]; then
echo " /root/server_hivemind_5_0.py — $(wc -l < /root/server_hivemind_5_0.py) lines"
elif [ -f /root/server_hivemind_* ]; then
ls -la /root/server_hivemind_* 2>/dev/null | sed 's/^/ /'
else
echo " No hivemind script found in /root"
fi
echo ""
echo "Last 10 Hivemind log lines:"
journalctl -u hivemind --no-pager -n 10 2>/dev/null | sed 's/^/ /' || echo " No journal entries"
# ── 9. Nginx ─────────────────────────────────────────────────
echo ""; echo "=== NGINX ==="
if command -v nginx &>/dev/null; then
echo "Binary : $(which nginx)"
echo "Version : $(nginx -v 2>&1)"
echo "Status : $(systemctl is-active nginx 2>/dev/null || echo 'N/A')"
echo ""
echo "Config test:"
nginx -t 2>&1 | sed 's/^/ /'
echo ""
echo "Sites enabled:"
ls -la /etc/nginx/sites-enabled/ 2>/dev/null | sed 's/^/ /'
echo ""
echo "/tunnel location block exists?"
grep -r "location /tunnel" /etc/nginx/sites-enabled/ 2>/dev/null | sed 's/^/ /' || echo " NOT FOUND"
echo ""
echo "/api location block exists?"
grep -r "location /api" /etc/nginx/sites-enabled/ 2>/dev/null | sed 's/^/ /' || echo " NOT FOUND"
else
echo " NGINX NOT INSTALLED"
fi
# ── 10. SSL ──────────────────────────────────────────────────
echo ""; echo "=== SSL CERTIFICATES ==="
if command -v certbot &>/dev/null; then
certbot certificates 2>/dev/null | sed 's/^/ /' || echo " No certificates found"
echo ""
echo "Certificate files:"
ls -la /etc/letsencrypt/live/ 2>/dev/null | sed 's/^/ /' || echo " /etc/letsencrypt/live/ not found"
else
echo " certbot not installed"
fi
# ── 11. Docker ────────────────────────────────────────────────
echo ""; echo "=== DOCKER ==="
if command -v docker &>/dev/null; then
echo "Running containers:"
docker ps --format ' {{.Names}} {{.Image}} {{.Status}} {{.Ports}}' 2>/dev/null || echo " None"
else
echo " Docker not installed"
fi
# ── 12. Traffic Control (tc) ─────────────────────────────────
echo ""; echo "=== TRAFFIC CONTROL (tc) ==="
if command -v tc &>/dev/null; then
for iface in $(ip -br link 2>/dev/null | awk '{print $1}'); do
rules=$(tc qdisc show dev "$iface" 2>/dev/null | grep -v "noqueue\|fq_codel\|pfifo_fast" | head -5)
if [ -n "$rules" ]; then
echo " Interface $iface:"
echo "$rules" | sed 's/^/ /'
fi
done
echo " (no custom tc rules found unless listed above)"
else
echo " tc not available"
fi
# ── 13. AWG leftovers ────────────────────────────────────────
echo ""; echo "=== AWG LEFTOVERS ==="
echo "awg0 interface : $([ -d /sys/class/net/awg0 ] && echo 'STILL EXISTS' || echo 'gone ✓')"
echo "awg binary : $(command -v awg 2>/dev/null && echo 'STILL INSTALLED' || echo 'gone ✓')"
echo "wg-easy docker : $(docker ps -a --filter 'name=wg-easy' --format '{{.Names}}' 2>/dev/null || echo 'gone ✓')"
echo "iptables 10.8.x: $(iptables -t nat -L POSTROUTING 2>/dev/null | grep '10\.8\.' | head -3 || echo 'gone ✓')"
# ── 14. Recent Errors ────────────────────────────────────────
echo ""; echo "=== RECENT ERRORS (last 50 journal lines, filtered) ==="
journalctl -u hivemind -u xray -u nginx --no-pager -n 50 --since "1 hour ago" 2>/dev/null | grep -iE 'error|fail|warn|exception|cannot|denied|timeout' | tail -10 | sed 's/^/ /' || echo " No recent errors"
echo ""; echo "============================================================"
echo " Report complete — $(date '+%H:%M:%S')"
echo "============================================================"
REPORT