Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -139,4 +139,4 @@ clean:
rm -f AIS-catcher

install:
cp AIS-catcher /usr/local/bin/AIS-catcher
cp AIS-catcher /usr/bin/AIS-catcher
120 changes: 104 additions & 16 deletions scripts/aiscatcher-install
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ Options:
-p, --package Install from pre-built package (faster, recommended)
-i, --interactive Source build with interactive feature selection (forces source path)
-b, --branch Specify git branch to clone (default: main)
-L, --legacy Use older source code dated by a given commit (the argument must be followed by a commit ID to determine the timestamp)
-M, --managed Run the service in managed mode (-E ${MANAGED_CONFIG_FILE} ${MANAGED_BIND})
--no-systemd Skip systemd service installation (useful for containers)
--no-user Skip user creation and run as root (useful for containers)
Expand Down Expand Up @@ -111,7 +112,6 @@ INSTALL_PACKAGE=false
INTERACTIVE=false
SKIP_SYSTEMD=false
SKIP_USER=false
MANAGED=false
BRANCH="main"
SERVICE_STATE=""
SERVICE_START_FAILED=false
Expand All @@ -123,6 +123,14 @@ INSTALL_REQUESTED=false
TMP_DIR=""
DEB_TMP_DIR=""

# User-facing installation option toggles.
OPTION_MANAGED=OFF
OPTION_LEGACY=OFF
COMMIT_ID=""

# Options that may be toggled in the interactive feature selection (does not affect CMakelists.txt).
readonly OPTIONS=(MANAGED LEGACY)

# User-facing CMake feature toggles (defaults mirror CMakeLists.txt option() lines).
# Infrastructure libs (SOXR, SAMPLERATE, ZLIB, OPENSSL) and link-style toggles
# (RTLSDR_STATIC, HYDRASDR_STATIC, ARMV6, RBIT) are intentionally not exposed
Expand All @@ -140,6 +148,7 @@ FEATURE_PSQL=ON
FEATURE_SQLITE=ON
FEATURE_WEBVIEWER=ON

# Features that may be added to CMakelists.txt
readonly FEATURES=(RTLSDR AIRSPY AIRSPYHF HACKRF HYDRASDR SDRPLAY SOAPYSDR ZMQ NMEA2000 PSQL SQLITE WEBVIEWER)

# Color constants (using ANSI escape codes)
Expand Down Expand Up @@ -236,8 +245,27 @@ prompt_yes_no() {
done
}

# Prompt for an answer specifying the commit ID with a default; echoes COMMIT_ID.
# Args: $1 label, $2 default (if COMMIT_ID was given as an argument)
prompt_commit_id() {
local label="$1" default="$2"
local suffix
suffix="["$default"]"

while true; do
local answer
read -r -p "$(printf "${COLOR_BOLD}%s${COLOR_RESET} %s: " "$label" "$suffix")" answer
# shellcheck disable=SC2155 # locale lowercasing needs ${var,,}
local lower="${answer,,}"
case "${lower:-${default,,}}" in
"") echo "Commit ID must not be empty." >&2 ;;
*) echo "${lower:-${default,,}}"; return 0 ;;
esac
done
}

# Walk the user through per-feature toggles.
# Sets the FEATURE_* globals from current defaults, and aborts if the user
# Sets the FEATURE_* and OPTIONS_* globals from current defaults, and aborts if the user
# rejects the summary. Requires a TTY.
interactive_feature_select() {
[[ -t 0 ]] || error_exit "Interactive mode requires a TTY (stdin is not a terminal)"
Expand All @@ -264,16 +292,41 @@ interactive_feature_select() {
FEATURE_SQLITE=$(prompt_yes_no " SQLite (offline map tiles and -D output)" "$FEATURE_SQLITE")

echo
echo -e "${COLOR_BOLD}Other${COLOR_RESET}"
echo -e "${COLOR_BOLD}User interfaces${COLOR_RESET}"
FEATURE_WEBVIEWER=$(prompt_yes_no " Embedded web UI (-N)" "$FEATURE_WEBVIEWER")
OPTION_MANAGED=$(prompt_yes_no " Managed Mode" "$OPTION_MANAGED")

echo
echo -e "${COLOR_BOLD}Source-code version${COLOR_RESET}"
OPTION_LEGACY=$(prompt_yes_no " Build AIS-catcher from legacy source-code" "$OPTION_LEGACY")
if [[ "${OPTION_LEGACY}" == ON ]]; then
COMMIT_ID=$(prompt_commit_id " Commit ID (SHA from a github commit)" "$COMMIT_ID")
fi

echo
# MANAGED must override WEBVIEWER if the necessary feature is disabled.
if [[ "${OPTION_MANAGED}" == ON && "${FEATURE_WEBVIEWER}" != ON ]]; then
FEATURE_WEBVIEWER=ON
log "INFO" "Managed Mode requires Embedded web UI. The feature WEBVIEWER was enabled automatically."
fi


echo -e "${COLOR_BOLD_BLUE}Selected features:${COLOR_RESET}"
local f vname
for f in "${FEATURES[@]}"; do
vname="FEATURE_$f"
printf " %-12s %s\n" "$f" "${!vname}"
done

for f in "${OPTIONS[@]}"; do
vname="OPTION_$f"
printf " %-12s %s\n" "$f" "${!vname}"
done

if [[ "${OPTION_LEGACY}" == ON ]]; then
printf " %-12s %s\n" "COMMIT ID:" "${COMMIT_ID}"
fi

echo
local confirm
read -r -p "Proceed with these settings? [Y/n]: " confirm
Expand Down Expand Up @@ -567,8 +620,18 @@ install_sdr_libraries_from_source() {
build_ais_catcher_from_source() {
log "INFO" "Building AIS-catcher from branch: ${BRANCH}"

git clone https://github.com/jvde-github/AIS-catcher.git --depth 1 --branch "${BRANCH}" || error_exit "Failed to download AIS-catcher (branch: ${BRANCH})"
cd AIS-catcher
if [[ ${OPTION_LEGACY} == ON ]]; then
# Selects the source code version detemined by the time of a choosen commit.
log "INFO" "Cloning AIS-catcher repository dated at commit ID: ${COMMIT_ID}"
git clone https://github.com/jvde-github/AIS-catcher.git --branch "${BRANCH}" || error_exit "Failed to download AIS-catcher (branch: ${BRANCH})"
cd AIS-catcher
git checkout "${COMMIT_ID}" || error_exit "Failed to set git version. The given commit ID ("${COMMIT_ID}") was not found."
else
# Defaults to clone the latest commit from "EDGE" realese.
log "INFO" "Cloning AIS-catcher reposertory of the latest realese ("EDGE")"
git clone https://github.com/jvde-github/AIS-catcher.git --depth 1 --branch "${BRANCH}" || error_exit "Failed to download AIS-catcher (branch: ${BRANCH})"
cd AIS-catcher
fi

# Set environment variables to find custom libraries
export PKG_CONFIG_PATH="${LIB_DIR}/lib/pkgconfig:${PKG_CONFIG_PATH:-}"
Expand Down Expand Up @@ -602,7 +665,11 @@ build_ais_catcher_from_source() {
make -j"$MAKE_JOBS" || error_exit "Failed to build AIS-catcher"

cd ../..

delete_previous_binary

install -m 755 AIS-catcher/build/AIS-catcher /usr/bin/AIS-catcher || error_exit "Failed to install AIS-catcher executable"

}

# Install additional files (plugins, DBMS, README, LICENSE) from source
Expand Down Expand Up @@ -802,7 +869,7 @@ setup_aiscatcher_user() {
setup_configuration() {
log "INFO" "Setting up configuration files"
mkdir -p "$CONFIG_DIR" || error_exit "Failed to create config directory"
if [[ "$MANAGED" == true ]]; then
if [[ "$OPTION_MANAGED" == ON ]]; then
mkdir -p "$CONFIG_DIR/places" || error_exit "Failed to create places directory"
chmod 750 "$CONFIG_DIR/places"
fi
Expand All @@ -811,7 +878,7 @@ setup_configuration() {
# managed config with its path so the setup wizard can offer to import
# the configured output channels. Must run before the legacy default
# config is (re)created below, so a fresh install does not qualify.
if [[ "$MANAGED" == true && ! -f "$MANAGED_CONFIG_FILE" && -f "$CONFIG_FILE" ]]; then
if [[ "$OPTION_MANAGED" == ON && ! -f "$MANAGED_CONFIG_FILE" && -f "$CONFIG_FILE" ]]; then
log "INFO" "Existing config.json found; the setup wizard will offer to import its outputs"
cat << EOF | tee "$MANAGED_CONFIG_FILE" > /dev/null || error_exit "Failed to create aiscatcher.json"
{
Expand Down Expand Up @@ -945,7 +1012,7 @@ SupplementaryGroups=plugdev dialout"
fi

local exec_start="/usr/bin/AIS-catcher -G level debug -o 0 -C ${CONFIG_FILE} @${CMD_FILE}"
if [ "$MANAGED" = true ]; then
if [ "$OPTION_MANAGED" = ON ]; then
exec_start="/usr/bin/AIS-catcher -E ${MANAGED_CONFIG_FILE} ${MANAGED_BIND}"
fi

Expand Down Expand Up @@ -1021,7 +1088,7 @@ verify_service_active() {
log "ERROR" "$SERVICE_NAME did not stay running after start"
systemctl status "$SERVICE_NAME" --no-pager -l 2>/dev/null | tail -n 15 || true
journalctl -u "$SERVICE_NAME" -n 20 --no-pager 2>/dev/null || true
if [[ "$MANAGED" == true ]]; then
if [[ "$OPTION_MANAGED" == ON ]]; then
local ctrl_port="${MANAGED_BIND##*:}"
log "ERROR" "Managed mode could not start — control port ${ctrl_port} or viewer port $((ctrl_port + 1)) may already be in use"
log "ERROR" "Free the port(s), then run: systemctl restart ${SERVICE_NAME}"
Expand Down Expand Up @@ -1060,7 +1127,7 @@ restore_service_state() {
log "INFO" "$SERVICE_NAME start command issued"
# Only verify the managed default-start (the script's own decision);
# a plain restore of the user's prior state is taken at face value.
if [[ "$MANAGED" == true ]]; then
if [[ "$OPTION_MANAGED" == ON ]]; then
verify_service_active || SERVICE_START_FAILED=true
fi
else
Expand Down Expand Up @@ -1105,9 +1172,20 @@ parse_arguments() {
error_exit "Option -b/--branch requires a branch name argument"
fi
;;
-L|--legacy)
if [[ -n "${2:-}" && "${2:0:1}" != "-" ]]; then
OPTION_LEGACY=ON
COMMIT_ID="$2"
INSTALL_REQUESTED=true
log "INFO" "Legacy installation requested, building with source code by the time of commit: ${COMMIT_ID}"
shift 2
else
error_exit "Option -L/--legacy requires a commit ID argument to select a version (-L must be followed by the SHA/hash identifier for a chosen commit)"
fi
;;
-M|--managed)
log "INFO" "Managed mode service requested (-E ${MANAGED_CONFIG_FILE} ${MANAGED_BIND})"
MANAGED=true
OPTION_MANAGED=ON
INSTALL_REQUESTED=true
shift
;;
Expand Down Expand Up @@ -1165,14 +1243,24 @@ parse_arguments() {
log "WARN" "--branch is ignored when --package is used (pre-built packages are always Edge)"
fi

# Warn if --legacy is ignored because --package was also specified
if [[ "$INSTALL_PACKAGE" == true && "$OPTION_LEGACY" != OFF ]]; then
log "WARN" "--legacy is ignored when --package is specified (Legacy installation of a given commit ID requires building from source)"
fi

# --interactive only makes sense for source builds; the .deb is pre-baked.
if [[ "$INTERACTIVE" == true && "$INSTALL_PACKAGE" == true ]]; then
log "WARN" "--interactive overrides --package (pre-built packages can't be customised)"
INSTALL_PACKAGE=false
fi
}


delete_previous_binary() {
# If a package installation has been present, it should be removed after building from source.
# This ensures compatiblity when switching between pre-packaged installation and source builds. Otherwise apt install may be confused why the ais-catcher package exist but has been replaced by an installation which is built from source (-which may be either a newer or older version).
log "INFO" "A pre-built AIS-catcher package is no longer used and will be removed (if present)"
apt remove -y ais-catcher || error_exit "Failed to remove preexisting AIS-catcher package"
}

# Install from source
install_from_source_method() {
Expand Down Expand Up @@ -1235,7 +1323,7 @@ display_installation_summary() {
if [ "$SERVICE_START_FAILED" = true ]; then
echo ""
echo -e " ${COLOR_YELLOW}⚠ The service was started but did not stay running.${COLOR_RESET}"
if [ "$MANAGED" = true ]; then
if [ "$OPTION_MANAGED" = ON ]; then
local status_port="${MANAGED_BIND##*:}"
echo -e " Control port ${status_port} or viewer port $((status_port + 1)) may already be in use."
fi
Expand Down Expand Up @@ -1311,7 +1399,7 @@ display_installation_summary() {
fi

echo -e "${COLOR_BOLD}CONFIGURATION:${COLOR_RESET}"
if [ "$MANAGED" = true ]; then
if [ "$OPTION_MANAGED" = ON ]; then
echo " • Managed mode: configure everything from the hub, not by hand"
echo -e " config file: ${COLOR_YELLOW}${MANAGED_CONFIG_FILE}${COLOR_RESET}"
echo ""
Expand All @@ -1329,7 +1417,7 @@ display_installation_summary() {
echo ""

echo -e "${COLOR_BOLD}WEB INTERFACE:${COLOR_RESET}"
if [ "$MANAGED" = true ]; then
if [ "$OPTION_MANAGED" = ON ]; then
local ctrl_port="${MANAGED_BIND##*:}"
echo " • Control hub (configuration and control):"
echo -e " ${COLOR_CYAN}http://localhost:${ctrl_port}${COLOR_RESET}"
Expand Down Expand Up @@ -1427,7 +1515,7 @@ main() {
setup_systemd_service

# Managed installs (-M) are enabled and started by default, regardless of prior state.
if [ "$MANAGED" = true ]; then
if [ "$OPTION_MANAGED" = ON ]; then
log "INFO" "Managed mode: enabling and starting ${SERVICE_NAME} by default"
SERVICE_STATE="true,true"
fi
Expand Down