mirror of
https://github.com/LFManifesto/ReticulumHF
synced 2026-08-14 19:31:48 -04:00
Add v1.0 image build system and configuration files
- Add image/build.sh: Automated Pi image builder for ReticulumHF v1.0 - Add configs/: System configuration files (hostapd, dnsmasq, ALSA, radios DB) - Add scripts/wait-for-port: Service dependency utility - Update .gitignore: Exclude .claude/ and test files This release includes the complete build infrastructure for creating ReticulumHF Raspberry Pi images with pre-configured WiFi AP, web portal, and Reticulum gateway services.
This commit is contained in:
parent
92d56eb041
commit
92c0a239a2
8 changed files with 1701 additions and 0 deletions
7
.gitignore
vendored
7
.gitignore
vendored
|
|
@ -25,3 +25,10 @@ ENV/
|
|||
# Local configuration
|
||||
.env
|
||||
.env.local
|
||||
|
||||
# Claude Code
|
||||
.claude/
|
||||
|
||||
# Test files
|
||||
test_*.py
|
||||
*_test.py
|
||||
|
|
|
|||
51
configs/asound.conf
Normal file
51
configs/asound.conf
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
# ReticulumHF ALSA Configuration
|
||||
# Configures USB audio device (Digirig or similar) for freedvtnc2
|
||||
#
|
||||
# This file should be installed to /etc/asound.conf
|
||||
|
||||
# Disable the modem PCM type which causes "Unknown PCM cards.pcm.modem" error
|
||||
# This is a known issue with ALSA when using USB audio devices
|
||||
pcm.!modem {
|
||||
type null
|
||||
}
|
||||
|
||||
ctl.!modem {
|
||||
type null
|
||||
}
|
||||
|
||||
# Define USB audio as a named device
|
||||
# freedvtnc2 uses PortAudio which will enumerate these devices
|
||||
pcm.usbaudio {
|
||||
type hw
|
||||
card 1
|
||||
device 0
|
||||
}
|
||||
|
||||
ctl.usbaudio {
|
||||
type hw
|
||||
card 1
|
||||
}
|
||||
|
||||
# Software mixing for USB audio (allows multiple applications to use the device)
|
||||
pcm.usbmix {
|
||||
type dmix
|
||||
ipc_key 1024
|
||||
slave {
|
||||
pcm "usbaudio"
|
||||
period_time 0
|
||||
period_size 1024
|
||||
buffer_size 4096
|
||||
}
|
||||
}
|
||||
|
||||
# Duplex device for simultaneous input/output
|
||||
pcm.usbduplex {
|
||||
type asym
|
||||
playback.pcm "usbmix"
|
||||
capture.pcm "usbaudio"
|
||||
}
|
||||
|
||||
# Default device - use built-in audio to avoid conflicts
|
||||
# freedvtnc2 explicitly selects its device via PortAudio
|
||||
defaults.pcm.card 0
|
||||
defaults.ctl.card 0
|
||||
21
configs/dnsmasq.conf
Normal file
21
configs/dnsmasq.conf
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
# ReticulumHF - dnsmasq configuration
|
||||
# Provides DHCP for setup WiFi network
|
||||
|
||||
# Only listen on wlan0
|
||||
interface=wlan0
|
||||
bind-interfaces
|
||||
|
||||
# DHCP range for connected clients
|
||||
dhcp-range=192.168.4.10,192.168.4.50,255.255.255.0,24h
|
||||
|
||||
# Set gateway to Pi
|
||||
dhcp-option=3,192.168.4.1
|
||||
|
||||
# Set DNS to Pi (for local resolution only)
|
||||
dhcp-option=6,192.168.4.1
|
||||
|
||||
# Don't use upstream DNS - no internet through AP
|
||||
no-resolv
|
||||
|
||||
# Disable captive portal detection redirect
|
||||
# (Let users manually browse to 192.168.4.1)
|
||||
2
configs/hostapd-rfkill.conf
Normal file
2
configs/hostapd-rfkill.conf
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
[Service]
|
||||
ExecStartPre=/usr/sbin/rfkill unblock wlan
|
||||
22
configs/hostapd.conf
Normal file
22
configs/hostapd.conf
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
# ReticulumHF - WiFi Access Point configuration
|
||||
# Creates "ReticulumHF-Setup" network
|
||||
|
||||
interface=wlan0
|
||||
driver=nl80211
|
||||
ssid=ReticulumHF-Setup
|
||||
hw_mode=g
|
||||
channel=7
|
||||
wmm_enabled=0
|
||||
macaddr_acl=0
|
||||
auth_algs=1
|
||||
ignore_broadcast_ssid=0
|
||||
|
||||
# WPA2 security - default password: reticulumhf
|
||||
wpa=2
|
||||
wpa_passphrase=reticulumhf
|
||||
wpa_key_mgmt=WPA-PSK
|
||||
rsn_pairwise=CCMP
|
||||
|
||||
# Country code (US default - users can change)
|
||||
country_code=US
|
||||
ieee80211n=1
|
||||
1251
configs/radios.json
Normal file
1251
configs/radios.json
Normal file
File diff suppressed because it is too large
Load diff
331
image/build.sh
Executable file
331
image/build.sh
Executable file
|
|
@ -0,0 +1,331 @@
|
|||
#!/bin/bash
|
||||
# ReticulumHF Pi Image Build Script
|
||||
# Creates a custom Raspberry Pi OS Bookworm image with ReticulumHF pre-installed
|
||||
#
|
||||
# WiFi AP: Uses hostapd + dnsmasq (proven stable solution)
|
||||
# Base OS: Raspberry Pi OS Bookworm Lite (64-bit)
|
||||
#
|
||||
# Usage: ./build.sh [base-image.img]
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
|
||||
BUILD_DIR="$PROJECT_DIR/build"
|
||||
OUTPUT_DIR="$PROJECT_DIR/output"
|
||||
MOUNT_DIR="$BUILD_DIR/mnt"
|
||||
LOOP_DEV=""
|
||||
|
||||
cleanup() {
|
||||
echo "Cleaning up..."
|
||||
sudo fuser -k "$MOUNT_DIR" 2>/dev/null || true
|
||||
sleep 1
|
||||
if [ -f "$MOUNT_DIR/etc/ld.so.preload.bak" ]; then
|
||||
sudo mv "$MOUNT_DIR/etc/ld.so.preload.bak" "$MOUNT_DIR/etc/ld.so.preload" 2>/dev/null || true
|
||||
fi
|
||||
sudo umount "$MOUNT_DIR/dev/pts" 2>/dev/null || true
|
||||
sudo umount "$MOUNT_DIR/dev" 2>/dev/null || true
|
||||
sudo umount "$MOUNT_DIR/proc" 2>/dev/null || true
|
||||
sudo umount "$MOUNT_DIR/sys" 2>/dev/null || true
|
||||
sudo umount "$MOUNT_DIR/boot/firmware" 2>/dev/null || true
|
||||
sudo umount "$MOUNT_DIR" 2>/dev/null || true
|
||||
if [ -n "$LOOP_DEV" ]; then
|
||||
sudo losetup -d "$LOOP_DEV" 2>/dev/null || true
|
||||
fi
|
||||
}
|
||||
|
||||
trap cleanup EXIT
|
||||
|
||||
BASE_IMAGE="${1:-bookworm-lite.img}"
|
||||
OUTPUT_IMAGE="reticulumhf_v1.0.img"
|
||||
|
||||
echo "============================================"
|
||||
echo "ReticulumHF Pi Image Builder"
|
||||
echo "============================================"
|
||||
echo "Base image: $BASE_IMAGE"
|
||||
echo "Output: $OUTPUT_IMAGE"
|
||||
echo "Approach: Bookworm + hostapd/dnsmasq"
|
||||
echo ""
|
||||
|
||||
mkdir -p "$BUILD_DIR" "$OUTPUT_DIR"
|
||||
|
||||
if [ ! -f "$BASE_IMAGE" ]; then
|
||||
echo "Error: Base image not found: $BASE_IMAGE"
|
||||
echo ""
|
||||
echo "Download Raspberry Pi OS Lite Bookworm (64-bit) from:"
|
||||
echo "https://www.raspberrypi.com/software/operating-systems/"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "[1/10] Copying base image..."
|
||||
cp "$BASE_IMAGE" "$BUILD_DIR/$OUTPUT_IMAGE"
|
||||
|
||||
echo "[2/10] Expanding image (adding 3GB for software)..."
|
||||
dd if=/dev/zero bs=1M count=3072 >> "$BUILD_DIR/$OUTPUT_IMAGE"
|
||||
|
||||
echo "[3/10] Setting up loop device..."
|
||||
LOOP_DEV=$(sudo losetup -fP --show "$BUILD_DIR/$OUTPUT_IMAGE")
|
||||
echo "Loop device: $LOOP_DEV"
|
||||
|
||||
echo "[4/10] Resizing partition..."
|
||||
sudo parted -s "$LOOP_DEV" resizepart 2 100%
|
||||
sudo e2fsck -fy "${LOOP_DEV}p2" 2>/dev/null || echo "Warning: e2fsck returned non-zero"
|
||||
sudo resize2fs -f "${LOOP_DEV}p2"
|
||||
|
||||
echo "[5/10] Mounting filesystems..."
|
||||
mkdir -p "$MOUNT_DIR"
|
||||
sudo mount "${LOOP_DEV}p2" "$MOUNT_DIR"
|
||||
sudo mkdir -p "$MOUNT_DIR/boot/firmware"
|
||||
sudo mount "${LOOP_DEV}p1" "$MOUNT_DIR/boot/firmware"
|
||||
|
||||
sudo mount --bind /dev "$MOUNT_DIR/dev"
|
||||
sudo mount --bind /dev/pts "$MOUNT_DIR/dev/pts"
|
||||
sudo mount -t proc proc "$MOUNT_DIR/proc"
|
||||
sudo mount -t sysfs sysfs "$MOUNT_DIR/sys"
|
||||
|
||||
echo "[6/10] Preparing chroot environment..."
|
||||
|
||||
if [ -f /usr/bin/qemu-aarch64-static ]; then
|
||||
sudo cp /usr/bin/qemu-aarch64-static "$MOUNT_DIR/usr/bin/"
|
||||
fi
|
||||
|
||||
# Use public DNS (not host's - may be Tailscale/VPN which won't work in chroot)
|
||||
echo "nameserver 1.1.1.1" | sudo tee "$MOUNT_DIR/etc/resolv.conf" > /dev/null
|
||||
echo "nameserver 8.8.8.8" | sudo tee -a "$MOUNT_DIR/etc/resolv.conf" > /dev/null
|
||||
|
||||
if [ -f "$MOUNT_DIR/etc/ld.so.preload" ]; then
|
||||
sudo cp "$MOUNT_DIR/etc/ld.so.preload" "$MOUNT_DIR/etc/ld.so.preload.bak"
|
||||
sudo sed -i 's/^/#DISABLED /g' "$MOUNT_DIR/etc/ld.so.preload"
|
||||
fi
|
||||
|
||||
echo "[7/10] Installing ReticulumHF files..."
|
||||
|
||||
sudo mkdir -p "$MOUNT_DIR/opt/reticulumhf"
|
||||
sudo cp -r "$PROJECT_DIR/setup-portal" "$MOUNT_DIR/opt/reticulumhf/"
|
||||
sudo cp -r "$PROJECT_DIR/configs" "$MOUNT_DIR/opt/reticulumhf/"
|
||||
sudo cp -r "$PROJECT_DIR/scripts" "$MOUNT_DIR/opt/reticulumhf/"
|
||||
|
||||
sudo cp "$PROJECT_DIR/services/"*.service "$MOUNT_DIR/etc/systemd/system/"
|
||||
|
||||
# Install ALSA configuration for USB audio (fixes freedvtnc2 "Unknown PCM" errors)
|
||||
sudo cp "$PROJECT_DIR/configs/asound.conf" "$MOUNT_DIR/etc/asound.conf"
|
||||
|
||||
# Install wait-for-port utility (used by service files, replaces netcat dependency)
|
||||
sudo cp "$PROJECT_DIR/scripts/wait-for-port" "$MOUNT_DIR/usr/local/bin/"
|
||||
sudo chmod +x "$MOUNT_DIR/usr/local/bin/wait-for-port"
|
||||
|
||||
sudo ln -sf /etc/systemd/system/reticulumhf-firstboot.service \
|
||||
"$MOUNT_DIR/etc/systemd/system/multi-user.target.wants/reticulumhf-firstboot.service"
|
||||
|
||||
# Enable rnsd service (will be started by first-boot, then auto-start on subsequent boots)
|
||||
sudo ln -sf /etc/systemd/system/reticulumhf-rnsd.service \
|
||||
"$MOUNT_DIR/etc/systemd/system/multi-user.target.wants/reticulumhf-rnsd.service"
|
||||
|
||||
# Enable portal service
|
||||
sudo ln -sf /etc/systemd/system/reticulumhf-portal.service \
|
||||
"$MOUNT_DIR/etc/systemd/system/multi-user.target.wants/reticulumhf-portal.service"
|
||||
|
||||
# Enable wlan0 static IP service (runs before hostapd)
|
||||
sudo ln -sf /etc/systemd/system/reticulumhf-wlan.service \
|
||||
"$MOUNT_DIR/etc/systemd/system/multi-user.target.wants/reticulumhf-wlan.service"
|
||||
|
||||
# Install hostapd rfkill override (unblock WiFi before starting hostapd)
|
||||
sudo mkdir -p "$MOUNT_DIR/etc/systemd/system/hostapd.service.d"
|
||||
sudo cp "$PROJECT_DIR/configs/hostapd-rfkill.conf" "$MOUNT_DIR/etc/systemd/system/hostapd.service.d/rfkill.conf"
|
||||
|
||||
echo "[8/10] Running installation in chroot..."
|
||||
|
||||
cat << 'CHROOT_SCRIPT' | sudo tee "$MOUNT_DIR/tmp/install.sh"
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
echo "============================================"
|
||||
echo "ReticulumHF Chroot Installation"
|
||||
echo "============================================"
|
||||
|
||||
# Set hostname
|
||||
echo "reticulumhf" > /etc/hostname
|
||||
sed -i 's/127.0.1.1.*/127.0.1.1\treticulumhf/' /etc/hosts
|
||||
|
||||
# Create pi user
|
||||
echo "[1/9] Creating pi user..."
|
||||
if ! id pi &>/dev/null; then
|
||||
useradd -m -s /bin/bash -G sudo,dialout,audio,video,plugdev,netdev,gpio,i2c,spi pi
|
||||
fi
|
||||
echo "pi:reticulumhf" | chpasswd
|
||||
mkdir -p /home/pi/.config
|
||||
touch /home/pi/.config/user-dirs.locale
|
||||
chown -R pi:pi /home/pi/.config
|
||||
|
||||
# Enable SSH
|
||||
echo "[2/9] Enabling SSH..."
|
||||
systemctl enable ssh
|
||||
|
||||
# Disable first-boot wizard
|
||||
echo "[3/9] Disabling first-boot wizard..."
|
||||
systemctl disable userconfig 2>/dev/null || true
|
||||
systemctl disable piwiz 2>/dev/null || true
|
||||
systemctl mask userconfig 2>/dev/null || true
|
||||
systemctl mask piwiz 2>/dev/null || true
|
||||
apt-get remove -y piwiz userconf-pi 2>/dev/null || true
|
||||
mkdir -p /etc/skel/.config
|
||||
touch /etc/skel/.config/user-dirs.locale
|
||||
|
||||
# Install system packages
|
||||
echo "[4/9] Installing system packages..."
|
||||
# Force IPv4 to avoid IPv6 connectivity issues with RPi repos
|
||||
echo 'Acquire::ForceIPv4 "true";' > /etc/apt/apt.conf.d/99force-ipv4
|
||||
apt-get update
|
||||
apt-get install -y \
|
||||
python3 python3-pip python3-venv python3-dev python3-flask pipx \
|
||||
git build-essential cmake \
|
||||
portaudio19-dev alsa-utils \
|
||||
libhamlib-utils libhamlib-dev \
|
||||
hostapd dnsmasq \
|
||||
iptables \
|
||||
fake-hwclock
|
||||
|
||||
# Configure fake-hwclock to preserve time across reboots
|
||||
# This fixes the "wrong date on first boot" issue for Pi without RTC
|
||||
echo "[4.1/9] Configuring fake-hwclock..."
|
||||
systemctl enable fake-hwclock
|
||||
|
||||
# Configure hostapd (WiFi AP)
|
||||
echo "[5/9] Configuring hostapd..."
|
||||
cat > /etc/hostapd/hostapd.conf << 'HOSTAPD'
|
||||
interface=wlan0
|
||||
driver=nl80211
|
||||
ssid=ReticulumHF
|
||||
hw_mode=g
|
||||
channel=7
|
||||
wmm_enabled=0
|
||||
macaddr_acl=0
|
||||
auth_algs=1
|
||||
ignore_broadcast_ssid=0
|
||||
wpa=2
|
||||
wpa_passphrase=reticulumhf
|
||||
wpa_key_mgmt=WPA-PSK
|
||||
wpa_pairwise=TKIP
|
||||
rsn_pairwise=CCMP
|
||||
HOSTAPD
|
||||
|
||||
# Point hostapd to config
|
||||
sed -i 's|^#DAEMON_CONF=.*|DAEMON_CONF="/etc/hostapd/hostapd.conf"|' /etc/default/hostapd 2>/dev/null || true
|
||||
echo 'DAEMON_CONF="/etc/hostapd/hostapd.conf"' >> /etc/default/hostapd
|
||||
|
||||
# Configure dnsmasq (DHCP + DNS for AP)
|
||||
echo "[6/9] Configuring dnsmasq..."
|
||||
cat > /etc/dnsmasq.d/reticulumhf.conf << 'DNSMASQ'
|
||||
interface=wlan0
|
||||
dhcp-range=192.168.4.2,192.168.4.20,255.255.255.0,24h
|
||||
address=/reticulumhf.local/192.168.4.1
|
||||
DNSMASQ
|
||||
|
||||
# Configure static IP for wlan0
|
||||
cat >> /etc/dhcpcd.conf << 'DHCPCD'
|
||||
|
||||
# ReticulumHF WiFi AP static IP
|
||||
interface wlan0
|
||||
static ip_address=192.168.4.1/24
|
||||
nohook wpa_supplicant
|
||||
DHCPCD
|
||||
|
||||
# Enable IP forwarding (for internet sharing later if connected via ethernet)
|
||||
echo "net.ipv4.ip_forward=1" >> /etc/sysctl.conf
|
||||
|
||||
# Tell NetworkManager to not manage wlan0 (we use hostapd for AP mode)
|
||||
mkdir -p /etc/NetworkManager/conf.d
|
||||
cat > /etc/NetworkManager/conf.d/unmanaged-wlan0.conf << 'NMCONF'
|
||||
[keyfile]
|
||||
unmanaged-devices=interface-name:wlan0
|
||||
NMCONF
|
||||
|
||||
# Disable hostapd and dnsmasq from auto-starting
|
||||
# First-boot script will start them after verifying everything
|
||||
systemctl disable hostapd
|
||||
systemctl disable dnsmasq
|
||||
|
||||
# Build codec2 from source
|
||||
echo "[7/9] Building codec2 from source..."
|
||||
cd /tmp
|
||||
rm -rf codec2
|
||||
git clone https://github.com/drowe67/codec2.git
|
||||
cd codec2
|
||||
mkdir -p build_linux && cd build_linux
|
||||
cmake .. -DCMAKE_BUILD_TYPE=Release
|
||||
make -j$(nproc)
|
||||
make install
|
||||
ldconfig
|
||||
cd /
|
||||
rm -rf /tmp/codec2
|
||||
echo "codec2 installed:"
|
||||
ldconfig -p | grep codec2
|
||||
|
||||
# Install Python packages for pi user
|
||||
echo "[8/9] Installing Python packages for pi user..."
|
||||
export HOME=/home/pi
|
||||
mkdir -p /home/pi/.local/bin
|
||||
chown -R pi:pi /home/pi
|
||||
|
||||
sudo -u pi bash -c 'export PATH="$PATH:/home/pi/.local/bin"; pipx install rns'
|
||||
sudo -u pi bash -c 'export PATH="$PATH:/home/pi/.local/bin"; pipx install nomadnet'
|
||||
# Install freedvtnc2-lfm fork with command interface (port 8002)
|
||||
sudo -u pi bash -c 'export PATH="$PATH:/home/pi/.local/bin"; pipx install git+https://github.com/LFManifesto/freedvtnc2.git'
|
||||
sudo -u pi bash -c 'export PATH="$PATH:/home/pi/.local/bin"; pipx runpip freedvtnc2 install audioop-lts' || true
|
||||
|
||||
# Set permissions
|
||||
echo "[9/9] Setting permissions..."
|
||||
chown -R pi:pi /opt/reticulumhf
|
||||
chmod +x /opt/reticulumhf/scripts/*.sh
|
||||
|
||||
# Clean up
|
||||
apt-get clean
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
echo ""
|
||||
echo "============================================"
|
||||
echo "Chroot installation complete!"
|
||||
echo "============================================"
|
||||
CHROOT_SCRIPT
|
||||
|
||||
sudo chmod +x "$MOUNT_DIR/tmp/install.sh"
|
||||
sudo chroot "$MOUNT_DIR" /tmp/install.sh
|
||||
|
||||
sudo rm -f "$MOUNT_DIR/tmp/install.sh"
|
||||
sudo rm -f "$MOUNT_DIR/usr/bin/qemu-aarch64-static"
|
||||
|
||||
echo "[9/10] Restoring ld.so.preload and unmounting..."
|
||||
|
||||
if [ -f "$MOUNT_DIR/etc/ld.so.preload.bak" ]; then
|
||||
sudo mv "$MOUNT_DIR/etc/ld.so.preload.bak" "$MOUNT_DIR/etc/ld.so.preload"
|
||||
fi
|
||||
|
||||
sudo umount "$MOUNT_DIR/dev/pts" 2>/dev/null || true
|
||||
sudo umount "$MOUNT_DIR/dev" 2>/dev/null || true
|
||||
sudo umount "$MOUNT_DIR/proc" 2>/dev/null || true
|
||||
sudo umount "$MOUNT_DIR/sys" 2>/dev/null || true
|
||||
sudo umount "$MOUNT_DIR/boot/firmware" 2>/dev/null || true
|
||||
sudo umount "$MOUNT_DIR" 2>/dev/null || true
|
||||
sudo losetup -d "$LOOP_DEV" 2>/dev/null || true
|
||||
LOOP_DEV=""
|
||||
|
||||
echo "[10/10] Compressing image..."
|
||||
mv "$BUILD_DIR/$OUTPUT_IMAGE" "$OUTPUT_DIR/"
|
||||
cd "$OUTPUT_DIR"
|
||||
xz -T0 "$OUTPUT_IMAGE"
|
||||
|
||||
echo ""
|
||||
echo "============================================"
|
||||
echo "Build complete!"
|
||||
echo "============================================"
|
||||
echo "Output: $OUTPUT_DIR/${OUTPUT_IMAGE}.xz"
|
||||
echo ""
|
||||
echo "Image contains:"
|
||||
echo " - Hostname: reticulumhf"
|
||||
echo " - User: pi / Password: reticulumhf"
|
||||
echo " - SSH: enabled"
|
||||
echo " - WiFi AP: ReticulumHF (hostapd + dnsmasq)"
|
||||
echo " - codec2, RNS, NomadNet, freedvtnc2: pre-installed"
|
||||
echo ""
|
||||
echo "Flash to SD card with Raspberry Pi Imager"
|
||||
echo ""
|
||||
16
scripts/wait-for-port
Normal file
16
scripts/wait-for-port
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
#!/bin/bash
|
||||
# Wait for a TCP port to be listening
|
||||
# Usage: wait-for-port [port] [timeout_seconds]
|
||||
|
||||
PORT=${1:-8001}
|
||||
TIMEOUT=${2:-30}
|
||||
|
||||
for i in $(seq 1 $TIMEOUT); do
|
||||
if python3 -c "import socket; s=socket.socket(); s.settimeout(1); exit(0 if s.connect_ex(('127.0.0.1', $PORT))==0 else 1)" 2>/dev/null; then
|
||||
exit 0
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
|
||||
echo "Port $PORT not ready after ${TIMEOUT}s" >&2
|
||||
exit 1
|
||||
Loading…
Add table
Add a link
Reference in a new issue