From e91e305950e0402da8fff28e360a605ebb2fc649 Mon Sep 17 00:00:00 2001
From: Luc Busquin <133058544+Cybis320@users.noreply.github.com>
Date: Tue, 9 Jun 2026 22:48:32 -0700
Subject: [PATCH 01/17] Offer conversion to MultiCam data structure during
RMS_FirstRun
RMS_FirstRun now offers a double-confirmed step 5 that converts new
installs to the Stations data structure by chaining to add_Pi_Station.sh.
add_Pi_Station.sh changes:
- Support single-camera platforms (RPi4 and older) with MaxStation=1
instead of refusing to run; skip multi-camera-only tweaks (wayfire
cascade, reboot disable, extra_space scaling) on those systems
- Migrate camera_settings.json to the station folder and point
camera_settings_path at it
- Move all existing captured data (including hidden files) with find
instead of a hardcoded list, avoiding the mv-into-itself error
- Restore pristine defaults via git checkout with a wget fallback so an
offline run cannot delete .config without a replacement
- Add an Edit_configs desktop helper for editing station configs
RMS_StartCapture_MCP.sh starts a single-camera system with a 2 s delay
instead of 70 s; the long delay is only needed to serialize first-start
updates across multiple stations.
Autorun is now enabled only after the optional conversion finishes so an
interrupted conversion is not skipped over by the autorun path on the
next boot.
---
.../MultiCamLinux/Pi/RMS_StartCapture_MCP.sh | 10 +-
Scripts/MultiCamLinux/Pi/add_Pi_Station.sh | 181 ++++++++++++------
Scripts/RMS_FirstRun.sh | 70 ++++++-
3 files changed, 199 insertions(+), 62 deletions(-)
diff --git a/Scripts/MultiCamLinux/Pi/RMS_StartCapture_MCP.sh b/Scripts/MultiCamLinux/Pi/RMS_StartCapture_MCP.sh
index 1911bbb25..1afc9fbca 100755
--- a/Scripts/MultiCamLinux/Pi/RMS_StartCapture_MCP.sh
+++ b/Scripts/MultiCamLinux/Pi/RMS_StartCapture_MCP.sh
@@ -1,6 +1,14 @@
#!/bin/bash
-seconds=70
+# On a single camera system there is no risk of concurrent first-start updates,
+# so the long delay is only needed when more than one station is configured
+dircount=$(find ~/source/Stations -mindepth 1 -maxdepth 1 -type d 2>/dev/null | wc -l)
+if [[ $dircount -le 1 ]]; then
+ seconds=2
+else
+ seconds=70
+fi
+
echo " Starting all configured stations post-update..."
loop=0
diff --git a/Scripts/MultiCamLinux/Pi/add_Pi_Station.sh b/Scripts/MultiCamLinux/Pi/add_Pi_Station.sh
index 40eeb88fd..1cb8a3df7 100755
--- a/Scripts/MultiCamLinux/Pi/add_Pi_Station.sh
+++ b/Scripts/MultiCamLinux/Pi/add_Pi_Station.sh
@@ -1,20 +1,24 @@
#!/bin/bash
# This software is part of the Linux port of RMS
# Copyright (C) 2023 Ed Harman
-#
+#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
-#
+#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
-#
+#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
+# Version 1.9 - support single camera platforms (RPi4 and older) so RMS_FirstRun
+# can offer migration to the Stations data structure on all systems,
+# also migrate camera_settings.json and all captured data
+#
# Version 1.8 - replaced Desktop link with one that starts all captures
#
# Version 1.7 - changed desktop launcher display names and added conkyrc1 mod
@@ -45,11 +49,21 @@ then
Model=$(lscpu| grep -o 'Cortex.*'|grep -o '[0-9]*')
if [[ $Model -lt 76 ]] # RPi5 uses Cortex A76 - use 72 for script testing on an RPi4
then
- echo "This platform will not support multiple cameras, an RPi5 or similar is required"
- exit
+ echo "This platform only supports 1 camera, an RPi5 or similar is required for multiple cameras"
+ MaxStation=1
+ # quit if a camera is already configured under ~/source/Stations
+ if [[ -d ~/source/Stations ]]
+ then
+ dircount=$(find ~/source/Stations -mindepth 1 -maxdepth 1 -type d | wc -l)
+ if [[ $dircount -gt 0 ]]
+ then
+ read -n1 -r -p 'You are already running a camera on this platform, press ENTER to quit now' key
+ exit
+ fi
+ fi
fi
- DefStation=$(awk ' /stationID:/ {print toupper($2)}' ~/source/RMS/.config) # force uppercase
+ DefStation=$(awk ' /stationID:/ {print toupper($2)}' ~/source/RMS/.config) # force uppercase
if [[ $DefStation == XX0001 && ! -d ~/source/Stations/ ]]
then
echo "Please run RMS_Firstrun and configure your 1st station"
@@ -61,28 +75,26 @@ then
cat <
-
-It appears you have already configured your first station - id ${DefStation}
-so its config files will now be relocated to -
+You have already configured your first station as id ${DefStation}
+Its camera specific files will now be relocated to
~/source/Stations/${DefStation}
-Captured data from your default station are stored by default in
+Any previously captured data will be moved from
~/RMS_data
-
-This data will be moved to -
+to
~/RMS_data/${DefStation}
EOF
- # First station has been configured, so move it to ~/source/Stations/,
- # and move any captured data to ~/RMS_data/.
+ # First station has been configured, so move it to ~/source/Stations/,
+ # and move any captured data to ~/RMS_data/.
# tweak conky station title
sed -i 's/\(source\)\/RMS/\1\/Stations\/'"$DefStation"'/g' /home/rms/.conkyrc1
@@ -93,7 +105,7 @@ EOF
mkdir ~/source/Stations/${DefStation}
cp ~/source/RMS/.config ~/source/Stations/${DefStation}
cd ~/source/RMS
- sed -i "s,data_dir.*$,data_dir: ~/RMS_data/${DefStation},g" ~/source/Stations/${DefStation}/.config
+ sed -i "s,^data_dir.*$,data_dir: ~/RMS_data/${DefStation},g" ~/source/Stations/${DefStation}/.config
if [[ -e ~/source/RMS/platepar_cmn2010.cal ]]
then
mv ~/source/RMS/platepar_cmn2010.cal ~/source/Stations/${DefStation}/
@@ -101,15 +113,26 @@ EOF
if [[ -e ~/source/RMS/mask.bmp ]]
then
mv ~/source/RMS/mask.bmp ~/source/Stations/${DefStation}/
- #replace with copy from master
- cd ~/source/RMS
- wget -q https://raw.githubusercontent.com/CroatianMeteorNetwork/RMS/master/mask.bmp
fi
- mkdir ~/RMS_data/${DefStation}
- cd ~/RMS_data/${DefStation}
- mv ../ArchivedFiles .
- mv ../CapturedFiles .
- mv ../logs .
+ if [[ -e ~/source/RMS/camera_settings.json ]]
+ then
+ mv ~/source/RMS/camera_settings.json ~/source/Stations/${DefStation}/
+ # point the station .config at the relocated camera settings file
+ sed -i "s,^camera_settings_path:.*$,camera_settings_path: ~/source/Stations/${DefStation}/camera_settings.json,g" ~/source/Stations/${DefStation}/.config
+ fi
+
+ # restore pristine default copies in ~/source/RMS, these are used as
+ # templates when additional stations are added
+ cd ~/source/RMS
+ for file in mask.bmp camera_settings.json
+ do
+ git checkout -- "$file" 2>/dev/null || \
+ wget -q -O "$file" "https://raw.githubusercontent.com/CroatianMeteorNetwork/RMS/master/$file"
+ done
+
+ # move any existing captured data into the station folder
+ mkdir -p ~/RMS_data/${DefStation}
+ find ~/RMS_data -mindepth 1 -maxdepth 1 ! -name "${DefStation}" -exec mv -t ~/RMS_data/${DefStation}/ {} +
cat <<- EOF > ~/Desktop/${DefStation}_StartCapture.desktop
[Desktop Entry]
@@ -139,21 +162,25 @@ EOF
Desktop=`xdg-user-dir DESKTOP`
fi
- # cleanup erroneous Desktop shortcuts
+ # cleanup unneeded Desktop shortcuts
if [[ -f ~/Desktop/RMS_FirstRun.sh ]]
then
- rm ~/Desktop/CMNbinViewer.sh ~/Desktop/RMS_ShowLiveStream.sh ~/Desktop/RMS_StartCapture.sh
- rm ~/Desktop/RMS_config.txt ~/Desktop/TunnelIPCamera.sh ~/Desktop/DownloadOpenVPNconfig.sh
+ rm -f ~/Desktop/CMNbinViewer.sh ~/Desktop/RMS_ShowLiveStream.sh
+ rm -f ~/Desktop/RMS_StartCapture.sh ~/Desktop/RMS_config.txt
+ rm -f ~/Desktop/TunnelIPCamera.sh ~/Desktop/DownloadOpenVPNconfig.sh
+ fi
+
+ if [[ $MaxStation != 1 ]]
+ then
+ # remove comment from last line of wayfire.ini to enable window cascade
+ sed -i s/#mode/mode/ ~/.config/wayfire.ini
fi
-
- # remove comment from last line of wayfire.ini to enable window cascade
- sed -i s/#mode/mode/ ~/.config/wayfire.ini
fi # if [[ ! -d ~/source/Stations/ ]]
fi # if [[ $(uname -m ) == aarch64 ]]
-# need to count the number of directories under ~/source/Stations and only
+# need to count the number of directories under ~/source/Stations and only
# prompt for adding the correct number of new cameras, so subtract that number
# from MaxStation defined above
@@ -168,7 +195,7 @@ do
if [[ ${#Station[@]} == $Remaining ]]
then
echo ""
- echo "Done, This platform has a maximum of $numdirs cameras"
+ echo "Done, this platform supports a maximum of $MaxStation camera(s)"
break
fi
read -p "Enter station ID, to end: " this_Station
@@ -181,13 +208,16 @@ do
# echo $Station
done
-echo -e "\nNew stations to add -"
-printf '%s\n' "${Station[@]}"
+if [[ $MaxStation != 1 ]]
+then
+ echo -e "\nNew stations to add -"
+ printf '%s\n' "${Station[@]}"
+fi
-# check if ~/${RMS_data} exists and if not create it....
-if [[ ! -d "${RMS_data}/" ]]
+# check if ~/RMS_data exists and if not create it....
+if [[ ! -d ~/RMS_data ]]
then
- mkdir ${RMS_data}
+ mkdir ~/RMS_data
fi
No_Stations=${#Station[@]}
@@ -199,8 +229,8 @@ do
exit
else
echo "making dir Stations/${item}"
- mkdir ~/source/Stations/${item}
- if [[ ! -d ${RMS_data}/$item ]]
+ mkdir ~/source/Stations/${item}
+ if [[ ! -d ~/RMS_data/${item} ]]
then
mkdir ~/RMS_data/${item}
fi
@@ -239,12 +269,15 @@ do
# customise each .config
# set the station_id
- sed -i "s/D:.*$/D: $item/g" ~/source/Stations/${item}/.config
- # set the ${RMS_data} dir
- sed -i "s,data_dir.*$,data_dir: ~/RMS_data/${item},g" ~/source/Stations/${item}/.config
+ sed -i "s/D:.*$/D: $item/g" ~/source/Stations/${item}/.config
+ # set the data_dir
+ sed -i "s,^data_dir.*$,data_dir: ~/RMS_data/${item},g" ~/source/Stations/${item}/.config
echo -e "\n\nAdded station $item\n\n"
- # disable daily post processing reboot
- sed -i "s/\(reboot_after_processing:\).*/\1 false/g" ~/source/Stations/${item}/.config
+ if [[ $MaxStation != 1 ]]
+ then
+ # disable daily post processing reboot
+ sed -i "s/\(reboot_after_processing:\).*/\1 false/g" ~/source/Stations/${item}/.config
+ fi
done
# check if keys are already present
@@ -268,20 +301,47 @@ then
fi
echo -e "\n\nStation configuration complete\n\n"
-user=`id -u -n`
# Set up shortcut for CMNbinViewer
cat <<- EOF > ~/Desktop/CMNbinViewer.desktop
[Desktop Entry]
Name=CMNbinViewer
Type=Application
-Exec=/home/${user}/source/RMS/Scripts/CMNbinViewer_env.sh
+Exec=${HOME}/source/RMS/Scripts/CMNbinViewer_env.sh
Hidden=false
NoDisplay=false
-Icon=/home/${user}/source/RMS/Scripts/MultiCamLinux/icon.png
+Icon=${HOME}/source/RMS/Scripts/MultiCamLinux/icon.png
EOF
chmod +x ~/Desktop/CMNbinViewer.desktop
+# Create Desktop shortcut for editing the station .config files
+cat << 'EOF' > ~/Desktop/Edit_configs
+#!/bin/bash
+
+echo ""
+echo "One by one, each config file under ~/source/Stations will open for editing..."
+read -n1 -r -p 'Press ENTER to proceed, any other key to quit: ' key
+if [[ "${key}" != "" ]]; then
+ exit
+fi
+
+for Dir in ~/source/Stations/*/
+do
+ Station=$(basename "${Dir}")
+ echo ""
+ echo "Editing .config file for ${Station}"
+ read -n1 -r -p 'Press ENTER to edit, any other key to skip: ' key
+ if [[ "${key}" = "" ]]; then
+ mousepad ~/source/Stations/${Station}/.config
+ fi
+done
+
+echo ""
+echo "Done editing .config files"
+sleep 2
+EOF
+chmod +x ~/Desktop/Edit_configs
+
# set the option to allow launching of desktop shortcuts
#sed -i s/quick_exec=0/quick_exec=1/ ~/.config/libfm/libfm.conf
@@ -290,18 +350,21 @@ then
sudo timedatectl set-timezone UTC
fi
-# set the extra_space for all configured stations -
-mult=$(ls -1 /home/${USER}/source/Stations/|wc -l)
-for Dir in /home/${USER}/source/Stations/*
- do
- sed -i "s/extra_space_gb: .*$/extra_space_gb: $(( ${mult} * 30 ))/g" ${Dir}/.config
-done
+if [[ $MaxStation != 1 ]]
+then
+ # set the extra_space for all configured stations -
+ mult=$(ls -1 /home/${USER}/source/Stations/|wc -l)
+ for Dir in /home/${USER}/source/Stations/*
+ do
+ sed -i "s/extra_space_gb: .*$/extra_space_gb: $(( ${mult} * 30 ))/g" ${Dir}/.config
+ done
+fi
# set up so RMS_FirstRun can autostart all captures
rm -f /home/${USER}/Desktop/RMS_StartCapture.sh
ln -s /home/${USER}/source/RMS/Scripts/MultiCamLinux/Pi/RMS_StartCapture_MCP.sh /home/${USER}/Desktop/RMS_StartCapture.sh
-# get a copy of the default .config
+# replace the potentially modified .config in ~/source/RMS with a pristine default
cd ~/source/RMS
-rm .config #delete potentially modified .config and replace with new..
-wget -q https://raw.githubusercontent.com/CroatianMeteorNetwork/RMS/master/.config
+git checkout -- .config 2>/dev/null || \
+ wget -q -O .config https://raw.githubusercontent.com/CroatianMeteorNetwork/RMS/master/.config
diff --git a/Scripts/RMS_FirstRun.sh b/Scripts/RMS_FirstRun.sh
index 7787ab169..56f076f71 100755
--- a/Scripts/RMS_FirstRun.sh
+++ b/Scripts/RMS_FirstRun.sh
@@ -117,7 +117,9 @@ echo " 1. Connect your Pi to the Internet. "
echo " 2. Change the default password for security reasons."
echo " 3. Generate a new SSH key."
echo " 4. Edit the RMS config file. "
-
+echo " 5. Convert to the multi-camera data structure (recommended)."
+echo ""
+echo "At the end of these steps the first data capture session will start."
echo ""
echo "
@@ -249,9 +251,73 @@ else
fi
done
-# Enable autorun
+# Offer the conversion to the multi-camera data structure. The conversion
+# script only supports 64-bit Pi platforms, so skip this step elsewhere.
+if [[ $(uname -m) == aarch64 ]]; then
+
+echo "
+5) Converting the data structure
+--------------------------------
+The new recommended data structure keeps important camera files in a safer
+place. Converting to this multi-camera data structure is recommended for
+both single and multiple camera systems.
+
+The important files (.config, mask.bmp, platepar_cmn2010.cal and
+camera_settings.json) will be moved to ~/source/Stations/,
+and captured data will be stored in ~/RMS_data/, where
+ is the unique station code you were given by GMN.
+"
+sleep 1
+
+# clear the input buffer
+while read -t 0.01; do :; done
+
+read -n1 -r -p 'Press ENTER to convert to the new data structure, or Q to stay with the legacy structure... ' key
+if [[ "$key" != "" ]]; then
+ echo ""
+ echo ""
+ echo "Are you sure you want to stay with the legacy structure?"
+ read -n1 -r -p 'Press Q again to confirm, or ENTER to convert to the new data structure... ' key
+ echo ""
+fi
+
+if [[ "$key" = "" ]]; then
+ echo ""
+ echo "Next, this script will run to convert to the recommended data structure:"
+ echo " ~/source/RMS/Scripts/MultiCamLinux/Pi/add_Pi_Station.sh"
+ echo ""
+ echo "Note: if you have a Pi5 or similar, you can run the same script later to add more cameras."
+ echo ""
+ sleep 2
+ read -n1 -r -p 'Press ENTER a second time to confirm the conversion, or Q to keep the legacy structure... ' key
+fi
+echo ""
+
+if [[ "$key" = "" ]]; then
+ echo ""
+ echo "Converting to the recommended data structure..."
+ echo ""
+ bash ~/source/RMS/Scripts/MultiCamLinux/Pi/add_Pi_Station.sh
+else
+ echo ""
+ echo "You have chosen to keep the legacy data structure."
+ echo "If you decide to convert in the future, run this command from a terminal:"
+ echo " ~/source/RMS/Scripts/MultiCamLinux/Pi/add_Pi_Station.sh"
+fi
+
+fi # if aarch64
+
+# Enable autorun. This is done only after the optional data structure
+# conversion so an interrupted conversion does not get skipped over by
+# the autorun path on the next boot.
echo "1" > $RMSAUTORUNFILE
+sleep 2
+echo ""
+echo "Configuration is done"
+echo ""
+read -n1 -r -p 'Press ENTER when you are ready to start camera data capture... '
+echo ""
# If the configuration was done, run recording
bash $RMSSTARTCAPTURE
From 3bfa5837f896f1d396638173f083bd5271064bca Mon Sep 17 00:00:00 2001
From: Luc Busquin <133058544+Cybis320@users.noreply.github.com>
Date: Tue, 9 Jun 2026 23:00:53 -0700
Subject: [PATCH 02/17] Support CSV-driven station creation in add_GStation.sh
Stations can now be created in bulk from a stations.csv file. The header
row names the columns: station_id (required), camera_ip (spliced into
the rtsp device URL), and any other .config key, which is applied
verbatim to the station config. Usage:
add_GStation.sh [RMS_data_path] [stations.csv]
With no arguments the script uses ~/source/RMS/stations.csv if present
(the file is gitignored), otherwise it falls back to the previous
interactive prompts, so the GRMSL_Install.sh flow keeps working.
Desktop and autostart entries now use gnome-terminal (an optional
StartCapture profile customises the windows) instead of installing and
configuring lxterminal.
Robustness fixes over the old version: config seds are anchored at line
start so commented examples are not rewritten, CSV fields are trimmed
without xargs (handles CRLF endings and quotes in values), a final CSV
row without a trailing newline is not dropped, and the CMNbinViewer
icon points at the RMS copy instead of a cmn_binviewer path that only
exists via an install-time symlink.
The script is also slimmed down to station creation: the GNOME
sleep/screen-lock prevention moved to RMS_Installer.sh as one-time
machine setup (guarded for headless installs), the duplicated UTC
timezone setting is dropped (RMS_Installer.sh already does it), and the
interactive optional installs (pcmanfm, mousepad, AnyDesk), cron-based
update scheduling, and AnyDesk Wayland workaround are removed.
---
.gitignore | 4 +-
Scripts/MultiCamLinux/add_GStation.sh | 501 ++++++++++++--------------
Scripts/RMS_Installer.sh | 8 +
3 files changed, 238 insertions(+), 275 deletions(-)
diff --git a/.gitignore b/.gitignore
index a20bf3960..a1710c8d2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -115,4 +115,6 @@ Catalogs/GMN_StarCatalog_LM12.0.bin
# Agents
.agents/
-skills-lock.json
\ No newline at end of file
+skills-lock.json
+# Station list for Scripts/MultiCamLinux/add_GStation.sh
+stations.csv
diff --git a/Scripts/MultiCamLinux/add_GStation.sh b/Scripts/MultiCamLinux/add_GStation.sh
index 4b26275a6..607b346e8 100755
--- a/Scripts/MultiCamLinux/add_GStation.sh
+++ b/Scripts/MultiCamLinux/add_GStation.sh
@@ -1,20 +1,24 @@
#!/bin/bash
# This software is part of the Linux port of RMS
# Copyright (C) 2023 Ed Harman
-#
+#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
-#
+#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
-#
+#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
+# Version 1.7 - stations can be created in bulk from a stations.csv file,
+# desktop entries use gnome-terminal instead of lxterminal,
+# interactive prompts remain the fallback when no CSV is given
+#
# Version 1.6 - added logic to prevent running this script on a Raspberry Pi
# added by Peter E., June 2024
#
@@ -30,311 +34,260 @@
# Version 1.1
# Changes - added station arguments to Launch scripts
# - changed desktop links for StartCapture to symbolic links of the scripts within .config/autostart
-#
-#
-# Prevent running this script on a Raspberry Pi, because
-# add_Pi_Station.sh should be used to add cameras on a Pi.
+#
+
+# Prevent running this script on a Raspberry Pi
file=/sys/firmware/devicetree/base/model
if [[ -f "$file" ]]; then
- echo ""
contents=$(tr -d '\0' < $file)
- echo "The file $file reads: $contents"
if [[ $contents == 'Raspberry'* ]]; then
- echo "The add_GStation.sh script should not be used on Raspberry Pi."
- echo "Please use add_Pi_Station.sh to add cameras on a Pi5."
- echo ""
- exit 1
+ echo "The add_GStation.sh script should not be used on Raspberry Pi."
+ echo "Please use add_Pi_Station.sh to add cameras on a Pi5."
+ exit 1
fi
fi
-# Check if the user's desktop directory environment variable is set
-if [ -n "$xdg-user-dir DESKTOP" ]; then
- Desktop=`xdg-user-dir DESKTOP`
-fi
-#
-if [[ "$#" -eq 0 ]] #called with no arg
-then
- RMS_data=~/RMS_data
- else
- RMS_data=${1}
-fi
-# cleanup erroneous Desktop shortcuts
-if [[ -f ~/Desktop/RMS_FirstRun.sh ]]
- then
- rm ~/Desktop/RMS_FirstRun.sh ~/Desktop/RMS_StartCapture.sh ~/Desktop/RMS_ShowLiveStream.sh ~/Desktop/TunnelIPCamera.sh ~/Desktop/CMNbinViewer.sh
-fi
+# Get user's desktop directory
+Desktop=$(xdg-user-dir DESKTOP 2>/dev/null || echo "$HOME/Desktop")
-# Install the Buster RMS default terminal - lxterminal
-if ! command -v lxterminal &> /dev/null
-then
- sudo apt-get install -y lxterminal &
- wait %1
- echo -e "\n\nlaunching lxterminal\n\n"
-# we need to launch lxterminal at least once for it to write its default preferences file
- echo -e "\n\nclosed lxterminal\n\n"
- # now lxterminal is installed and written it's config we can customise it
- sed -i "s/scrollback=.*/scrollback=10000/g" ~/.config/lxterminal/lxterminal.conf
- sed -i "s/geometry_columns=.*/geometry_columns=120/g" ~/.config/lxterminal/lxterminal.conf
- sed -i "s/geometry_rows=.*/geometry_rows=25/g" ~/.config/lxterminal/lxterminal.conf
- echo -e "\n\nlxterminal installed and preferences set\n\n"
-fi
-if [[ ! -d ~/source/Stations ]]
- then
- mkdir ~/source/Stations
-fi
-declare Station
-while :
-do
- read -p "Enter station ID, to end: " this_Station
- if [[ -z $this_Station ]]
- then
- break
- fi
- Station+=("$this_Station")
-# echo $Station
-done
+# Strip leading/trailing whitespace (including CR from Windows-edited CSVs)
+trim() {
+ local s="$*"
+ s="${s#"${s%%[![:space:]]*}"}"
+ s="${s%"${s##*[![:space:]]}"}"
+ printf '%s' "$s"
+}
-echo -e "\nNew stations to add -"
-printf '%s\n' "${Station[@]}"
+# Create a station directory with config, mask and desktop entries.
+# Returns 1 if the station already exists.
+create_station() {
+ local item="$1"
+ local config_file=~/source/Stations/${item}/.config
-# check if ~/${RMS_data} exists and if not create it....
-if [[ ! -d "${RMS_data}/" ]]
- then
- mkdir ${RMS_data}
-fi
+ if [[ -d ~/source/Stations/${item} ]]; then
+ echo -e "\nNot creating station ${item} - it already exists"
+ return 1
+ fi
-# check if user has an autostart dir in their .config directory and if not create it...
-if [[ ! -d ~/.config/autostart ]]
- then
- mkdir ~/.config/autostart
-fi
+ echo "Creating station ${item}..."
+ mkdir -p ~/source/Stations/${item}
+ mkdir -p "${RMS_data}/${item}"
+
+ # Copy config template
+ cp ~/source/RMS/.config ~/source/Stations/${item}
-No_Stations=${#Station[@]}
-for item in "${Station[@]}"
-do
- if [[ -d ~/source/Stations/${item} ]]
- then
- echo -e "\n\nNot creating station ${item} - it already exists\n"
- exit
- else
- echo "making dir Stations/${item}"
- mkdir ~/source/Stations/${item}
- if [[ ! -d ${RMS_data}/$item ]]
- then
- mkdir ${RMS_data}/${item}
- fi
-
- fi
- cp ~/source/RMS/.config ~/source/Stations/${item}
- # create autostart entry for the station
- cat <<- EOF > ~/.config/autostart/${item}_StartCap.desktop
+ # Create autostart .desktop entry using gnome-terminal.
+ # The StartCapture profile is optional - create one to customise the
+ # capture window; without it gnome-terminal uses the default profile.
+ cat <<- EOF > ~/.config/autostart/${item}_StartCap.desktop
[Desktop Entry]
- Name=${item}-Startcapture
+ Name=${item}_StartCap
Type=Application
- Exec=lxterminal --title=${item} -e "~/source/RMS/Scripts/MultiCamLinux/StartCapture.sh ${item}"
+ Exec=gnome-terminal --profile=StartCapture --title=${item} -- bash -c "~/source/RMS/Scripts/MultiCamLinux/StartCapture.sh ${item}"
Hidden=false
NoDisplay=false
- Icon=lxterminal
+ Icon=gnome-terminal
EOF
- # create Desktop softlink for StartCapture
- chmod +x ~/.config/autostart/${item}_StartCap.desktop
- ln -s ~/.config/autostart/${item}_StartCap.desktop ~/Desktop/${item}_StartCap.desktop
- chmod -x ~/Desktop/${item}_StartCap.desktop
- gio set ~/Desktop/${item}_StartCap.desktop metadata::trusted true
- chmod +x ~/Desktop/${item}_StartCap.desktop
- cp ~/source/RMS/mask.bmp ~/source/Stations/${item}
- # create a ShowLiveStream- ~/Desktop/Show_LiveStream-${item}.desktop
+
+ chmod +x ~/.config/autostart/${item}_StartCap.desktop
+
+ # Create Desktop symlink for StartCapture
+ ln -sf ~/.config/autostart/${item}_StartCap.desktop "${Desktop}/${item}_StartCap.desktop"
+ gio set "${Desktop}/${item}_StartCap.desktop" metadata::trusted true 2>/dev/null
+ chmod +x "${Desktop}/${item}_StartCap.desktop"
+
+ # Copy mask file
+ cp ~/source/RMS/mask.bmp ~/source/Stations/${item} 2>/dev/null
+
+ # Create ShowLiveStream desktop shortcut using gnome-terminal
+ cat <<- EOF > "${Desktop}/Show_LiveStream-${item}.desktop"
[Desktop Entry]
Name=${item}-ShowLiveStream
Type=Application
- Exec=lxterminal --title=Stream-${item} -e "~/source/RMS/Scripts/MultiCamLinux/LiveStream.sh ${item}"
+ Exec=gnome-terminal --profile=StartCapture --title=Stream-${item} -- bash -c "~/source/RMS/Scripts/MultiCamLinux/LiveStream.sh ${item}"
Hidden=false
NoDisplay=false
- Icon=lxterminal
+ Icon=gnome-terminal
EOF
- chmod -x ~/Desktop/Show_LiveStream-${item}.desktop
- gio set ~/Desktop/Show_LiveStream-${item}.desktop metadata::trusted true
- chmod +x ~/Desktop/Show_LiveStream-${item}.desktop
- #
- # customise each .config
- #
- # set the station_id
- sed -i "s/D:.*$/D: $item/g" ~/source/Stations/${item}/.config
- # set the ${RMS_data} dir
- sed -i "s,data_dir.*$,data_dir: ${RMS_data}/${item},g" ~/source/Stations/${item}/.config
- # update the free space in ratio of number of stations being added, using a 20Gb factor for now
- sed -i "s/extra_space_gb: .*$/extra_space_gb: $(( ${No_Stations} * 20 ))/g" ~/source/Stations/${item}/.config
- echo -e "\n\nAdded station $item\n\n"
- # disable daily post processing reboot
- sed -i "s/\(reboot_after_processing:\).*/\1 false/g" ~/source/Stations/${item}/.config
-done
-# check if keys are already present
-if [[ -f ~/.ssh/id_rsa ]]
- then
- if [ ! -z ${Configured} ]
- then
- echo "SSH keys already exist, not overwriting them"
- fi
- else
- ssh-keygen -t rsa -f ~/.ssh/id_rsa -q -P ""
- echo "SSH keys successfully generated in ~/.ssh"
-fi
-timedatectl set-timezone UTC
-gsettings set org.gnome.settings-daemon.plugins.power sleep-inactive-ac-type 'nothing' # disables inactivity lock
-gsettings set org.gnome.settings-daemon.plugins.power sleep-inactive-ac-timeout 0 # sleep inactivity 0=='never'
-gsettings set org.gnome.desktop.session idle-delay 600 # screen blank after xxx secs, adjust to taste 0=='never'
-gsettings set org.gnome.desktop.screensaver lock-enabled false # disable screensaver lock
-
-# only prompt for these optional installs if the user has run this for the 1st time
-
-# launch the vRMS environment at logon
-if ( ! grep -q RMS ~/.bashrc)
-then
- cat <>~/.bashrc
- # Activate RMS
- cd ~/source/RMS
- source ~/vRMS/bin/activate
-EOF
+
+ gio set "${Desktop}/Show_LiveStream-${item}.desktop" metadata::trusted true 2>/dev/null
+ chmod +x "${Desktop}/Show_LiveStream-${item}.desktop"
+
+ # Apply default config customizations
+ sed -i "s/^stationID:.*$/stationID: $item/g" "${config_file}"
+ sed -i "s,^data_dir:.*$,data_dir: ${RMS_data}/${item},g" "${config_file}"
+ sed -i "s/^extra_space_gb:.*$/extra_space_gb: $(( ${No_Stations} * 20 ))/g" "${config_file}"
+ sed -i "s/^\(reboot_after_processing:\).*/\1 false/g" "${config_file}"
+
+ return 0
+}
+
+# Parse arguments: [RMS_data_path] [stations.csv]
+# Smart detection: if single arg ends in .csv, treat as CSV file
+RMS_data=~/RMS_data
+CSV_FILE=""
+
+if [[ "$#" -eq 0 ]]; then
+ # No args: use the default CSV if one exists
+ if [[ -f ~/source/RMS/stations.csv ]]; then
+ CSV_FILE=~/source/RMS/stations.csv
+ fi
+elif [[ "$#" -eq 1 ]]; then
+ # Single arg: check if it's a CSV file or a directory
+ if [[ "${1}" == *.csv ]]; then
+ CSV_FILE="${1}"
+ else
+ RMS_data="${1}"
+ if [[ -f ~/source/RMS/stations.csv ]]; then
+ CSV_FILE=~/source/RMS/stations.csv
+ fi
+ fi
+else
+ # Two args: first is RMS_data, second is CSV
+ RMS_data="${1}"
+ CSV_FILE="${2}"
fi
-if ( ! command -v pcmanfm &> /dev/null )
-then
- echo -e "Install pcmanfm file manager ?\n"
- while true; do
- read -p "Y/N: " Ans
- case $Ans in
- [yY]* ) sudo apt install -y pcmanfm; break;;
- [nN] ) break ;;
- esac
- done
+
+# A CSV that was explicitly requested must exist
+if [[ -n "${CSV_FILE}" && ! -f "${CSV_FILE}" ]]; then
+ echo "Error: CSV file not found: ${CSV_FILE}"
+ echo "Usage: $0 [RMS_data_path] [stations.csv]"
+ exit 1
fi
-if (! command -v mousepad &> /dev/null )
-then
- echo -e "Install mousepad ?\n"
- while true; do
- read -p "Y/N: " Ans
- case $Ans in
- [yY]* ) sudo apt install -y mousepad; break;;
- [nN] ) break ;;
- esac
- done
+
+# Ensure required directories exist
+mkdir -p ~/source/Stations
+mkdir -p "${RMS_data}"
+mkdir -p ~/.config/autostart
+
+if [[ -n "${CSV_FILE}" ]]; then
+
+ echo "Reading stations from: ${CSV_FILE}"
+
+ # Read CSV header to get column names
+ IFS=',' read -r -a HEADERS < "${CSV_FILE}"
+
+ # Trim whitespace from headers
+ for i in "${!HEADERS[@]}"; do
+ HEADERS[$i]=$(trim "${HEADERS[$i]}")
+ done
+
+ # Find required column indices
+ station_id_col=-1
+ camera_ip_col=-1
+ for i in "${!HEADERS[@]}"; do
+ case "${HEADERS[$i]}" in
+ station_id|stationID) station_id_col=$i ;;
+ camera_ip) camera_ip_col=$i ;;
+ esac
+ done
+
+ if [[ $station_id_col -eq -1 ]]; then
+ echo "Error: CSV must have a 'station_id' or 'stationID' column"
+ exit 1
+ fi
+
+ # Count stations for extra_space_gb calculation
+ No_Stations=$(tail -n +2 "${CSV_FILE}" | grep -c -v '^[[:space:]]*$')
+
+ echo -e "\nStations to add from CSV: ${No_Stations}"
+
+ # Process each data row (the || keeps a final line without a trailing newline)
+ tail -n +2 "${CSV_FILE}" | while IFS=',' read -r -a VALUES || [[ ${#VALUES[@]} -gt 0 ]]; do
+ # Skip empty lines
+ [[ ${#VALUES[@]} -eq 0 ]] && continue
+
+ # Trim whitespace from values
+ for i in "${!VALUES[@]}"; do
+ VALUES[$i]=$(trim "${VALUES[$i]}")
+ done
+
+ item="${VALUES[$station_id_col]}"
+ [[ -z "$item" ]] && continue
+
+ create_station "$item" || continue
+
+ config_file=~/source/Stations/${item}/.config
+
+ # Apply camera_ip to device URL if column exists
+ if [[ $camera_ip_col -ne -1 && -n "${VALUES[$camera_ip_col]}" ]]; then
+ camera_ip="${VALUES[$camera_ip_col]}"
+ # Replace IP address in the device URL (matches IP pattern in rtsp:// URL)
+ sed -i -E "s|^(device:.*rtsp://)[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+|\1${camera_ip}|g" "${config_file}"
+ echo " Set camera IP: ${camera_ip}"
+ fi
+
+ # Apply any additional CSV columns that match .config keys
+ for i in "${!HEADERS[@]}"; do
+ header="${HEADERS[$i]}"
+ value="${VALUES[$i]}"
+
+ # Skip station_id, camera_ip (already handled), and empty values
+ [[ "$header" == "station_id" || "$header" == "stationID" || "$header" == "camera_ip" ]] && continue
+ [[ -z "$value" ]] && continue
+
+ # Check if this header exists as a key in .config
+ if grep -q "^${header}:" "${config_file}"; then
+ sed -i "s|^${header}:.*$|${header}: ${value}|g" "${config_file}"
+ echo " Set ${header}: ${value}"
+ fi
+ done
+
+ echo "Added station ${item}"
+ done
+
+else
+
+ # No CSV available - prompt for station IDs interactively
+ declare -a Station
+ while :
+ do
+ read -p "Enter station ID, to end: " this_Station
+ this_Station=$(trim "${this_Station^^}")
+ if [[ -z $this_Station ]]; then
+ break
+ fi
+ Station+=("$this_Station")
+ done
+
+ No_Stations=${#Station[@]}
+ echo -e "\nNew stations to add -"
+ printf '%s\n' "${Station[@]}"
+
+ for item in "${Station[@]}"; do
+ create_station "$item" && echo "Added station ${item}"
+ done
fi
-if ( ! command -v anydesk &> /dev/null )
-then
- echo -e "Install AnyDesk ?"
- echo -e "This will require sudo privileges \n"
- while true; do
- read -p "Y/N: " Ans
- case $Ans in
- [yY]* )
- wget -qO - https://keys.anydesk.com/repos/DEB-GPG-KEY | sudo apt-key add -
- echo "deb http://deb.anydesk.com/ all main" > anydesk-stable.list
- sudo mv anydesk-stable.list /etc/apt/sources.list.d/
- sudo apt update
- sudo apt install -y anydesk
- break;;
- [nN] ) break ;;
- esac
- done
+# Generate SSH keys if not present
+if [[ ! -f ~/.ssh/id_rsa ]]; then
+ ssh-keygen -t rsa -f ~/.ssh/id_rsa -q -P ""
+ echo "SSH keys generated in ~/.ssh"
fi
-echo -e "\n\nStation configuration complete\n\n"
-user=`id -u -n`
-
-# Optionaly create a crontab entry for the user to automate regular RMS updates I've chosen midday as a default but this can easily be changed
-echo -e "Do you wish to schedule a regular cron job to stop all running instances,"
-echo -e "perform an update of RMS, and finally restart all configured instances? "
-while true; do
- read -p "Y/N: " Ans
- case $Ans in
- [yY]* )
- echo " The following template entry will schedule the action to run @3pm every Sunday"
- echo " feel free to edit the line if you want to alter the timing and/or frequency, when you are happy with"
- echo "the schedule, just hit return"
- echo " If you want some examples of the required format check out https://crontab.guru/examples.html"
- echo "0 15 * * SUN /home/${user}/source/RMS/Scripts/MultiCamLinux/GRMSUpdate.sh" | read Schedule
- if sudo test -f "/var/spool/cron/crontabs/$user" # does user have an existing crontab?
- then
- if sudo grep -q RMSUpdater /var/spool/cron/crontabs/$user # check there isn't an existing entry
- then
- echo -e "\nNot modifying ${user}'s crontab -that user has an existing entry\n"
- else
- read -e -i "0 15 * * SUN" Schedule
- # can't edit users crontab in-place without messing with dir permissions...
- sudo mv /var/spool/cron/crontabs/$user $user.tmp
- set -o noglob
- sudo echo "$Schedule /home/$user/source/RMS/Scripts/MultiCamLinux/GRMSUpdater.sh" >> ${user}.tmp
- set +o noglob
- sudo mv ${user}.tmp /var/spool/cron/crontabs/${user}
- echo -e "\n User ${user}'s crontab has been updated, if you subsequently wish to edit it you can do so"
- echo -e "by issuing the following cmd in a terminal- crontab -e\n"
- fi
-else
- read -e -i "0 15 * * SUN" Schedule
- set -o noglob
- # user does not have a crontab so create one with the standard boilerplate stuff
- touch ${user}.tmp
- cat <<- EOF >${user}.tmp
- # Edit this file to introduce tasks to be run by cron.
- #
- # Each task to run has to be defined through a single line
- # indicating with different fields when the task will be run
- # and what command to run for the task
- #
- # To define the time you can provide concrete values for
- # minute (m), hour (h), day of month (dom), month (mon),
- # and day of week (dow) or use '*' in these fields (for 'any').
- #
- # Notice that tasks will be started based on the cron's system
- # daemon's notion of time and timezones.
- #
- # Output of the crontab jobs (including errors) is sent through
- # email to the user the crontab file belongs to (unless redirected).
- #
- # For example, you can run a backup of all your user accounts
- # at 5 a.m every week with:
- # 0 5 * * 1 tar -zcf /var/backups/home.tgz /home/
- #
- # For more information see the manual pages of crontab(5) and cron(8)
- #
- # m h dom mon dow command
- $Schedule /home/$user/source/RMS/Scripts/MultiCamLinux/GRMSUpdater.sh
- EOF
+# Add RMS activation to .bashrc if not present
+if ! grep -q "source ~/vRMS/bin/activate" ~/.bashrc; then
+ cat <> ~/.bashrc
+
+# Activate RMS
+cd ~/source/RMS
+source ~/vRMS/bin/activate
+EOF
+fi
- set -o noglob
- sudo mv ${user}.tmp /var/spool/cron/crontabs/${user}
- echo -e "\nA new crontab has been installed for ${user}, if you subsequently wish to change it in any way"
- echo -e "you can edit it in a terminal with the cmd - crontab -e\n"
- fi
- break ;;
- [nN]* ) break ;;
- esac
- done
-
-# Set up shortcut for CMNbinViewer
-cat <<- EOF > ~/Desktop/CMNbinViewer.desktop
+# Create CMNbinViewer desktop shortcut
+cat <<- EOF > "${Desktop}/CMNbinViewer.desktop"
[Desktop Entry]
Name=CMNbinViewer
Type=Application
-Exec=/home/${user}/source/RMS/Scripts/CMNbinViewer_env.sh
+Exec=${HOME}/source/RMS/Scripts/CMNbinViewer_env.sh
Hidden=false
NoDisplay=false
-Icon=/home/${user}/source/cmn_binviewer/icon.png
+Icon=${HOME}/source/RMS/Scripts/MultiCamLinux/icon.png
EOF
-# set the 'allow launching' feature
-chmod -x ~/Desktop/CMNbinViewer.desktop
-gio set ~/Desktop/CMNbinViewer.desktop metadata::trusted true
-chmod +x ~/Desktop/CMNbinViewer.desktop
-if grep -Fqv "UTC" /etc/timezone
-then
-sudo timedatectl set-timezone UTC
-fi
-# Fix AnyDesk issue with Wayland ... requires sudo
-if grep -q '#Way' /etc/gdm3/custom.conf
-then
- sudo sed -i 's/#\(WaylandEnable.*\)/\1/g' /etc/gdm3/custom.conf
-fi
-exit
+
+gio set "${Desktop}/CMNbinViewer.desktop" metadata::trusted true 2>/dev/null
+chmod +x "${Desktop}/CMNbinViewer.desktop"
+
+echo -e "\nStation configuration complete"
diff --git a/Scripts/RMS_Installer.sh b/Scripts/RMS_Installer.sh
index 4dde5a263..03cc8c12a 100755
--- a/Scripts/RMS_Installer.sh
+++ b/Scripts/RMS_Installer.sh
@@ -59,6 +59,14 @@ sudo systemctl enable chrony
sudo systemctl start ssh
sudo systemctl enable ssh
+# On GNOME desktops, prevent sleep and screen lock so captures are not interrupted
+if command -v gsettings >/dev/null 2>&1; then
+ gsettings set org.gnome.settings-daemon.plugins.power sleep-inactive-ac-type 'nothing' 2>/dev/null
+ gsettings set org.gnome.settings-daemon.plugins.power sleep-inactive-ac-timeout 0 2>/dev/null
+ gsettings set org.gnome.desktop.session idle-delay 600 2>/dev/null
+ gsettings set org.gnome.desktop.screensaver lock-enabled false 2>/dev/null
+fi
+
# Clone repositories
git clone https://github.com/CroatianMeteorNetwork/RMS.git
git clone https://github.com/CroatianMeteorNetwork/cmn_binviewer.git
From 345c12d622b1e61d8e58f023615e2c7896be3421 Mon Sep 17 00:00:00 2001
From: Luc Busquin <133058544+Cybis320@users.noreply.github.com>
Date: Wed, 10 Jun 2026 00:28:36 -0700
Subject: [PATCH 03/17] Unify station tooling into a universal add_Station.sh
add_Pi_Station.sh and add_GStation.sh duplicated the same station
creation logic with platform assumptions baked in, and the copies had
drifted (different extra_space factors, entry names, and bug fixes
applied to one but not the other). Both are now thin compatibility
wrappers around a single universal Scripts/MultiCamLinux/add_Station.sh
that works on any Linux platform, Raspberry Pi included:
- Platform detection reads /sys/firmware/devicetree/base/model instead
of uname/lscpu, so 32-bit Pi installs are detected and can migrate too
- The Pi camera limit (1, or 4 on a Pi5 or later) is now advisory: the
script warns that it is the tested ceiling and lets the operator
override it, instead of hard-stopping. Future more capable Pis and
experiments are not blocked. Generic Linux has no limit, as before
- The legacy single-camera relocation is universal, so a PC that was set
up as a single camera can convert just like a Pi. Pi image cosmetics
(conky, wayfire, image desktop shortcuts) only run when the files
exist
- Launch entries use whichever of lxterminal/gnome-terminal fits the
desktop. On FirstRun-managed systems (the Pi images) captures are
started by RMS_StartCapture_MCP.sh as before; elsewhere per-station
XDG autostart entries start captures at login
- CSV-driven bulk creation and the interactive prompts are available on
every platform
RMS_FirstRun.sh now offers the conversion on any platform that has not
converted yet (the raspi-config step is skipped where raspi-config does
not exist, and the config editor falls back to gedit/gnome-text-editor
on GNOME), and chains to add_Station.sh, which shows the relocation
details and asks the final confirmation. RMS_StartCapture_MCP.sh picks
the terminal emulator the same way.
---
.../MultiCamLinux/Pi/RMS_StartCapture_MCP.sh | 25 +-
Scripts/MultiCamLinux/Pi/add_Pi_Station.sh | 372 +------------
Scripts/MultiCamLinux/add_GStation.sh | 295 +---------
Scripts/MultiCamLinux/add_Station.sh | 521 ++++++++++++++++++
Scripts/RMS_FirstRun.sh | 58 +-
5 files changed, 575 insertions(+), 696 deletions(-)
create mode 100755 Scripts/MultiCamLinux/add_Station.sh
diff --git a/Scripts/MultiCamLinux/Pi/RMS_StartCapture_MCP.sh b/Scripts/MultiCamLinux/Pi/RMS_StartCapture_MCP.sh
index 1afc9fbca..d079de974 100755
--- a/Scripts/MultiCamLinux/Pi/RMS_StartCapture_MCP.sh
+++ b/Scripts/MultiCamLinux/Pi/RMS_StartCapture_MCP.sh
@@ -1,7 +1,9 @@
#!/bin/bash
-# On a single camera system there is no risk of concurrent first-start updates,
-# so the long delay is only needed when more than one station is configured
+# Start capture on all configured stations, staggered so concurrent
+# first-start updates cannot collide. On a single camera system there is
+# no such risk, so the long delay is skipped
+
dircount=$(find ~/source/Stations -mindepth 1 -maxdepth 1 -type d 2>/dev/null | wc -l)
if [[ $dircount -le 1 ]]; then
seconds=2
@@ -9,14 +11,27 @@ else
seconds=70
fi
+# Detect the terminal emulator
+if [[ "${XDG_CURRENT_DESKTOP:-}" == *GNOME* ]] && command -v gnome-terminal >/dev/null 2>&1; then
+ TERMINAL=gnome-terminal
+elif command -v lxterminal >/dev/null 2>&1; then
+ TERMINAL=lxterminal
+else
+ TERMINAL=gnome-terminal
+fi
+
echo " Starting all configured stations post-update..."
loop=0
-for Dir in ~/source/Stations/*
+for Dir in ~/source/Stations/*/
do
- Station=$(basename $Dir)
+ Station=$(basename "$Dir")
echo " Starting camera ${Station}"
- lxterminal --title=${Station} -e "$HOME/source/RMS/Scripts/MultiCamLinux/StartCapture.sh ${Station}" &
+ if [[ "$TERMINAL" == "gnome-terminal" ]]; then
+ gnome-terminal --profile=StartCapture --title=${Station} -- bash -c "$HOME/source/RMS/Scripts/MultiCamLinux/StartCapture.sh ${Station}" &
+ else
+ lxterminal --title=${Station} -e "$HOME/source/RMS/Scripts/MultiCamLinux/StartCapture.sh ${Station}" &
+ fi
echo " waiting $seconds seconds..."
sleep ${seconds}
if [[ $loop = 0 ]] ; then
diff --git a/Scripts/MultiCamLinux/Pi/add_Pi_Station.sh b/Scripts/MultiCamLinux/Pi/add_Pi_Station.sh
index 1cb8a3df7..9e2e7180c 100755
--- a/Scripts/MultiCamLinux/Pi/add_Pi_Station.sh
+++ b/Scripts/MultiCamLinux/Pi/add_Pi_Station.sh
@@ -1,370 +1,4 @@
#!/bin/bash
-# This software is part of the Linux port of RMS
-# Copyright (C) 2023 Ed Harman
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-#
-# Version 1.9 - support single camera platforms (RPi4 and older) so RMS_FirstRun
-# can offer migration to the Stations data structure on all systems,
-# also migrate camera_settings.json and all captured data
-#
-# Version 1.8 - replaced Desktop link with one that starts all captures
-#
-# Version 1.7 - changed desktop launcher display names and added conkyrc1 mod
-#
-# Version 1.6 - added support for RPi-5 platforms that can run max 4 cameras
-#
-# Version 1.5 - added support for non English locales where user user directories may not include a directory named Desktop
-# i.e. this enables support of RMS on a non English distro install
-#
-# Version 1.4 - moved codebase into RMS/Scripts/MultiCamLinux
-#
-# Version 1.3 - fixed path to CMN desktop shortcut
-#
-# Version 1.2 - added a change to the flag reboot_after_processing from true to false
-#
-# Version 1.1
-# Changes - added station arguments to Launch scripts
-# - changed desktop links for StartCapture to symbolic links of the scripts within .config/autostart
-#
-# Version 1.2 - Peter E. took over dev of this script and any blame going forward
-# - white space and indents added
-#
-
-MaxStation=4 # maximum number of stations allowed on a Pi5
-
-if [[ $(uname -m ) == aarch64 ]]
-then
- Model=$(lscpu| grep -o 'Cortex.*'|grep -o '[0-9]*')
- if [[ $Model -lt 76 ]] # RPi5 uses Cortex A76 - use 72 for script testing on an RPi4
- then
- echo "This platform only supports 1 camera, an RPi5 or similar is required for multiple cameras"
- MaxStation=1
- # quit if a camera is already configured under ~/source/Stations
- if [[ -d ~/source/Stations ]]
- then
- dircount=$(find ~/source/Stations -mindepth 1 -maxdepth 1 -type d | wc -l)
- if [[ $dircount -gt 0 ]]
- then
- read -n1 -r -p 'You are already running a camera on this platform, press ENTER to quit now' key
- exit
- fi
- fi
- fi
-
- DefStation=$(awk ' /stationID:/ {print toupper($2)}' ~/source/RMS/.config) # force uppercase
- if [[ $DefStation == XX0001 && ! -d ~/source/Stations/ ]]
- then
- echo "Please run RMS_Firstrun and configure your 1st station"
- echo "then if you wish to configure additional stations execute add_Pi_Station again"
- exit
- fi
- if [[ ! -d ~/source/Stations/ ]]
- then
-
-cat <,
- # and move any captured data to ~/RMS_data/.
-
- # tweak conky station title
- sed -i 's/\(source\)\/RMS/\1\/Stations\/'"$DefStation"'/g' /home/rms/.conkyrc1
- # tweak conky data source path for log data
- sed -i "s/\(.*RMS_data\)/\1\/${DefStation}/g" /home/rms/.conkyrc1
-
- mkdir ~/source/Stations/
- mkdir ~/source/Stations/${DefStation}
- cp ~/source/RMS/.config ~/source/Stations/${DefStation}
- cd ~/source/RMS
- sed -i "s,^data_dir.*$,data_dir: ~/RMS_data/${DefStation},g" ~/source/Stations/${DefStation}/.config
- if [[ -e ~/source/RMS/platepar_cmn2010.cal ]]
- then
- mv ~/source/RMS/platepar_cmn2010.cal ~/source/Stations/${DefStation}/
- fi
- if [[ -e ~/source/RMS/mask.bmp ]]
- then
- mv ~/source/RMS/mask.bmp ~/source/Stations/${DefStation}/
- fi
- if [[ -e ~/source/RMS/camera_settings.json ]]
- then
- mv ~/source/RMS/camera_settings.json ~/source/Stations/${DefStation}/
- # point the station .config at the relocated camera settings file
- sed -i "s,^camera_settings_path:.*$,camera_settings_path: ~/source/Stations/${DefStation}/camera_settings.json,g" ~/source/Stations/${DefStation}/.config
- fi
-
- # restore pristine default copies in ~/source/RMS, these are used as
- # templates when additional stations are added
- cd ~/source/RMS
- for file in mask.bmp camera_settings.json
- do
- git checkout -- "$file" 2>/dev/null || \
- wget -q -O "$file" "https://raw.githubusercontent.com/CroatianMeteorNetwork/RMS/master/$file"
- done
-
- # move any existing captured data into the station folder
- mkdir -p ~/RMS_data/${DefStation}
- find ~/RMS_data -mindepth 1 -maxdepth 1 ! -name "${DefStation}" -exec mv -t ~/RMS_data/${DefStation}/ {} +
-
- cat <<- EOF > ~/Desktop/${DefStation}_StartCapture.desktop
- [Desktop Entry]
- Name=${DefStation}-StartCapture
- Type=Application
- Exec=lxterminal --title=${DefStation} -e "~/source/RMS/Scripts/MultiCamLinux/StartCapture.sh ${DefStation}"
- Hidden=false
- NoDisplay=false
- Icon=lxterminal
- EOF
- chmod +x ~/Desktop/${DefStation}_StartCapture.desktop
-
- cat <<- EOF > ~/Desktop/${DefStation}-Show_LiveStream.desktop
- [Desktop Entry]
- Name=${DefStation}-ShowLiveStream
- Type=Application
- Exec=lxterminal --title=${DefStation}-LiveStream -e "~/source/RMS/Scripts/MultiCamLinux/LiveStream.sh ${DefStation}"
- Hidden=false
- NoDisplay=false
- Icon=lxterminal
- EOF
- chmod +x ~/Desktop/${DefStation}-Show_LiveStream.desktop
-
- # Check if the user's desktop directory environment variable is set
- if [ -n "$xdg-user-dir DESKTOP" ]
- then
- Desktop=`xdg-user-dir DESKTOP`
- fi
-
- # cleanup unneeded Desktop shortcuts
- if [[ -f ~/Desktop/RMS_FirstRun.sh ]]
- then
- rm -f ~/Desktop/CMNbinViewer.sh ~/Desktop/RMS_ShowLiveStream.sh
- rm -f ~/Desktop/RMS_StartCapture.sh ~/Desktop/RMS_config.txt
- rm -f ~/Desktop/TunnelIPCamera.sh ~/Desktop/DownloadOpenVPNconfig.sh
- fi
-
- if [[ $MaxStation != 1 ]]
- then
- # remove comment from last line of wayfire.ini to enable window cascade
- sed -i s/#mode/mode/ ~/.config/wayfire.ini
- fi
-
- fi # if [[ ! -d ~/source/Stations/ ]]
-
-fi # if [[ $(uname -m ) == aarch64 ]]
-
-# need to count the number of directories under ~/source/Stations and only
-# prompt for adding the correct number of new cameras, so subtract that number
-# from MaxStation defined above
-
-numdirs=$(find ~/source/Stations/ -maxdepth 1 -type d | wc -l)
-numdirs=$((numdirs-1))
-Remaining=$((MaxStation-numdirs))
-#echo MaxStation:$MaxStation NumDirs:$numdirs Remaining:$Remaining
-
-declare Station
-while :
-do
- if [[ ${#Station[@]} == $Remaining ]]
- then
- echo ""
- echo "Done, this platform supports a maximum of $MaxStation camera(s)"
- break
- fi
- read -p "Enter station ID, to end: " this_Station
- this_Station="${this_Station^^}" #uppercase it..
- if [[ -z $this_Station ]]
- then
- break
- fi
- Station+=("$this_Station")
- # echo $Station
-done
-
-if [[ $MaxStation != 1 ]]
-then
- echo -e "\nNew stations to add -"
- printf '%s\n' "${Station[@]}"
-fi
-
-# check if ~/RMS_data exists and if not create it....
-if [[ ! -d ~/RMS_data ]]
-then
- mkdir ~/RMS_data
-fi
-
-No_Stations=${#Station[@]}
-for item in "${Station[@]}"
-do
- if [[ -d ~/source/Stations/${item} ]]
- then
- echo -e "\n\nNot creating station ${item} - it already exists\n"
- exit
- else
- echo "making dir Stations/${item}"
- mkdir ~/source/Stations/${item}
- if [[ ! -d ~/RMS_data/${item} ]]
- then
- mkdir ~/RMS_data/${item}
- fi
- fi
-
- cp ~/source/RMS/.config ~/source/Stations/${item}
-
- # create autostart entry for the station
- cat <<- EOF > ~/Desktop/${item}_StartCapture.desktop
- [Desktop Entry]
- Name=${item}-StartCapture
- Type=Application
- Exec=lxterminal --title=${item} -e "~/source/RMS/Scripts/MultiCamLinux/StartCapture.sh ${item}"
- Hidden=false
- NoDisplay=false
- Icon=lxterminal
- EOF
- chmod +x ~/Desktop/${item}_StartCapture.desktop
-
- if [[ -e ~/source/RMS/mask.bmp ]]
- then
- cp ~/source/RMS/mask.bmp ~/source/Stations/${item}/
- fi
-
- # create a ShowLiveStream- ~/Desktop/${item}-Show_LiveStream.desktop
- [Desktop Entry]
- Name=${item}-ShowLiveStream
- Type=Application
- Exec=lxterminal --title=Stream-${item} -e "~/source/RMS/Scripts/MultiCamLinux/LiveStream.sh ${item}"
- Hidden=false
- NoDisplay=false
- Icon=lxterminal
- EOF
- chmod +x ~/Desktop/${item}-Show_LiveStream.desktop
-
- # customise each .config
- # set the station_id
- sed -i "s/D:.*$/D: $item/g" ~/source/Stations/${item}/.config
- # set the data_dir
- sed -i "s,^data_dir.*$,data_dir: ~/RMS_data/${item},g" ~/source/Stations/${item}/.config
- echo -e "\n\nAdded station $item\n\n"
- if [[ $MaxStation != 1 ]]
- then
- # disable daily post processing reboot
- sed -i "s/\(reboot_after_processing:\).*/\1 false/g" ~/source/Stations/${item}/.config
- fi
-done
-
-# check if keys are already present
-if [[ -f ~/.ssh/id_rsa ]]
-then
- if [ ! -z ${Configured} ]
- then
- echo "SSH keys already exist, not overwriting them"
- fi
-else
- ssh-keygen -t rsa -f ~/.ssh/id_rsa -q -P ""
- cp ~/.ssh/id_rsa.pub ~/Desktop
- echo "SSH keys successfully generated in ~/.ssh"
- echo "your new id_rsa.pub public key file now placed on the Desktop"
- echo "Be sure to send a copy of this file to Denis"
-fi
-
-if [[ ! -v Model ]]
-then
- timedatectl set-timezone UTC
-fi
-
-echo -e "\n\nStation configuration complete\n\n"
-
-# Set up shortcut for CMNbinViewer
-cat <<- EOF > ~/Desktop/CMNbinViewer.desktop
-[Desktop Entry]
-Name=CMNbinViewer
-Type=Application
-Exec=${HOME}/source/RMS/Scripts/CMNbinViewer_env.sh
-Hidden=false
-NoDisplay=false
-Icon=${HOME}/source/RMS/Scripts/MultiCamLinux/icon.png
-EOF
-chmod +x ~/Desktop/CMNbinViewer.desktop
-
-# Create Desktop shortcut for editing the station .config files
-cat << 'EOF' > ~/Desktop/Edit_configs
-#!/bin/bash
-
-echo ""
-echo "One by one, each config file under ~/source/Stations will open for editing..."
-read -n1 -r -p 'Press ENTER to proceed, any other key to quit: ' key
-if [[ "${key}" != "" ]]; then
- exit
-fi
-
-for Dir in ~/source/Stations/*/
-do
- Station=$(basename "${Dir}")
- echo ""
- echo "Editing .config file for ${Station}"
- read -n1 -r -p 'Press ENTER to edit, any other key to skip: ' key
- if [[ "${key}" = "" ]]; then
- mousepad ~/source/Stations/${Station}/.config
- fi
-done
-
-echo ""
-echo "Done editing .config files"
-sleep 2
-EOF
-chmod +x ~/Desktop/Edit_configs
-
-# set the option to allow launching of desktop shortcuts
-#sed -i s/quick_exec=0/quick_exec=1/ ~/.config/libfm/libfm.conf
-
-if grep -Fqv "UTC" /etc/timezone
-then
- sudo timedatectl set-timezone UTC
-fi
-
-if [[ $MaxStation != 1 ]]
-then
- # set the extra_space for all configured stations -
- mult=$(ls -1 /home/${USER}/source/Stations/|wc -l)
- for Dir in /home/${USER}/source/Stations/*
- do
- sed -i "s/extra_space_gb: .*$/extra_space_gb: $(( ${mult} * 30 ))/g" ${Dir}/.config
- done
-fi
-
-# set up so RMS_FirstRun can autostart all captures
-rm -f /home/${USER}/Desktop/RMS_StartCapture.sh
-ln -s /home/${USER}/source/RMS/Scripts/MultiCamLinux/Pi/RMS_StartCapture_MCP.sh /home/${USER}/Desktop/RMS_StartCapture.sh
-
-# replace the potentially modified .config in ~/source/RMS with a pristine default
-cd ~/source/RMS
-git checkout -- .config 2>/dev/null || \
- wget -q -O .config https://raw.githubusercontent.com/CroatianMeteorNetwork/RMS/master/.config
+# Kept for compatibility with existing documentation and older images.
+# The station tooling is now universal - see add_Station.sh.
+exec "$(dirname "$0")/../add_Station.sh" "$@"
diff --git a/Scripts/MultiCamLinux/add_GStation.sh b/Scripts/MultiCamLinux/add_GStation.sh
index 607b346e8..1ed202c0a 100755
--- a/Scripts/MultiCamLinux/add_GStation.sh
+++ b/Scripts/MultiCamLinux/add_GStation.sh
@@ -1,293 +1,4 @@
#!/bin/bash
-# This software is part of the Linux port of RMS
-# Copyright (C) 2023 Ed Harman
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-#
-# Version 1.7 - stations can be created in bulk from a stations.csv file,
-# desktop entries use gnome-terminal instead of lxterminal,
-# interactive prompts remain the fallback when no CSV is given
-#
-# Version 1.6 - added logic to prevent running this script on a Raspberry Pi
-# added by Peter E., June 2024
-#
-# Version 1.5 - added support for non English locales where user user directories may not include a directory named Desktop
-# i.e. this enables support of RMS on a non English distro install
-#
-# Version 1.4 - moved codebase into RMS/Scripts/MultiCamLinux
-#
-# Version 1.3 - fixed path to CMN desktop shortcut
-#
-# Version 1.2 - added a change to the flag reboot_after_processing from true to false
-#
-# Version 1.1
-# Changes - added station arguments to Launch scripts
-# - changed desktop links for StartCapture to symbolic links of the scripts within .config/autostart
-#
-
-# Prevent running this script on a Raspberry Pi
-file=/sys/firmware/devicetree/base/model
-if [[ -f "$file" ]]; then
- contents=$(tr -d '\0' < $file)
- if [[ $contents == 'Raspberry'* ]]; then
- echo "The add_GStation.sh script should not be used on Raspberry Pi."
- echo "Please use add_Pi_Station.sh to add cameras on a Pi5."
- exit 1
- fi
-fi
-
-# Get user's desktop directory
-Desktop=$(xdg-user-dir DESKTOP 2>/dev/null || echo "$HOME/Desktop")
-
-# Strip leading/trailing whitespace (including CR from Windows-edited CSVs)
-trim() {
- local s="$*"
- s="${s#"${s%%[![:space:]]*}"}"
- s="${s%"${s##*[![:space:]]}"}"
- printf '%s' "$s"
-}
-
-# Create a station directory with config, mask and desktop entries.
-# Returns 1 if the station already exists.
-create_station() {
- local item="$1"
- local config_file=~/source/Stations/${item}/.config
-
- if [[ -d ~/source/Stations/${item} ]]; then
- echo -e "\nNot creating station ${item} - it already exists"
- return 1
- fi
-
- echo "Creating station ${item}..."
- mkdir -p ~/source/Stations/${item}
- mkdir -p "${RMS_data}/${item}"
-
- # Copy config template
- cp ~/source/RMS/.config ~/source/Stations/${item}
-
- # Create autostart .desktop entry using gnome-terminal.
- # The StartCapture profile is optional - create one to customise the
- # capture window; without it gnome-terminal uses the default profile.
- cat <<- EOF > ~/.config/autostart/${item}_StartCap.desktop
- [Desktop Entry]
- Name=${item}_StartCap
- Type=Application
- Exec=gnome-terminal --profile=StartCapture --title=${item} -- bash -c "~/source/RMS/Scripts/MultiCamLinux/StartCapture.sh ${item}"
- Hidden=false
- NoDisplay=false
- Icon=gnome-terminal
- EOF
-
- chmod +x ~/.config/autostart/${item}_StartCap.desktop
-
- # Create Desktop symlink for StartCapture
- ln -sf ~/.config/autostart/${item}_StartCap.desktop "${Desktop}/${item}_StartCap.desktop"
- gio set "${Desktop}/${item}_StartCap.desktop" metadata::trusted true 2>/dev/null
- chmod +x "${Desktop}/${item}_StartCap.desktop"
-
- # Copy mask file
- cp ~/source/RMS/mask.bmp ~/source/Stations/${item} 2>/dev/null
-
- # Create ShowLiveStream desktop shortcut using gnome-terminal
- cat <<- EOF > "${Desktop}/Show_LiveStream-${item}.desktop"
- [Desktop Entry]
- Name=${item}-ShowLiveStream
- Type=Application
- Exec=gnome-terminal --profile=StartCapture --title=Stream-${item} -- bash -c "~/source/RMS/Scripts/MultiCamLinux/LiveStream.sh ${item}"
- Hidden=false
- NoDisplay=false
- Icon=gnome-terminal
- EOF
-
- gio set "${Desktop}/Show_LiveStream-${item}.desktop" metadata::trusted true 2>/dev/null
- chmod +x "${Desktop}/Show_LiveStream-${item}.desktop"
-
- # Apply default config customizations
- sed -i "s/^stationID:.*$/stationID: $item/g" "${config_file}"
- sed -i "s,^data_dir:.*$,data_dir: ${RMS_data}/${item},g" "${config_file}"
- sed -i "s/^extra_space_gb:.*$/extra_space_gb: $(( ${No_Stations} * 20 ))/g" "${config_file}"
- sed -i "s/^\(reboot_after_processing:\).*/\1 false/g" "${config_file}"
-
- return 0
-}
-
-# Parse arguments: [RMS_data_path] [stations.csv]
-# Smart detection: if single arg ends in .csv, treat as CSV file
-RMS_data=~/RMS_data
-CSV_FILE=""
-
-if [[ "$#" -eq 0 ]]; then
- # No args: use the default CSV if one exists
- if [[ -f ~/source/RMS/stations.csv ]]; then
- CSV_FILE=~/source/RMS/stations.csv
- fi
-elif [[ "$#" -eq 1 ]]; then
- # Single arg: check if it's a CSV file or a directory
- if [[ "${1}" == *.csv ]]; then
- CSV_FILE="${1}"
- else
- RMS_data="${1}"
- if [[ -f ~/source/RMS/stations.csv ]]; then
- CSV_FILE=~/source/RMS/stations.csv
- fi
- fi
-else
- # Two args: first is RMS_data, second is CSV
- RMS_data="${1}"
- CSV_FILE="${2}"
-fi
-
-# A CSV that was explicitly requested must exist
-if [[ -n "${CSV_FILE}" && ! -f "${CSV_FILE}" ]]; then
- echo "Error: CSV file not found: ${CSV_FILE}"
- echo "Usage: $0 [RMS_data_path] [stations.csv]"
- exit 1
-fi
-
-# Ensure required directories exist
-mkdir -p ~/source/Stations
-mkdir -p "${RMS_data}"
-mkdir -p ~/.config/autostart
-
-if [[ -n "${CSV_FILE}" ]]; then
-
- echo "Reading stations from: ${CSV_FILE}"
-
- # Read CSV header to get column names
- IFS=',' read -r -a HEADERS < "${CSV_FILE}"
-
- # Trim whitespace from headers
- for i in "${!HEADERS[@]}"; do
- HEADERS[$i]=$(trim "${HEADERS[$i]}")
- done
-
- # Find required column indices
- station_id_col=-1
- camera_ip_col=-1
- for i in "${!HEADERS[@]}"; do
- case "${HEADERS[$i]}" in
- station_id|stationID) station_id_col=$i ;;
- camera_ip) camera_ip_col=$i ;;
- esac
- done
-
- if [[ $station_id_col -eq -1 ]]; then
- echo "Error: CSV must have a 'station_id' or 'stationID' column"
- exit 1
- fi
-
- # Count stations for extra_space_gb calculation
- No_Stations=$(tail -n +2 "${CSV_FILE}" | grep -c -v '^[[:space:]]*$')
-
- echo -e "\nStations to add from CSV: ${No_Stations}"
-
- # Process each data row (the || keeps a final line without a trailing newline)
- tail -n +2 "${CSV_FILE}" | while IFS=',' read -r -a VALUES || [[ ${#VALUES[@]} -gt 0 ]]; do
- # Skip empty lines
- [[ ${#VALUES[@]} -eq 0 ]] && continue
-
- # Trim whitespace from values
- for i in "${!VALUES[@]}"; do
- VALUES[$i]=$(trim "${VALUES[$i]}")
- done
-
- item="${VALUES[$station_id_col]}"
- [[ -z "$item" ]] && continue
-
- create_station "$item" || continue
-
- config_file=~/source/Stations/${item}/.config
-
- # Apply camera_ip to device URL if column exists
- if [[ $camera_ip_col -ne -1 && -n "${VALUES[$camera_ip_col]}" ]]; then
- camera_ip="${VALUES[$camera_ip_col]}"
- # Replace IP address in the device URL (matches IP pattern in rtsp:// URL)
- sed -i -E "s|^(device:.*rtsp://)[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+|\1${camera_ip}|g" "${config_file}"
- echo " Set camera IP: ${camera_ip}"
- fi
-
- # Apply any additional CSV columns that match .config keys
- for i in "${!HEADERS[@]}"; do
- header="${HEADERS[$i]}"
- value="${VALUES[$i]}"
-
- # Skip station_id, camera_ip (already handled), and empty values
- [[ "$header" == "station_id" || "$header" == "stationID" || "$header" == "camera_ip" ]] && continue
- [[ -z "$value" ]] && continue
-
- # Check if this header exists as a key in .config
- if grep -q "^${header}:" "${config_file}"; then
- sed -i "s|^${header}:.*$|${header}: ${value}|g" "${config_file}"
- echo " Set ${header}: ${value}"
- fi
- done
-
- echo "Added station ${item}"
- done
-
-else
-
- # No CSV available - prompt for station IDs interactively
- declare -a Station
- while :
- do
- read -p "Enter station ID, to end: " this_Station
- this_Station=$(trim "${this_Station^^}")
- if [[ -z $this_Station ]]; then
- break
- fi
- Station+=("$this_Station")
- done
-
- No_Stations=${#Station[@]}
- echo -e "\nNew stations to add -"
- printf '%s\n' "${Station[@]}"
-
- for item in "${Station[@]}"; do
- create_station "$item" && echo "Added station ${item}"
- done
-
-fi
-
-# Generate SSH keys if not present
-if [[ ! -f ~/.ssh/id_rsa ]]; then
- ssh-keygen -t rsa -f ~/.ssh/id_rsa -q -P ""
- echo "SSH keys generated in ~/.ssh"
-fi
-
-# Add RMS activation to .bashrc if not present
-if ! grep -q "source ~/vRMS/bin/activate" ~/.bashrc; then
- cat <> ~/.bashrc
-
-# Activate RMS
-cd ~/source/RMS
-source ~/vRMS/bin/activate
-EOF
-fi
-
-# Create CMNbinViewer desktop shortcut
-cat <<- EOF > "${Desktop}/CMNbinViewer.desktop"
-[Desktop Entry]
-Name=CMNbinViewer
-Type=Application
-Exec=${HOME}/source/RMS/Scripts/CMNbinViewer_env.sh
-Hidden=false
-NoDisplay=false
-Icon=${HOME}/source/RMS/Scripts/MultiCamLinux/icon.png
-EOF
-
-gio set "${Desktop}/CMNbinViewer.desktop" metadata::trusted true 2>/dev/null
-chmod +x "${Desktop}/CMNbinViewer.desktop"
-
-echo -e "\nStation configuration complete"
+# Kept for compatibility with existing documentation.
+# The station tooling is now universal - see add_Station.sh.
+exec "$(dirname "$0")/add_Station.sh" "$@"
diff --git a/Scripts/MultiCamLinux/add_Station.sh b/Scripts/MultiCamLinux/add_Station.sh
new file mode 100755
index 000000000..18917c376
--- /dev/null
+++ b/Scripts/MultiCamLinux/add_Station.sh
@@ -0,0 +1,521 @@
+#!/bin/bash
+# This software is part of the Linux port of RMS
+# Copyright (C) 2023 Ed Harman
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+#
+# Universal station tool for any Linux platform, Raspberry Pi included.
+# Merged from add_Pi_Station.sh and add_GStation.sh, which are kept as
+# compatibility wrappers around this script.
+#
+# What it does:
+# - relocates a legacy single-camera setup (config files in ~/source/RMS,
+# data directly in ~/RMS_data) into the per-station layout under
+# ~/source/Stations/ and ~/RMS_data/
+# - creates new stations, either interactively or in bulk from a CSV file
+#
+# Usage: add_Station.sh [RMS_data_path] [stations.csv]
+#
+# The CSV header row names the columns: station_id (required), camera_ip
+# (spliced into the rtsp device URL), and any other .config key, which is
+# applied verbatim to that station's config. With no CSV argument the
+# script uses ~/source/RMS/stations.csv if present, otherwise it prompts
+# for station IDs interactively.
+#
+# On Raspberry Pi platforms a recommended camera limit applies (1 camera,
+# or 4 on a Pi5 or later). The limit is advisory - it reflects what has
+# been tested - and can be overridden at the prompt.
+
+# ---------------------------------------------------------------- helpers
+
+# Strip leading/trailing whitespace (including CR from Windows-edited CSVs)
+trim() {
+ local s="$*"
+ s="${s#"${s%%[![:space:]]*}"}"
+ s="${s%"${s##*[![:space:]]}"}"
+ printf '%s' "$s"
+}
+
+uppercase() {
+ printf '%s' "$*" | tr '[:lower:]' '[:upper:]'
+}
+
+# Restore a pristine copy of a file in ~/source/RMS from git, or from
+# GitHub when ~/source/RMS is not a git checkout
+restore_default() {
+ ( cd ~/source/RMS && git checkout -- "$1" 2>/dev/null ) || \
+ wget -q -O ~/source/RMS/"$1" "https://raw.githubusercontent.com/CroatianMeteorNetwork/RMS/master/$1"
+}
+
+count_stations() {
+ find ~/source/Stations -mindepth 1 -maxdepth 1 -type d 2>/dev/null | wc -l
+}
+
+# Build the Exec= line for a .desktop entry using the detected terminal.
+# The gnome-terminal StartCapture profile is optional - create one to
+# customise the capture windows; without it the default profile is used.
+term_exec() { # $1 = window title, $2 = command
+ if [[ "$TERMINAL" == "gnome-terminal" ]]; then
+ printf 'gnome-terminal --profile=StartCapture --title=%s -- bash -c "%s"' "$1" "$2"
+ else
+ printf 'lxterminal --title=%s -e "%s"' "$1" "$2"
+ fi
+}
+
+# Create a station directory with config, mask and launch entries.
+# Returns 1 if the station already exists.
+create_station() {
+ local item="$1"
+ local config_file=~/source/Stations/${item}/.config
+ local entry
+
+ if [[ -d ~/source/Stations/${item} ]]; then
+ echo -e "\nNot creating station ${item} - it already exists"
+ return 1
+ fi
+
+ echo "Creating station ${item}..."
+ mkdir -p ~/source/Stations/${item}
+ mkdir -p "${RMS_data}/${item}"
+
+ # Copy config template
+ cp ~/source/RMS/.config ~/source/Stations/${item}
+
+ # Copy mask file
+ cp ~/source/RMS/mask.bmp ~/source/Stations/${item} 2>/dev/null
+
+ # Launch entry for the station. On FirstRun-managed systems (the Pi
+ # images) captures are started by RMS_StartCapture_MCP.sh, so only a
+ # Desktop launcher is created; elsewhere the entry goes into XDG
+ # autostart with a Desktop symlink, so captures start at login.
+ if [[ -f ~/.rmsautorunflag ]]; then
+ entry="${Desktop}/${item}_StartCapture.desktop"
+ else
+ entry=~/.config/autostart/${item}_StartCapture.desktop
+ fi
+
+ cat <<- EOF > "$entry"
+ [Desktop Entry]
+ Name=${item}-StartCapture
+ Type=Application
+ Exec=$(term_exec "${item}" "~/source/RMS/Scripts/MultiCamLinux/StartCapture.sh ${item}")
+ Hidden=false
+ NoDisplay=false
+ Icon=${TERMINAL}
+ EOF
+ chmod +x "$entry"
+
+ if [[ "$entry" != "${Desktop}/"* ]]; then
+ ln -sf "$entry" "${Desktop}/${item}_StartCapture.desktop"
+ gio set "${Desktop}/${item}_StartCapture.desktop" metadata::trusted true 2>/dev/null
+ chmod +x "${Desktop}/${item}_StartCapture.desktop"
+ fi
+
+ # ShowLiveStream desktop shortcut
+ cat <<- EOF > "${Desktop}/${item}-ShowLiveStream.desktop"
+ [Desktop Entry]
+ Name=${item}-ShowLiveStream
+ Type=Application
+ Exec=$(term_exec "Stream-${item}" "~/source/RMS/Scripts/MultiCamLinux/LiveStream.sh ${item}")
+ Hidden=false
+ NoDisplay=false
+ Icon=${TERMINAL}
+ EOF
+ gio set "${Desktop}/${item}-ShowLiveStream.desktop" metadata::trusted true 2>/dev/null
+ chmod +x "${Desktop}/${item}-ShowLiveStream.desktop"
+
+ # Customise the config
+ sed -i "s/^stationID:.*$/stationID: $item/g" "${config_file}"
+ sed -i "s,^data_dir:.*$,data_dir: ${RMS_data}/${item},g" "${config_file}"
+
+ return 0
+}
+
+# Relocate a legacy single-camera setup into the per-station layout
+migrate_legacy() {
+
+cat </dev/null 2>&1; then
+ TERMINAL=gnome-terminal
+elif command -v lxterminal >/dev/null 2>&1; then
+ TERMINAL=lxterminal
+elif command -v gnome-terminal >/dev/null 2>&1; then
+ TERMINAL=gnome-terminal
+else
+ TERMINAL=lxterminal
+ echo "Warning: neither lxterminal nor gnome-terminal was found, launch entries will use lxterminal"
+fi
+
+# Get user's desktop directory
+Desktop=$(xdg-user-dir DESKTOP 2>/dev/null || echo "$HOME/Desktop")
+
+MIGRATED=0
+DefStation=$(uppercase "$(awk '/^stationID:/ {print $2}' ~/source/RMS/.config 2>/dev/null)")
+
+# A configured legacy single-camera setup is offered relocation first
+if [[ ! -d ~/source/Stations && -n "$DefStation" && "$DefStation" != "XX0001" ]]; then
+ migrate_legacy
+elif [[ -f ~/.rmsautorunflag && "$DefStation" == "XX0001" && ! -d ~/source/Stations ]]; then
+ echo "Please run RMS_FirstRun and configure your 1st station,"
+ echo "then run add_Station again to relocate it or add more cameras."
+ exit
+fi
+
+# Ensure required directories exist
+mkdir -p ~/source/Stations
+mkdir -p "${RMS_data}"
+mkdir -p ~/.config/autostart
+
+if [[ -n "${CSV_FILE}" ]]; then
+
+ echo "Reading stations from: ${CSV_FILE}"
+
+ # Read CSV header to get column names
+ IFS=',' read -r -a HEADERS < "${CSV_FILE}"
+
+ # Trim whitespace from headers
+ for i in "${!HEADERS[@]}"; do
+ HEADERS[$i]=$(trim "${HEADERS[$i]}")
+ done
+
+ # Find required column indices
+ station_id_col=-1
+ camera_ip_col=-1
+ for i in "${!HEADERS[@]}"; do
+ case "${HEADERS[$i]}" in
+ station_id|stationID) station_id_col=$i ;;
+ camera_ip) camera_ip_col=$i ;;
+ esac
+ done
+
+ if [[ $station_id_col -eq -1 ]]; then
+ echo "Error: CSV must have a 'station_id' or 'stationID' column"
+ exit 1
+ fi
+
+ No_Stations=$(tail -n +2 "${CSV_FILE}" | grep -c -v '^[[:space:]]*$')
+ echo -e "\nStations to add from CSV: ${No_Stations}"
+
+ # Warn when the CSV would exceed the recommended camera limit
+ if [[ $RECOMMENDED -gt 0 && $(( $(count_stations) + No_Stations )) -gt $RECOMMENDED ]]; then
+ echo ""
+ echo "This would exceed the recommended maximum of ${RECOMMENDED} camera(s) for this platform"
+ echo "(the tested limit - more cameras may drop frames during capture and processing)."
+ read -n1 -r -p 'Press Y to continue anyway, any other key to quit: ' key
+ echo ""
+ if [[ "$key" != "y" && "$key" != "Y" ]]; then
+ exit
+ fi
+ fi
+
+ # Process each data row (the || keeps a final line without a trailing newline)
+ tail -n +2 "${CSV_FILE}" | while IFS=',' read -r -a VALUES || [[ ${#VALUES[@]} -gt 0 ]]; do
+ # Skip empty lines
+ [[ ${#VALUES[@]} -eq 0 ]] && continue
+
+ # Trim whitespace from values
+ for i in "${!VALUES[@]}"; do
+ VALUES[$i]=$(trim "${VALUES[$i]}")
+ done
+
+ item="${VALUES[$station_id_col]}"
+ [[ -z "$item" ]] && continue
+
+ create_station "$item" || continue
+
+ config_file=~/source/Stations/${item}/.config
+
+ # Apply camera_ip to device URL if column exists
+ if [[ $camera_ip_col -ne -1 && -n "${VALUES[$camera_ip_col]}" ]]; then
+ camera_ip="${VALUES[$camera_ip_col]}"
+ # Replace IP address in the device URL (matches IP pattern in rtsp:// URL)
+ sed -i -E "s|^(device:.*rtsp://)[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+|\1${camera_ip}|g" "${config_file}"
+ echo " Set camera IP: ${camera_ip}"
+ fi
+
+ # Apply any additional CSV columns that match .config keys
+ for i in "${!HEADERS[@]}"; do
+ header="${HEADERS[$i]}"
+ value="${VALUES[$i]}"
+
+ # Skip station_id, camera_ip (already handled), and empty values
+ [[ "$header" == "station_id" || "$header" == "stationID" || "$header" == "camera_ip" ]] && continue
+ [[ -z "$value" ]] && continue
+
+ # Check if this header exists as a key in .config
+ if grep -q "^${header}:" "${config_file}"; then
+ sed -i "s|^${header}:.*$|${header}: ${value}|g" "${config_file}"
+ echo " Set ${header}: ${value}"
+ fi
+ done
+
+ echo "Added station ${item}"
+ done
+
+else
+
+ # No CSV available - prompt for station IDs interactively
+ declare -a Station
+ while :
+ do
+ # Advise when the recommended camera limit is reached, allow override
+ if [[ $RECOMMENDED -gt 0 && $(( $(count_stations) + ${#Station[@]} )) -ge $RECOMMENDED ]]; then
+ echo ""
+ echo "The recommended maximum of ${RECOMMENDED} camera(s) for this platform is configured"
+ echo "(the tested limit - more cameras may drop frames during capture and processing)."
+ read -n1 -r -p 'Press ENTER to finish, or A to add another camera anyway: ' key
+ echo ""
+ if [[ "$key" != "a" && "$key" != "A" ]]; then
+ break
+ fi
+ fi
+ read -p "Enter station ID, to end: " this_Station
+ this_Station=$(uppercase "$(trim "$this_Station")")
+ if [[ -z "$this_Station" ]]; then
+ break
+ fi
+ Station+=("$this_Station")
+ done
+
+ if [[ ${#Station[@]} -gt 0 ]]; then
+ echo -e "\nNew stations to add -"
+ printf '%s\n' "${Station[@]}"
+ fi
+
+ for item in "${Station[@]}"; do
+ create_station "$item" && echo "Added station ${item}"
+ done
+
+fi
+
+# -------------------------------------------------- per-platform settings
+
+total=$(count_stations)
+
+if [[ $total -gt 1 ]]; then
+ for dir in ~/source/Stations/*/; do
+ # scale the reserved free space with the number of stations
+ sed -i "s/^extra_space_gb:.*$/extra_space_gb: $(( total * 20 ))/g" "${dir}/.config"
+ # disable the daily post processing reboot, it would kill the other captures
+ sed -i "s/^\(reboot_after_processing:\).*/\1 false/g" "${dir}/.config"
+ done
+
+ # remove comment from last line of wayfire.ini to enable window cascade (Pi image)
+ if [[ -f ~/.config/wayfire.ini ]]; then
+ sed -i s/#mode/mode/ ~/.config/wayfire.ini
+ fi
+fi
+
+# On FirstRun-managed systems (the Pi images) captures are started by
+# RMS_StartCapture_MCP.sh after the boot-time update
+if [[ -f ~/.rmsautorunflag ]]; then
+ rm -f "${Desktop}/RMS_StartCapture.sh"
+ ln -s ~/source/RMS/Scripts/MultiCamLinux/Pi/RMS_StartCapture_MCP.sh "${Desktop}/RMS_StartCapture.sh"
+fi
+
+# Generate SSH keys if not present
+if [[ ! -f ~/.ssh/id_rsa ]]; then
+ ssh-keygen -t rsa -f ~/.ssh/id_rsa -q -P ""
+ cp ~/.ssh/id_rsa.pub "${Desktop}/"
+ echo "SSH keys generated in ~/.ssh"
+ echo "Your new id_rsa.pub public key file is now placed on the Desktop."
+ echo "Be sure to send a copy of this file to Denis"
+fi
+
+# Add RMS activation to .bashrc if not present
+if ! grep -q "source ~/vRMS/bin/activate" ~/.bashrc; then
+ cat <> ~/.bashrc
+
+# Activate RMS
+cd ~/source/RMS
+source ~/vRMS/bin/activate
+EOF
+fi
+
+# Create CMNbinViewer desktop shortcut
+cat <<- EOF > "${Desktop}/CMNbinViewer.desktop"
+[Desktop Entry]
+Name=CMNbinViewer
+Type=Application
+Exec=${HOME}/source/RMS/Scripts/CMNbinViewer_env.sh
+Hidden=false
+NoDisplay=false
+Icon=${HOME}/source/RMS/Scripts/MultiCamLinux/icon.png
+EOF
+gio set "${Desktop}/CMNbinViewer.desktop" metadata::trusted true 2>/dev/null
+chmod +x "${Desktop}/CMNbinViewer.desktop"
+
+# Create Desktop shortcut for editing the station .config files
+cat << 'EOF' > "${Desktop}/Edit_configs"
+#!/bin/bash
+
+echo ""
+echo "One by one, each config file under ~/source/Stations will open for editing..."
+read -n1 -r -p 'Press ENTER to proceed, any other key to quit: ' key
+if [[ "${key}" != "" ]]; then
+ exit
+fi
+
+editor=""
+for e in mousepad leafpad gedit gnome-text-editor nano; do
+ if command -v "$e" >/dev/null 2>&1; then
+ editor="$e"
+ break
+ fi
+done
+
+for Dir in ~/source/Stations/*/
+do
+ Station=$(basename "${Dir}")
+ echo ""
+ echo "Editing .config file for ${Station}"
+ read -n1 -r -p 'Press ENTER to edit, any other key to skip: ' key
+ if [[ "${key}" = "" ]]; then
+ "$editor" ~/source/Stations/${Station}/.config
+ fi
+done
+
+echo ""
+echo "Done editing .config files"
+sleep 2
+EOF
+chmod +x "${Desktop}/Edit_configs"
+
+# After a relocation, replace the now station-specific .config in
+# ~/source/RMS with a pristine default. This happens last so stations
+# added in the same run used the configured one as their template.
+if [[ $MIGRATED -eq 1 ]]; then
+ restore_default .config
+fi
+
+echo -e "\nStation configuration complete"
diff --git a/Scripts/RMS_FirstRun.sh b/Scripts/RMS_FirstRun.sh
index 56f076f71..11d478d39 100755
--- a/Scripts/RMS_FirstRun.sh
+++ b/Scripts/RMS_FirstRun.sh
@@ -25,18 +25,18 @@ RMSUPDATESCRIPT=~/Desktop/RMS_Update.sh
# Function for editing the RMS config
editRMSConfig () {
- # If leafpad is not available (Raspbian Jessie), use mousepad (Raspbian Buster)
- if [ $( command -v leafpad ) ]; then
+ # Use the first available editor (leafpad on Raspbian Jessie, mousepad on
+ # Raspbian Buster and later, gedit/gnome-text-editor on GNOME desktops)
+ local editor
+ for editor in leafpad mousepad gedit gnome-text-editor nano; do
+ if [ $( command -v $editor ) ]; then
- # Open the config file
- leafpad $RMSCONFIG
+ # Open the config file
+ $editor $RMSCONFIG
+ return
- else
-
- # Open the config file
- mousepad $RMSCONFIG
-
- fi
+ fi
+ done
}
@@ -122,6 +122,9 @@ echo ""
echo "At the end of these steps the first data capture session will start."
echo ""
+# Expanding the file system only applies to Raspberry Pi SD card images
+if command -v raspi-config >/dev/null 2>&1; then
+
echo "
0) Expanding the file system
----------------------------
@@ -133,12 +136,12 @@ read -n1 -r -p 'If you have flashed this SD card yourself, press ENTER.' key
if [[ "$key" = "" ]]; then
lxterminal -e "sudo raspi-config"
-
+
echo "
Another window has opened where you can expand the file system.
-
+
Go to:
-
+
7 ADVANCED OPTIONS -->
A1 EXPAND FILE SYSTEM -->
< OK >
@@ -152,6 +155,8 @@ if [[ "$key" = "" ]]; then
read -p "Press ENTER to continue..."
fi
+fi # raspi-config available
+
echo ""
echo ""
@@ -251,9 +256,9 @@ else
fi
done
-# Offer the conversion to the multi-camera data structure. The conversion
-# script only supports 64-bit Pi platforms, so skip this step elsewhere.
-if [[ $(uname -m) == aarch64 ]]; then
+# Offer the conversion to the multi-camera data structure, unless the
+# system already uses it
+if [[ ! -d ~/source/Stations ]]; then
echo "
5) Converting the data structure
@@ -283,29 +288,22 @@ fi
if [[ "$key" = "" ]]; then
echo ""
- echo "Next, this script will run to convert to the recommended data structure:"
- echo " ~/source/RMS/Scripts/MultiCamLinux/Pi/add_Pi_Station.sh"
+ echo "Next, this script will run to convert to the recommended data structure"
+ echo "(it will show the details and ask you to confirm):"
+ echo " ~/source/RMS/Scripts/MultiCamLinux/add_Station.sh"
echo ""
- echo "Note: if you have a Pi5 or similar, you can run the same script later to add more cameras."
+ echo "Note: you can run the same script later to add more cameras."
echo ""
sleep 2
- read -n1 -r -p 'Press ENTER a second time to confirm the conversion, or Q to keep the legacy structure... ' key
-fi
-echo ""
-
-if [[ "$key" = "" ]]; then
- echo ""
- echo "Converting to the recommended data structure..."
- echo ""
- bash ~/source/RMS/Scripts/MultiCamLinux/Pi/add_Pi_Station.sh
+ bash ~/source/RMS/Scripts/MultiCamLinux/add_Station.sh
else
echo ""
echo "You have chosen to keep the legacy data structure."
echo "If you decide to convert in the future, run this command from a terminal:"
- echo " ~/source/RMS/Scripts/MultiCamLinux/Pi/add_Pi_Station.sh"
+ echo " ~/source/RMS/Scripts/MultiCamLinux/add_Station.sh"
fi
-fi # if aarch64
+fi # if no ~/source/Stations yet
# Enable autorun. This is done only after the optional data structure
# conversion so an interrupted conversion does not get skipped over by
From 0ec29a70d89fc31a681ec30bf2ea55184c6e9477 Mon Sep 17 00:00:00 2001
From: Luc Busquin <133058544+Cybis320@users.noreply.github.com>
Date: Wed, 10 Jun 2026 00:37:48 -0700
Subject: [PATCH 04/17] Prompt for station ID and coordinates directly in
RMS_FirstRun
Instead of opening the whole .config in a GUI editor (leafpad, mousepad,
or whatever the desktop provides) and grep-checking afterwards that the
station ID was changed, FirstRun now asks for the four values it needs -
station ID, latitude, longitude, elevation - right at the terminal,
validates them (GMN code format with an override, numeric ranges for the
coordinates), shows a summary to confirm or re-enter, and writes them
into .config with anchored seds. EOF on any prompt aborts the script
instead of looping. The RMS_config desktop shortcut remains the way to
edit any other setting.
---
Scripts/RMS_FirstRun.sh | 111 ++++++++++++++++++++++++----------------
1 file changed, 67 insertions(+), 44 deletions(-)
diff --git a/Scripts/RMS_FirstRun.sh b/Scripts/RMS_FirstRun.sh
index 11d478d39..7ece49674 100755
--- a/Scripts/RMS_FirstRun.sh
+++ b/Scripts/RMS_FirstRun.sh
@@ -22,22 +22,10 @@ RMSSTARTCAPTURE=~/Desktop/RMS_StartCapture.sh
RMSUPDATESCRIPT=~/Desktop/RMS_Update.sh
-# Function for editing the RMS config
-editRMSConfig () {
-
- # Use the first available editor (leafpad on Raspbian Jessie, mousepad on
- # Raspbian Buster and later, gedit/gnome-text-editor on GNOME desktops)
- local editor
- for editor in leafpad mousepad gedit gnome-text-editor nano; do
- if [ $( command -v $editor ) ]; then
-
- # Open the config file
- $editor $RMSCONFIG
- return
-
- fi
- done
-
+# Check that a value is a number within the given range
+isNumberInRange () { # $1 = value, $2 = min, $3 = max
+ [[ "$1" =~ ^-?[0-9]+(\.[0-9]+)?$ ]] || return 1
+ awk -v v="$1" -v lo="$2" -v hi="$3" 'BEGIN { exit !(v >= lo && v <= hi) }'
}
@@ -116,7 +104,7 @@ echo " 0. Expand the file system (if you flashed this SD card yourself)."
echo " 1. Connect your Pi to the Internet. "
echo " 2. Change the default password for security reasons."
echo " 3. Generate a new SSH key."
-echo " 4. Edit the RMS config file. "
+echo " 4. Enter the station ID and the geo coordinates of your camera."
echo " 5. Convert to the multi-camera data structure (recommended)."
echo ""
echo "At the end of these steps the first data capture session will start."
@@ -215,46 +203,81 @@ echo "Updating to the latest version of RMS..."
bash $RMSUPDATESCRIPT
echo ""
-echo "4) Editing the configuration file"
-echo "---------------------------------"
-echo "Finally, edit the RMS configuration file."
-echo "Put in the station code/ID and the geo coordinates of your camera, save the"
-echo "file and you are good to go!"
+echo "4) Station configuration"
+echo "------------------------"
+echo "Enter the station ID and the geo coordinates of your camera."
echo ""
echo "
-If you need to make changes to the configuration file in the future, you can
-find a shortcut to it on desktop (RMS_config), or open it directly in
-$RMSCONFIG.
+If you need to change any other setting in the future, you can find a
+shortcut to the configuration file on desktop (RMS_config), or open it
+directly in $RMSCONFIG.
"
-read -p "The configuration file will open for editing after you press ENTER..."
-
-
-editRMSConfig
-
-
-echo ""
+while true; do
+ # Station ID
+ while true; do
+ read -p "Station ID (e.g. US01AB): " statID || exit 1
+ statID=$(echo "$statID" | xargs | tr '[:lower:]' '[:upper:]')
+ if [[ "$statID" =~ ^[A-Z]{2}[A-Z0-9]{4}$ && "$statID" != "XX0001" ]]; then
+ break
+ fi
+ echo " A station code has 2 country letters followed by 4 characters, e.g. US01AB."
+ if [[ -n "$statID" ]]; then
+ read -n1 -r -p " Use '$statID' anyway? Press Y to accept it, any other key to retype... " key
+ echo ""
+ if [[ "$key" = "y" || "$key" = "Y" ]]; then
+ break
+ fi
+ fi
+ done
+ # Latitude
+ while true; do
+ read -p "Latitude (+N, in degrees, e.g. 40.689298): " lat || exit 1
+ lat=$(echo "$lat" | xargs)
+ isNumberInRange "$lat" -90 90 && break
+ echo " The latitude must be a number between -90 and 90."
+ done
-while true; do
+ # Longitude
+ while true; do
+ read -p "Longitude (+E, in degrees, NEGATIVE in the western hemisphere, e.g. -74.044479): " lon || exit 1
+ lon=$(echo "$lon" | xargs)
+ isNumberInRange "$lon" -180 180 && break
+ echo " The longitude must be a number between -180 and 180."
+ done
-# Check if the config file was changed
-statID=$(grep stationID $RMSCONFIG | cut -d ":" -f 2 | xargs)
+ # Elevation
+ while true; do
+ read -p "Elevation (mean sea level, in meters, NOT feet): " elev || exit 1
+ elev=$(echo "$elev" | xargs)
+ isNumberInRange "$elev" -500 9000 && break
+ echo " The elevation must be a number in meters, e.g. 95.3."
+ done
+ echo "
+Station ID: $statID
+Latitude: $lat
+Longitude: $lon
+Elevation: $elev m
+"
+ read -n1 -r -p 'Press ENTER to save these values, or R to re-enter them... ' key
+ echo ""
+ if [[ "$key" = "" ]]; then
+ break
+ fi
-if [ "$statID" = "XX0001" ]; then
-
- echo "The config file was not changed!"
- echo "Please change the station ID and the geo coordinates in the config file!"
+done
- editRMSConfig
+# Write the values into the config file
+sed -i "s/^stationID:.*$/stationID: $statID/" $RMSCONFIG
+sed -i "s/^latitude:.*$/latitude: $lat/" $RMSCONFIG
+sed -i "s/^longitude:.*$/longitude: $lon/" $RMSCONFIG
+sed -i "s/^elevation:.*$/elevation: $elev/" $RMSCONFIG
-else
- break
-fi
-done
+echo "Configuration saved to $RMSCONFIG"
# Offer the conversion to the multi-camera data structure, unless the
# system already uses it
From d26f89d2e18a7542b36c5ac8726ae958fce404b8 Mon Sep 17 00:00:00 2001
From: Luc Busquin <133058544+Cybis320@users.noreply.github.com>
Date: Wed, 10 Jun 2026 00:51:22 -0700
Subject: [PATCH 05/17] Clean up inherited quirks in the first-run and capture
scripts
- RMS_FirstRun no longer overwrites an existing SSH key: the public half
is already on file with GMN, so regenerating it on a re-run would
silently break the data uploads. The Desktop link uses ln -sf so a
re-run does not error either
- The successful autorun path exits 0 instead of 1
- The updater is invoked directly from Scripts/ instead of through the
Desktop symlink (the StartCapture symlink stays, it is retargeted by
add_Station.sh on purpose)
- The internet check falls back to an HTTP probe of github.com when
ping is blocked, since GitHub is what the update needs to reach anyway
- The init message no longer promises 10 seconds while sleeping 12
- Prompts that accepted any key to skip no longer claim Q is special
- Removed the unused delay_by_station_sequence() from StartCapture.sh
---
Scripts/MultiCamLinux/StartCapture.sh | 13 --------
Scripts/MultiCamLinux/add_Station.sh | 2 +-
Scripts/RMS_FirstRun.sh | 44 ++++++++++++++++++---------
3 files changed, 30 insertions(+), 29 deletions(-)
diff --git a/Scripts/MultiCamLinux/StartCapture.sh b/Scripts/MultiCamLinux/StartCapture.sh
index 066c5730c..2f34bdf34 100755
--- a/Scripts/MultiCamLinux/StartCapture.sh
+++ b/Scripts/MultiCamLinux/StartCapture.sh
@@ -25,19 +25,6 @@
# second command line arg is optional, can be used to adjust the sleep time in seconds
# so that there can be a delay between starting each station
-delay_by_station_sequence() {
-
-# implement a delay based on position of station in directory
-# this is not used presently
-
-delay=$(ls /home/$(whoami)/source/Stations/ | grep -n $1 | cut -d':' -f1)
-delay=$((delay*30))
-echo Additional $delay second delay will be added.
-sleep $delay
-
-}
-
-
if [[ -z "$1" ]] # called with no args
then
echo " No Station directory specified, quitting now"
diff --git a/Scripts/MultiCamLinux/add_Station.sh b/Scripts/MultiCamLinux/add_Station.sh
index 18917c376..a7ba27763 100755
--- a/Scripts/MultiCamLinux/add_Station.sh
+++ b/Scripts/MultiCamLinux/add_Station.sh
@@ -165,7 +165,7 @@ to
${RMS_data}/${DefStation}
EOF
- read -n1 -r -p 'Press ENTER to continue with the relocation, or Q to skip it: ' key
+ read -n1 -r -p 'Press ENTER to continue with the relocation, or any other key to skip it: ' key
echo ""
if [[ "$key" != "" ]]; then
echo "Skipping the relocation"
diff --git a/Scripts/RMS_FirstRun.sh b/Scripts/RMS_FirstRun.sh
index 7ece49674..2cca72936 100755
--- a/Scripts/RMS_FirstRun.sh
+++ b/Scripts/RMS_FirstRun.sh
@@ -1,6 +1,6 @@
#!/bin/bash
-echo "Waiting 10 seconds to init the Pi..."
+echo "Waiting a few seconds for the system to initialize..."
echo ""
echo "IMPORTANT: RMS will first update itself."
echo "Do not touch any file during the update and do not close this window."
@@ -18,8 +18,10 @@ RMSCONFIG=~/source/RMS/.config
# Auto run enable flag file
RMSAUTORUNFILE=~/.rmsautorunflag
+# The StartCapture Desktop entry is a symlink that add_Station.sh retargets
+# to the multi-camera launcher after a conversion, so it is used on purpose
RMSSTARTCAPTURE=~/Desktop/RMS_StartCapture.sh
-RMSUPDATESCRIPT=~/Desktop/RMS_Update.sh
+RMSUPDATESCRIPT=~/source/RMS/Scripts/RMS_Update.sh
# Check that a value is a number within the given range
@@ -45,7 +47,7 @@ else
# If the configuration was done, run recording
bash $RMSSTARTCAPTURE
- exit 1
+ exit 0
fi
fi
@@ -117,7 +119,7 @@ echo "
0) Expanding the file system
----------------------------
If you have bought a system that was already assembled, or the file system
-has already been expanded, PRESS Q to skip this step.
+has already been expanded, press any key to skip this step.
"
read -n1 -r -p 'If you have flashed this SD card yourself, press ENTER.' key
@@ -152,11 +154,13 @@ echo ""
echo "1) Internet connection"
echo "----------------------"
echo "Checking if connected to the Internet..."
-if ping -c 1 $IP &> /dev/null
+# Some networks block ping, so fall back to an HTTP check of the server
+# the update needs to reach anyway
+if ping -c 1 $IP &> /dev/null || wget -q --spider --timeout=10 https://github.com
then
echo "Success!"
else
- echo "The device is not connected to the internet! Please connect the device to the Internet to proceed!"
+ echo "The device is not connected to the internet! Please connect the device to the Internet to proceed!"
read -p "Press ENTER to continue..."
exit 1
fi
@@ -167,7 +171,7 @@ echo "--------------------------------"
echo "The default password is either 'raspberry' or 'rmsraspberry'. Please change it so nobody can connect to your Raspberry Pi and hack the computers on your network!"
echo ""
-read -n1 -r -p 'Press ENTER to change the password (recommended), or Q to skip this step...' key
+read -n1 -r -p 'Press ENTER to change the password (recommended), or any other key to skip this step...' key
if [[ "$key" = "" ]]; then
passwd
@@ -178,17 +182,27 @@ echo ""
echo "3) Generating a new SSH key"
echo "---------------------------"
-read -n1 -r -p 'Press ENTER to generate the SSH key (recommended), or Q to skip this step...' key
+read -n1 -r -p 'Press ENTER to generate the SSH key (recommended), or any other key to skip this step...' key
if [[ "$key" = "" ]]; then
- echo ""
- echo "Generating a new SSH key..."
- # Generate an SSH key without a passphrase
- yes y | ssh-keygen -t rsa -m PEM -N "" -f ~/.ssh/id_rsa >/dev/null
+ if [[ -f ~/.ssh/id_rsa ]]; then
+
+ # Never overwrite an existing key - GMN already has its public half on
+ # file, and replacing it would break the data uploads
+ echo ""
+ echo "An SSH key already exists in ~/.ssh, not overwriting it."
+
+ else
+ echo ""
+ echo "Generating a new SSH key..."
+
+ # Generate an SSH key without a passphrase
+ ssh-keygen -t rsa -m PEM -N "" -f ~/.ssh/id_rsa >/dev/null
+ fi
# Link the public SSH key to desktop
- ln -s ~/.ssh/id_rsa.pub ~/Desktop/id_rsa.pub
+ ln -sf ~/.ssh/id_rsa.pub ~/Desktop/id_rsa.pub
echo ""
echo "A file called id_rsa.pub appeared on Desktop, please send this file to Denis "
@@ -300,12 +314,12 @@ sleep 1
# clear the input buffer
while read -t 0.01; do :; done
-read -n1 -r -p 'Press ENTER to convert to the new data structure, or Q to stay with the legacy structure... ' key
+read -n1 -r -p 'Press ENTER to convert to the new data structure, or any other key to stay with the legacy structure... ' key
if [[ "$key" != "" ]]; then
echo ""
echo ""
echo "Are you sure you want to stay with the legacy structure?"
- read -n1 -r -p 'Press Q again to confirm, or ENTER to convert to the new data structure... ' key
+ read -n1 -r -p 'Press any key to confirm, or ENTER to convert to the new data structure... ' key
echo ""
fi
From 78f5893eff15bbfa0ca5b163928674187ad7a0e1 Mon Sep 17 00:00:00 2001
From: Luc Busquin <133058544+Cybis320@users.noreply.github.com>
Date: Wed, 10 Jun 2026 00:55:15 -0700
Subject: [PATCH 06/17] Add stations_template.csv as a sample for bulk station
creation
The template shows the CSV format add_Station.sh reads: station_id,
camera_ip, and any .config key as further columns (capture_wait_seconds
here, which staggers the capture start across cameras). Copy it to
~/source/RMS/stations.csv (gitignored) and edit. The script's usage and
error messages point to it.
---
Scripts/MultiCamLinux/add_Station.sh | 4 +++-
stations_template.csv | 7 +++++++
2 files changed, 10 insertions(+), 1 deletion(-)
create mode 100644 stations_template.csv
diff --git a/Scripts/MultiCamLinux/add_Station.sh b/Scripts/MultiCamLinux/add_Station.sh
index a7ba27763..e9a303fc1 100755
--- a/Scripts/MultiCamLinux/add_Station.sh
+++ b/Scripts/MultiCamLinux/add_Station.sh
@@ -31,7 +31,8 @@
# (spliced into the rtsp device URL), and any other .config key, which is
# applied verbatim to that station's config. With no CSV argument the
# script uses ~/source/RMS/stations.csv if present, otherwise it prompts
-# for station IDs interactively.
+# for station IDs interactively. A sample to copy and edit is provided in
+# ~/source/RMS/stations_template.csv.
#
# On Raspberry Pi platforms a recommended camera limit applies (1 camera,
# or 4 on a Pi5 or later). The limit is advisory - it reflects what has
@@ -243,6 +244,7 @@ fi
if [[ -n "${CSV_FILE}" && ! -f "${CSV_FILE}" ]]; then
echo "Error: CSV file not found: ${CSV_FILE}"
echo "Usage: $0 [RMS_data_path] [stations.csv]"
+ echo "A sample to copy and edit is provided in ~/source/RMS/stations_template.csv"
exit 1
fi
diff --git a/stations_template.csv b/stations_template.csv
new file mode 100644
index 000000000..24345f151
--- /dev/null
+++ b/stations_template.csv
@@ -0,0 +1,7 @@
+station_id,camera_ip,capture_wait_seconds
+XX0001,192.168.42.101,0
+XX0002,192.168.42.102,30
+XX0003,192.168.42.103,60
+XX0004,192.168.42.104,90
+XX0005,192.168.42.105,120
+XX0006,192.168.42.106,150
From 919061204c43d87a636bc2fb33a4c4a8d7c86bad Mon Sep 17 00:00:00 2001
From: Luc Busquin <133058544+Cybis320@users.noreply.github.com>
Date: Wed, 10 Jun 2026 01:04:18 -0700
Subject: [PATCH 07/17] Fix capture startup for FirstRun conversions on PCs
The XDG-autostart-vs-Desktop-entry choice keyed on the autorun flag
alone, but a PC that converts through RMS_FirstRun also carries that
flag - and nothing launches FirstRun at boot on a PC, so such systems
ended up with no automatic capture start at all. The Pi-image behavior
(Desktop entry only, MCP starts captures at boot) now additionally
requires Pi hardware; everything else gets autostart entries.
The Desktop RMS_StartCapture.sh symlink is retargeted to the
multi-camera launcher unconditionally - after any conversion the legacy
script it pointed at would read the reset template config.
Also create ~/.config/autostart and the Desktop dir inside
create_station: the migration path runs before the main-flow mkdirs, so
the autostart entry write could fail silently.
---
Scripts/MultiCamLinux/add_Station.sh | 25 ++++++++++++++-----------
1 file changed, 14 insertions(+), 11 deletions(-)
diff --git a/Scripts/MultiCamLinux/add_Station.sh b/Scripts/MultiCamLinux/add_Station.sh
index e9a303fc1..5f305cb7a 100755
--- a/Scripts/MultiCamLinux/add_Station.sh
+++ b/Scripts/MultiCamLinux/add_Station.sh
@@ -89,6 +89,7 @@ create_station() {
echo "Creating station ${item}..."
mkdir -p ~/source/Stations/${item}
mkdir -p "${RMS_data}/${item}"
+ mkdir -p ~/.config/autostart "${Desktop}"
# Copy config template
cp ~/source/RMS/.config ~/source/Stations/${item}
@@ -96,11 +97,13 @@ create_station() {
# Copy mask file
cp ~/source/RMS/mask.bmp ~/source/Stations/${item} 2>/dev/null
- # Launch entry for the station. On FirstRun-managed systems (the Pi
- # images) captures are started by RMS_StartCapture_MCP.sh, so only a
- # Desktop launcher is created; elsewhere the entry goes into XDG
- # autostart with a Desktop symlink, so captures start at login.
- if [[ -f ~/.rmsautorunflag ]]; then
+ # Launch entry for the station. On the Pi images, FirstRun runs at boot
+ # and starts captures through RMS_StartCapture_MCP.sh, so only a Desktop
+ # launcher is created there; elsewhere the entry goes into XDG autostart
+ # with a Desktop symlink, so captures start at login. (A PC may carry the
+ # autorun flag from running FirstRun, but nothing launches FirstRun at
+ # boot on a PC, hence the additional Pi check.)
+ if [[ -f ~/.rmsautorunflag && $RECOMMENDED -gt 0 ]]; then
entry="${Desktop}/${item}_StartCapture.desktop"
else
entry=~/.config/autostart/${item}_StartCapture.desktop
@@ -438,12 +441,12 @@ if [[ $total -gt 1 ]]; then
fi
fi
-# On FirstRun-managed systems (the Pi images) captures are started by
-# RMS_StartCapture_MCP.sh after the boot-time update
-if [[ -f ~/.rmsautorunflag ]]; then
- rm -f "${Desktop}/RMS_StartCapture.sh"
- ln -s ~/source/RMS/Scripts/MultiCamLinux/Pi/RMS_StartCapture_MCP.sh "${Desktop}/RMS_StartCapture.sh"
-fi
+# Point the Desktop StartCapture entry at the multi-camera launcher, which
+# starts every configured station. The legacy entry it replaces reads
+# ~/source/RMS/.config, which after a conversion is just the template.
+# On the Pi images FirstRun also runs this launcher at boot.
+rm -f "${Desktop}/RMS_StartCapture.sh"
+ln -sf ~/source/RMS/Scripts/MultiCamLinux/Pi/RMS_StartCapture_MCP.sh "${Desktop}/RMS_StartCapture.sh"
# Generate SSH keys if not present
if [[ ! -f ~/.ssh/id_rsa ]]; then
From 02f77aa4c9b715c35ae1d33c4f1979b303763c5f Mon Sep 17 00:00:00 2001
From: Luc Busquin <133058544+Cybis320@users.noreply.github.com>
Date: Wed, 10 Jun 2026 01:12:59 -0700
Subject: [PATCH 08/17] Move add_Station.sh up to Scripts/
A universal tool does not belong in the MultiCamLinux subdirectory; it
now sits next to RMS_Installer.sh and RMS_FirstRun.sh. The runtime
launchers (StartCapture.sh, LiveStream.sh, RMS_StartCapture_MCP.sh)
stay in MultiCamLinux/ because desktop entries on deployed systems
reference those paths. Compatibility wrappers and FirstRun updated.
---
Scripts/MultiCamLinux/Pi/add_Pi_Station.sh | 2 +-
Scripts/MultiCamLinux/add_GStation.sh | 2 +-
Scripts/RMS_FirstRun.sh | 6 +++---
Scripts/{MultiCamLinux => }/add_Station.sh | 0
4 files changed, 5 insertions(+), 5 deletions(-)
rename Scripts/{MultiCamLinux => }/add_Station.sh (100%)
diff --git a/Scripts/MultiCamLinux/Pi/add_Pi_Station.sh b/Scripts/MultiCamLinux/Pi/add_Pi_Station.sh
index 9e2e7180c..3670ebe85 100755
--- a/Scripts/MultiCamLinux/Pi/add_Pi_Station.sh
+++ b/Scripts/MultiCamLinux/Pi/add_Pi_Station.sh
@@ -1,4 +1,4 @@
#!/bin/bash
# Kept for compatibility with existing documentation and older images.
# The station tooling is now universal - see add_Station.sh.
-exec "$(dirname "$0")/../add_Station.sh" "$@"
+exec "$(dirname "$0")/../../add_Station.sh" "$@"
diff --git a/Scripts/MultiCamLinux/add_GStation.sh b/Scripts/MultiCamLinux/add_GStation.sh
index 1ed202c0a..03cbb7674 100755
--- a/Scripts/MultiCamLinux/add_GStation.sh
+++ b/Scripts/MultiCamLinux/add_GStation.sh
@@ -1,4 +1,4 @@
#!/bin/bash
# Kept for compatibility with existing documentation.
# The station tooling is now universal - see add_Station.sh.
-exec "$(dirname "$0")/add_Station.sh" "$@"
+exec "$(dirname "$0")/../add_Station.sh" "$@"
diff --git a/Scripts/RMS_FirstRun.sh b/Scripts/RMS_FirstRun.sh
index 2cca72936..2024af498 100755
--- a/Scripts/RMS_FirstRun.sh
+++ b/Scripts/RMS_FirstRun.sh
@@ -327,17 +327,17 @@ if [[ "$key" = "" ]]; then
echo ""
echo "Next, this script will run to convert to the recommended data structure"
echo "(it will show the details and ask you to confirm):"
- echo " ~/source/RMS/Scripts/MultiCamLinux/add_Station.sh"
+ echo " ~/source/RMS/Scripts/add_Station.sh"
echo ""
echo "Note: you can run the same script later to add more cameras."
echo ""
sleep 2
- bash ~/source/RMS/Scripts/MultiCamLinux/add_Station.sh
+ bash ~/source/RMS/Scripts/add_Station.sh
else
echo ""
echo "You have chosen to keep the legacy data structure."
echo "If you decide to convert in the future, run this command from a terminal:"
- echo " ~/source/RMS/Scripts/MultiCamLinux/add_Station.sh"
+ echo " ~/source/RMS/Scripts/add_Station.sh"
fi
fi # if no ~/source/Stations yet
diff --git a/Scripts/MultiCamLinux/add_Station.sh b/Scripts/add_Station.sh
similarity index 100%
rename from Scripts/MultiCamLinux/add_Station.sh
rename to Scripts/add_Station.sh
From b679a42d95b770e35c8ede427213703b4a84b670 Mon Sep 17 00:00:00 2001
From: Luc Busquin <133058544+Cybis320@users.noreply.github.com>
Date: Wed, 10 Jun 2026 01:16:50 -0700
Subject: [PATCH 09/17] Update Scripts/README.md for the universal station
tooling
---
Scripts/README.md | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/Scripts/README.md b/Scripts/README.md
index ecd4c6308..1cda662fb 100644
--- a/Scripts/README.md
+++ b/Scripts/README.md
@@ -1 +1,6 @@
-These scripts are usually kept on the desktop of the Pi and assume that RMS is installed in ~/source/RMS.
+Helper scripts for running a meteor station. All of them assume that RMS is installed in `~/source/RMS`.
+
+- Most scripts here (`RMS_FirstRun.sh`, `RMS_StartCapture.sh`, `RMS_Update.sh`, ...) are linked on the desktop of a station and run from there.
+- `RMS_Installer.sh` installs RMS and everything it needs on a generic Linux machine (Ubuntu LTS or Debian).
+- `add_Station.sh` creates camera stations under `~/source/Stations/` — interactively, or in bulk from a CSV file (see `stations_template.csv` in the repository root). It also relocates a legacy single-camera setup into that layout. Works on any Linux platform, Raspberry Pi included.
+- `MultiCamLinux/` holds the per-station runtime launchers (`StartCapture.sh`, `LiveStream.sh`, `Pi/RMS_StartCapture_MCP.sh`) plus compatibility wrappers for the older `add_GStation.sh` and `add_Pi_Station.sh` entry points.
From 65e1889155bf2ad0fe7b9c39b8d9ed95ead4053c Mon Sep 17 00:00:00 2001
From: Luc Busquin <133058544+Cybis320@users.noreply.github.com>
Date: Wed, 10 Jun 2026 01:32:47 -0700
Subject: [PATCH 10/17] Make the conversion copy-only: add_Station never
modifies ~/source/RMS
The legacy conversion used to mutate the repo working tree: it deleted
the root .config and re-downloaded it from GitHub (leaving no config at
all when offline), moved mask.bmp and platepar out, and the
camera_settings.json move had been proposed too. None of that is the
station tool's business - RMS_Update already owns the lifecycle of the
root .config/mask.bmp/camera_settings.json (backup, reset, restore),
and camera_settings.json is shared by all stations from the root by
default (every station's camera_settings_path points there).
The conversion now only COPIES .config, mask.bmp and platepar into
~/source/Stations/ and moves the captured data under
~/RMS_data/. Nothing in ~/source/RMS is created, modified or
deleted - no git commands, no downloads. The root files keep the
operator's values and remain the templates for adding further
stations.
---
Scripts/README.md | 2 +-
Scripts/RMS_FirstRun.sh | 8 +++---
Scripts/add_Station.sh | 58 ++++++++++++-----------------------------
3 files changed, 21 insertions(+), 47 deletions(-)
diff --git a/Scripts/README.md b/Scripts/README.md
index 1cda662fb..fc7b27a25 100644
--- a/Scripts/README.md
+++ b/Scripts/README.md
@@ -2,5 +2,5 @@ Helper scripts for running a meteor station. All of them assume that RMS is inst
- Most scripts here (`RMS_FirstRun.sh`, `RMS_StartCapture.sh`, `RMS_Update.sh`, ...) are linked on the desktop of a station and run from there.
- `RMS_Installer.sh` installs RMS and everything it needs on a generic Linux machine (Ubuntu LTS or Debian).
-- `add_Station.sh` creates camera stations under `~/source/Stations/` — interactively, or in bulk from a CSV file (see `stations_template.csv` in the repository root). It also relocates a legacy single-camera setup into that layout. Works on any Linux platform, Raspberry Pi included.
+- `add_Station.sh` creates camera stations under `~/source/Stations/` — interactively, or in bulk from a CSV file (see `stations_template.csv` in the repository root). It also converts a legacy single-camera setup to that layout (by copying — nothing in the RMS root is modified). Works on any Linux platform, Raspberry Pi included.
- `MultiCamLinux/` holds the per-station runtime launchers (`StartCapture.sh`, `LiveStream.sh`, `Pi/RMS_StartCapture_MCP.sh`) plus compatibility wrappers for the older `add_GStation.sh` and `add_Pi_Station.sh` entry points.
diff --git a/Scripts/RMS_FirstRun.sh b/Scripts/RMS_FirstRun.sh
index 2024af498..aa991ee87 100755
--- a/Scripts/RMS_FirstRun.sh
+++ b/Scripts/RMS_FirstRun.sh
@@ -304,10 +304,10 @@ The new recommended data structure keeps important camera files in a safer
place. Converting to this multi-camera data structure is recommended for
both single and multiple camera systems.
-The important files (.config, mask.bmp, platepar_cmn2010.cal and
-camera_settings.json) will be moved to ~/source/Stations/,
-and captured data will be stored in ~/RMS_data/, where
- is the unique station code you were given by GMN.
+The important files (.config, mask.bmp and platepar_cmn2010.cal) will be
+copied to ~/source/Stations/, and captured data will be stored
+in ~/RMS_data/, where is the unique station code
+you were given by GMN. Nothing in ~/source/RMS is modified.
"
sleep 1
diff --git a/Scripts/add_Station.sh b/Scripts/add_Station.sh
index 5f305cb7a..fbc9ccfa5 100755
--- a/Scripts/add_Station.sh
+++ b/Scripts/add_Station.sh
@@ -20,7 +20,7 @@
# compatibility wrappers around this script.
#
# What it does:
-# - relocates a legacy single-camera setup (config files in ~/source/RMS,
+# - converts a legacy single-camera setup (config files in ~/source/RMS,
# data directly in ~/RMS_data) into the per-station layout under
# ~/source/Stations/ and ~/RMS_data/
# - creates new stations, either interactively or in bulk from a CSV file
@@ -52,13 +52,6 @@ uppercase() {
printf '%s' "$*" | tr '[:lower:]' '[:upper:]'
}
-# Restore a pristine copy of a file in ~/source/RMS from git, or from
-# GitHub when ~/source/RMS is not a git checkout
-restore_default() {
- ( cd ~/source/RMS && git checkout -- "$1" 2>/dev/null ) || \
- wget -q -O ~/source/RMS/"$1" "https://raw.githubusercontent.com/CroatianMeteorNetwork/RMS/master/$1"
-}
-
count_stations() {
find ~/source/Stations -mindepth 1 -maxdepth 1 -type d 2>/dev/null | wc -l
}
@@ -146,22 +139,22 @@ create_station() {
return 0
}
-# Relocate a legacy single-camera setup into the per-station layout
+# Convert a legacy single-camera setup to the per-station layout by copying
migrate_legacy() {
cat <.
+
+Your configured station ${DefStation} will get copies of:
- .config
- platepar_cmn2010.cal
- mask.bmp
-- camera_settings.json
-from their default location of ~/source/RMS to the folder
-~/source/Stations/${DefStation}
+in ~/source/Stations/${DefStation}
-You have already configured your first station as id ${DefStation}
-Its camera specific files will now be relocated to
-~/source/Stations/${DefStation}
+Nothing in ~/source/RMS is modified, and camera_settings.json is shared
+by all stations from there.
Any previously captured data will be moved from
${RMS_data}
@@ -169,30 +162,20 @@ to
${RMS_data}/${DefStation}
EOF
- read -n1 -r -p 'Press ENTER to continue with the relocation, or any other key to skip it: ' key
+ read -n1 -r -p 'Press ENTER to continue with the conversion, or any other key to skip it: ' key
echo ""
if [[ "$key" != "" ]]; then
- echo "Skipping the relocation"
+ echo "Skipping the conversion"
return
fi
create_station "${DefStation}" || return
+ # the .config and mask copies are made by create_station
if [[ -e ~/source/RMS/platepar_cmn2010.cal ]]; then
- mv ~/source/RMS/platepar_cmn2010.cal ~/source/Stations/${DefStation}/
- fi
- if [[ -e ~/source/RMS/camera_settings.json ]]; then
- mv ~/source/RMS/camera_settings.json ~/source/Stations/${DefStation}/
- # point the station .config at the relocated camera settings file
- sed -i "s,^camera_settings_path:.*$,camera_settings_path: ~/source/Stations/${DefStation}/camera_settings.json,g" ~/source/Stations/${DefStation}/.config
+ cp ~/source/RMS/platepar_cmn2010.cal ~/source/Stations/${DefStation}/
fi
- # restore pristine default copies in ~/source/RMS, these are used as
- # templates when additional stations are added (the user's .config is
- # restored at the end, so stations added in this run inherit it)
- restore_default mask.bmp
- restore_default camera_settings.json
-
# move any existing captured data into the station folder
find "${RMS_data}" -mindepth 1 -maxdepth 1 ! -name "${DefStation}" -exec mv -t "${RMS_data}/${DefStation}/" {} +
@@ -210,9 +193,8 @@ EOF
rm -f ~/Desktop/TunnelIPCamera.sh ~/Desktop/DownloadOpenVPNconfig.sh
fi
- MIGRATED=1
echo ""
- echo "Relocation of station ${DefStation} complete"
+ echo "Conversion of station ${DefStation} complete"
}
# ------------------------------------------------------------------ main
@@ -284,15 +266,14 @@ fi
# Get user's desktop directory
Desktop=$(xdg-user-dir DESKTOP 2>/dev/null || echo "$HOME/Desktop")
-MIGRATED=0
DefStation=$(uppercase "$(awk '/^stationID:/ {print $2}' ~/source/RMS/.config 2>/dev/null)")
-# A configured legacy single-camera setup is offered relocation first
+# A configured legacy single-camera setup is offered conversion first
if [[ ! -d ~/source/Stations && -n "$DefStation" && "$DefStation" != "XX0001" ]]; then
migrate_legacy
elif [[ -f ~/.rmsautorunflag && "$DefStation" == "XX0001" && ! -d ~/source/Stations ]]; then
echo "Please run RMS_FirstRun and configure your 1st station,"
- echo "then run add_Station again to relocate it or add more cameras."
+ echo "then run add_Station again to convert it or add more cameras."
exit
fi
@@ -516,11 +497,4 @@ sleep 2
EOF
chmod +x "${Desktop}/Edit_configs"
-# After a relocation, replace the now station-specific .config in
-# ~/source/RMS with a pristine default. This happens last so stations
-# added in the same run used the configured one as their template.
-if [[ $MIGRATED -eq 1 ]]; then
- restore_default .config
-fi
-
echo -e "\nStation configuration complete"
From 989372201f58e0c3c69f03320b2796dc53be8191 Mon Sep 17 00:00:00 2001
From: Luc Busquin <133058544+Cybis320@users.noreply.github.com>
Date: Tue, 28 Jul 2026 11:02:17 -0700
Subject: [PATCH 11/17] Auto-detect and expand an undersized root file system
in RMS_FirstRun
Instead of walking the user through the raspi-config menus, measure the
root file system against the device it lives on and offer a one-key
expansion (raspi-config --expand-rootfs) followed by an automatic
reboot. If the device is already fully used but below the 64 GB minimum,
warn about it instead of offering an expansion that would do nothing.
Based on Peter E.'s revised first-run proposal.
---
Scripts/RMS_FirstRun.sh | 68 ++++++++++++++++++++++++++++-------------
1 file changed, 47 insertions(+), 21 deletions(-)
diff --git a/Scripts/RMS_FirstRun.sh b/Scripts/RMS_FirstRun.sh
index aa991ee87..5af19a261 100755
--- a/Scripts/RMS_FirstRun.sh
+++ b/Scripts/RMS_FirstRun.sh
@@ -102,7 +102,7 @@ read -p ""
echo ""
echo "This is a brief overview of this guide:"
-echo " 0. Expand the file system (if you flashed this SD card yourself)."
+echo " 0. Expand the file system (checked automatically)."
echo " 1. Connect your Pi to the Internet. "
echo " 2. Change the default password for security reasons."
echo " 3. Generate a new SSH key."
@@ -118,31 +118,57 @@ if command -v raspi-config >/dev/null 2>&1; then
echo "
0) Expanding the file system
----------------------------
-If you have bought a system that was already assembled, or the file system
-has already been expanded, press any key to skip this step.
-"
+Checking if the file system needs to be expanded..."
+
+# Compare the root file system to the device it lives on to see if there
+# is unallocated space left - a freshly flashed image is much smaller
+# than the SD card it was written to
+fs_gb=$(( $(df --output=size -B1 / | tail -1) / 1000000000 ))
+root_part=$(findmnt -n -o SOURCE / 2>/dev/null)
+root_dev=$(lsblk -n -o PKNAME "$root_part" 2>/dev/null | head -1)
+if [[ -n "$root_dev" ]]; then
+ dev_gb=$(( $(lsblk -b -d -n -o SIZE "/dev/$root_dev") / 1000000000 ))
+else
+ dev_gb=$fs_gb
+fi
-read -n1 -r -p 'If you have flashed this SD card yourself, press ENTER.' key
+# clear the input buffer
+while read -t 0.01; do :; done
-if [[ "$key" = "" ]]; then
- lxterminal -e "sudo raspi-config"
+if (( dev_gb - fs_gb >= 2 )); then
echo "
- Another window has opened where you can expand the file system.
-
- Go to:
-
- 7 ADVANCED OPTIONS -->
- A1 EXPAND FILE SYSTEM -->
- < OK >
-
- < FINISH > (at the bottom) -->
- < YES > (reboot)
-
- Your Raspberry Pi will reboot.
- "
+The file system takes up ${fs_gb} GB of the ${dev_gb} GB storage device.
+RMS needs the whole device for data capture: 64 GB at minimum, 128 GB or
+more is recommended. The usable size is always a bit smaller than the
+rated size, e.g. a 64 GB card gives about 62 GB.
+"
+ read -n1 -r -p 'Press ENTER to expand the file system to the full device (recommended), or any other key to skip this step...' key
+
+ if [[ "$key" = "" ]]; then
+ echo ""
+ echo "Expanding the file system, then the system will reboot."
+ echo "This guide will start again after the reboot."
+ sudo raspi-config --expand-rootfs
+ echo "Rebooting in 5 seconds, please wait..."
+ sleep 5
+ sudo reboot
+ exit 0
+ else
+ echo ""
+ echo "The file system was not expanded, so part of the storage device"
+ echo "will stay unused and there may not be enough room for data capture!"
+ fi
- read -p "Press ENTER to continue..."
+elif (( fs_gb < 61 )); then
+ echo ""
+ echo "The whole ${dev_gb} GB storage device is already in use, but it is smaller"
+ echo "than the 64 GB minimum RMS needs for data capture (128 GB or more is"
+ echo "recommended). Please consider moving to a larger storage device."
+ read -p "Press ENTER to continue anyway..."
+else
+ echo ""
+ echo "${fs_gb} GB of space is available, no expansion needed."
fi
fi # raspi-config available
From a1d321ef37625863757b7e49f389e3e83c245c2e Mon Sep 17 00:00:00 2001
From: Luc Busquin <133058544+Cybis320@users.noreply.github.com>
Date: Tue, 28 Jul 2026 11:03:21 -0700
Subject: [PATCH 12/17] Make RMS_StartCapture.sh handle both data structures
RMS_StartCapture.sh now detects the multi-camera structure (station
directories under ~/source/Stations) and starts every configured station
itself, staggered as before, falling back to the legacy single-camera
start otherwise. RMS_StartCapture_MCP.sh becomes a compatibility wrapper
for Desktop shortcuts created by older installs, and add_Station.sh no
longer needs to retarget the Desktop entry after a conversion.
Based on Peter E.'s revised proposal (RMS_StartCapture2.sh).
---
.../MultiCamLinux/Pi/RMS_StartCapture_MCP.sh | 44 +---------
Scripts/README.md | 3 +-
Scripts/RMS_FirstRun.sh | 6 +-
Scripts/RMS_StartCapture.sh | 82 +++++++++++++++----
Scripts/add_Station.sh | 11 ++-
5 files changed, 79 insertions(+), 67 deletions(-)
diff --git a/Scripts/MultiCamLinux/Pi/RMS_StartCapture_MCP.sh b/Scripts/MultiCamLinux/Pi/RMS_StartCapture_MCP.sh
index d079de974..a105b5dbc 100755
--- a/Scripts/MultiCamLinux/Pi/RMS_StartCapture_MCP.sh
+++ b/Scripts/MultiCamLinux/Pi/RMS_StartCapture_MCP.sh
@@ -1,42 +1,6 @@
#!/bin/bash
-# Start capture on all configured stations, staggered so concurrent
-# first-start updates cannot collide. On a single camera system there is
-# no such risk, so the long delay is skipped
-
-dircount=$(find ~/source/Stations -mindepth 1 -maxdepth 1 -type d 2>/dev/null | wc -l)
-if [[ $dircount -le 1 ]]; then
- seconds=2
-else
- seconds=70
-fi
-
-# Detect the terminal emulator
-if [[ "${XDG_CURRENT_DESKTOP:-}" == *GNOME* ]] && command -v gnome-terminal >/dev/null 2>&1; then
- TERMINAL=gnome-terminal
-elif command -v lxterminal >/dev/null 2>&1; then
- TERMINAL=lxterminal
-else
- TERMINAL=gnome-terminal
-fi
-
-echo " Starting all configured stations post-update..."
-
-loop=0
-for Dir in ~/source/Stations/*/
- do
- Station=$(basename "$Dir")
- echo " Starting camera ${Station}"
- if [[ "$TERMINAL" == "gnome-terminal" ]]; then
- gnome-terminal --profile=StartCapture --title=${Station} -- bash -c "$HOME/source/RMS/Scripts/MultiCamLinux/StartCapture.sh ${Station}" &
- else
- lxterminal --title=${Station} -e "$HOME/source/RMS/Scripts/MultiCamLinux/StartCapture.sh ${Station}" &
- fi
- echo " waiting $seconds seconds..."
- sleep ${seconds}
- if [[ $loop = 0 ]] ; then
- seconds=10
- fi
- let loop++
- done
-echo " All cameras started"
+# Kept for compatibility with Desktop shortcuts created by older
+# installs - RMS_StartCapture.sh now handles the multi-camera data
+# structure directly
+exec ~/source/RMS/Scripts/RMS_StartCapture.sh "$@"
diff --git a/Scripts/README.md b/Scripts/README.md
index fc7b27a25..baa6d028a 100644
--- a/Scripts/README.md
+++ b/Scripts/README.md
@@ -3,4 +3,5 @@ Helper scripts for running a meteor station. All of them assume that RMS is inst
- Most scripts here (`RMS_FirstRun.sh`, `RMS_StartCapture.sh`, `RMS_Update.sh`, ...) are linked on the desktop of a station and run from there.
- `RMS_Installer.sh` installs RMS and everything it needs on a generic Linux machine (Ubuntu LTS or Debian).
- `add_Station.sh` creates camera stations under `~/source/Stations/` — interactively, or in bulk from a CSV file (see `stations_template.csv` in the repository root). It also converts a legacy single-camera setup to that layout (by copying — nothing in the RMS root is modified). Works on any Linux platform, Raspberry Pi included.
-- `MultiCamLinux/` holds the per-station runtime launchers (`StartCapture.sh`, `LiveStream.sh`, `Pi/RMS_StartCapture_MCP.sh`) plus compatibility wrappers for the older `add_GStation.sh` and `add_Pi_Station.sh` entry points.
+- `RMS_StartCapture.sh` starts capture on either data structure: every station under `~/source/Stations/` in its own terminal, or the single legacy camera if there are none.
+- `MultiCamLinux/` holds the per-station runtime launchers (`StartCapture.sh`, `LiveStream.sh`) plus compatibility wrappers for the older `add_GStation.sh`, `add_Pi_Station.sh` and `Pi/RMS_StartCapture_MCP.sh` entry points.
diff --git a/Scripts/RMS_FirstRun.sh b/Scripts/RMS_FirstRun.sh
index 5af19a261..593be773f 100755
--- a/Scripts/RMS_FirstRun.sh
+++ b/Scripts/RMS_FirstRun.sh
@@ -18,9 +18,9 @@ RMSCONFIG=~/source/RMS/.config
# Auto run enable flag file
RMSAUTORUNFILE=~/.rmsautorunflag
-# The StartCapture Desktop entry is a symlink that add_Station.sh retargets
-# to the multi-camera launcher after a conversion, so it is used on purpose
-RMSSTARTCAPTURE=~/Desktop/RMS_StartCapture.sh
+# RMS_StartCapture.sh handles both the legacy and the multi-camera data
+# structure, so the same launcher is used before and after a conversion
+RMSSTARTCAPTURE=~/source/RMS/Scripts/RMS_StartCapture.sh
RMSUPDATESCRIPT=~/source/RMS/Scripts/RMS_Update.sh
diff --git a/Scripts/RMS_StartCapture.sh b/Scripts/RMS_StartCapture.sh
index 677e6e5b3..b501737a2 100755
--- a/Scripts/RMS_StartCapture.sh
+++ b/Scripts/RMS_StartCapture.sh
@@ -1,26 +1,74 @@
#!/bin/bash
+
+# Starts capture on either supported data structure:
+# - multi-camera (~/source/Stations has station directories): every
+# configured station is started in its own terminal, staggered so
+# concurrent first-start updates cannot collide
+# - legacy: the single camera is started in this terminal
+
echo "Starting RMS..."
sleep 10
-source ~/vRMS/bin/activate
-cd ~/source/RMS
-# Init log file
-LOGPATH=~/RMS_data/logs/
-LOGDATE=$(date +"%Y%m%d_%H%M%S")
-LOGSUFFIX="_log.txt"
-LOGFILE=$LOGPATH$LOGDATE$LOGSUFFIX
+dircount=$(find ~/source/Stations -mindepth 1 -maxdepth 1 -type d 2>/dev/null | wc -l)
+
+if [[ $dircount -gt 0 ]]; then
+
+ # On a single camera system concurrent first-start updates are no
+ # risk, so the long stagger delay is skipped
+ if [[ $dircount -le 1 ]]; then
+ seconds=2
+ else
+ seconds=70
+ fi
+
+ # Detect the terminal emulator
+ if [[ "${XDG_CURRENT_DESKTOP:-}" == *GNOME* ]] && command -v gnome-terminal >/dev/null 2>&1; then
+ TERMINAL=gnome-terminal
+ elif command -v lxterminal >/dev/null 2>&1; then
+ TERMINAL=lxterminal
+ else
+ TERMINAL=gnome-terminal
+ fi
+
+ echo " Starting all configured stations..."
+
+ loop=0
+ for Dir in ~/source/Stations/*/
+ do
+ Station=$(basename "$Dir")
+ echo " Starting camera ${Station}"
+ if [[ "$TERMINAL" == "gnome-terminal" ]]; then
+ gnome-terminal --profile=StartCapture --title=${Station} -- bash -c "$HOME/source/RMS/Scripts/MultiCamLinux/StartCapture.sh ${Station}" &
+ else
+ lxterminal --title=${Station} -e "$HOME/source/RMS/Scripts/MultiCamLinux/StartCapture.sh ${Station}" &
+ fi
+ echo " waiting $seconds seconds..."
+ sleep ${seconds}
+ if [[ $loop = 0 ]] ; then
+ seconds=10
+ fi
+ let loop++
+ done
+ echo " All cameras started"
+
+else
+
+ # Legacy single-camera data structure
+ source ~/vRMS/bin/activate
+ cd ~/source/RMS
-mkdir -p $LOGPATH
+ mkdir -p ~/RMS_data/logs/
-echo ""
-echo ""
-echo "If you need to update the RMS config file, you can do it now."
-echo "Any changes to the config file will be read only after this script is started again or the Pi is rebooted."
-echo ""
-sleep 5
+ echo ""
+ echo ""
+ echo "If you need to update the RMS config file, you can do it now."
+ echo "Any changes to the config file will be read only after this script is started again or the Pi is rebooted."
+ echo ""
+ sleep 5
-python -m RMS.StartCapture "$@"
+ python -m RMS.StartCapture "$@"
-read -p "Press any key to continue... "
+ read -p "Press any key to continue... "
-$SHELL
+ $SHELL
+fi
diff --git a/Scripts/add_Station.sh b/Scripts/add_Station.sh
index fbc9ccfa5..674a511d8 100755
--- a/Scripts/add_Station.sh
+++ b/Scripts/add_Station.sh
@@ -91,7 +91,7 @@ create_station() {
cp ~/source/RMS/mask.bmp ~/source/Stations/${item} 2>/dev/null
# Launch entry for the station. On the Pi images, FirstRun runs at boot
- # and starts captures through RMS_StartCapture_MCP.sh, so only a Desktop
+ # and starts captures through RMS_StartCapture.sh, so only a Desktop
# launcher is created there; elsewhere the entry goes into XDG autostart
# with a Desktop symlink, so captures start at login. (A PC may carry the
# autorun flag from running FirstRun, but nothing launches FirstRun at
@@ -422,12 +422,11 @@ if [[ $total -gt 1 ]]; then
fi
fi
-# Point the Desktop StartCapture entry at the multi-camera launcher, which
-# starts every configured station. The legacy entry it replaces reads
-# ~/source/RMS/.config, which after a conversion is just the template.
+# Make sure the Desktop StartCapture entry points at RMS_StartCapture.sh,
+# which handles both the legacy and the multi-camera data structure (the
+# entry may be a stale copy rather than a symlink on older installs).
# On the Pi images FirstRun also runs this launcher at boot.
-rm -f "${Desktop}/RMS_StartCapture.sh"
-ln -sf ~/source/RMS/Scripts/MultiCamLinux/Pi/RMS_StartCapture_MCP.sh "${Desktop}/RMS_StartCapture.sh"
+ln -sf ~/source/RMS/Scripts/RMS_StartCapture.sh "${Desktop}/RMS_StartCapture.sh"
# Generate SSH keys if not present
if [[ ! -f ~/.ssh/id_rsa ]]; then
From 5d50987ef067d3d32703c5ee6fbb2ea41e9b8bbd Mon Sep 17 00:00:00 2001
From: Luc Busquin <133058544+Cybis320@users.noreply.github.com>
Date: Mon, 3 Aug 2026 13:17:15 -0700
Subject: [PATCH 13/17] Fix the expansion check flagging large cards as
unexpanded forever
---
Scripts/RMS_FirstRun.sh | 86 ++++++++++++++++++++++++++++++++++-------
1 file changed, 72 insertions(+), 14 deletions(-)
diff --git a/Scripts/RMS_FirstRun.sh b/Scripts/RMS_FirstRun.sh
index 593be773f..8e57783fc 100755
--- a/Scripts/RMS_FirstRun.sh
+++ b/Scripts/RMS_FirstRun.sh
@@ -30,6 +30,22 @@ isNumberInRange () { # $1 = value, $2 = min, $3 = max
awk -v v="$1" -v lo="$2" -v hi="$3" 'BEGIN { exit !(v >= lo && v <= hi) }'
}
+# Strip leading/trailing whitespace. Used instead of xargs, which aborts
+# with an error when the input contains a stray quote or backslash
+trimSpaces () {
+ local s="$*"
+ s="${s#"${s%%[![:space:]]*}"}"
+ s="${s%"${s##*[![:space:]]}"}"
+ printf '%s' "$s"
+}
+
+# Discard buffered keypresses (e.g. the tail of an arrow key escape
+# sequence left behind by a single-key read) so they cannot pollute the
+# next typed entry
+flushInput () {
+ while read -t 0.01; do :; done
+}
+
# If the autorun file does not exist, create it and run the configuration
@@ -120,22 +136,36 @@ echo "
----------------------------
Checking if the file system needs to be expanded..."
-# Compare the root file system to the device it lives on to see if there
-# is unallocated space left - a freshly flashed image is much smaller
-# than the SD card it was written to
+# Compare the root file system to the partition and the device it lives
+# on. A freshly flashed image is much smaller than the SD card it was
+# written to: expanding first grows the partition to fill the device
+# (raspi-config + reboot), then a boot-time resize2fs grows the file
+# system to fill the partition. On large cards that second step can still
+# be running in the background when this guide starts again after the
+# reboot, so the two are checked separately.
fs_gb=$(( $(df --output=size -B1 / | tail -1) / 1000000000 ))
root_part=$(findmnt -n -o SOURCE / 2>/dev/null)
root_dev=$(lsblk -n -o PKNAME "$root_part" 2>/dev/null | head -1)
+part_gb=$(( $(lsblk -b -d -n -o SIZE "$root_part" 2>/dev/null || echo 0) / 1000000000 ))
+if (( part_gb == 0 )); then
+ part_gb=$fs_gb
+fi
if [[ -n "$root_dev" ]]; then
dev_gb=$(( $(lsblk -b -d -n -o SIZE "/dev/$root_dev") / 1000000000 ))
else
- dev_gb=$fs_gb
+ dev_gb=$part_gb
fi
+# The file system reported by df is always a few percent smaller than its
+# partition (ext4 keeps inode tables and a journal), and that overhead
+# grows with the card size - so the comparison slack must scale too, or
+# large cards get flagged as unexpanded forever
+slack_gb=$(( part_gb / 20 + 2 ))
+
# clear the input buffer
while read -t 0.01; do :; done
-if (( dev_gb - fs_gb >= 2 )); then
+if (( dev_gb - part_gb >= 2 )); then
echo "
The file system takes up ${fs_gb} GB of the ${dev_gb} GB storage device.
@@ -160,6 +190,28 @@ rated size, e.g. a 64 GB card gives about 62 GB.
echo "will stay unused and there may not be enough room for data capture!"
fi
+elif (( part_gb - fs_gb >= slack_gb )); then
+
+ # The partition already fills the device but the file system does not,
+ # so the resize scheduled by raspi-config is still running (or failed)
+ echo "
+The file system is still being expanded to fill the ${dev_gb} GB storage
+device - on large cards this takes a few minutes after the resize reboot.
+Waiting for it to finish..."
+ while pgrep -x resize2fs >/dev/null 2>&1; do
+ printf '.'
+ sleep 2
+ done
+ echo ""
+ fs_gb=$(( $(df --output=size -B1 / | tail -1) / 1000000000 ))
+ if (( part_gb - fs_gb >= slack_gb )); then
+ # The scheduled resize never ran or did not finish the job -
+ # resize directly, which is safe online on a mounted ext4 root
+ sudo resize2fs "$root_part"
+ fs_gb=$(( $(df --output=size -B1 / | tail -1) / 1000000000 ))
+ fi
+ echo "${fs_gb} GB of space is now available."
+
elif (( fs_gb < 61 )); then
echo ""
echo "The whole ${dev_gb} GB storage device is already in use, but it is smaller"
@@ -258,13 +310,16 @@ while true; do
# Station ID
while true; do
- read -p "Station ID (e.g. US01AB): " statID || exit 1
- statID=$(echo "$statID" | xargs | tr '[:lower:]' '[:upper:]')
+ flushInput
+ read -r -p "Station ID (e.g. US01AB): " statID || exit 1
+ statID=$(trimSpaces "$statID" | tr '[:lower:]' '[:upper:]')
if [[ "$statID" =~ ^[A-Z]{2}[A-Z0-9]{4}$ && "$statID" != "XX0001" ]]; then
break
fi
echo " A station code has 2 country letters followed by 4 characters, e.g. US01AB."
- if [[ -n "$statID" ]]; then
+ # Only offer to keep a nonstandard entry when it is still safe to use
+ # as a directory name and a .config value
+ if [[ "$statID" =~ ^[A-Z0-9_-]+$ ]]; then
read -n1 -r -p " Use '$statID' anyway? Press Y to accept it, any other key to retype... " key
echo ""
if [[ "$key" = "y" || "$key" = "Y" ]]; then
@@ -275,24 +330,27 @@ while true; do
# Latitude
while true; do
- read -p "Latitude (+N, in degrees, e.g. 40.689298): " lat || exit 1
- lat=$(echo "$lat" | xargs)
+ flushInput
+ read -r -p "Latitude (+N, in degrees, e.g. 40.689298): " lat || exit 1
+ lat=$(trimSpaces "$lat")
isNumberInRange "$lat" -90 90 && break
echo " The latitude must be a number between -90 and 90."
done
# Longitude
while true; do
- read -p "Longitude (+E, in degrees, NEGATIVE in the western hemisphere, e.g. -74.044479): " lon || exit 1
- lon=$(echo "$lon" | xargs)
+ flushInput
+ read -r -p "Longitude (+E, in degrees, NEGATIVE in the western hemisphere, e.g. -74.044479): " lon || exit 1
+ lon=$(trimSpaces "$lon")
isNumberInRange "$lon" -180 180 && break
echo " The longitude must be a number between -180 and 180."
done
# Elevation
while true; do
- read -p "Elevation (mean sea level, in meters, NOT feet): " elev || exit 1
- elev=$(echo "$elev" | xargs)
+ flushInput
+ read -r -p "Elevation (mean sea level, in meters, NOT feet): " elev || exit 1
+ elev=$(trimSpaces "$elev")
isNumberInRange "$elev" -500 9000 && break
echo " The elevation must be a number in meters, e.g. 95.3."
done
From bf655e99b76acf9d95d83dd0f84f81dd847fa6f8 Mon Sep 17 00:00:00 2001
From: Luc Busquin <133058544+Cybis320@users.noreply.github.com>
Date: Mon, 3 Aug 2026 13:17:15 -0700
Subject: [PATCH 14/17] Validate station ID entries and harden the prompts
against stray input
---
Scripts/add_Station.sh | 25 ++++++++++++++++++++++++-
1 file changed, 24 insertions(+), 1 deletion(-)
diff --git a/Scripts/add_Station.sh b/Scripts/add_Station.sh
index 674a511d8..deb37fc58 100755
--- a/Scripts/add_Station.sh
+++ b/Scripts/add_Station.sh
@@ -337,6 +337,12 @@ if [[ -n "${CSV_FILE}" ]]; then
item="${VALUES[$station_id_col]}"
[[ -z "$item" ]] && continue
+ # reject IDs that would break directory names or the sed edits
+ if [[ ! "$item" =~ ^[A-Za-z0-9_-]+$ ]]; then
+ echo "Skipping invalid station ID from CSV: ${item}"
+ continue
+ fi
+
create_station "$item" || continue
config_file=~/source/Stations/${item}/.config
@@ -385,11 +391,28 @@ else
break
fi
fi
- read -p "Enter station ID, to end: " this_Station
+ # drop buffered keypresses (e.g. the tail of an arrow key escape
+ # sequence left by a single-key read) before taking a typed entry
+ while read -t 0.01; do :; done
+ read -r -p "Enter station ID, to end: " this_Station
this_Station=$(uppercase "$(trim "$this_Station")")
if [[ -z "$this_Station" ]]; then
break
fi
+ if [[ ! "$this_Station" =~ ^[A-Z]{2}[A-Z0-9]{4}$ ]]; then
+ echo " A station code has 2 country letters followed by 4 characters, e.g. US01AB."
+ # Only offer to keep a nonstandard entry when it is still safe
+ # to use as a directory name and a .config value
+ if [[ "$this_Station" =~ ^[A-Z0-9_-]+$ ]]; then
+ read -n1 -r -p " Use '${this_Station}' anyway? Press Y to accept it, any other key to retype: " key
+ echo ""
+ if [[ "$key" != "y" && "$key" != "Y" ]]; then
+ continue
+ fi
+ else
+ continue
+ fi
+ fi
Station+=("$this_Station")
done
From b2aae718aeb5a8c5dc32aca68b8ac00b09718c06 Mon Sep 17 00:00:00 2001
From: Luc Busquin <133058544+Cybis320@users.noreply.github.com>
Date: Mon, 3 Aug 2026 17:11:01 -0700
Subject: [PATCH 15/17] Pause for review before the file system expansion
reboot
---
Scripts/RMS_FirstRun.sh | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/Scripts/RMS_FirstRun.sh b/Scripts/RMS_FirstRun.sh
index 8e57783fc..b30fa90ae 100755
--- a/Scripts/RMS_FirstRun.sh
+++ b/Scripts/RMS_FirstRun.sh
@@ -180,8 +180,12 @@ rated size, e.g. a 64 GB card gives about 62 GB.
echo "Expanding the file system, then the system will reboot."
echo "This guide will start again after the reboot."
sudo raspi-config --expand-rootfs
- echo "Rebooting in 5 seconds, please wait..."
- sleep 5
+ echo ""
+ flushInput
+ read -n1 -s -r -p 'Take your time to review the output above, then press any key to reboot... '
+ echo ""
+ echo "Rebooting..."
+ sleep 2
sudo reboot
exit 0
else
From d7b4e699df8beb7982e73f7468715d22c0bedc68 Mon Sep 17 00:00:00 2001
From: Luc Busquin <133058544+Cybis320@users.noreply.github.com>
Date: Mon, 3 Aug 2026 17:11:01 -0700
Subject: [PATCH 16/17] Recompute reboot_after_processing from the station
count on every run
---
Scripts/add_Station.sh | 21 ++++++++++++++-------
1 file changed, 14 insertions(+), 7 deletions(-)
diff --git a/Scripts/add_Station.sh b/Scripts/add_Station.sh
index deb37fc58..f22c178e3 100755
--- a/Scripts/add_Station.sh
+++ b/Scripts/add_Station.sh
@@ -431,18 +431,25 @@ fi
total=$(count_stations)
-if [[ $total -gt 1 ]]; then
- for dir in ~/source/Stations/*/; do
+# Recompute the per-station settings from the current station count on
+# every run, so cameras added (or removed) since the last run are
+# accounted for
+for dir in ~/source/Stations/*/; do
+ [[ -f "${dir}/.config" ]] || continue
+ if [[ $total -gt 1 ]]; then
# scale the reserved free space with the number of stations
sed -i "s/^extra_space_gb:.*$/extra_space_gb: $(( total * 20 ))/g" "${dir}/.config"
# disable the daily post processing reboot, it would kill the other captures
sed -i "s/^\(reboot_after_processing:\).*/\1 false/g" "${dir}/.config"
- done
-
- # remove comment from last line of wayfire.ini to enable window cascade (Pi image)
- if [[ -f ~/.config/wayfire.ini ]]; then
- sed -i s/#mode/mode/ ~/.config/wayfire.ini
+ else
+ # a single camera keeps the standard daily post processing reboot
+ sed -i "s/^\(reboot_after_processing:\).*/\1 true/g" "${dir}/.config"
fi
+done
+
+# remove comment from last line of wayfire.ini to enable window cascade (Pi image)
+if [[ $total -gt 1 && -f ~/.config/wayfire.ini ]]; then
+ sed -i s/#mode/mode/ ~/.config/wayfire.ini
fi
# Make sure the Desktop StartCapture entry points at RMS_StartCapture.sh,
From d0fe849e57a3da52b29237c2e87cf4a75a7f229b Mon Sep 17 00:00:00 2001
From: Luc Busquin <133058544+Cybis320@users.noreply.github.com>
Date: Mon, 3 Aug 2026 22:18:29 -0700
Subject: [PATCH 17/17] Clean up legacy launch entry names and match
nonstandard IDs in GRMSUpdater
---
Scripts/MultiCamLinux/GRMSUpdater.sh | 4 +++-
Scripts/add_Station.sh | 10 ++++++++++
2 files changed, 13 insertions(+), 1 deletion(-)
diff --git a/Scripts/MultiCamLinux/GRMSUpdater.sh b/Scripts/MultiCamLinux/GRMSUpdater.sh
index 51246d6ba..c9e148c31 100755
--- a/Scripts/MultiCamLinux/GRMSUpdater.sh
+++ b/Scripts/MultiCamLinux/GRMSUpdater.sh
@@ -527,7 +527,9 @@ fi
mapfile -t RunList < <(
pgrep -f "Scripts/MultiCamLinux/StartCapture.sh" | while read -r pid; do
cmdline=$(ps -p "$pid" -o args= 2>/dev/null || continue)
- if [[ "$cmdline" =~ Scripts/MultiCamLinux/StartCapture\.sh[[:space:]]+([[:alnum:]]{6}) ]]; then
+ # Station IDs are normally 6 alphanumerics, but add_Station.sh
+ # also accepts underscores/hyphens and other lengths on request
+ if [[ "$cmdline" =~ Scripts/MultiCamLinux/StartCapture\.sh[[:space:]]+([[:alnum:]_-]+) ]]; then
echo "${BASH_REMATCH[1]}"
fi
done | sort -u
diff --git a/Scripts/add_Station.sh b/Scripts/add_Station.sh
index f22c178e3..2fad718e8 100755
--- a/Scripts/add_Station.sh
+++ b/Scripts/add_Station.sh
@@ -436,6 +436,16 @@ total=$(count_stations)
# accounted for
for dir in ~/source/Stations/*/; do
[[ -f "${dir}/.config" ]] || continue
+ station=$(basename "$dir")
+
+ # Remove launch entries left over from the pre-merge per-platform
+ # scripts - their different file names would otherwise double-launch
+ # the station at login
+ rm -f ~/.config/autostart/"${station}_StartCap.desktop" \
+ "${Desktop}/${station}_StartCap.desktop" \
+ "${Desktop}/Show_LiveStream-${station}.desktop" \
+ "${Desktop}/${station}-Show_LiveStream.desktop"
+
if [[ $total -gt 1 ]]; then
# scale the reserved free space with the number of stations
sed -i "s/^extra_space_gb:.*$/extra_space_gb: $(( total * 20 ))/g" "${dir}/.config"