feat(scripts): Add SecuBox seed and slipstream scripts for auto-install
Add comprehensive scripts for bootstrapping SecuBox on fresh OpenWrt: - secubox-seed.sh: Bootstrap script for fresh installations - Auto-detects architecture (x86_64, aarch64, armv7l) - Configures SecuBox repository with fallback mechanisms - Installation profiles: minimal, standard, full - Graceful handling when repo.secubox.in is unavailable - secubox-slipstream.sh: Bake SecuBox config into images during build - Pre-configures repository feeds in rootfs - Installs seed script and first-boot setup - Adds SecuBox branding (banner, release info) - Works with rootfs directories or image files Update GitHub Actions workflows: - build-secubox-vm.yml: Add slipstream step for x86_64 VMs - build-secubox-images.yml: Add slipstream step for GlobalScale devices Images now include: - Pre-configured SecuBox repository (/etc/opkg/customfeeds.conf) - Seed script (/usr/sbin/secubox-seed) - First-boot auto-setup script - SecuBox banner Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
ccfb58124c
commit
7d0f47f465
@ -605,7 +605,8 @@
|
||||
"Bash({ echo \"=== ROOT .md FILES ===\")",
|
||||
"Bash(if [ -f \"$d/README.md\" ])",
|
||||
"Bash([ ! -f \"$d/README.fr.md\" ])",
|
||||
"Bash(basename:*)"
|
||||
"Bash(basename:*)",
|
||||
"Bash(VBoxManage internalcommands sethduuid:*)"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
86
.github/workflows/build-secubox-images.yml
vendored
86
.github/workflows/build-secubox-images.yml
vendored
@ -232,6 +232,92 @@ jobs:
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
|
||||
- name: Slipstream SecuBox configuration
|
||||
if: ${{ github.event.inputs.include_secubox == 'true' || github.event_name == 'push' }}
|
||||
run: |
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo "🔧 SLIPSTREAMING SECUBOX CONFIGURATION"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
|
||||
# Determine architecture for this device
|
||||
ARCH="${{ matrix.subtarget }}"
|
||||
case "$ARCH" in
|
||||
cortexa72) OPKG_ARCH="aarch64_cortex-a72" ;;
|
||||
cortexa53) OPKG_ARCH="aarch64_cortex-a53" ;;
|
||||
*) OPKG_ARCH="aarch64_generic" ;;
|
||||
esac
|
||||
|
||||
# Create files directory for slipstream content
|
||||
mkdir -p openwrt/files/etc/opkg
|
||||
mkdir -p openwrt/files/etc/uci-defaults
|
||||
mkdir -p openwrt/files/usr/sbin
|
||||
|
||||
# Add SecuBox repository configuration
|
||||
cat > openwrt/files/etc/opkg/customfeeds.conf << EOF
|
||||
# SecuBox Package Repository
|
||||
# Pre-configured by GitHub Actions build
|
||||
# Device: ${{ matrix.device }}
|
||||
src/gz secubox_packages https://repo.secubox.in/packages/${OPKG_ARCH}
|
||||
src/gz secubox_luci https://repo.secubox.in/luci/${OPKG_ARCH}
|
||||
EOF
|
||||
echo " ✅ Repository configured for ${OPKG_ARCH}"
|
||||
|
||||
# Copy seed script
|
||||
if [[ -f "scripts/secubox-seed.sh" ]]; then
|
||||
cp scripts/secubox-seed.sh openwrt/files/usr/sbin/secubox-seed
|
||||
chmod +x openwrt/files/usr/sbin/secubox-seed
|
||||
echo " ✅ Seed script installed"
|
||||
fi
|
||||
|
||||
# Create first-boot setup script
|
||||
cat > openwrt/files/etc/uci-defaults/99-secubox-setup << 'FIRSTBOOT'
|
||||
#!/bin/sh
|
||||
# SecuBox First-Boot Setup
|
||||
LOG="/tmp/secubox-firstboot.log"
|
||||
echo "[$(date)] SecuBox first-boot starting..." >> "$LOG"
|
||||
|
||||
# Update package lists
|
||||
opkg update >> "$LOG" 2>&1 || true
|
||||
|
||||
# Try to install core packages if repo is available
|
||||
for pkg in secubox-core secubox-base; do
|
||||
if opkg list | grep -q "^$pkg "; then
|
||||
opkg install "$pkg" >> "$LOG" 2>&1 || true
|
||||
fi
|
||||
done
|
||||
|
||||
# Set SecuBox theme as default
|
||||
if [ -f /etc/config/luci ]; then
|
||||
uci set luci.main.mediaurlbase='/luci-static/secubox' 2>/dev/null || true
|
||||
uci commit luci 2>/dev/null || true
|
||||
fi
|
||||
|
||||
echo "[$(date)] SecuBox first-boot complete" >> "$LOG"
|
||||
exit 0
|
||||
FIRSTBOOT
|
||||
chmod +x openwrt/files/etc/uci-defaults/99-secubox-setup
|
||||
echo " ✅ First-boot script created"
|
||||
|
||||
# Create SecuBox banner
|
||||
cat > openwrt/files/etc/banner << BANNER
|
||||
|
||||
____ ____
|
||||
/ ___| ___ ___ _ _| __ ) _____ __
|
||||
\___ \ / _ \/ __| | | | _ \ / _ \ \/ /
|
||||
___) | __/ (__| |_| | |_) | (_) > <
|
||||
|____/ \___|\___|\__,_|____/ \___/_/\_\
|
||||
|
||||
SecuBox - ${{ matrix.description }}
|
||||
https://secubox.in
|
||||
|
||||
Run 'secubox-seed --help' for setup options
|
||||
|
||||
BANNER
|
||||
echo " ✅ SecuBox banner created"
|
||||
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
|
||||
- name: Generate SecuBox config
|
||||
run: |
|
||||
cd openwrt
|
||||
|
||||
75
.github/workflows/build-secubox-vm.yml
vendored
75
.github/workflows/build-secubox-vm.yml
vendored
@ -170,6 +170,81 @@ jobs:
|
||||
echo "📊 Total: $PKG_COUNT SecuBox packages copied"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
|
||||
- name: Slipstream SecuBox configuration
|
||||
run: |
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo "🔧 SLIPSTREAMING SECUBOX CONFIGURATION"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
|
||||
# Create files directory for slipstream content
|
||||
mkdir -p openwrt/files/etc/opkg
|
||||
mkdir -p openwrt/files/etc/uci-defaults
|
||||
mkdir -p openwrt/files/usr/sbin
|
||||
|
||||
# Add SecuBox repository configuration
|
||||
cat > openwrt/files/etc/opkg/customfeeds.conf << 'EOF'
|
||||
# SecuBox Package Repository
|
||||
# Pre-configured by GitHub Actions build
|
||||
src/gz secubox_packages https://repo.secubox.in/packages/x86_64
|
||||
src/gz secubox_luci https://repo.secubox.in/luci/x86_64
|
||||
EOF
|
||||
|
||||
# Copy seed script
|
||||
if [[ -f "scripts/secubox-seed.sh" ]]; then
|
||||
cp scripts/secubox-seed.sh openwrt/files/usr/sbin/secubox-seed
|
||||
chmod +x openwrt/files/usr/sbin/secubox-seed
|
||||
echo " ✅ Seed script installed"
|
||||
fi
|
||||
|
||||
# Create first-boot setup script
|
||||
cat > openwrt/files/etc/uci-defaults/99-secubox-setup << 'FIRSTBOOT'
|
||||
#!/bin/sh
|
||||
# SecuBox First-Boot Setup
|
||||
LOG="/tmp/secubox-firstboot.log"
|
||||
echo "[$(date)] SecuBox first-boot starting..." >> "$LOG"
|
||||
|
||||
# Update package lists
|
||||
opkg update >> "$LOG" 2>&1 || true
|
||||
|
||||
# Try to install core packages if repo is available
|
||||
for pkg in secubox-core secubox-base; do
|
||||
if opkg list | grep -q "^$pkg "; then
|
||||
opkg install "$pkg" >> "$LOG" 2>&1 || true
|
||||
fi
|
||||
done
|
||||
|
||||
# Set SecuBox theme as default
|
||||
if [ -f /etc/config/luci ]; then
|
||||
uci set luci.main.mediaurlbase='/luci-static/secubox' 2>/dev/null || true
|
||||
uci commit luci 2>/dev/null || true
|
||||
fi
|
||||
|
||||
echo "[$(date)] SecuBox first-boot complete" >> "$LOG"
|
||||
exit 0
|
||||
FIRSTBOOT
|
||||
chmod +x openwrt/files/etc/uci-defaults/99-secubox-setup
|
||||
echo " ✅ First-boot script created"
|
||||
|
||||
# Create SecuBox banner
|
||||
cat > openwrt/files/etc/banner << 'BANNER'
|
||||
|
||||
____ ____
|
||||
/ ___| ___ ___ _ _| __ ) _____ __
|
||||
\___ \ / _ \/ __| | | | _ \ / _ \ \/ /
|
||||
___) | __/ (__| |_| | |_) | (_) > <
|
||||
|____/ \___|\___|\__,_|____/ \___/_/\_\
|
||||
|
||||
SecuBox VM - Security Gateway for OpenWrt
|
||||
https://secubox.in
|
||||
|
||||
Run 'secubox-seed --help' for setup options
|
||||
|
||||
BANNER
|
||||
echo " ✅ SecuBox banner created"
|
||||
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
|
||||
- name: Generate VM configuration
|
||||
run: |
|
||||
cd openwrt
|
||||
|
||||
@ -1,16 +1,66 @@
|
||||
# Documentation Publishing Scripts
|
||||
# SecuBox Scripts
|
||||
|
||||
English | [Francais](README.fr.md) | [中文](README.zh.md)
|
||||
|
||||
**Version:** 1.0.0
|
||||
**Last Updated:** 2025-12-28
|
||||
**Purpose:** Automated scripts for publishing SecuBox documentation
|
||||
**Version:** 1.1.0
|
||||
**Last Updated:** 2026-03-20
|
||||
**Purpose:** Automated scripts for SecuBox installation and documentation publishing
|
||||
|
||||
---
|
||||
|
||||
## 📜 Available Scripts
|
||||
|
||||
### 1. setup-wiki.sh
|
||||
### 1. secubox-seed.sh
|
||||
**Purpose:** Bootstrap a fresh OpenWrt installation with SecuBox packages
|
||||
|
||||
**Usage:**
|
||||
```bash
|
||||
# Remote install (recommended)
|
||||
wget -O- https://repo.secubox.in/seed.sh | sh
|
||||
|
||||
# Or with curl
|
||||
curl -fsSL https://repo.secubox.in/seed.sh | sh
|
||||
|
||||
# Local install with options
|
||||
./scripts/secubox-seed.sh --profile=full
|
||||
```
|
||||
|
||||
**Installation Profiles:**
|
||||
|
||||
| Profile | Description | Packages |
|
||||
|---------|-------------|----------|
|
||||
| `minimal` | Core only | secubox-core, secubox-base, luci-theme-secubox |
|
||||
| `standard` | Core + Security + Network | + crowdsec, haproxy, mitmproxy, ipblocklist |
|
||||
| `full` | Everything | + tor, exposure, glances, master-link, p2p |
|
||||
|
||||
**Options:**
|
||||
- `--profile=PROFILE` - Choose installation profile (minimal/standard/full)
|
||||
- `--mirror=URL` - Override repository URL
|
||||
- `--dry-run` - Show what would be installed
|
||||
- `--skip-update` - Skip opkg update
|
||||
|
||||
**Environment Variables:**
|
||||
- `SECUBOX_PROFILE` - Same as --profile
|
||||
- `SECUBOX_MIRROR` - Same as --mirror
|
||||
- `SECUBOX_DRY_RUN=1` - Same as --dry-run
|
||||
- `SECUBOX_SKIP_UPDATE=1` - Same as --skip-update
|
||||
|
||||
**What it does:**
|
||||
1. Detects system architecture (x86_64, aarch64, armv7l)
|
||||
2. Configures SecuBox package repository
|
||||
3. Updates package lists
|
||||
4. Installs packages based on selected profile
|
||||
5. Runs post-installation setup
|
||||
6. Enables and starts services
|
||||
|
||||
**Requirements:**
|
||||
- OpenWrt 24.10+
|
||||
- Internet connectivity
|
||||
- Root access
|
||||
|
||||
---
|
||||
|
||||
### 2. setup-wiki.sh
|
||||
**Purpose:** Sync DOCS/ to GitHub Wiki
|
||||
|
||||
**Usage:**
|
||||
|
||||
479
scripts/secubox-seed.sh
Executable file
479
scripts/secubox-seed.sh
Executable file
@ -0,0 +1,479 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# SecuBox Seed Script
|
||||
# Bootstrap a fresh OpenWrt installation with SecuBox packages
|
||||
#
|
||||
# Usage:
|
||||
# wget -O- https://repo.secubox.in/seed.sh | sh
|
||||
# OR
|
||||
# curl -fsSL https://repo.secubox.in/seed.sh | sh
|
||||
#
|
||||
# Options (via environment):
|
||||
# SECUBOX_PROFILE=minimal|standard|full (default: standard)
|
||||
# SECUBOX_MIRROR=url (override repo URL)
|
||||
# SECUBOX_SKIP_UPDATE=1 (skip opkg update)
|
||||
# SECUBOX_DRY_RUN=1 (show what would be installed)
|
||||
#
|
||||
|
||||
set -e
|
||||
|
||||
# Colors (if terminal supports it)
|
||||
if [ -t 1 ]; then
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m'
|
||||
else
|
||||
RED='' GREEN='' YELLOW='' BLUE='' NC=''
|
||||
fi
|
||||
|
||||
log_info() { printf "${BLUE}[INFO]${NC} %s\n" "$1"; }
|
||||
log_ok() { printf "${GREEN}[OK]${NC} %s\n" "$1"; }
|
||||
log_warn() { printf "${YELLOW}[WARN]${NC} %s\n" "$1"; }
|
||||
log_error() { printf "${RED}[ERROR]${NC} %s\n" "$1"; }
|
||||
|
||||
# Detect architecture
|
||||
detect_arch() {
|
||||
local arch
|
||||
if [ -f /etc/openwrt_release ]; then
|
||||
arch=$(grep "DISTRIB_ARCH" /etc/openwrt_release | cut -d"'" -f2)
|
||||
fi
|
||||
|
||||
if [ -z "$arch" ]; then
|
||||
# Fallback: detect from uname
|
||||
case "$(uname -m)" in
|
||||
x86_64) arch="x86_64" ;;
|
||||
aarch64) arch="aarch64_cortex-a72" ;;
|
||||
armv7l) arch="arm_cortex-a7_neon-vfpv4" ;;
|
||||
*) arch="$(uname -m)" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
echo "$arch"
|
||||
}
|
||||
|
||||
# Repository URLs in order of preference
|
||||
REPO_URLS="
|
||||
https://repo.secubox.in
|
||||
https://secubox.in/repo
|
||||
https://github.com/CyberMind-FR/secubox-openwrt/releases/download/packages
|
||||
"
|
||||
|
||||
# Check connectivity and find working repo
|
||||
check_connectivity() {
|
||||
log_info "Checking network connectivity..."
|
||||
|
||||
# First check general connectivity
|
||||
if ! ping -c 1 -W 3 "downloads.openwrt.org" >/dev/null 2>&1; then
|
||||
if ! wget -q -T 5 --spider "https://downloads.openwrt.org" 2>/dev/null; then
|
||||
log_error "No network connectivity. Please check your network configuration."
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
log_ok "Network connectivity OK"
|
||||
return 0
|
||||
}
|
||||
|
||||
# Find a working SecuBox repository
|
||||
find_working_repo() {
|
||||
local arch="$1"
|
||||
|
||||
# If user specified a mirror, use it directly
|
||||
if [ -n "$SECUBOX_MIRROR" ]; then
|
||||
log_info "Using user-specified mirror: $SECUBOX_MIRROR"
|
||||
echo "$SECUBOX_MIRROR"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Check local feed first (for bonus package)
|
||||
if [ -d "/www/secubox-feed" ] && [ -f "/www/secubox-feed/Packages" ]; then
|
||||
log_info "Found local SecuBox feed at /www/secubox-feed"
|
||||
echo "file:///www/secubox-feed"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Try each remote URL
|
||||
log_info "Searching for working SecuBox repository..."
|
||||
for base_url in $REPO_URLS; do
|
||||
local test_url="${base_url}/packages/${arch}/Packages.gz"
|
||||
log_info "Trying: $base_url"
|
||||
|
||||
# Try wget first (more common on OpenWrt)
|
||||
if wget -q -T 10 --spider "$test_url" 2>/dev/null; then
|
||||
log_ok "Found working repository: $base_url"
|
||||
echo "$base_url"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Try curl as fallback
|
||||
if command -v curl >/dev/null 2>&1; then
|
||||
if curl -sf -m 10 "$test_url" >/dev/null 2>&1; then
|
||||
log_ok "Found working repository: $base_url"
|
||||
echo "$base_url"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
log_warn "No remote SecuBox repository available"
|
||||
return 1
|
||||
}
|
||||
|
||||
# Configure SecuBox repository
|
||||
configure_repo() {
|
||||
local arch="$1"
|
||||
local feeds_file="/etc/opkg/customfeeds.conf"
|
||||
|
||||
log_info "Configuring SecuBox package repository..."
|
||||
|
||||
# Find a working repository
|
||||
local repo_url
|
||||
repo_url=$(find_working_repo "$arch")
|
||||
|
||||
if [ -z "$repo_url" ]; then
|
||||
log_warn "No SecuBox repository found. Packages will need to be installed manually."
|
||||
log_info "You can set SECUBOX_MIRROR to specify a custom repository URL."
|
||||
|
||||
# Still configure the default URL for future use
|
||||
repo_url="https://repo.secubox.in"
|
||||
SECUBOX_REPO_AVAILABLE=0
|
||||
else
|
||||
SECUBOX_REPO_AVAILABLE=1
|
||||
fi
|
||||
|
||||
# Backup existing customfeeds.conf
|
||||
if [ -f "$feeds_file" ]; then
|
||||
cp "$feeds_file" "${feeds_file}.bak"
|
||||
fi
|
||||
|
||||
# Check if SecuBox feed already configured
|
||||
if grep -q "secubox_packages" "$feeds_file" 2>/dev/null; then
|
||||
log_warn "SecuBox feeds already configured, updating..."
|
||||
sed -i '/secubox_/d' "$feeds_file"
|
||||
fi
|
||||
|
||||
# Handle local file:// URLs differently
|
||||
if echo "$repo_url" | grep -q "^file://"; then
|
||||
cat >> "$feeds_file" << EOF
|
||||
|
||||
# SecuBox Local Package Repository
|
||||
# Added by secubox-seed.sh on $(date +%Y-%m-%d)
|
||||
src secubox_local ${repo_url}
|
||||
EOF
|
||||
else
|
||||
cat >> "$feeds_file" << EOF
|
||||
|
||||
# SecuBox Package Repository
|
||||
# Added by secubox-seed.sh on $(date +%Y-%m-%d)
|
||||
src/gz secubox_packages ${repo_url}/packages/${arch}
|
||||
src/gz secubox_luci ${repo_url}/luci/${arch}
|
||||
EOF
|
||||
fi
|
||||
|
||||
log_ok "Repository configured: ${repo_url}"
|
||||
}
|
||||
|
||||
# Update package lists
|
||||
update_packages() {
|
||||
if [ "${SECUBOX_SKIP_UPDATE:-0}" = "1" ]; then
|
||||
log_warn "Skipping package list update (SECUBOX_SKIP_UPDATE=1)"
|
||||
return 0
|
||||
fi
|
||||
|
||||
log_info "Updating package lists..."
|
||||
|
||||
if ! opkg update 2>&1; then
|
||||
log_warn "Some feeds failed to update, continuing anyway..."
|
||||
fi
|
||||
|
||||
log_ok "Package lists updated"
|
||||
}
|
||||
|
||||
# Install a package with fallback
|
||||
install_pkg() {
|
||||
local pkg="$1"
|
||||
local optional="${2:-0}"
|
||||
|
||||
if [ "${SECUBOX_DRY_RUN:-0}" = "1" ]; then
|
||||
log_info "[DRY RUN] Would install: $pkg"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Check if already installed
|
||||
if opkg list-installed | grep -q "^${pkg} "; then
|
||||
log_ok "$pkg already installed"
|
||||
return 0
|
||||
fi
|
||||
|
||||
log_info "Installing $pkg..."
|
||||
|
||||
if opkg install "$pkg" 2>&1; then
|
||||
log_ok "$pkg installed successfully"
|
||||
return 0
|
||||
else
|
||||
if [ "$optional" = "1" ]; then
|
||||
log_warn "$pkg installation failed (optional, continuing)"
|
||||
return 0
|
||||
else
|
||||
log_error "$pkg installation failed"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# Install package group with error handling
|
||||
install_group() {
|
||||
local group_name="$1"
|
||||
shift
|
||||
local packages="$@"
|
||||
local failed=""
|
||||
|
||||
log_info "Installing $group_name packages..."
|
||||
|
||||
for pkg in $packages; do
|
||||
# Check if package is optional (prefixed with ?)
|
||||
local optional=0
|
||||
if echo "$pkg" | grep -q "^?"; then
|
||||
optional=1
|
||||
pkg="${pkg#?}"
|
||||
fi
|
||||
|
||||
if ! install_pkg "$pkg" "$optional"; then
|
||||
failed="$failed $pkg"
|
||||
fi
|
||||
done
|
||||
|
||||
if [ -n "$failed" ]; then
|
||||
log_warn "Some packages failed to install:$failed"
|
||||
return 1
|
||||
fi
|
||||
|
||||
log_ok "$group_name packages installed"
|
||||
return 0
|
||||
}
|
||||
|
||||
# Define package profiles
|
||||
get_profile_packages() {
|
||||
local profile="${1:-standard}"
|
||||
|
||||
case "$profile" in
|
||||
minimal)
|
||||
# Minimal: Just core + theme
|
||||
echo "CORE:secubox-core secubox-base"
|
||||
echo "THEME:luci-theme-secubox"
|
||||
;;
|
||||
standard)
|
||||
# Standard: Core + Security + Basic LuCI apps
|
||||
echo "CORE:secubox-core secubox-base secubox-identity"
|
||||
echo "THEME:luci-theme-secubox"
|
||||
echo "SECURITY:?secubox-app-crowdsec ?secubox-app-cs-firewall-bouncer secubox-app-ipblocklist"
|
||||
echo "NETWORK:secubox-app-haproxy secubox-app-mitmproxy"
|
||||
echo "LUCI:luci-app-secubox luci-app-haproxy ?luci-app-crowdsec-dashboard"
|
||||
;;
|
||||
full)
|
||||
# Full: Everything
|
||||
echo "CORE:secubox-core secubox-base secubox-identity secubox-master-link secubox-p2p"
|
||||
echo "THEME:luci-theme-secubox"
|
||||
echo "SECURITY:?secubox-app-crowdsec ?secubox-app-cs-firewall-bouncer secubox-app-ipblocklist secubox-app-mitmproxy"
|
||||
echo "NETWORK:secubox-app-haproxy secubox-app-dns-master secubox-app-exposure secubox-app-tor"
|
||||
echo "MONITORING:secubox-app-glances secubox-app-netifyd secubox-app-watchdog"
|
||||
echo "LUCI:luci-app-secubox luci-app-haproxy luci-app-exposure luci-app-dns-master"
|
||||
echo "LUCI:?luci-app-crowdsec-dashboard luci-app-glances luci-app-master-link"
|
||||
echo "BONUS:?secubox-app-bonus"
|
||||
;;
|
||||
*)
|
||||
log_error "Unknown profile: $profile"
|
||||
log_info "Available profiles: minimal, standard, full"
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Post-installation setup
|
||||
post_install() {
|
||||
log_info "Running post-installation setup..."
|
||||
|
||||
# Initialize SecuBox if secubox-core is installed
|
||||
if [ -x /usr/sbin/secuboxctl ]; then
|
||||
log_info "Initializing SecuBox..."
|
||||
/usr/sbin/secuboxctl init 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# Enable and start key services
|
||||
local services="secubox haproxy"
|
||||
for svc in $services; do
|
||||
if [ -f "/etc/init.d/$svc" ]; then
|
||||
log_info "Enabling $svc service..."
|
||||
/etc/init.d/$svc enable 2>/dev/null || true
|
||||
fi
|
||||
done
|
||||
|
||||
# Reload rpcd for new RPC methods
|
||||
if [ -f /etc/init.d/rpcd ]; then
|
||||
log_info "Reloading RPCD..."
|
||||
/etc/init.d/rpcd restart 2>/dev/null || true
|
||||
fi
|
||||
|
||||
log_ok "Post-installation setup complete"
|
||||
}
|
||||
|
||||
# Print summary
|
||||
print_summary() {
|
||||
local profile="$1"
|
||||
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
printf "${GREEN}SecuBox Installation Complete${NC}\n"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
echo "Profile: $profile"
|
||||
echo "Architecture: $(detect_arch)"
|
||||
echo ""
|
||||
echo "Access LuCI at: http://$(uci get network.lan.ipaddr 2>/dev/null || echo '192.168.1.1')"
|
||||
echo ""
|
||||
echo "Installed packages:"
|
||||
opkg list-installed | grep -E "^secubox-|^luci-.*secubox|^luci-theme-secubox" | while read line; do
|
||||
echo " - $line"
|
||||
done
|
||||
echo ""
|
||||
echo "For more information:"
|
||||
echo " https://docs.secubox.in"
|
||||
echo ""
|
||||
}
|
||||
|
||||
# Main installation flow
|
||||
main() {
|
||||
local profile="${SECUBOX_PROFILE:-standard}"
|
||||
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
printf "${BLUE}SecuBox Seed Installer${NC}\n"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
log_info "Profile: $profile"
|
||||
|
||||
# Pre-flight checks
|
||||
if [ "$(id -u)" != "0" ]; then
|
||||
log_error "This script must be run as root"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v opkg >/dev/null 2>&1; then
|
||||
log_error "opkg not found. Is this an OpenWrt system?"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Detect architecture
|
||||
local arch
|
||||
arch=$(detect_arch)
|
||||
log_info "Detected architecture: $arch"
|
||||
|
||||
# Check connectivity
|
||||
if ! check_connectivity; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Configure repository
|
||||
configure_repo "$arch"
|
||||
|
||||
# Update package lists
|
||||
update_packages
|
||||
|
||||
# Check if repo is available before trying to install
|
||||
if [ "${SECUBOX_REPO_AVAILABLE:-1}" = "0" ]; then
|
||||
log_warn "SecuBox repository not available. Skipping package installation."
|
||||
log_info "Repository has been configured for future use."
|
||||
log_info "When packages are available, run: opkg update && opkg install secubox-core"
|
||||
if [ "${SECUBOX_DRY_RUN:-0}" != "1" ]; then
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
printf "${YELLOW}SecuBox Setup Incomplete${NC}\n"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
echo "Repository configured but packages not available."
|
||||
echo "To complete installation later:"
|
||||
echo " opkg update"
|
||||
echo " opkg install secubox-core secubox-base luci-theme-secubox"
|
||||
echo ""
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Get packages for selected profile
|
||||
local profile_data
|
||||
profile_data=$(get_profile_packages "$profile")
|
||||
|
||||
if [ -z "$profile_data" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Install each package group
|
||||
local install_failed=0
|
||||
echo "$profile_data" | while IFS=: read group packages; do
|
||||
if ! install_group "$group" $packages; then
|
||||
install_failed=1
|
||||
fi
|
||||
done
|
||||
|
||||
# Post-installation
|
||||
if [ "${SECUBOX_DRY_RUN:-0}" != "1" ]; then
|
||||
post_install
|
||||
print_summary "$profile"
|
||||
else
|
||||
log_info "[DRY RUN] Skipping post-installation"
|
||||
fi
|
||||
|
||||
if [ "$install_failed" = "1" ]; then
|
||||
log_warn "Installation completed with some errors"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log_ok "SecuBox installation completed successfully!"
|
||||
}
|
||||
|
||||
# Handle command-line arguments
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--profile=*)
|
||||
SECUBOX_PROFILE="${1#*=}"
|
||||
;;
|
||||
--mirror=*)
|
||||
SECUBOX_MIRROR="${1#*=}"
|
||||
;;
|
||||
--dry-run)
|
||||
SECUBOX_DRY_RUN=1
|
||||
;;
|
||||
--skip-update)
|
||||
SECUBOX_SKIP_UPDATE=1
|
||||
;;
|
||||
--help|-h)
|
||||
echo "SecuBox Seed Installer"
|
||||
echo ""
|
||||
echo "Usage: $0 [OPTIONS]"
|
||||
echo ""
|
||||
echo "Options:"
|
||||
echo " --profile=PROFILE Installation profile (minimal|standard|full)"
|
||||
echo " --mirror=URL Override repository URL"
|
||||
echo " --dry-run Show what would be installed"
|
||||
echo " --skip-update Skip opkg update"
|
||||
echo " --help Show this help"
|
||||
echo ""
|
||||
echo "Environment variables:"
|
||||
echo " SECUBOX_PROFILE Same as --profile"
|
||||
echo " SECUBOX_MIRROR Same as --mirror"
|
||||
echo " SECUBOX_DRY_RUN=1 Same as --dry-run"
|
||||
echo " SECUBOX_SKIP_UPDATE=1 Same as --skip-update"
|
||||
echo ""
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
log_error "Unknown option: $1"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
main
|
||||
396
scripts/secubox-slipstream.sh
Executable file
396
scripts/secubox-slipstream.sh
Executable file
@ -0,0 +1,396 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# SecuBox Slipstream Script
|
||||
# Bakes SecuBox repository configuration into OpenWrt images during build
|
||||
#
|
||||
# This script is designed to be run during the OpenWrt build process
|
||||
# to pre-configure the SecuBox repository and optionally pre-install packages.
|
||||
#
|
||||
# Usage (during OpenWrt build):
|
||||
# ./scripts/secubox-slipstream.sh <rootfs_dir> [--profile=PROFILE]
|
||||
#
|
||||
# Or as a standalone image modifier:
|
||||
# ./scripts/secubox-slipstream.sh --image=<image.img.gz> [--profile=PROFILE]
|
||||
#
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
REPO_URL="${SECUBOX_REPO_URL:-https://repo.secubox.in}"
|
||||
PROFILE="${SECUBOX_PROFILE:-standard}"
|
||||
|
||||
# Colors
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m'
|
||||
|
||||
log_info() { printf "${BLUE}[SLIPSTREAM]${NC} %s\n" "$1"; }
|
||||
log_ok() { printf "${GREEN}[SLIPSTREAM]${NC} %s\n" "$1"; }
|
||||
log_warn() { printf "${YELLOW}[SLIPSTREAM]${NC} %s\n" "$1"; }
|
||||
log_error() { printf "${RED}[SLIPSTREAM]${NC} %s\n" "$1"; }
|
||||
|
||||
# Detect architecture from rootfs
|
||||
detect_arch() {
|
||||
local rootfs="$1"
|
||||
local arch=""
|
||||
|
||||
# Try reading from openwrt_release
|
||||
if [ -f "$rootfs/etc/openwrt_release" ]; then
|
||||
arch=$(grep "DISTRIB_ARCH" "$rootfs/etc/openwrt_release" 2>/dev/null | cut -d"'" -f2)
|
||||
fi
|
||||
|
||||
# Fallback: check for common arch indicators
|
||||
if [ -z "$arch" ]; then
|
||||
if [ -d "$rootfs/lib/aarch64-linux-gnu" ] || file "$rootfs/bin/busybox" 2>/dev/null | grep -q "aarch64"; then
|
||||
arch="aarch64_cortex-a72"
|
||||
elif file "$rootfs/bin/busybox" 2>/dev/null | grep -q "x86-64"; then
|
||||
arch="x86_64"
|
||||
elif file "$rootfs/bin/busybox" 2>/dev/null | grep -q "ARM"; then
|
||||
arch="arm_cortex-a7_neon-vfpv4"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "$arch"
|
||||
}
|
||||
|
||||
# Create the customfeeds.conf with SecuBox repository
|
||||
create_feeds_config() {
|
||||
local rootfs="$1"
|
||||
local arch="$2"
|
||||
local feeds_file="$rootfs/etc/opkg/customfeeds.conf"
|
||||
|
||||
log_info "Creating SecuBox feed configuration..."
|
||||
|
||||
mkdir -p "$rootfs/etc/opkg"
|
||||
|
||||
cat > "$feeds_file" << EOF
|
||||
# SecuBox Package Repository
|
||||
# Pre-configured by secubox-slipstream.sh
|
||||
# Architecture: $arch
|
||||
# Generated: $(date -u +%Y-%m-%dT%H:%M:%SZ)
|
||||
|
||||
src/gz secubox_packages ${REPO_URL}/packages/${arch}
|
||||
src/gz secubox_luci ${REPO_URL}/luci/${arch}
|
||||
EOF
|
||||
|
||||
log_ok "Feed configuration created: $feeds_file"
|
||||
}
|
||||
|
||||
# Install the seed script into the image
|
||||
install_seed_script() {
|
||||
local rootfs="$1"
|
||||
local seed_script="$SCRIPT_DIR/secubox-seed.sh"
|
||||
|
||||
log_info "Installing seed script..."
|
||||
|
||||
mkdir -p "$rootfs/usr/sbin"
|
||||
|
||||
if [ -f "$seed_script" ]; then
|
||||
cp "$seed_script" "$rootfs/usr/sbin/secubox-seed"
|
||||
chmod +x "$rootfs/usr/sbin/secubox-seed"
|
||||
log_ok "Seed script installed: /usr/sbin/secubox-seed"
|
||||
else
|
||||
log_warn "Seed script not found: $seed_script"
|
||||
fi
|
||||
}
|
||||
|
||||
# Create first-boot script for auto-setup
|
||||
create_firstboot_script() {
|
||||
local rootfs="$1"
|
||||
local profile="$2"
|
||||
|
||||
log_info "Creating first-boot setup script..."
|
||||
|
||||
mkdir -p "$rootfs/etc/uci-defaults"
|
||||
|
||||
cat > "$rootfs/etc/uci-defaults/99-secubox-setup" << 'FIRSTBOOT'
|
||||
#!/bin/sh
|
||||
#
|
||||
# SecuBox First-Boot Setup
|
||||
# Runs once on first boot to complete SecuBox installation
|
||||
#
|
||||
|
||||
PROFILE="${SECUBOX_PROFILE:-standard}"
|
||||
LOG_FILE="/tmp/secubox-firstboot.log"
|
||||
|
||||
log() {
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
|
||||
}
|
||||
|
||||
log "SecuBox first-boot setup starting..."
|
||||
|
||||
# Check if opkg feeds are already configured
|
||||
if grep -q "secubox_packages" /etc/opkg/customfeeds.conf 2>/dev/null; then
|
||||
log "SecuBox feeds already configured"
|
||||
else
|
||||
log "Error: SecuBox feeds not configured!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Update package lists
|
||||
log "Updating package lists..."
|
||||
if opkg update 2>&1 | tee -a "$LOG_FILE"; then
|
||||
log "Package lists updated successfully"
|
||||
else
|
||||
log "Warning: opkg update had errors"
|
||||
fi
|
||||
|
||||
# Install core packages if not already installed
|
||||
CORE_PACKAGES="secubox-core secubox-base luci-theme-secubox"
|
||||
|
||||
for pkg in $CORE_PACKAGES; do
|
||||
if ! opkg list-installed | grep -q "^$pkg "; then
|
||||
log "Installing $pkg..."
|
||||
if opkg install "$pkg" 2>&1 | tee -a "$LOG_FILE"; then
|
||||
log "$pkg installed successfully"
|
||||
else
|
||||
log "Warning: $pkg installation failed"
|
||||
fi
|
||||
else
|
||||
log "$pkg already installed"
|
||||
fi
|
||||
done
|
||||
|
||||
# Set SecuBox theme as default
|
||||
if [ -f /etc/config/luci ]; then
|
||||
uci set luci.main.mediaurlbase='/luci-static/secubox' 2>/dev/null || true
|
||||
uci commit luci 2>/dev/null || true
|
||||
log "SecuBox theme set as default"
|
||||
fi
|
||||
|
||||
# Mark setup complete
|
||||
touch /etc/secubox-installed
|
||||
log "SecuBox first-boot setup complete"
|
||||
|
||||
# Remove this script (run once)
|
||||
exit 0
|
||||
FIRSTBOOT
|
||||
|
||||
chmod +x "$rootfs/etc/uci-defaults/99-secubox-setup"
|
||||
log_ok "First-boot script created"
|
||||
}
|
||||
|
||||
# Create SecuBox branding
|
||||
create_branding() {
|
||||
local rootfs="$1"
|
||||
|
||||
log_info "Adding SecuBox branding..."
|
||||
|
||||
# Custom banner
|
||||
mkdir -p "$rootfs/etc"
|
||||
cat > "$rootfs/etc/banner" << 'BANNER'
|
||||
|
||||
____ ____
|
||||
/ ___| ___ ___ _ _| __ ) _____ __
|
||||
\___ \ / _ \/ __| | | | _ \ / _ \ \/ /
|
||||
___) | __/ (__| |_| | |_) | (_) > <
|
||||
|____/ \___|\___|\__,_|____/ \___/_/\_\
|
||||
|
||||
SecuBox - Security Gateway for OpenWrt
|
||||
https://secubox.in
|
||||
|
||||
Run 'secubox-seed --help' for setup options
|
||||
|
||||
BANNER
|
||||
|
||||
# Version info
|
||||
cat > "$rootfs/etc/secubox-release" << EOF
|
||||
SECUBOX_VERSION="1.0.0"
|
||||
SECUBOX_CODENAME="Genesis"
|
||||
SECUBOX_BUILD_DATE="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
||||
SECUBOX_PROFILE="${PROFILE}"
|
||||
EOF
|
||||
|
||||
log_ok "Branding added"
|
||||
}
|
||||
|
||||
# Slipstream into an existing rootfs directory
|
||||
slipstream_rootfs() {
|
||||
local rootfs="$1"
|
||||
local profile="$2"
|
||||
|
||||
log_info "Slipstreaming SecuBox into rootfs: $rootfs"
|
||||
|
||||
if [ ! -d "$rootfs" ]; then
|
||||
log_error "Rootfs directory not found: $rootfs"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Detect architecture
|
||||
local arch
|
||||
arch=$(detect_arch "$rootfs")
|
||||
if [ -z "$arch" ]; then
|
||||
log_warn "Could not detect architecture, using x86_64"
|
||||
arch="x86_64"
|
||||
fi
|
||||
log_info "Detected architecture: $arch"
|
||||
|
||||
# Apply slipstream
|
||||
create_feeds_config "$rootfs" "$arch"
|
||||
install_seed_script "$rootfs"
|
||||
create_firstboot_script "$rootfs" "$profile"
|
||||
create_branding "$rootfs"
|
||||
|
||||
log_ok "Slipstream complete for: $rootfs"
|
||||
}
|
||||
|
||||
# Slipstream into a compressed image file
|
||||
slipstream_image() {
|
||||
local image="$1"
|
||||
local profile="$2"
|
||||
|
||||
log_info "Slipstreaming SecuBox into image: $image"
|
||||
|
||||
if [ ! -f "$image" ]; then
|
||||
log_error "Image file not found: $image"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Create temp directory
|
||||
local tmpdir
|
||||
tmpdir=$(mktemp -d)
|
||||
trap "rm -rf '$tmpdir'" EXIT
|
||||
|
||||
local mount_point="$tmpdir/rootfs"
|
||||
local work_image="$tmpdir/work.img"
|
||||
mkdir -p "$mount_point"
|
||||
|
||||
# Decompress if needed
|
||||
case "$image" in
|
||||
*.gz)
|
||||
log_info "Decompressing image..."
|
||||
gunzip -c "$image" > "$work_image"
|
||||
;;
|
||||
*.img|*.raw)
|
||||
cp "$image" "$work_image"
|
||||
;;
|
||||
*)
|
||||
log_error "Unsupported image format: $image"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
# Find and mount rootfs partition
|
||||
log_info "Mounting image..."
|
||||
|
||||
# Try to find rootfs partition offset
|
||||
local offset
|
||||
offset=$(fdisk -l "$work_image" 2>/dev/null | grep -E "Linux|ext4" | tail -1 | awk '{print $2}')
|
||||
|
||||
if [ -n "$offset" ]; then
|
||||
offset=$((offset * 512))
|
||||
sudo mount -o loop,offset=$offset "$work_image" "$mount_point"
|
||||
else
|
||||
# Try mounting as-is (might be raw rootfs)
|
||||
sudo mount -o loop "$work_image" "$mount_point"
|
||||
fi
|
||||
|
||||
# Apply slipstream
|
||||
slipstream_rootfs "$mount_point" "$profile"
|
||||
|
||||
# Unmount
|
||||
log_info "Unmounting image..."
|
||||
sudo umount "$mount_point"
|
||||
|
||||
# Recompress if original was compressed
|
||||
case "$image" in
|
||||
*.gz)
|
||||
log_info "Recompressing image..."
|
||||
local output="${image%.gz}-secubox.img.gz"
|
||||
gzip -c "$work_image" > "$output"
|
||||
log_ok "Output: $output"
|
||||
;;
|
||||
*)
|
||||
local output="${image%.*}-secubox.${image##*.}"
|
||||
mv "$work_image" "$output"
|
||||
log_ok "Output: $output"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Print usage
|
||||
usage() {
|
||||
cat << EOF
|
||||
SecuBox Slipstream Script
|
||||
|
||||
Usage:
|
||||
$0 <rootfs_dir> Slipstream into rootfs directory
|
||||
$0 --image=<file.img.gz> Slipstream into image file
|
||||
|
||||
Options:
|
||||
--profile=PROFILE Installation profile (minimal|standard|full)
|
||||
--repo=URL Override repository URL
|
||||
--help Show this help
|
||||
|
||||
Environment:
|
||||
SECUBOX_REPO_URL Repository URL (default: https://repo.secubox.in)
|
||||
SECUBOX_PROFILE Installation profile (default: standard)
|
||||
|
||||
Examples:
|
||||
# During OpenWrt build (in files/):
|
||||
$0 ../build_dir/target-*/root-*/
|
||||
|
||||
# Modify existing image:
|
||||
$0 --image=openwrt-x86-64-generic-ext4-combined.img.gz
|
||||
|
||||
# With custom profile:
|
||||
$0 --profile=full /path/to/rootfs
|
||||
EOF
|
||||
}
|
||||
|
||||
# Main
|
||||
main() {
|
||||
local rootfs=""
|
||||
local image=""
|
||||
|
||||
# Parse arguments
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--image=*)
|
||||
image="${1#*=}"
|
||||
;;
|
||||
--profile=*)
|
||||
PROFILE="${1#*=}"
|
||||
;;
|
||||
--repo=*)
|
||||
REPO_URL="${1#*=}"
|
||||
;;
|
||||
--help|-h)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
-*)
|
||||
log_error "Unknown option: $1"
|
||||
usage
|
||||
exit 1
|
||||
;;
|
||||
*)
|
||||
rootfs="$1"
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo " SecuBox Slipstream"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo " Profile: $PROFILE"
|
||||
echo " Repo: $REPO_URL"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
|
||||
if [ -n "$image" ]; then
|
||||
slipstream_image "$image" "$PROFILE"
|
||||
elif [ -n "$rootfs" ]; then
|
||||
slipstream_rootfs "$rootfs" "$PROFILE"
|
||||
else
|
||||
log_error "No rootfs directory or image specified"
|
||||
usage
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
main "$@"
|
||||
Loading…
Reference in New Issue
Block a user