Setup Script & Actual Firewall DNS Scripts. As well as optional functions to do automated setups

This commit is contained in:
SimplifiedPrivacy 2026-07-22 20:37:20 -04:00
parent dbff960aad
commit 4d6f810100
9 changed files with 404 additions and 14 deletions

View file

@ -0,0 +1,53 @@
#!/bin/bash
set -euo pipefail
if [[ "$EUID" -ne 0 ]]; then
echo "[killswitch] ERROR: must be run as root" >&2
exit 1
fi
ACTION="${1:-}"
IFACE="${2:-}"
DNS="${3:-}"
_check_prereqs() {
[[ -z "$DNS" ]] && { echo "Error: DNS IP required" >&2; exit 1; }
# ✓ Check it's a valid IPv4 address:
if ! [[ "$DNS" =~ ^[0-9]{1,3}(\.[0-9]{1,3}){3}$ ]]; then
echo "Error: Invalid IP: $DNS" >&2
exit 1
fi
# ✓ checks interface exists before configuring
if ! ip link show "$IFACE" &>/dev/null; then
echo "Error: Interface $IFACE not found" >&2
exit 1
fi
}
_set_dns() {
resolvectl dns "$IFACE" "$DNS" || {
echo "ERROR: Failed to set DNS to $DNS" >&2
return 1
}
resolvectl domain "$IFACE" '~.' || {
echo "ERROR: Failed to set domain routing" >&2
return 1
}
resolvectl default-route "$IFACE" true || {
echo "ERROR: Failed to set default route" >&2
return 1
}
}
if [[ "$ACTION" == "set" ]]; then
_check_prereqs
_set_dns
fi
if [[ "$ACTION" == "revert" ]]; then
resolvectl revert "$IFACE" 2>/dev/null || true
fi

View file

@ -0,0 +1,120 @@
#!/bin/bash
set -euo pipefail
if [[ "$EUID" -ne 0 ]]; then
echo "[killswitch] ERROR: must be run as root" >&2
exit 1
fi
ACTION="${1:-}"
SERVER_IP="${2:-}"
TUNNEL_IF="${3:-}"
INTERNAL_SUBNET="${4:-}"
TABLE="hydraveil"
if [[ "$ACTION" != "arm" && "$ACTION" != "disarm" && "$ACTION" != "status" ]]; then
echo "[killswitch] ERROR: invalid action '$ACTION'. Usage: arm <server_ip> <tunnel_if> [internal_subnet] | disarm | status" >&2
exit 1
fi
_default_iface() {
ip route show default 2>/dev/null | awk 'NR==1{print $5}'
}
_log() {
local level="$1"; shift
echo "[killswitch] [$level] $* — $(date '+%Y-%m-%d %H:%M:%S')" >&2
}
if [[ "$ACTION" == "status" ]]; then
if nft list table inet "$TABLE" &>/dev/null 2>&1; then
echo "armed"
else
echo "disarmed"
fi
exit 0
fi
if [[ "$ACTION" == "disarm" ]]; then
if nft list table inet "$TABLE" &>/dev/null 2>&1; then
nft delete table inet "$TABLE"
_log "INFO" "disarmed"
else
_log "INFO" "already disarmed"
fi
exit 0
fi
[[ -z "$SERVER_IP" ]] && { _log "ERROR" "server_ip required for arm"; exit 1; }
[[ -z "$TUNNEL_IF" ]] && { _log "ERROR" "tunnel_if required for arm"; exit 1; }
if ! [[ "$SERVER_IP" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]; then
_log "ERROR" "invalid IPv4: $SERVER_IP"
exit 1
fi
IFS='.' read -r o1 o2 o3 o4 <<< "$SERVER_IP"
for oct in "$o1" "$o2" "$o3" "$o4"; do
if (( oct > 255 )); then
_log "ERROR" "invalid IPv4 octet ($oct) in $SERVER_IP"
exit 1
fi
done
WAN_IFACE=$(_default_iface)
if [[ -z "$WAN_IFACE" ]]; then
_log "ERROR" "could not detect primary network interface"
exit 1
fi
INTERNAL_RULE=""
if [[ -n "$INTERNAL_SUBNET" ]]; then
INTERNAL_RULE=" ip daddr ${INTERNAL_SUBNET} accept"
fi
nft list table inet "$TABLE" &>/dev/null 2>&1 && nft delete table inet "$TABLE" || true
nft -f - << NFTEOF
table inet ${TABLE} {
chain output {
type filter hook output priority filter; policy drop;
oifname "lo" accept
ether type arp drop
ip6 daddr != ::1 drop
ip daddr 224.0.0.0/4 drop
ip daddr 255.255.255.255 drop
ip daddr 192.168.1.0/24 drop
oifname "${WAN_IFACE}" ip daddr ${SERVER_IP} accept
oifname "${TUNNEL_IF}" accept
log prefix "hydraveil-drop " drop
}
chain input {
type filter hook input priority filter; policy drop;
iifname "lo" accept
ether type arp drop
ip6 saddr != ::1 drop
ip daddr 224.0.0.0/4 drop
ip daddr 255.255.255.255 drop
ct state established,related accept
iifname "${WAN_IFACE}" ip saddr ${SERVER_IP} accept
iifname "${TUNNEL_IF}" accept
log prefix "hydraveil-drop " drop
}
chain forward {
type filter hook forward priority filter; policy drop;
}
}
NFTEOF
_log "INFO" "armed — wan=${WAN_IFACE} server=${SERVER_IP} tunnel=${TUNNEL_IF} internal=${INTERNAL_SUBNET:-none}"
exit 0

View file

@ -0,0 +1,102 @@
#!/bin/bash
ORIGINAL_FOLDER="${ORIGINAL_FOLDER:-$(pwd)}"
TARGET_FOLDER="${TARGET_FOLDER:-/opt/hydra-veil}"
LINUX_USER="${LINUX_USER:-$SUDO_USER}"
add_sudoers_rule() {
sudo tee /etc/sudoers.d/zzzzzzzzzzzzzzzzzzz > /dev/null <<EOF
${LINUX_USER} ALL=(root) NOPASSWD: /opt/hydra-veil/firewall
${LINUX_USER} ALL=(root) NOPASSWD: /opt/hydra-veil/dns
EOF
}
# Check if running as root
if [[ $EUID -ne 0 ]]; then
echo "Error: This script must be run as root (use sudo)" >&2
exit 1
fi
# Validate environment variables
if [[ -z "$ORIGINAL_FOLDER" ]]; then
echo "Error: ORIGINAL_FOLDER environment variable not set" >&2
exit 1
fi
if [[ -z "$TARGET_FOLDER" ]]; then
echo "Error: TARGET_FOLDER environment variable not set" >&2
exit 1
fi
# Validate that source folder exists
if [[ ! -d "$ORIGINAL_FOLDER" ]]; then
echo "Error: ORIGINAL_FOLDER does not exist: $ORIGINAL_FOLDER" >&2
exit 1
fi
# Create target folder if it doesn't exist
if [[ ! -d "$TARGET_FOLDER" ]]; then
echo "Creating target folder: $TARGET_FOLDER"
mkdir -p "$TARGET_FOLDER"
fi
# Get the setup script's own filename, because we'll exclude it from being copied,
SCRIPT_NAME="$(basename "$0")"
echo "Starting file copy from $ORIGINAL_FOLDER to $TARGET_FOLDER"
echo "Script name to exclude: $SCRIPT_NAME"
echo ""
# Counter for tracking progress
COPIED=0
SKIPPED=0
# Loop through all files in source folder
while IFS= read -r -d '' file; do
filename="$(basename "$file")"
# Skip if this is the script itself
if [[ "$filename" == "$SCRIPT_NAME" ]]; then
echo "⊘ Skipping: $filename (this script)"
((SKIPPED++))
continue
fi
# Copy the file
if cp "$file" "$TARGET_FOLDER/"; then
echo "✓ Copied: $filename"
((COPIED++))
# Set permissions to 755
if chmod 755 "$TARGET_FOLDER/$filename"; then
echo " └─ chmod 755 applied"
else
echo " └─ WARNING: chmod 755 failed for $filename" >&2
fi
else
echo "✗ ERROR: Failed to copy $filename" >&2
((SKIPPED++))
fi
done < <(find "$ORIGINAL_FOLDER" -maxdepth 1 -type f -print0)
echo ""
echo "=========================================="
echo "Copy complete!"
echo "Files copied: $COPIED"
echo "Files skipped: $SKIPPED"
echo "Target folder: $TARGET_FOLDER"
echo "=========================================="
# Try to apply the rules, if not emit 1
if add_sudoers_rule; then
echo "Operation successful"
else
echo "Operation failed - sudoers rule not applied"
exit 1
fi
exit 0

View file

@ -65,6 +65,14 @@ class Configuration:
) )
) )
did_sudo_setup: Optional[bool] = field(
default=False,
metadata=config(
undefined=dataclasses_json.Undefined.EXCLUDE,
exclude=lambda value: value is None
)
)
def save(self: Self): def save(self: Self):
config_file_contents = f'{self.to_json(indent=4)}\n' config_file_contents = f'{self.to_json(indent=4)}\n'

View file

@ -1,16 +1,28 @@
from core.Constants import Constants from core.Constants import Constants
from core.errors.logger import logger from core.errors.logger import logger
# generic
from pathlib import Path
import shutil import shutil
import os import os
def assets_folder_setup(): def assets_folder_setup():
initial_appimage_assets = f"{Constants.APPDIR_HOME}/assets" initial_appimage_assets = f"{Constants.APPDIR_HOME}/assets"
current_assets_folder = f"{Constants.HV_DATA_HOME}/assets" current_assets_folder = f"{Constants.HV_DATA_HOME}/assets"
if os.path.exists(current_assets_folder): if os.path.exists(current_assets_folder):
return True return True
return copy_folders(initial_appimage_assets, current_assets_folder)
def sudo_assets_folder_setup() -> bool:
# initial_appimage_assets = f"{Constants.APPDIR_HOME}/assets/sudo_scripts"
initial_appimage_assets = f"/home/a/VPN/code/DEV/sp-hydra-veil-core/core/assets/sudo_scripts"
current_assets_folder = f"{Constants.HOME}/Downloads/hydraveil_sudo_scripts"
return copy_folders(initial_appimage_assets, current_assets_folder)
def copy_folders(initial_appimage_assets: str, current_assets_folder: str) -> bool:
try: try:
os.makedirs(current_assets_folder, exist_ok=True) os.makedirs(current_assets_folder, exist_ok=True)
for item in os.listdir(initial_appimage_assets): for item in os.listdir(initial_appimage_assets):
@ -20,9 +32,22 @@ def assets_folder_setup():
shutil.copytree(src, dst) shutil.copytree(src, dst)
else: else:
shutil.copy2(src, dst) shutil.copy2(src, dst)
logger.info(f"[MANAGE ASSETS] Successfully copied over the folder.")
return True return True
except Exception as e: except Exception as e:
logger.error(f"initial_appimage_assets is {initial_appimage_assets}") logger.error(f"[MANAGE ASSETS] initial_appimage_assets is {initial_appimage_assets}")
logger.error(f"current_assets_folder is {current_assets_folder}") logger.error(f"[MANAGE ASSETS] current_assets_folder is {current_assets_folder}")
logger.error(f"ERROR: {e}") logger.error(f"[MANAGE ASSETS] ERROR: {e}")
return False
def cleanup_setup_dir():
"""After install completes"""
delete_this_folder = f"{Constants.HOME}/Downloads/hydraveil_sudo_scripts"
try:
shutil.rmtree(delete_this_folder)
logger.info(f"[MANAGE ASSETS] Cleaned up {delete_this_folder}")
return True
except Exception as e:
logger.error(f"[MANAGE ASSETS] We could not delete {delete_this_folder} because {e}")
return False return False

View file

@ -0,0 +1,67 @@
from core.services.helpers.manage_assets import sudo_assets_folder_setup
from core.utils.basic_operations.confirm_files_exist import confirm_files_and_folders_exist
from core.models.Result import Result, ResultError
from core.Constants import Constants
from core.errors.logger import logger
import subprocess
from pathlib import Path
import shutil
import os
from subprocess import CalledProcessError
import copy
FILES_TO_CHECK = ["setup.sh", "firewall", "dns"]
TARGET_FOLDER = "/opt/hydra-veil"
def auto_install_sudo_script() -> Result:
setup_dir = Path(f"{Constants.HOME}/Downloads/hydraveil_sudo_scripts")
setup_script = Path(setup_dir, 'setup.sh')
original_linux_user = os.getlogin()
# Make sure the folder exists
files_exist = confirm_files_and_folders_exist(
folder_path=setup_dir, files_to_check=FILES_TO_CHECK
)
if not files_exist:
copied = sudo_assets_folder_setup()
if not copied:
return Result(valid=False, message="Could not copy scripts to begin.")
try:
result = subprocess.run(
['pkexec', 'env', f'ORIGINAL_FOLDER={setup_dir}', f'TARGET_FOLDER={TARGET_FOLDER}', f'LINUX_USER={original_linux_user}', 'bash', setup_script],
capture_output=True,
text=True,
)
if result.returncode == 0:
logger.info("Sudo Setup Ran. Now testing..")
return Result(valid=True)
else:
logger.error(f"Sudo setup setup failed: {result.stderr.strip()}")
return Result(valid=False, message=result.stderr.strip())
except CalledProcessError as e:
return Result(valid=False, message=e)
except Exception as e:
return Result(valid=False, message=e)
def test_if_in_sudo_folder() -> Result:
final_sudo_folder = Path("/opt/hydra-veil/")
_files_to_check = FILES_TO_CHECK.copy() # to not affect the other functions.
# setup script itself doesn't get copied over:
_files_to_check.remove("setup.sh")
# Now verify it copied them:
sudo_files_copied = confirm_files_and_folders_exist(
folder_path=final_sudo_folder, files_to_check=_files_to_check
)
if sudo_files_copied:
return Result(valid=True, message="Sudo Script Setup Completed Successfully!")
else:
error_msg = "We failed to find the files in the sudo folder."
logger.error(error_msg)
return Result(valid=False, message=error_msg)

View file

@ -3,12 +3,10 @@ from core.Constants import Constants
from core.models.Result import Result, ResultError from core.models.Result import Result, ResultError
from core.errors.logger import logger from core.errors.logger import logger
# generic
import subprocess import subprocess
from subprocess import CalledProcessError from subprocess import CalledProcessError
# temp location: DNS_SCRIPT = f"/opt/hydra-veil/dns"
DNS_SCRIPT = f"/opt/hydra-veil/dns_script"
# requires script to have sudo # requires script to have sudo

View file

@ -1,15 +1,12 @@
import subprocess import subprocess
from core.Constants import Constants
from core.models.Result import Result, ResultError from core.models.Result import Result, ResultError
from core.errors.logger import logger from core.errors.logger import logger
_C = Constants()
# _C.KILLSWITCH_WRAPPER
TESTING_KILLSWITCH = "/opt/hydra-veil/test" # needs chmod 755 KILLSWITCH_WRAPPER = "/opt/hydra-veil/firewall" # needs chmod 755
def arm(server_ip: str, tunnel_if: str, internal_subnet: str = None) -> Result: def arm(server_ip: str, tunnel_if: str, internal_subnet: str = None) -> Result:
cmd = ["sudo", TESTING_KILLSWITCH, "arm", server_ip, tunnel_if] cmd = ["sudo", KILLSWITCH_WRAPPER, "arm", server_ip, tunnel_if]
if internal_subnet: if internal_subnet:
cmd.append(internal_subnet) cmd.append(internal_subnet)
result = subprocess.run(cmd, capture_output=True, text=True) result = subprocess.run(cmd, capture_output=True, text=True)
@ -23,7 +20,7 @@ def arm(server_ip: str, tunnel_if: str, internal_subnet: str = None) -> Result:
def disarm() -> bool: def disarm() -> bool:
result = subprocess.run( result = subprocess.run(
["sudo", TESTING_KILLSWITCH, "disarm"], ["sudo", KILLSWITCH_WRAPPER, "disarm"],
capture_output=True, capture_output=True,
text=True, text=True,
) )
@ -37,7 +34,7 @@ def disarm() -> bool:
def status() -> bool: def status() -> bool:
result = subprocess.run( result = subprocess.run(
["sudo", TESTING_KILLSWITCH, "status"], ["sudo", KILLSWITCH_WRAPPER, "status"],
capture_output=True, capture_output=True,
text=True, text=True,
) )

View file

@ -0,0 +1,20 @@
from core.errors.logger import logger
from pathlib import Path
def confirm_files_and_folders_exist(folder_path, files_to_check):
if folder_path.is_dir():
logger.info(f"Folder exists: {folder_path}")
# Check each file
for filename in files_to_check:
file_path = folder_path / filename
if file_path.is_file():
logger.info(f"{filename} exists")
return True
else:
logger.error(f"{filename} NOT found")
return False
else:
logger.error("Folder does not exist")
return False