-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathproxymon.sh
More file actions
1162 lines (1021 loc) · 50.1 KB
/
Copy pathproxymon.sh
File metadata and controls
1162 lines (1021 loc) · 50.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#!/bin/bash
# maravento.com
#
################################################################################
#
# Proxy Monitor (Proxymon) — install / update / uninstall script
#
# Proxymon bundles Squid bandwidth monitoring (Bandata), LightSquid,
# SARG, SquidAnalyzer and SquidMon behind an Apache web panel, with
# optional Unifi Hotspot integration.
#
# USAGE
# ./proxymon.sh install Fresh install. Aborts if /var/www/proxymon
# already exists (use update or uninstall
# first — see below).
# ./proxymon.sh update Refresh code and permissions under
# /var/www/proxymon. Stops Apache, backs up
# live data, replaces code, restores live
# data, resets permissions, restarts Apache.
# Does not touch Apache/PHP/SARG system
# config, cron, ACL lists, or
# /etc/proxymon/proxymon.env.
# ./proxymon.sh uninstall Remove Proxymon: Apache sites, cron entries,
# iptables/ipset rules, restore .bak configs.
# Prompts before deleting /etc/proxymon and
# /etc/acl (ACLs, MAC registrations, LLM
# credentials).
# ./proxymon.sh Interactive menu with the same 3 options.
# ./proxymon.sh -h|--help Show usage.
#
# REQUIREMENTS
# - Run as root (via sudo, from a regular user's session — update needs
# a resolvable local user to place its backup folder; see below).
# - Must be run from a directory containing a populated modules/
# folder (fetched separately via gitfolder.py — see check_repo()).
# install and update both read from this local modules/ tree; the
# script itself does not fetch or pull anything from git.
# - System packages from check_dependencies() (squid, apache2, sarg,
# php, rsync, etc.) must already be installed.
#
# INSTALL vs UPDATE — WHAT EACH TOUCHES
# install_proxymon() writes everything from scratch: Apache vhosts and
# Listen directives, /etc/proxymon/proxymon.env (interactive prompts),
# ACL directories/lists (with a fresh download), SARG config and
# usertab, SquidAnalyzer, PHP/Apache hardening (php.ini, security.conf,
# apache2.conf, mpm_prefork.conf), cron entries, and enables the sites.
# Because it prompts for configuration and can overwrite an existing
# setup, it refuses to run if /var/www/proxymon already exists.
#
# update_proxymon() only refreshes code and permissions under
# /var/www/proxymon. It never touches Apache/PHP/SARG system config,
# cron, ACL lists, or proxymon.env, and it never prompts. Sequence:
# 1. Stop Apache (avoid serving a half-swapped tree).
# 2. rsync live data that isn't part of the modules/ repo tree into
# ~<local_user>/proxymonbak/ (a real, persistent folder — never
# /tmp, which may be a small tmpfs and fail mid-copy on a large
# report set). The local user is auto-detected (graphical
# session, logname, SUDO_USER, active session, or first /home
# entry) so the backup lands in a real home directory, not root's.
# 3. cp -rf modules/* into /var/www/proxymon (same method install
# uses).
# 4. rsync proxymonbak/ back into /var/www/proxymon, restoring the
# live data over the freshly-copied placeholders.
# 5. Reset permissions/ownership on /var/www/proxymon.
# 6. Restart Apache.
# The backup at ~<local_user>/proxymonbak/ is kept after a successful
# update (not auto-deleted) as a safety net. Live data preserved:
# - lightsquid/report (daily LightSquid reports)
# - lightsquid/realname.cfg (hostname mappings)
# - lightsquid/skipuser.cfg (excluded users)
# - sarg/squid-reports (SARG rendered reports)
# - squidmon/etc/config (SquidMon config file)
# - squidanalyzer/output (SquidAnalyzer rendered reports)
# - sqstat/config.inc.php (SQStat custom config, e.g. cachemgr credentials)
#
################################################################################
set -e
trap 'echo "Error on line $LINENO"; exit 1' ERR
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
# ════════════════════════════════════════════════════════════════
# INITIAL CHECKS
# ════════════════════════════════════════════════════════════════
## root check
if [ "$(id -u)" != "0" ]; then
echo "ERROR: This script must be run as root"
exit 1
fi
# prevent overlapping runs
SCRIPT_LOCK="/var/lock/$(basename "$0" .sh).lock"
exec 200>"$SCRIPT_LOCK"
if ! flock -n 200; then
echo "Script $(basename "$0") is already running"
exit 1
fi
# LOCAL USER (multi-strategy detection with validation)
local_user=""
# 1. Local graphical session (:0)
local_user=$(who | awk '/\(:0\)/{print $1; exit}')
# 2. Parent process logname (works well with sudo)
[ -z "$local_user" ] && local_user=$(logname 2>/dev/null || true)
# 3. SUDO_USER variable (when run via sudo from terminal)
[ -z "$local_user" ] && local_user="${SUDO_USER:-}"
# 4. First active session user (SSH or other)
[ -z "$local_user" ] && local_user=$(who | awk 'NR==1{print $1}')
# 5. First valid home directory
[ -z "$local_user" ] && local_user=$(ls -l /home 2>/dev/null | awk '/^d/{print $3; exit}')
# Validate the user actually exists on the system
if [ -z "$local_user" ] || ! id "$local_user" &>/dev/null; then
echo "ERROR: Cannot determine a valid local user"
exit 1
fi
check_dependencies() {
declare -A pkgs_alts
pkgs_alts=(
[squid]="squid squid-openssl"
[apache2]="apache2 apache2-bin apache2-data apache2-doc apache2-utils"
)
pkgs='wget git rsync ipset nbtscan libcgi-session-perl libgd-perl coreutils sarg php libapache2-mod-php php-cli php-curl sudo fonts-lato fonts-liberation fonts-dejavu'
for p in "${!pkgs_alts[@]}"; do
pkgs+=" $p"
done
missing=""
for p in $pkgs; do
if [[ -n "${pkgs_alts[$p]}" ]]; then
found=false
for alt in ${pkgs_alts[$p]}; do
dpkg -s "$alt" &>/dev/null && { found=true; break; }
done
if ! $found; then
missing+=" $p"
fi
else
dpkg -s "$p" &>/dev/null || missing+=" $p"
fi
done
missing=$(echo "$missing" | xargs)
if [[ -n "$missing" ]]; then
echo -e "${RED}Missing dependencies: $missing${NC}"
echo -e "${YELLOW}Please install manually with:${NC}"
echo -e "apt-get install $missing"
echo -e "${YELLOW}After installation, if apache2-doc has issues, run:${NC}"
echo -e "apt -qq install -y --reinstall apache2-doc"
exit 1
else
echo -e "${GREEN}All dependencies are installed${NC}"
fi
}
check_apache_config() {
if command -v php >/dev/null 2>&1; then
PHP_VERSION=$(php -r "echo PHP_MAJOR_VERSION.'.'.PHP_MINOR_VERSION;" 2>/dev/null)
else
echo -e "${RED}PHP is not installed${NC}"
exit 1
fi
if [[ ! "$PHP_VERSION" =~ ^[0-9]+\.[0-9]+$ ]]; then
echo -e "${RED}Could not determine PHP version (got '$PHP_VERSION'). Is PHP working correctly?${NC}"
exit 1
fi
config_errors=""
if [ ! -f /etc/apache2/mods-available/mpm_prefork.conf ]; then
config_errors+="/etc/apache2/mods-available/mpm_prefork.conf not found\n"
fi
if [ ! -f /etc/php/$PHP_VERSION/apache2/php.ini ]; then
if [ -f /etc/php/$PHP_VERSION/cli/php.ini ]; then
mkdir -p /etc/php/$PHP_VERSION/apache2
cp /etc/php/$PHP_VERSION/cli/php.ini /etc/php/$PHP_VERSION/apache2/php.ini
echo -e "${GREEN}php.ini copied to /etc/php/$PHP_VERSION/apache2/${NC}"
else
config_errors+="php.ini not found\n"
fi
fi
if ! apache2ctl -M 2>/dev/null | grep -q "mpm_prefork"; then
config_errors+="mpm_prefork module is not enabled\n"
fi
if ! apache2ctl -M 2>/dev/null | grep -qE "php[0-9.]*_module"; then
config_errors+="php module is not enabled\n"
fi
if [[ -n "$config_errors" ]]; then
echo -e "${RED}$config_errors${NC}"
exit 1
else
echo -e "${GREEN}Apache and PHP configuration is valid${NC}"
fi
}
check_squid_traffic() {
if [ ! -f /var/log/squid/access.log ]; then
echo -e "${RED}/var/log/squid/access.log not found${NC}"
exit 1
fi
log_lines=$(wc -l < /var/log/squid/access.log 2>/dev/null || echo 0)
if [ "$log_lines" -eq 0 ]; then
echo -e "${YELLOW}WARNING: access.log is empty (0 lines) — Squid may not have served any traffic yet.${NC}"
echo -e "${YELLOW}Continuing anyway; reports will be empty until traffic starts flowing.${NC}"
return 0
fi
log_entries=$(grep -cE "TCP_(HIT|MISS|TUNNEL|DENIED|ERROR)" /var/log/squid/access.log 2>/dev/null || true)
log_entries=${log_entries:-0}
if [ "$log_entries" -eq 0 ]; then
echo -e "${YELLOW}WARNING: no valid traffic found ($log_lines lines, 0 valid) — check Squid ACLs, port,${NC}"
echo -e "${YELLOW}and that clients are actually pointing at this proxy. Continuing anyway.${NC}"
else
echo -e "${GREEN}Squid traffic: $log_lines lines, $log_entries valid entries${NC}"
fi
}
run_initial_checks() {
echo -e "${BLUE}Running initial checks...${NC}\n"
check_dependencies
check_apache_config
check_squid_traffic
echo -e "${GREEN}All checks passed!${NC}\n"
}
# ════════════════════════════════════════════════════════════════
# REPOSITORY STRUCTURE CHECK
# ════════════════════════════════════════════════════════════════
check_repo() {
local missing=0
if [ ! -d "modules" ] || [ -z "$(ls -A "modules" 2>/dev/null)" ]; then
missing=1
fi
if [ "$missing" -eq 1 ]; then
echo ""
echo "ERROR: Repository files not found. Run:"
echo ""
echo " git clone https://github.com/maravento/proxymon"
echo ""
exit 1
fi
}
# ════════════════════════════════════════════════════════════════
# PROXYMON ENV CONFIGURATION
# ════════════════════════════════════════════════════════════════
create_proxymon_env() {
local env_file="/etc/proxymon/proxymon.env"
mkdir -p /etc/proxymon
if [ -f "$env_file" ]; then
echo -e "${GREEN}$env_file already exists — skipping configuration${NC}"
# Warn if a newer version of this script expects variables
# not present in an existing env file (version drift).
local required_vars="LAN SERVER_IP RANGE REPORT_IP_GLOB REPORT_PATH ACL_PATH ACL_MAC_PATH ACL_SQUID_PATH ACL_BANDATA_PATH ALLOW_LIST BLOCK_LIST_DAY BLOCK_LIST_WEEK BLOCK_LIST_MONTH SQUID_LOG_DIR SQUID_LOG_FILE MAX_BANDWIDTH_DAY MAX_BANDWIDTH_WEEK MAX_BANDWIDTH_MONTH UNIFI_HOTSPOT_ENABLED HOTSPOT_PATH UPDATE_REALNAME"
local missing=""
for var in $required_vars; do
grep -q "^${var}=" "$env_file" || missing="$missing $var"
done
if [ -n "$missing" ]; then
echo -e "${YELLOW}WARNING: $env_file is missing variables expected by this version:${NC}"
echo -e "${YELLOW} $missing${NC}"
echo -e "${YELLOW} Add them manually, or remove $env_file and re-run install to regenerate.${NC}"
fi
# RANGE used to hold a filename-matching glob (e.g. "192.168.10*"),
# not a network CIDR. An env file from before this change will fail
# this check and needs RANGE corrected manually (and REPORT_IP_GLOB
# added, per the check above) before Require ip will work correctly.
local _range_val
_range_val=$(grep "^RANGE=" "$env_file" | head -n1 | cut -d'=' -f2-)
if [ -n "$_range_val" ] && ! [[ "$_range_val" =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}/[0-9]{1,2}$ ]]; then
echo -e "${YELLOW}WARNING: RANGE='$_range_val' in $env_file is not a network CIDR (e.g. 192.168.10.0/24).${NC}"
echo -e "${YELLOW} This looks like the old filename-glob value. Add REPORT_IP_GLOB=$_range_val,${NC}"
echo -e "${YELLOW} then set RANGE to your actual LAN subnet (e.g. RANGE=192.168.10.0/24).${NC}"
fi
return 0
fi
echo -e "${BLUE}════════════════════════════════════════${NC}"
echo -e "${BLUE} Bandata Configuration${NC}"
echo -e "${BLUE}════════════════════════════════════════${NC}"
printf "\n"
# LAN interface
echo -e "${YELLOW}Available network interfaces:${NC}"
ip -o link | awk '$2 != "lo:" {print " " $2, $(NF-2)}' | sed 's_: _ _'
_lan_default=$(ip -o link | awk -F': ' '$2 != "lo" {print $2; exit}')
_lan_default=${_lan_default:-eth0}
while true; do
read -rp "LAN interface (default: $_lan_default): " _lan
_lan=${_lan:-$_lan_default}
if [ -e "/sys/class/net/$_lan" ]; then
break
fi
echo -e "${RED}Interface '$_lan' not found on this system. Try again.${NC}"
done
# Server IP
while true; do
read -rp "Server IP for LAN (default: 192.168.0.10): " _serverip
_serverip=${_serverip:-192.168.0.10}
if [[ "$_serverip" =~ ^([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})$ ]]; then
valid=true
for octet in "${BASH_REMATCH[@]:1}"; do
if [ "$octet" -gt 255 ]; then valid=false; fi
done
[ "$valid" = true ] && break
fi
echo -e "${RED}'$_serverip' is not a valid IPv4 address. Try again.${NC}"
done
# Glob pattern used to match per-IP report filenames under REPORT_PATH
# (e.g. bandata.sh's "for file in $REPORT_IP_GLOB"). This is NOT a
# network range — it's a filename-matching pattern derived from the
# server's own /24, since report files are named after client IPs.
_report_ip_glob="$(echo "$_serverip" | cut -d'.' -f1-3)*"
# Real network range (CIDR) for the LAN this server serves — used to
# restrict the web panel to LAN clients. Derived from the same /24
# assumption as above. For a non-standard subnet, edit RANGE manually
# in /etc/proxymon/proxymon.env after installation.
_range="$(echo "$_serverip" | cut -d'.' -f1-3).0/24"
# Bandwidth limits — validate with numfmt (accepts e.g. 500M, 1G, 1.5G)
read_bandwidth() {
local prompt="$1" default="$2" result
while true; do
read -rp "$prompt" result
result=${result:-$default}
if LC_ALL=C numfmt --from=iec "${result/,/.}" >/dev/null 2>&1; then
echo "$result"
return
fi
echo -e "${RED}'$result' is not a valid size (e.g. 500M, 1G, 1.5G). Try again.${NC}" >&2
done
}
_bw_day=$(read_bandwidth "Max bandwidth per day (default: 1G): " "1G")
_bw_week=$(read_bandwidth "Max bandwidth per week (default: 5G): " "5G")
_bw_month=$(read_bandwidth "Max bandwidth per month (default: 20G): " "20G")
# Unifi Hotspot — only ask if /etc/uhotspot exists
_hotspot_enabled=false
_hotspot_path="/etc/uhotspot"
if [ -d "/etc/uhotspot" ]; then
read -rp "Se ha detectado Unifi Hotspot. ¿Desea activarlo en Bandata? (y/n, default: n): " _hs
if [[ "$_hs" =~ ^[Yy]$ ]]; then
_hotspot_enabled=true
fi
fi
# Auto-update Lightsquid realname
read -rp "Actualizar hostnames en Lightsquid automáticamente? (y/n, default: n): " _realname
if [[ "$_realname" =~ ^[Yy]$ ]]; then
_update_realname=true
else
_update_realname=false
fi
cat > "$env_file" << ENVEOF
# proxymon.env — Bandata configuration
# Generated by proxymon.sh on $(date '+%Y-%m-%d %H:%M:%S')
# Edit manually if needed. Re-run proxymon.sh install to regenerate.
# Network
LAN=${_lan}
SERVER_IP=${_serverip}
RANGE=${_range}
REPORT_IP_GLOB=${_report_ip_glob}
# Paths (defaults — edit only if your setup differs)
LIGHTSQUID_DIR=/var/www/proxymon/lightsquid
REPORT_PATH=$LIGHTSQUID_DIR/report
REALNAME_CFG=$LIGHTSQUID_DIR/realname.cfg
SKIPUSERS_CFG=$LIGHTSQUID_DIR/skipuser.cfg
ACL_PATH=/etc/acl
ACL_MAC_PATH=$ACL_PATH/acl_mac
ACL_SQUID_PATH=$ACL_PATH/acl_squid
ACL_BANDATA_PATH=$ACL_PATH/acl_bandata
ALLOW_LIST=$ACL_BANDATA_PATH/allowdata.txt
BLOCK_LIST_DAY=$ACL_BANDATA_PATH/banday.txt
BLOCK_LIST_WEEK=$ACL_BANDATA_PATH/banweek.txt
BLOCK_LIST_MONTH=$ACL_BANDATA_PATH/banmonth.txt
SQUID_LOG_DIR=/var/log/squid
SQUID_LOG_FILE=$SQUID_LOG_DIR/access.log
# Bandwidth limits
MAX_BANDWIDTH_DAY=${_bw_day}
MAX_BANDWIDTH_WEEK=${_bw_week}
MAX_BANDWIDTH_MONTH=${_bw_month}
# Unifi Hotspot
UNIFI_HOTSPOT_ENABLED=${_hotspot_enabled}
HOTSPOT_PATH=${_hotspot_path}
# Lightsquid realname auto-update
UPDATE_REALNAME=${_update_realname}
ENVEOF
chmod 640 "$env_file"
chown root:www-data "$env_file"
echo -e "${GREEN}$env_file created${NC}"
printf "\n"
}
# ════════════════════════════════════════════════════════════════
# INSTALL FUNCTION
# ════════════════════════════════════════════════════════════════
install_proxymon() {
if [[ -d "/var/www/proxymon" ]]; then
echo -e "${RED}Proxy Monitor is already installed (/var/www/proxymon exists).${NC}"
echo -e "${YELLOW}Use '$0 update' to update it, or '$0 uninstall' to remove it first.${NC}"
exit 1
fi
check_repo
mkdir -p /var/www/proxymon
cp -rf modules/* /var/www/proxymon/
echo -e "${YELLOW}Configuring Apache...${NC}"
if [[ -f "/var/www/proxymon/tools/proxymon.conf" ]]; then
cp -f /var/www/proxymon/tools/proxymon.conf /etc/apache2/sites-available/proxymon.conf
echo -e "${GREEN}Proxymon virtualhost configured${NC}"
fi
if [[ -f "/var/www/proxymon/warning/warning.conf" ]]; then
cp -f /var/www/proxymon/warning/warning.conf /etc/apache2/sites-available/warning.conf
echo -e "${GREEN}Warning virtualhost configured${NC}"
fi
[ -f /etc/apache2/ports.conf.bak ] || cp -f /etc/apache2/ports.conf{,.bak} &>/dev/null
echo -e "${YELLOW}Configuring Squid Monitor...${NC}"
create_proxymon_env
echo -e "${YELLOW}Configuring LightSquid...${NC}"
/var/www/proxymon/lightsquid/lightparser.pl today || true
echo -e "${GREEN}Initial LightSquid report generated${NC}"
echo -e "${YELLOW}Configuring ACL directories and files...${NC}"
# Load env to get ACL paths defined by create_proxymon_env()
# Verify ownership/permissions before sourcing — this file is executed
# as root, so it must not be writable by anyone other than root.
_env_file="/etc/proxymon/proxymon.env"
_env_owner=$(stat -c '%U' "$_env_file" 2>/dev/null)
_env_perms=$(stat -c '%a' "$_env_file" 2>/dev/null)
_env_group_digit="${_env_perms: -2:1}"
_env_other_digit="${_env_perms: -1}"
if [ "$_env_owner" != "root" ] || [[ "$_env_group_digit" =~ [2367] ]] || [[ "$_env_other_digit" =~ [2367] ]]; then
echo -e "${RED}ERROR: $_env_file has unsafe owner/permissions (owner=$_env_owner perms=$_env_perms).${NC}"
echo -e "${RED}Expected owner root with no group/other write access. Refusing to source it.${NC}"
exit 1
fi
source "$_env_file"
echo -e "${YELLOW}Configuring Apache Listen directives...${NC}"
if [ -n "$SERVER_IP" ]; then
# Drop any prior Listen line for these ports (0.0.0.0, a stale IP,
# or a bare "Listen <port>") before adding the current ones.
for port in 18080 18081; do
sed -i -E "/^Listen [^[:space:]]*:${port}\$/d; /^Listen ${port}\$/d" /etc/apache2/ports.conf
done
# 18080 is the app — reachable from the LAN and from loopback
# (e.g. a local Cloudflare Tunnel connecting to the origin).
echo "Listen ${SERVER_IP}:18080" >> /etc/apache2/ports.conf
echo "Listen 127.0.0.1:18080" >> /etc/apache2/ports.conf
# 18081 is Bandata's warning page — LAN-only, no loopback needed.
echo "Listen ${SERVER_IP}:18081" >> /etc/apache2/ports.conf
echo -e "${GREEN}Port 18080 bound to ${SERVER_IP} and 127.0.0.1${NC}"
echo -e "${GREEN}Port 18081 bound to ${SERVER_IP}${NC}"
else
echo -e "${RED}SERVER_IP not set in proxymon.env — cannot configure Listen. Set it and re-run install.${NC}"
exit 1
fi
echo -e "${YELLOW}Restricting Proxymon panel to LAN...${NC}"
if [[ -f /etc/apache2/sites-available/proxymon.conf ]]; then
if [[ "$RANGE" =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}/[0-9]{1,2}$ ]]; then
# 127.0.0.1 allowed alongside the LAN so a local tunnel/trusted
# proxy connecting over loopback to port 18080 still works.
sed -i "s|@@LAN_CIDR@@|$RANGE 127.0.0.1|g" /etc/apache2/sites-available/proxymon.conf
echo -e "${GREEN}Proxymon panel restricted to $RANGE and 127.0.0.1${NC}"
else
echo -e "${RED}RANGE='$RANGE' in $_env_file is not a valid CIDR (e.g. 192.168.10.0/24).${NC}"
echo -e "${RED}Fix RANGE in $_env_file, then re-run install. Aborting before enabling any site.${NC}"
exit 1
fi
fi
# Create ACL directories
mkdir -p "$ACL_PATH" "$ACL_MAC_PATH" "$ACL_SQUID_PATH" "$ACL_BANDATA_PATH"
chmod 755 "$ACL_PATH" "$ACL_MAC_PATH" "$ACL_SQUID_PATH" "$ACL_BANDATA_PATH"
chown root:root "$ACL_PATH" "$ACL_MAC_PATH" "$ACL_SQUID_PATH" "$ACL_BANDATA_PATH"
echo -e "${GREEN}ACL directories created${NC}"
# Create ACL files
touch "$ALLOW_LIST" "$BLOCK_LIST_DAY" "$BLOCK_LIST_WEEK" "$BLOCK_LIST_MONTH"
chmod 644 "$ALLOW_LIST" "$BLOCK_LIST_DAY" "$BLOCK_LIST_WEEK" "$BLOCK_LIST_MONTH"
chown root:root "$ALLOW_LIST" "$BLOCK_LIST_DAY" "$BLOCK_LIST_WEEK" "$BLOCK_LIST_MONTH"
echo -e "${GREEN}ACL files created${NC}"
# Create LightSquid report directory if it does not exist
mkdir -p "$REPORT_PATH"
chmod 755 "$REPORT_PATH"
chown www-data:www-data "$REPORT_PATH"
echo -e "${GREEN}LightSquid report directory ready${NC}"
echo -e "${YELLOW}Downloading ACL lists...${NC}"
wget -q --show-progress -N https://raw.githubusercontent.com/maravento/blackweb/refs/heads/master/bwupdate/lst/blocktlds.txt -O "$ACL_SQUID_PATH/blocktlds.txt"
chmod 644 "$ACL_SQUID_PATH/blocktlds.txt"
chown root:root "$ACL_SQUID_PATH/blocktlds.txt"
echo -e "${GREEN}blocktlds.txt downloaded${NC}"
wget -q --show-progress -N https://raw.githubusercontent.com/maravento/blackweb/refs/heads/master/bwupdate/lst/debugbl.txt -O "$ACL_SQUID_PATH/blockdomains.txt"
chmod 644 "$ACL_SQUID_PATH/blockdomains.txt"
chown root:root "$ACL_SQUID_PATH/blockdomains.txt"
echo -e "${GREEN}blockdomains.txt downloaded${NC}"
crontab -l 2>/dev/null | {
grep -v "bandata.sh"
echo "*/5 * * * * /var/www/proxymon/tools/bandata.sh >> /var/log/bandata.log 2>&1"
} | crontab -
echo -e "${GREEN}Squid Monitor crontab added${NC}"
echo -e "${YELLOW}Configuring SARG...${NC}"
mkdir -p /var/www/proxymon/sarg/squid-reports
[ -f /etc/sarg/sarg.conf.bak ] || cp -f /etc/sarg/sarg.conf{,.bak} &>/dev/null
sed -i 's|output_dir /var/lib/sarg|output_dir /var/www/proxymon/sarg/squid-reports|g' /etc/sarg/sarg.conf
sed -i 's|^resolve_ip .*|resolve_ip no|g' /etc/sarg/sarg.conf
sed -i 's|lastlog 0|lastlog 7|g' /etc/sarg/sarg.conf
HOSTNAME=$(hostname)
[ -f /etc/sarg/usertab.bak ] || cp -f /etc/sarg/usertab{,.bak} &>/dev/null
if [ -n "$SERVER_IP" ]; then
if ! grep -q "^${SERVER_IP//./\\.}[[:space:]]" /etc/sarg/usertab; then
echo "$SERVER_IP $HOSTNAME" >> /etc/sarg/usertab
echo -e "${GREEN}Added $SERVER_IP $HOSTNAME to usertab${NC}"
fi
else
echo -e "${RED}SERVER_IP not set in proxymon.env — skipping usertab entry${NC}"
fi
echo -e "${YELLOW}🔧 Generating Initial SARG Report...${NC}"
timeout 30 /usr/bin/sarg -f /etc/sarg/sarg.conf -l /var/log/squid/access.log > /dev/null 2>&1 || true
echo -e "${GREEN}Initial SARG report generated${NC}"
echo -e "${YELLOW}Configuring SquidAnalyzer...${NC}"
chmod -R 755 /var/www/proxymon/squidanalyzer
mkdir -p /var/www/proxymon/squidanalyzer/output
rm -rf /var/www/proxymon/squidanalyzer/output/* 2>/dev/null
chown -R www-data:www-data /var/www/proxymon/squidanalyzer
cd /var/www/proxymon/squidanalyzer || exit 1
sudo -u www-data perl -I. ./squid-analyzer -c etc/squidanalyzer.conf -d &> /dev/null || true
cd - > /dev/null
# ── Consolidated www-data crontab update (single atomic write) ──
# All www-data cron entries (LightSquid, SARG daily/weekly, SquidAnalyzer)
# are rewritten together to avoid leaving the crontab in a partial
# state if the installer is interrupted between operations.
sudo -u www-data crontab -l 2>/dev/null | {
grep -v "lightparser.pl" \
| grep -v "sarg.*sarg.conf.*access.log" \
| grep -v "find.*sarg.*squid-reports" \
| grep -v "squid-analyzer"
echo "*/10 * * * * /var/www/proxymon/lightsquid/lightparser.pl today"
echo "@daily /usr/bin/sarg -f /etc/sarg/sarg.conf -l /var/log/squid/access.log"
echo '@weekly find /var/www/proxymon/sarg/squid-reports -name "2*" -mtime +30 -type d -exec rm -rf "{}" \;'
echo '0 2 * * * cd /var/www/proxymon/squidanalyzer && perl -I. ./squid-analyzer -c etc/squidanalyzer.conf'
} | sudo -u www-data crontab -
echo -e "${GREEN}www-data crontab entries updated (LightSquid, SARG, SquidAnalyzer)${NC}"
echo -e "${YELLOW} Updating Prefork MPM...${NC}"
[ -f /etc/apache2/mods-available/mpm_prefork.conf.bak ] || cp -f /etc/apache2/mods-available/mpm_prefork.conf{,.bak} &>/dev/null
sed -i \
-e 's/^\(StartServers[[:space:]]*\)5/\110/' \
-e 's/^\(MinSpareServers[[:space:]]*\)5/\110/' \
-e 's/^\(MaxSpareServers[[:space:]]*\)10/\115/' \
-e 's/^\(MaxRequestWorkers[[:space:]]*\)150/\1200/' \
-e 's/^\(MaxConnectionsPerChild[[:space:]]*\)0/\11000/' \
/etc/apache2/mods-available/mpm_prefork.conf
echo -e "${YELLOW} Updating PHP...${NC}"
[ -f /etc/php/$PHP_VERSION/apache2/php.ini.bak ] || cp -f /etc/php/$PHP_VERSION/apache2/php.ini{,.bak} &>/dev/null
sed -i \
-e 's/^\s*;*\s*max_execution_time\s*=.*/max_execution_time = 120/' \
-e 's/^\s*max_input_time\s*=.*/max_input_time = 120/' \
-e 's/^;\s*max_input_time\s*=.*/max_input_time = 120/' \
-e 's/^\s*;*\s*memory_limit\s*=.*/memory_limit = 1024M/' \
-e 's/^\s*;*\s*post_max_size\s*=.*/post_max_size = 64M/' \
-e 's/^\s*;*\s*upload_max_filesize\s*=.*/upload_max_filesize = 64M/' \
-e 's/^\s*;*\s*opcache.memory_consumption\s*=.*/opcache.memory_consumption = 256/' \
-e 's/^\s*;*\s*realpath_cache_size\s*=.*/realpath_cache_size = 16M/' \
-e 's/^\s*;*\s*allow_url_fopen\s*=.*/allow_url_fopen = On/' \
/etc/php/$PHP_VERSION/apache2/php.ini
# Hardening
echo -e "${YELLOW} Updating Apache2 Security...${NC}"
if [ -f /etc/apache2/conf-available/security.conf ]; then
[ -f /etc/apache2/conf-available/security.conf.bak ] || cp -f /etc/apache2/conf-available/security.conf{,.bak} &>/dev/null
else
touch /etc/apache2/conf-available/security.conf
fi
sed -i "s/^#*\s*ServerSignature.*/ServerSignature Off/" /etc/apache2/conf-available/security.conf
sed -i "s/^#*\s*ServerTokens.*/ServerTokens Prod/" /etc/apache2/conf-available/security.conf
declare -A headers=(
["X-Content-Type-Options"]="nosniff"
["X-Frame-Options"]="sameorigin"
["X-XSS-Protection"]="1; mode=block"
["Referrer-Policy"]="strict-origin-when-cross-origin"
)
for name in "${!headers[@]}"; do
value="${headers[$name]}"
if grep -q "Header set $name" /etc/apache2/conf-available/security.conf; then
sed -i "s|^#*\s*Header set $name.*|Header set $name \"$value\"|" /etc/apache2/conf-available/security.conf
else
echo "Header set $name \"$value\"" >> /etc/apache2/conf-available/security.conf
fi
done
grep -q "^FileETag None" /etc/apache2/conf-available/security.conf || \
echo 'FileETag None' >> /etc/apache2/conf-available/security.conf
grep -q "^Header unset ETag" /etc/apache2/conf-available/security.conf || \
echo 'Header unset ETag' >> /etc/apache2/conf-available/security.conf
grep -q "^Timeout" /etc/apache2/conf-available/security.conf || \
echo 'Timeout 60' >> /etc/apache2/conf-available/security.conf
[ -f /etc/apache2/apache2.conf.bak ] || cp -f /etc/apache2/apache2.conf{,.bak} &>/dev/null
sed -i -E '/^[[:space:]]*#/!s/^([[:space:]]*Options[[:space:]]+)(-?Indexes[[:space:]]+)?FollowSymLinks[[:space:]]*$/\1-Indexes +FollowSymLinks/' /etc/apache2/apache2.conf
a2enmod headers &>/dev/null
a2enconf security &>/dev/null
echo -e "${YELLOW}Configuring SquidAI...${NC}"
mkdir -p /etc/proxymon
if [ ! -f /etc/proxymon/.env ]; then
cat > /etc/proxymon/.env << 'EOF'
# SquidAI — LLM Provider Configuration
# ─────────────────────────────────────────────────────────────────
# Uncomment ONE provider block and fill in your credentials.
# Leave LLM_MODEL empty if the model is already part of the URL.
# LLM_API_KEY can be left empty for local providers (Ollama, LM Studio).
#
# LLM_RESPONSE_FORMAT tells the worker how to read the response:
# openai → choices[0].message.content (most cloud providers)
# ollama → message.content (Ollama)
# gemini → passthrough, no transform (Google Gemini)
# ─────────────────────────────────────────────────────────────────
# ── Active provider (uncomment one block below) ──────────────────
LLM_URL=
LLM_API_KEY=
LLM_MODEL=
LLM_RESPONSE_FORMAT=openai
# ─────────────────────────────────────────────────────────────────
# PROVIDER EXAMPLES — copy the values above and replace
# ─────────────────────────────────────────────────────────────────
# Cloudflare Workers AI (model goes in the URL, no LLM_MODEL needed)
# LLM_URL=https://api.cloudflare.com/client/v4/accounts/ACCOUNT_ID/ai/run/@cf/meta/llama-3.1-8b-instruct-fast
# LLM_API_KEY=your_token
# LLM_RESPONSE_FORMAT=openai
# OpenAI
# LLM_URL=https://api.openai.com/v1/chat/completions
# LLM_API_KEY=sk-...
# LLM_MODEL=gpt-4o-mini
# LLM_RESPONSE_FORMAT=openai
# Groq (fast inference, free tier available)
# LLM_URL=https://api.groq.com/openai/v1/chat/completions
# LLM_API_KEY=gsk_...
# LLM_MODEL=llama-3.1-8b-instant
# LLM_RESPONSE_FORMAT=openai
# OpenRouter (access to many models, free tier available)
# LLM_URL=https://openrouter.ai/api/v1/chat/completions
# LLM_API_KEY=sk-or-...
# LLM_MODEL=mistralai/mistral-7b-instruct
# LLM_RESPONSE_FORMAT=openai
# Together AI
# LLM_URL=https://api.together.xyz/v1/chat/completions
# LLM_API_KEY=your_key
# LLM_MODEL=meta-llama/Llama-3-8b-chat-hf
# LLM_RESPONSE_FORMAT=openai
# Ollama (local, no API key required)
# LLM_URL=http://localhost:11434/api/chat
# LLM_API_KEY=
# LLM_MODEL=llama3.1
# LLM_RESPONSE_FORMAT=ollama
# LM Studio (local, OpenAI-compatible)
# LLM_URL=http://localhost:1234/v1/chat/completions
# LLM_API_KEY=lm-studio
# LLM_MODEL=
# LLM_RESPONSE_FORMAT=openai
# Google Gemini
# LLM_URL=https://generativelanguage.googleapis.com/v1beta/models/gemini-pro:generateContent?key=YOUR_KEY
# LLM_API_KEY=
# LLM_MODEL=
# LLM_RESPONSE_FORMAT=gemini
EOF
fi
chmod 640 /etc/proxymon/.env
chown root:www-data /etc/proxymon/.env
chmod 750 /etc/proxymon
chown root:www-data /etc/proxymon
echo -e "${GREEN}SquidAI config directory created: /etc/proxymon/${NC}"
echo -e "${YELLOW}Edit /etc/proxymon/.env and set your LLM credentials${NC}"
echo -e "${YELLOW} Setting Permissions...${NC}"
find /var/www/proxymon -type d -exec chmod 755 {} +
find /var/www/proxymon -type f -exec chmod 644 {} +
find /var/www/proxymon -type f -name "*.cgi" -exec chmod +x {} +
chmod +x /var/www/proxymon/tools/bandata.sh
chmod +x /var/www/proxymon/lightsquid/lightparser.pl
chown -R www-data:www-data /var/www/proxymon
if getent group proxy >/dev/null; then
usermod -aG proxy www-data
else
echo -e "${RED}ERROR: group 'proxy' not found (expected to be created by the squid package).${NC}"
echo -e "${RED}Ensure squid is installed before running this step.${NC}"
exit 1
fi
chown root:root /etc/squid/squid.conf
chmod 644 /etc/squid/squid.conf
echo -e "${YELLOW} Setting Logs...${NC}"
touch /var/log/apache2/{warning_access,warning_error,proxymon_access,proxymon_error}.log
chown root:adm /var/log/apache2/{warning_access,warning_error,proxymon_access,proxymon_error}.log
chmod 640 /var/log/apache2/{warning_access,warning_error,proxymon_access,proxymon_error}.log
shopt -s nullglob
squid_logs=(/var/log/squid/*.log)
if [ ${#squid_logs[@]} -gt 0 ]; then
chown proxy:proxy "${squid_logs[@]}"
chmod 640 "${squid_logs[@]}"
else
echo -e "${YELLOW}No /var/log/squid/*.log files found yet — skipping permissions${NC}"
fi
shopt -u nullglob
echo -e "${YELLOW} Enabling Apache Modules...${NC}"
a2dismod mpm_event 2>/dev/null || true
# mod_cgid requires a threaded MPM (worker/event) and is incompatible
# with mpm_prefork enabled below. Use mod_cgi instead.
a2dismod cgid 2>/dev/null || true
for mod in mpm_prefork "php$PHP_VERSION" cgi rewrite; do
if a2enmod "$mod" 2>/dev/null; then
continue
fi
# Fallback for systems where the module is registered as plain "php"
if [[ "$mod" == "php$PHP_VERSION" ]] && a2enmod php 2>/dev/null; then
continue
fi
echo -e "${RED}ERROR: failed to enable Apache module '$mod'. Is it installed?${NC}"
exit 1
done
echo -e "${YELLOW} Enabling Apache Sites...${NC}"
a2ensite proxymon.conf || { echo -e "${RED}Failed to enable proxymon.conf${NC}"; exit 1; }
a2ensite warning.conf || { echo -e "${RED}Failed to enable warning.conf${NC}"; exit 1; }
echo -e "${GREEN} Restarting Cron...${NC}"
systemctl restart cron
echo -e "${YELLOW} Restarting Apache2...${NC}"
systemctl daemon-reload
if ! apachectl -t -D DUMP_INCLUDES -S &>/dev/null; then
echo -e "${RED}Apache configuration test failed. Disabling the sites just enabled so a future${NC}"
echo -e "${RED}unrelated Apache restart (reboot, unattended-upgrades, etc.) doesn't break on it.${NC}"
a2dissite proxymon.conf 2>/dev/null || true
a2dissite warning.conf 2>/dev/null || true
echo -e "${RED}Run 'apachectl -t' to see the error, fix the configuration, then re-run install.${NC}"
exit 1
fi
echo -e "${GREEN}Apache configuration OK${NC}"
systemctl restart apache2
echo -e "${GREEN} Check Active Apache sites:${NC}"
a2query -s
echo -e "${GREEN}Proxy Monitor installed successfully${NC}"
echo -e "${GREEN}Access Proxy Monitor: http://${SERVER_IP}:18080${NC}"
echo -e "${GREEN}Access Warning Portal: http://${SERVER_IP}:18081${NC}"
}
# ════════════════════════════════════════════════════════════════
# UPDATE FUNCTION
# ════════════════════════════════════════════════════════════════
# Refreshes code under /var/www/proxymon only. Does NOT touch anything
# outside that path: no Apache/PHP/SARG/cron config, no ACL lists, no
# proxymon.env, no service restarts. Preserves live data that lives
# inside /var/www/proxymon but isn't part of the repo tree.
update_proxymon() {
if [[ ! -d "/var/www/proxymon" ]]; then
echo -e "${RED}Proxy Monitor is not installed. Run '$0 install' first.${NC}"
exit 1
fi
if ! command -v rsync &>/dev/null; then
echo -e "${RED}rsync is required for 'update' but is not installed.${NC}"
echo -e "${YELLOW}Install it with: apt-get install rsync${NC}"
exit 1
fi
_user_home=$(getent passwd "$local_user" | cut -d: -f6)
if [ -z "$_user_home" ] || [ ! -d "$_user_home" ]; then
echo -e "${RED}Could not resolve home directory for user '$local_user'${NC}"
exit 1
fi
_backup_dir="$_user_home/proxymonbak/$(date '+%Y%m%d_%H%M%S')"
mkdir -p "$_backup_dir"
# Live data that isn't part of the modules/ repo tree — backed up before
# the file swap and restored after. Never modified in place.
_protected_rel=(
"lightsquid/report"
"lightsquid/realname.cfg"
"lightsquid/skipuser.cfg"
"sarg/squid-reports"
"squidmon/etc/config"
"squidanalyzer/output"
"sqstat/config.inc.php"
)
check_repo
echo -e "${YELLOW}Stopping Apache...${NC}"
systemctl stop apache2
echo -e "${YELLOW}Backing up live data to $_backup_dir ...${NC}"
_backup_sources=()
for _r in "${_protected_rel[@]}"; do
if [ -e "/var/www/proxymon/$_r" ]; then
_backup_sources+=("/var/www/proxymon/./$_r")
fi
done
if [ ${#_backup_sources[@]} -gt 0 ]; then
rsync -a --relative "${_backup_sources[@]}" "$_backup_dir/"
echo -e "${GREEN}Backed up: ${_protected_rel[*]}${NC}"
else
echo -e "${YELLOW}Nothing to back up yet (first update on this install)${NC}"
fi
echo -e "${YELLOW}Replacing Proxy Monitor code...${NC}"
cp -rf modules/* /var/www/proxymon/
echo -e "${YELLOW}Restoring live data from backup...${NC}"
rsync -a "$_backup_dir/" /var/www/proxymon/
echo -e "${GREEN}Live data restored${NC}"
echo -e "${YELLOW}Setting permissions...${NC}"
find /var/www/proxymon -type d -exec chmod 755 {} +
find /var/www/proxymon -type f -exec chmod 644 {} +
find /var/www/proxymon -type f -name "*.cgi" -exec chmod +x {} +
[ -f /var/www/proxymon/tools/bandata.sh ] && chmod +x /var/www/proxymon/tools/bandata.sh
[ -f /var/www/proxymon/lightsquid/lightparser.pl ] && chmod +x /var/www/proxymon/lightsquid/lightparser.pl
chown -R www-data:www-data /var/www/proxymon
echo -e "${GREEN}Permissions set${NC}"
echo -e "${YELLOW}Starting Apache...${NC}"
if systemctl start apache2; then
echo -e "${GREEN}Apache started${NC}"
else
echo -e "${RED}Apache failed to start — check: systemctl status apache2${NC}"
exit 1
fi
echo -e "${YELLOW}Backup kept at $_backup_dir (not deleted automatically).${NC}"
echo -e "${GREEN}Proxy Monitor updated successfully${NC}"
}
# ════════════════════════════════════════════════════════════════
# UNINSTALL FUNCTION
# ════════════════════════════════════════════════════════════════
uninstall_proxymon() {
echo -e "${YELLOW} Uninstalling Proxy Monitor...${NC}"
if [[ ! -d "/var/www/proxymon" ]]; then
if ! (sudo crontab -l 2>/dev/null | grep -q "bandata.sh") && \
! (sudo -u www-data crontab -l 2>/dev/null | grep -q "lightparser.pl\|sarg\|squid-analyzer") && \
[[ ! -d "/etc/proxymon" ]]; then
echo -e "${YELLOW} Proxy Monitor is not installed${NC}"
return 0
fi
fi
# ── Consolidated www-data crontab cleanup (single atomic write) ──
if sudo -u www-data crontab -l 2>/dev/null \
| grep -v "lightparser.pl" \
| grep -v "sarg.*sarg.conf.*access.log" \
| grep -v "find.*sarg.*squid-reports" \
| grep -v "squid-analyzer" \
| sudo -u www-data crontab - 2>/dev/null; then
echo -e "${GREEN}LightSquid, SARG and SquidAnalyzer crontab entries removed${NC}"
else
echo -e "${YELLOW}WARNING: failed to update www-data crontab — entries may remain${NC}"
fi
if crontab -l 2>/dev/null | grep -v "bandata.sh" | crontab - 2>/dev/null; then
echo -e "${GREEN}Squid Monitor crontab removed${NC}"
else
echo -e "${YELLOW}WARNING: failed to update root crontab — bandata.sh entry may remain${NC}"
fi
if command -v iptables >/dev/null 2>&1; then
# Remove FORWARD/INPUT jumps into the Bandata chains by rule number
# (matched by target name, so this works regardless of which LAN
# interface bandata.sh was configured with).
for entry in "FORWARD:BANDATA_FWD" "INPUT:BANDATA_IN"; do
base_chain="${entry%%:*}"
target_chain="${entry##*:}"
while true; do
rulenum=$(iptables -L "$base_chain" --line-numbers -n 2>/dev/null | awk -v t="$target_chain" '$2==t{print $1; exit}')
[ -n "$rulenum" ] || break
iptables -D "$base_chain" "$rulenum" 2>/dev/null || break
done
done
# Remove the NAT redirect to the warning portal
while true; do
rulenum=$(iptables -t nat -L PREROUTING --line-numbers -n 2>/dev/null | awk '/match-set bandata/{print $1; exit}')
[ -n "$rulenum" ] || break
iptables -t nat -D PREROUTING "$rulenum" 2>/dev/null || break
done
# Flush and remove the now-unreferenced Bandata chains
for chain in BANDATA_FWD BANDATA_IN; do
if iptables -L "$chain" -n &>/dev/null; then
iptables -F "$chain" 2>/dev/null || true
iptables -X "$chain" 2>/dev/null || true
fi
done
echo -e "${GREEN}Bandata iptables rules removed${NC}"
fi
if command -v ipset >/dev/null 2>&1 && ipset list bandata &>/dev/null; then
ipset destroy bandata 2>/dev/null && echo -e "${GREEN}Bandata ipset destroyed${NC}" \
|| echo -e "${YELLOW}WARNING: could not destroy ipset 'bandata' — remove manually if needed${NC}"
fi
if [[ -f "/etc/sarg/sarg.conf.bak" ]]; then
mv -f /etc/sarg/sarg.conf.bak /etc/sarg/sarg.conf
echo -e "${GREEN}SARG configuration restored${NC}"
fi
if [[ -f "/etc/sarg/usertab.bak" ]]; then
mv -f /etc/sarg/usertab.bak /etc/sarg/usertab
echo -e "${GREEN}SARG usertab restored${NC}"
fi
if [[ -f "/etc/apache2/mods-available/mpm_prefork.conf.bak" ]]; then
mv -f /etc/apache2/mods-available/mpm_prefork.conf.bak /etc/apache2/mods-available/mpm_prefork.conf
echo -e "${GREEN}mpm_prefork configuration restored${NC}"
fi
PHP_VERSION=""
if command -v php >/dev/null 2>&1; then
PHP_VERSION=$(php -r "echo PHP_MAJOR_VERSION.'.'.PHP_MINOR_VERSION;" 2>/dev/null || true)
fi
if [[ -n "$PHP_VERSION" && -f "/etc/php/$PHP_VERSION/apache2/php.ini.bak" ]]; then
mv -f "/etc/php/$PHP_VERSION/apache2/php.ini.bak" "/etc/php/$PHP_VERSION/apache2/php.ini"
echo -e "${GREEN}php.ini restored${NC}"
fi
if [[ -f "/etc/apache2/conf-available/security.conf.bak" ]]; then
mv -f /etc/apache2/conf-available/security.conf.bak /etc/apache2/conf-available/security.conf
echo -e "${GREEN}security.conf restored${NC}"
fi
if [[ -f "/etc/apache2/apache2.conf.bak" ]]; then