v1.1.1 Security Hardening & Config Enhancement 🛡️

🚀 Major architectural release with a near full rewrite of the entrypoint, validation system, diagnostics, and templates. The image is now ~20MB, fully busybox based, more secure, and more flexible for relay and bridge operators.

🔧 Critical Fixes
- Busybox compatible rewrite of OBFS4V_* parsing for values with spaces.
- Rewritten TOR_CONTACT_INFO validation to prevent crash loops.
- Restored bootstrap logs with Log notice stdout.
- Fixed ENV healthchecks and validation order.
- Resolved busybox regex and quoting issues across the script.

 Features and Enhancements
- Added PT_PORT with complete obfs4 bridge compatibility.
- Support for OR_PORT, PT_PORT, EMAIL, and NICKNAME.
- Rewritten bandwidth logic with correct Rate and Burst translation.
- Unified guard, exit, and bridge via TOR_RELAY_MODE.
- Integrated obfs4 with rewritten diagnostics for status, health, fingerprint, and bridge-line.
- Reliable ENV only mode without torrc files.

📦 Build Improvements
- Image reduced ~45MB to ~20MB with busybox only tools.
- Rewritten healthcheck for ENV and mounted configs.
- Four diagnostic tools rewritten to pure busybox sh.
- Weekly rebuilds with latest Alpine and Tor.

📚 Templates and Documentation
- All templates rewritten and updated with bandwidth options and naming alternatives.
- Updated Cosmos and Docker Compose templates for bridge, guard, exit.
- New templates README with full deployment, migration, and config comparisons.
- Revised Claude file with clearer differences and bandwidth notes.

🔒 Security Hardening
- 32 vulnerabilities fixed across critical, high, medium, low categories.
- Non root runtime with UID 100.
- Strict OBFS4V_* whitelist and rewritten validation.
- No exposed diagnostics ports, docker exec only.
- Smaller attack surface with removed binaries.

🚀 Migration Notes
- From v1.1.0: direct upgrade, no config changes, fingerprint preserved.
- From official obfs4 bridge: one time UID fix required, full ENV compatibility afterward.
- Templates include both TOR_ and official naming for smooth migration.

🧩 Compatibility
- Alpine 3.22.2 base, latest Tor from edge.
- AMD64 and ARM64 supported.
- Works with Docker, Compose, Cosmos Cloud, Portainer.
This commit is contained in:
rE-Bo0t.bx1
2025-11-14 02:08:27 +08:00
parent 384d6ed669
commit 9ed70bdb89
74 changed files with 10345 additions and 7440 deletions

View File

@@ -93,7 +93,7 @@ body:
id: diagnostic
attributes:
label: Diagnostic Output
description: Output from `docker exec guard-relay relay-status`
description: Output from `docker exec guard-relay status` or `docker exec guard-relay health`
render: shell
placeholder: Paste diagnostic output here

View File

@@ -12,12 +12,13 @@ updates:
interval: "weekly"
day: "monday"
time: "03:00"
timezone: "Asia/Tokyo"
timezone: "UTC"
open-pull-requests-limit: 5
rebase-strategy: "auto"
labels:
- "dependencies"
- "github-actions"
- "security"
commit-message:
prefix: "ci"
include: "scope"
@@ -25,6 +26,10 @@ updates:
- "r3bo0tbx1"
assignees:
- "r3bo0tbx1"
# Security: Allow both patch and minor updates for GitHub Actions
ignore:
- dependency-name: "*"
update-types: ["version-update:semver-major"]
# ────────────────────────────────
# 🐳 Docker (Base Image Updates)
@@ -34,12 +39,13 @@ updates:
schedule:
interval: "daily"
time: "03:00"
timezone: "Asia/Tokyo"
timezone: "UTC"
open-pull-requests-limit: 3
rebase-strategy: "auto"
labels:
- "dependencies"
- "docker"
- "security"
commit-message:
prefix: "build"
include: "scope"
@@ -47,6 +53,10 @@ updates:
- "r3bo0tbx1"
assignees:
- "r3bo0tbx1"
# Security: Allow patch updates for Alpine base image
ignore:
- dependency-name: "alpine"
update-types: ["version-update:semver-major"]
# ────────────────────────────────
# Strategy Notes
@@ -55,12 +65,19 @@ updates:
# ✅ Daily Docker checks for timely base image security patches
# ✅ Automatic PR rebasing to prevent stale dependency branches
# ✅ Manual merge approval required (no auto-merge)
# ✅ Security-focused: Block major version updates, allow patch/minor
#
# Monitored ecosystems:
# 1. GitHub Actions (workflow dependencies)
# 2. Docker (Alpine base image)
# 2. Docker (Alpine base image - tracks 3.22.x → 3.23.x updates)
#
# Manual updates:
# - Shell scripts and Tor binary (require validation)
# - Alpine system packages pinned for consistency
# - Other dependencies managed outside Dependabot
# NOT monitored (by design):
# - Alpine packages (tor, tini, lyrebird) - No pinned versions
# → Updated automatically via weekly rebuilds (Sundays 18:30 UTC)
# → Strategy: apk add --no-cache always pulls latest available
#
# Security strategy:
# - Base image updates: Dependabot tracks Alpine version changes
# - Package updates: Weekly rebuilds pull latest tor/tini/lyrebird from Alpine repos
# - CI/CD updates: Dependabot tracks GitHub Actions version changes
# - Manual review required for all PRs (no auto-merge)

55
.github/renovate.json vendored
View File

@@ -13,46 +13,41 @@
"assignees": ["r3bo0tbx1"],
"reviewers": ["r3bo0tbx1"],
"enabledManagers": ["dockerfile", "regex"],
"regexManagers": [
{
"fileMatch": ["Dockerfile"],
"matchStrings": [
"tor=(?<currentValue>[0-9a-zA-Z\\.-]+)",
"bash=(?<currentValue>[0-9a-zA-Z\\.-]+)",
"tini=(?<currentValue>[0-9a-zA-Z\\.-]+)",
"curl=(?<currentValue>[0-9a-zA-Z\\.-]+)",
"jq=(?<currentValue>[0-9a-zA-Z\\.-]+)",
"grep=(?<currentValue>[0-9a-zA-Z\\.-]+)",
"coreutils=(?<currentValue>[0-9a-zA-Z\\.-]+)",
"bind-tools=(?<currentValue>[0-9a-zA-Z\\.-]+)",
"netcat-openbsd=(?<currentValue>[0-9a-zA-Z\\.-]+)",
"dos2unix=(?<currentValue>[0-9a-zA-Z\\.-]+)"
],
"datasourceTemplate": "alpine",
"depNameTemplate": "{{{matchString}}}"
}
],
"enabledManagers": ["dockerfile"],
"packageRules": [
{
"matchDatasources": ["alpine"],
"groupName": "🧅 Alpine Packages",
"matchDatasources": ["docker"],
"matchPackageNames": ["alpine"],
"groupName": "🐳 Alpine Base Image",
"schedule": ["before 3am on monday"],
"prPriority": 1,
"allowedVersions": "<3.23.0"
"automerge": false,
"allowedVersions": "/^3\\.(2[2-9]|[3-9][0-9])\\./",
"description": "Track Alpine 3.22+ versions for security updates"
},
{
"matchManagers": ["dockerfile"],
"groupName": "🐳 Base Images",
"excludePackageNames": ["alpine"],
"groupName": "🐳 Other Docker Images",
"schedule": ["before 3am on monday"],
"prPriority": 2
"prPriority": 2,
"automerge": false
}
],
"timezone": "UTC",
"automerge": false,
"prConcurrentLimit": 5
}
"vulnerabilityAlerts": {
"enabled": true,
"labels": ["security", "dependencies"],
"assignees": ["r3bo0tbx1"],
"schedule": ["at any time"]
},
"osvVulnerabilityAlerts": true,
"timezone": "UTC",
"prConcurrentLimit": 5,
"customManagers": [],
"description": "Renovate tracks only Docker base images. Alpine packages (tor, tini, lyrebird) are NOT pinned and update automatically via weekly rebuilds."
}

View File

@@ -18,9 +18,10 @@ on:
tags:
- 'v*.*.*'
# SECURITY: Global permissions set to read-only (principle of least privilege)
# Individual jobs grant additional permissions as needed
permissions:
contents: write
packages: write
contents: read
env:
GHCR_REGISTRY: ghcr.io
@@ -31,6 +32,8 @@ jobs:
determine-version:
name: 🏷️ Determine Version and Build Type
runs-on: ubuntu-latest
permissions:
contents: read # Only needs to read repo for version detection
outputs:
version: ${{ steps.version.outputs.version }}
build_type: ${{ steps.version.outputs.build_type }}
@@ -61,11 +64,13 @@ jobs:
IS_RELEASE="false"
echo "👤 Manual build version: ${VERSION}"
else
# Weekly rebuild: Use the last release version (no suffix)
# This rebuilds the image with updated Alpine packages
LATEST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "v1.0.0")
VERSION="${LATEST_TAG#v}"
BUILD_TYPE="weekly"
IS_RELEASE="false"
echo "📅 Weekly build version: ${VERSION}"
echo "📅 Weekly rebuild of last release: ${VERSION} (with updated packages)"
fi
BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ')
SHORT_SHA=$(git rev-parse --short HEAD)
@@ -88,6 +93,9 @@ jobs:
name: 🏗️ Multi-Arch Build and Push
runs-on: ubuntu-latest
needs: determine-version
permissions:
contents: read # Read repository code
packages: write # Push to GHCR
if: |
github.event_name != 'workflow_run' ||
github.event.workflow_run.conclusion == 'success'
@@ -96,10 +104,10 @@ jobs:
- name: 📥 Checkout Repository
uses: actions/checkout@v5
- name: 🎯 Verify Shell Script Extensions
- name: 🎯 Verify Tools Directory
run: |
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "📝 Pre-Build: Verifying .sh Extensions"
echo "📝 Pre-Build: Verifying Tools"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
@@ -108,31 +116,24 @@ jobs:
exit 1
fi
MISSING_EXT=0
SH_COUNT=0
TOOL_COUNT=0
for file in tools/*; do
[ -f "$file" ] || continue
filename=$(basename "$file")
extension="${filename##*.}"
if head -1 "$file" 2>/dev/null | grep -q "^#!/"; then
if [ "$extension" != "sh" ]; then
echo "❌ Shell script missing .sh extension: $filename"
MISSING_EXT=1
else
echo "✅ $filename"
SH_COUNT=$((SH_COUNT + 1))
fi
if head -1 "$file" 2>/dev/null | grep -q "^#!/bin/sh"; then
echo "✅ $filename (busybox sh)"
TOOL_COUNT=$((TOOL_COUNT + 1))
fi
done
echo ""
if [ $MISSING_EXT -eq 1 ]; then
echo "❌ Build blocked: Some shell scripts missing .sh extension"
if [ $TOOL_COUNT -lt 1 ]; then
echo "❌ Build blocked: No tools found in tools/"
exit 1
else
echo "🎉 All $SH_COUNT shell scripts have proper .sh extension"
echo "🎉 Found $TOOL_COUNT diagnostic tools (busybox-only, no .sh extension)"
fi
- name: 🔧 Normalize scripts before build
@@ -145,7 +146,7 @@ jobs:
sudo apt-get update -qq && sudo apt-get install -y dos2unix
echo "📄 Processing main scripts..."
for script in docker-entrypoint.sh integration-check.sh Dockerfile; do
for script in docker-entrypoint.sh healthcheck.sh Dockerfile; do
if [ -f "$script" ]; then
dos2unix "$script" 2>/dev/null || true
echo " ✅ Normalized: $script"
@@ -153,17 +154,17 @@ jobs:
done
echo ""
echo "📁 Processing tools/*.sh..."
echo "📁 Processing tools/*..."
if [ -d "tools" ]; then
find tools -type f -name "*.sh" -exec dos2unix {} \; 2>/dev/null || true
TOOL_COUNT=$(find tools -type f -name "*.sh" | wc -l)
find tools -type f -exec dos2unix {} \; 2>/dev/null || true
TOOL_COUNT=$(find tools -type f | wc -l)
echo " ✅ Normalized: $TOOL_COUNT tool scripts"
fi
echo ""
echo "🔐 Setting execute permissions..."
chmod +x docker-entrypoint.sh integration-check.sh 2>/dev/null || true
[ -d "tools" ] && chmod +x tools/*.sh 2>/dev/null || true
chmod +x docker-entrypoint.sh healthcheck.sh 2>/dev/null || true
[ -d "tools" ] && chmod +x tools/* 2>/dev/null || true
echo " ✅ Permissions verified"
echo ""
@@ -200,17 +201,22 @@ jobs:
TAGS=()
if [ "$BUILD_TYPE" = "release" ]; then
# New release: Update both :latest and version tags
TAGS+=("${{ env.GHCR_REGISTRY }}/${{ env.GHCR_IMAGE_NAME }}:latest")
TAGS+=("${{ env.GHCR_REGISTRY }}/${{ env.GHCR_IMAGE_NAME }}:${VERSION}")
TAGS+=("${{ env.DOCKERHUB_IMAGE_NAME }}:latest")
TAGS+=("${{ env.DOCKERHUB_IMAGE_NAME }}:${VERSION}")
elif [ "$BUILD_TYPE" = "weekly" ]; then
TAGS+=("${{ env.GHCR_REGISTRY }}/${{ env.GHCR_IMAGE_NAME }}:${VERSION}-weekly")
TAGS+=("${{ env.DOCKERHUB_IMAGE_NAME }}:${VERSION}-weekly")
# Weekly rebuild: Update version tag with fresh packages, keep :latest pointing to last release
TAGS+=("${{ env.GHCR_REGISTRY }}/${{ env.GHCR_IMAGE_NAME }}:${VERSION}")
TAGS+=("${{ env.DOCKERHUB_IMAGE_NAME }}:${VERSION}")
TAGS+=("${{ env.GHCR_REGISTRY }}/${{ env.GHCR_IMAGE_NAME }}:latest")
TAGS+=("${{ env.DOCKERHUB_IMAGE_NAME }}:latest")
elif [ "$BUILD_TYPE" = "validated" ]; then
TAGS+=("${{ env.GHCR_REGISTRY }}/${{ env.GHCR_IMAGE_NAME }}:${VERSION}-validated")
TAGS+=("${{ env.DOCKERHUB_IMAGE_NAME }}:${VERSION}-validated")
else
# Manual builds: version tag only
TAGS+=("${{ env.GHCR_REGISTRY }}/${{ env.GHCR_IMAGE_NAME }}:${VERSION}")
TAGS+=("${{ env.DOCKERHUB_IMAGE_NAME }}:${VERSION}")
fi
@@ -243,6 +249,8 @@ jobs:
name: 📝 Generate Release Notes
runs-on: ubuntu-latest
needs: [determine-version, build-and-push]
permissions:
contents: write # Create GitHub releases
if: needs.determine-version.outputs.is_release == 'true'
steps:
@@ -304,4 +312,3 @@ jobs:
body_path: release_notes.md
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -14,10 +14,9 @@ on:
# 🐳 Core build and runtime logic
- 'Dockerfile'
- 'docker-entrypoint.sh'
- 'integration-check.sh'
# 🧩 Tools and validation scripts
- 'tools/*.sh'
# 🧩 Tools and validation scripts (no .sh extension)
- 'tools/*'
# ⚙️ CI/CD workflows
- '.github/workflows/release.yml'
@@ -64,7 +63,7 @@ jobs:
- name: 🔍 Validate Dockerfile Syntax
run: |
echo "🐳 Validating Dockerfile build context..."
docker build --no-cache --target builder -t tor-relay-test . 2>&1 | \
docker build --no-cache -t tor-relay-test . 2>&1 | \
tee /tmp/docker-build.log || true
if grep -i "error" /tmp/docker-build.log; then
echo "❌ Dockerfile validation failed"
@@ -85,31 +84,23 @@ jobs:
bash -n docker-entrypoint.sh || exit 1
echo " ✅ docker-entrypoint.sh syntax valid"
fi
# Check integration test script
if [ -f integration-check.sh ]; then
echo "📄 Checking integration-check.sh..."
bash -n integration-check.sh || exit 1
echo " ✅ integration-check.sh syntax valid"
fi
echo ""
echo "📁 Checking tools directory (.sh files)..."
# Check if tools directory exists and has .sh files
echo "📁 Checking tools directory (no .sh extension)..."
# Check if tools directory exists
if [ ! -d "tools" ]; then
echo " ⚠️ tools/ directory not found"
elif [ -z "$(find tools -maxdepth 1 -type f -name '*.sh' 2>/dev/null)" ]; then
echo " ⚠️ No .sh files found in tools/"
else
# Check all .sh files in tools/
# Check all files in tools/ (no .sh extension)
TOOL_COUNT=0
for script in tools/*.sh; do
[ -f "$script" ] || continue
echo "📄 Checking $(basename "$script")..."
bash -n "$script" || exit 1
echo " ✅ $script syntax valid"
TOOL_COUNT=$((TOOL_COUNT + 1))
for script in tools/status tools/health tools/fingerprint tools/bridge-line; do
if [ -f "$script" ]; then
echo "📄 Checking $(basename "$script")..."
sh -n "$script" || exit 1
echo " ✅ $script syntax valid"
TOOL_COUNT=$((TOOL_COUNT + 1))
fi
done
echo ""
echo " ✅ All $TOOL_COUNT tool scripts validated"
@@ -135,17 +126,17 @@ jobs:
echo "🔍 ShellCheck: docker-entrypoint.sh"
shellcheck -S warning docker-entrypoint.sh || true
fi
if [ -f integration-check.sh ]; then
echo "🔍 ShellCheck: integration-check.sh"
shellcheck -S warning integration-check.sh || true
fi
# ShellCheck all .sh files in tools/
# ShellCheck all tools (no .sh extension)
if [ -d "tools" ]; then
echo ""
echo "🔍 ShellCheck: tools/*.sh"
find tools -maxdepth 1 -type f -name "*.sh" -exec shellcheck -S warning {} \; || true
echo "🔍 ShellCheck: tools/*"
for tool in tools/status tools/health tools/fingerprint tools/bridge-line; do
if [ -f "$tool" ]; then
echo " Checking $(basename $tool)..."
shellcheck -S warning "$tool" || true
fi
done
fi
echo ""
@@ -154,51 +145,50 @@ jobs:
- name: 🎯 Verify Tool Extensions
run: |
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "📝 Verifying Tool File Extensions"
echo "📝 Verifying Tool File Extensions (NO .sh)"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
if [ ! -d "tools" ]; then
echo "⚠️ tools/ directory not found - skipping check"
exit 0
fi
MISSING_EXT=0
NON_SH_COUNT=0
SH_COUNT=0
HAS_SH_EXT=0
NO_EXT_COUNT=0
# Check all files in tools/
for file in tools/*; do
[ -f "$file" ] || continue
filename=$(basename "$file")
extension="${filename##*.}"
# Check if it's a shell script (has shebang)
if head -1 "$file" 2>/dev/null | grep -q "^#!/"; then
if [ "$extension" != "sh" ]; then
echo "❌ Shell script missing .sh extension: $filename"
MISSING_EXT=1
# Tools should NOT have .sh extension
if echo "$filename" | grep -q '\.sh$'; then
echo "❌ Tool should NOT have .sh extension: $filename"
HAS_SH_EXT=1
else
SH_COUNT=$((SH_COUNT + 1))
NO_EXT_COUNT=$((NO_EXT_COUNT + 1))
echo "✅ Tool has correct format (no .sh): $filename"
fi
else
NON_SH_COUNT=$((NON_SH_COUNT + 1))
echo " Non-script file: $filename"
fi
done
echo ""
echo "📊 Summary:"
echo " • Shell scripts with .sh: $SH_COUNT"
[ $NON_SH_COUNT -gt 0 ] && echo " • Other files: $NON_SH_COUNT"
echo " • Tools without .sh extension: $NO_EXT_COUNT"
echo ""
if [ $MISSING_EXT -eq 1 ]; then
echo "❌ Some shell scripts are missing .sh extension"
if [ $HAS_SH_EXT -eq 1 ]; then
echo "❌ Some tools have .sh extension (should not have it)"
exit 1
elif [ $NO_EXT_COUNT -lt 4 ]; then
echo "❌ Expected 4 tools (status, health, fingerprint, bridge-line)"
exit 1
else
echo "✅ All shell scripts have proper .sh extension"
echo "✅ All tools have correct format (no .sh extension)"
fi
- name: 📋 Lint YAML Files
@@ -343,9 +333,8 @@ jobs:
- name: 🚀 Run Container Smoke Test
run: |
echo "🚀 Starting container for smoke test..."
docker run -d --name tor-test -e ENABLE_METRICS=true \
-e ENABLE_HEALTH_CHECK=true tor-relay:test \
/bin/sh -c "echo 'Test mode active'; sleep 60" || true
docker run -d --name tor-test --entrypoint /bin/sh tor-relay:test \
-c "echo '✅ Container started successfully'; sleep 60" || true
sleep 5
- name: 🔍 Check Container Status
@@ -359,16 +348,15 @@ jobs:
- name: 🔧 Verify Tools Installation
run: |
echo "🔍 Checking installed tools..."
docker run --rm tor-relay:test ls -la /usr/local/bin/ || exit 1
docker run --rm --entrypoint /bin/sh tor-relay:test -c "ls -la /usr/local/bin/" || exit 1
echo ""
echo "🛠️ Available diagnostic tools:"
docker run --rm tor-relay:test sh -c "ls -1 /usr/local/bin/*.sh 2>/dev/null | \
xargs -n1 basename | sort"
docker run --rm --entrypoint /bin/sh tor-relay:test -c "ls -1 /usr/local/bin/status /usr/local/bin/health /usr/local/bin/fingerprint /usr/local/bin/bridge-line"
- name: 🧅 Verify Tor Installation
run: |
echo "🔍 Checking Tor version..."
TOR_VERSION=$(docker run --rm tor-relay:test tor --version | head -1)
TOR_VERSION=$(docker run --rm --entrypoint tor tor-relay:test --version | head -1)
if [ -z "$TOR_VERSION" ]; then
echo "❌ Tor installation failed"
exit 1
@@ -377,7 +365,7 @@ jobs:
- name: 📁 Verify File Structure
run: |
docker run --rm tor-relay:test sh -c "
docker run --rm --entrypoint /bin/sh tor-relay:test -c "
echo '📂 === Directory Structure ===' && \
ls -la / | grep -E 'var|etc|usr' && \
echo '' && \
@@ -394,16 +382,27 @@ jobs:
- name: 📋 Check Build Metadata
run: |
echo "🔍 Checking build metadata..."
docker run --rm tor-relay:test cat /build-info.txt || exit 1
docker run --rm --entrypoint cat tor-relay:test /build-info.txt || exit 1
- name: 🔐 Verify Permissions
run: |
echo "🔐 Verifying directory permissions..."
docker run --rm tor-relay:test sh -c "
ls -ld /var/lib/tor | grep -q '^d.*tor tor' && \
echo '✅ /var/lib/tor permissions correct' || exit 1
ls -ld /var/log/tor | grep -q '^d.*tor tor' && \
echo '✅ /var/log/tor permissions correct' || exit 1
docker run --rm --entrypoint /bin/sh tor-relay:test -c "
DATA_OWNER=\$(stat -c '%u %g' /var/lib/tor)
if [ \"\$DATA_OWNER\" = '100 101' ]; then
echo '✅ /var/lib/tor ownership correct (100:101)'
else
echo '❌ /var/lib/tor ownership incorrect: \$DATA_OWNER'
exit 1
fi
LOG_OWNER=\$(stat -c '%u %g' /var/log/tor)
if [ \"\$LOG_OWNER\" = '100 101' ]; then
echo '✅ /var/log/tor ownership correct (100:101)'
else
echo '❌ /var/log/tor ownership incorrect: \$LOG_OWNER'
exit 1
fi
"
- name: ✅ Integration Tests Summary
@@ -490,32 +489,29 @@ jobs:
- name: "🔍 Test: Help Flags"
if: matrix.test-case == 'help-flags'
run: |
echo "🧪 Testing tool help flags..."
for tool in health.sh metrics.sh status.sh fingerprint.sh view-logs.sh \
net-check.sh setup.sh dashboard.sh; do
echo "🧪 Testing tool existence..."
for tool in status health fingerprint bridge-line; do
TOOL_PATH="/usr/local/bin/$tool"
if docker run --rm tor-relay:test sh -c "test -f $TOOL_PATH"; then
echo "🔍 Testing $tool --help..."
docker run --rm tor-relay:test $tool --help > /dev/null && \
echo "✅ $tool --help works" || \
echo "⚠️ $tool --help failed (non-critical)"
if docker run --rm --entrypoint /bin/sh tor-relay:test -c "test -f $TOOL_PATH"; then
echo "✅ $tool exists at $TOOL_PATH"
else
echo "⚠️ $tool not found at $TOOL_PATH"
echo " $tool not found at $TOOL_PATH"
exit 1
fi
done
- name: "🔐 Test: File Permissions"
if: matrix.test-case == 'file-permissions'
run: |
echo "🔐 Verifying file permissions for .sh tools..."
docker run --rm tor-relay:test sh -c "
for tool in /usr/local/bin/*.sh; do
echo "🔐 Verifying file permissions for tools..."
docker run --rm --entrypoint /bin/sh tor-relay:test -c "
for tool in /usr/local/bin/status /usr/local/bin/health /usr/local/bin/fingerprint /usr/local/bin/bridge-line; do
if [ -f \"\$tool\" ]; then
test -x \"\$tool\" && echo \"✅ \$(basename \$tool) is executable\" || exit 1
fi
done
"
docker run --rm tor-relay:test sh -c "
docker run --rm --entrypoint /bin/sh tor-relay:test -c "
test -x /usr/local/bin/docker-entrypoint.sh && \
echo '✅ docker-entrypoint.sh is executable' || exit 1
"
@@ -524,7 +520,7 @@ jobs:
if: matrix.test-case == 'alpine-compatibility'
run: |
echo "🏔️ Verifying Alpine base image..."
docker run --rm tor-relay:test sh -c "
docker run --rm --entrypoint /bin/sh tor-relay:test -c "
cat /etc/os-release | grep -i alpine && \
echo '✅ Alpine base confirmed' || exit 1
test -x /bin/sh && echo '✅ /bin/sh available' || exit 1
@@ -536,13 +532,13 @@ jobs:
if: matrix.test-case == 'tool-executability'
run: |
echo "🔧 Testing tool execution..."
docker run --rm tor-relay:test sh -c "
for tool in /usr/local/bin/*.sh; do
docker run --rm --entrypoint /bin/sh tor-relay:test -c "
for tool in /usr/local/bin/status /usr/local/bin/health /usr/local/bin/fingerprint /usr/local/bin/bridge-line; do
if [ -f \"\$tool\" ]; then
BASENAME=\$(basename \"\$tool\")
echo \"🔍 Testing \$BASENAME...\"
\"\$tool\" --help > /dev/null 2>&1 || \
echo \" ⚠️ \$BASENAME --help failed (may require Tor process)\"
# Tools require Tor to be running, just verify they're executable
test -x \"\$tool\" && echo \" \$BASENAME is executable\" || exit 1
fi
done
"
@@ -574,8 +570,8 @@ jobs:
## ✅ Checks Performed
- 🐳 Dockerfile validation with Hadolint
- 📝 Shell script syntax checking (all .sh files)
- 🎯 Shell script extension verification
- 📝 Shell script syntax checking (POSIX sh)
- 🎯 Tool extension verification (no .sh)
- 🔎 ShellCheck static analysis
- 📋 YAML validation (lenient for templates)
- 🔖 JSON validation
@@ -583,7 +579,7 @@ jobs:
- 🏗️ Docker image build (multi-arch ready)
- 🚀 Container smoke test
- 🧅 Tor installation verification
- 🔧 Tool availability check (.sh files)
- 🔧 Tool availability check (status, health, fingerprint, bridge-line)
- 🔐 Permission verification
- 🛡️ Security scanning with Trivy
- 🧪 Test matrix execution