Skip to content

Full installer with SSL & Security#49

Open
synologyy wants to merge 4 commits intoGaisberg:mainfrom
synologyy:main
Open

Full installer with SSL & Security#49
synologyy wants to merge 4 commits intoGaisberg:mainfrom
synologyy:main

Conversation

@synologyy
Copy link

@synologyy synologyy commented Feb 15, 2026

Add automated VPS installation script

Summary

This PR adds a comprehensive installation script (install.sh) that automates the complete setup of StreamNZB on a VPS with SSL/TLS encryption.

Features

SSL Options

  • Cloudflare DNS API (Recommended): End-to-end encryption with Full (Strict) mode
  • Let's Encrypt HTTP Challenge: Direct SSL certificate without Cloudflare

Security

  • Caddy reverse proxy with security headers (HSTS, X-Frame-Options, etc.)
  • UFW firewall configuration (ports 80, 443, 1119, SSH)
  • Fail2Ban for brute-force protection

Convenience

  • Interactive setup wizard
  • Existing installation detection (Update/Reinstall options)
  • DNS validation before SSL setup (IPv4 + IPv6 support)
  • Cloudflare API token verification
  • Automatic Zone ID lookup
  • Systemd service for auto-start on boot
  • Info file with all access details saved to /opt/streamnzb/INFO.txt

Usage

# Download and run
curl -O https://raw.githubusercontent.com/Gaisberg/streamnzb/main/install.sh
chmod +x install.sh
sudo bash install.sh

Requirements

  • Ubuntu 20.04+ or Debian 11+
  • Root access
  • Domain pointing to server
  • (Optional) Cloudflare account with API token for DNS challenge

What it installs

  • Docker + Docker Compose
  • Caddy (with Cloudflare DNS plugin if selected)
  • StreamNZB container
  • UFW + Fail2Ban

Screenshots / Example Output

╔═══════════════════════════════════════════════════════════════════╗
║           StreamNZB VPS Installation Script v2.2                 ║
║     End-to-End SSL via Cloudflare DNS API or Let's Encrypt       ║
╚═══════════════════════════════════════════════════════════════════╝

=== SSL Configuration ===
Choose SSL method:

  1) Cloudflare DNS API (Recommended - Most Secure)
  2) Caddy + Let's Encrypt (HTTP Challenge)

=== DNS Check ===
ℹ Server IPv4: 77.77.77.77
ℹ Checking DNS for streamnzb.example.com...
✓ DNS correctly configured! (IPv4 match)

╔═══════════════════════════════════════════════════════════════════╗
║                   Installation successful!                        ║
╚═══════════════════════════════════════════════════════════════════╝


## Tested on
- Ubuntu 24.04 LTS
- Hetzner Cloud VPS

---

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

* **New Features**
  * Interactive, automated installer for StreamNZB with guided setup, update and reinstall flows
  * SSL choices: Cloudflare DNS (API token + zone lookup) or Let’s Encrypt HTTP challenge
  * Domain, email and DNS validation with server IP checks and prompts
  * System provisioning: dependency installation, Docker Compose orchestration, and service setup
  * Basic hardening: firewall and fail2ban configuration
  * Post-install summary with UI, NNTP endpoints and deployment details
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

# Add automated VPS installation script

## Summary
This PR adds a comprehensive installation script (`install.sh`) that automates the complete setup of StreamNZB on a VPS with SSL/TLS encryption.

## Features

### SSL Options
- **Cloudflare DNS API** (Recommended): End-to-end encryption with Full (Strict) mode
- **Let's Encrypt HTTP Challenge**: Direct SSL certificate without Cloudflare

### Security
- Caddy reverse proxy with security headers (HSTS, X-Frame-Options, etc.)
- UFW firewall configuration (ports 80, 443, 1119, SSH)
- Fail2Ban for brute-force protection
- Auto-generated security token (or custom)

### Convenience
- Interactive setup wizard
- Existing installation detection (Update/Reinstall options)
- DNS validation before SSL setup (IPv4 + IPv6 support)
- Cloudflare API token verification
- Automatic Zone ID lookup
- Systemd service for auto-start on boot
- Info file with all access details saved to `/opt/streamnzb/INFO.txt`

## Usage

```bash
# Download and run
curl -O https://raw.githubusercontent.com/Gaisberg/streamnzb/main/install.sh
chmod +x install.sh
sudo bash install.sh
```

## Requirements
- Ubuntu 20.04+ or Debian 11+
- Root access
- Domain pointing to server
- (Optional) Cloudflare account with API token for DNS challenge

## What it installs
- Docker + Docker Compose
- Caddy (with Cloudflare DNS plugin if selected)
- StreamNZB container
- UFW + Fail2Ban

## Screenshots / Example Output

```
╔═══════════════════════════════════════════════════════════════════╗
║           StreamNZB VPS Installation Script v2.2                 ║
║     End-to-End SSL via Cloudflare DNS API or Let's Encrypt       ║
╚═══════════════════════════════════════════════════════════════════╝

=== SSL Configuration ===
Choose SSL method:

  1) Cloudflare DNS API (Recommended - Most Secure)
  2) Caddy + Let's Encrypt (HTTP Challenge)

=== DNS Check ===
ℹ Server IPv4: 77.77.77.77
ℹ Checking DNS for streamnzb.example.com...
✓ DNS correctly configured! (IPv4 match)

╔═══════════════════════════════════════════════════════════════════╗
║                   Installation successful!                        ║
╚═══════════════════════════════════════════════════════════════════╝


## Tested on
- Ubuntu 24.04 LTS
- Hetzner Cloud VPS

---
@coderabbitai
Copy link

coderabbitai bot commented Feb 15, 2026

📝 Walkthrough

Walkthrough

Adds a new interactive Bash installer script that deploys StreamNZB with Docker Compose, supports Cloudflare DNS‑01 or Let’s Encrypt HTTP SSL, performs domain/IP/DNS validation, manages Cloudflare token/zone, writes config/env/Caddy/docker-compose files, applies UFW/Fail2Ban, creates a systemd service, and starts containers.

Changes

Cohort / File(s) Summary
StreamNZB Installation Orchestrator
install-streamnzb.sh
New ~1.1k-line Bash installer script. Implements interactive install/update/reinstall flow, root/OS checks, domain & email validation, server IP and DNS checks, optional Cloudflare token and zone lookup, generation of Caddyfile and docker-compose.yml for DNS‑01 or HTTP challenge SSL, creation of .install-config and secure .env, Docker provisioning, UFW and Fail2Ban configuration, systemd service creation, INFO.txt output, and container startup/status reporting.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant Installer as Installer Script
    participant Host as Server
    participant DNS as DNS Resolver
    participant Cloudflare as Cloudflare API
    participant Docker as Docker/Compose
    participant Caddy as Caddy (SSL)
    participant Security as UFW/Fail2Ban

    User->>Installer: run `install-streamnzb.sh` (root)
    Installer->>Host: detect OS, check prerequisites
    Installer->>User: prompt domain, email, SSL mode, Cloudflare token?
    Installer->>DNS: resolve/verify domain -> server IP
    alt Cloudflare DNS‑01
        Installer->>Cloudflare: validate token, fetch zone ID
        Cloudflare-->>Installer: zone ID
        Installer->>Caddy: generate DNS‑01 configuration
    else Let's Encrypt HTTP
        Installer->>Caddy: generate HTTP challenge configuration
    end
    Installer->>Docker: write `docker-compose.yml`, pull images, start containers
    Docker-->>Installer: container status
    Installer->>Security: apply UFW rules, configure Fail2Ban
    Installer->>Host: create systemd service, save `.install-config` and `.env`, write INFO.txt
    Installer->>User: output UI/NNTP endpoints and next steps
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐇 I hopped through prompts, checked DNS and token bright,
I stitched Caddy rules and woke Docker in the night,
I fenced the server, taught Fail2Ban to stand,
StreamNZB is running now — all set by rabbit-hand! 🥕✨

🚥 Pre-merge checks | ✅ 2 | ❌ 2
❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 72.22% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Merge Conflict Detection ⚠️ Warning ❌ Merge conflicts detected (16 files):

⚔️ .github/workflows/build-release.yml (content)
⚔️ pkg/api/stats.go (content)
⚔️ pkg/api/websocket.go (content)
⚔️ pkg/availnzb/availnzb.go (content)
⚔️ pkg/indexer/types.go (content)
⚔️ pkg/loader/file.go (content)
⚔️ pkg/loader/smart_stream.go (content)
⚔️ pkg/logger/logger.go (content)
⚔️ pkg/nntp/client.go (content)
⚔️ pkg/nntp/pool.go (content)
⚔️ pkg/nntp/proxy/commands.go (content)
⚔️ pkg/persistence/manager.go (content)
⚔️ pkg/persistence/manager_test.go (content)
⚔️ pkg/session/manager.go (content)
⚔️ pkg/stremio/handlers.go (content)
⚔️ pkg/validation/checker.go (content)

These conflicts must be resolved before merging into main.
Resolve conflicts locally and push changes to this branch.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Full installer with SSL & Security' accurately reflects the main change: a comprehensive installation script with SSL/TLS options and security hardening features.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
⚔️ Resolve merge conflicts (beta)
  • Auto-commit resolved conflicts to branch main
  • Post resolved changes as copyable diffs in a comment

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@install-streamnzb.sh`:
- Around line 732-739: The APT repo line currently hardcodes
"download.docker.com/linux/ubuntu" (the echo ... | tee ... that constructs
docker.list) which breaks on Debian; change it to derive the distro ID from
/etc/os-release (e.g. use $(. /etc/os-release && echo "$ID") or an OS_ID
variable) and use that in the URL instead of "ubuntu", with a sensible fallback
(e.g. ubuntu) to keep behavior safe; ensure the rest of the stanza (dpkg
--print-architecture, signed-by=/etc/apt/keyrings/docker.asc, and
VERSION_CODENAME usage) is left intact.
- Around line 947-956: Allow SSH access before enabling the firewall and detect
the actual SSH port to avoid lockout: parse /etc/ssh/sshd_config (look for an
explicit Port setting) or fallback to 22 into a variable (e.g., SSH_PORT), run
ufw allow for that port/protocol (ufw allow ${SSH_PORT}/tcp) — ideally before
running ufw --force enable — and keep ufw allow ssh as a fallback; ensure these
checks/allow rules occur prior to the call to ufw --force enable so a mid-script
failure cannot lock out SSH.
🧹 Nitpick comments (7)
install-streamnzb.sh (7)

147-159: Separate declaration and assignment to avoid masking return values.

When local and command substitution are combined, the exit status of the command is masked by the success of local. If the curl fails, the error won't be detected.

♻️ Proposed fix
 verify_cloudflare_token() {
     local token="$1"
-    
-    local response=$(curl -s -X GET "https://api.cloudflare.com/client/v4/user/tokens/verify" \
+    local response
+    response=$(curl -s -X GET "https://api.cloudflare.com/client/v4/user/tokens/verify" \
         -H "Authorization: Bearer ${token}" \
         -H "Content-Type: application/json")

161-176: Separate declaration and assignment; root domain extraction may fail for multi-part TLDs.

  1. Same SC2155 issue: separate local declaration from assignment for root_domain, response, and zone_id.
  2. The root domain extraction '{print $(NF-1)"."$NF}' will produce incorrect results for domains with country-code second-level TLDs (e.g., app.example.co.ukco.uk instead of example.co.uk).
♻️ Proposed fix for declaration separation
 get_cloudflare_zone_id() {
     local token="$1"
     local domain="$2"
     
     # Extract root domain (last two parts)
-    local root_domain=$(echo "$domain" | awk -F. '{print $(NF-1)"."$NF}')
-    
-    local response=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones?name=${root_domain}" \
+    local root_domain
+    root_domain=$(echo "$domain" | awk -F. '{print $(NF-1)"."$NF}')
+    
+    local response
+    response=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones?name=${root_domain}" \
         -H "Authorization: Bearer ${token}" \
         -H "Content-Type: application/json")
     
-    local zone_id=$(echo "$response" | grep -o '"id":"[^"]*"' | head -1 | cut -d'"' -f4)
+    local zone_id
+    zone_id=$(echo "$response" | grep -o '"id":"[^"]*"' | head -1 | cut -d'"' -f4)

For multi-part TLDs, consider documenting this limitation or implementing a more robust extraction that handles common cases like .co.uk, .com.au, etc.


706-707: Consider removing or making the full system upgrade optional.

Running apt-get upgrade -y can be time-consuming and may introduce unexpected changes. For an installation script, apt-get update alone is typically sufficient to ensure package lists are current before installing dependencies.

♻️ Proposed fix
 print_section "System Update"
 apt-get update
-apt-get upgrade -y
+# Uncomment the following line if you want to upgrade all packages:
+# apt-get upgrade -y

812-814: The version key in docker-compose.yml is obsolete.

Docker Compose V2+ ignores the version field and may emit deprecation warnings. Consider removing it for cleaner output.

♻️ Proposed fix (Cloudflare mode)
-version: '3.8'
-
 services:

The same applies to the Let's Encrypt mode docker-compose.yml at line 898.


996-1002: TimeoutStartSec=0 disables timeout, risking indefinite hangs.

If Docker or containers fail to start, systemd will wait forever. Consider setting a reasonable timeout (e.g., 300 seconds).

♻️ Proposed fix
 [Service]
 Type=oneshot
 RemainAfterExit=yes
 WorkingDirectory=${INSTALL_DIR}
 ExecStart=/usr/bin/docker compose up -d
 ExecStop=/usr/bin/docker compose down
-TimeoutStartSec=0
+TimeoutStartSec=300

590-598: Consider validating the timezone input.

Invalid timezones could cause container issues. A basic validation could check against /usr/share/zoneinfo or use timedatectl list-timezones if available.

♻️ Proposed validation
 # Ask for timezone
 printf "\n"
 printf "${YELLOW}Timezone [${DEFAULT_TZ}]: ${NC}"
 read INPUT_TZ
 if [ -z "$INPUT_TZ" ]; then
     TIMEZONE="$DEFAULT_TZ"
 else
+    # Basic validation: check if timezone exists
+    if [ -f "/usr/share/zoneinfo/${INPUT_TZ}" ]; then
+        TIMEZONE="$INPUT_TZ"
+    else
+        print_warning "Timezone '${INPUT_TZ}' not found, using ${DEFAULT_TZ}"
+        TIMEZONE="$DEFAULT_TZ"
+    fi
-    TIMEZONE="$INPUT_TZ"
 fi

1080-1093: Container status check is fragile.

The check docker compose ps | grep -q "running" depends on output format which can vary between Docker Compose versions. Consider using a more robust approach.

♻️ More robust alternative
-if docker compose ps | grep -q "running"; then
+if docker compose ps --status running --quiet 2>/dev/null | grep -q .; then
     print_success "Containers are running!"

Or check specific container:

if docker inspect --format='{{.State.Running}}' streamnzb 2>/dev/null | grep -q "true"; then

The same pattern at line 381 in the update section should also be updated.

Comment on lines +732 to +739
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
chmod a+r /etc/apt/keyrings/docker.asc

echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
tee /etc/apt/sources.list.d/docker.list > /dev/null
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Docker installation is Ubuntu-specific but script claims Debian support.

The Docker repository URL hardcodes download.docker.com/linux/ubuntu. This will fail on Debian systems. Consider using $ID from /etc/os-release to select the correct repository.

🐛 Proposed fix
     install -m 0755 -d /etc/apt/keyrings
-    curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
+    curl -fsSL "https://download.docker.com/linux/${OS_NAME}/gpg" -o /etc/apt/keyrings/docker.asc
     chmod a+r /etc/apt/keyrings/docker.asc

     echo \
-      "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
+      "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/${OS_NAME} \
       $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
       tee /etc/apt/sources.list.d/docker.list > /dev/null
🤖 Prompt for AI Agents
In `@install-streamnzb.sh` around lines 732 - 739, The APT repo line currently
hardcodes "download.docker.com/linux/ubuntu" (the echo ... | tee ... that
constructs docker.list) which breaks on Debian; change it to derive the distro
ID from /etc/os-release (e.g. use $(. /etc/os-release && echo "$ID") or an OS_ID
variable) and use that in the URL instead of "ubuntu", with a sensible fallback
(e.g. ubuntu) to keep behavior safe; ensure the rest of the stanza (dpkg
--print-architecture, signed-by=/etc/apt/keyrings/docker.asc, and
VERSION_CODENAME usage) is left intact.

Comment on lines +947 to +956
print_section "Firewall Configuration"

ufw --force enable
ufw allow ssh
ufw allow 80/tcp
ufw allow 443/tcp
ufw allow 1119/tcp
ufw reload

print_success "Firewall configured."
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Risk of SSH lockout if script fails mid-execution or uses non-standard SSH port.

Two concerns:

  1. If the script fails between ufw --force enable and ufw allow ssh, the user could be locked out.
  2. ufw allow ssh only allows port 22. Users with non-standard SSH ports would be locked out immediately.

Consider allowing SSH before enabling ufw, and checking the actual SSH port.

🛡️ Proposed fix
 print_section "Firewall Configuration"

+# Get current SSH port from sshd config (fallback to 22)
+SSH_PORT=$(grep -E "^Port\s+" /etc/ssh/sshd_config 2>/dev/null | awk '{print $2}' || echo "22")
+[ -z "$SSH_PORT" ] && SSH_PORT="22"
+
+# Allow SSH first before enabling firewall to prevent lockout
+ufw allow "${SSH_PORT}/tcp" comment 'SSH'
 ufw --force enable
-ufw allow ssh
 ufw allow 80/tcp
 ufw allow 443/tcp
 ufw allow 1119/tcp
 ufw reload
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
print_section "Firewall Configuration"
ufw --force enable
ufw allow ssh
ufw allow 80/tcp
ufw allow 443/tcp
ufw allow 1119/tcp
ufw reload
print_success "Firewall configured."
print_section "Firewall Configuration"
# Get current SSH port from sshd config (fallback to 22)
SSH_PORT=$(grep -E "^Port\s+" /etc/ssh/sshd_config 2>/dev/null | awk '{print $2}' || echo "22")
[ -z "$SSH_PORT" ] && SSH_PORT="22"
# Allow SSH first before enabling firewall to prevent lockout
ufw allow "${SSH_PORT}/tcp" comment 'SSH'
ufw --force enable
ufw allow 80/tcp
ufw allow 443/tcp
ufw allow 1119/tcp
ufw reload
print_success "Firewall configured."
🤖 Prompt for AI Agents
In `@install-streamnzb.sh` around lines 947 - 956, Allow SSH access before
enabling the firewall and detect the actual SSH port to avoid lockout: parse
/etc/ssh/sshd_config (look for an explicit Port setting) or fallback to 22 into
a variable (e.g., SSH_PORT), run ufw allow for that port/protocol (ufw allow
${SSH_PORT}/tcp) — ideally before running ufw --force enable — and keep ufw
allow ssh as a fallback; ensure these checks/allow rules occur prior to the call
to ufw --force enable so a mid-script failure cannot lock out SSH.

@coderabbitai
Copy link

coderabbitai bot commented Feb 15, 2026

Note

Docstrings generation - SUCCESS
Generated docstrings for this pull request at #50

coderabbitai bot added a commit that referenced this pull request Feb 15, 2026
Docstrings generation was requested by @Gaisberg.

* #49 (comment)

The following files were modified:

* `install-streamnzb.sh`
Copy link
Author

@synologyy synologyy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed SECURITY_TOKEN and fixes docker

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In `@install-streamnzb.sh`:
- Around line 166-167: The root domain extraction using local root_domain=$(echo
"$domain" | awk -F. '{print $(NF-1)"."$NF}') fails on multi-part TLDs (e.g.,
co.uk) and yields incorrect Zone IDs; change the logic that sets root_domain
(and uses domain) to determine the registrable domain using a public
suffix-aware method (either fetch and consult the public suffix list from
publicsuffix.org or use an existing tld/psl utility) or, if that is not
available, prompt the user to confirm/enter the correct root domain before
performing the Zone ID lookup; update references to root_domain accordingly so
downstream Zone ID lookup uses the confirmed/PSL-derived registrable domain.
- Around line 263-270: The load_existing_config function currently sources the
CONFIG_FILE which can execute arbitrary code; change it to a safe parser that
reads CONFIG_FILE line-by-line and extracts only simple KEY=VALUE pairs (skip
empty lines, comments, and any lines containing shell metacharacters or command
substitutions), trim surrounding quotes, and assign the values to the same
variables instead of using `. "$CONFIG_FILE"`; update load_existing_config to
validate file permissions (ensure 600) and return non-zero on parse errors, and
reference CONFIG_FILE and load_existing_config in your change so reviewers can
find the new safe parsing logic.
- Around line 806-809: The CF_API_TOKEN is being embedded in docker-compose.yml
as plaintext; modify the installer to write the token into a .env file (use
INSTALL_DIR and the CF_API_TOKEN variable), set restrictive permissions (chmod
600), and ensure this .env is created before docker-compose is invoked, then
update docker-compose.yml to stop hardcoding the token in the service
environment and instead reference the .env via env_file (or rely on Compose
variable substitution from .env) so CF_API_TOKEN is read from the .env; change
the install logic that writes docker-compose.yml to remove the literal
CF_API_TOKEN entry and add an env_file reference and the installer should create
the .env prior to starting the stack.
🧹 Nitpick comments (5)
install-streamnzb.sh (5)

15-15: Consider adding an error trap for graceful failure handling.

With set -e, the script exits on first error but performs no cleanup. If the script fails mid-execution (e.g., during firewall configuration), the system could be left in an inconsistent state. Consider adding a trap to handle cleanup or at least notify the user.

 set -e
+
+cleanup_on_error() {
+    print_error "Installation failed! Check the output above for details."
+    print_info "The system may be in a partial state. Review before re-running."
+}
+trap cleanup_on_error ERR

147-176: Declare and assign separately to avoid masking return values (SC2155).

When using local var=$(command), the exit status of command is masked by the successful local declaration. If curl fails, the script won't detect it properly.

♻️ Proposed fix
 verify_cloudflare_token() {
     local token="$1"
-    
-    local response=$(curl -s -X GET "https://api.cloudflare.com/client/v4/user/tokens/verify" \
+    local response
+    response=$(curl -s -X GET "https://api.cloudflare.com/client/v4/user/tokens/verify" \
         -H "Authorization: Bearer ${token}" \
         -H "Content-Type: application/json")
 get_cloudflare_zone_id() {
     local token="$1"
     local domain="$2"
     
     # Extract root domain (last two parts)
-    local root_domain=$(echo "$domain" | awk -F. '{print $(NF-1)"."$NF}')
-    
-    local response=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones?name=${root_domain}" \
+    local root_domain
+    root_domain=$(echo "$domain" | awk -F. '{print $(NF-1)"."$NF}')
+    
+    local response
+    response=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones?name=${root_domain}" \
         -H "Authorization: Bearer ${token}" \
         -H "Content-Type: application/json")
     
-    local zone_id=$(echo "$response" | grep -o '"id":"[^"]*"' | head -1 | cut -d'"' -f4)
+    local zone_id
+    zone_id=$(echo "$response" | grep -o '"id":"[^"]*"' | head -1 | cut -d'"' -f4)

336-338: Use read -r to prevent backslash interpretation.

Multiple read commands throughout the script (lines 338, 412, 456, 483, 526, 569, 614, 667) lack the -r flag. Without it, backslashes in user input are interpreted as escape characters, potentially causing unexpected behavior.

-        read CHOICE
+        read -r CHOICE

Apply this pattern to all read statements in the script.


940-943: Consider detecting the auth log path for broader compatibility.

The hardcoded /var/log/auth.log works for Ubuntu/Debian with rsyslog, but some systems use journald exclusively. Modern Debian 12+ installations may not have this file by default.

♻️ Optional: Add log path detection
+# Detect auth log path
+if [ -f /var/log/auth.log ]; then
+    AUTH_LOG="/var/log/auth.log"
+elif [ -f /var/log/secure ]; then
+    AUTH_LOG="/var/log/secure"
+else
+    AUTH_LOG="%(sshd_log)s"
+fi
+
 cat > /etc/fail2ban/jail.local << EOF
 [DEFAULT]
 bantime = 3600
 findtime = 600
 maxretry = 5

 [sshd]
 enabled = true
 port = ssh
 filter = sshd
-logpath = /var/log/auth.log
+logpath = ${AUTH_LOG}
 maxretry = 3
 EOF

1054-1064: Container health check could be more robust.

The pattern docker compose ps | grep -q "running" is fragile as the output format varies across Docker Compose versions. Consider using the --filter or --format options for more reliable detection.

-if docker compose ps | grep -q "running"; then
+if docker compose ps --format '{{.State}}' | grep -q "running"; then

Or use exit code of specific service health:

if docker compose ps streamnzb --format '{{.State}}' | grep -q "running"; then

Comment on lines +166 to +167
# Extract root domain (last two parts)
local root_domain=$(echo "$domain" | awk -F. '{print $(NF-1)"."$NF}')
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Root domain extraction fails for multi-part TLDs (e.g., co.uk, com.au).

The extraction awk -F. '{print $(NF-1)"."$NF}' assumes single-part TLDs. For streamnzb.example.co.uk, this returns co.uk instead of example.co.uk, causing the Zone ID lookup to fail.

Consider using a public suffix list or prompting the user to confirm the root domain.

🧰 Tools
🪛 Shellcheck (0.11.0)

[warning] 167-167: Declare and assign separately to avoid masking return values.

(SC2155)

🤖 Prompt for AI Agents
In `@install-streamnzb.sh` around lines 166 - 167, The root domain extraction
using local root_domain=$(echo "$domain" | awk -F. '{print $(NF-1)"."$NF}')
fails on multi-part TLDs (e.g., co.uk) and yields incorrect Zone IDs; change the
logic that sets root_domain (and uses domain) to determine the registrable
domain using a public suffix-aware method (either fetch and consult the public
suffix list from publicsuffix.org or use an existing tld/psl utility) or, if
that is not available, prompt the user to confirm/enter the correct root domain
before performing the Zone ID lookup; update references to root_domain
accordingly so downstream Zone ID lookup uses the confirmed/PSL-derived
registrable domain.

Comment on lines +263 to +270
# Load existing configuration
load_existing_config() {
if [ -f "$CONFIG_FILE" ]; then
. "$CONFIG_FILE"
return 0
fi
return 1
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Sourcing config file poses a code injection risk (SC1090).

Using . "$CONFIG_FILE" executes arbitrary shell code if the config file is tampered with. While the file has chmod 600, a safer approach is to parse key-value pairs explicitly rather than executing the file.

🛡️ Proposed safer config loading
 load_existing_config() {
     if [ -f "$CONFIG_FILE" ]; then
-        . "$CONFIG_FILE"
+        # Parse config safely without executing arbitrary code
+        while IFS='=' read -r key value; do
+            # Skip comments and empty lines
+            [[ "$key" =~ ^#.*$ || -z "$key" ]] && continue
+            # Remove surrounding quotes from value
+            value="${value%\"}"
+            value="${value#\"}"
+            case "$key" in
+                DOMAIN) DOMAIN="$value" ;;
+                EMAIL) EMAIL="$value" ;;
+                TIMEZONE) TIMEZONE="$value" ;;
+                SSL_MODE) SSL_MODE="$value" ;;
+                CF_API_TOKEN) CF_API_TOKEN="$value" ;;
+                INSTALL_DATE) INSTALL_DATE="$value" ;;
+            esac
+        done < "$CONFIG_FILE"
         return 0
     fi
     return 1
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# Load existing configuration
load_existing_config() {
if [ -f "$CONFIG_FILE" ]; then
. "$CONFIG_FILE"
return 0
fi
return 1
}
# Load existing configuration
load_existing_config() {
if [ -f "$CONFIG_FILE" ]; then
# Parse config safely without executing arbitrary code
while IFS='=' read -r key value; do
# Skip comments and empty lines
[[ "$key" =~ ^#.*$ || -z "$key" ]] && continue
# Remove surrounding quotes from value
value="${value%\"}"
value="${value#\"}"
case "$key" in
DOMAIN) DOMAIN="$value" ;;
EMAIL) EMAIL="$value" ;;
TIMEZONE) TIMEZONE="$value" ;;
SSL_MODE) SSL_MODE="$value" ;;
CF_API_TOKEN) CF_API_TOKEN="$value" ;;
INSTALL_DATE) INSTALL_DATE="$value" ;;
esac
done < "$CONFIG_FILE"
return 0
fi
return 1
}
🧰 Tools
🪛 Shellcheck (0.11.0)

[warning] 266-266: ShellCheck can't follow non-constant source. Use a directive to specify location.

(SC1090)

🤖 Prompt for AI Agents
In `@install-streamnzb.sh` around lines 263 - 270, The load_existing_config
function currently sources the CONFIG_FILE which can execute arbitrary code;
change it to a safe parser that reads CONFIG_FILE line-by-line and extracts only
simple KEY=VALUE pairs (skip empty lines, comments, and any lines containing
shell metacharacters or command substitutions), trim surrounding quotes, and
assign the values to the same variables instead of using `. "$CONFIG_FILE"`;
update load_existing_config to validate file permissions (ensure 600) and return
non-zero on parse errors, and reference CONFIG_FILE and load_existing_config in
your change so reviewers can find the new safe parsing logic.

Copy link
Author

@synologyy synologyy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CF_API_TOKEN in .env with chmod 600

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@install-streamnzb.sh`:
- Around line 707-708: The script currently runs a full system upgrade via the
apt-get upgrade -y command (immediately after apt-get update), which can cause
unintended side effects; remove the unconditional apt-get upgrade -y or gate it
behind an explicit opt-in (e.g., check an environment variable/flag like
SKIP_SYSTEM_UPGRADE or --upgrade, or prompt the user interactively) and document
the behavior; locate the apt-get update / apt-get upgrade -y block in
install-streamnzb.sh and either delete the apt-get upgrade -y line or wrap it in
a conditional that only runs when the user has explicitly requested upgrades.
- Line 800: Remove the deprecated header line X-XSS-Protection "1; mode=block"
and replace it with a Content-Security-Policy header; locate the exact string
X-XSS-Protection "1; mode=block" in the script and delete it, then add a CSP
response header with restrictive directives (e.g., at minimum default-src 'self'
and a tight script-src/style-src policy avoiding 'unsafe-*' tokens) so browsers
enforce an explicit content policy for XSS mitigation.
🧹 Nitpick comments (4)
install-streamnzb.sh (4)

175-175: Fragile JSON parsing with grep; consider using jq.

Parsing JSON with grep -o '"id":"[^"]*"' is brittle and may fail on edge cases (e.g., whitespace variations, nested structures). The script already installs dnsutils; consider adding jq to dependencies for reliable JSON parsing.

-    local zone_id=$(echo "$response" | grep -o '"id":"[^"]*"' | head -1 | cut -d'"' -f4)
+    local zone_id
+    zone_id=$(echo "$response" | jq -r '.result[0].id // empty' 2>/dev/null)

And add jq to the apt-get install line (around line 711).


360-360: Use read -r to prevent backslash interpretation.

Throughout the script, read is used without the -r flag. This causes backslashes in user input to be interpreted as escape characters. While unlikely to cause issues for typical domain/email input, using read -r is the safer pattern.

Affected lines include: 360, 434, 482, 509, 552, 595, 640, 693.

-        read CHOICE
+        read -r CHOICE

1090-1090: Container status check may be fragile across Docker Compose versions.

The pattern docker compose ps | grep -q "running" depends on the text output format, which varies between Docker Compose versions. Consider using a more robust check.

-if docker compose ps | grep -q "running"; then
+if docker compose ps --status running 2>/dev/null | grep -q "streamnzb\|caddy"; then

Alternatively, check specific container health:

if docker inspect --format='{{.State.Running}}' streamnzb 2>/dev/null | grep -q "true"; then

388-388: Add explicit directory existence check before cd.

If the install directory doesn't exist (e.g., partially deleted installation), cd "$INSTALL_DIR" will fail with an unclear error message.

+if [ ! -d "$INSTALL_DIR" ]; then
+    print_error "Installation directory not found: ${INSTALL_DIR}"
+    exit 1
+fi
 cd "$INSTALL_DIR"

Copy link
Author

@synologyy synologyy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

apt update upgrade changes and Content-Security-Policy

@Gaisberg
Copy link
Owner

This is a good addition but im wondering if its necessary as people with VPS's normally have the knowhow on this stuff. If I were to merge this it would required README instructions, maybe a wget oneliner or something like that and it should be place unders /scripts or something like that.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants