1045 lines
42 KiB
Bash
Executable File
1045 lines
42 KiB
Bash
Executable File
#!/bin/bash
|
|
# /usr/local/bin/opsec-config.sh — Interactive OPSEC Configuration Manager
|
|
# TUI: whiptail primary, pure ANSI fallback
|
|
# CLI: --boot, --profile, --theme, --set, --get, --apply
|
|
#
|
|
# Usage:
|
|
# sudo opsec-config.sh # Interactive TUI
|
|
# sudo opsec-config.sh --boot on|off # Toggle boot-into-advanced
|
|
# sudo opsec-config.sh --profile save|load|list|delete NAME
|
|
# sudo opsec-config.sh --set KEY VALUE # Set config value
|
|
# sudo opsec-config.sh --get KEY # Get config value
|
|
# sudo opsec-config.sh --apply # Regenerate configs and restart services
|
|
|
|
set -euo pipefail
|
|
|
|
if [ "$EUID" -ne 0 ]; then
|
|
echo "[!] Please run as root: sudo opsec-config.sh"
|
|
exit 1
|
|
fi
|
|
|
|
OPSEC_LIB="/usr/local/lib/opsec/opsec-lib.sh"
|
|
BOOT_MARKER="/etc/opsec/boot-advanced.enabled"
|
|
|
|
# Source shared library
|
|
if [ -f "$OPSEC_LIB" ]; then
|
|
. "$OPSEC_LIB"
|
|
opsec_load_config 2>/dev/null || true
|
|
else
|
|
echo "[-] OPSEC library not found at $OPSEC_LIB"
|
|
echo " Deploy with: sudo mkdir -p /usr/local/lib/opsec && sudo cp opsec-lib.sh $OPSEC_LIB"
|
|
exit 1
|
|
fi
|
|
|
|
# ─── TUI DETECTION ─────────────────────────────────────────────────────────────
|
|
USE_WHIPTAIL=false
|
|
if command -v whiptail >/dev/null 2>&1 && [ -t 0 ]; then
|
|
USE_WHIPTAIL=true
|
|
fi
|
|
|
|
TERM_ROWS=$(tput lines 2>/dev/null || echo 24)
|
|
TERM_COLS=$(tput cols 2>/dev/null || echo 80)
|
|
WT_HEIGHT=$((TERM_ROWS - 4))
|
|
WT_WIDTH=$((TERM_COLS - 10))
|
|
[ "$WT_HEIGHT" -gt 30 ] && WT_HEIGHT=30
|
|
[ "$WT_WIDTH" -gt 78 ] && WT_WIDTH=78
|
|
|
|
# ─── ANSI COLORS ───────────────────────────────────────────────────────────────
|
|
C_CYAN="\033[38;5;51m"
|
|
C_MAG="\033[38;5;201m"
|
|
C_GREEN="\033[38;5;49m"
|
|
C_RED="\033[38;5;196m"
|
|
C_AMBER="\033[38;5;214m"
|
|
C_DIM="\033[38;5;244m"
|
|
C_WHITE="\033[38;5;255m"
|
|
C_BLUE="\033[38;5;75m"
|
|
C_RST="\033[0m"
|
|
|
|
# ─── ANSI FALLBACK HELPERS ─────────────────────────────────────────────────────
|
|
|
|
ansi_banner() {
|
|
echo -e "${C_CYAN}╔══════════════════════════════════════════════════╗${C_RST}"
|
|
echo -e "${C_CYAN}║${C_MAG} OPSEC CONFIGURATION MANAGER ${C_CYAN}║${C_RST}"
|
|
echo -e "${C_CYAN}║${C_DIM} Profile: ${PROFILE_NAME:-default}${C_CYAN}$(printf '%*s' $((36 - ${#PROFILE_NAME:-7})) '')║${C_RST}"
|
|
echo -e "${C_CYAN}╚══════════════════════════════════════════════════╝${C_RST}"
|
|
echo ""
|
|
}
|
|
|
|
ansi_menu() {
|
|
local title="$1"
|
|
shift
|
|
local items=("$@")
|
|
local choice=""
|
|
|
|
echo -e "${C_CYAN}━━━ ${C_MAG}${title}${C_CYAN} ━━━${C_RST}"
|
|
echo ""
|
|
|
|
local i=1
|
|
for item in "${items[@]}"; do
|
|
local num label
|
|
num=$(echo "$item" | cut -d'|' -f1)
|
|
label=$(echo "$item" | cut -d'|' -f2)
|
|
echo -e " ${C_CYAN}${num}${C_RST}) ${C_WHITE}${label}${C_RST}"
|
|
i=$((i + 1))
|
|
done
|
|
echo -e " ${C_RED}0${C_RST}) ${C_DIM}Back / Exit${C_RST}"
|
|
echo ""
|
|
echo -ne "${C_BLUE}▸ ${C_RST}"
|
|
read -r choice
|
|
echo "$choice"
|
|
}
|
|
|
|
ansi_toggle() {
|
|
local label="$1" current="$2"
|
|
local display
|
|
if [ "$current" = "1" ]; then
|
|
display="${C_GREEN}ON${C_RST}"
|
|
else
|
|
display="${C_RED}OFF${C_RST}"
|
|
fi
|
|
echo -e " ${C_WHITE}${label}: ${display}${C_RST}"
|
|
}
|
|
|
|
ansi_input() {
|
|
local prompt="$1" default="$2"
|
|
echo -ne "${C_BLUE}${prompt}${C_RST} [${C_DIM}${default}${C_RST}]: "
|
|
local val
|
|
read -r val
|
|
echo "${val:-$default}"
|
|
}
|
|
|
|
ansi_confirm() {
|
|
local prompt="$1"
|
|
echo -ne "${C_AMBER}${prompt} (y/N): ${C_RST}"
|
|
local ans
|
|
read -r ans
|
|
[[ "$ans" =~ ^[Yy] ]]
|
|
}
|
|
|
|
# ─── WHIPTAIL HELPERS ──────────────────────────────────────────────────────────
|
|
|
|
wt_menu() {
|
|
local title="$1"
|
|
shift
|
|
local items=()
|
|
while [ $# -gt 0 ]; do
|
|
items+=("$1" "$2")
|
|
shift 2
|
|
done
|
|
whiptail --title "$title" --menu "Profile: ${PROFILE_NAME:-default}" \
|
|
"$WT_HEIGHT" "$WT_WIDTH" $((WT_HEIGHT - 8)) "${items[@]}" 3>&1 1>&2 2>&3 || echo ""
|
|
}
|
|
|
|
wt_yesno() {
|
|
whiptail --title "$1" --yesno "$2" 10 60 3>&1 1>&2 2>&3
|
|
}
|
|
|
|
wt_input() {
|
|
whiptail --title "$1" --inputbox "$2" 10 60 "$3" 3>&1 1>&2 2>&3 || echo "$3"
|
|
}
|
|
|
|
wt_toggle() {
|
|
local key="$1" label="$2" current
|
|
current=$(opsec_get_value "$key")
|
|
if [ "$current" = "1" ]; then
|
|
opsec_set_value "$key" "0"
|
|
opsec_yellow "${label}: OFF"
|
|
else
|
|
opsec_set_value "$key" "1"
|
|
opsec_green "${label}: ON"
|
|
fi
|
|
opsec_load_config
|
|
}
|
|
|
|
# ─── MENU IMPLEMENTATIONS ─────────────────────────────────────────────────────
|
|
|
|
menu_tor() {
|
|
while true; do
|
|
opsec_load_config 2>/dev/null || true
|
|
local choice
|
|
if $USE_WHIPTAIL; then
|
|
choice=$(wt_menu "Tor Settings" \
|
|
"1" "Circuit Rotation: ${TOR_CIRCUIT_ROTATION:-30}s" \
|
|
"2" "Exit Blacklist: ${TOR_BLACKLIST:-us,gb,ca,au,nz}" \
|
|
"3" "Strict Nodes: $([ "${TOR_STRICT_NODES:-1}" = "1" ] && echo ON || echo OFF)" \
|
|
"4" "Stream Isolation: $([ "${TOR_ISOLATION:-1}" = "1" ] && echo ON || echo OFF)" \
|
|
"5" "Traffic Padding: $([ "${TOR_PADDING:-1}" = "1" ] && echo ON || echo OFF)" \
|
|
"6" "SOCKS Port: ${TOR_SOCKS_PORT:-9050}" \
|
|
"7" "DNS Port: ${TOR_DNS_PORT:-5353}" \
|
|
"8" "Entry Guards: ${TOR_NUM_GUARDS:-3}" \
|
|
"9" "Blacklist Editor...")
|
|
else
|
|
clear
|
|
ansi_banner
|
|
echo -e "${C_CYAN}━━━ ${C_MAG}TOR SETTINGS${C_CYAN} ━━━${C_RST}"
|
|
echo ""
|
|
echo -e " ${C_CYAN}1${C_RST}) Circuit Rotation: ${C_WHITE}${TOR_CIRCUIT_ROTATION:-30}s${C_RST}"
|
|
echo -e " ${C_CYAN}2${C_RST}) Exit Blacklist: ${C_WHITE}${TOR_BLACKLIST:-us,gb,ca,au,nz}${C_RST}"
|
|
ansi_toggle "3) Strict Nodes" "${TOR_STRICT_NODES:-1}"
|
|
ansi_toggle "4) Stream Isolation" "${TOR_ISOLATION:-1}"
|
|
ansi_toggle "5) Traffic Padding" "${TOR_PADDING:-1}"
|
|
echo -e " ${C_CYAN}6${C_RST}) SOCKS Port: ${C_WHITE}${TOR_SOCKS_PORT:-9050}${C_RST}"
|
|
echo -e " ${C_CYAN}7${C_RST}) DNS Port: ${C_WHITE}${TOR_DNS_PORT:-5353}${C_RST}"
|
|
echo -e " ${C_CYAN}8${C_RST}) Entry Guards: ${C_WHITE}${TOR_NUM_GUARDS:-3}${C_RST}"
|
|
echo -e " ${C_CYAN}9${C_RST}) ${C_MAG}Blacklist Editor...${C_RST}"
|
|
echo -e " ${C_RED}0${C_RST}) ${C_DIM}Back${C_RST}"
|
|
echo ""
|
|
echo -ne "${C_BLUE}▸ ${C_RST}"
|
|
read -r choice
|
|
fi
|
|
|
|
case "$choice" in
|
|
1) local val; val=$(ansi_input "Circuit rotation (seconds)" "${TOR_CIRCUIT_ROTATION:-30}")
|
|
opsec_set_value TOR_CIRCUIT_ROTATION "$val" ;;
|
|
2) local val; val=$(ansi_input "Blacklist (comma-separated country codes)" "${TOR_BLACKLIST:-us,gb,ca,au,nz}")
|
|
opsec_set_value TOR_BLACKLIST "$val" ;;
|
|
3) wt_toggle TOR_STRICT_NODES "Strict Nodes" ;;
|
|
4) wt_toggle TOR_ISOLATION "Stream Isolation" ;;
|
|
5) wt_toggle TOR_PADDING "Traffic Padding" ;;
|
|
6) local val; val=$(ansi_input "SOCKS port" "${TOR_SOCKS_PORT:-9050}")
|
|
opsec_set_value TOR_SOCKS_PORT "$val" ;;
|
|
7) local val; val=$(ansi_input "DNS port" "${TOR_DNS_PORT:-5353}")
|
|
opsec_set_value TOR_DNS_PORT "$val" ;;
|
|
8) local val; val=$(ansi_input "Number of entry guards" "${TOR_NUM_GUARDS:-3}")
|
|
opsec_set_value TOR_NUM_GUARDS "$val" ;;
|
|
9) menu_blacklist_editor ;;
|
|
0|"") return ;;
|
|
esac
|
|
done
|
|
}
|
|
|
|
menu_blacklist_editor() {
|
|
while true; do
|
|
opsec_load_config 2>/dev/null || true
|
|
opsec_load_country_presets 2>/dev/null || true
|
|
local current="${TOR_BLACKLIST:-}"
|
|
|
|
clear
|
|
echo -e "${C_CYAN}━━━ ${C_MAG}BLACKLIST EDITOR${C_CYAN} ━━━${C_RST}"
|
|
echo ""
|
|
echo -e "${C_DIM}Current blacklist:${C_RST} ${C_WHITE}${current:-none}${C_RST}"
|
|
echo ""
|
|
echo -e " ${C_CYAN}1${C_RST}) Add country code"
|
|
echo -e " ${C_CYAN}2${C_RST}) Remove country code"
|
|
echo -e " ${C_CYAN}3${C_RST}) Load preset: ${C_GREEN}Five Eyes${C_RST}"
|
|
echo -e " ${C_CYAN}4${C_RST}) Load preset: ${C_AMBER}Nine Eyes${C_RST}"
|
|
echo -e " ${C_CYAN}5${C_RST}) Load preset: ${C_RED}Fourteen Eyes${C_RST}"
|
|
echo -e " ${C_CYAN}6${C_RST}) Load preset: ${C_MAG}Max Exclusion${C_RST}"
|
|
echo -e " ${C_CYAN}7${C_RST}) Clear blacklist"
|
|
echo -e " ${C_RED}0${C_RST}) ${C_DIM}Back${C_RST}"
|
|
echo ""
|
|
echo -ne "${C_BLUE}▸ ${C_RST}"
|
|
local choice
|
|
read -r choice
|
|
|
|
case "$choice" in
|
|
1)
|
|
local code
|
|
code=$(ansi_input "Country code to add (e.g. us, de, cn)" "")
|
|
if [ -n "$code" ]; then
|
|
if [ -z "$current" ]; then
|
|
opsec_set_value TOR_BLACKLIST "$code"
|
|
else
|
|
# Avoid duplicates
|
|
if ! echo ",$current," | grep -q ",$code,"; then
|
|
opsec_set_value TOR_BLACKLIST "${current},${code}"
|
|
else
|
|
opsec_yellow "${code} already in blacklist"
|
|
sleep 1
|
|
fi
|
|
fi
|
|
fi
|
|
;;
|
|
2)
|
|
local code
|
|
code=$(ansi_input "Country code to remove" "")
|
|
if [ -n "$code" ]; then
|
|
local new_list
|
|
new_list=$(echo "$current" | tr ',' '\n' | grep -v "^${code}$" | paste -sd,)
|
|
opsec_set_value TOR_BLACKLIST "$new_list"
|
|
fi
|
|
;;
|
|
3) opsec_set_value TOR_BLACKLIST "$FIVE_EYES"; opsec_green "Five Eyes preset loaded" ; sleep 1 ;;
|
|
4) opsec_set_value TOR_BLACKLIST "$NINE_EYES"; opsec_green "Nine Eyes preset loaded" ; sleep 1 ;;
|
|
5) opsec_set_value TOR_BLACKLIST "$FOURTEEN_EYES"; opsec_green "Fourteen Eyes preset loaded" ; sleep 1 ;;
|
|
6) opsec_set_value TOR_BLACKLIST "$MAX_EXCLUSION"; opsec_green "Max exclusion preset loaded" ; sleep 1 ;;
|
|
7) opsec_set_value TOR_BLACKLIST ""; opsec_yellow "Blacklist cleared" ; sleep 1 ;;
|
|
0|"") return ;;
|
|
esac
|
|
done
|
|
}
|
|
|
|
menu_dns() {
|
|
while true; do
|
|
opsec_load_config 2>/dev/null || true
|
|
clear
|
|
echo -e "${C_CYAN}━━━ ${C_MAG}DNS SETTINGS${C_CYAN} ━━━${C_RST}"
|
|
echo ""
|
|
echo -e "${C_DIM}Current mode:${C_RST} ${C_WHITE}${DNS_MODE:-tor}${C_RST}"
|
|
[ -n "${DNS_CUSTOM_SERVERS:-}" ] && echo -e "${C_DIM}Custom servers:${C_RST} ${C_WHITE}${DNS_CUSTOM_SERVERS}${C_RST}"
|
|
echo ""
|
|
echo -e " ${C_CYAN}1${C_RST}) ${C_GREEN}tor${C_RST} — Route DNS through Tor (127.0.0.1)"
|
|
echo -e " ${C_CYAN}2${C_RST}) ${C_WHITE}quad9${C_RST} — Quad9 (9.9.9.9 / 149.112.112.112)"
|
|
echo -e " ${C_CYAN}3${C_RST}) ${C_WHITE}cloudflare${C_RST} — Cloudflare (1.1.1.1 / 1.0.0.1)"
|
|
echo -e " ${C_CYAN}4${C_RST}) ${C_AMBER}doh${C_RST} — DNS-over-HTTPS via systemd-resolved"
|
|
echo -e " ${C_CYAN}5${C_RST}) ${C_AMBER}dot${C_RST} — DNS-over-TLS via systemd-resolved"
|
|
echo -e " ${C_CYAN}6${C_RST}) ${C_MAG}custom${C_RST} — Specify custom servers"
|
|
echo -e " ${C_RED}0${C_RST}) ${C_DIM}Back${C_RST}"
|
|
echo ""
|
|
echo -ne "${C_BLUE}▸ ${C_RST}"
|
|
local choice
|
|
read -r choice
|
|
|
|
case "$choice" in
|
|
1) opsec_set_value DNS_MODE "tor" ;;
|
|
2) opsec_set_value DNS_MODE "quad9" ;;
|
|
3) opsec_set_value DNS_MODE "cloudflare" ;;
|
|
4) opsec_set_value DNS_MODE "doh" ;;
|
|
5) opsec_set_value DNS_MODE "dot" ;;
|
|
6)
|
|
local servers
|
|
servers=$(ansi_input "DNS servers (comma-separated IPs)" "${DNS_CUSTOM_SERVERS:-}")
|
|
opsec_set_value DNS_MODE "custom"
|
|
opsec_set_value DNS_CUSTOM_SERVERS "$servers"
|
|
;;
|
|
0|"") return ;;
|
|
esac
|
|
done
|
|
}
|
|
|
|
menu_killswitch() {
|
|
while true; do
|
|
opsec_load_config 2>/dev/null || true
|
|
clear
|
|
echo -e "${C_CYAN}━━━ ${C_MAG}KILL SWITCH SETTINGS${C_CYAN} ━━━${C_RST}"
|
|
echo ""
|
|
ansi_toggle "1) Allow DHCP" "${KILLSWITCH_ALLOW_DHCP:-1}"
|
|
ansi_toggle "2) Allow OpenVPN" "${KILLSWITCH_ALLOW_OPENVPN:-1}"
|
|
ansi_toggle "3) Allow WireGuard" "${KILLSWITCH_ALLOW_WIREGUARD:-1}"
|
|
echo -e " ${C_CYAN}4${C_RST}) Extra Ports: ${C_WHITE}${KILLSWITCH_EXTRA_PORTS:-none}${C_RST}"
|
|
echo -e " ${C_RED}0${C_RST}) ${C_DIM}Back${C_RST}"
|
|
echo ""
|
|
echo -ne "${C_BLUE}▸ ${C_RST}"
|
|
local choice
|
|
read -r choice
|
|
|
|
case "$choice" in
|
|
1) wt_toggle KILLSWITCH_ALLOW_DHCP "DHCP" ;;
|
|
2) wt_toggle KILLSWITCH_ALLOW_OPENVPN "OpenVPN" ;;
|
|
3) wt_toggle KILLSWITCH_ALLOW_WIREGUARD "WireGuard" ;;
|
|
4) local val; val=$(ansi_input "Extra ports (format: tcp:443,udp:8080)" "${KILLSWITCH_EXTRA_PORTS:-}")
|
|
opsec_set_value KILLSWITCH_EXTRA_PORTS "$val" ;;
|
|
0|"") return ;;
|
|
esac
|
|
done
|
|
}
|
|
|
|
menu_mac() {
|
|
while true; do
|
|
opsec_load_config 2>/dev/null || true
|
|
clear
|
|
echo -e "${C_CYAN}━━━ ${C_MAG}MAC ADDRESS SETTINGS${C_CYAN} ━━━${C_RST}"
|
|
echo ""
|
|
echo -e " ${C_CYAN}1${C_RST}) Interfaces: ${C_WHITE}${MAC_INTERFACES:-auto}${C_RST}"
|
|
echo -e " ${C_CYAN}2${C_RST}) Vendor Spoof OUI: ${C_WHITE}${MAC_VENDOR_SPOOF:-random}${C_RST}"
|
|
echo -e " ${C_RED}0${C_RST}) ${C_DIM}Back${C_RST}"
|
|
echo ""
|
|
echo -ne "${C_BLUE}▸ ${C_RST}"
|
|
local choice
|
|
read -r choice
|
|
|
|
case "$choice" in
|
|
1) local val; val=$(ansi_input "Interfaces (auto or comma-separated, e.g. eth0,wlan0)" "${MAC_INTERFACES:-auto}")
|
|
opsec_set_value MAC_INTERFACES "$val" ;;
|
|
2) local val; val=$(ansi_input "Vendor OUI prefix (e.g. 00:1A:2B) or empty for random" "${MAC_VENDOR_SPOOF:-}")
|
|
opsec_set_value MAC_VENDOR_SPOOF "$val" ;;
|
|
0|"") return ;;
|
|
esac
|
|
done
|
|
}
|
|
|
|
menu_hostname() {
|
|
while true; do
|
|
opsec_load_config 2>/dev/null || true
|
|
clear
|
|
echo -e "${C_CYAN}━━━ ${C_MAG}HOSTNAME SETTINGS${C_CYAN} ━━━${C_RST}"
|
|
echo ""
|
|
echo -e " ${C_CYAN}1${C_RST}) Pattern: ${C_WHITE}${HOSTNAME_PATTERN:-desktop}${C_RST}"
|
|
echo -e " ${C_DIM}desktop = desktop-XXXX | random = XXXXXXXX | custom = PREFIX-XXXX${C_RST}"
|
|
echo -e " ${C_CYAN}2${C_RST}) Custom Prefix: ${C_WHITE}${HOSTNAME_CUSTOM_PREFIX:-}${C_RST}"
|
|
echo -e " ${C_RED}0${C_RST}) ${C_DIM}Back${C_RST}"
|
|
echo ""
|
|
echo -ne "${C_BLUE}▸ ${C_RST}"
|
|
local choice
|
|
read -r choice
|
|
|
|
case "$choice" in
|
|
1) local val; val=$(ansi_input "Pattern (desktop/random/custom)" "${HOSTNAME_PATTERN:-desktop}")
|
|
opsec_set_value HOSTNAME_PATTERN "$val" ;;
|
|
2) local val; val=$(ansi_input "Custom prefix" "${HOSTNAME_CUSTOM_PREFIX:-}")
|
|
opsec_set_value HOSTNAME_CUSTOM_PREFIX "$val" ;;
|
|
0|"") return ;;
|
|
esac
|
|
done
|
|
}
|
|
|
|
menu_hardening() {
|
|
while true; do
|
|
opsec_load_config 2>/dev/null || true
|
|
clear
|
|
echo -e "${C_CYAN}━━━ ${C_MAG}SYSTEM HARDENING${C_CYAN} ━━━${C_RST}"
|
|
echo ""
|
|
ansi_toggle "1) Disable IPv6" "${HARDEN_IPV6:-1}"
|
|
ansi_toggle "2) Disable Swap" "${HARDEN_SWAP:-1}"
|
|
ansi_toggle "3) Disable Core Dumps" "${HARDEN_CORE_DUMPS:-1}"
|
|
ansi_toggle "4) Clipboard Auto-Clear" "${HARDEN_CLIPBOARD_CLEAR:-0}"
|
|
ansi_toggle "5) Screen Lock" "${HARDEN_SCREEN_LOCK:-1}"
|
|
echo -e " ${C_CYAN}6${C_RST}) Screen Lock Timeout: ${C_WHITE}${HARDEN_SCREEN_LOCK_TIMEOUT:-300}s${C_RST}"
|
|
ansi_toggle "7) Timezone Spoof" "${HARDEN_TIMEZONE_SPOOF:-0}"
|
|
echo -e " ${C_CYAN}8${C_RST}) Timezone Value: ${C_WHITE}${HARDEN_TIMEZONE_VALUE:-UTC}${C_RST}"
|
|
ansi_toggle "9) Locale Spoof" "${HARDEN_LOCALE_SPOOF:-0}"
|
|
echo -e " ${C_RED}0${C_RST}) ${C_DIM}Back${C_RST}"
|
|
echo ""
|
|
echo -ne "${C_BLUE}▸ ${C_RST}"
|
|
local choice
|
|
read -r choice
|
|
|
|
case "$choice" in
|
|
1) wt_toggle HARDEN_IPV6 "IPv6 Disable" ;;
|
|
2) wt_toggle HARDEN_SWAP "Swap Disable" ;;
|
|
3) wt_toggle HARDEN_CORE_DUMPS "Core Dumps Disable" ;;
|
|
4) wt_toggle HARDEN_CLIPBOARD_CLEAR "Clipboard Auto-Clear" ;;
|
|
5) wt_toggle HARDEN_SCREEN_LOCK "Screen Lock" ;;
|
|
6) local val; val=$(ansi_input "Lock timeout (seconds)" "${HARDEN_SCREEN_LOCK_TIMEOUT:-300}")
|
|
opsec_set_value HARDEN_SCREEN_LOCK_TIMEOUT "$val" ;;
|
|
7) wt_toggle HARDEN_TIMEZONE_SPOOF "Timezone Spoof" ;;
|
|
8) local val; val=$(ansi_input "Timezone (e.g. UTC, Europe/London)" "${HARDEN_TIMEZONE_VALUE:-UTC}")
|
|
opsec_set_value HARDEN_TIMEZONE_VALUE "$val" ;;
|
|
9) wt_toggle HARDEN_LOCALE_SPOOF "Locale Spoof" ;;
|
|
0|"") return ;;
|
|
esac
|
|
done
|
|
}
|
|
|
|
menu_leak_prevention() {
|
|
while true; do
|
|
opsec_load_config 2>/dev/null || true
|
|
clear
|
|
echo -e "${C_CYAN}━━━ ${C_MAG}LEAK PREVENTION${C_CYAN} ━━━${C_RST}"
|
|
echo ""
|
|
ansi_toggle "1) WebRTC Blocking" "${LEAK_WEBRTC_BLOCK:-1}"
|
|
ansi_toggle "2) USB Device Blocking" "${LEAK_USB_BLOCK:-0}"
|
|
echo -e " ${C_RED}0${C_RST}) ${C_DIM}Back${C_RST}"
|
|
echo ""
|
|
echo -ne "${C_BLUE}▸ ${C_RST}"
|
|
local choice
|
|
read -r choice
|
|
|
|
case "$choice" in
|
|
1) wt_toggle LEAK_WEBRTC_BLOCK "WebRTC Block" ;;
|
|
2) wt_toggle LEAK_USB_BLOCK "USB Block" ;;
|
|
0|"") return ;;
|
|
esac
|
|
done
|
|
}
|
|
|
|
menu_monitoring() {
|
|
while true; do
|
|
opsec_load_config 2>/dev/null || true
|
|
clear
|
|
echo -e "${C_CYAN}━━━ ${C_MAG}NETWORK MONITORING${C_CYAN} ━━━${C_RST}"
|
|
echo ""
|
|
ansi_toggle "1) Process Monitor" "${MONITOR_PROCESSES:-0}"
|
|
ansi_toggle "2) Traffic Jitter" "${TRAFFIC_JITTER_ENABLED:-0}"
|
|
echo -e " ${C_CYAN}3${C_RST}) Jitter Delay: ${C_WHITE}${TRAFFIC_JITTER_MS:-50}ms${C_RST}"
|
|
ansi_toggle "4) Log Rotation" "${MONITOR_LOG_ROTATION:-1}"
|
|
echo -e " ${C_CYAN}5${C_RST}) Log Rotation Interval: ${C_WHITE}${LOG_ROTATION_HOURS:-4}h${C_RST}"
|
|
echo -e " ${C_RED}0${C_RST}) ${C_DIM}Back${C_RST}"
|
|
echo ""
|
|
echo -ne "${C_BLUE}▸ ${C_RST}"
|
|
local choice
|
|
read -r choice
|
|
|
|
case "$choice" in
|
|
1) wt_toggle MONITOR_PROCESSES "Process Monitor" ;;
|
|
2) wt_toggle TRAFFIC_JITTER_ENABLED "Traffic Jitter" ;;
|
|
3) local val; val=$(ansi_input "Jitter delay (ms)" "${TRAFFIC_JITTER_MS:-50}")
|
|
opsec_set_value TRAFFIC_JITTER_MS "$val" ;;
|
|
4) wt_toggle MONITOR_LOG_ROTATION "Log Rotation" ;;
|
|
5) local val; val=$(ansi_input "Rotation interval (hours)" "${LOG_ROTATION_HOURS:-4}")
|
|
opsec_set_value LOG_ROTATION_HOURS "$val" ;;
|
|
0|"") return ;;
|
|
esac
|
|
done
|
|
}
|
|
|
|
menu_boot_mode() {
|
|
clear
|
|
echo -e "${C_CYAN}━━━ ${C_MAG}BOOT MODE${C_CYAN} ━━━${C_RST}"
|
|
echo ""
|
|
if [ -f "$BOOT_MARKER" ]; then
|
|
echo -e "${C_GREEN}Boot-into-advanced is: ENABLED${C_RST}"
|
|
echo -e "${C_DIM}System will boot into advanced OPSEC mode automatically${C_RST}"
|
|
echo ""
|
|
if ansi_confirm "Disable boot-into-advanced?"; then
|
|
rm -f "$BOOT_MARKER"
|
|
systemctl disable opsec-boot-advanced.service 2>/dev/null || true
|
|
opsec_green "Boot-into-advanced disabled"
|
|
fi
|
|
else
|
|
echo -e "${C_AMBER}Boot-into-advanced is: DISABLED${C_RST}"
|
|
echo -e "${C_DIM}System boots in standard mode (manual activation)${C_RST}"
|
|
echo ""
|
|
if ansi_confirm "Enable boot-into-advanced?"; then
|
|
mkdir -p /etc/opsec
|
|
touch "$BOOT_MARKER"
|
|
systemctl enable opsec-boot-advanced.service 2>/dev/null || true
|
|
opsec_green "Boot-into-advanced enabled"
|
|
fi
|
|
fi
|
|
sleep 1
|
|
}
|
|
|
|
menu_profiles() {
|
|
while true; do
|
|
clear
|
|
echo -e "${C_CYAN}━━━ ${C_MAG}PROFILE MANAGEMENT${C_CYAN} ━━━${C_RST}"
|
|
echo ""
|
|
echo -e "${C_DIM}Active profile:${C_RST} ${C_WHITE}${PROFILE_NAME:-default}${C_RST}"
|
|
echo ""
|
|
|
|
# List existing profiles
|
|
local profiles
|
|
profiles=$(opsec_profile_list 2>/dev/null)
|
|
if [ -n "$profiles" ]; then
|
|
echo -e "${C_DIM}Saved profiles:${C_RST}"
|
|
echo "$profiles" | while read -r p; do
|
|
if [ "$p" = "${PROFILE_NAME:-default}" ]; then
|
|
echo -e " ${C_GREEN}▸ ${p} (active)${C_RST}"
|
|
else
|
|
echo -e " ${C_DIM} ${p}${C_RST}"
|
|
fi
|
|
done
|
|
echo ""
|
|
fi
|
|
|
|
echo -e " ${C_CYAN}1${C_RST}) Save current config as profile"
|
|
echo -e " ${C_CYAN}2${C_RST}) Load profile"
|
|
echo -e " ${C_CYAN}3${C_RST}) Delete profile"
|
|
echo -e " ${C_CYAN}4${C_RST}) Export profile to file"
|
|
echo -e " ${C_CYAN}5${C_RST}) Import profile from file"
|
|
echo -e " ${C_RED}0${C_RST}) ${C_DIM}Back${C_RST}"
|
|
echo ""
|
|
echo -ne "${C_BLUE}▸ ${C_RST}"
|
|
local choice
|
|
read -r choice
|
|
|
|
case "$choice" in
|
|
1)
|
|
local name
|
|
name=$(ansi_input "Profile name" "")
|
|
if [ -n "$name" ]; then
|
|
opsec_profile_save "$name"
|
|
opsec_set_value PROFILE_NAME "$name"
|
|
opsec_green "Profile '${name}' saved"
|
|
sleep 1
|
|
fi
|
|
;;
|
|
2)
|
|
local name
|
|
name=$(ansi_input "Profile name to load" "")
|
|
if [ -n "$name" ]; then
|
|
if opsec_profile_load "$name"; then
|
|
opsec_load_config
|
|
opsec_green "Profile '${name}' loaded"
|
|
else
|
|
opsec_red "Profile '${name}' not found"
|
|
fi
|
|
sleep 1
|
|
fi
|
|
;;
|
|
3)
|
|
local name
|
|
name=$(ansi_input "Profile name to delete" "")
|
|
if [ -n "$name" ] && ansi_confirm "Delete profile '${name}'?"; then
|
|
opsec_profile_delete "$name"
|
|
opsec_green "Profile '${name}' deleted"
|
|
sleep 1
|
|
fi
|
|
;;
|
|
4)
|
|
local name dest
|
|
name=$(ansi_input "Profile name to export" "${PROFILE_NAME:-default}")
|
|
dest=$(ansi_input "Export path" "/tmp/${name}.conf")
|
|
if opsec_profile_export "$name" "$dest" 2>/dev/null || cp "$OPSEC_CONF" "$dest"; then
|
|
opsec_green "Exported to ${dest}"
|
|
else
|
|
opsec_red "Export failed"
|
|
fi
|
|
sleep 1
|
|
;;
|
|
5)
|
|
local src name
|
|
src=$(ansi_input "Import file path" "")
|
|
name=$(ansi_input "Profile name" "")
|
|
if [ -n "$src" ] && [ -n "$name" ]; then
|
|
if opsec_profile_import "$src" "$name"; then
|
|
opsec_green "Profile '${name}' imported"
|
|
else
|
|
opsec_red "Import failed (file not found?)"
|
|
fi
|
|
sleep 1
|
|
fi
|
|
;;
|
|
0|"") return ;;
|
|
esac
|
|
done
|
|
}
|
|
|
|
menu_tor_bridges() {
|
|
while true; do
|
|
opsec_load_config 2>/dev/null || true
|
|
clear
|
|
echo -e "${C_CYAN}━━━ ${C_MAG}TOR BRIDGES (PLUGGABLE TRANSPORTS)${C_CYAN} ━━━${C_RST}"
|
|
echo ""
|
|
echo -e "${C_DIM}Current mode:${C_RST} ${C_WHITE}${TOR_BRIDGE_MODE:-off}${C_RST}"
|
|
[ -n "${TOR_BRIDGE_RELAY:-}" ] && echo -e "${C_DIM}Custom relay:${C_RST} ${C_WHITE}${TOR_BRIDGE_RELAY}${C_RST}"
|
|
echo ""
|
|
echo -e " ${C_CYAN}1${C_RST}) ${C_GREEN}off${C_RST} — Direct Tor connection (no bridge)"
|
|
echo -e " ${C_CYAN}2${C_RST}) ${C_AMBER}obfs4${C_RST} — obfs4 obfuscated bridge (recommended)"
|
|
echo -e " ${C_CYAN}3${C_RST}) ${C_AMBER}meek-azure${C_RST} — Domain-fronted via Azure CDN"
|
|
echo -e " ${C_CYAN}4${C_RST}) ${C_AMBER}snowflake${C_RST} — WebRTC-based (resembles video call)"
|
|
echo -e " ${C_CYAN}5${C_RST}) ${C_WHITE}Custom relay${C_RST} — Set bridge relay line manually"
|
|
echo -e " ${C_RED}0${C_RST}) ${C_DIM}Back${C_RST}"
|
|
echo ""
|
|
echo -ne "${C_BLUE}▸ ${C_RST}"
|
|
local choice
|
|
read -r choice
|
|
|
|
case "$choice" in
|
|
1) opsec_set_value TOR_BRIDGE_MODE "off"; opsec_green "Bridges disabled" ; sleep 1 ;;
|
|
2) opsec_set_value TOR_BRIDGE_MODE "obfs4"; opsec_green "Bridge mode: obfs4" ; sleep 1 ;;
|
|
3) opsec_set_value TOR_BRIDGE_MODE "meek-azure"; opsec_green "Bridge mode: meek-azure" ; sleep 1 ;;
|
|
4) opsec_set_value TOR_BRIDGE_MODE "snowflake"; opsec_green "Bridge mode: snowflake" ; sleep 1 ;;
|
|
5)
|
|
local relay
|
|
relay=$(ansi_input "Bridge relay line (e.g. obfs4 IP:PORT FINGERPRINT ...)" "${TOR_BRIDGE_RELAY:-}")
|
|
opsec_set_value TOR_BRIDGE_RELAY "$relay"
|
|
;;
|
|
0|"") return ;;
|
|
esac
|
|
done
|
|
}
|
|
|
|
menu_widget_theme() {
|
|
while true; do
|
|
opsec_load_config 2>/dev/null || true
|
|
clear
|
|
echo -e "${C_CYAN}━━━ ${C_MAG}WIDGET THEME${C_CYAN} ━━━${C_RST}"
|
|
echo ""
|
|
echo -e "${C_DIM}Current theme:${C_RST} ${C_WHITE}${WIDGET_THEME:-default}${C_RST}"
|
|
echo ""
|
|
|
|
# List available themes with numbers
|
|
local themes=()
|
|
local i=1
|
|
if [ -d /etc/opsec/themes ]; then
|
|
while IFS= read -r t; do
|
|
[ -z "$t" ] && continue
|
|
# Source theme to get label
|
|
local label="$t"
|
|
if [ -f "/etc/opsec/themes/${t}.theme" ]; then
|
|
label=$(grep '^THEME_LABEL=' "/etc/opsec/themes/${t}.theme" 2>/dev/null | cut -d'"' -f2)
|
|
[ -z "$label" ] && label="$t"
|
|
fi
|
|
themes+=("$t")
|
|
local marker=""
|
|
if [ "$t" = "${WIDGET_THEME:-default}" ]; then
|
|
marker=" ${C_GREEN}◀ active${C_RST}"
|
|
fi
|
|
echo -e " ${C_CYAN}${i}${C_RST}) ${C_WHITE}${label}${C_RST} ${C_DIM}(${t})${C_RST}${marker}"
|
|
i=$((i + 1))
|
|
done < <(opsec_theme_list 2>/dev/null)
|
|
fi
|
|
|
|
if [ ${#themes[@]} -eq 0 ]; then
|
|
echo -e "${C_RED}No themes found in /etc/opsec/themes/${C_RST}"
|
|
echo -e "${C_DIM}Deploy with playbook first.${C_RST}"
|
|
echo ""
|
|
echo -e "${C_DIM}Press Enter to go back...${C_RST}"
|
|
read -r
|
|
return
|
|
fi
|
|
|
|
echo -e " ${C_RED}0${C_RST}) ${C_DIM}Back${C_RST}"
|
|
echo ""
|
|
echo -ne "${C_BLUE}▸ ${C_RST}"
|
|
local choice
|
|
read -r choice
|
|
|
|
if [ "$choice" = "0" ] || [ -z "$choice" ]; then
|
|
return
|
|
fi
|
|
|
|
# Validate numeric choice
|
|
if [[ "$choice" =~ ^[0-9]+$ ]] && [ "$choice" -ge 1 ] && [ "$choice" -le ${#themes[@]} ]; then
|
|
local selected="${themes[$((choice - 1))]}"
|
|
opsec_set_value WIDGET_THEME "$selected"
|
|
opsec_load_config
|
|
opsec_info "Applying theme '${selected}'..."
|
|
opsec_generate_conky
|
|
opsec_green "Theme '${selected}' applied"
|
|
sleep 1
|
|
fi
|
|
done
|
|
}
|
|
|
|
do_apply() {
|
|
echo ""
|
|
opsec_hdr "APPLYING CONFIGURATION"
|
|
echo ""
|
|
|
|
opsec_load_config
|
|
|
|
# Regenerate torrc
|
|
opsec_info "Regenerating torrc..."
|
|
opsec_generate_torrc
|
|
opsec_green "Torrc regenerated"
|
|
|
|
# Regenerate resolv.conf if in advanced mode
|
|
if opsec_is_advanced; then
|
|
opsec_info "Regenerating resolv.conf..."
|
|
opsec_generate_resolv
|
|
opsec_green "DNS config regenerated"
|
|
|
|
# Restart Tor
|
|
opsec_info "Restarting Tor..."
|
|
systemctl restart tor 2>/dev/null || true
|
|
|
|
# Reapply hardening
|
|
if [ -x /usr/local/bin/opsec-harden.sh ]; then
|
|
opsec_info "Reapplying hardening..."
|
|
/usr/local/bin/opsec-harden.sh apply
|
|
fi
|
|
|
|
# Reapply kill switch
|
|
opsec_info "Reapplying kill switch..."
|
|
/usr/local/bin/opsec-killswitch.sh on 2>/dev/null || true
|
|
fi
|
|
|
|
# Regenerate conky widget with current theme
|
|
opsec_info "Regenerating conky widget..."
|
|
opsec_generate_conky 2>/dev/null && opsec_green "Widget theme applied" || true
|
|
|
|
opsec_green "Configuration applied"
|
|
echo ""
|
|
}
|
|
|
|
menu_deployment_level() {
|
|
while true; do
|
|
opsec_load_config 2>/dev/null || true
|
|
clear
|
|
echo -e "${C_CYAN}━━━ ${C_MAG}DEPLOYMENT LEVEL${C_CYAN} ━━━${C_RST}"
|
|
echo ""
|
|
echo -e "${C_DIM}Current level:${C_RST} ${C_WHITE}${DEPLOYMENT_LEVEL:-bare-metal}${C_RST}"
|
|
echo ""
|
|
|
|
# List available levels
|
|
local levels
|
|
levels=$(opsec_level_list 2>/dev/null)
|
|
if [ -z "$levels" ]; then
|
|
echo -e "${C_RED}No levels found in /etc/opsec/levels/${C_RST}"
|
|
echo -e "${C_DIM}Deploy with playbook first.${C_RST}"
|
|
echo ""
|
|
echo -e "${C_DIM}Press Enter to go back...${C_RST}"
|
|
read -r
|
|
return
|
|
fi
|
|
|
|
echo -e " ${C_CYAN}1${C_RST}) ${C_WHITE}bare-metal-standard${C_RST} ${C_DIM}— Physical box, privacy base + ghost toggle${C_RST}"
|
|
echo -e " ${C_CYAN}2${C_RST}) ${C_RED}bare-metal-paranoid${C_RST} ${C_DIM}— Physical box, ghost mode always on${C_RST}"
|
|
echo -e " ${C_CYAN}3${C_RST}) ${C_AMBER}cloud-normal${C_RST} ${C_DIM}— Cloud VPS, privacy base + ghost toggle${C_RST}"
|
|
echo -e " ${C_CYAN}4${C_RST}) ${C_RED}cloud-paranoid${C_RST} ${C_DIM}— Cloud VPS, ghost mode always on${C_RST}"
|
|
echo ""
|
|
echo -e " ${C_CYAN}5${C_RST}) ${C_BLUE}Banner Mode${C_RST} ${C_DIM}— Terminal banner: ${OPSEC_BANNER:-compact}${C_RST}"
|
|
echo -e " ${C_RED}0${C_RST}) ${C_DIM}Back${C_RST}"
|
|
echo ""
|
|
echo -ne "${C_BLUE}▸ ${C_RST}"
|
|
local choice
|
|
read -r choice
|
|
|
|
case "$choice" in
|
|
1) if ansi_confirm "Apply 'bare-metal-standard'? (privacy base + ghost toggle)"; then
|
|
opsec_level_apply "bare-metal-standard" && opsec_green "Level 'bare-metal-standard' applied" || opsec_red "Failed to apply level"
|
|
sleep 1
|
|
fi ;;
|
|
2) if ansi_confirm "Apply 'bare-metal-paranoid'? (ghost mode always on)"; then
|
|
opsec_level_apply "bare-metal-paranoid" && opsec_green "Level 'bare-metal-paranoid' applied" || opsec_red "Failed to apply level"
|
|
sleep 1
|
|
fi ;;
|
|
3) if ansi_confirm "Apply 'cloud-normal'? (cloud privacy base + ghost toggle)"; then
|
|
opsec_level_apply "cloud-normal" && opsec_green "Level 'cloud-normal' applied" || opsec_red "Failed to apply level"
|
|
sleep 1
|
|
fi ;;
|
|
4) if ansi_confirm "Apply 'cloud-paranoid'? (cloud ghost mode always on)"; then
|
|
opsec_level_apply "cloud-paranoid" && opsec_green "Level 'cloud-paranoid' applied" || opsec_red "Failed to apply level"
|
|
sleep 1
|
|
fi ;;
|
|
5)
|
|
echo ""
|
|
echo -e " ${C_CYAN}a${C_RST}) compact"
|
|
echo -e " ${C_CYAN}b${C_RST}) full"
|
|
echo -e " ${C_CYAN}c${C_RST}) off"
|
|
echo -ne "${C_BLUE}▸ ${C_RST}"
|
|
local bchoice
|
|
read -r bchoice
|
|
case "$bchoice" in
|
|
a) opsec_set_value OPSEC_BANNER "compact"; opsec_green "Banner: compact" ;;
|
|
b) opsec_set_value OPSEC_BANNER "full"; opsec_green "Banner: full" ;;
|
|
c) opsec_set_value OPSEC_BANNER "off"; opsec_green "Banner: off" ;;
|
|
esac
|
|
sleep 1
|
|
;;
|
|
0|"") return ;;
|
|
esac
|
|
done
|
|
}
|
|
|
|
menu_apply() {
|
|
if ansi_confirm "Regenerate configs and restart affected services?"; then
|
|
do_apply
|
|
echo ""
|
|
echo -e "${C_DIM}Press Enter to continue...${C_RST}"
|
|
read -r
|
|
fi
|
|
}
|
|
|
|
# ─── MAIN MENU ─────────────────────────────────────────────────────────────────
|
|
|
|
main_menu() {
|
|
while true; do
|
|
opsec_load_config 2>/dev/null || true
|
|
clear
|
|
ansi_banner
|
|
|
|
local mode_str
|
|
if [ -f /var/run/opsec-advanced.enabled ]; then
|
|
mode_str="${C_GREEN}ADVANCED${C_RST}"
|
|
else
|
|
mode_str="${C_AMBER}STANDARD${C_RST}"
|
|
fi
|
|
|
|
echo -e " ${C_DIM}Mode: ${mode_str} ${C_DIM}Boot: $([ -f "$BOOT_MARKER" ] && echo -e "${C_GREEN}PERSIST" || echo -e "${C_AMBER}MANUAL")${C_RST}"
|
|
echo ""
|
|
echo -e " ${C_CYAN} 1${C_RST}) ${C_WHITE}Tor Settings${C_RST} ${C_DIM}— circuits, blacklist, isolation, padding${C_RST}"
|
|
echo -e " ${C_CYAN} 2${C_RST}) ${C_WHITE}DNS Settings${C_RST} ${C_DIM}— tor/quad9/cloudflare/doh/dot/custom${C_RST}"
|
|
echo -e " ${C_CYAN} 3${C_RST}) ${C_WHITE}Kill Switch${C_RST} ${C_DIM}— DHCP/VPN toggles, extra ports${C_RST}"
|
|
echo -e " ${C_CYAN} 4${C_RST}) ${C_WHITE}MAC Address${C_RST} ${C_DIM}— interfaces, vendor spoof${C_RST}"
|
|
echo -e " ${C_CYAN} 5${C_RST}) ${C_WHITE}Hostname${C_RST} ${C_DIM}— pattern, custom prefix${C_RST}"
|
|
echo -e " ${C_CYAN} 6${C_RST}) ${C_WHITE}System Hardening${C_RST} ${C_DIM}— IPv6, swap, core dumps, clipboard, etc.${C_RST}"
|
|
echo -e " ${C_CYAN} 7${C_RST}) ${C_WHITE}Leak Prevention${C_RST} ${C_DIM}— WebRTC, USB blocking${C_RST}"
|
|
echo -e " ${C_CYAN} 8${C_RST}) ${C_WHITE}Network Monitoring${C_RST} ${C_DIM}— process monitor, traffic jitter${C_RST}"
|
|
echo -e " ${C_CYAN} 9${C_RST}) ${C_WHITE}Boot Mode${C_RST} ${C_DIM}— toggle boot-into-advanced${C_RST}"
|
|
echo -e " ${C_CYAN}10${C_RST}) ${C_WHITE}Profiles${C_RST} ${C_DIM}— save/load/list/delete/export/import${C_RST}"
|
|
echo -e " ${C_CYAN}11${C_RST}) ${C_WHITE}Deployment Level${C_RST} ${C_DIM}— ${DEPLOYMENT_LEVEL:-bare-metal} | banner: ${OPSEC_BANNER:-compact}${C_RST}"
|
|
echo -e " ${C_CYAN}12${C_RST}) ${C_WHITE}Tor Bridges${C_RST} ${C_DIM}— pluggable transports: ${TOR_BRIDGE_MODE:-off}${C_RST}"
|
|
echo -e " ${C_CYAN}13${C_RST}) ${C_WHITE}Widget Theme${C_RST} ${C_DIM}— conky color theme: ${WIDGET_THEME:-default}${C_RST}"
|
|
echo -e " ${C_MAG}14${C_RST}) ${C_MAG}Apply & Restart${C_RST} ${C_DIM}— regenerate configs, restart services${C_RST}"
|
|
echo -e " ${C_RED} 0${C_RST}) ${C_DIM}Exit${C_RST}"
|
|
echo ""
|
|
echo -ne "${C_BLUE}▸ ${C_RST}"
|
|
local choice
|
|
read -r choice
|
|
|
|
case "$choice" in
|
|
1) menu_tor ;;
|
|
2) menu_dns ;;
|
|
3) menu_killswitch ;;
|
|
4) menu_mac ;;
|
|
5) menu_hostname ;;
|
|
6) menu_hardening ;;
|
|
7) menu_leak_prevention ;;
|
|
8) menu_monitoring ;;
|
|
9) menu_boot_mode ;;
|
|
10) menu_profiles ;;
|
|
11) menu_deployment_level ;;
|
|
12) menu_tor_bridges ;;
|
|
13) menu_widget_theme ;;
|
|
14) menu_apply ;;
|
|
0|"")
|
|
clear
|
|
exit 0
|
|
;;
|
|
esac
|
|
done
|
|
}
|
|
|
|
# ─── CLI MODE ──────────────────────────────────────────────────────────────────
|
|
|
|
cli_boot() {
|
|
case "${1:-}" in
|
|
on)
|
|
mkdir -p /etc/opsec
|
|
touch "$BOOT_MARKER"
|
|
systemctl enable opsec-boot-advanced.service 2>/dev/null || true
|
|
opsec_green "Boot-into-advanced: ENABLED"
|
|
;;
|
|
off)
|
|
rm -f "$BOOT_MARKER"
|
|
systemctl disable opsec-boot-advanced.service 2>/dev/null || true
|
|
opsec_green "Boot-into-advanced: DISABLED"
|
|
;;
|
|
*)
|
|
if [ -f "$BOOT_MARKER" ]; then
|
|
echo "enabled"
|
|
else
|
|
echo "disabled"
|
|
fi
|
|
;;
|
|
esac
|
|
}
|
|
|
|
cli_profile() {
|
|
local action="${1:-}" name="${2:-}"
|
|
case "$action" in
|
|
save)
|
|
[ -z "$name" ] && { echo "Usage: --profile save NAME"; exit 1; }
|
|
opsec_profile_save "$name"
|
|
opsec_set_value PROFILE_NAME "$name"
|
|
opsec_green "Profile '${name}' saved"
|
|
;;
|
|
load)
|
|
[ -z "$name" ] && { echo "Usage: --profile load NAME"; exit 1; }
|
|
if opsec_profile_load "$name"; then
|
|
opsec_green "Profile '${name}' loaded"
|
|
else
|
|
opsec_red "Profile '${name}' not found"
|
|
exit 1
|
|
fi
|
|
;;
|
|
list)
|
|
opsec_profile_list
|
|
;;
|
|
delete)
|
|
[ -z "$name" ] && { echo "Usage: --profile delete NAME"; exit 1; }
|
|
opsec_profile_delete "$name"
|
|
opsec_green "Profile '${name}' deleted"
|
|
;;
|
|
*)
|
|
echo "Usage: --profile save|load|list|delete [NAME]"
|
|
exit 1
|
|
;;
|
|
esac
|
|
}
|
|
|
|
# ─── ARGUMENT PARSING ─────────────────────────────────────────────────────────
|
|
|
|
case "${1:-}" in
|
|
--boot)
|
|
cli_boot "${2:-}"
|
|
;;
|
|
--profile)
|
|
cli_profile "${2:-}" "${3:-}"
|
|
;;
|
|
--level)
|
|
case "${2:-}" in
|
|
apply)
|
|
[ -z "${3:-}" ] && { echo "Usage: --level apply NAME"; echo "Available: $(opsec_level_list 2>/dev/null | tr '\n' ' ')"; exit 1; }
|
|
opsec_level_apply "$3" && opsec_green "Level '${3}' applied"
|
|
;;
|
|
list)
|
|
opsec_level_list
|
|
;;
|
|
*)
|
|
echo "Usage: --level apply|list [NAME]"
|
|
echo "Available levels: $(opsec_level_list 2>/dev/null | tr '\n' ' ')"
|
|
exit 1
|
|
;;
|
|
esac
|
|
;;
|
|
--banner)
|
|
case "${2:-}" in
|
|
compact|full|off)
|
|
opsec_set_value OPSEC_BANNER "$2"
|
|
opsec_green "Banner mode: ${2}"
|
|
;;
|
|
*)
|
|
echo "Usage: --banner compact|full|off"
|
|
echo "Current: $(opsec_get_value OPSEC_BANNER)"
|
|
exit 1
|
|
;;
|
|
esac
|
|
;;
|
|
--theme)
|
|
case "${2:-}" in
|
|
list)
|
|
_themes=$(opsec_theme_list 2>/dev/null)
|
|
if [ -z "$_themes" ]; then
|
|
echo "No themes found in /etc/opsec/themes/"
|
|
exit 1
|
|
fi
|
|
_current=$(opsec_get_value WIDGET_THEME)
|
|
echo "$_themes" | while IFS= read -r t; do
|
|
if [ "$t" = "$_current" ]; then
|
|
echo "* ${t} (active)"
|
|
else
|
|
echo " ${t}"
|
|
fi
|
|
done
|
|
;;
|
|
apply)
|
|
[ -z "${3:-}" ] && { echo "Usage: --theme apply NAME"; echo "Available: $(opsec_theme_list 2>/dev/null | tr '\n' ' ')"; exit 1; }
|
|
if [ ! -f "/etc/opsec/themes/${3}.theme" ]; then
|
|
opsec_red "Theme '${3}' not found"
|
|
echo "Available: $(opsec_theme_list 2>/dev/null | tr '\n' ' ')"
|
|
exit 1
|
|
fi
|
|
opsec_set_value WIDGET_THEME "$3"
|
|
opsec_load_config
|
|
opsec_generate_conky && opsec_green "Theme '${3}' applied"
|
|
;;
|
|
*)
|
|
echo "Usage: --theme list|apply NAME"
|
|
echo "Current: $(opsec_get_value WIDGET_THEME)"
|
|
exit 1
|
|
;;
|
|
esac
|
|
;;
|
|
--set)
|
|
[ -z "${2:-}" ] && { echo "Usage: --set KEY VALUE"; exit 1; }
|
|
opsec_set_value "$2" "${3:-}"
|
|
opsec_green "${2}=${3:-}"
|
|
;;
|
|
--get)
|
|
[ -z "${2:-}" ] && { echo "Usage: --get KEY"; exit 1; }
|
|
opsec_get_value "$2"
|
|
;;
|
|
--apply)
|
|
do_apply
|
|
;;
|
|
--help|-h)
|
|
echo "OPSEC Configuration Manager"
|
|
echo ""
|
|
echo "Usage:"
|
|
echo " sudo opsec-config.sh Interactive TUI"
|
|
echo " sudo opsec-config.sh --boot on|off Toggle boot-into-advanced"
|
|
echo " sudo opsec-config.sh --profile CMD NAME Profile management"
|
|
echo " sudo opsec-config.sh --level apply NAME Apply deployment level"
|
|
echo " sudo opsec-config.sh --level list List available levels"
|
|
echo " sudo opsec-config.sh --banner compact|full|off Set terminal banner mode"
|
|
echo " sudo opsec-config.sh --theme list List widget themes"
|
|
echo " sudo opsec-config.sh --theme apply NAME Apply widget theme"
|
|
echo " sudo opsec-config.sh --set KEY VALUE Set config value"
|
|
echo " sudo opsec-config.sh --get KEY Get config value"
|
|
echo " sudo opsec-config.sh --apply Apply config changes"
|
|
;;
|
|
"")
|
|
main_menu
|
|
;;
|
|
*)
|
|
echo "Unknown option: $1"
|
|
echo "Try: sudo opsec-config.sh --help"
|
|
exit 1
|
|
;;
|
|
esac
|