Last active 1 month ago

beanman109 revised this gist 1 month ago. Go to revision

1 file changed, 1 insertion, 1 deletion

readme.md

@@ -1 +1 @@
1 - wget -qO- 'https://beanman.net/gist/beanman109/setup-https-squid-proxy/raw/HEAD/setup-https-squid-proxy.sh' | bash
1 + wget -O setup-https-squid-proxy.sh 'https://beanman.net/gist/beanman109/setup-https-squid-proxy/raw/HEAD/setup-https-squid-proxy.sh' && chmod +x setup-https-squid-proxy.sh && ./setup-https-squid-proxy.sh

beanman109 revised this gist 1 month ago. Go to revision

1 file changed, 1 insertion

readme.md(file created)

@@ -0,0 +1 @@
1 + wget -qO- 'https://beanman.net/gist/beanman109/setup-https-squid-proxy/raw/HEAD/setup-https-squid-proxy.sh' | bash

beanman109 revised this gist 1 month ago. Go to revision

No changes

beanman109 revised this gist 1 month ago. Go to revision

1 file changed, 620 insertions

setup-https-squid-proxy.sh(file created)

@@ -0,0 +1,620 @@
1 + #!/usr/bin/env bash
2 + set -euo pipefail
3 +
4 + # ============================================================
5 + # Interactive Squid HTTPS Proxy Installer with Let's Encrypt
6 + # Debian 12/13
7 + #
8 + # Creates:
9 + # Firefox/FoxyProxy -> HTTPS proxy -> Squid -> Internet
10 + #
11 + # IMPORTANT:
12 + # In FoxyProxy, use the DOMAIN NAME, not the IP address.
13 + # ============================================================
14 +
15 + RED="\033[31m"
16 + GREEN="\033[32m"
17 + YELLOW="\033[33m"
18 + BLUE="\033[34m"
19 + RESET="\033[0m"
20 +
21 + log() { echo -e "${GREEN}[OK]${RESET} $*"; }
22 + info() { echo -e "${BLUE}[INFO]${RESET} $*"; }
23 + warn() { echo -e "${YELLOW}[WARN]${RESET} $*"; }
24 + die() { echo -e "${RED}[ERROR]${RESET} $*" >&2; exit 1; }
25 +
26 + SQUID_USER="proxy"
27 + SQUID_GROUP="proxy"
28 + STOPPED_SERVICES=()
29 +
30 + require_root() {
31 + if [[ "${EUID}" -ne 0 ]]; then
32 + die "Run this script as root."
33 + fi
34 + }
35 +
36 + ask() {
37 + local prompt="$1"
38 + local default="${2:-}"
39 + local value
40 +
41 + if [[ -n "$default" ]]; then
42 + read -rp "$prompt [$default]: " value
43 + echo "${value:-$default}"
44 + else
45 + read -rp "$prompt: " value
46 + echo "$value"
47 + fi
48 + }
49 +
50 + ask_password_confirmed() {
51 + local pass1 pass2
52 +
53 + while true; do
54 + read -rsp "Proxy password: " pass1
55 + printf '\n' >&2
56 +
57 + read -rsp "Confirm proxy password: " pass2
58 + printf '\n' >&2
59 +
60 + if [[ -z "$pass1" ]]; then
61 + warn "Password cannot be empty." >&2
62 + continue
63 + fi
64 +
65 + if [[ "$pass1" != "$pass2" ]]; then
66 + warn "Passwords do not match." >&2
67 + continue
68 + fi
69 +
70 + printf '%s' "$pass1"
71 + return 0
72 + done
73 + }
74 +
75 + validate_domain() {
76 + local domain="$1"
77 +
78 + if [[ ! "$domain" =~ ^[A-Za-z0-9.-]+\.[A-Za-z]{2,}$ ]]; then
79 + die "Invalid domain: $domain"
80 + fi
81 +
82 + if [[ "$domain" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
83 + die "Do not use an IP address. Use a real hostname, e.g. proxy.example.com"
84 + fi
85 + }
86 +
87 + validate_port() {
88 + local port="$1"
89 +
90 + if ! [[ "$port" =~ ^[0-9]+$ ]]; then
91 + die "Invalid port: $port"
92 + fi
93 +
94 + if (( port < 1 || port > 65535 )); then
95 + die "Port must be between 1 and 65535."
96 + fi
97 + }
98 +
99 + detect_proxy_user_group() {
100 + if id proxy >/dev/null 2>&1; then
101 + SQUID_USER="proxy"
102 + elif id squid >/dev/null 2>&1; then
103 + SQUID_USER="squid"
104 + else
105 + SQUID_USER="proxy"
106 + fi
107 +
108 + if getent group proxy >/dev/null 2>&1; then
109 + SQUID_GROUP="proxy"
110 + elif getent group squid >/dev/null 2>&1; then
111 + SQUID_GROUP="squid"
112 + else
113 + SQUID_GROUP="$SQUID_USER"
114 + fi
115 + }
116 +
117 + install_packages() {
118 + info "Installing required packages..."
119 +
120 + apt update
121 +
122 + if apt-cache show squid-openssl >/dev/null 2>&1; then
123 + apt install -y squid-openssl apache2-utils certbot openssl curl ca-certificates dnsutils
124 + else
125 + warn "squid-openssl package was not found."
126 + warn "Installing normal squid, but HTTPS proxy listener may not work."
127 + apt install -y squid apache2-utils certbot openssl curl ca-certificates dnsutils
128 + fi
129 +
130 + log "Packages installed."
131 + }
132 +
133 + check_squid_openssl() {
134 + info "Checking Squid OpenSSL support..."
135 +
136 + if squid -v 2>/dev/null | grep -qi openssl; then
137 + log "Squid appears to have OpenSSL support."
138 + else
139 + warn "Could not confirm OpenSSL support from squid -v."
140 + warn "If https_port fails later, install squid-openssl or use a TLS wrapper."
141 + fi
142 + }
143 +
144 + check_dns() {
145 + local domain="$1"
146 +
147 + info "Checking DNS for $domain..."
148 +
149 + local resolved_v4=""
150 + local resolved_v6=""
151 +
152 + resolved_v4="$(dig +short A "$domain" | tail -n1 || true)"
153 + resolved_v6="$(dig +short AAAA "$domain" | tail -n1 || true)"
154 +
155 + if [[ -z "$resolved_v4" && -z "$resolved_v6" ]]; then
156 + warn "No A or AAAA record found for $domain."
157 + warn "Let's Encrypt will fail unless this hostname points to this server."
158 + read -rp "Continue anyway? [y/N]: " yn
159 + [[ "${yn,,}" == "y" ]] || die "Stopped."
160 + else
161 + if [[ -n "$resolved_v4" ]]; then
162 + log "$domain A record: $resolved_v4"
163 + fi
164 +
165 + if [[ -n "$resolved_v6" ]]; then
166 + log "$domain AAAA record: $resolved_v6"
167 + else
168 + warn "$domain has no AAAA record. That is fine if you only want IPv4."
169 + fi
170 + fi
171 +
172 + return 0
173 + }
174 +
175 + open_firewall() {
176 + local port="$1"
177 +
178 + info "Attempting to open firewall ports 80 and $port..."
179 +
180 + if command -v ufw >/dev/null 2>&1 && ufw status 2>/dev/null | grep -qi active; then
181 + ufw allow 80/tcp || true
182 + ufw allow "$port/tcp" || true
183 + log "UFW rules added."
184 + else
185 + warn "UFW not active or not installed. Skipping UFW rules."
186 + fi
187 +
188 + if command -v iptables >/dev/null 2>&1; then
189 + iptables -C INPUT -p tcp --dport 80 -j ACCEPT 2>/dev/null || iptables -I INPUT -p tcp --dport 80 -j ACCEPT
190 + iptables -C INPUT -p tcp --dport "$port" -j ACCEPT 2>/dev/null || iptables -I INPUT -p tcp --dport "$port" -j ACCEPT
191 + log "Temporary iptables rules added."
192 + warn "iptables rules may not persist after reboot unless your VPS image saves them."
193 + fi
194 + }
195 +
196 + stop_port_80_services_if_needed() {
197 + STOPPED_SERVICES=()
198 +
199 + if ss -tulpn | grep -qE '(:80[[:space:]]|:80$|:80,)'; then
200 + warn "Something appears to be using port 80."
201 + echo
202 + ss -tulpn | grep ':80' || true
203 + echo
204 +
205 + read -rp "Temporarily stop nginx/apache2/caddy if running so Certbot can use port 80? [Y/n]: " yn
206 + yn="${yn:-Y}"
207 +
208 + if [[ "${yn,,}" == "y" ]]; then
209 + for svc in nginx apache2 caddy; do
210 + if systemctl is-active --quiet "$svc" 2>/dev/null; then
211 + systemctl stop "$svc"
212 + STOPPED_SERVICES+=("systemd:$svc")
213 + warn "Stopped $svc temporarily."
214 + fi
215 + done
216 + fi
217 + fi
218 + }
219 +
220 + restart_stopped_services() {
221 + if [[ ${#STOPPED_SERVICES[@]} -gt 0 ]]; then
222 + info "Restarting services stopped for Certbot..."
223 +
224 + for item in "${STOPPED_SERVICES[@]}"; do
225 + kind="${item%%:*}"
226 + name="${item#*:}"
227 +
228 + if [[ "$kind" == "systemd" ]]; then
229 + systemctl start "$name" || warn "Could not restart $name."
230 + fi
231 + done
232 + fi
233 + }
234 +
235 + get_certificate() {
236 + local domain="$1"
237 + local email="$2"
238 +
239 + if [[ -f "/etc/letsencrypt/live/$domain/fullchain.pem" && -f "/etc/letsencrypt/live/$domain/privkey.pem" ]]; then
240 + log "Existing Let's Encrypt cert found for $domain."
241 + read -rp "Use existing certificate? [Y/n]: " yn
242 + yn="${yn:-Y}"
243 +
244 + if [[ "${yn,,}" == "y" ]]; then
245 + return 0
246 + fi
247 + fi
248 +
249 + stop_port_80_services_if_needed
250 +
251 + info "Requesting Let's Encrypt certificate for $domain..."
252 +
253 + if [[ -n "$email" ]]; then
254 + certbot certonly --standalone \
255 + -d "$domain" \
256 + --non-interactive \
257 + --agree-tos \
258 + --email "$email"
259 + else
260 + certbot certonly --standalone \
261 + -d "$domain" \
262 + --non-interactive \
263 + --agree-tos \
264 + --register-unsafely-without-email
265 + fi
266 +
267 + restart_stopped_services
268 +
269 + log "Certificate issued."
270 + }
271 +
272 + copy_certs() {
273 + local domain="$1"
274 +
275 + info "Copying certificate files into /etc/squid/certs..."
276 +
277 + mkdir -p /etc/squid/certs
278 +
279 + cp "/etc/letsencrypt/live/$domain/fullchain.pem" /etc/squid/certs/proxy-fullchain.pem
280 + cp "/etc/letsencrypt/live/$domain/privkey.pem" /etc/squid/certs/proxy-privkey.pem
281 +
282 + chown -R "$SQUID_USER:$SQUID_GROUP" /etc/squid/certs
283 + chmod 644 /etc/squid/certs/proxy-fullchain.pem
284 + chmod 600 /etc/squid/certs/proxy-privkey.pem
285 +
286 + log "Certificates copied."
287 + }
288 +
289 + create_auth() {
290 + local username="$1"
291 + local password="$2"
292 +
293 + info "Creating Squid username/password auth..."
294 +
295 + touch /etc/squid/passwd
296 + chown "$SQUID_USER:$SQUID_GROUP" /etc/squid/passwd
297 + chmod 640 /etc/squid/passwd
298 +
299 + printf '%s\n' "$password" | htpasswd -m -i /etc/squid/passwd "$username" >/dev/null
300 +
301 + chown "$SQUID_USER:$SQUID_GROUP" /etc/squid/passwd
302 + chmod 640 /etc/squid/passwd
303 +
304 + log "Proxy user created: $username"
305 + }
306 +
307 + test_auth_helper() {
308 + local username="$1"
309 + local password="$2"
310 +
311 + info "Testing Squid auth helper directly..."
312 +
313 + local result
314 + result="$(printf '%s %s\n' "$username" "$password" | /usr/lib/squid/basic_ncsa_auth /etc/squid/passwd || true)"
315 +
316 + if echo "$result" | grep -q '^OK'; then
317 + log "Squid auth helper accepted the username/password."
318 + else
319 + warn "Squid auth helper did not accept the username/password."
320 + echo "Auth helper output:"
321 + echo "$result"
322 + die "Stopping because proxy auth would fail."
323 + fi
324 + }
325 +
326 + write_squid_config() {
327 + local domain="$1"
328 + local port="$2"
329 + local syntax="$3"
330 +
331 + local https_line
332 +
333 + if [[ "$syntax" == "new" ]]; then
334 + https_line="https_port $port tls-cert=/etc/squid/certs/proxy-fullchain.pem tls-key=/etc/squid/certs/proxy-privkey.pem"
335 + else
336 + https_line="https_port $port cert=/etc/squid/certs/proxy-fullchain.pem key=/etc/squid/certs/proxy-privkey.pem"
337 + fi
338 +
339 + info "Writing Squid config using syntax: $syntax"
340 +
341 + if [[ -f /etc/squid/squid.conf && ! -f /etc/squid/squid.conf.bak-before-https-proxy ]]; then
342 + cp /etc/squid/squid.conf /etc/squid/squid.conf.bak-before-https-proxy
343 + log "Backed up original config to /etc/squid/squid.conf.bak-before-https-proxy"
344 + fi
345 +
346 + cat > /etc/squid/squid.conf <<EOF
347 + # ============================================================
348 + # Squid HTTPS/TLS encrypted forward proxy
349 + # Generated by setup-https-squid-proxy.sh
350 + #
351 + # Domain: $domain
352 + # Port: $port
353 + #
354 + # IMPORTANT:
355 + # In FoxyProxy, use host "$domain", not the server IP.
356 + # ============================================================
357 +
358 + $https_line
359 +
360 + auth_param basic program /usr/lib/squid/basic_ncsa_auth /etc/squid/passwd
361 + auth_param basic realm HTTPS Proxy
362 + auth_param basic credentialsttl 2 hours
363 +
364 + acl authenticated proxy_auth REQUIRED
365 +
366 + acl SSL_ports port 443
367 + acl Safe_ports port 80
368 + acl Safe_ports port 443
369 + acl Safe_ports port 1025-65535
370 + acl CONNECT method CONNECT
371 +
372 + http_access deny !Safe_ports
373 + http_access deny CONNECT !SSL_ports
374 + http_access allow authenticated
375 + http_access deny all
376 +
377 + cache deny all
378 + cache_mem 8 MB
379 + maximum_object_size 0 KB
380 +
381 + via off
382 + forwarded_for delete
383 + request_header_access X-Forwarded-For deny all
384 + request_header_access Via deny all
385 + request_header_access Cache-Control deny all
386 +
387 + access_log /var/log/squid/access.log
388 + cache_log /var/log/squid/cache.log
389 +
390 + cache_effective_user $SQUID_USER
391 + cache_effective_group $SQUID_GROUP
392 +
393 + visible_hostname $domain
394 + EOF
395 + }
396 +
397 + find_working_squid_syntax() {
398 + local domain="$1"
399 + local port="$2"
400 +
401 + write_squid_config "$domain" "$port" "new"
402 +
403 + if squid -k parse >/tmp/squid-parse.log 2>&1; then
404 + log "Squid config parsed successfully with tls-cert/tls-key syntax."
405 + return 0
406 + fi
407 +
408 + warn "tls-cert/tls-key syntax failed. Trying older cert/key syntax..."
409 + cat /tmp/squid-parse.log || true
410 +
411 + write_squid_config "$domain" "$port" "old"
412 +
413 + if squid -k parse >/tmp/squid-parse.log 2>&1; then
414 + log "Squid config parsed successfully with cert/key syntax."
415 + return 0
416 + fi
417 +
418 + cat /tmp/squid-parse.log || true
419 + die "Squid could not parse either HTTPS config style. Your Squid build may not support https_port/OpenSSL."
420 + }
421 +
422 + write_renewal_hook() {
423 + local domain="$1"
424 +
425 + info "Creating Certbot renewal hook..."
426 +
427 + mkdir -p /etc/letsencrypt/renewal-hooks/deploy
428 +
429 + cat > /etc/letsencrypt/renewal-hooks/deploy/squid-cert-copy.sh <<EOF
430 + #!/bin/bash
431 + set -e
432 +
433 + DOMAIN="$domain"
434 +
435 + SRC_CERT="/etc/letsencrypt/live/\$DOMAIN/fullchain.pem"
436 + SRC_KEY="/etc/letsencrypt/live/\$DOMAIN/privkey.pem"
437 +
438 + DST_DIR="/etc/squid/certs"
439 + DST_CERT="\$DST_DIR/proxy-fullchain.pem"
440 + DST_KEY="\$DST_DIR/proxy-privkey.pem"
441 +
442 + mkdir -p "\$DST_DIR"
443 +
444 + cp "\$SRC_CERT" "\$DST_CERT"
445 + cp "\$SRC_KEY" "\$DST_KEY"
446 +
447 + chown $SQUID_USER:$SQUID_GROUP "\$DST_CERT" "\$DST_KEY"
448 + chmod 644 "\$DST_CERT"
449 + chmod 600 "\$DST_KEY"
450 +
451 + systemctl reload squid || systemctl restart squid
452 + EOF
453 +
454 + chmod +x /etc/letsencrypt/renewal-hooks/deploy/squid-cert-copy.sh
455 +
456 + log "Renewal hook created at /etc/letsencrypt/renewal-hooks/deploy/squid-cert-copy.sh"
457 + }
458 +
459 + restart_squid_cleanly() {
460 + info "Restarting Squid cleanly..."
461 +
462 + systemctl stop squid >/dev/null 2>&1 || true
463 + sleep 2
464 +
465 + pkill -9 squid >/dev/null 2>&1 || true
466 + sleep 1
467 +
468 + systemctl start squid
469 +
470 + if systemctl is-active --quiet squid; then
471 + systemctl enable squid >/dev/null 2>&1 || true
472 + log "Squid is running."
473 + else
474 + systemctl status squid --no-pager -l || true
475 + tail -100 /var/log/squid/cache.log || true
476 + die "Squid failed to start."
477 + fi
478 + }
479 +
480 + test_listener() {
481 + local port="$1"
482 +
483 + info "Checking if Squid is listening on port $port..."
484 +
485 + if ss -tulpn | grep -q ":$port"; then
486 + log "Something is listening on port $port:"
487 + ss -tulpn | grep ":$port" || true
488 + else
489 + warn "Port $port does not appear in ss output."
490 + fi
491 + }
492 +
493 + test_proxy() {
494 + local domain="$1"
495 + local port="$2"
496 + local username="$3"
497 + local password="$4"
498 +
499 + info "Testing proxy with curl..."
500 +
501 + set +e
502 + local result
503 + result="$(curl -4 -sS --max-time 30 \
504 + --proxy "https://$domain:$port" \
505 + --proxy-user "$username:$password" \
506 + https://chy.li/ip 2>&1)"
507 + local rc=$?
508 + set -e
509 +
510 + if [[ $rc -eq 0 ]]; then
511 + log "Proxy test succeeded. Outgoing IP:"
512 + echo "$result"
513 + else
514 + warn "Curl proxy test failed."
515 + echo "$result"
516 + echo
517 + warn "Check logs:"
518 + echo " tail -f /var/log/squid/cache.log"
519 + echo " tail -f /var/log/squid/access.log"
520 + fi
521 + }
522 +
523 + print_summary() {
524 + local domain="$1"
525 + local port="$2"
526 + local username="$3"
527 +
528 + echo
529 + echo "============================================================"
530 + echo " HTTPS Squid Proxy Setup Complete"
531 + echo "============================================================"
532 + echo
533 + echo "FoxyProxy settings:"
534 + echo
535 + echo " Proxy Type: HTTPS"
536 + echo " Host: $domain"
537 + echo " Port: $port"
538 + echo " Username: $username"
539 + echo " Password: the password you entered"
540 + echo
541 + echo "IMPORTANT:"
542 + echo " Use the hostname \"$domain\" in FoxyProxy."
543 + echo " Do NOT use the server IP address."
544 + echo
545 + echo "Test command:"
546 + echo
547 + echo " curl -v \\"
548 + echo " --proxy \"https://$domain:$port\" \\"
549 + echo " --proxy-user \"$username:YOUR_PASSWORD\" \\"
550 + echo " https://chy.li/ip"
551 + echo
552 + echo "Useful logs:"
553 + echo
554 + echo " tail -f /var/log/squid/cache.log"
555 + echo " tail -f /var/log/squid/access.log"
556 + echo
557 + echo "============================================================"
558 + }
559 +
560 + main() {
561 + require_root
562 +
563 + echo
564 + echo "============================================================"
565 + echo " Interactive Squid HTTPS Proxy + Let's Encrypt Installer"
566 + echo "============================================================"
567 + echo
568 +
569 + DOMAIN="$(ask "Proxy domain, e.g. proxy.example.com")"
570 + validate_domain "$DOMAIN"
571 +
572 + PORT="$(ask "HTTPS proxy port" "8443")"
573 + validate_port "$PORT"
574 +
575 + USERNAME="$(ask "Proxy username" "proxyuser")"
576 + if [[ -z "$USERNAME" ]]; then
577 + die "Username cannot be empty."
578 + fi
579 +
580 + PASSWORD="$(ask_password_confirmed)"
581 +
582 + EMAIL="$(ask "Email for Let's Encrypt renewal notices, or leave blank" "")"
583 +
584 + echo
585 + echo "Summary:"
586 + echo " Domain: $DOMAIN"
587 + echo " Port: $PORT"
588 + echo " Username: $USERNAME"
589 + if [[ -n "$EMAIL" ]]; then
590 + echo " LE Email: $EMAIL"
591 + else
592 + echo " LE Email: none"
593 + fi
594 + echo
595 +
596 + read -rp "Continue with install? [Y/n]: " confirm
597 + confirm="${confirm:-Y}"
598 + [[ "${confirm,,}" == "y" ]] || die "Cancelled."
599 +
600 + install_packages
601 + detect_proxy_user_group
602 +
603 + info "Detected Squid user/group: $SQUID_USER:$SQUID_GROUP"
604 +
605 + check_squid_openssl
606 + check_dns "$DOMAIN"
607 + open_firewall "$PORT"
608 + get_certificate "$DOMAIN" "$EMAIL"
609 + copy_certs "$DOMAIN"
610 + create_auth "$USERNAME" "$PASSWORD"
611 + test_auth_helper "$USERNAME" "$PASSWORD"
612 + find_working_squid_syntax "$DOMAIN" "$PORT"
613 + write_renewal_hook "$DOMAIN"
614 + restart_squid_cleanly
615 + test_listener "$PORT"
616 + test_proxy "$DOMAIN" "$PORT" "$USERNAME" "$PASSWORD"
617 + print_summary "$DOMAIN" "$PORT" "$USERNAME"
618 + }
619 +
620 + main "$@"
Newer Older