| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493 |
- #!/bin/bash
- C_RESET=$'\033[0m'
- C_BOLD=$'\033[1m'
- C_DIM=$'\033[2m'
- C_UL=$'\033[4m'
- # Premium Color Palette
- C_RED=$'\033[38;5;196m' # Bright Red
- C_GREEN=$'\033[38;5;46m' # Neon Green
- C_YELLOW=$'\033[38;5;226m' # Bright Yellow
- C_BLUE=$'\033[38;5;39m' # Deep Sky Blue
- C_PURPLE=$'\033[38;5;135m' # Light Purple
- C_CYAN=$'\033[38;5;51m' # Cyan
- C_WHITE=$'\033[38;5;255m' # Bright White
- C_GRAY=$'\033[38;5;245m' # Gray
- C_ORANGE=$'\033[38;5;208m' # Orange
- # Semantic Aliases
- C_TITLE=$C_PURPLE
- C_CHOICE=$C_CYAN
- C_PROMPT=$C_BLUE
- C_WARN=$C_YELLOW
- C_DANGER=$C_RED
- C_STATUS_A=$C_GREEN
- C_STATUS_I=$C_GRAY
- C_ACCENT=$C_ORANGE
- DB_DIR="/etc/firewallfalcon"
- DB_FILE="$DB_DIR/users.db"
- INSTALL_FLAG_FILE="$DB_DIR/.install"
- BADVPN_SERVICE_FILE="/etc/systemd/system/badvpn.service"
- BADVPN_BUILD_DIR="/root/badvpn-build"
- HAPROXY_CONFIG="/etc/haproxy/haproxy.cfg"
- NGINX_CONFIG_FILE="/etc/nginx/sites-available/default"
- SSL_CERT_DIR="/etc/firewallfalcon/ssl"
- SSL_CERT_FILE="$SSL_CERT_DIR/firewallfalcon.pem"
- NGINX_PORTS_FILE="$DB_DIR/nginx_ports.conf"
- DNSTT_SERVICE_FILE="/etc/systemd/system/dnstt.service"
- DNSTT_BINARY="/usr/local/bin/dnstt-server"
- DNSTT_KEYS_DIR="/etc/firewallfalcon/dnstt"
- DNSTT_CONFIG_FILE="$DB_DIR/dnstt_info.conf"
- DNS_INFO_FILE="$DB_DIR/dns_info.conf"
- UDP_CUSTOM_DIR="/root/udp"
- UDP_CUSTOM_SERVICE_FILE="/etc/systemd/system/udp-custom.service"
- SSH_BANNER_FILE="/etc/bannerssh"
- FALCONPROXY_SERVICE_FILE="/etc/systemd/system/falconproxy.service"
- FALCONPROXY_BINARY="/usr/local/bin/falconproxy"
- FALCONPROXY_CONFIG_FILE="$DB_DIR/falconproxy_config.conf"
- LIMITER_SCRIPT="/usr/local/bin/firewallfalcon-limiter.sh"
- LIMITER_SERVICE="/etc/systemd/system/firewallfalcon-limiter.service"
- BANDWIDTH_DIR="$DB_DIR/bandwidth"
- BANDWIDTH_SCRIPT="/usr/local/bin/firewallfalcon-bandwidth.sh"
- BANDWIDTH_SERVICE="/etc/systemd/system/firewallfalcon-bandwidth.service"
- TRIAL_CLEANUP_SCRIPT="/usr/local/bin/firewallfalcon-trial-cleanup.sh"
- LOGIN_INFO_SCRIPT="/usr/local/bin/firewallfalcon-login-info.sh"
- SSHD_FF_CONFIG="/etc/ssh/sshd_config.d/firewallfalcon.conf"
- # --- ZiVPN Variables ---
- ZIVPN_DIR="/etc/zivpn"
- ZIVPN_BIN="/usr/local/bin/zivpn"
- ZIVPN_SERVICE_FILE="/etc/systemd/system/zivpn.service"
- ZIVPN_CONFIG_FILE="$ZIVPN_DIR/config.json"
- ZIVPN_CERT_FILE="$ZIVPN_DIR/zivpn.crt"
- ZIVPN_KEY_FILE="$ZIVPN_DIR/zivpn.key"
- DESEC_TOKEN="V55cFY8zTictLCPfviiuX5DHjs15"
- DESEC_DOMAIN="manager.firewallfalcon.qzz.io"
- SELECTED_USER=""
- UNINSTALL_MODE="interactive"
- if [[ $EUID -ne 0 ]]; then
- echo -e "${C_RED}❌ Error: This script requires root privileges to run.${C_RESET}"
- exit 1
- fi
- # Mandatory Dependency Check (Added jq and curl)
- check_environment() {
- # Mandatory Dependency Check (Added jq and curl)
- for cmd in bc jq curl wget; do
- if ! command -v $cmd &> /dev/null; then
- echo -e "${C_YELLOW}⚠️ Warning: '$cmd' not found. Installing...${C_RESET}"
- apt-get update > /dev/null 2>&1 && apt-get install -y $cmd || {
- echo -e "${C_RED}❌ Error: Failed to install '$cmd'. Please install it manually.${C_RESET}"
- exit 1
- }
- fi
- done
- }
- initial_setup() {
- echo -e "${C_BLUE}⚙️ Initializing FirewallFalcon Manager setup...${C_RESET}"
- check_environment
-
- mkdir -p "$DB_DIR"
- touch "$DB_FILE"
- mkdir -p "$SSL_CERT_DIR"
- mkdir -p "$BANDWIDTH_DIR"
-
- echo -e "${C_BLUE}🔹 Configuring user limiter service...${C_RESET}"
- setup_limiter_service
-
- echo -e "${C_BLUE}🔹 Configuring bandwidth monitoring service...${C_RESET}"
- setup_bandwidth_service
-
- echo -e "${C_BLUE}🔹 Installing trial account cleanup script...${C_RESET}"
- setup_trial_cleanup_script
-
- echo -e "${C_BLUE}🔹 Configuring SSH login info banner...${C_RESET}"
- setup_ssh_login_info
-
- if [ ! -f "$INSTALL_FLAG_FILE" ]; then
- touch "$INSTALL_FLAG_FILE"
- fi
- echo -e "${C_GREEN}✅ Setup finished.${C_RESET}"
- }
- _is_valid_ipv4() {
- local ip=$1
- if [[ $ip =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then
- return 0
- else
- return 1
- fi
- }
- check_and_open_firewall_port() {
- local port="$1"
- local protocol="${2:-tcp}"
- local firewall_detected=false
- if command -v ufw &> /dev/null && ufw status | grep -q "Status: active"; then
- firewall_detected=true
- if ! ufw status | grep -qw "$port/$protocol"; then
- echo -e "${C_YELLOW}🔥 UFW firewall is active and port ${port}/${protocol} is closed.${C_RESET}"
- read -p "👉 Do you want to open this port now? (y/n): " confirm
- if [[ "$confirm" == "y" || "$confirm" == "Y" ]]; then
- ufw allow "$port/$protocol"
- echo -e "${C_GREEN}✅ Port ${port}/${protocol} has been opened in UFW.${C_RESET}"
- else
- echo -e "${C_RED}❌ Warning: Port ${port}/${protocol} was not opened. The service may not work correctly.${C_RESET}"
- return 1
- fi
- else
- echo -e "${C_GREEN}✅ Port ${port}/${protocol} is already open in UFW.${C_RESET}"
- fi
- fi
- if command -v firewall-cmd &> /dev/null && systemctl is-active --quiet firewalld; then
- firewall_detected=true
- if ! firewall-cmd --list-ports --permanent | grep -qw "$port/$protocol"; then
- echo -e "${C_YELLOW}🔥 firewalld is active and port ${port}/${protocol} is not open.${C_RESET}"
- read -p "👉 Do you want to open this port now? (y/n): " confirm
- if [[ "$confirm" == "y" || "$confirm" == "Y" ]]; then
- firewall-cmd --add-port="$port/$protocol" --permanent
- firewall-cmd --reload
- echo -e "${C_GREEN}✅ Port ${port}/${protocol} has been opened in firewalld.${C_RESET}"
- else
- echo -e "${C_RED}❌ Warning: Port ${port}/${protocol} was not opened. The service may not work correctly.${C_RESET}"
- return 1
- fi
- else
- echo -e "${C_GREEN}✅ Port ${port}/${protocol} is already open in firewalld.${C_RESET}"
- fi
- fi
- if ! $firewall_detected; then
- echo -e "${C_BLUE}ℹ️ No active firewall (UFW or firewalld) detected. Assuming ports are open.${C_RESET}"
- fi
- return 0
- }
- check_and_free_ports() {
- local ports_to_check=("$@")
- for port in "${ports_to_check[@]}"; do
- echo -e "\n${C_BLUE}🔎 Checking if port $port is available...${C_RESET}"
- local conflicting_process_info
- conflicting_process_info=$(ss -lntp | grep ":$port\s" || ss -lunp | grep ":$port\s")
-
- if [[ -n "$conflicting_process_info" ]]; then
- local conflicting_pid
- conflicting_pid=$(echo "$conflicting_process_info" | grep -oP 'pid=\K[0-9]+' | head -n 1)
- local conflicting_name
- conflicting_name=$(echo "$conflicting_process_info" | grep -oP 'users:\(\("(\K[^"]+)' | head -n 1)
-
- echo -e "${C_YELLOW}⚠️ Warning: Port $port is in use by process '${conflicting_name:-unknown}' (PID: ${conflicting_pid:-N/A}).${C_RESET}"
- read -p "👉 Do you want to attempt to stop this process? (y/n): " kill_confirm
- if [[ "$kill_confirm" == "y" || "$kill_confirm" == "Y" ]]; then
- echo -e "${C_GREEN}🛑 Stopping process PID $conflicting_pid...${C_RESET}"
- systemctl stop "$(ps -p "$conflicting_pid" -o comm=)" &>/dev/null || kill -9 "$conflicting_pid"
- sleep 2
-
- if ss -lntp | grep -q ":$port\s" || ss -lunp | grep -q ":$port\s"; then
- echo -e "${C_RED}❌ Failed to free port $port. Please handle it manually. Aborting.${C_RESET}"
- return 1
- else
- echo -e "${C_GREEN}✅ Port $port has been successfully freed.${C_RESET}"
- fi
- else
- echo -e "${C_RED}❌ Cannot proceed without freeing port $port. Aborting.${C_RESET}"
- return 1
- fi
- else
- echo -e "${C_GREEN}✅ Port $port is free to use.${C_RESET}"
- fi
- done
- return 0
- }
- setup_limiter_service() {
- # Combined limiter + bandwidth monitoring
- cat > "$LIMITER_SCRIPT" << 'EOF'
- #!/bin/bash
- DB_FILE="/etc/firewallfalcon/users.db"
- BW_DIR="/etc/firewallfalcon/bandwidth"
- PID_DIR="$BW_DIR/pidtrack"
- mkdir -p "$BW_DIR" "$PID_DIR"
- while true; do
- if [[ ! -f "$DB_FILE" ]]; then
- sleep 30
- continue
- fi
-
- current_ts=$(date +%s)
-
- while IFS=: read -r user pass expiry limit bandwidth_gb _extra; do
- [[ -z "$user" || "$user" == \#* ]] && continue
-
- # --- Expiry Check ---
- if [[ "$expiry" != "Never" && "$expiry" != "" ]]; then
- expiry_ts=$(date -d "$expiry" +%s 2>/dev/null || echo 0)
- if [[ $expiry_ts -lt $current_ts && $expiry_ts -ne 0 ]]; then
- if ! passwd -S "$user" | grep -q " L "; then
- usermod -L "$user" &>/dev/null
- killall -u "$user" -9 &>/dev/null
- fi
- continue
- fi
- fi
-
- # --- Connection Limit Check ---
- online_count=$(pgrep -c -u "$user" sshd)
- if ! [[ "$limit" =~ ^[0-9]+$ ]]; then limit=1; fi
-
- if [[ "$online_count" -gt "$limit" ]]; then
- if ! passwd -S "$user" | grep -q " L "; then
- usermod -L "$user" &>/dev/null
- killall -u "$user" -9 &>/dev/null
- (sleep 120; usermod -U "$user" &>/dev/null) &
- else
- killall -u "$user" -9 &>/dev/null
- fi
- fi
-
- # --- SSH Banner Generation (Delay of 1 cycle for BW stats is fine) ---
- if [[ -d "/etc/firewallfalcon/banners" ]]; then
- days_left="N/A"
- if [[ "$expiry" != "Never" && -n "$expiry" ]]; then
- if [[ $expiry_ts -gt 0 ]]; then
- diff_secs=$((expiry_ts - current_ts))
- if [[ $diff_secs -le 0 ]]; then
- days_left="EXPIRED"
- else
- d_l=$(( diff_secs / 86400 ))
- h_l=$(( (diff_secs % 86400) / 3600 ))
- if [[ $d_l -eq 0 ]]; then days_left="${h_l}h left"
- else days_left="${d_l}d ${h_l}h"; fi
- fi
- fi
- fi
-
- bw_info="Unlimited"
- if [[ "$bandwidth_gb" != "0" && -n "$bandwidth_gb" ]]; then
- usagefile="$BW_DIR/${user}.usage"
- accum_disp=0
- [[ -f "$usagefile" ]] && accum_disp=$(cat "$usagefile" 2>/dev/null)
- used_gb=$(awk "BEGIN {printf \"%.2f\", $accum_disp / 1073741824}")
- remain_gb=$(awk "BEGIN {r=$bandwidth_gb - $used_gb; if(r<0) r=0; printf \"%.2f\", r}")
- bw_info="${used_gb}/${bandwidth_gb} GB used | ${remain_gb} GB left"
- fi
-
- # Format the output with HTML tags since clients like HTTP Custom render Server Messages using Html.fromHtml()
- # Crucial: Use echo -e instead of heredoc to prevent DOS CRLF syntax errors when moving script to Linux
- echo -e "<font color=\"cyan\"><b>━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━</b></font><br>" > "/etc/firewallfalcon/banners/${user}.txt"
- echo -e "<font color=\"yellow\"><b> ✨ ACCOUNT STATUS ✨ </b></font><br>" >> "/etc/firewallfalcon/banners/${user}.txt"
- echo -e "<font color=\"cyan\"><b>━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━</b></font><br>" >> "/etc/firewallfalcon/banners/${user}.txt"
- echo -e "<font color=\"white\">👤 <b>Username :</b> $user</font><br>" >> "/etc/firewallfalcon/banners/${user}.txt"
- echo -e "<font color=\"white\">📅 <b>Expiration :</b> $expiry ($days_left)</font><br>" >> "/etc/firewallfalcon/banners/${user}.txt"
- echo -e "<font color=\"white\">📊 <b>Bandwidth :</b> $bw_info</font><br>" >> "/etc/firewallfalcon/banners/${user}.txt"
- echo -e "<font color=\"white\">🔌 <b>Sessions :</b> $online_count/$limit</font><br>" >> "/etc/firewallfalcon/banners/${user}.txt"
- echo -e "<font color=\"cyan\"><b>━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━</b></font><br>" >> "/etc/firewallfalcon/banners/${user}.txt"
-
- # --- Bandwidth Check ---
- [[ -z "$bandwidth_gb" || "$bandwidth_gb" == "0" ]] && continue
-
- # Get user UID
- user_uid=$(id -u "$user" 2>/dev/null)
- [[ -z "$user_uid" ]] && continue
-
- # Find sshd PIDs for this user via loginuid
- pids=""
-
- # Method 1: pgrep
- m1=$(pgrep -u "$user" sshd 2>/dev/null | tr '\n' ' ')
- pids="$m1"
-
- # Method 2: loginuid scan
- for p in /proc/[0-9]*/loginuid; do
- [[ ! -f "$p" ]] && continue
- luid=$(cat "$p" 2>/dev/null)
- [[ -z "$luid" || "$luid" == "4294967295" ]] && continue
- [[ "$luid" != "$user_uid" ]] && continue
-
- pid_dir=$(dirname "$p")
- pid_num=$(basename "$pid_dir")
-
- cname=$(cat "$pid_dir/comm" 2>/dev/null)
- [[ "$cname" != "sshd" ]] && continue
-
- ppid_val=$(awk '/^PPid:/{print $2}' "$pid_dir/status" 2>/dev/null)
- [[ "$ppid_val" == "1" ]] && continue
-
- pids="$pids $pid_num"
- done
-
- # Deduplicate
- pids=$(echo "$pids" | tr ' ' '\n' | sort -u | grep -v '^$' | tr '\n' ' ')
-
- # Read accumulated usage
- usagefile="$BW_DIR/${user}.usage"
- accumulated=0
- if [[ -f "$usagefile" ]]; then
- accumulated=$(cat "$usagefile" 2>/dev/null)
- if ! [[ "$accumulated" =~ ^[0-9]+$ ]]; then accumulated=0; fi
- fi
-
- if [[ -z "$pids" ]]; then
- rm -f "$PID_DIR/${user}__"*.last 2>/dev/null
- continue
- fi
-
- delta_total=0
-
- for pid in $pids; do
- [[ -z "$pid" ]] && continue
- io_file="/proc/$pid/io"
- if [[ -r "$io_file" ]]; then
- rchar=$(awk '/^rchar:/{print $2}' "$io_file" 2>/dev/null)
- wchar=$(awk '/^wchar:/{print $2}' "$io_file" 2>/dev/null)
- [[ -z "$rchar" ]] && rchar=0
- [[ -z "$wchar" ]] && wchar=0
- cur=$((rchar + wchar))
- else
- cur=0
- fi
-
- pidfile="$PID_DIR/${user}__${pid}.last"
-
- if [[ -f "$pidfile" ]]; then
- prev=$(cat "$pidfile" 2>/dev/null)
- if ! [[ "$prev" =~ ^[0-9]+$ ]]; then prev=0; fi
-
- if [[ "$cur" -ge "$prev" ]]; then
- d=$((cur - prev))
- else
- d=$cur
- fi
- delta_total=$((delta_total + d))
- fi
- echo "$cur" > "$pidfile"
- done
-
- # Clean up dead PID files
- for f in "$PID_DIR/${user}__"*.last; do
- [[ ! -f "$f" ]] && continue
- fpid=$(basename "$f" .last)
- fpid=${fpid#${user}__}
- [[ ! -d "/proc/$fpid" ]] && rm -f "$f"
- done
-
- # Update total
- new_total=$((accumulated + delta_total))
- echo "$new_total" > "$usagefile"
-
- # Check quota
- quota_bytes=$(awk "BEGIN {printf \"%.0f\", $bandwidth_gb * 1073741824}")
-
- if [[ "$new_total" -ge "$quota_bytes" ]]; then
- if ! passwd -S "$user" 2>/dev/null | grep -q " L "; then
- usermod -L "$user" &>/dev/null
- killall -u "$user" -9 &>/dev/null
- fi
- fi
-
- done < "$DB_FILE"
-
- sleep 15
- done
- EOF
- chmod +x "$LIMITER_SCRIPT"
- cat > "$LIMITER_SERVICE" << EOF
- [Unit]
- Description=FirewallFalcon Active User Limiter
- After=network.target
- [Service]
- Type=simple
- ExecStart=$LIMITER_SCRIPT
- Restart=always
- RestartSec=5
- [Install]
- WantedBy=multi-user.target
- EOF
- pkill -f "firewallfalcon-limiter" 2>/dev/null
- if ! systemctl is-active --quiet firewallfalcon-limiter; then
- systemctl daemon-reload
- systemctl enable firewallfalcon-limiter &>/dev/null
- systemctl start firewallfalcon-limiter --no-block &>/dev/null
-
- else
- systemctl restart firewallfalcon-limiter --no-block &>/dev/null
-
- fi
- }
- setup_bandwidth_service() {
- mkdir -p "$BANDWIDTH_DIR"
- # Bandwidth monitoring is now integrated into the limiter service above.
- # Stop the old standalone bandwidth service if it exists.
- if systemctl is-active --quiet firewallfalcon-bandwidth 2>/dev/null; then
- systemctl stop firewallfalcon-bandwidth &>/dev/null
- systemctl disable firewallfalcon-bandwidth &>/dev/null
- fi
- rm -f "$BANDWIDTH_SERVICE" "$BANDWIDTH_SCRIPT" 2>/dev/null
- }
- setup_trial_cleanup_script() {
- cat > "$TRIAL_CLEANUP_SCRIPT" << 'TREOF'
- #!/bin/bash
- # FirewallFalcon Trial Account Auto-Cleanup
- # Usage: firewallfalcon-trial-cleanup.sh <username>
- DB_FILE="/etc/firewallfalcon/users.db"
- BW_DIR="/etc/firewallfalcon/bandwidth"
- username="$1"
- if [[ -z "$username" ]]; then exit 1; fi
- # Kill active sessions
- killall -u "$username" -9 &>/dev/null
- sleep 1
- # Delete system user
- userdel -r "$username" &>/dev/null
- # Remove from DB
- sed -i "/^${username}:/d" "$DB_FILE"
- # Remove bandwidth tracking
- rm -f "$BW_DIR/${username}.usage"
- rm -rf "$BW_DIR/pidtrack/${username}"
- TREOF
- chmod +x "$TRIAL_CLEANUP_SCRIPT"
- }
- update_ssh_banners_config() {
- rm -f /usr/local/bin/firewallfalcon-login-info.sh 2>/dev/null
-
- if [[ ! -f "/etc/firewallfalcon/banners_enabled" ]]; then
- rm -f "$SSHD_FF_CONFIG" 2>/dev/null
- systemctl reload sshd 2>/dev/null || systemctl reload ssh 2>/dev/null
- return
- fi
-
- mkdir -p "/etc/firewallfalcon/banners" /etc/ssh/sshd_config.d
-
- tmp_conf="/tmp/ff_banners_new.conf"
- echo "# FirewallFalcon - Show login info native banners" > "$tmp_conf"
-
- if [[ -f "$DB_FILE" ]]; then
- while IFS=: read -r u _rest; do
- [[ -z "$u" || "$u" == \#* ]] && continue
- echo "Match User $u" >> "$tmp_conf"
- echo " Banner /etc/firewallfalcon/banners/${u}.txt" >> "$tmp_conf"
- done < "$DB_FILE"
- fi
-
- if ! cmp -s "$tmp_conf" "$SSHD_FF_CONFIG" 2>/dev/null; then
- mv "$tmp_conf" "$SSHD_FF_CONFIG"
- if ! grep -q "^Include /etc/ssh/sshd_config.d/" /etc/ssh/sshd_config 2>/dev/null; then
- echo "Include /etc/ssh/sshd_config.d/*.conf" >> /etc/ssh/sshd_config
- fi
- systemctl reload sshd 2>/dev/null || systemctl reload ssh 2>/dev/null
- else
- rm -f "$tmp_conf"
- fi
- }
- setup_ssh_login_info() {
- touch "/etc/firewallfalcon/banners_enabled"
- update_ssh_banners_config
- }
- generate_dns_record() {
- echo -e "\n${C_BLUE}⚙️ Generating a random domain...${C_RESET}"
- if ! command -v jq &> /dev/null; then
- echo -e "${C_YELLOW}⚠️ jq not found, attempting to install...${C_RESET}"
- apt-get update > /dev/null 2>&1 && apt-get install -y jq || {
- echo -e "${C_RED}❌ Failed to install jq. Cannot manage DNS records.${C_RESET}"
- return 1
- }
- fi
- local SERVER_IPV4
- SERVER_IPV4=$(curl -s -4 icanhazip.com)
- if ! _is_valid_ipv4 "$SERVER_IPV4"; then
- echo -e "\n${C_RED}❌ Error: Could not retrieve a valid public IPv4 address from icanhazip.com.${C_RESET}"
- echo -e "${C_YELLOW}ℹ️ Please check your server's network connection and DNS resolver settings.${C_RESET}"
- echo -e " Output received: '$SERVER_IPV4'"
- return 1
- fi
- local SERVER_IPV6
- SERVER_IPV6=$(curl -s -6 icanhazip.com --max-time 5)
- local RANDOM_SUBDOMAIN="vps-$(head /dev/urandom | tr -dc a-z0-9 | head -c 8)"
- local FULL_DOMAIN="$RANDOM_SUBDOMAIN.$DESEC_DOMAIN"
- local HAS_IPV6="false"
- local API_DATA
- API_DATA=$(printf '[{"subname": "%s", "type": "A", "ttl": 3600, "records": ["%s"]}]' "$RANDOM_SUBDOMAIN" "$SERVER_IPV4")
- if [[ -n "$SERVER_IPV6" ]]; then
- local aaaa_record
- aaaa_record=$(printf ',{"subname": "%s", "type": "AAAA", "ttl": 3600, "records": ["%s"]}' "$RANDOM_SUBDOMAIN" "$SERVER_IPV6")
- API_DATA="${API_DATA%?}${aaaa_record}]"
- HAS_IPV6="true"
- fi
- local CREATE_RESPONSE
- CREATE_RESPONSE=$(curl -s -w "%{http_code}" -X POST "https://desec.io/api/v1/domains/$DESEC_DOMAIN/rrsets/" \
- -H "Authorization: Token $DESEC_TOKEN" -H "Content-Type: application/json" \
- --data "$API_DATA")
-
- local HTTP_CODE=${CREATE_RESPONSE: -3}
- local RESPONSE_BODY=${CREATE_RESPONSE:0:${#CREATE_RESPONSE}-3}
- if [[ "$HTTP_CODE" -ne 201 ]]; then
- echo -e "${C_RED}❌ Failed to create DNS records. API returned HTTP $HTTP_CODE.${C_RESET}"
- if ! echo "$RESPONSE_BODY" | jq . > /dev/null 2>&1; then
- echo "Raw Response: $RESPONSE_BODY"
- else
- echo "Response: $RESPONSE_BODY" | jq
- fi
- return 1
- fi
-
- cat > "$DNS_INFO_FILE" <<-EOF
- SUBDOMAIN="$RANDOM_SUBDOMAIN"
- FULL_DOMAIN="$FULL_DOMAIN"
- HAS_IPV6="$HAS_IPV6"
- EOF
- echo -e "\n${C_GREEN}✅ Successfully created domain: ${C_YELLOW}$FULL_DOMAIN${C_RESET}"
- }
- delete_dns_record() {
- if [ ! -f "$DNS_INFO_FILE" ]; then
- echo -e "\n${C_YELLOW}ℹ️ No domain to delete.${C_RESET}"
- return
- fi
- echo -e "\n${C_BLUE}🗑️ Deleting DNS records...${C_RESET}"
- source "$DNS_INFO_FILE"
- if [[ -z "$SUBDOMAIN" ]]; then
- echo -e "${C_RED}❌ Could not read record details from config file. Skipping deletion.${C_RESET}"
- return
- fi
- curl -s -X DELETE "https://desec.io/api/v1/domains/$DESEC_DOMAIN/rrsets/$SUBDOMAIN/A/" \
- -H "Authorization: Token $DESEC_TOKEN" > /dev/null
- if [[ "$HAS_IPV6" == "true" ]]; then
- curl -s -X DELETE "https://desec.io/api/v1/domains/$DESEC_DOMAIN/rrsets/$SUBDOMAIN/AAAA/" \
- -H "Authorization: Token $DESEC_TOKEN" > /dev/null
- fi
- echo -e "\n${C_GREEN}✅ Deleted domain: ${C_YELLOW}$FULL_DOMAIN${C_RESET}"
- rm -f "$DNS_INFO_FILE"
- }
- dns_menu() {
- clear; show_banner
- echo -e "${C_BOLD}${C_PURPLE}--- 🌐 DNS Domain Management ---${C_RESET}"
- if [ -f "$DNS_INFO_FILE" ]; then
- source "$DNS_INFO_FILE"
- echo -e "\nℹ️ A domain already exists for this server:"
- echo -e " - ${C_CYAN}Domain:${C_RESET} ${C_YELLOW}$FULL_DOMAIN${C_RESET}"
- echo
- read -p "👉 Do you want to DELETE this domain? (y/n): " choice
- if [[ "$choice" == "y" || "$choice" == "Y" ]]; then
- delete_dns_record
- else
- echo -e "\n${C_YELLOW}❌ Action cancelled.${C_RESET}"
- fi
- else
- echo -e "\nℹ️ No domain has been generated for this server yet."
- echo
- read -p "👉 Do you want to generate a new random domain now? (y/n): " choice
- if [[ "$choice" == "y" || "$choice" == "Y" ]]; then
- generate_dns_record
- else
- echo -e "\n${C_YELLOW}❌ Action cancelled.${C_RESET}"
- fi
- fi
- }
- _select_user_interface() {
- local title="$1"
- clear; show_banner
- echo -e "${C_BOLD}${C_PURPLE}${title}${C_RESET}\n"
- if [[ ! -s $DB_FILE ]]; then
- echo -e "${C_YELLOW}ℹ️ No users found in the database.${C_RESET}"
- SELECTED_USER="NO_USERS"; return
- fi
- read -p "👉 Enter a search term (or press Enter to list all): " search_term
- if [[ -z "$search_term" ]]; then
- mapfile -t users < <(cut -d: -f1 "$DB_FILE" | sort)
- else
- mapfile -t users < <(cut -d: -f1 "$DB_FILE" | grep -i "$search_term" | sort)
- fi
- if [ ${#users[@]} -eq 0 ]; then
- echo -e "\n${C_YELLOW}ℹ️ No users found matching your criteria.${C_RESET}"
- SELECTED_USER="NO_USERS"; return
- fi
- echo -e "\nPlease select a user:\n"
- for i in "${!users[@]}"; do
- printf " ${C_GREEN}[%2d]${C_RESET} %s\n" "$((i+1))" "${users[$i]}"
- done
- echo -e "\n ${C_RED} [ 0]${C_RESET} ↩️ Cancel and return to main menu"
- echo
- local choice
- while true; do
- read -p "👉 Enter the number of the user: " choice
- if [[ "$choice" =~ ^[0-9]+$ ]] && [ "$choice" -ge 0 ] && [ "$choice" -le "${#users[@]}" ]; then
- if [ "$choice" -eq 0 ]; then
- SELECTED_USER=""; return
- else
- SELECTED_USER="${users[$((choice-1))]}"; return
- fi
- else
- echo -e "${C_RED}❌ Invalid selection. Please try again.${C_RESET}"
- fi
- done
- }
- get_user_status() {
- local username="$1"
- if ! id "$username" &>/dev/null; then echo -e "${C_RED}Not Found${C_RESET}"; return; fi
- local expiry_date=$(grep "^$username:" "$DB_FILE" | cut -d: -f3)
- if passwd -S "$username" 2>/dev/null | grep -q " L "; then echo -e "${C_YELLOW}🔒 Locked${C_RESET}"; return; fi
- local expiry_ts=$(date -d "$expiry_date" +%s 2>/dev/null || echo 0)
- local current_ts=$(date +%s)
- if [[ $expiry_ts -lt $current_ts ]]; then echo -e "${C_RED}🗓️ Expired${C_RESET}"; return; fi
- echo -e "${C_GREEN}🟢 Active${C_RESET}"
- }
- create_user() {
- clear; show_banner
- echo -e "${C_BOLD}${C_PURPLE}--- ✨ Create New SSH User ---${C_RESET}"
- read -p "👉 Enter username (or '0' to cancel): " username
- if [[ "$username" == "0" ]]; then
- echo -e "\n${C_YELLOW}❌ User creation cancelled.${C_RESET}"
- return
- fi
- if [[ -z "$username" ]]; then
- echo -e "\n${C_RED}❌ Error: Username cannot be empty.${C_RESET}"
- return
- fi
- if id "$username" &>/dev/null || grep -q "^$username:" "$DB_FILE"; then
- echo -e "\n${C_RED}❌ Error: User '$username' already exists.${C_RESET}"; return
- fi
- local password=""
- while true; do
- read -p "🔑 Enter password (or press Enter for auto-generated): " password
- if [[ -z "$password" ]]; then
- password=$(head /dev/urandom | tr -dc 'A-Za-z0-9' | head -c 8)
- echo -e "${C_GREEN}🔑 Auto-generated password: ${C_YELLOW}$password${C_RESET}"
- break
- else
- break
- fi
- done
- read -p "🗓️ Enter account duration (in days): " days
- if ! [[ "$days" =~ ^[0-9]+$ ]]; then echo -e "\n${C_RED}❌ Invalid number.${C_RESET}"; return; fi
- read -p "📶 Enter simultaneous connection limit [1]: " limit
- limit=${limit:-1}
- if ! [[ "$limit" =~ ^[0-9]+$ ]]; then echo -e "\n${C_RED}❌ Invalid number.${C_RESET}"; return; fi
- read -p "📦 Enter bandwidth limit in GB (0 = unlimited) [0]: " bandwidth_gb
- bandwidth_gb=${bandwidth_gb:-0}
- if ! [[ "$bandwidth_gb" =~ ^[0-9]+\.?[0-9]*$ ]]; then echo -e "\n${C_RED}❌ Invalid number.${C_RESET}"; return; fi
- local expire_date
- expire_date=$(date -d "+$days days" +%Y-%m-%d)
- useradd -m -s /usr/sbin/nologin "$username"
- usermod -aG ffusers "$username" 2>/dev/null
- echo "$username:$password" | chpasswd; chage -E "$expire_date" "$username"
- echo "$username:$password:$expire_date:$limit:$bandwidth_gb" >> "$DB_FILE"
-
- local bw_display="Unlimited"
- if [[ "$bandwidth_gb" != "0" ]]; then bw_display="${bandwidth_gb} GB"; fi
-
- clear; show_banner
- echo -e "${C_GREEN}✅ User '$username' created successfully!${C_RESET}\n"
- echo -e " - 👤 Username: ${C_YELLOW}$username${C_RESET}"
- echo -e " - 🔑 Password: ${C_YELLOW}$password${C_RESET}"
- echo -e " - 🗓️ Expires on: ${C_YELLOW}$expire_date${C_RESET}"
- echo -e " - 📶 Connection Limit: ${C_YELLOW}$limit${C_RESET}"
- echo -e " - 📦 Bandwidth Limit: ${C_YELLOW}$bw_display${C_RESET}"
- echo -e " ${C_DIM}(Active monitoring service will enforce these limits)${C_RESET}"
- # Auto-ask for config generation
- echo
- read -p "👉 Do you want to generate a client connection config for this user? (y/n): " gen_conf
- if [[ "$gen_conf" == "y" || "$gen_conf" == "Y" ]]; then
- generate_client_config "$username" "$password"
- fi
-
- update_ssh_banners_config
- }
- delete_user() {
- _select_user_interface "--- 🗑️ Delete a User (from DB) ---"
- local username=$SELECTED_USER
-
- if [[ "$username" == "NO_USERS" ]] || [[ -z "$username" ]]; then
- if [[ "$username" == "NO_USERS" ]]; then
- echo -e "\n${C_YELLOW}ℹ️ No users found in database.${C_RESET}"
- fi
-
- read -p "👉 Type username to MANUALLY delete (or '0' to cancel): " manual_user
- if [[ "$manual_user" == "0" ]] || [[ -z "$manual_user" ]]; then
- echo -e "\n${C_YELLOW}❌ Action cancelled.${C_RESET}"
- return
- fi
- username="$manual_user"
-
- if ! id "$username" &>/dev/null; then
- echo -e "\n${C_RED}❌ Error: User '$username' does not exist on this system.${C_RESET}"
- return
- fi
-
- if grep -q "^$username:" "$DB_FILE"; then
- echo -e "\n${C_YELLOW}ℹ️ User '$username' is in the database. Please use the normal selection method.${C_RESET}"
- echo -e " For safety, manual deletion is only for users NOT in the database."
- return
- fi
-
- echo -e "${C_YELLOW}⚠️ User '$username' exists on the system but is NOT in the database.${C_RESET}"
- fi
- read -p "👉 Are you sure you want to PERMANENTLY delete '$username'? (y/n): " confirm
- if [[ "$confirm" != "y" ]]; then echo -e "\n${C_YELLOW}❌ Deletion cancelled.${C_RESET}"; return; fi
-
- echo -e "${C_BLUE}🔌 Force killing active connections for $username...${C_RESET}"
- killall -u "$username" -9 &>/dev/null
- sleep 1
- userdel -r "$username" &>/dev/null
- if [ $? -eq 0 ]; then
- echo -e "\n${C_GREEN}✅ System user '$username' has been deleted.${C_RESET}"
- else
- echo -e "\n${C_RED}❌ Failed to delete system user '$username'.${C_RESET}"
- fi
- # Clean up bandwidth tracking
- rm -f "$BANDWIDTH_DIR/${username}.usage"
- rm -rf "$BANDWIDTH_DIR/pidtrack/${username}"
- sed -i "/^$username:/d" "$DB_FILE"
- echo -e "${C_GREEN}✅ User '$username' has been completely removed.${C_RESET}"
-
- update_ssh_banners_config
- }
- edit_user() {
- _select_user_interface "--- ✏️ Edit a User ---"
- local username=$SELECTED_USER
- if [[ "$username" == "NO_USERS" ]] || [[ -z "$username" ]]; then return; fi
- while true; do
- clear; show_banner; echo -e "${C_BOLD}${C_PURPLE}--- Editing User: ${C_YELLOW}$username${C_PURPLE} ---${C_RESET}"
-
- # Show current user details
- local current_line; current_line=$(grep "^$username:" "$DB_FILE")
- local cur_pass; cur_pass=$(echo "$current_line" | cut -d: -f2)
- local cur_expiry; cur_expiry=$(echo "$current_line" | cut -d: -f3)
- local cur_limit; cur_limit=$(echo "$current_line" | cut -d: -f4)
- local cur_bw; cur_bw=$(echo "$current_line" | cut -d: -f5)
- [[ -z "$cur_bw" ]] && cur_bw="0"
- local cur_bw_display="Unlimited"; [[ "$cur_bw" != "0" ]] && cur_bw_display="${cur_bw} GB"
-
- # Show bandwidth usage
- local bw_used_display="N/A"
- if [[ -f "$BANDWIDTH_DIR/${username}.usage" ]]; then
- local used_bytes; used_bytes=$(cat "$BANDWIDTH_DIR/${username}.usage" 2>/dev/null)
- if [[ -n "$used_bytes" && "$used_bytes" != "0" ]]; then
- bw_used_display=$(awk "BEGIN {printf \"%.2f GB\", $used_bytes / 1073741824}")
- else
- bw_used_display="0.00 GB"
- fi
- fi
-
- echo -e "\n ${C_DIM}Current: Pass=${C_YELLOW}$cur_pass${C_RESET}${C_DIM} Exp=${C_YELLOW}$cur_expiry${C_RESET}${C_DIM} Conn=${C_YELLOW}$cur_limit${C_RESET}${C_DIM} BW=${C_YELLOW}$cur_bw_display${C_RESET}${C_DIM} Used=${C_CYAN}$bw_used_display${C_RESET}"
- echo -e "\nSelect a detail to edit:\n"
- printf " ${C_GREEN}[ 1]${C_RESET} %-35s\n" "🔑 Change Password"
- printf " ${C_GREEN}[ 2]${C_RESET} %-35s\n" "🗓️ Change Expiration Date"
- printf " ${C_GREEN}[ 3]${C_RESET} %-35s\n" "📶 Change Connection Limit"
- printf " ${C_GREEN}[ 4]${C_RESET} %-35s\n" "📦 Change Bandwidth Limit"
- printf " ${C_GREEN}[ 5]${C_RESET} %-35s\n" "🔄 Reset Bandwidth Counter"
- echo -e "\n ${C_RED}[ 0]${C_RESET} ✅ Finish Editing"; echo; read -p "👉 Enter your choice: " edit_choice
- case $edit_choice in
- 1)
- local new_pass=""
- read -p "Enter new password (or press Enter for auto-generated): " new_pass
- if [[ -z "$new_pass" ]]; then
- new_pass=$(head /dev/urandom | tr -dc 'A-Za-z0-9' | head -c 8)
- echo -e "${C_GREEN}🔑 Auto-generated: ${C_YELLOW}$new_pass${C_RESET}"
- fi
- echo "$username:$new_pass" | chpasswd
- sed -i "s/^$username:.*/$username:$new_pass:$cur_expiry:$cur_limit:$cur_bw/" "$DB_FILE"
- echo -e "\n${C_GREEN}✅ Password for '$username' changed to: ${C_YELLOW}$new_pass${C_RESET}"
- ;;
- 2) read -p "Enter new duration (in days from today): " days
- if [[ "$days" =~ ^[0-9]+$ ]]; then
- local new_expire_date; new_expire_date=$(date -d "+$days days" +%Y-%m-%d); chage -E "$new_expire_date" "$username"
- sed -i "s/^$username:.*/$username:$cur_pass:$new_expire_date:$cur_limit:$cur_bw/" "$DB_FILE"
- echo -e "\n${C_GREEN}✅ Expiration for '$username' set to ${C_YELLOW}$new_expire_date${C_RESET}."
- else echo -e "\n${C_RED}❌ Invalid number of days.${C_RESET}"; fi ;;
- 3) read -p "Enter new simultaneous connection limit: " new_limit
- if [[ "$new_limit" =~ ^[0-9]+$ ]]; then
- sed -i "s/^$username:.*/$username:$cur_pass:$cur_expiry:$new_limit:$cur_bw/" "$DB_FILE"
- echo -e "\n${C_GREEN}✅ Connection limit for '$username' set to ${C_YELLOW}$new_limit${C_RESET}."
- else echo -e "\n${C_RED}❌ Invalid limit.${C_RESET}"; fi ;;
- 4) read -p "Enter new bandwidth limit in GB (0 = unlimited): " new_bw
- if [[ "$new_bw" =~ ^[0-9]+\.?[0-9]*$ ]]; then
- sed -i "s/^$username:.*/$username:$cur_pass:$cur_expiry:$cur_limit:$new_bw/" "$DB_FILE"
- local bw_msg="Unlimited"; [[ "$new_bw" != "0" ]] && bw_msg="${new_bw} GB"
- echo -e "\n${C_GREEN}✅ Bandwidth limit for '$username' set to ${C_YELLOW}$bw_msg${C_RESET}."
- # Unlock user if they were locked due to bandwidth
- if [[ "$new_bw" == "0" ]] || [[ -f "$BANDWIDTH_DIR/${username}.usage" ]]; then
- local used_bytes; used_bytes=$(cat "$BANDWIDTH_DIR/${username}.usage" 2>/dev/null || echo 0)
- local new_quota_bytes; new_quota_bytes=$(awk "BEGIN {printf \"%.0f\", $new_bw * 1073741824}")
- if [[ "$new_bw" == "0" ]] || [[ "$used_bytes" -lt "$new_quota_bytes" ]]; then
- usermod -U "$username" &>/dev/null
- fi
- fi
- else echo -e "\n${C_RED}❌ Invalid bandwidth value.${C_RESET}"; fi ;;
- 5)
- echo "0" > "$BANDWIDTH_DIR/${username}.usage"
- # Unlock user if they were locked due to bandwidth
- usermod -U "$username" &>/dev/null
- echo -e "\n${C_GREEN}✅ Bandwidth counter for '$username' has been reset to 0.${C_RESET}"
- ;;
- 0) return ;;
- *) echo -e "\n${C_RED}❌ Invalid option.${C_RESET}" ;;
- esac
- echo -e "\nPress ${C_YELLOW}[Enter]${C_RESET} to continue editing..." && read -r
- done
- }
- lock_user() {
- _select_user_interface "--- 🔒 Lock a User (from DB) ---"
- local u=$SELECTED_USER
- if [[ "$u" == "NO_USERS" ]] || [[ -z "$u" ]]; then
- if [[ "$u" == "NO_USERS" ]]; then
- echo -e "\n${C_YELLOW}ℹ️ No users found in database.${C_RESET}"
- fi
-
- read -p "👉 Type username to MANUALLY lock (or '0' to cancel): " manual_user
- if [[ "$manual_user" == "0" ]] || [[ -z "$manual_user" ]]; then
- echo -e "\n${C_YELLOW}❌ Action cancelled.${C_RESET}"
- return
- fi
- u="$manual_user"
-
- if ! id "$u" &>/dev/null; then
- echo -e "\n${C_RED}❌ Error: User '$u' does not exist on this system.${C_RESET}"
- return
- fi
-
- if grep -q "^$u:" "$DB_FILE"; then
- echo -e "\n${C_YELLOW}ℹ️ User '$u' is in the database. Use the normal selection method.${C_RESET}"
- else
- echo -e "${C_YELLOW}⚠️ User '$u' exists on the system but is NOT in the database.${C_RESET}"
- fi
- fi
- usermod -L "$u"
- if [ $? -eq 0 ]; then
- killall -u "$u" -9 &>/dev/null
- echo -e "\n${C_GREEN}✅ User '$u' has been locked and active sessions killed.${C_RESET}"
- else
- echo -e "\n${C_RED}❌ Failed to lock user '$u'.${C_RESET}"
- fi
- }
- unlock_user() {
- _select_user_interface "--- 🔓 Unlock a User (from DB) ---"
- local u=$SELECTED_USER
- if [[ "$u" == "NO_USERS" ]] || [[ -z "$u" ]]; then
- if [[ "$u" == "NO_USERS" ]]; then
- echo -e "\n${C_YELLOW}ℹ️ No users found in database.${C_RESET}"
- fi
-
- read -p "👉 Type username to MANUALLY unlock (or '0' to cancel): " manual_user
- if [[ "$manual_user" == "0" ]] || [[ -z "$manual_user" ]]; then
- echo -e "\n${C_YELLOW}❌ Action cancelled.${C_RESET}"
- return
- fi
- u="$manual_user"
-
- if ! id "$u" &>/dev/null; then
- echo -e "\n${C_RED}❌ Error: User '$u' does not exist on this system.${C_RESET}"
- return
- fi
-
- if grep -q "^$u:" "$DB_FILE"; then
- echo -e "\n${C_YELLOW}ℹ️ User '$u' is in the database. Use the normal selection method.${C_RESET}"
- else
- echo -e "${C_YELLOW}⚠️ User '$u' exists on the system but is NOT in the database.${C_RESET}"
- fi
- fi
- usermod -U "$u"
- if [ $? -eq 0 ]; then
- echo -e "\n${C_GREEN}✅ User '$u' has been unlocked.${C_RESET}"
- else
- echo -e "\n${C_RED}❌ Failed to unlock user '$u'.${C_RESET}"
- fi
- }
- list_users() {
- clear; show_banner
- if [[ ! -s "$DB_FILE" ]]; then
- echo -e "\n${C_YELLOW}ℹ️ No users are currently being managed.${C_RESET}"
- return
- fi
- echo -e "${C_BOLD}${C_PURPLE}--- 📋 Managed Users ---${C_RESET}"
- echo -e "${C_CYAN}=========================================================================================${C_RESET}"
- printf "${C_BOLD}${C_WHITE}%-18s | %-12s | %-10s | %-15s | %-20s${C_RESET}\n" "USERNAME" "EXPIRES" "CONNS" "BANDWIDTH" "STATUS"
- echo -e "${C_CYAN}-----------------------------------------------------------------------------------------${C_RESET}"
-
- while IFS=: read -r user pass expiry limit bandwidth_gb _extra; do
- local online_count
- online_count=$(pgrep -u "$user" sshd | wc -l)
-
- local status
- status=$(get_user_status "$user")
- local plain_status
- plain_status=$(echo -e "$status" | sed 's/\x1b\[[0-9;]*m//g')
-
- local connection_string="$online_count / $limit"
-
- # Bandwidth display
- [[ -z "$bandwidth_gb" ]] && bandwidth_gb="0"
- local bw_string="Unlimited"
- if [[ "$bandwidth_gb" != "0" ]]; then
- local used_bytes=0
- if [[ -f "$BANDWIDTH_DIR/${user}.usage" ]]; then
- used_bytes=$(cat "$BANDWIDTH_DIR/${user}.usage" 2>/dev/null)
- [[ -z "$used_bytes" ]] && used_bytes=0
- fi
- local used_gb
- used_gb=$(awk "BEGIN {printf \"%.1f\", $used_bytes / 1073741824}")
- bw_string="${used_gb}/${bandwidth_gb}GB"
- fi
- local line_color="$C_WHITE"
- case $plain_status in
- *"Active"*) line_color="$C_GREEN" ;;
- *"Locked"*) line_color="$C_YELLOW" ;;
- *"Expired"*) line_color="$C_RED" ;;
- *"Not Found"*) line_color="$C_DIM" ;;
- esac
- printf "${line_color}%-18s ${C_RESET}| ${C_YELLOW}%-12s ${C_RESET}| ${C_CYAN}%-10s ${C_RESET}| ${C_ORANGE}%-15s ${C_RESET}| %-20s\n" "$user" "$expiry" "$connection_string" "$bw_string" "$status"
- done < <(sort "$DB_FILE")
- echo -e "${C_CYAN}=========================================================================================${C_RESET}\n"
- }
- renew_user() {
- _select_user_interface "--- 🔄 Renew a User ---"; local u=$SELECTED_USER; if [[ "$u" == "NO_USERS" || -z "$u" ]]; then return; fi
- read -p "👉 Enter number of days to extend the account: " days; if ! [[ "$days" =~ ^[0-9]+$ ]]; then echo -e "\n${C_RED}❌ Invalid number.${C_RESET}"; return; fi
- local new_expire_date; new_expire_date=$(date -d "+$days days" +%Y-%m-%d); chage -E "$new_expire_date" "$u"
- local line; line=$(grep "^$u:" "$DB_FILE"); local pass; pass=$(echo "$line"|cut -d: -f2); local limit; limit=$(echo "$line"|cut -d: -f4); local bw; bw=$(echo "$line"|cut -d: -f5)
- [[ -z "$bw" ]] && bw="0"
- sed -i "s/^$u:.*/$u:$pass:$new_expire_date:$limit:$bw/" "$DB_FILE"
- echo -e "\n${C_GREEN}✅ User '$u' has been renewed. New expiration date is ${C_YELLOW}${new_expire_date}${C_RESET}."
- }
- cleanup_expired() {
- clear; show_banner
- echo -e "${C_BOLD}${C_PURPLE}--- 🧹 Cleanup Expired Users ---${C_RESET}"
-
- local expired_users=()
- local current_ts
- current_ts=$(date +%s)
- if [[ ! -s "$DB_FILE" ]]; then
- echo -e "\n${C_GREEN}✅ User database is empty. No expired users found.${C_RESET}"
- return
- fi
-
- while IFS=: read -r user pass expiry limit bandwidth_gb _extra; do
- local expiry_ts
- expiry_ts=$(date -d "$expiry" +%s 2>/dev/null || echo 0)
-
- if [[ $expiry_ts -lt $current_ts && $expiry_ts -ne 0 ]]; then
- expired_users+=("$user")
- fi
- done < "$DB_FILE"
- if [ ${#expired_users[@]} -eq 0 ]; then
- echo -e "\n${C_GREEN}✅ No expired users found.${C_RESET}"
- return
- fi
- echo -e "\nThe following users have expired: ${C_RED}${expired_users[*]}${C_RESET}"
- read -p "👉 Do you want to delete all of them? (y/n): " confirm
- if [[ "$confirm" == "y" || "$confirm" == "Y" ]]; then
- for user in "${expired_users[@]}"; do
- echo " - Deleting ${C_YELLOW}$user...${C_RESET}"
- killall -u "$user" -9 &>/dev/null
- # Clean up bandwidth tracking
- rm -f "$BANDWIDTH_DIR/${user}.usage"
- rm -rf "$BANDWIDTH_DIR/pidtrack/${user}"
- userdel -r "$user" &>/dev/null
- sed -i "/^$user:/d" "$DB_FILE"
- done
- echo -e "\n${C_GREEN}✅ Expired users have been cleaned up.${C_RESET}"
- update_ssh_banners_config
- else
- echo -e "\n${C_YELLOW}❌ Cleanup cancelled.${C_RESET}"
- fi
- }
- backup_user_data() {
- clear; show_banner
- echo -e "${C_BOLD}${C_PURPLE}--- 💾 Backup User Data ---${C_RESET}"
- read -p "👉 Enter path for backup file [/root/firewallfalcon_users.tar.gz]: " backup_path
- backup_path=${backup_path:-/root/firewallfalcon_users.tar.gz}
- if [ ! -d "$DB_DIR" ] || [ ! -s "$DB_FILE" ]; then
- echo -e "\n${C_YELLOW}ℹ️ No user data found to back up.${C_RESET}"
- return
- fi
- echo -e "\n${C_BLUE}⚙️ Backing up user database and settings to ${C_YELLOW}$backup_path${C_RESET}..."
- tar -czf "$backup_path" -C "$(dirname "$DB_DIR")" "$(basename "$DB_DIR")"
- if [ $? -eq 0 ]; then
- echo -e "\n${C_GREEN}✅ SUCCESS: User data backup created at ${C_YELLOW}$backup_path${C_RESET}"
- else
- echo -e "\n${C_RED}❌ ERROR: Backup failed.${C_RESET}"
- fi
- }
- restore_user_data() {
- clear; show_banner
- echo -e "${C_BOLD}${C_PURPLE}--- 📥 Restore User Data ---${C_RESET}"
- read -p "👉 Enter the full path to the user data backup file [/root/firewallfalcon_users.tar.gz]: " backup_path
- backup_path=${backup_path:-/root/firewallfalcon_users.tar.gz}
- if [ ! -f "$backup_path" ]; then
- echo -e "\n${C_RED}❌ ERROR: Backup file not found at '$backup_path'.${C_RESET}"
- return
- fi
- echo -e "\n${C_RED}${C_BOLD}⚠️ WARNING:${C_RESET} This will overwrite all current users and settings."
- echo -e "It will restore user accounts, passwords, limits, and expiration dates from the backup file."
- read -p "👉 Are you absolutely sure you want to proceed? (y/n): " confirm
- if [[ "$confirm" != "y" ]]; then echo -e "\n${C_YELLOW}❌ Restore cancelled.${C_RESET}"; return; fi
- local temp_dir
- temp_dir=$(mktemp -d)
- echo -e "\n${C_BLUE}⚙️ Extracting backup file to a temporary location...${C_RESET}"
- tar -xzf "$backup_path" -C "$temp_dir"
- if [ $? -ne 0 ]; then
- echo -e "\n${C_RED}❌ ERROR: Failed to extract backup file. Aborting.${C_RESET}"
- rm -rf "$temp_dir"
- return
- fi
- local restored_db_file="$temp_dir/firewallfalcon/users.db"
- if [ ! -f "$restored_db_file" ]; then
- echo -e "\n${C_RED}❌ ERROR: users.db not found in the backup. Cannot restore user accounts.${C_RESET}"
- rm -rf "$temp_dir"
- return
- fi
- echo -e "${C_BLUE}⚙️ Overwriting current user database...${C_RESET}"
- mkdir -p "$DB_DIR"
- cp "$restored_db_file" "$DB_FILE"
- if [ -d "$temp_dir/firewallfalcon/ssl" ]; then
- cp -r "$temp_dir/firewallfalcon/ssl" "$DB_DIR/"
- fi
- if [ -d "$temp_dir/firewallfalcon/dnstt" ]; then
- cp -r "$temp_dir/firewallfalcon/dnstt" "$DB_DIR/"
- fi
- if [ -f "$temp_dir/firewallfalcon/dns_info.conf" ]; then
- cp "$temp_dir/firewallfalcon/dns_info.conf" "$DB_DIR/"
- fi
- if [ -f "$temp_dir/firewallfalcon/dnstt_info.conf" ]; then
- cp "$temp_dir/firewallfalcon/dnstt_info.conf" "$DB_DIR/"
- fi
- if [ -f "$temp_dir/firewallfalcon/falconproxy_config.conf" ]; then
- cp "$temp_dir/firewallfalcon/falconproxy_config.conf" "$DB_DIR/"
- fi
-
- echo -e "${C_BLUE}⚙️ Re-synchronizing system accounts with the restored database...${C_RESET}"
-
- while IFS=: read -r user pass expiry limit; do
- echo "Processing user: ${C_YELLOW}$user${C_RESET}"
- if ! id "$user" &>/dev/null; then
- echo " - User does not exist in system. Creating..."
- useradd -m -s /usr/sbin/nologin "$user"
- usermod -aG ffusers "$user" 2>/dev/null
- fi
- echo " - Setting password..."
- echo "$user:$pass" | chpasswd
- echo " - Setting expiration to $expiry..."
- chage -E "$expiry" "$user"
- echo " - Connection limit is $limit (enforced by PAM)"
- done < "$DB_FILE"
- rm -rf "$temp_dir"
- echo -e "\n${C_GREEN}✅ SUCCESS: User data restore completed.${C_RESET}"
-
- update_ssh_banners_config
- }
- _enable_banner_in_sshd_config() {
- echo -e "\n${C_BLUE}⚙️ Configuring sshd_config...${C_RESET}"
- sed -i.bak -E 's/^( *Banner *).*/#\1/' /etc/ssh/sshd_config
- if ! grep -q -E "^Banner $SSH_BANNER_FILE" /etc/ssh/sshd_config; then
- echo -e "\n# FirewallFalcon SSH Banner\nBanner $SSH_BANNER_FILE" >> /etc/ssh/sshd_config
- fi
- echo -e "${C_GREEN}✅ sshd_config updated.${C_RESET}"
- }
- _restart_ssh() {
- echo -e "\n${C_BLUE}🔄 Restarting SSH service to apply changes...${C_RESET}"
- local ssh_service_name=""
- if [ -f /lib/systemd/system/sshd.service ]; then
- ssh_service_name="sshd.service"
- elif [ -f /lib/systemd/system/ssh.service ]; then
- ssh_service_name="ssh.service"
- else
- echo -e "${C_RED}❌ Could not find sshd.service or ssh.service. Cannot restart SSH.${C_RESET}"
- return 1
- fi
- systemctl restart "${ssh_service_name}"
- if [ $? -eq 0 ]; then
- echo -e "${C_GREEN}✅ SSH service ('${ssh_service_name}') restarted successfully.${C_RESET}"
- else
- echo -e "${C_RED}❌ Failed to restart SSH service ('${ssh_service_name}'). Please check 'journalctl -u ${ssh_service_name}' for errors.${C_RESET}"
- fi
- }
- set_ssh_banner_paste() {
- clear; show_banner
- echo -e "${C_BOLD}${C_PURPLE}--- 📋 Paste SSH Banner ---${C_RESET}"
- echo -e "Paste your banner code below. Press ${C_YELLOW}[Ctrl+D]${C_RESET} when you are finished."
- echo -e "${C_DIM}The current banner (if any) will be overwritten.${C_RESET}"
- echo -e "--------------------------------------------------"
- cat > "$SSH_BANNER_FILE"
- chmod 644 "$SSH_BANNER_FILE"
- echo -e "\n--------------------------------------------------"
- echo -e "\n${C_GREEN}✅ Banner content saved from paste.${C_RESET}"
- _enable_banner_in_sshd_config
- _restart_ssh
- echo -e "\nPress ${C_YELLOW}[Enter]${C_RESET} to return..." && read -r
- }
- view_ssh_banner() {
- clear; show_banner
- echo -e "${C_BOLD}${C_PURPLE}--- 👁️ Current SSH Banner ---${C_RESET}"
- if [ -f "$SSH_BANNER_FILE" ]; then
- echo -e "\n${C_CYAN}--- BEGIN BANNER ---${C_RESET}"
- cat "$SSH_BANNER_FILE"
- echo -e "${C_CYAN}---- END BANNER ----${C_RESET}"
- else
- echo -e "\n${C_YELLOW}ℹ️ No banner file found at $SSH_BANNER_FILE.${C_RESET}"
- fi
- echo -e "\nPress ${C_YELLOW}[Enter]${C_RESET} to return..." && read -r
- }
- remove_ssh_banner() {
- clear; show_banner
- echo -e "${C_BOLD}${C_PURPLE}--- 🗑️ Remove SSH Banner ---${C_RESET}"
- read -p "👉 Are you sure you want to disable and remove the SSH banner? (y/n): " confirm
- if [[ "$confirm" != "y" ]]; then
- echo -e "\n${C_YELLOW}❌ Action cancelled.${C_RESET}"
- echo -e "\nPress ${C_YELLOW}[Enter]${C_RESET} to return..." && read -r
- return
- fi
- if [ -f "$SSH_BANNER_FILE" ]; then
- rm -f "$SSH_BANNER_FILE"
- echo -e "\n${C_GREEN}✅ Removed banner file: $SSH_BANNER_FILE${C_RESET}"
- else
- echo -e "\n${C_YELLOW}ℹ️ No banner file to remove.${C_RESET}"
- fi
- echo -e "\n${C_BLUE}⚙️ Disabling banner in sshd_config...${C_RESET}"
- sed -i.bak -E "s/^( *Banner\s+$SSH_BANNER_FILE)/#\1/" /etc/ssh/sshd_config
- echo -e "${C_GREEN}✅ Banner disabled in configuration.${C_RESET}"
- _restart_ssh
- echo -e "\nPress ${C_YELLOW}[Enter]${C_RESET} to return..." && read -r
- }
- ssh_banner_menu() {
- while true; do
- show_banner
- local banner_status
- if grep -q -E "^\s*Banner\s+$SSH_BANNER_FILE" /etc/ssh/sshd_config && [ -f "$SSH_BANNER_FILE" ]; then
- banner_status="${C_STATUS_A}(Active)${C_RESET}"
- else
- banner_status="${C_STATUS_I}(Inactive)${C_RESET}"
- fi
-
- echo -e "\n ${C_TITLE}═════════════════[ ${C_BOLD}🎨 SSH Banner Management ${banner_status} ${C_RESET}${C_TITLE}]═════════════════${C_RESET}"
- printf " ${C_CHOICE}[ 1]${C_RESET} %-40s\n" "📋 Paste or Edit Banner"
- printf " ${C_CHOICE}[ 2]${C_RESET} %-40s\n" "👁️ View Current Banner"
- printf " ${C_DANGER}[ 3]${C_RESET} %-40s\n" "🗑️ Disable and Remove Banner"
- echo -e " ${C_DIM}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~${C_RESET}"
- echo -e " ${C_WARN}[ 0]${C_RESET} ↩️ Return to Main Menu"
- echo
- read -p "$(echo -e ${C_PROMPT}"👉 Select an option: "${C_RESET})" choice
- case $choice in
- 1) set_ssh_banner_paste ;;
- 2) view_ssh_banner ;;
- 3) remove_ssh_banner ;;
- 0) return ;;
- *) echo -e "\n${C_RED}❌ Invalid option.${C_RESET}" && sleep 2 ;;
- esac
- done
- }
- install_udp_custom() {
- clear; show_banner
- echo -e "${C_BOLD}${C_PURPLE}--- 🚀 Installing udp-custom ---${C_RESET}"
- if [ -f "$UDP_CUSTOM_SERVICE_FILE" ]; then
- echo -e "\n${C_YELLOW}ℹ️ udp-custom is already installed.${C_RESET}"
- return
- fi
- echo -e "\n${C_GREEN}⚙️ Creating directory for udp-custom...${C_RESET}"
- rm -rf "$UDP_CUSTOM_DIR"
- mkdir -p "$UDP_CUSTOM_DIR"
- echo -e "\n${C_GREEN}⚙️ Detecting system architecture...${C_RESET}"
- local arch
- arch=$(uname -m)
- local binary_url=""
- if [[ "$arch" == "x86_64" ]]; then
- binary_url="https://github.com/firewallfalcons/FirewallFalcon-Manager/raw/main/udp/udp-custom-linux-amd64"
- echo -e "${C_BLUE}ℹ️ Detected x86_64 (amd64) architecture.${C_RESET}"
- elif [[ "$arch" == "aarch64" || "$arch" == "arm64" ]]; then
- binary_url="https://github.com/firewallfalcons/FirewallFalcon-Manager/raw/main/udp/udp-custom-linux-arm"
- echo -e "${C_BLUE}ℹ️ Detected ARM64 architecture.${C_RESET}"
- else
- echo -e "\n${C_RED}❌ Unsupported architecture: $arch. Cannot install udp-custom.${C_RESET}"
- rm -rf "$UDP_CUSTOM_DIR"
- return
- fi
- echo -e "\n${C_GREEN}📥 Downloading udp-custom binary...${C_RESET}"
- wget -q --show-progress -O "$UDP_CUSTOM_DIR/udp-custom" "$binary_url"
- if [ $? -ne 0 ]; then
- echo -e "\n${C_RED}❌ Failed to download the udp-custom binary.${C_RESET}"
- rm -rf "$UDP_CUSTOM_DIR"
- return
- fi
- chmod +x "$UDP_CUSTOM_DIR/udp-custom"
- echo -e "\n${C_GREEN}📝 Creating default config.json...${C_RESET}"
- cat > "$UDP_CUSTOM_DIR/config.json" <<EOF
- {
- "listen": ":36712",
- "stream_buffer": 33554432,
- "receive_buffer": 83886080,
- "auth": {
- "mode": "passwords"
- }
- }
- EOF
- chmod 644 "$UDP_CUSTOM_DIR/config.json"
- echo -e "\n${C_GREEN}📝 Creating systemd service file...${C_RESET}"
- cat > "$UDP_CUSTOM_SERVICE_FILE" <<EOF
- [Unit]
- Description=UDP Custom by FirewallFalcon
- After=network.target
- [Service]
- User=root
- Type=simple
- ExecStart=$UDP_CUSTOM_DIR/udp-custom server -exclude 53,5300
- WorkingDirectory=$UDP_CUSTOM_DIR/
- Restart=always
- RestartSec=2s
- [Install]
- WantedBy=default.target
- EOF
- echo -e "\n${C_GREEN}▶️ Enabling and starting udp-custom service...${C_RESET}"
- systemctl daemon-reload
- systemctl enable udp-custom.service
- systemctl start udp-custom.service
- sleep 2
- if systemctl is-active --quiet udp-custom; then
- echo -e "\n${C_GREEN}✅ SUCCESS: udp-custom is installed and active.${C_RESET}"
- else
- echo -e "\n${C_RED}❌ ERROR: udp-custom service failed to start.${C_RESET}"
- echo -e "${C_YELLOW}ℹ️ Displaying last 15 lines of the service log for diagnostics:${C_RESET}"
- journalctl -u udp-custom.service -n 15 --no-pager
- fi
- }
- uninstall_udp_custom() {
- echo -e "\n${C_BOLD}${C_PURPLE}--- 🗑️ Uninstalling udp-custom ---${C_RESET}"
- if [ ! -f "$UDP_CUSTOM_SERVICE_FILE" ]; then
- echo -e "${C_YELLOW}ℹ️ udp-custom is not installed, skipping.${C_RESET}"
- return
- fi
- echo -e "${C_GREEN}🛑 Stopping and disabling udp-custom service...${C_RESET}"
- systemctl stop udp-custom.service >/dev/null 2>&1
- systemctl disable udp-custom.service >/dev/null 2>&1
- echo -e "${C_GREEN}🗑️ Removing systemd service file...${C_RESET}"
- rm -f "$UDP_CUSTOM_SERVICE_FILE"
- systemctl daemon-reload
- echo -e "${C_GREEN}🗑️ Removing udp-custom directory and files...${C_RESET}"
- rm -rf "$UDP_CUSTOM_DIR"
- echo -e "${C_GREEN}✅ udp-custom has been uninstalled successfully.${C_RESET}"
- }
- install_badvpn() {
- clear; show_banner
- echo -e "${C_BOLD}${C_PURPLE}--- 🚀 Installing badvpn (udpgw) ---${C_RESET}"
- if [ -f "$BADVPN_SERVICE_FILE" ]; then
- echo -e "\n${C_YELLOW}ℹ️ badvpn is already installed.${C_RESET}"
- return
- fi
- check_and_open_firewall_port 7300 udp || return
- echo -e "\n${C_GREEN}🔄 Updating package lists...${C_RESET}"
- apt-get update
- echo -e "\n${C_GREEN}📦 Installing all required packages...${C_RESET}"
- apt-get install -y cmake g++ make screen git build-essential libssl-dev libnspr4-dev libnss3-dev pkg-config
- echo -e "\n${C_GREEN}📥 Cloning badvpn from github...${C_RESET}"
- git clone https://github.com/ambrop72/badvpn.git "$BADVPN_BUILD_DIR"
- cd "$BADVPN_BUILD_DIR" || { echo -e "${C_RED}❌ Failed to change directory to build folder.${C_RESET}"; return; }
- echo -e "\n${C_GREEN}⚙️ Running CMake...${C_RESET}"
- cmake . || { echo -e "${C_RED}❌ CMake configuration failed.${C_RESET}"; rm -rf "$BADVPN_BUILD_DIR"; return; }
- echo -e "\n${C_GREEN}🛠️ Compiling source...${C_RESET}"
- make || { echo -e "${C_RED}❌ Compilation (make) failed.${C_RESET}"; rm -rf "$BADVPN_BUILD_DIR"; return; }
- local badvpn_binary
- badvpn_binary=$(find "$BADVPN_BUILD_DIR" -name "badvpn-udpgw" -type f | head -n 1)
- if [[ -z "$badvpn_binary" || ! -f "$badvpn_binary" ]]; then
- echo -e "${C_RED}❌ ERROR: Could not find the compiled 'badvpn-udpgw' binary after compilation.${C_RESET}"
- rm -rf "$BADVPN_BUILD_DIR"
- return
- fi
- echo -e "${C_GREEN}ℹ️ Found binary at: $badvpn_binary${C_RESET}"
- chmod +x "$badvpn_binary"
- echo -e "\n${C_GREEN}📝 Creating systemd service file...${C_RESET}"
- cat > "$BADVPN_SERVICE_FILE" <<-EOF
- [Unit]
- Description=BadVPN UDP Gateway
- After=network.target
- [Service]
- ExecStart=$badvpn_binary --listen-addr 0.0.0.0:7300 --max-clients 1000 --max-connections-for-client 8
- User=root
- Restart=always
- RestartSec=3
- [Install]
- WantedBy=multi-user.target
- EOF
- echo -e "\n${C_GREEN}▶️ Enabling and starting badvpn service...${C_RESET}"
- systemctl daemon-reload
- systemctl enable badvpn.service
- systemctl start badvpn.service
- sleep 2
- if systemctl is-active --quiet badvpn; then
- echo -e "\n${C_GREEN}✅ SUCCESS: badvpn (udpgw) is installed and active on port 7300.${C_RESET}"
- else
- echo -e "\n${C_RED}❌ ERROR: badvpn service failed to start.${C_RESET}"
- echo -e "${C_YELLOW}ℹ️ Displaying last 15 lines of the service log for diagnostics:${C_RESET}"
- journalctl -u badvpn.service -n 15 --no-pager
- fi
- }
- uninstall_badvpn() {
- echo -e "\n${C_BOLD}${C_PURPLE}--- 🗑️ Uninstalling badvpn (udpgw) ---${C_RESET}"
- if [ ! -f "$BADVPN_SERVICE_FILE" ]; then
- echo -e "${C_YELLOW}ℹ️ badvpn is not installed, skipping.${C_RESET}"
- return
- fi
- echo -e "${C_GREEN}🛑 Stopping and disabling badvpn service...${C_RESET}"
- systemctl stop badvpn.service >/dev/null 2>&1
- systemctl disable badvpn.service >/dev/null 2>&1
- echo -e "${C_GREEN}🗑️ Removing systemd service file...${C_RESET}"
- rm -f "$BADVPN_SERVICE_FILE"
- systemctl daemon-reload
- echo -e "${C_GREEN}🗑️ Removing badvpn build directory...${C_RESET}"
- rm -rf "$BADVPN_BUILD_DIR"
- echo -e "${C_GREEN}✅ badvpn has been uninstalled successfully.${C_RESET}"
- }
- install_ssl_tunnel() {
- clear; show_banner
- echo -e "${C_BOLD}${C_PURPLE}--- 🚀 Installing SSL Tunnel (HAProxy) for SSH ---${C_RESET}"
- if ! command -v haproxy &> /dev/null; then
- echo -e "\n${C_YELLOW}⚠️ HAProxy not found. Installing...${C_RESET}"
- apt-get update && apt-get install -y haproxy || { echo -e "${C_RED}❌ Failed to install HAProxy.${C_RESET}"; return; }
- fi
- read -p "👉 Enter the port for the SSL tunnel [444]: " ssl_port
- ssl_port=${ssl_port:-444}
- if ! [[ "$ssl_port" =~ ^[0-9]+$ ]] || [ "$ssl_port" -lt 1 ] || [ "$ssl_port" -gt 65535 ]; then
- echo -e "\n${C_RED}❌ Invalid port number. Aborting.${C_RESET}"
- return
- fi
-
- check_and_free_ports "$ssl_port" || return
- check_and_open_firewall_port "$ssl_port" || return
- if [ -f "$SSL_CERT_FILE" ]; then
- read -p "SSL certificate already exists. Overwrite? (y/n): " overwrite_cert
- if [[ "$overwrite_cert" != "y" ]]; then
- echo -e "${C_YELLOW}ℹ️ Using existing certificate.${C_RESET}"
- else
- rm -f "$SSL_CERT_FILE"
- fi
- fi
- if [ ! -f "$SSL_CERT_FILE" ]; then
- echo -e "\n${C_GREEN}🔐 Generating self-signed SSL certificate...${C_RESET}"
- openssl req -x509 -newkey rsa:2048 -nodes -days 3650 \
- -keyout "$SSL_CERT_FILE" -out "$SSL_CERT_FILE" \
- -subj "/CN=@FIREWALLFALCON" \
- >/dev/null 2>&1 || { echo -e "${C_RED}❌ Failed to generate SSL certificate.${C_RESET}"; return; }
- echo -e "${C_GREEN}✅ Certificate created: ${C_YELLOW}$SSL_CERT_FILE${C_RESET}"
- fi
- echo -e "\n${C_GREEN}📝 Creating HAProxy configuration for port $ssl_port...${C_RESET}"
- cat > "$HAPROXY_CONFIG" <<-EOF
- global
- log /dev/log local0
- log /dev/log local1 notice
- chroot /var/lib/haproxy
- stats socket /run/haproxy/admin.sock mode 660 level admin expose-fd listeners
- stats timeout 30s
- user haproxy
- group haproxy
- daemon
- defaults
- log global
- mode tcp
- option tcplog
- option dontlognull
- timeout connect 5000
- timeout client 50000
- timeout server 50000
- frontend ssh_ssl_in
- bind *:$ssl_port ssl crt $SSL_CERT_FILE
- mode tcp
- default_backend ssh_backend
- backend ssh_backend
- mode tcp
- server ssh_server 127.0.0.1:22
- EOF
- echo -e "\n${C_GREEN}▶️ Reloading and starting HAProxy service...${C_RESET}"
- systemctl daemon-reload
- systemctl restart haproxy
- sleep 2
- if systemctl is-active --quiet haproxy; then
- echo -e "\n${C_GREEN}✅ SUCCESS: SSL Tunnel is active.${C_RESET}"
- echo -e "Clients can now connect to this server's IP on port ${C_YELLOW}${ssl_port}${C_RESET} using an SSL/TLS tunnel."
- else
- echo -e "\n${C_RED}❌ ERROR: HAProxy service failed to start.${C_RESET}"
- echo -e "${C_YELLOW}ℹ️ Displaying HAProxy status for diagnostics:${C_RESET}"
- systemctl status haproxy --no-pager
- fi
- }
- uninstall_ssl_tunnel() {
- echo -e "\n${C_BOLD}${C_PURPLE}--- 🗑️ Uninstalling SSL Tunnel ---${C_RESET}"
- if ! command -v haproxy &> /dev/null; then
- echo -e "${C_YELLOW}ℹ️ HAProxy not installed, skipping.${C_RESET}"
- return
- fi
- echo -e "${C_GREEN}🛑 Stopping HAProxy service...${C_RESET}"
- systemctl stop haproxy >/dev/null 2>&1
- if [ -f "$HAPROXY_CONFIG" ]; then
- echo -e "${C_GREEN}📝 Restoring default/empty HAProxy config...${C_RESET}"
- cat > "$HAPROXY_CONFIG" <<-EOF
- global
- log /dev/log local0
- log /dev/log local1 notice
- defaults
- log global
- EOF
- fi
- if [ -f "$SSL_CERT_FILE" ]; then
- local delete_cert="y"
- if [[ "$UNINSTALL_MODE" != "silent" ]]; then
- read -p "👉 Delete the SSL certificate at $SSL_CERT_FILE? (y/n): " delete_cert
- fi
- if [[ "$delete_cert" == "y" ]]; then
- echo -e "${C_GREEN}🗑️ Removing SSL certificate...${C_RESET}"
- rm -f "$SSL_CERT_FILE"
- fi
- fi
- echo -e "${C_GREEN}✅ SSL Tunnel has been uninstalled.${C_RESET}"
- }
- show_dnstt_details() {
- if [ -f "$DNSTT_CONFIG_FILE" ]; then
- source "$DNSTT_CONFIG_FILE"
- echo -e "\n${C_GREEN}=====================================================${C_RESET}"
- echo -e "${C_GREEN} 📡 DNSTT Connection Details ${C_RESET}"
- echo -e "${C_GREEN}=====================================================${C_RESET}"
- echo -e "\n${C_WHITE}Your connection details:${C_RESET}"
- echo -e " - ${C_CYAN}Tunnel Domain:${C_RESET} ${C_YELLOW}$TUNNEL_DOMAIN${C_RESET}"
- echo -e " - ${C_CYAN}Public Key:${C_RESET} ${C_YELLOW}$PUBLIC_KEY${C_RESET}"
- if [[ -n "$FORWARD_DESC" ]]; then
- echo -e " - ${C_CYAN}Forwarding To:${C_RESET} ${C_YELLOW}$FORWARD_DESC${C_RESET}"
- else
- echo -e " - ${C_CYAN}Forwarding To:${C_RESET} ${C_YELLOW}Unknown (config_missing)${C_RESET}"
- fi
- if [[ -n "$MTU_VALUE" ]]; then
- echo -e " - ${C_CYAN}MTU Value:${C_RESET} ${C_YELLOW}$MTU_VALUE${C_RESET}"
- fi
- if [[ "$DNSTT_RECORDS_MANAGED" == "false" && -n "$NS_DOMAIN" ]]; then
- echo -e " - ${C_CYAN}NS Record:${C_RESET} ${C_YELLOW}$NS_DOMAIN${C_RESET}"
- fi
-
- if [[ "$FORWARD_DESC" == *"V2Ray"* ]]; then
- echo -e " - ${C_CYAN}Action Required:${C_RESET} ${C_YELLOW}Ensure a V2Ray service (vless/vmess/trojan) listens on port 8787 (no TLS)${C_RESET}"
- elif [[ "$FORWARD_DESC" == *"SSH"* ]]; then
- echo -e " - ${C_CYAN}Action Required:${C_RESET} ${C_YELLOW}Ensure your SSH client is configured to use the DNS tunnel.${C_RESET}"
- fi
-
- echo -e "\n${C_DIM}Use these details in your client configuration.${C_RESET}"
- else
- echo -e "\n${C_YELLOW}ℹ️ DNSTT configuration file not found. Details are unavailable.${C_RESET}"
- fi
- }
- install_dnstt() {
- clear; show_banner
- echo -e "${C_BOLD}${C_PURPLE}--- 📡 DNSTT (DNS Tunnel) Management ---${C_RESET}"
- if [ -f "$DNSTT_SERVICE_FILE" ]; then
- echo -e "\n${C_YELLOW}ℹ️ DNSTT is already installed.${C_RESET}"
- show_dnstt_details
- return
- fi
-
- # --- FIX: Force release of Port 53 / Disable systemd-resolved ---
- echo -e "${C_GREEN}⚙️ Forcing release of Port 53 (stopping systemd-resolved)...${C_RESET}"
- systemctl stop systemd-resolved >/dev/null 2>&1
- systemctl disable systemd-resolved >/dev/null 2>&1
- rm -f /etc/resolv.conf
- echo "nameserver 8.8.8.8" | tee /etc/resolv.conf > /dev/null
- # ----------------------------------------------------------------
-
- echo -e "\n${C_BLUE}🔎 Checking if port 53 (UDP) is available...${C_RESET}"
- if ss -lunp | grep -q ':53\s'; then
- if [[ $(ps -p $(ss -lunp | grep ':53\s' | grep -oP 'pid=\K[0-9]+') -o comm=) == "systemd-resolve" ]]; then
- echo -e "${C_YELLOW}⚠️ Warning: Port 53 is in use by 'systemd-resolved'.${C_RESET}"
- echo -e "${C_YELLOW}This is the system's DNS stub resolver. It must be disabled to run DNSTT.${C_RESET}"
- read -p "👉 Allow the script to automatically disable it and reconfigure DNS? (y/n): " resolve_confirm
- if [[ "$resolve_confirm" == "y" || "$resolve_confirm" == "Y" ]]; then
- echo -e "${C_GREEN}⚙️ Stopping and disabling systemd-resolved to free port 53...${C_RESET}"
- systemctl stop systemd-resolved
- systemctl disable systemd-resolved
- chattr -i /etc/resolv.conf &>/dev/null
- rm -f /etc/resolv.conf
- echo "nameserver 8.8.8.8" > /etc/resolv.conf
- chattr +i /etc/resolv.conf
- echo -e "${C_GREEN}✅ Port 53 has been freed and DNS set to 8.8.8.8.${C_RESET}"
- else
- echo -e "${C_RED}❌ Cannot proceed without freeing port 53. Aborting.${C_RESET}"
- return
- fi
- else
- check_and_free_ports "53" || return
- fi
- else
- echo -e "${C_GREEN}✅ Port 53 (UDP) is free to use.${C_RESET}"
- fi
- check_and_open_firewall_port 53 udp || return
- local forward_port=""
- local forward_desc=""
- echo -e "\n${C_BLUE}Please choose where DNSTT should forward traffic:${C_RESET}"
- echo -e " ${C_GREEN}[ 1]${C_RESET} ➡️ Forward to local SSH service (port 22)"
- echo -e " ${C_GREEN}[ 2]${C_RESET} ➡️ Forward to local V2Ray backend (port 8787)"
- read -p "👉 Enter your choice [2]: " fwd_choice
- fwd_choice=${fwd_choice:-2}
- if [[ "$fwd_choice" == "1" ]]; then
- forward_port="22"
- forward_desc="SSH (port 22)"
- echo -e "${C_GREEN}ℹ️ DNSTT will forward to SSH on 127.0.0.1:22.${C_RESET}"
-
-
- elif [[ "$fwd_choice" == "2" ]]; then
- forward_port="8787"
- forward_desc="V2Ray (port 8787)"
- echo -e "${C_GREEN}ℹ️ DNSTT will forward to V2Ray on 127.0.0.1:8787.${C_RESET}"
- else
- echo -e "${C_RED}❌ Invalid choice. Aborting.${C_RESET}"
- return
- fi
- local FORWARD_TARGET="127.0.0.1:$forward_port"
-
- local NS_DOMAIN=""
- local TUNNEL_DOMAIN=""
- local DNSTT_RECORDS_MANAGED="true"
- local NS_SUBDOMAIN=""
- local TUNNEL_SUBDOMAIN=""
- local HAS_IPV6="false"
- read -p "👉 Auto-generate DNS records or use custom ones? (auto/custom) [auto]: " dns_choice
- dns_choice=${dns_choice:-auto}
- if [[ "$dns_choice" == "custom" ]]; then
- DNSTT_RECORDS_MANAGED="false"
- read -p "👉 Enter your full nameserver domain (e.g., ns1.yourdomain.com): " NS_DOMAIN
- if [[ -z "$NS_DOMAIN" ]]; then echo -e "\n${C_RED}❌ Nameserver domain cannot be empty. Aborting.${C_RESET}"; return; fi
- read -p "👉 Enter your full tunnel domain (e.g., tun.yourdomain.com): " TUNNEL_DOMAIN
- if [[ -z "$TUNNEL_DOMAIN" ]]; then echo -e "\n${C_RED}❌ Tunnel domain cannot be empty. Aborting.${C_RESET}"; return; fi
- else
- echo -e "\n${C_BLUE}⚙️ Configuring DNS records for DNSTT...${C_RESET}"
- local SERVER_IPV4
- SERVER_IPV4=$(curl -s -4 icanhazip.com)
- if ! _is_valid_ipv4 "$SERVER_IPV4"; then
- echo -e "\n${C_RED}❌ Error: Could not retrieve a valid public IPv4 address from icanhazip.com.${C_RESET}"
- echo -e "${C_YELLOW}ℹ️ Please check your server's network connection and DNS resolver settings.${C_RESET}"
- echo -e " Output received: '$SERVER_IPV4'"
- return 1
- fi
-
- local SERVER_IPV6
- SERVER_IPV6=$(curl -s -6 icanhazip.com --max-time 5)
-
- local RANDOM_STR
- RANDOM_STR=$(head /dev/urandom | tr -dc a-z0-9 | head -c 6)
- NS_SUBDOMAIN="ns-$RANDOM_STR"
- TUNNEL_SUBDOMAIN="tun-$RANDOM_STR"
- NS_DOMAIN="$NS_SUBDOMAIN.$DESEC_DOMAIN"
- TUNNEL_DOMAIN="$TUNNEL_SUBDOMAIN.$DESEC_DOMAIN"
- local API_DATA
- API_DATA=$(printf '[{"subname": "%s", "type": "A", "ttl": 3600, "records": ["%s"]}, {"subname": "%s", "type": "NS", "ttl": 3600, "records": ["%s."]}]' \
- "$NS_SUBDOMAIN" "$SERVER_IPV4" "$TUNNEL_SUBDOMAIN" "$NS_DOMAIN")
- if [[ -n "$SERVER_IPV6" ]]; then
- local aaaa_record
- aaaa_record=$(printf ',{"subname": "%s", "type": "AAAA", "ttl": 3600, "records": ["%s"]}' "$NS_SUBDOMAIN" "$SERVER_IPV6")
- API_DATA="${API_DATA%?}${aaaa_record}]"
- HAS_IPV6="true"
- fi
- local CREATE_RESPONSE
- CREATE_RESPONSE=$(curl -s -w "%{http_code}" -X POST "https://desec.io/api/v1/domains/$DESEC_DOMAIN/rrsets/" \
- -H "Authorization: Token $DESEC_TOKEN" -H "Content-Type: application/json" \
- --data "$API_DATA")
-
- local HTTP_CODE=${CREATE_RESPONSE: -3}
- local RESPONSE_BODY=${CREATE_RESPONSE:0:${#CREATE_RESPONSE}-3}
- if [[ "$HTTP_CODE" -ne 201 ]]; then
- echo -e "${C_RED}❌ Failed to create DNSTT records. API returned HTTP $HTTP_CODE.${C_RESET}"
- echo "Response: $RESPONSE_BODY" | jq
- return 1
- fi
- fi
-
- read -p "👉 Enter MTU value (e.g., 512, 1200) or press [Enter] for default: " mtu_value
- local mtu_string=""
- if [[ "$mtu_value" =~ ^[0-9]+$ ]]; then
- mtu_string=" -mtu $mtu_value"
- echo -e "${C_GREEN}ℹ️ Using MTU: $mtu_value${C_RESET}"
- else
- mtu_value=""
- echo -e "${C_YELLOW}ℹ️ Using default MTU.${C_RESET}"
- fi
- echo -e "\n${C_BLUE}📥 Downloading pre-compiled DNSTT server binary...${C_RESET}"
- local arch
- arch=$(uname -m)
- local binary_url=""
- if [[ "$arch" == "x86_64" ]]; then
- binary_url="https://dnstt.network/dnstt-server-linux-amd64"
- echo -e "${C_BLUE}ℹ️ Detected x86_64 (amd64) architecture.${C_RESET}"
- elif [[ "$arch" == "aarch64" || "$arch" == "arm64" ]]; then
- binary_url="https://dnstt.network/dnstt-server-linux-arm64"
- echo -e "${C_BLUE}ℹ️ Detected ARM64 architecture.${C_RESET}"
- else
- echo -e "\n${C_RED}❌ Unsupported architecture: $arch. Cannot install DNSTT.${C_RESET}"
- return
- fi
-
- curl -sL "$binary_url" -o "$DNSTT_BINARY"
- if [ $? -ne 0 ]; then
- echo -e "\n${C_RED}❌ Failed to download the DNSTT binary.${C_RESET}"
- return
- fi
- chmod +x "$DNSTT_BINARY"
- echo -e "${C_BLUE}🔐 Generating cryptographic keys...${C_RESET}"
- mkdir -p "$DNSTT_KEYS_DIR"
- "$DNSTT_BINARY" -gen-key -privkey-file "$DNSTT_KEYS_DIR/server.key" -pubkey-file "$DNSTT_KEYS_DIR/server.pub"
- if [[ ! -f "$DNSTT_KEYS_DIR/server.key" ]]; then echo -e "${C_RED}❌ Failed to generate DNSTT keys.${C_RESET}"; return; fi
-
- local PUBLIC_KEY
- PUBLIC_KEY=$(cat "$DNSTT_KEYS_DIR/server.pub")
-
- echo -e "\n${C_BLUE}📝 Creating systemd service...${C_RESET}"
- cat > "$DNSTT_SERVICE_FILE" <<-EOF
- [Unit]
- Description=DNSTT (DNS Tunnel) Server for $forward_desc
- After=network.target
- [Service]
- Type=simple
- User=root
- ExecStart=$DNSTT_BINARY -udp :53$mtu_string -privkey-file $DNSTT_KEYS_DIR/server.key $TUNNEL_DOMAIN $FORWARD_TARGET
- Restart=always
- RestartSec=3
- [Install]
- WantedBy=multi-user.target
- EOF
- echo -e "\n${C_BLUE}💾 Saving configuration and starting service...${C_RESET}"
- cat > "$DNSTT_CONFIG_FILE" <<-EOF
- NS_SUBDOMAIN="$NS_SUBDOMAIN"
- TUNNEL_SUBDOMAIN="$TUNNEL_SUBDOMAIN"
- NS_DOMAIN="$NS_DOMAIN"
- TUNNEL_DOMAIN="$TUNNEL_DOMAIN"
- PUBLIC_KEY="$PUBLIC_KEY"
- FORWARD_DESC="$forward_desc"
- DNSTT_RECORDS_MANAGED="$DNSTT_RECORDS_MANAGED"
- HAS_IPV6="$HAS_IPV6"
- MTU_VALUE="$mtu_value"
- EOF
- systemctl daemon-reload
- systemctl enable dnstt.service
- systemctl start dnstt.service
- sleep 2
- if systemctl is-active --quiet dnstt.service; then
- echo -e "\n${C_GREEN}✅ SUCCESS: DNSTT has been installed and started!${C_RESET}"
- show_dnstt_details
- else
- echo -e "\n${C_RED}❌ ERROR: DNSTT service failed to start.${C_RESET}"
- journalctl -u dnstt.service -n 15 --no-pager
- fi
- }
- uninstall_dnstt() {
- echo -e "\n${C_BOLD}${C_PURPLE}--- 🗑️ Uninstalling DNSTT ---${C_RESET}"
- if [ ! -f "$DNSTT_SERVICE_FILE" ]; then
- echo -e "${C_YELLOW}ℹ️ DNSTT does not appear to be installed, skipping.${C_RESET}"
- return
- fi
- local confirm="y"
- if [[ "$UNINSTALL_MODE" != "silent" ]]; then
- read -p "👉 Are you sure you want to uninstall DNSTT? This will delete DNS records if they were auto-generated. (y/n): " confirm
- fi
- if [[ "$confirm" != "y" ]]; then
- echo -e "\n${C_YELLOW}❌ Uninstallation cancelled.${C_RESET}"
- return
- fi
- echo -e "${C_BLUE}🛑 Stopping and disabling DNSTT service...${C_RESET}"
- systemctl stop dnstt.service > /dev/null 2>&1
- systemctl disable dnstt.service > /dev/null 2>&1
- if [ -f "$DNSTT_CONFIG_FILE" ]; then
- source "$DNSTT_CONFIG_FILE"
- if [[ "$DNSTT_RECORDS_MANAGED" == "true" ]]; then
- echo -e "${C_BLUE}🗑️ Removing auto-generated DNS records...${C_RESET}"
- curl -s -X DELETE "https://desec.io/api/v1/domains/$DESEC_DOMAIN/rrsets/$TUNNEL_SUBDOMAIN/NS/" \
- -H "Authorization: Token $DESEC_TOKEN" > /dev/null
- curl -s -X DELETE "https://desec.io/api/v1/domains/$DESEC_DOMAIN/rrsets/$NS_SUBDOMAIN/A/" \
- -H "Authorization: Token $DESEC_TOKEN" > /dev/null
- if [[ "$HAS_IPV6" == "true" ]]; then
- curl -s -X DELETE "https://desec.io/api/v1/domains/$DESEC_DOMAIN/rrsets/$NS_SUBDOMAIN/AAAA/" \
- -H "Authorization: Token $DESEC_TOKEN" > /dev/null
- fi
- echo -e "${C_GREEN}✅ DNS records have been removed.${C_RESET}"
- else
- echo -e "${C_YELLOW}⚠️ DNS records were manually configured. Please delete them from your DNS provider.${C_RESET}"
- fi
- fi
- echo -e "${C_BLUE}🗑️ Removing service files and binaries...${C_RESET}"
- rm -f "$DNSTT_SERVICE_FILE"
- rm -f "$DNSTT_BINARY"
- rm -rf "$DNSTT_KEYS_DIR"
- rm -f "$DNSTT_CONFIG_FILE"
- systemctl daemon-reload
-
- echo -e "${C_YELLOW}ℹ️ Making /etc/resolv.conf writable again...${C_RESET}"
- chattr -i /etc/resolv.conf &>/dev/null
- echo -e "\n${C_GREEN}✅ DNSTT has been successfully uninstalled.${C_RESET}"
- }
- install_falcon_proxy() {
- clear; show_banner
- echo -e "${C_BOLD}${C_PURPLE}--- 🦅 Installing Falcon Proxy (Websockets/Socks) ---${C_RESET}"
-
- if [ -f "$FALCONPROXY_SERVICE_FILE" ]; then
- echo -e "\n${C_YELLOW}ℹ️ Falcon Proxy is already installed.${C_RESET}"
- if [ -f "$FALCONPROXY_CONFIG_FILE" ]; then
- source "$FALCONPROXY_CONFIG_FILE"
- echo -e " It is configured to run on port(s): ${C_YELLOW}$PORTS${C_RESET}"
- echo -e " Installed Version: ${C_YELLOW}${INSTALLED_VERSION:-Unknown}${C_RESET}"
- fi
- read -p "👉 Do you want to reinstall/update? (y/n): " confirm_reinstall
- if [[ "$confirm_reinstall" != "y" ]]; then return; fi
- fi
- echo -e "\n${C_BLUE}🌐 Fetching available versions from GitHub...${C_RESET}"
- local releases_json=$(curl -s "https://api.github.com/repos/firewallfalcons/FirewallFalcon-Manager/releases")
- if [[ -z "$releases_json" || "$releases_json" == "[]" ]]; then
- echo -e "${C_RED}❌ Error: Could not fetch releases. Check internet or API limits.${C_RESET}"
- return
- fi
- # Extract tag names
- mapfile -t versions < <(echo "$releases_json" | jq -r '.[].tag_name')
-
- if [ ${#versions[@]} -eq 0 ]; then
- echo -e "${C_RED}❌ No releases found in the repository.${C_RESET}"
- return
- fi
- echo -e "\n${C_CYAN}Select a version to install:${C_RESET}"
- for i in "${!versions[@]}"; do
- printf " ${C_GREEN}[%2d]${C_RESET} %s\n" "$((i+1))" "${versions[$i]}"
- done
- echo -e " ${C_RED} [ 0]${C_RESET} ↩️ Cancel"
-
- local choice
- while true; do
- read -p "👉 Enter version number [1]: " choice
- choice=${choice:-1}
- if [[ "$choice" == "0" ]]; then return; fi
- if [[ "$choice" =~ ^[0-9]+$ ]] && [ "$choice" -le "${#versions[@]}" ]; then
- SELECTED_VERSION="${versions[$((choice-1))]}"
- break
- else
- echo -e "${C_RED}❌ Invalid selection.${C_RESET}"
- fi
- done
- local ports
- read -p "👉 Enter port(s) for Falcon Proxy (e.g., 8080 or 8080 8888) [8080]: " ports
- ports=${ports:-8080}
- local port_array=($ports)
- for port in "${port_array[@]}"; do
- if ! [[ "$port" =~ ^[0-9]+$ ]] || [ "$port" -lt 1 ] || [ "$port" -gt 65535 ]; then
- echo -e "\n${C_RED}❌ Invalid port number: $port. Aborting.${C_RESET}"
- return
- fi
- check_and_free_ports "$port" || return
- check_and_open_firewall_port "$port" tcp || return
- done
- echo -e "\n${C_GREEN}⚙️ Detecting system architecture...${C_RESET}"
- local arch=$(uname -m)
- local binary_name=""
- if [[ "$arch" == "x86_64" ]]; then
- binary_name="falconproxy"
- echo -e "${C_BLUE}ℹ️ Detected x86_64 (amd64) architecture.${C_RESET}"
- elif [[ "$arch" == "aarch64" || "$arch" == "arm64" ]]; then
- binary_name="falconproxyarm"
- echo -e "${C_BLUE}ℹ️ Detected ARM64 architecture.${C_RESET}"
- else
- echo -e "\n${C_RED}❌ Unsupported architecture: $arch. Cannot install Falcon Proxy.${C_RESET}"
- return
- fi
-
- # Construct download URL based on selected version
- local download_url="https://github.com/firewallfalcons/FirewallFalcon-Manager/releases/download/$SELECTED_VERSION/$binary_name"
- echo -e "\n${C_GREEN}📥 Downloading Falcon Proxy $SELECTED_VERSION ($binary_name)...${C_RESET}"
- wget -q --show-progress -O "$FALCONPROXY_BINARY" "$download_url"
- if [ $? -ne 0 ]; then
- echo -e "\n${C_RED}❌ Failed to download the binary. Please ensure version $SELECTED_VERSION has asset '$binary_name'.${C_RESET}"
- return
- fi
- chmod +x "$FALCONPROXY_BINARY"
- echo -e "\n${C_GREEN}📝 Creating systemd service file...${C_RESET}"
- cat > "$FALCONPROXY_SERVICE_FILE" <<EOF
- [Unit]
- Description=Falcon Proxy ($SELECTED_VERSION)
- After=network.target
- [Service]
- User=root
- Type=simple
- ExecStart=$FALCONPROXY_BINARY -p $ports
- Restart=always
- RestartSec=2s
- [Install]
- WantedBy=default.target
- EOF
- echo -e "\n${C_GREEN}💾 Saving configuration...${C_RESET}"
- cat > "$FALCONPROXY_CONFIG_FILE" <<EOF
- PORTS="$ports"
- INSTALLED_VERSION="$SELECTED_VERSION"
- EOF
- echo -e "\n${C_GREEN}▶️ Enabling and starting Falcon Proxy service...${C_RESET}"
- systemctl daemon-reload
- systemctl enable falconproxy.service
- systemctl restart falconproxy.service
- sleep 2
-
- if systemctl is-active --quiet falconproxy; then
- echo -e "\n${C_GREEN}✅ SUCCESS: Falcon Proxy $SELECTED_VERSION is installed and active.${C_RESET}"
- echo -e " Listening on port(s): ${C_YELLOW}$ports${C_RESET}"
- else
- echo -e "\n${C_RED}❌ ERROR: Falcon Proxy service failed to start.${C_RESET}"
- echo -e "${C_YELLOW}ℹ️ Displaying last 15 lines of the service log for diagnostics:${C_RESET}"
- journalctl -u falconproxy.service -n 15 --no-pager
- fi
- }
- uninstall_falcon_proxy() {
- echo -e "\n${C_BOLD}${C_PURPLE}--- 🗑️ Uninstalling Falcon Proxy ---${C_RESET}"
- if [ ! -f "$FALCONPROXY_SERVICE_FILE" ]; then
- echo -e "${C_YELLOW}ℹ️ Falcon Proxy is not installed, skipping.${C_RESET}"
- return
- fi
- echo -e "${C_GREEN}🛑 Stopping and disabling Falcon Proxy service...${C_RESET}"
- systemctl stop falconproxy.service >/dev/null 2>&1
- systemctl disable falconproxy.service >/dev/null 2>&1
- echo -e "${C_GREEN}🗑️ Removing service file...${C_RESET}"
- rm -f "$FALCONPROXY_SERVICE_FILE"
- systemctl daemon-reload
- echo -e "${C_GREEN}🗑️ Removing binary and config files...${C_RESET}"
- rm -f "$FALCONPROXY_BINARY"
- rm -f "$FALCONPROXY_CONFIG_FILE"
- echo -e "${C_GREEN}✅ Falcon Proxy has been uninstalled successfully.${C_RESET}"
- }
- # --- ZiVPN Installation Logic ---
- install_zivpn() {
- clear; show_banner
- echo -e "${C_BOLD}${C_PURPLE}--- 🚀 Installing ZiVPN (UDP/VPN) ---${C_RESET}"
-
- if [ -f "$ZIVPN_SERVICE_FILE" ]; then
- echo -e "\n${C_YELLOW}ℹ️ ZiVPN is already installed.${C_RESET}"
- return
- fi
- echo -e "\n${C_GREEN}⚙️ Checking system architecture...${C_RESET}"
- local arch=$(uname -m)
- local zivpn_url=""
-
- if [[ "$arch" == "x86_64" ]]; then
- zivpn_url="https://github.com/zahidbd2/udp-zivpn/releases/download/udp-zivpn_1.4.9/udp-zivpn-linux-amd64"
- echo -e "${C_BLUE}ℹ️ Detected AMD64/x86_64 architecture.${C_RESET}"
- elif [[ "$arch" == "aarch64" ]]; then
- zivpn_url="https://github.com/zahidbd2/udp-zivpn/releases/download/udp-zivpn_1.4.9/udp-zivpn-linux-arm64"
- echo -e "${C_BLUE}ℹ️ Detected ARM64 architecture.${C_RESET}"
- elif [[ "$arch" == "armv7l" || "$arch" == "arm" ]]; then
- zivpn_url="https://github.com/zahidbd2/udp-zivpn/releases/download/udp-zivpn_1.4.9/udp-zivpn-linux-arm"
- echo -e "${C_BLUE}ℹ️ Detected ARM architecture.${C_RESET}"
- else
- echo -e "${C_RED}❌ Unsupported architecture: $arch${C_RESET}"
- return
- fi
- echo -e "\n${C_GREEN}📦 Downloading ZiVPN binary...${C_RESET}"
- if ! wget -q --show-progress -O "$ZIVPN_BIN" "$zivpn_url"; then
- echo -e "${C_RED}❌ Download failed. Check internet connection.${C_RESET}"
- return
- fi
- chmod +x "$ZIVPN_BIN"
- echo -e "\n${C_GREEN}⚙️ Configuring ZIVPN...${C_RESET}"
- mkdir -p "$ZIVPN_DIR"
-
- # Generate Certificates
- echo -e "${C_BLUE}🔐 Generating self-signed certificates...${C_RESET}"
- if ! command -v openssl &>/dev/null; then apt-get install -y openssl &>/dev/null; fi
-
- openssl req -new -newkey rsa:4096 -days 365 -nodes -x509 \
- -subj "/C=US/ST=California/L=Los Angeles/O=Example Corp/OU=IT Department/CN=zivpn" \
- -keyout "$ZIVPN_KEY_FILE" -out "$ZIVPN_CERT_FILE" 2>/dev/null
- if [ ! -f "$ZIVPN_CERT_FILE" ]; then
- echo -e "${C_RED}❌ Failed to generate certificates.${C_RESET}"
- return
- fi
- # System Tuning
- echo -e "${C_BLUE}🔧 Tuning system network parameters...${C_RESET}"
- sysctl -w net.core.rmem_max=16777216 >/dev/null
- sysctl -w net.core.wmem_max=16777216 >/dev/null
- # Create Service
- echo -e "${C_BLUE}📝 Creating systemd service file...${C_RESET}"
- cat <<EOF > "$ZIVPN_SERVICE_FILE"
- [Unit]
- Description=zivpn VPN Server
- After=network.target
- [Service]
- Type=simple
- User=root
- WorkingDirectory=$ZIVPN_DIR
- ExecStart=$ZIVPN_BIN server -c $ZIVPN_CONFIG_FILE
- Restart=always
- RestartSec=3
- Environment=ZIVPN_LOG_LEVEL=info
- CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_NET_RAW
- AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_NET_RAW
- NoNewPrivileges=true
- [Install]
- WantedBy=multi-user.target
- EOF
- # Configure Passwords
- echo -e "\n${C_YELLOW}🔑 ZiVPN Password Setup${C_RESET}"
- read -p "👉 Enter passwords separated by commas (e.g., user1,user2) [Default: 'zi']: " input_config
-
- if [ -n "$input_config" ]; then
- IFS=',' read -r -a config_array <<< "$input_config"
- # Ensure array format for JSON
- json_passwords=$(printf '"%s",' "${config_array[@]}")
- json_passwords="[${json_passwords%,}]"
- else
- json_passwords='["zi"]'
- fi
- # Create Config File
- cat <<EOF > "$ZIVPN_CONFIG_FILE"
- {
- "listen": ":5667",
- "cert": "$ZIVPN_CERT_FILE",
- "key": "$ZIVPN_KEY_FILE",
- "obfs":"zivpn",
- "auth": {
- "mode": "passwords",
- "config": $json_passwords
- }
- }
- EOF
- echo -e "\n${C_GREEN}🚀 Starting ZiVPN Service...${C_RESET}"
- systemctl daemon-reload
- systemctl enable zivpn.service
- systemctl start zivpn.service
- # Port Forwarding / Firewall
- echo -e "${C_BLUE}🔥 Configuring Firewall Rules (Redirecting 6000-19999 -> 5667)...${C_RESET}"
-
- # Determine primary interface
- local iface=$(ip -4 route ls | grep default | grep -Po '(?<=dev )(\S+)' | head -1)
-
- if [ -n "$iface" ]; then
- iptables -t nat -A PREROUTING -i "$iface" -p udp --dport 6000:19999 -j DNAT --to-destination :5667
- # Note: IPTables rules are not persistent by default without iptables-persistent package
- else
- echo -e "${C_YELLOW}⚠️ Could not detect default interface for IPTables redirection.${C_RESET}"
- fi
- if command -v ufw &>/dev/null; then
- ufw allow 6000:19999/udp >/dev/null
- ufw allow 5667/udp >/dev/null
- fi
- # Cleanup
- rm -f zi.sh zi2.sh 2>/dev/null
- if systemctl is-active --quiet zivpn.service; then
- echo -e "\n${C_GREEN}✅ ZiVPN Installed Successfully!${C_RESET}"
- echo -e " - UDP Port: 5667 (Direct)"
- echo -e " - UDP Ports: 6000-19999 (Forwarded)"
- else
- echo -e "\n${C_RED}❌ ZiVPN Service failed to start. Check logs: journalctl -u zivpn.service${C_RESET}"
- fi
- }
- uninstall_zivpn() {
- clear; show_banner
- echo -e "${C_BOLD}${C_PURPLE}--- 🗑️ Uninstall ZiVPN ---${C_RESET}"
-
- if [ ! -f "$ZIVPN_SERVICE_FILE" ] && [ ! -f "$ZIVPN_BIN" ]; then
- echo -e "\n${C_YELLOW}ℹ️ ZiVPN does not appear to be installed.${C_RESET}"
- return
- fi
- read -p "👉 Are you sure you want to uninstall ZiVPN? (y/n): " confirm
- if [[ "$confirm" != "y" ]]; then echo -e "${C_YELLOW}Cancelled.${C_RESET}"; return; fi
- echo -e "\n${C_BLUE}🛑 Stopping services...${C_RESET}"
- systemctl stop zivpn.service 2>/dev/null
- systemctl disable zivpn.service 2>/dev/null
-
- echo -e "${C_BLUE}🗑️ Removing files...${C_RESET}"
- rm -f "$ZIVPN_SERVICE_FILE"
- rm -rf "$ZIVPN_DIR"
- rm -f "$ZIVPN_BIN"
-
- systemctl daemon-reload
-
- # Clean cache (from original uninstall script logic)
- echo -e "${C_BLUE}🧹 Cleaning memory cache...${C_RESET}"
- sync; echo 3 > /proc/sys/vm/drop_caches
- echo -e "\n${C_GREEN}✅ ZiVPN Uninstalled Successfully.${C_RESET}"
- }
- purge_nginx() {
- local mode="$1"
- if [[ "$mode" != "silent" ]]; then
- clear; show_banner
- echo -e "${C_BOLD}${C_PURPLE}--- 🔥 Purge Nginx Installation ---${C_RESET}"
- if ! command -v nginx &> /dev/null; then
- echo -e "\n${C_YELLOW}ℹ️ Nginx is not installed. Nothing to do.${C_RESET}"
- return
- fi
- read -p "👉 This will COMPLETELY REMOVE Nginx and all its configuration files. Are you sure? (y/n): " confirm
- if [[ "$confirm" != "y" ]]; then
- echo -e "\n${C_YELLOW}❌ Uninstallation cancelled.${C_RESET}"
- return
- fi
- fi
- echo -e "\n${C_BLUE}🛑 Stopping Nginx service...${C_RESET}"
- systemctl stop nginx >/dev/null 2>&1
- echo -e "\n${C_BLUE}🗑️ Purging Nginx packages...${C_RESET}"
- apt-get purge -y nginx nginx-common >/dev/null 2>&1
- apt-get autoremove -y >/dev/null 2>&1
- echo -e "\n${C_BLUE}🗑️ Removing leftover files...${C_RESET}"
- rm -f /etc/ssl/certs/nginx-selfsigned.pem
- rm -f /etc/ssl/private/nginx-selfsigned.key
- rm -rf /etc/nginx
- rm -f "${NGINX_CONFIG_FILE}.bak"
- rm -f "${NGINX_CONFIG_FILE}.bak.certbot"
- rm -f "${NGINX_CONFIG_FILE}.bak.selfsigned"
- if [[ "$mode" != "silent" ]]; then
- echo -e "\n${C_GREEN}✅ Nginx has been completely purged from the system.${C_RESET}"
- fi
- }
- install_nginx_proxy() {
- clear; show_banner
- echo -e "${C_BOLD}${C_PURPLE}--- 🚀 Installing Nginx Main Proxy (Ports 80 & 443) ---${C_RESET}"
- if command -v nginx &> /dev/null; then
- echo -e "\n${C_YELLOW}⚠️ An existing Nginx installation was found.${C_RESET}"
- read -p "👉 To ensure a clean setup, the existing Nginx will be purged. Continue? (y/n): " confirm_purge
- if [[ "$confirm_purge" != "y" ]]; then
- echo -e "\n${C_RED}❌ Installation cancelled.${C_RESET}"
- return
- fi
- purge_nginx "silent"
- fi
- echo -e "\n${C_BLUE}📦 Installing Nginx package...${C_RESET}"
- apt-get update && apt-get install -y nginx || { echo -e "${C_RED}❌ Failed to install Nginx.${C_RESET}"; return; }
-
- check_and_free_ports "80" "443" || return
- # --- Custom Port Selection ---
- local tls_ports
- read -p "👉 Enter TLS/SSL Port(s) [Default: 443]: " input_tls
- if [[ -z "$input_tls" ]]; then tls_ports="443"; else tls_ports="$input_tls"; fi
- local http_ports
- read -p "👉 Enter HTTP/Non-TLS Port(s) [Default: 80]: " input_http
- if [[ -z "$input_http" ]]; then http_ports="80"; else http_ports="$input_http"; fi
- # Convert to arrays
- read -a tls_ports_array <<< "$tls_ports"
- read -a http_ports_array <<< "$http_ports"
-
- # Process Ports: Free and Open
- for port in "${tls_ports_array[@]}" "${http_ports_array[@]}"; do
- if ! [[ "$port" =~ ^[0-9]+$ ]]; then echo -e "${C_RED}❌ Invalid port: $port${C_RESET}"; return; fi
- check_and_free_ports "$port" || return
- check_and_open_firewall_port "$port" tcp || return
- done
-
- echo -e "\n${C_GREEN}🔐 Generating self-signed SSL certificate for Nginx...${C_RESET}"
- local SSL_CERT="/etc/ssl/certs/nginx-selfsigned.pem"
- local SSL_KEY="/etc/ssl/private/nginx-selfsigned.key"
- mkdir -p /etc/ssl/certs /etc/ssl/private
- openssl req -x509 -nodes -days 3650 -newkey rsa:2048 \
- -keyout "$SSL_KEY" \
- -out "$SSL_CERT" \
- -subj "/CN=firewallfalcon.proxy" >/dev/null 2>&1 || { echo -e "${C_RED}❌ Failed to generate SSL certificate.${C_RESET}"; return; }
- echo -e "\n${C_GREEN}📝 Applying Nginx reverse proxy configuration...${C_RESET}"
- mv "$NGINX_CONFIG_FILE" "${NGINX_CONFIG_FILE}.bak" 2>/dev/null
-
- # --- Generate Listen Directives ---
- local listen_block=""
- for port in "${http_ports_array[@]}"; do
- listen_block="${listen_block} listen $port;\n listen [::]:$port;\n"
- done
- for port in "${tls_ports_array[@]}"; do
- listen_block="${listen_block} listen $port ssl http2;\n listen [::]:$port ssl http2;\n"
- done
- cat > "$NGINX_CONFIG_FILE" <<EOF
- server {
- server_tokens off;
- server_name _;
-
- $(echo -e "$listen_block")
- ssl_certificate /etc/ssl/certs/nginx-selfsigned.pem;
- ssl_certificate_key /etc/ssl/private/nginx-selfsigned.key;
- ssl_protocols TLSv1.2 TLSv1.3;
- ssl_ciphers HIGH:!aNULL:!eNULL:!MD5:!DES:!RC4:!ADH!SSLv3:!EXP!PSK!DSS;
- resolver 8.8.8.8;
-
- location ~ ^/(?<fwdport>\d+)/(?<fwdpath>.*)$ {
- client_max_body_size 0;
- client_body_timeout 1d;
- grpc_read_timeout 1d;
- grpc_socket_keepalive on;
- proxy_read_timeout 1d;
- proxy_http_version 1.1;
- proxy_buffering off;
- proxy_request_buffering off;
- proxy_socket_keepalive on;
- proxy_set_header Upgrade \$http_upgrade;
- proxy_set_header Connection "upgrade";
- proxy_set_header Host \$host;
- proxy_set_header X-Real-IP \$remote_addr;
- proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
- if (\$content_type ~* "GRPC") { grpc_pass grpc://127.0.0.1:\$fwdport\$is_args\$args; break; }
- proxy_pass http://127.0.0.1:\$fwdport\$is_args\$args;
- break;
- }
-
- location / {
- proxy_read_timeout 3600s;
- proxy_buffering off;
- proxy_request_buffering off;
- proxy_http_version 1.1;
- proxy_socket_keepalive on;
- tcp_nodelay on;
- tcp_nopush off;
- proxy_pass http://127.0.0.1:8080;
- proxy_set_header Upgrade \$http_upgrade;
- proxy_set_header Connection "upgrade";
- proxy_set_header Host \$host;
- proxy_set_header X-Real-IP \$remote_addr;
- proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
- }
- }
- EOF
- echo -e "\n${C_GREEN}▶️ Restarting Nginx service...${C_RESET}"
- systemctl restart nginx
- sleep 2
- if systemctl is-active --quiet nginx; then
- echo -e "\n${C_GREEN}✅ SUCCESS: Nginx Reverse Proxy is active.${C_RESET}"
- echo -e " - TLS Ports: ${C_YELLOW}${tls_ports}${C_RESET}"
- echo -e " - HTTP Ports: ${C_YELLOW}${http_ports}${C_RESET}"
-
- # Save ports for future reference
- echo "TLS_PORTS=\"$tls_ports\"" > "$NGINX_PORTS_FILE"
- echo "HTTP_PORTS=\"$http_ports\"" >> "$NGINX_PORTS_FILE"
- else
- echo -e "\n${C_RED}❌ ERROR: Nginx service failed to start.${C_RESET}"
- echo -e "${C_YELLOW}ℹ️ Displaying Nginx status for diagnostics:${C_RESET}"
- systemctl status nginx --no-pager
- echo -e "${C_YELLOW}🔄 Restoring previous Nginx config...${C_RESET}"
- mv "${NGINX_CONFIG_FILE}.bak" "$NGINX_CONFIG_FILE" 2>/dev/null
- fi
- }
- _install_certbot() {
- if command -v certbot &> /dev/null; then
- echo -e "${C_GREEN}✅ Certbot is already installed.${C_RESET}"
- return 0
- fi
- echo -e "${C_YELLOW}⚠️ Certbot (for SSL) is not found.${C_RESET}"
- read -p "👉 Do you want to install Certbot now? (y/n): " confirm_install
- if [[ "$confirm_install" != "y" ]]; then
- echo -e "${C_RED}❌ Installation skipped. Cannot proceed.${C_RESET}"
- return 1
- fi
- echo -e "${C_BLUE}📦 Installing Certbot...${C_RESET}"
- apt-get update > /dev/null 2>&1
- apt-get install -y certbot || {
- echo -e "${C_RED}❌ Failed to install Certbot.${C_RESET}"
- return 1
- }
- echo -e "${C_GREEN}✅ Certbot installed successfully.${C_RESET}"
- return 0
- }
- request_certbot_ssl() {
- clear; show_banner
- echo -e "${C_BOLD}${C_PURPLE}--- 🔒 Request Let's Encrypt SSL (Certbot) ---${C_RESET}"
- if ! systemctl is-active --quiet nginx; then
- echo -e "\n${C_RED}❌ Nginx is not running. Please ensure Nginx is installed and active.${C_RESET}"
- return
- fi
- _install_certbot || return
-
- echo
- read -p "👉 Enter your domain name (e.g., vps.example.com): " domain_name
- if [[ -z "$domain_name" ]]; then
- echo -e "\n${C_RED}❌ Domain name cannot be empty. Aborting.${C_RESET}"
- return
- fi
-
- read -p "👉 Enter your email address (for Let's Encrypt): " email
- if [[ -z "$email" ]]; then
- echo -e "\n${C_RED}❌ Email address cannot be empty. Aborting.${C_RESET}"
- return
- fi
-
- echo -e "\n${C_BLUE}🛑 Stopping Nginx temporarily for validation...${C_RESET}"
- systemctl stop nginx
- sleep 2
- if ss -lntp | grep -q ":80\s"; then
- echo -e "${C_RED}❌ Failed to free port 80, another process might be using it. Aborting.${C_RESET}"
- systemctl start nginx
- return
- fi
- echo -e "\n${C_BLUE}🚀 Requesting certificate for ${C_YELLOW}$domain_name...${C_RESET}"
- certbot certonly --standalone -d "$domain_name" --non-interactive --agree-tos -m "$email"
-
- if [ $? -ne 0 ]; then
- echo -e "\n${C_RED}❌ Certbot failed to obtain a certificate.${C_RESET}"
- echo -e "${C_YELLOW}ℹ️ Please check your domain's DNS 'A' record points to this server's IP.${C_RESET}"
- systemctl start nginx
- return
- fi
- local SSL_CERT_LIVE="/etc/letsencrypt/live/$domain_name/fullchain.pem"
- local SSL_KEY_LIVE="/etc/letsencrypt/live/$domain_name/privkey.pem"
- if [ ! -f "$SSL_CERT_LIVE" ] || [ ! -f "$SSL_KEY_LIVE" ]; then
- echo -e "\n${C_RED}❌ Certbot succeeded, but cert files not found at expected location.${C_RESET}"
- systemctl start nginx
- return
- fi
- echo -e "\n${C_GREEN}✅ Certificate obtained successfully!${C_RESET}"
- echo -e "${C_BLUE}📝 Updating Nginx configuration...${C_RESET}"
- cp "$NGINX_CONFIG_FILE" "${NGINX_CONFIG_FILE}.bak.selfsigned"
-
- sed -i "s|server_name _;|server_name $domain_name;|" "$NGINX_CONFIG_FILE"
- sed -i "s|ssl_certificate /etc/ssl/certs/nginx-selfsigned.pem;|ssl_certificate $SSL_CERT_LIVE;|" "$NGINX_CONFIG_FILE"
- sed -i "s|ssl_certificate_key /etc/ssl/private/nginx-selfsigned.key;|ssl_certificate_key $SSL_KEY_LIVE;|" "$NGINX_CONFIG_FILE"
- echo -e "\n${C_BLUE}▶️ Restarting Nginx with new certificate...${C_RESET}"
- systemctl start nginx
- sleep 2
-
- if systemctl is-active --quiet nginx; then
- echo -e "\n${C_GREEN}✅ SUCCESS: Nginx is active with your new Let's Encrypt certificate!${C_RESET}"
- else
- echo -e "\n${C_RED}❌ ERROR: Nginx failed to start with the new certificate.${C_RESET}"
- echo -e "${C_YELLOW}🔄 Restoring self-signed certificate config...${C_RESET}"
- mv "${NGINX_CONFIG_FILE}.bak.selfsigned" "$NGINX_CONFIG_FILE"
- systemctl restart nginx
- fi
- }
- nginx_proxy_menu() {
- clear; show_banner
- echo -e "${C_BOLD}${C_PURPLE}--- 🌐 Nginx Main Proxy Management ---${C_RESET}"
-
- local active_status="${C_STATUS_I}Inactive${C_RESET}"
- if systemctl is-active --quiet nginx; then
- active_status="${C_STATUS_A}Active${C_RESET}"
- fi
- # Retrieve Ports Info
- local ports_info=""
- if [ -f "$NGINX_PORTS_FILE" ]; then
- source "$NGINX_PORTS_FILE"
- ports_info="\n ${C_DIM}TLS: $TLS_PORTS | HTTP: $HTTP_PORTS${C_RESET}"
- fi
- echo -e "\n${C_WHITE}Current Status: ${active_status}${ports_info}"
-
- echo -e "\n${C_BOLD}Select an action:${C_RESET}\n"
-
- if systemctl is-active --quiet nginx; then
- printf " ${C_CHOICE}[ 1]${C_RESET} %-40s\n" "🛑 Stop Nginx Service"
- printf " ${C_CHOICE}[ 2]${C_RESET} %-40s\n" "🔄 Restart Nginx Service"
- printf " ${C_CHOICE}[ 3]${C_RESET} %-40s\n" "⚙️ Re-install/Re-configure (Change Ports)"
- printf " ${C_CHOICE}[ 4]${C_RESET} %-40s\n" "🔒 Request/Renew SSL (Certbot)"
- printf " ${C_CHOICE}[ 5]${C_RESET} %-40s\n" "🔥 Uninstall/Purge Nginx"
- else
- printf " ${C_CHOICE}[ 1]${C_RESET} %-40s\n" "▶️ Start Nginx Service"
- printf " ${C_CHOICE}[ 3]${C_RESET} %-40s\n" "⚙️ Install/Configure Nginx"
- printf " ${C_CHOICE}[ 5]${C_RESET} %-40s\n" "🔥 Uninstall/Purge Nginx"
- fi
- echo -e "\n ${C_WARN}[ 0]${C_RESET} ↩️ Return to previous menu"
- echo
- read -p "👉 Enter your choice: " choice
-
- case $choice in
- 1)
- if systemctl is-active --quiet nginx; then
- echo -e "\n${C_BLUE}🛑 Stopping Nginx...${C_RESET}"
- systemctl stop nginx
- echo -e "${C_GREEN}✅ Nginx stopped.${C_RESET}"
- else
- echo -e "\n${C_BLUE}▶️ Starting Nginx...${C_RESET}"
- systemctl start nginx
- if systemctl is-active --quiet nginx; then echo -e "${C_GREEN}✅ Nginx Started.${C_RESET}"; else echo -e "${C_RED}❌ Failed to start.${C_RESET}"; fi
- fi
- press_enter
- ;;
- 2)
- echo -e "\n${C_BLUE}🔄 Restarting Nginx...${C_RESET}"
- systemctl restart nginx
- press_enter
- ;;
- 3)
- install_nginx_proxy; press_enter
- ;;
- 4)
- request_certbot_ssl; press_enter
- ;;
- 5)
- purge_nginx; press_enter
- ;;
- 0) return ;;
- *) invalid_option ;;
- esac
- }
- install_xui_panel() {
- clear; show_banner
- echo -e "${C_BOLD}${C_PURPLE}--- 🚀 Install X-UI Panel ---${C_RESET}"
- echo -e "\nThis will download and run the official installation script for X-UI."
- echo -e "Choose an installation option:\n"
- echo -e "Choose an installation option:\n"
- printf " ${C_GREEN}[ 1]${C_RESET} %-40s\n" "Install the latest version of X-UI"
- printf " ${C_GREEN}[ 2]${C_RESET} %-40s\n" "Install a specific version of X-UI"
- echo -e "\n ${C_RED}[ 0]${C_RESET} ❌ Cancel Installation"
- echo
- read -p "👉 Select an option: " choice
- case $choice in
- 1)
- echo -e "\n${C_BLUE}⚙️ Installing the latest version...${C_RESET}"
- bash <(curl -Ls https://raw.githubusercontent.com/alireza0/x-ui/master/install.sh)
- ;;
- 2)
- read -p "👉 Enter the version to install (e.g., 1.8.0): " version
- if [[ -z "$version" ]]; then
- echo -e "\n${C_RED}❌ Version number cannot be empty.${C_RESET}"
- return
- fi
- echo -e "\n${C_BLUE}⚙️ Installing version ${C_YELLOW}$version...${C_RESET}"
- VERSION=$version bash <(curl -Ls "https://raw.githubusercontent.com/alireza0/x-ui/$version/install.sh") "$version"
- ;;
- 0)
- echo -e "\n${C_YELLOW}❌ Installation cancelled.${C_RESET}"
- ;;
- *)
- echo -e "\n${C_RED}❌ Invalid option.${C_RESET}"
- ;;
- esac
- }
- uninstall_xui_panel() {
- clear; show_banner
- echo -e "${C_BOLD}${C_PURPLE}--- 🗑️ Uninstall X-UI Panel ---${C_RESET}"
- if ! command -v x-ui &> /dev/null; then
- echo -e "\n${C_YELLOW}ℹ️ X-UI does not appear to be installed.${C_RESET}"
- return
- fi
- read -p "👉 Are you sure you want to thoroughly uninstall X-UI? (y/n): " confirm
- if [[ "$confirm" == "y" ]]; then
- echo -e "\n${C_BLUE}⚙️ Running the default X-UI uninstaller first...${C_RESET}"
- x-ui uninstall >/dev/null 2>&1
- echo -e "\n${C_BLUE}🧹 Performing a full cleanup to ensure complete removal...${C_RESET}"
- echo " - Stopping and disabling x-ui service..."
- systemctl stop x-ui >/dev/null 2>&1
- systemctl disable x-ui >/dev/null 2>&1
- echo " - Removing x-ui files and directories..."
- rm -f /etc/systemd/system/x-ui.service
- rm -f /usr/local/bin/x-ui
- rm -rf /usr/local/x-ui/
- rm -rf /etc/x-ui/
- echo " - Reloading systemd daemon..."
- systemctl daemon-reload
- echo -e "\n${C_GREEN}✅ X-UI has been thoroughly uninstalled.${C_RESET}"
- else
- echo -e "\n${C_YELLOW}❌ Uninstallation cancelled.${C_RESET}"
- fi
- }
- show_banner() {
- local os_name=$(grep -oP 'PRETTY_NAME="\K[^"]+' /etc/os-release || echo "Linux")
- local up_time=$(uptime -p | sed 's/up //')
- local ram_usage=$(free -m | awk '/^Mem:/{printf "%.2f", $3*100/$2}')
-
- # Efficient CPU Load check (Load Average)
- local cpu_load=$(cat /proc/loadavg | awk '{print $1}')
-
- local online_users=0
- # Optimize online user count: Get total active sshd procs roughly (may overcount if multiple procs per session but faster)
- # Or just keep it if DB is small. Let's trust pgrep is okay for menu load.
- if [[ -s "$DB_FILE" ]]; then
- while IFS=: read -r user pass expiry limit; do
- # Use pgrep -c for speed
- local count=$(pgrep -c -u "$user" sshd)
- online_users=$((online_users + count))
- done < "$DB_FILE"
- fi
-
- local total_users=0
- if [[ -s "$DB_FILE" ]]; then total_users=$(grep -c . "$DB_FILE"); fi
-
- clear
- echo
- echo -e "${C_TITLE} FirewallFalcon Manager ${C_RESET}${C_DIM}| v4.0.0 Premium Edition${C_RESET}"
- echo -e "${C_BLUE} ─────────────────────────────────────────────────────────${C_RESET}"
- printf " ${C_GRAY}%-10s${C_RESET} %-20s ${C_GRAY}|${C_RESET} %s\n" "OS" "$os_name" "Uptime: $up_time"
- printf " ${C_GRAY}%-10s${C_RESET} %-20s ${C_GRAY}|${C_RESET} %s\n" "Memory" "${ram_usage}% Used" "Online Sessions: ${C_WHITE}${online_users}${C_RESET}"
- printf " ${C_GRAY}%-10s${C_RESET} %-20s ${C_GRAY}|${C_RESET} %s\n" "Users" "${total_users} Managed Accounts" "Sys Load (1m): ${C_GREEN}${cpu_load}${C_RESET}"
- echo -e "${C_BLUE} ─────────────────────────────────────────────────────────${C_RESET}"
- }
- protocol_menu() {
- while true; do
- show_banner
- local badvpn_status; if systemctl is-active --quiet badvpn; then badvpn_status="${C_STATUS_A}(Active)${C_RESET}"; else badvpn_status="${C_STATUS_I}(Inactive)${C_RESET}"; fi
- local udp_custom_status; if systemctl is-active --quiet udp-custom; then udp_custom_status="${C_STATUS_A}(Active)${C_RESET}"; else udp_custom_status="${C_STATUS_I}(Inactive)${C_RESET}"; fi
- local zivpn_status; if systemctl is-active --quiet zivpn.service; then zivpn_status="${C_STATUS_A}(Active)${C_RESET}"; else zivpn_status="${C_STATUS_I}(Inactive)${C_RESET}"; fi
-
- local ssl_tunnel_text="SSL Tunnel (Port 444)"
- local ssl_tunnel_status="${C_STATUS_I}(Inactive)${C_RESET}"
- if systemctl is-active --quiet haproxy; then
- local active_port
- active_port=$(grep -oP 'bind \*:(\d+)' "$HAPROXY_CONFIG" 2>/dev/null | awk -F: '{print $2}')
- if [[ -n "$active_port" ]]; then
- ssl_tunnel_text="SSL Tunnel (Port $active_port)"
- fi
- ssl_tunnel_status="${C_STATUS_A}(Active)${C_RESET}"
- fi
-
- local dnstt_status; if systemctl is-active --quiet dnstt.service; then dnstt_status="${C_STATUS_A}(Active)${C_RESET}"; else dnstt_status="${C_STATUS_I}(Inactive)${C_RESET}"; fi
-
- local falconproxy_status="${C_STATUS_I}(Inactive)${C_RESET}"
- local falconproxy_ports=""
- if systemctl is-active --quiet falconproxy; then
- if [ -f "$FALCONPROXY_CONFIG_FILE" ]; then source "$FALCONPROXY_CONFIG_FILE"; fi
- falconproxy_ports=" ($PORTS)"
- falconproxy_status="${C_STATUS_A}(Active - ${INSTALLED_VERSION:-latest})${C_RESET}"
- fi
- local nginx_status; if systemctl is-active --quiet nginx; then nginx_status="${C_STATUS_A}(Active)${C_RESET}"; else nginx_status="${C_STATUS_I}(Inactive)${C_RESET}"; fi
- local xui_status; if command -v x-ui &> /dev/null; then xui_status="${C_STATUS_A}(Installed)${C_RESET}"; else xui_status="${C_STATUS_I}(Not Installed)${C_RESET}"; fi
-
- echo -e "\n ${C_TITLE}══════════════[ ${C_BOLD}🔌 PROTOCOL & PANEL MANAGEMENT ${C_RESET}${C_TITLE}]══════════════${C_RESET}"
- echo -e " ${C_ACCENT}--- TUNNELLING PROTOCOLS---${C_RESET}"
- printf " ${C_CHOICE}[ 1]${C_RESET} %-45s %s\n" "🚀 Install badvpn (UDP 7300)" "$badvpn_status"
- printf " ${C_CHOICE}[ 2]${C_RESET} %-45s\n" "🗑️ Uninstall badvpn"
- printf " ${C_CHOICE}[ 3]${C_RESET} %-45s %s\n" "🚀 Install udp-custom" "$udp_custom_status"
- printf " ${C_CHOICE}[ 4]${C_RESET} %-45s\n" "🗑️ Uninstall udp-custom"
- printf " ${C_CHOICE}[ 5]${C_RESET} %-45s %s\n" "🔒 Install ${ssl_tunnel_text}" "$ssl_tunnel_status"
- printf " ${C_CHOICE}[ 6]${C_RESET} %-45s\n" "🗑️ Uninstall SSL Tunnel"
- printf " ${C_CHOICE}[ 7]${C_RESET} %-45s %s\n" "📡 Install/View DNSTT (Port 53)" "$dnstt_status"
- printf " ${C_CHOICE}[ 8]${C_RESET} %-45s\n" "🗑️ Uninstall DNSTT"
- printf " ${C_CHOICE}[ 9]${C_RESET} %-45s %s\n" "🦅 Install Falcon Proxy (Select Version)" "$falconproxy_status"
- printf " ${C_CHOICE}[10]${C_RESET} %-45s\n" "🗑️ Uninstall Falcon Proxy"
- printf " ${C_CHOICE}[11]${C_RESET} %-45s %s\n" "🌐 Install/Manage Nginx Proxy (80/443)" "$nginx_status"
- printf " ${C_CHOICE}[16]${C_RESET} %-45s %s\n" "🛡️ Install ZiVPN (UDP 5667)" "$zivpn_status"
- printf " ${C_CHOICE}[17]${C_RESET} %-45s\n" "🗑️ Uninstall ZiVPN"
-
- echo -e " ${C_ACCENT}--- 💻 MANAGEMENT PANELS ---${C_RESET}"
- printf " ${C_CHOICE}[12]${C_RESET} %-45s %s\n" "💻 Install X-UI Panel" "$xui_status"
- printf " ${C_CHOICE}[13]${C_RESET} %-45s\n" "🗑️ Uninstall X-UI Panel"
-
- echo -e " ${C_DIM}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~${C_RESET}"
- echo -e " ${C_WARN}[ 0]${C_RESET} ↩️ Return to Main Menu"
- echo
- read -p "$(echo -e ${C_PROMPT}"👉 Select an option: "${C_RESET})" choice
- case $choice in
- 1) install_badvpn; press_enter ;; 2) uninstall_badvpn; press_enter ;;
- 3) install_udp_custom; press_enter ;; 4) uninstall_udp_custom; press_enter ;;
- 5) install_ssl_tunnel; press_enter ;; 6) uninstall_ssl_tunnel; press_enter ;;
- 7) install_dnstt; press_enter ;; 8) uninstall_dnstt; press_enter ;;
- 9) install_falcon_proxy; press_enter ;; 10) uninstall_falcon_proxy; press_enter ;;
- 11) nginx_proxy_menu ;;
- 12) install_xui_panel; press_enter ;; 13) uninstall_xui_panel; press_enter ;;
- 16) install_zivpn; press_enter ;; 17) uninstall_zivpn; press_enter ;;
- 0) return ;;
- *) invalid_option ;;
- esac
- done
- }
- install_dt_proxy_full() {
- clear; show_banner
- echo -e "${C_BOLD}${C_PURPLE}--- 🚀 Full DT Tunnel Installation ---${C_RESET}"
- if [ -f "/usr/local/bin/main" ]; then
- echo -e "\n${C_YELLOW}ℹ️ DT Proxy appears to be already installed.${C_RESET}"
- echo -e "If you wish to reinstall, please uninstall it first."
- return
- fi
- echo -e "\n${C_BLUE}--- Step 1 of 2: Installing DT Tunnel Mod ---${C_RESET}"
- echo "This will download and run the prerequisite mod installer."
- read -p "👉 Press [Enter] to continue or [Ctrl+C] to cancel."
- if curl -sL https://raw.githubusercontent.com/firewallfalcons/ProxyMods/main/install.sh | bash; then
- echo -e "\n${C_GREEN}✅ DT Tunnel Mod installed successfully.${C_RESET}"
- else
- echo -e "\n${C_RED}❌ ERROR: DT Tunnel Mod installation failed. Aborting.${C_RESET}"
- return
- fi
- echo -e "\n${C_BLUE}--- Step 2 of 2: Installing DT Tunnel Proxy ---${C_RESET}"
- echo "This will download and run the main DT Tunnel proxy installer."
- read -p "👉 Press [Enter] to continue or [Ctrl+C] to cancel."
- if bash <(curl -fsSL https://raw.githubusercontent.com/firewallfalcons/ProxyDT-Go-Releases/main/install.sh); then
- echo -e "\n${C_GREEN}✅ DT Tunnel Proxy installed successfully.${C_RESET}"
- echo -e "You can now manage it from the DT Proxy Management menu."
- else
- echo -e "\n${C_RED}❌ ERROR: DT Tunnel Proxy installation failed.${C_RESET}"
- fi
- }
- launch_dt_proxy_menu() {
- clear; show_banner
- if [ -f "/usr/local/bin/main" ]; then
- echo -e "\n${C_GREEN}✅ DT Proxy is installed. Launching its management panel...${C_RESET}"
- sleep 2
- /usr/local/bin/main
- else
- echo -e "\n${C_RED}❌ DT Proxy is not installed. Please use the install option first.${C_RESET}"
- fi
- }
- uninstall_dt_proxy_full() {
- clear; show_banner
- echo -e "${C_BOLD}${C_PURPLE}--- 🗑️ Uninstall DT Proxy (Mod + Proxy) ---${C_RESET}"
- if [ ! -f "/usr/local/bin/proxy" ] && [ ! -f "/usr/local/bin/main" ]; then
- echo -e "\n${C_YELLOW}ℹ️ DT Proxy is not installed. Nothing to do.${C_RESET}"
- return
- fi
- read -p "👉 Are you sure you want to PERMANENTLY delete DT Proxy and all its services? (y/n): " confirm
- if [[ "$confirm" != "y" ]]; then
- echo -e "\n${C_YELLOW}❌ Uninstallation cancelled.${C_RESET}"
- return
- fi
- echo -e "\n${C_BLUE}🛑 Stopping and disabling all DT Proxy services...${C_RESET}"
- systemctl list-units --type=service --state=running | grep 'proxy-' | awk '{print $1}' | xargs -r systemctl stop
- systemctl list-unit-files --type=service | grep 'proxy-' | awk '{print $1}' | xargs -r systemctl disable
- echo -e "\n${C_BLUE}🗑️ Removing files...${C_RESET}"
- rm -f /etc/systemd/system/proxy-*.service
- systemctl daemon-reload
- rm -f /usr/local/bin/proxy
- rm -f /usr/local/bin/main
- rm -f "$HOME/.proxy_token"
- rm -f /var/log/proxy-*.log
- rm -f /usr/local/bin/install_mod
- echo -e "\n${C_GREEN}✅ DT Proxy has been successfully uninstalled.${C_RESET}"
- }
- dt_proxy_menu() {
- while true; do
- show_banner
- local dt_proxy_status
- if [ -f "/usr/local/bin/main" ] && [ -f "/usr/local/bin/proxy" ]; then
- dt_proxy_status="${C_STATUS_A}(Installed)${C_RESET}"
- else
- dt_proxy_status="${C_STATUS_I}(Not Installed)${C_RESET}"
- fi
- echo -e "\n ${C_TITLE}═════════════════[ ${C_BOLD}🚀 DT Proxy Management ${dt_proxy_status} ${C_RESET}${C_TITLE}]═════════════════${C_RESET}"
- printf " ${C_CHOICE}[ 1]${C_RESET} %-45s\n" "🚀 Install DT Tunnel (Mod + Proxy)"
- printf " ${C_CHOICE}[ 2]${C_RESET} %-45s\n" "▶️ Launch DT Tunnel Management Menu"
- printf " ${C_DANGER}[ 3]${C_RESET} %-45s\n" "🗑️ Uninstall DT Tunnel (Mod + Proxy)"
- echo -e " ${C_DIM}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~${C_RESET}"
- echo -e " ${C_WARN}[ 0]${C_RESET} ↩️ Return to Main Menu"
- echo
- read -p "$(echo -e ${C_PROMPT}"👉 Select an option: "${C_RESET})" choice
- case $choice in
- 1) install_dt_proxy_full; press_enter ;;
- 2) launch_dt_proxy_menu; press_enter ;;
- 3) uninstall_dt_proxy_full; press_enter ;;
- 0) return ;;
- *) invalid_option ;;
- esac
- done
- }
- uninstall_script() {
- clear; show_banner
- echo -e "${C_RED}=====================================================${C_RESET}"
- echo -e "${C_RED} 🔥 DANGER: UNINSTALL SCRIPT & ALL DATA 🔥 ${C_RESET}"
- echo -e "${C_RED}=====================================================${C_RESET}"
- echo -e "${C_YELLOW}This will PERMANENTLY remove this script and all its components, including:"
- echo -e " - The main command ($(command -v menu))"
- echo -e " - All configuration and user data ($DB_DIR)"
- echo -e " - The active limiter service ($LIMITER_SERVICE)"
- echo -e " - All installed services (badvpn, udp-custom, SSL Tunnel, Nginx, DNSTT, FalconProxy)"
- echo -e "\n${C_RED}This action is irreversible.${C_RESET}"
- echo ""
- read -p "👉 Type 'yes' to confirm and proceed with uninstallation: " confirm
- if [[ "$confirm" != "yes" ]]; then
- echo -e "\n${C_GREEN}✅ Uninstallation cancelled.${C_RESET}"
- return
- fi
- export UNINSTALL_MODE="silent"
- echo -e "\n${C_BLUE}--- 💥 Starting Uninstallation 💥 ---${C_RESET}"
-
- echo -e "\n${C_BLUE}🗑️ Removing active limiter service...${C_RESET}"
- systemctl stop firewallfalcon-limiter &>/dev/null
- systemctl disable firewallfalcon-limiter &>/dev/null
- rm -f "$LIMITER_SERVICE"
- rm -f "$LIMITER_SCRIPT"
-
- echo -e "\n${C_BLUE}🗑️ Removing bandwidth monitoring service...${C_RESET}"
- systemctl stop firewallfalcon-bandwidth &>/dev/null
- systemctl disable firewallfalcon-bandwidth &>/dev/null
- rm -f "$BANDWIDTH_SERVICE"
- rm -f "$BANDWIDTH_SCRIPT"
- rm -f "$TRIAL_CLEANUP_SCRIPT"
-
- echo -e "\n${C_BLUE}\ud83d\uddd1\ufe0f Removing SSH login banner...${C_RESET}"
- rm -f "$LOGIN_INFO_SCRIPT"
- rm -f "$SSHD_FF_CONFIG"
- systemctl reload sshd 2>/dev/null || systemctl reload ssh 2>/dev/null
-
- chattr -i /etc/resolv.conf &>/dev/null
- purge_nginx "silent"
- uninstall_dnstt
- uninstall_badvpn
- uninstall_udp_custom
- uninstall_ssl_tunnel
- uninstall_falcon_proxy
- uninstall_zivpn
- delete_dns_record
-
- echo -e "\n${C_BLUE}🔄 Reloading systemd daemon...${C_RESET}"
- systemctl daemon-reload
-
- echo -e "\n${C_BLUE}🗑️ Removing script and configuration files...${C_RESET}"
- rm -rf "$BADVPN_BUILD_DIR"
- rm -rf "$UDP_CUSTOM_DIR"
- rm -rf "$DB_DIR"
- rm -f "$(command -v menu)"
-
- echo -e "\n${C_GREEN}=============================================${C_RESET}"
- echo -e "${C_GREEN} Script has been successfully uninstalled. ${C_RESET}"
- echo -e "${C_GREEN}=============================================${C_RESET}"
- echo -e "\nAll associated files and services have been removed."
- echo "The 'menu' command will no longer work."
- exit 0
- }
- # --- NEW FEATURES ---
- create_trial_account() {
- clear; show_banner
- echo -e "${C_BOLD}${C_PURPLE}--- ⏱️ Create Trial/Test Account ---${C_RESET}"
-
- # Ensure 'at' daemon is available
- if ! command -v at &>/dev/null; then
- echo -e "${C_YELLOW}⚠️ 'at' command not found. Installing...${C_RESET}"
- apt-get update > /dev/null 2>&1 && apt-get install -y at || {
- echo -e "${C_RED}❌ Failed to install 'at'. Cannot schedule auto-expiry.${C_RESET}"
- return
- }
- systemctl enable atd &>/dev/null
- systemctl start atd &>/dev/null
- fi
-
- # Ensure atd is running
- if ! systemctl is-active --quiet atd; then
- systemctl start atd &>/dev/null
- fi
-
- echo -e "\n${C_CYAN}Select trial duration:${C_RESET}\n"
- printf " ${C_GREEN}[ 1]${C_RESET} ⏱️ 1 Hour\n"
- printf " ${C_GREEN}[ 2]${C_RESET} ⏱️ 2 Hours\n"
- printf " ${C_GREEN}[ 3]${C_RESET} ⏱️ 3 Hours\n"
- printf " ${C_GREEN}[ 4]${C_RESET} ⏱️ 6 Hours\n"
- printf " ${C_GREEN}[ 5]${C_RESET} ⏱️ 12 Hours\n"
- printf " ${C_GREEN}[ 6]${C_RESET} 📅 1 Day\n"
- printf " ${C_GREEN}[ 7]${C_RESET} 📅 3 Days\n"
- printf " ${C_GREEN}[ 8]${C_RESET} ⚙️ Custom (enter hours)\n"
- echo -e "\n ${C_RED}[ 0]${C_RESET} ↩️ Cancel"
- echo
- read -p "👉 Select duration: " dur_choice
-
- local duration_hours=0
- local duration_label=""
- case $dur_choice in
- 1) duration_hours=1; duration_label="1 Hour" ;;
- 2) duration_hours=2; duration_label="2 Hours" ;;
- 3) duration_hours=3; duration_label="3 Hours" ;;
- 4) duration_hours=6; duration_label="6 Hours" ;;
- 5) duration_hours=12; duration_label="12 Hours" ;;
- 6) duration_hours=24; duration_label="1 Day" ;;
- 7) duration_hours=72; duration_label="3 Days" ;;
- 8) read -p "👉 Enter custom duration in hours: " custom_hours
- if ! [[ "$custom_hours" =~ ^[0-9]+$ ]] || [[ "$custom_hours" -lt 1 ]]; then
- echo -e "\n${C_RED}❌ Invalid number of hours.${C_RESET}"; return
- fi
- duration_hours=$custom_hours
- duration_label="$custom_hours Hours"
- ;;
- 0) echo -e "\n${C_YELLOW}❌ Cancelled.${C_RESET}"; return ;;
- *) echo -e "\n${C_RED}❌ Invalid option.${C_RESET}"; return ;;
- esac
-
- # Username
- local rand_suffix=$(head /dev/urandom | tr -dc 'a-z0-9' | head -c 5)
- local default_username="trial_${rand_suffix}"
- read -p "👤 Username [${default_username}]: " username
- username=${username:-$default_username}
-
- if id "$username" &>/dev/null || grep -q "^$username:" "$DB_FILE"; then
- echo -e "\n${C_RED}❌ Error: User '$username' already exists.${C_RESET}"; return
- fi
-
- # Password
- local password=$(head /dev/urandom | tr -dc 'A-Za-z0-9' | head -c 8)
- read -p "🔑 Password [${password}]: " custom_pass
- password=${custom_pass:-$password}
-
- # Connection limit
- read -p "📶 Connection limit [1]: " limit
- limit=${limit:-1}
- if ! [[ "$limit" =~ ^[0-9]+$ ]]; then echo -e "\n${C_RED}❌ Invalid number.${C_RESET}"; return; fi
-
- # Bandwidth limit
- read -p "📦 Bandwidth limit in GB (0 = unlimited) [0]: " bandwidth_gb
- bandwidth_gb=${bandwidth_gb:-0}
- if ! [[ "$bandwidth_gb" =~ ^[0-9]+\.?[0-9]*$ ]]; then echo -e "\n${C_RED}❌ Invalid number.${C_RESET}"; return; fi
-
- # Calculate expiry
- local expire_date
- if [[ "$duration_hours" -ge 24 ]]; then
- local days=$((duration_hours / 24))
- expire_date=$(date -d "+$days days" +%Y-%m-%d)
- else
- # For sub-day durations, set expiry to tomorrow to be safe (at job does the real cleanup)
- expire_date=$(date -d "+1 day" +%Y-%m-%d)
- fi
- local expiry_timestamp
- expiry_timestamp=$(date -d "+${duration_hours} hours" '+%Y-%m-%d %H:%M:%S')
-
- # Create the system user
- useradd -m -s /usr/sbin/nologin "$username"
- usermod -aG ffusers "$username" 2>/dev/null
- echo "$username:$password" | chpasswd
- chage -E "$expire_date" "$username"
- echo "$username:$password:$expire_date:$limit:$bandwidth_gb" >> "$DB_FILE"
-
- # Schedule auto-cleanup via 'at'
- echo "$TRIAL_CLEANUP_SCRIPT $username" | at now + ${duration_hours} hours 2>/dev/null
-
- local bw_display="Unlimited"
- if [[ "$bandwidth_gb" != "0" ]]; then bw_display="${bandwidth_gb} GB"; fi
-
- clear; show_banner
- echo -e "${C_GREEN}✅ Trial account created successfully!${C_RESET}\n"
- echo -e "${C_YELLOW}========================================${C_RESET}"
- echo -e " ⏱️ ${C_BOLD}TRIAL ACCOUNT${C_RESET}"
- echo -e "${C_YELLOW}========================================${C_RESET}"
- echo -e " - 👤 Username: ${C_YELLOW}$username${C_RESET}"
- echo -e " - 🔑 Password: ${C_YELLOW}$password${C_RESET}"
- echo -e " - ⏱️ Duration: ${C_CYAN}$duration_label${C_RESET}"
- echo -e " - 🕐 Auto-expires at: ${C_RED}$expiry_timestamp${C_RESET}"
- echo -e " - 📶 Connection Limit: ${C_YELLOW}$limit${C_RESET}"
- echo -e " - 📦 Bandwidth Limit: ${C_YELLOW}$bw_display${C_RESET}"
- echo -e "${C_YELLOW}========================================${C_RESET}"
- echo -e "\n${C_DIM}The account will be automatically deleted when the trial expires.${C_RESET}"
-
- # Auto-ask for config generation
- echo
- read -p "👉 Generate client config for this trial user? (y/n): " gen_conf
- if [[ "$gen_conf" == "y" || "$gen_conf" == "Y" ]]; then
- generate_client_config "$username" "$password"
- fi
-
- update_ssh_banners_config
- }
- view_user_bandwidth() {
- _select_user_interface "--- 📊 View User Bandwidth ---"
- local u=$SELECTED_USER
- if [[ "$u" == "NO_USERS" || -z "$u" ]]; then return; fi
-
- clear; show_banner
- echo -e "${C_BOLD}${C_PURPLE}--- 📊 Bandwidth Details: ${C_YELLOW}$u${C_PURPLE} ---${C_RESET}\n"
-
- local line; line=$(grep "^$u:" "$DB_FILE")
- local bandwidth_gb; bandwidth_gb=$(echo "$line" | cut -d: -f5)
- [[ -z "$bandwidth_gb" ]] && bandwidth_gb="0"
-
- local used_bytes=0
- if [[ -f "$BANDWIDTH_DIR/${u}.usage" ]]; then
- used_bytes=$(cat "$BANDWIDTH_DIR/${u}.usage" 2>/dev/null)
- [[ -z "$used_bytes" ]] && used_bytes=0
- fi
-
- local used_mb; used_mb=$(awk "BEGIN {printf \"%.2f\", $used_bytes / 1048576}")
- local used_gb; used_gb=$(awk "BEGIN {printf \"%.3f\", $used_bytes / 1073741824}")
-
- echo -e " ${C_CYAN}Data Used:${C_RESET} ${C_WHITE}${used_gb} GB${C_RESET} (${used_mb} MB)"
-
- if [[ "$bandwidth_gb" == "0" ]]; then
- echo -e " ${C_CYAN}Bandwidth Limit:${C_RESET} ${C_GREEN}Unlimited${C_RESET}"
- echo -e " ${C_CYAN}Status:${C_RESET} ${C_GREEN}No quota restrictions${C_RESET}"
- else
- local quota_bytes; quota_bytes=$(awk "BEGIN {printf \"%.0f\", $bandwidth_gb * 1073741824}")
- local percentage; percentage=$(awk "BEGIN {printf \"%.1f\", ($used_bytes / $quota_bytes) * 100}")
- local remaining_bytes; remaining_bytes=$((quota_bytes - used_bytes))
- if [[ "$remaining_bytes" -lt 0 ]]; then remaining_bytes=0; fi
- local remaining_gb; remaining_gb=$(awk "BEGIN {printf \"%.3f\", $remaining_bytes / 1073741824}")
-
- echo -e " ${C_CYAN}Bandwidth Limit:${C_RESET} ${C_YELLOW}${bandwidth_gb} GB${C_RESET}"
- echo -e " ${C_CYAN}Remaining:${C_RESET} ${C_WHITE}${remaining_gb} GB${C_RESET}"
- echo -e " ${C_CYAN}Usage:${C_RESET} ${C_WHITE}${percentage}%${C_RESET}"
-
- # Progress bar
- local bar_width=30
- local filled; filled=$(awk "BEGIN {printf \"%.0f\", ($percentage / 100) * $bar_width}")
- if [[ "$filled" -gt "$bar_width" ]]; then filled=$bar_width; fi
- local empty=$((bar_width - filled))
- local bar_color="$C_GREEN"
- if (( $(awk "BEGIN {print ($percentage > 80)}" ) )); then bar_color="$C_RED"
- elif (( $(awk "BEGIN {print ($percentage > 50)}" ) )); then bar_color="$C_YELLOW"
- fi
- printf " ${C_CYAN}Progress:${C_RESET} ${bar_color}["
- for ((i=0; i<filled; i++)); do printf "█"; done
- for ((i=0; i<empty; i++)); do printf "░"; done
- printf "]${C_RESET} ${percentage}%%\n"
-
- if [[ "$used_bytes" -ge "$quota_bytes" ]]; then
- echo -e "\n ${C_RED}⚠️ USER HAS EXCEEDED BANDWIDTH QUOTA — ACCOUNT LOCKED${C_RESET}"
- fi
- fi
- }
- bulk_create_users() {
- clear; show_banner
- echo -e "${C_BOLD}${C_PURPLE}--- 👥 Bulk Create Users ---${C_RESET}"
-
- read -p "👉 Enter username prefix (e.g., 'user'): " prefix
- if [[ -z "$prefix" ]]; then echo -e "\n${C_RED}❌ Prefix cannot be empty.${C_RESET}"; return; fi
-
- read -p "🔢 How many users to create? " count
- if ! [[ "$count" =~ ^[0-9]+$ ]] || [[ "$count" -lt 1 ]] || [[ "$count" -gt 100 ]]; then
- echo -e "\n${C_RED}❌ Invalid count (1-100).${C_RESET}"; return
- fi
-
- read -p "🗓️ Account duration (in days): " days
- if ! [[ "$days" =~ ^[0-9]+$ ]]; then echo -e "\n${C_RED}❌ Invalid number.${C_RESET}"; return; fi
-
- read -p "📶 Connection limit per user [1]: " limit
- limit=${limit:-1}
- if ! [[ "$limit" =~ ^[0-9]+$ ]]; then echo -e "\n${C_RED}❌ Invalid number.${C_RESET}"; return; fi
-
- read -p "📦 Bandwidth limit in GB per user (0 = unlimited) [0]: " bandwidth_gb
- bandwidth_gb=${bandwidth_gb:-0}
- if ! [[ "$bandwidth_gb" =~ ^[0-9]+\.?[0-9]*$ ]]; then echo -e "\n${C_RED}❌ Invalid number.${C_RESET}"; return; fi
-
- local expire_date
- expire_date=$(date -d "+$days days" +%Y-%m-%d)
- local bw_display="Unlimited"; [[ "$bandwidth_gb" != "0" ]] && bw_display="${bandwidth_gb} GB"
-
- echo -e "\n${C_BLUE}⚙️ Creating $count users with prefix '${prefix}'...${C_RESET}\n"
- echo -e "${C_YELLOW}================================================================${C_RESET}"
- printf "${C_BOLD}${C_WHITE}%-20s | %-15s | %-12s${C_RESET}\n" "USERNAME" "PASSWORD" "EXPIRES"
- echo -e "${C_YELLOW}----------------------------------------------------------------${C_RESET}"
-
- local created=0
- for ((i=1; i<=count; i++)); do
- local username="${prefix}${i}"
- if id "$username" &>/dev/null || grep -q "^$username:" "$DB_FILE"; then
- echo -e "${C_RED} ⚠️ Skipping '$username' — already exists${C_RESET}"
- continue
- fi
- local password=$(head /dev/urandom | tr -dc 'A-Za-z0-9' | head -c 8)
- useradd -m -s /usr/sbin/nologin "$username"
- usermod -aG ffusers "$username" 2>/dev/null
- echo "$username:$password" | chpasswd
- chage -E "$expire_date" "$username"
- echo "$username:$password:$expire_date:$limit:$bandwidth_gb" >> "$DB_FILE"
- printf " ${C_GREEN}%-20s${C_RESET} | ${C_YELLOW}%-15s${C_RESET} | ${C_CYAN}%-12s${C_RESET}\n" "$username" "$password" "$expire_date"
- created=$((created + 1))
- done
-
- echo -e "${C_YELLOW}================================================================${C_RESET}"
- echo -e "\n${C_GREEN}✅ Created $created users. Conn Limit: ${limit} | BW: ${bw_display}${C_RESET}"
-
- update_ssh_banners_config
- }
- generate_client_config() {
- local user=$1
- local pass=$2
-
- # Auto-detect Host
- local host_ip=$(curl -s -4 icanhazip.com)
- local host_domain="$host_ip"
- if [ -f "$DNS_INFO_FILE" ]; then
- local managed_domain=$(grep 'FULL_DOMAIN' "$DNS_INFO_FILE" | cut -d'"' -f2)
- if [[ -n "$managed_domain" ]]; then host_domain="$managed_domain"; fi
- fi
- # Also check if Nginx Certbot is used
- if [ -f "$NGINX_CONFIG_FILE" ]; then
- local nginx_domain=$(grep -oP 'server_name \K[^\s;]+' "$NGINX_CONFIG_FILE" | head -n 1)
- if [[ "$nginx_domain" != "_" && -n "$nginx_domain" ]]; then host_domain="$nginx_domain"; fi
- fi
- echo -e "\n${C_BOLD}${C_PURPLE}--- 📱 Client Connection Configuration ---${C_RESET}"
- echo -e "${C_CYAN}Copy the details below to your clipboard:${C_RESET}\n"
- echo -e "${C_YELLOW}========================================${C_RESET}"
- echo -e "👤 ${C_BOLD}User Details${C_RESET}"
- echo -e " • Username: ${C_WHITE}$user${C_RESET}"
- echo -e " • Password: ${C_WHITE}$pass${C_RESET}"
- echo -e " • Host/IP : ${C_WHITE}$host_domain${C_RESET}"
- echo -e "${C_YELLOW}========================================${C_RESET}"
-
- # 1. SSH Direct
- echo -e "\n🔹 ${C_BOLD}SSH Direct${C_RESET}:"
- echo -e " • Host: $host_domain"
- echo -e " • Port: 22"
- echo -e " • payload: (Standard SSH)"
- # 2. SSL/TLS Tunnel (HAProxy or Nginx)
- local ssl_port=""
- local ssl_type=""
-
- # Check HAProxy
- if systemctl is-active --quiet haproxy; then
- local haproxy_port=$(grep -oP 'bind \*:(\d+)' "$HAPROXY_CONFIG" 2>/dev/null | awk -F: '{print $2}')
- if [[ -n "$haproxy_port" ]]; then ssl_port="$haproxy_port"; ssl_type="HAProxy"; fi
- fi
- # Check Nginx (Override if both exist, or show both)
- if systemctl is-active --quiet nginx && [ -f "$NGINX_PORTS_FILE" ]; then
- source "$NGINX_PORTS_FILE"
- # Take the first TLS port
- local nginx_ssl_port=$(echo "$TLS_PORTS" | awk '{print $1}')
- if [[ -n "$nginx_ssl_port" ]]; then
- if [[ -n "$ssl_port" ]]; then ssl_port="$ssl_port, $nginx_ssl_port"; else ssl_port="$nginx_ssl_port"; fi
- ssl_type="Nginx/TLS"
- fi
- fi
-
- if [[ -n "$ssl_port" ]]; then
- echo -e "\n🔹 ${C_BOLD}SSL/TLS Tunnel ($ssl_type)${C_RESET}:"
- echo -e " • Host: $host_domain"
- echo -e " • Port(s): $ssl_port"
- echo -e " • SNI (BugHost): $host_domain (or your preferred SNI)"
- fi
- # 3. UDP Custom
- if systemctl is-active --quiet udp-custom; then
- echo -e "\n🔹 ${C_BOLD}UDP Custom${C_RESET}:"
- echo -e " • IP: $host_ip (Must use numeric IP)"
- echo -e " • Port: 1-65535 (Exclude 53, 5300)"
- echo -e " • Obfs: (None/Plain)"
- fi
- # 4. DNSTT
- if systemctl is-active --quiet dnstt; then
- if [ -f "$DNSTT_CONFIG_FILE" ]; then
- source "$DNSTT_CONFIG_FILE"
- echo -e "\n🔹 ${C_BOLD}DNSTT (SlowDNS)${C_RESET}:"
- echo -e " • Nameserver: $TUNNEL_DOMAIN"
- echo -e " • PubKey: $PUBLIC_KEY"
- echo -e " • DNS IP: 1.1.1.1 / 8.8.8.8"
- fi
- fi
-
- # 5. ZiVPN
- if systemctl is-active --quiet zivpn; then
- echo -e "\n🔹 ${C_BOLD}ZiVPN${C_RESET}:"
- echo -e " • UDP Port: 5667"
- echo -e " • Forwarded Ports: 6000-19999"
- fi
-
- echo -e "${C_YELLOW}========================================${C_RESET}"
- press_enter
- }
- client_config_menu() {
- _select_user_interface "--- 📱 Generate Client Config ---"
- local u=$SELECTED_USER
- if [[ "$u" == "NO_USERS" || -z "$u" ]]; then return; fi
-
- # We need to find the password. It's in the DB.
- local pass=$(grep "^$u:" "$DB_FILE" | cut -d: -f2)
- generate_client_config "$u" "$pass"
- }
- # Lightweight Bash Monitor (No vnStat required)
- simple_live_monitor() {
- local iface=$1
- echo -e "\n${C_BLUE}⚡ Starting Lightweight Traffic Monitor for $iface...${C_RESET}"
- echo -e "${C_DIM}Press [Ctrl+C] to stop.${C_RESET}\n"
-
- # Get initial values
- local rx1=$(cat /sys/class/net/$iface/statistics/rx_bytes)
- local tx1=$(cat /sys/class/net/$iface/statistics/tx_bytes)
-
- printf "%-15s | %-15s\n" "⬇️ Download" "⬆️ Upload"
- echo "-----------------------------------"
-
- while true; do
- sleep 1
- local rx2=$(cat /sys/class/net/$iface/statistics/rx_bytes)
- local tx2=$(cat /sys/class/net/$iface/statistics/tx_bytes)
-
- # Calculate diffs
- local rx_diff=$((rx2 - rx1))
- local tx_diff=$((tx2 - tx1))
-
- # Convert to KB/s
- local rx_kbs=$((rx_diff / 1024))
- local tx_kbs=$((tx_diff / 1024))
-
- # Formatting for MB/s if > 1024 KB
- if [ $rx_kbs -gt 1024 ]; then rx_fmt=$(awk "BEGIN {printf \"%.2f MB/s\", $rx_kbs/1024}"); else rx_fmt="${rx_kbs} KB/s"; fi
- if [ $tx_kbs -gt 1024 ]; then tx_fmt=$(awk "BEGIN {printf \"%.2f MB/s\", $tx_kbs/1024}"); else tx_fmt="${tx_kbs} KB/s"; fi
-
- printf "\r%-15s | %-15s" "$rx_fmt" "$tx_fmt"
-
- # Reset for next loop
- rx1=$rx2
- tx1=$tx2
- done
- }
- traffic_monitor_menu() {
- clear; show_banner
- echo -e "${C_BOLD}${C_PURPLE}--- 📈 Network Traffic Monitor ---${C_RESET}"
-
- # Find active interface
- local iface=$(ip -4 route ls | grep default | grep -Po '(?<=dev )(\S+)' | head -1)
-
- echo -e "\nInterface: ${C_CYAN}${iface}${C_RESET}"
-
- echo -e "\n${C_BOLD}Select a monitoring option:${C_RESET}\n"
- printf " ${C_CHOICE}[ 1]${C_RESET} %-40s\n" "⚡ Live Monitor ${C_DIM}(Lightweight, No Install)${C_RESET}"
- printf " ${C_CHOICE}[ 2]${C_RESET} %-40s\n" "📊 View Total Traffic Since Boot"
- printf " ${C_CHOICE}[ 3]${C_RESET} %-40s\n" "📅 Daily/Monthly Logs ${C_DIM}(Requires vnStat)${C_RESET}"
-
- echo -e "\n ${C_WARN}[ 0]${C_RESET} ↩️ Return"
- echo
- read -p "👉 Enter choice: " t_choice
- case $t_choice in
- 1)
- simple_live_monitor "$iface"
- ;;
- 2)
- local rx_total=$(cat /sys/class/net/$iface/statistics/rx_bytes)
- local tx_total=$(cat /sys/class/net/$iface/statistics/tx_bytes)
- local rx_mb=$((rx_total / 1024 / 1024))
- local tx_mb=$((tx_total / 1024 / 1024))
- echo -e "\n${C_BLUE}📊 Total Traffic (Since Boot):${C_RESET}"
- echo -e " ⬇️ Download: ${C_WHITE}${rx_mb} MB${C_RESET}"
- echo -e " ⬆️ Upload: ${C_WHITE}${tx_mb} MB${C_RESET}"
- press_enter
- ;;
- 3)
- # vnStat Logic
- if ! command -v vnstat &> /dev/null; then
- echo -e "\n${C_YELLOW}⚠️ vnStat is not installed.${C_RESET}"
- echo -e " This tool provides persistent history (Daily/Monthly reports)."
- echo -e " It is lightweight but requires installation."
- read -p "👉 Install vnStat now? (y/n): " confirm
- if [[ "$confirm" == "y" || "$confirm" == "Y" ]]; then
- echo -e "\n${C_BLUE}📦 Installing vnStat...${C_RESET}"
- apt-get update >/dev/null 2>&1
- apt-get install -y vnstat >/dev/null 2>&1
- systemctl enable vnstat >/dev/null 2>&1
- systemctl restart vnstat >/dev/null 2>&1
- local default_iface=$(ip -4 route ls | grep default | grep -Po '(?<=dev )(\S+)' | head -1)
- vnstat --add -i "$default_iface" >/dev/null 2>&1
- echo -e "${C_GREEN}✅ Installed.${C_RESET}"
- sleep 1
- else
- return
- fi
- fi
- echo
- vnstat -i "$iface"
- echo -e "\n${C_DIM}Run 'vnstat -d' or 'vnstat -m' manually for specific views.${C_RESET}"
- press_enter
- ;;
- *) return ;;
- esac
- }
- torrent_block_menu() {
- clear; show_banner
- echo -e "${C_BOLD}${C_PURPLE}--- 🚫 Torrent Blocking (Anti-Torrent) ---${C_RESET}"
-
- # Check status
- local torrent_status="${C_STATUS_I}Disabled${C_RESET}"
- if iptables -L FORWARD | grep -q "ipp2p"; then
- torrent_status="${C_STATUS_A}Enabled${C_RESET}"
- elif iptables -L OUTPUT | grep -q "BitTorrent"; then
- # Fallback check for string matching
- torrent_status="${C_STATUS_A}Enabled${C_RESET}"
- fi
-
- echo -e "\n${C_WHITE}Current Status: ${torrent_status}${C_RESET}"
- echo -e "${C_DIM}This feature uses iptables string matching to block common torrent keywords.${C_RESET}"
-
- echo -e "\n${C_BOLD}Select an action:${C_RESET}\n"
- printf " ${C_CHOICE}[ 1]${C_RESET} %-40s\n" "🔒 Enable Torrent Blocking"
- printf " ${C_CHOICE}[ 2]${C_RESET} %-40s\n" "🔓 Disable Torrent Blocking"
- echo -e "\n ${C_WARN}[ 0]${C_RESET} ↩️ Return"
- echo
- read -p "👉 Enter choice: " b_choice
-
- case $b_choice in
- 1)
- echo -e "\n${C_BLUE}🛡️ Applying Anti-Torrent rules...${C_RESET}"
- # Clean old rules first to avoid duplicates
- _flush_torrent_rules
-
- # Block Common Torrent Ports/Keywords
- # String matching using iptables extension
- iptables -A FORWARD -m string --string "BitTorrent" --algo bm -j DROP
- iptables -A FORWARD -m string --string "BitTorrent protocol" --algo bm -j DROP
- iptables -A FORWARD -m string --string "peer_id=" --algo bm -j DROP
- iptables -A FORWARD -m string --string ".torrent" --algo bm -j DROP
- iptables -A FORWARD -m string --string "announce.php?passkey=" --algo bm -j DROP
- iptables -A FORWARD -m string --string "torrent" --algo bm -j DROP
- iptables -A FORWARD -m string --string "info_hash" --algo bm -j DROP
- iptables -A FORWARD -m string --string "get_peers" --algo bm -j DROP
- iptables -A FORWARD -m string --string "find_node" --algo bm -j DROP
-
- # Same for OUTPUT to be safe
- iptables -A OUTPUT -m string --string "BitTorrent" --algo bm -j DROP
- iptables -A OUTPUT -m string --string "BitTorrent protocol" --algo bm -j DROP
- iptables -A OUTPUT -m string --string "peer_id=" --algo bm -j DROP
- iptables -A OUTPUT -m string --string ".torrent" --algo bm -j DROP
- iptables -A OUTPUT -m string --string "announce.php?passkey=" --algo bm -j DROP
- iptables -A OUTPUT -m string --string "torrent" --algo bm -j DROP
- iptables -A OUTPUT -m string --string "info_hash" --algo bm -j DROP
- iptables -A OUTPUT -m string --string "get_peers" --algo bm -j DROP
- iptables -A OUTPUT -m string --string "find_node" --algo bm -j DROP
-
- # Attempt to save if iptables-persistent exists
- if dpkg -s iptables-persistent &>/dev/null; then
- netfilter-persistent save &>/dev/null
- fi
-
- echo -e "${C_GREEN}✅ Torrent Blocking Enabled.${C_RESET}"
- press_enter
- ;;
- 2)
- echo -e "\n${C_BLUE}🔓 Removing Anti-Torrent rules...${C_RESET}"
- _flush_torrent_rules
- if dpkg -s iptables-persistent &>/dev/null; then
- netfilter-persistent save &>/dev/null
- fi
- echo -e "${C_GREEN}✅ Torrent Blocking Disabled.${C_RESET}"
- press_enter
- ;;
- *) return ;;
- esac
- }
- _flush_torrent_rules() {
- # Helper to remove rules containing specific strings
- # This is a bit brute-force but effective for this script's scope
- iptables -D FORWARD -m string --string "BitTorrent" --algo bm -j DROP 2>/dev/null
- iptables -D FORWARD -m string --string "BitTorrent protocol" --algo bm -j DROP 2>/dev/null
- iptables -D FORWARD -m string --string "peer_id=" --algo bm -j DROP 2>/dev/null
- iptables -D FORWARD -m string --string ".torrent" --algo bm -j DROP 2>/dev/null
- iptables -D FORWARD -m string --string "announce.php?passkey=" --algo bm -j DROP 2>/dev/null
- iptables -D FORWARD -m string --string "torrent" --algo bm -j DROP 2>/dev/null
- iptables -D FORWARD -m string --string "info_hash" --algo bm -j DROP 2>/dev/null
- iptables -D FORWARD -m string --string "get_peers" --algo bm -j DROP 2>/dev/null
- iptables -D FORWARD -m string --string "find_node" --algo bm -j DROP 2>/dev/null
- iptables -D OUTPUT -m string --string "BitTorrent" --algo bm -j DROP 2>/dev/null
- iptables -D OUTPUT -m string --string "BitTorrent protocol" --algo bm -j DROP 2>/dev/null
- iptables -D OUTPUT -m string --string "peer_id=" --algo bm -j DROP 2>/dev/null
- iptables -D OUTPUT -m string --string ".torrent" --algo bm -j DROP 2>/dev/null
- iptables -D OUTPUT -m string --string "announce.php?passkey=" --algo bm -j DROP 2>/dev/null
- iptables -D OUTPUT -m string --string "torrent" --algo bm -j DROP 2>/dev/null
- iptables -D OUTPUT -m string --string "info_hash" --algo bm -j DROP 2>/dev/null
- iptables -D OUTPUT -m string --string "get_peers" --algo bm -j DROP 2>/dev/null
- iptables -D OUTPUT -m string --string "find_node" --algo bm -j DROP 2>/dev/null
- }
- ssh_banner_menu() {
- clear; show_banner
- echo -e "${C_BOLD}${C_PURPLE}--- 🎨 SSH Login Banner ---${C_RESET}"
-
- echo -e "\n${C_CYAN}When enabled, users connecting via SSH tunnel (HTTP Custom,"
- echo -e "HTTP Injector, etc.) will see their account details:${C_RESET}"
- echo -e " • Days/hours remaining"
- echo -e " • Bandwidth used and remaining"
- echo -e " • Active sessions count"
-
- # Check current status
- if [[ -f "$SSHD_FF_CONFIG" ]]; then
- echo -e "\n Status: ${C_GREEN}✅ ENABLED${C_RESET}"
- else
- echo -e "\n Status: ${C_RED}❌ DISABLED${C_RESET}"
- fi
-
- echo -e "\n${C_BOLD}Options:${C_RESET}\n"
- printf " ${C_CHOICE}[ 1]${C_RESET} %-35s\n" "✅ Enable Login Banner"
- printf " ${C_CHOICE}[ 2]${C_RESET} %-35s\n" "❌ Disable Login Banner"
- printf " ${C_CHOICE}[ 3]${C_RESET} %-35s\n" "📝 Preview Banner (test with a user)"
- echo -e "\n ${C_WARN}[ 0]${C_RESET} ↩️ Return"
- echo
- read -p "👉 Enter choice: " banner_choice
- case $banner_choice in
- 1)
- touch "/etc/firewallfalcon/banners_enabled"
- update_ssh_banners_config
- echo -e "\n${C_GREEN}✅ SSH Login Banner has been enabled!${C_RESET}"
- echo -e "${C_DIM}Users will see account info when they connect via SSH tunnel.${C_RESET}"
- press_enter
- ;;
- 2)
- rm -f "/etc/firewallfalcon/banners_enabled"
- update_ssh_banners_config
- echo -e "\n${C_YELLOW}❌ SSH Login Banner has been disabled.${C_RESET}"
- press_enter
- ;;
- 3)
- _select_user_interface "--- 📝 Preview Login Banner ---"
- local u=$SELECTED_USER
- if [[ -z "$u" || "$u" == "NO_USERS" ]]; then return; fi
- echo -e "\n${C_CYAN}--- Banner Preview for user '$u' ---${C_RESET}\n"
- if [[ -f "/etc/firewallfalcon/banners/${u}.txt" ]]; then
- cat "/etc/firewallfalcon/banners/${u}.txt"
- else
- echo -e "${C_RED}Banner file not generated yet. Waiting up to 15s for limiter to write it...${C_RESET}"
- sleep 5
- cat "/etc/firewallfalcon/banners/${u}.txt" 2>/dev/null || echo -e "${C_RED}Still not generated. Ensure the limiter service is running.${C_RESET}"
- fi
- press_enter
- ;;
- *) return ;;
- esac
- }
- auto_reboot_menu() {
- clear; show_banner
- echo -e "${C_BOLD}${C_PURPLE}--- 🔄 Auto-Reboot Management ---${C_RESET}"
-
- # Check status
- local cron_check=$(crontab -l 2>/dev/null | grep "systemctl reboot")
- local status="${C_STATUS_I}Disabled${C_RESET}"
- if [[ -n "$cron_check" ]]; then
- status="${C_STATUS_A}Active (Midnight)${C_RESET}"
- fi
-
- echo -e "\n${C_WHITE}Current Status: ${status}${C_RESET}"
-
- echo -e "\n${C_BOLD}Select an action:${C_RESET}\n"
- printf " ${C_CHOICE}[ 1]${C_RESET} %-40s\n" "🕐 Enable Daily Reboot (00:00 midnight)"
- printf " ${C_CHOICE}[ 2]${C_RESET} %-40s\n" "❌ Disable Auto-Reboot"
- echo -e "\n ${C_WARN}[ 0]${C_RESET} ↩️ Return"
- echo
- read -p "👉 Enter choice: " r_choice
-
- case $r_choice in
- 1)
- # Remove existing to prevent duplicates
- (crontab -l 2>/dev/null | grep -v "systemctl reboot") | crontab -
- # Add new job
- (crontab -l 2>/dev/null; echo "0 0 * * * systemctl reboot") | crontab -
- echo -e "\n${C_GREEN}✅ Auto-reboot scheduled for every day at 00:00.${C_RESET}"
- press_enter
- ;;
- 2)
- (crontab -l 2>/dev/null | grep -v "systemctl reboot") | crontab -
- echo -e "\n${C_GREEN}✅ Auto-reboot disabled.${C_RESET}"
- press_enter
- ;;
- *) return ;;
- esac
- }
- press_enter() {
- echo -e "\nPress ${C_YELLOW}[Enter]${C_RESET} to return to the menu..." && read -r
- }
- invalid_option() {
- echo -e "\n${C_RED}❌ Invalid option.${C_RESET}" && sleep 1
- }
- main_menu() {
- while true; do
- export UNINSTALL_MODE="interactive"
- show_banner
-
- echo
- echo -e " ${C_TITLE}═══════════════════[ ${C_BOLD}👤 USER MANAGEMENT ${C_RESET}${C_TITLE}]═══════════════════${C_RESET}"
- printf " ${C_CHOICE}[%2s]${C_RESET} %-28s ${C_CHOICE}[%2s]${C_RESET} %-28s\n" "1" "✨ Create New User" "6" "✏️ Edit User Details"
- printf " ${C_CHOICE}[%2s]${C_RESET} %-28s ${C_CHOICE}[%2s]${C_RESET} %-28s\n" "2" "🗑️ Delete User" "7" "📋 List Managed Users"
- printf " ${C_CHOICE}[%2s]${C_RESET} %-28s ${C_CHOICE}[%2s]${C_RESET} %-28s\n" "3" "🔄 Renew User Account" "8" "📱 Generate Client Config"
- printf " ${C_CHOICE}[%2s]${C_RESET} %-28s ${C_CHOICE}[%2s]${C_RESET} %-28s\n" "4" "🔒 Lock User Account" "9" "⏱️ Create Trial Account"
- printf " ${C_CHOICE}[%2s]${C_RESET} %-28s ${C_CHOICE}[%2s]${C_RESET} %-28s\n" "5" "🔓 Unlock User Account" "10" "📊 View User Bandwidth"
- printf " ${C_CHOICE}[%2s]${C_RESET} %-28s\n" "11" "👥 Bulk Create Users"
-
- echo
- echo -e " ${C_TITLE}══════════════[ ${C_BOLD}🌐 VPN & PROTOCOLS ${C_RESET}${C_TITLE}]═══════════════${C_RESET}"
- printf " ${C_CHOICE}[%2s]${C_RESET} %-28s ${C_CHOICE}[%2s]${C_RESET} %-28s\n" "12" "🔌 Protocol Manager" "14" "📈 Traffic Monitor (Lite)"
- printf " ${C_CHOICE}[%2s]${C_RESET} %-28s ${C_CHOICE}[%2s]${C_RESET} %-28s\n" "13" "DT Proxy Manager" "15" "🚫 Block Torrent (Anti-P2P)"
- echo
- echo -e " ${C_TITLE}══════════════[ ${C_BOLD}⚙️ SYSTEM SETTINGS ${C_RESET}${C_TITLE}]═══════════════${C_RESET}"
- printf " ${C_CHOICE}[%2s]${C_RESET} %-28s ${C_CHOICE}[%2s]${C_RESET} %-28s\n" "16" "☁️ CloudFlare Free Domain" "19" "💾 Backup User Data"
- printf " ${C_CHOICE}[%2s]${C_RESET} %-28s ${C_CHOICE}[%2s]${C_RESET} %-28s\n" "17" "🎨 SSH Banner Config" "20" "📥 Restore User Data"
- printf " ${C_CHOICE}[%2s]${C_RESET} %-28s ${C_CHOICE}[%2s]${C_RESET} %-28s\n" "18" "🔄 Auto-Reboot Task" "21" "🧹 Cleanup Expired Users"
- echo
- echo -e " ${C_DANGER}═══════════════════[ ${C_BOLD}🔥 DANGER ZONE ${C_RESET}${C_DANGER}]═══════════════════${C_RESET}"
- echo -e " ${C_DANGER}[99]${C_RESET} Uninstall Script ${C_WARN}[ 0]${C_RESET} Exit"
- echo
- read -p "$(echo -e ${C_PROMPT}"👉 Select an option: "${C_RESET})" choice
- case $choice in
- 1) create_user; press_enter ;;
- 2) delete_user; press_enter ;;
- 3) renew_user; press_enter ;;
- 4) lock_user; press_enter ;;
- 5) unlock_user; press_enter ;;
- 6) edit_user; press_enter ;;
- 7) list_users; press_enter ;;
- 8) client_config_menu; press_enter ;;
- 9) create_trial_account; press_enter ;;
- 10) view_user_bandwidth; press_enter ;;
- 11) bulk_create_users; press_enter ;;
-
- 12) protocol_menu ;;
- 13) dt_proxy_menu ;;
- 14) traffic_monitor_menu ;;
- 15) torrent_block_menu ;;
-
- 16) dns_menu; press_enter ;;
- 17) ssh_banner_menu ;;
- 18) auto_reboot_menu ;;
- 19) backup_user_data; press_enter ;;
- 20) restore_user_data; press_enter ;;
- 21) cleanup_expired; press_enter ;;
-
- 99) uninstall_script ;;
- 0) exit 0 ;;
- *) invalid_option ;;
- esac
- done
- }
- if [[ "$1" == "--install-setup" ]]; then
- initial_setup
- exit 0
- fi
- main_menu
|