From 8c95c8e1d21515765619a42ba70286082b563f55 Mon Sep 17 00:00:00 2001 From: Guillaume Quintard Date: Wed, 11 Feb 2026 16:57:34 -0800 Subject: [PATCH] Introduce admin_socket_dump Varnish's workdir contains a lot of metadata about the running `varnishd` instance. The new directory contains a couple of implementations to dump the admin socket information. Co-authored-by: GitHub Copilot --- .github/workflows/admin_socket_dump.yml | 27 ++++ admin_socket_dump/README.md | 57 +++++++ .../assets/empty_socks/_.vsm_mgt/_.Arg.s_file | 1 + .../assets/empty_socks/_.vsm_mgt/_.Arg.t_file | 0 .../test/assets/empty_socks/_.vsm_mgt/_.index | 3 + .../malformed_socks/_.vsm_mgt/_.Arg.s_file | 1 + .../malformed_socks/_.vsm_mgt/_.Arg.t_file | 4 + .../assets/malformed_socks/_.vsm_mgt/_.index | 3 + .../missing_s_file/_.vsm_mgt/_.Arg.t_file | 1 + .../assets/missing_s_file/_.vsm_mgt/_.index | 3 + .../missing_t_file/_.vsm_mgt/_.Arg.s_file | 1 + .../assets/missing_t_file/_.vsm_mgt/_.index | 3 + .../test/assets/neither/_.vsm_mgt/_.index | 2 + .../assets/no_secret/_.vsm_mgt/_.Arg.t_file | 1 + .../test/assets/no_secret/_.vsm_mgt/_.index | 2 + .../assets/no_socks/_.vsm_mgt/_.Arg.s_file | 1 + .../test/assets/no_socks/_.vsm_mgt/_.index | 2 + .../test/assets/normal/_.vsm_mgt/_.Arg.s_file | 1 + .../test/assets/normal/_.vsm_mgt/_.Arg.t_file | 2 + .../test/assets/normal/_.vsm_mgt/_.index | 3 + admin_socket_dump/test/test_suite.bats | 145 ++++++++++++++++++ admin_socket_dump/test/test_suite.sh | 22 +++ admin_socket_dump/varnish_socket.py | 106 +++++++++++++ admin_socket_dump/varnish_socket.sh | 95 ++++++++++++ 24 files changed, 486 insertions(+) create mode 100644 .github/workflows/admin_socket_dump.yml create mode 100644 admin_socket_dump/README.md create mode 100644 admin_socket_dump/test/assets/empty_socks/_.vsm_mgt/_.Arg.s_file create mode 100644 admin_socket_dump/test/assets/empty_socks/_.vsm_mgt/_.Arg.t_file create mode 100644 admin_socket_dump/test/assets/empty_socks/_.vsm_mgt/_.index create mode 100644 admin_socket_dump/test/assets/malformed_socks/_.vsm_mgt/_.Arg.s_file create mode 100644 admin_socket_dump/test/assets/malformed_socks/_.vsm_mgt/_.Arg.t_file create mode 100644 admin_socket_dump/test/assets/malformed_socks/_.vsm_mgt/_.index create mode 100644 admin_socket_dump/test/assets/missing_s_file/_.vsm_mgt/_.Arg.t_file create mode 100644 admin_socket_dump/test/assets/missing_s_file/_.vsm_mgt/_.index create mode 100644 admin_socket_dump/test/assets/missing_t_file/_.vsm_mgt/_.Arg.s_file create mode 100644 admin_socket_dump/test/assets/missing_t_file/_.vsm_mgt/_.index create mode 100644 admin_socket_dump/test/assets/neither/_.vsm_mgt/_.index create mode 100644 admin_socket_dump/test/assets/no_secret/_.vsm_mgt/_.Arg.t_file create mode 100644 admin_socket_dump/test/assets/no_secret/_.vsm_mgt/_.index create mode 100644 admin_socket_dump/test/assets/no_socks/_.vsm_mgt/_.Arg.s_file create mode 100644 admin_socket_dump/test/assets/no_socks/_.vsm_mgt/_.index create mode 100644 admin_socket_dump/test/assets/normal/_.vsm_mgt/_.Arg.s_file create mode 100644 admin_socket_dump/test/assets/normal/_.vsm_mgt/_.Arg.t_file create mode 100644 admin_socket_dump/test/assets/normal/_.vsm_mgt/_.index create mode 100644 admin_socket_dump/test/test_suite.bats create mode 100755 admin_socket_dump/test/test_suite.sh create mode 100644 admin_socket_dump/varnish_socket.py create mode 100755 admin_socket_dump/varnish_socket.sh diff --git a/.github/workflows/admin_socket_dump.yml b/.github/workflows/admin_socket_dump.yml new file mode 100644 index 0000000..72184b5 --- /dev/null +++ b/.github/workflows/admin_socket_dump.yml @@ -0,0 +1,27 @@ +name: admin_socket_dump Tests + +on: + push: + paths: + - 'admin_socket_dump/**' + pull_request: + paths: + - 'admin_socket_dump/**' + +jobs: + test: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Install bats + run: | + sudo apt-get update + sudo apt-get install -y bats + + - name: Run test suite + run: | + cd admin_socket_dump + test/test_suite.sh \ No newline at end of file diff --git a/admin_socket_dump/README.md b/admin_socket_dump/README.md new file mode 100644 index 0000000..a60c075 --- /dev/null +++ b/admin_socket_dump/README.md @@ -0,0 +1,57 @@ +# What it is + +Two implementations (Python and Shell) that extract the Varnish admin socket configuration given a work directory: +- Management socket addresses and ports (from `-T` argument files) +- Secret file path (from `-S` argument files) + +Both implementations produce identical JSON output for non-errors and handle edge cases gracefully. + +# How it is built + +No need to build anything as it's `shell` and `python`, but you'll need: +- Either: + - Python 3.6+ (for the Python implementation) + - A POSIX-compliant shell like `sh` (for the shell implementation) and standard UNIX utilities: `awk`, `tr`, `cat`, `printf` +- `bats` (for running tests) + + +# How it works + +Both scripts accept a directory path (defaults to `/var/lib/varnish/varnishd`) and output JSON: + +```bash +# Python +python3 varnish_socket.py [directory] + +# Shell +sh varnish_socket.sh [directory] +``` + +Output format: + +```json +{ + "secret": "/path/to/secret", + "socks": [ + { + "addr": "127.0.0.1", + "port": 8080 + } + ] +} +``` + +Error cases (missing index, unreadable files) return JSON with an `"error"` field and exit code 1. + +# How it is tested + +Run tests for both implementations +```bash +test/test_suite.sh +``` + +Or test a specific implementation +```bash +VARNISH_SOCKET_TOOL=python test/test_suite.sh +VARNISH_SOCKET_TOOL=shell test/test_suite.sh +``` \ No newline at end of file diff --git a/admin_socket_dump/test/assets/empty_socks/_.vsm_mgt/_.Arg.s_file b/admin_socket_dump/test/assets/empty_socks/_.vsm_mgt/_.Arg.s_file new file mode 100644 index 0000000..9a13482 --- /dev/null +++ b/admin_socket_dump/test/assets/empty_socks/_.vsm_mgt/_.Arg.s_file @@ -0,0 +1 @@ +/var/secret \ No newline at end of file diff --git a/admin_socket_dump/test/assets/empty_socks/_.vsm_mgt/_.Arg.t_file b/admin_socket_dump/test/assets/empty_socks/_.vsm_mgt/_.Arg.t_file new file mode 100644 index 0000000..e69de29 diff --git a/admin_socket_dump/test/assets/empty_socks/_.vsm_mgt/_.index b/admin_socket_dump/test/assets/empty_socks/_.vsm_mgt/_.index new file mode 100644 index 0000000..f4f7316 --- /dev/null +++ b/admin_socket_dump/test/assets/empty_socks/_.vsm_mgt/_.index @@ -0,0 +1,3 @@ +# Both present but socks file is empty ++ _.Arg.t_file 0 32 Arg -T ++ _.Arg.s_file 0 40 Arg -S diff --git a/admin_socket_dump/test/assets/malformed_socks/_.vsm_mgt/_.Arg.s_file b/admin_socket_dump/test/assets/malformed_socks/_.vsm_mgt/_.Arg.s_file new file mode 100644 index 0000000..9a13482 --- /dev/null +++ b/admin_socket_dump/test/assets/malformed_socks/_.vsm_mgt/_.Arg.s_file @@ -0,0 +1 @@ +/var/secret \ No newline at end of file diff --git a/admin_socket_dump/test/assets/malformed_socks/_.vsm_mgt/_.Arg.t_file b/admin_socket_dump/test/assets/malformed_socks/_.vsm_mgt/_.Arg.t_file new file mode 100644 index 0000000..2fc99be --- /dev/null +++ b/admin_socket_dump/test/assets/malformed_socks/_.vsm_mgt/_.Arg.t_file @@ -0,0 +1,4 @@ +not_an_ip_port +127.0.0.1 +just_one_value +192.168.1.1 9000 diff --git a/admin_socket_dump/test/assets/malformed_socks/_.vsm_mgt/_.index b/admin_socket_dump/test/assets/malformed_socks/_.vsm_mgt/_.index new file mode 100644 index 0000000..a9220b4 --- /dev/null +++ b/admin_socket_dump/test/assets/malformed_socks/_.vsm_mgt/_.index @@ -0,0 +1,3 @@ +# Index with malformed socks data ++ _.Arg.t_file 0 32 Arg -T ++ _.Arg.s_file 0 40 Arg -S diff --git a/admin_socket_dump/test/assets/missing_s_file/_.vsm_mgt/_.Arg.t_file b/admin_socket_dump/test/assets/missing_s_file/_.vsm_mgt/_.Arg.t_file new file mode 100644 index 0000000..189bbae --- /dev/null +++ b/admin_socket_dump/test/assets/missing_s_file/_.vsm_mgt/_.Arg.t_file @@ -0,0 +1 @@ +127.0.0.1 8080 diff --git a/admin_socket_dump/test/assets/missing_s_file/_.vsm_mgt/_.index b/admin_socket_dump/test/assets/missing_s_file/_.vsm_mgt/_.index new file mode 100644 index 0000000..092d39a --- /dev/null +++ b/admin_socket_dump/test/assets/missing_s_file/_.vsm_mgt/_.index @@ -0,0 +1,3 @@ +# Index with -S reference but file doesn't exist ++ _.Arg.t_file 0 32 Arg -T ++ _.Arg.s_file 0 40 Arg -S diff --git a/admin_socket_dump/test/assets/missing_t_file/_.vsm_mgt/_.Arg.s_file b/admin_socket_dump/test/assets/missing_t_file/_.vsm_mgt/_.Arg.s_file new file mode 100644 index 0000000..c76b431 --- /dev/null +++ b/admin_socket_dump/test/assets/missing_t_file/_.vsm_mgt/_.Arg.s_file @@ -0,0 +1 @@ +/etc/varnish/secret \ No newline at end of file diff --git a/admin_socket_dump/test/assets/missing_t_file/_.vsm_mgt/_.index b/admin_socket_dump/test/assets/missing_t_file/_.vsm_mgt/_.index new file mode 100644 index 0000000..1d9e576 --- /dev/null +++ b/admin_socket_dump/test/assets/missing_t_file/_.vsm_mgt/_.index @@ -0,0 +1,3 @@ +# Index with -T reference but file doesn't exist ++ _.Arg.t_file 0 32 Arg -T ++ _.Arg.s_file 0 40 Arg -S diff --git a/admin_socket_dump/test/assets/neither/_.vsm_mgt/_.index b/admin_socket_dump/test/assets/neither/_.vsm_mgt/_.index new file mode 100644 index 0000000..11b7a05 --- /dev/null +++ b/admin_socket_dump/test/assets/neither/_.vsm_mgt/_.index @@ -0,0 +1,2 @@ +# Neither -T nor -S present ++ _.OtherStuff 0 100 Other diff --git a/admin_socket_dump/test/assets/no_secret/_.vsm_mgt/_.Arg.t_file b/admin_socket_dump/test/assets/no_secret/_.vsm_mgt/_.Arg.t_file new file mode 100644 index 0000000..36c5319 --- /dev/null +++ b/admin_socket_dump/test/assets/no_secret/_.vsm_mgt/_.Arg.t_file @@ -0,0 +1 @@ +192.168.1.1 9000 \ No newline at end of file diff --git a/admin_socket_dump/test/assets/no_secret/_.vsm_mgt/_.index b/admin_socket_dump/test/assets/no_secret/_.vsm_mgt/_.index new file mode 100644 index 0000000..2da9760 --- /dev/null +++ b/admin_socket_dump/test/assets/no_secret/_.vsm_mgt/_.index @@ -0,0 +1,2 @@ +# Only -T, no -S ++ _.Arg.t_file 0 32 Arg -T diff --git a/admin_socket_dump/test/assets/no_socks/_.vsm_mgt/_.Arg.s_file b/admin_socket_dump/test/assets/no_socks/_.vsm_mgt/_.Arg.s_file new file mode 100644 index 0000000..c76b431 --- /dev/null +++ b/admin_socket_dump/test/assets/no_socks/_.vsm_mgt/_.Arg.s_file @@ -0,0 +1 @@ +/etc/varnish/secret \ No newline at end of file diff --git a/admin_socket_dump/test/assets/no_socks/_.vsm_mgt/_.index b/admin_socket_dump/test/assets/no_socks/_.vsm_mgt/_.index new file mode 100644 index 0000000..d236ab4 --- /dev/null +++ b/admin_socket_dump/test/assets/no_socks/_.vsm_mgt/_.index @@ -0,0 +1,2 @@ +# Only -S, no -T ++ _.Arg.s_file 0 40 Arg -S diff --git a/admin_socket_dump/test/assets/normal/_.vsm_mgt/_.Arg.s_file b/admin_socket_dump/test/assets/normal/_.vsm_mgt/_.Arg.s_file new file mode 100644 index 0000000..5fb78c6 --- /dev/null +++ b/admin_socket_dump/test/assets/normal/_.vsm_mgt/_.Arg.s_file @@ -0,0 +1 @@ +/tmp/test.secret \ No newline at end of file diff --git a/admin_socket_dump/test/assets/normal/_.vsm_mgt/_.Arg.t_file b/admin_socket_dump/test/assets/normal/_.vsm_mgt/_.Arg.t_file new file mode 100644 index 0000000..043328c --- /dev/null +++ b/admin_socket_dump/test/assets/normal/_.vsm_mgt/_.Arg.t_file @@ -0,0 +1,2 @@ +127.0.0.1 8080 +::1 8081 \ No newline at end of file diff --git a/admin_socket_dump/test/assets/normal/_.vsm_mgt/_.index b/admin_socket_dump/test/assets/normal/_.vsm_mgt/_.index new file mode 100644 index 0000000..62798d6 --- /dev/null +++ b/admin_socket_dump/test/assets/normal/_.vsm_mgt/_.index @@ -0,0 +1,3 @@ +# Normal case with both -T and -S ++ _.Arg.t_file 0 32 Arg -T ++ _.Arg.s_file 0 40 Arg -S diff --git a/admin_socket_dump/test/test_suite.bats b/admin_socket_dump/test/test_suite.bats new file mode 100644 index 0000000..bbc6955 --- /dev/null +++ b/admin_socket_dump/test/test_suite.bats @@ -0,0 +1,145 @@ +#!/usr/bin/env bats + +# Test suite for Varnish configuration extractors +# Tests Python or shell implementation based on VARNISH_SOCKET_TOOL variable +# +# Usage: +# VARNISH_SOCKET_TOOL=python bats test/test_suite.bats +# VARNISH_SOCKET_TOOL=shell bats test/test_suite.bats +# Or use test/test_suite.sh to test both implementations + +setup() { + ASSETS_DIR="$BATS_TEST_DIRNAME/assets" + + case "$VARNISH_SOCKET_TOOL" in + python) + SCRIPT="python3 $BATS_TEST_DIRNAME/../varnish_socket.py" + ;; + shell) + SCRIPT="sh $BATS_TEST_DIRNAME/../varnish_socket.sh" + ;; + *) + echo "VARNISH_SOCKET_TOOL must be set to 'python' or 'shell'" >&2 + exit 1 + ;; + esac +} + +@test "normal case: both secret and socks present" { + run $SCRIPT "$ASSETS_DIR/normal" + [ "$status" -eq 0 ] + + expected='{ + "secret": "/tmp/test.secret", + "socks": [ + { + "addr": "127.0.0.1", + "port": 8080 + }, + { + "addr": "::1", + "port": 8081 + } + ] +}' + [ "$output" = "$expected" ] +} + +@test "no secret: only socks present" { + run $SCRIPT "$ASSETS_DIR/no_secret" + [ "$status" -eq 0 ] + + expected='{ + "secret": null, + "socks": [ + { + "addr": "192.168.1.1", + "port": 9000 + } + ] +}' + [ "$output" = "$expected" ] +} + +@test "no socks: only secret present" { + run $SCRIPT "$ASSETS_DIR/no_socks" + [ "$status" -eq 0 ] + + expected='{ + "secret": "/etc/varnish/secret", + "socks": [] +}' + [ "$output" = "$expected" ] +} + +@test "empty socks: secret present but socks file is empty" { + run $SCRIPT "$ASSETS_DIR/empty_socks" + [ "$status" -eq 0 ] + + expected='{ + "secret": "/var/secret", + "socks": [] +}' + [ "$output" = "$expected" ] +} + +@test "neither: no -T or -S arguments in index" { + run $SCRIPT "$ASSETS_DIR/neither" + [ "$status" -eq 0 ] + + expected='{ + "secret": null, + "socks": [] +}' + [ "$output" = "$expected" ] +} + +@test "invalid directory returns error" { + run $SCRIPT "$ASSETS_DIR/invalid_dir" + [ "$status" -eq 1 ] + + # Both implementations return error JSON, but with different messages + case "$output" in + *'"error"'*) ;; + *) return 1 ;; + esac +} + +@test "missing -T file: gracefully returns empty socks" { + run $SCRIPT "$ASSETS_DIR/missing_t_file" + [ "$status" -eq 1 ] + + # File is referenced in index but doesn't exist - should error + case "$output" in + *'"error"'*) ;; + *) return 1 ;; + esac +} + +@test "missing -S file: gracefully returns null secret" { + run $SCRIPT "$ASSETS_DIR/missing_s_file" + [ "$status" -eq 1 ] + + # File is referenced in index but doesn't exist - should error + case "$output" in + *'"error"'*) ;; + *) return 1 ;; + esac +} + +@test "malformed socks data: skips invalid lines" { + run $SCRIPT "$ASSETS_DIR/malformed_socks" + [ "$status" -eq 0 ] + + # Should skip malformed lines and only return valid socket entries + expected='{ + "secret": "/var/secret", + "socks": [ + { + "addr": "192.168.1.1", + "port": 9000 + } + ] +}' + [ "$output" = "$expected" ] +} diff --git a/admin_socket_dump/test/test_suite.sh b/admin_socket_dump/test/test_suite.sh new file mode 100755 index 0000000..ae7c93b --- /dev/null +++ b/admin_socket_dump/test/test_suite.sh @@ -0,0 +1,22 @@ +#!/bin/sh +# +# Test runner for Varnish socket extractors +# Runs BATS tests for one or both implementations + +set -e + +TEST_DIR="$(cd "$(dirname "$0")" && pwd)" +BATS_FILE="$TEST_DIR/test_suite.bats" + +# If VARNISH_SOCKET_TOOL is not set, test both implementations +if [ -z "$VARNISH_SOCKET_TOOL" ]; then + echo "Running tests for Python implementation..." + VARNISH_SOCKET_TOOL=python bats "$BATS_FILE" + + echo "" + echo "Running tests for Shell implementation..." + VARNISH_SOCKET_TOOL=shell bats "$BATS_FILE" +else + # Run tests for the specified implementation + bats "$BATS_FILE" +fi diff --git a/admin_socket_dump/varnish_socket.py b/admin_socket_dump/varnish_socket.py new file mode 100644 index 0000000..86d3447 --- /dev/null +++ b/admin_socket_dump/varnish_socket.py @@ -0,0 +1,106 @@ +#!/usr/bin/env python3 +""" +Extract Varnish configuration from _.index file. +Reads the _.index file to find: +- Management interface IP/ports (from -T argument) +- Secret file path (from -S argument) +""" + +import sys +import json +import os +from pathlib import Path + +def parse_index_file(index_path): + """Parse _.index file to find -T and -S arguments.""" + arg_t_file = None + arg_s_file = None + + with open(index_path, 'r') as f: + for line in f: + line = line.strip() + parts = line.split() + if len(parts) >= 2: + if line.endswith('Arg -T'): + # Second word is the path to the file with IP/ports + arg_t_file = parts[1] + elif line.endswith('Arg -S'): + # Second word is the path to the secret file + arg_s_file = parts[1] + + return arg_t_file, arg_s_file + +def read_ip_ports(file_path): + """Read IP/ports from the -T argument file.""" + sockets = [] + with open(file_path, 'rb') as f: + content = f.read() + # Strip null bytes and decode + content = content.rstrip(b'\x00').decode('utf-8') + sockets = [ + {"addr": parts[0], "port": int(parts[1])} + for line in content.split('\n') + if line.strip() + for parts in [line.strip().rsplit(maxsplit=1)] + if len(parts) == 2 + ] + + return sockets + +def read_secret_path(file_path): + """Read the secret file path from the -S argument file.""" + with open(file_path, 'rb') as f: + content = f.read() + # Strip null bytes and decode + content = content.rstrip(b'\x00').decode('utf-8').strip() + return content + +def main(): + # Get directory from command line argument or use default + if len(sys.argv) > 1: + work_dir = sys.argv[1] + else: + work_dir = '/var/lib/varnish/varnishd' + + # Construct path to _.index file + index_dir = os.path.join(work_dir, '_.vsm_mgt') + index_path = os.path.join(index_dir, '_.index') + + # Read IP/ports from -T file and secret file path from -S file + try: + # Parse the index file + arg_t_file, arg_s_file = parse_index_file(index_path) + + # Build full paths (files are in the same directory as _.index) + + # Read IP/ports from -T file (if present in index) + socks = [] + if arg_t_file: + arg_t_full_path = os.path.join(index_dir, arg_t_file) + # If file is referenced but doesn't exist, that's an error + socks = read_ip_ports(arg_t_full_path) + + # Read secret file path from -S file (if present in index) + secret = None + if arg_s_file: + arg_s_full_path = os.path.join(index_dir, arg_s_file) + # If file is referenced but doesn't exist, that's an error + secret = read_secret_path(arg_s_full_path) + except (ValueError, FileNotFoundError, PermissionError, Exception) as e: + print(json.dumps({ + "error": str(e) + }), file=sys.stderr) + sys.exit(1) + + # Prepare result + result = { + "secret": secret, + "socks": socks + } + + # Output JSON + print(json.dumps(result, indent=2)) + + +if __name__ == '__main__': + main() diff --git a/admin_socket_dump/varnish_socket.sh b/admin_socket_dump/varnish_socket.sh new file mode 100755 index 0000000..2e850d9 --- /dev/null +++ b/admin_socket_dump/varnish_socket.sh @@ -0,0 +1,95 @@ +#!/bin/sh +# +# Extract Varnish configuration from _.index file. +# Reads the _.index file to find: +# - Management interface IP/ports (from -T argument) +# - Secret file path (from -S argument) +# + +set -eu + +# Function to check if file exists and exit with error if not +check_file() { + if [ ! -f "$1" ]; then + printf '{"error": "%s file not found: %s"}\n' "$2" "$1" >&2 + exit 1 + fi +} + +# Get directory from command line argument or use default +WORK_DIR="${1:-/var/lib/varnish/varnishd}" + +# Construct path to _.index file +INDEX_DIR="$WORK_DIR/_.vsm_mgt" +INDEX_PATH="$INDEX_DIR/_.index" + +# Check if index file exists +check_file "$INDEX_PATH" "Index" + +# Parse the index file to find -T and -S argument files +ARG_T_FILE=$(awk '/Arg -T$/ {print $2}' "$INDEX_PATH") +ARG_S_FILE=$(awk '/Arg -S$/ {print $2}' "$INDEX_PATH") + +# Build full paths +ARG_T_FULL_PATH="$INDEX_DIR/$ARG_T_FILE" +ARG_S_FULL_PATH="$INDEX_DIR/$ARG_S_FILE" + +# Read secret file path (if present in index) +SECRET="null" +if [ -n "$ARG_S_FILE" ]; then + # If file is referenced but doesn't exist, that's an error + check_file "$ARG_S_FULL_PATH" "Secret argument" + SECRET=$(cat "$ARG_S_FULL_PATH" | tr -d '\0') + SECRET=$(printf '%s' "$SECRET") + SECRET="\"$SECRET\"" +fi + +# Read IP/ports and build JSON array (if present in index) +SOCKS_JSON="" +if [ -n "$ARG_T_FILE" ]; then + # If file is referenced but doesn't exist, that's an error + check_file "$ARG_T_FULL_PATH" "Management address argument" + COMMA="" + + while IFS= read -r line; do + # Skip empty lines + [ -z "$line" ] && continue + + # Split address and port + ADDR="${line% *}" + PORT="${line##* }" + + # Skip if we couldn't split properly + [ -z "$ADDR" ] || [ -z "$PORT" ] && continue + + # Add socket entry + SOCKS_JSON="${SOCKS_JSON}${COMMA} + { + \"addr\": \"$ADDR\", + \"port\": $PORT + }" + COMMA="," + done <