mirror of
https://github.com/cjfranko/NTP-Timeturner.git
synced 2025-11-08 18:32:02 +00:00
Compare commits
No commits in common. "ea55d087b5b24480749113633703e40c7246a863" and "7c5b7fe031482712b7d08648b3f6dfba38dd7e11" have entirely different histories.
ea55d087b5
...
7c5b7fe031
3 changed files with 70 additions and 227 deletions
35
README.md
35
README.md
|
|
@ -50,37 +50,36 @@ When running as a background daemon, TimeTurner provides a web interface for mon
|
|||
|
||||
## 🚀 Installation
|
||||
|
||||
The `setup.sh` script compiles and installs the TimeTurner application. You can run it by cloning the repository with `git` or by using the `curl` command below for a git-free installation.
|
||||
The `setup.sh` script is provided to compile and install the TimeTurner application and its systemd service on a Debian-based system like Raspberry Pi OS.
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- **Internet Connection**: To download dependencies.
|
||||
- **Curl and Unzip**: The script requires `curl` to download files and `unzip` for the git-free method. The setup script will attempt to install these if they are missing.
|
||||
- **Rust and Cargo**: The script requires the Rust programming language toolchain. If you don't have it, install it from [rustup.rs](https://rustup.rs/).
|
||||
|
||||
### Running the Installer (Recommended)
|
||||
### Running the Installer
|
||||
|
||||
This command downloads the latest version, unpacks it, and runs the setup script. Paste it into your Raspberry Pi terminal:
|
||||
|
||||
```bash
|
||||
curl -L https://github.com/cjfranko/NTP-Timeturner/archive/refs/heads/main.zip -o NTP-Timeturner.zip && \
|
||||
unzip NTP-Timeturner.zip && \
|
||||
cd NTP-Timeturner-main && \
|
||||
chmod +x setup.sh && \
|
||||
./setup.sh
|
||||
```
|
||||
1. First, clone the repository:
|
||||
```bash
|
||||
git clone https://github.com/cjfranko/NTP-Timeturner.git
|
||||
cd NTP-Timeturner
|
||||
```
|
||||
2. Make the script executable and run it. The script will use `sudo` for commands that require root privileges, so it may ask for your password.
|
||||
```bash
|
||||
chmod +x setup.sh
|
||||
./setup.sh
|
||||
```
|
||||
|
||||
### What the Script Does
|
||||
|
||||
The installation script automates the following steps:
|
||||
|
||||
1. **Installs Dependencies**: Installs `git`, `curl`, `unzip`, and necessary build tools.
|
||||
2. **Compiles the Binary**: Runs `cargo build --release` to create an optimised executable.
|
||||
3. **Creates Directories**: Creates `/opt/timeturner` to store the application files.
|
||||
4. **Installs Files**:
|
||||
1. **Compiles the Binary**: Runs `cargo build --release` to create an optimised executable.
|
||||
2. **Creates Directories**: Creates `/opt/timeturner` to store the application files.
|
||||
3. **Installs Files**:
|
||||
- The compiled binary is copied to `/opt/timeturner/timeturner`.
|
||||
- The web interface assets from the `static/` directory are copied to `/opt/timeturner/static`.
|
||||
- A symbolic link is created from `/usr/local/bin/timeturner` to the binary, allowing it to be run from any location.
|
||||
5. **Sets up Systemd Service**:
|
||||
4. **Sets up Systemd Service**:
|
||||
- Copies the `timeturner.service` file to `/etc/systemd/system/`.
|
||||
- Enables the service to start automatically on system boot.
|
||||
|
||||
|
|
|
|||
10
config.yml
10
config.yml
|
|
@ -12,8 +12,8 @@ defaultNudgeMs: 2
|
|||
# Time-turning offsets. All values are added to the incoming LTC time.
|
||||
# These can be positive or negative.
|
||||
timeturnerOffset:
|
||||
hours: 0
|
||||
minutes: 0
|
||||
seconds: 0
|
||||
frames: 0
|
||||
milliseconds: 0
|
||||
hours: 1
|
||||
minutes: 2
|
||||
seconds: 3
|
||||
frames: 4
|
||||
milliseconds: 5
|
||||
|
|
|
|||
252
setup.sh
252
setup.sh
|
|
@ -21,8 +21,7 @@ echo "Detected package manager: $PKG_MANAGER"
|
|||
# --- Update System Packages ---
|
||||
echo "Updating system packages..."
|
||||
if [ "$PKG_MANAGER" == "apt" ]; then
|
||||
sudo apt update
|
||||
sudo DEBIAN_FRONTEND=noninteractive apt upgrade -y -o Dpkg::Options::="--force-confold"
|
||||
sudo apt update && sudo apt upgrade -y
|
||||
elif [ "$PKG_MANAGER" == "dnf" ]; then
|
||||
sudo dnf upgrade -y
|
||||
elif [ "$PKG_MANAGER" == "pacman" ]; then
|
||||
|
|
@ -49,29 +48,14 @@ fi
|
|||
echo "Installing common build dependencies..."
|
||||
if [ "$PKG_MANAGER" == "apt" ]; then
|
||||
sudo apt update
|
||||
sudo apt install -y build-essential libudev-dev pkg-config curl wget
|
||||
sudo apt install -y build-essential libudev-dev pkg-config curl
|
||||
elif [ "$PKG_MANAGER" == "dnf" ]; then
|
||||
sudo dnf install -y gcc make perl-devel libudev-devel pkg-config curl wget
|
||||
sudo dnf install -y gcc make perl-devel libudev-devel pkg-config curl
|
||||
elif [ "$PKG_MANAGER" == "pacman" ]; then
|
||||
sudo pacman -Sy --noconfirm base-devel libudev pkg-config curl
|
||||
fi
|
||||
echo "Common build dependencies installed."
|
||||
|
||||
# --- Install Python dependencies for testing ---
|
||||
echo "🐍 Installing Python dependencies for test scripts..."
|
||||
if [ "$PKG_MANAGER" == "apt" ]; then
|
||||
# python3-serial is the name for pyserial in apt
|
||||
sudo apt install -y python3 python3-pip python3-serial
|
||||
elif [ "$PKG_MANAGER" == "dnf" ]; then
|
||||
# python3-pyserial is the name for pyserial in dnf
|
||||
sudo dnf install -y python3 python3-pip python3-pyserial
|
||||
elif [ "$PKG_MANAGER" == "pacman" ]; then
|
||||
# python-pyserial is the name for pyserial in pacman
|
||||
sudo pacman -Sy --noconfirm python python-pip python-pyserial
|
||||
fi
|
||||
# sudo pip3 install pyserial # This is replaced by the native package manager installs above
|
||||
echo "✅ Python dependencies installed."
|
||||
|
||||
# --- Apply custom splash screen ---
|
||||
if [[ "$(uname)" == "Linux" ]]; then
|
||||
echo "🖼️ Applying custom splash screen..."
|
||||
|
|
@ -116,10 +100,10 @@ echo "Removing NTPD (if installed) and installing Chrony, NMTUI, Adjtimex..."
|
|||
if [ "$PKG_MANAGER" == "apt" ]; then
|
||||
sudo apt update
|
||||
sudo apt remove -y ntp || true # Remove ntp if it exists, ignore if not
|
||||
sudo apt install -y chrony network-manager adjtimex
|
||||
sudo apt install -y chrony nmtui adjtimex
|
||||
sudo systemctl enable chrony --now
|
||||
elif [ "$PKG_MANAGER" == "dnf" ]; then
|
||||
sudo dnf remove -y ntp
|
||||
sudo dnf remove -y ntp || true
|
||||
sudo dnf install -y chrony NetworkManager-tui adjtimex
|
||||
sudo systemctl enable chronyd --now
|
||||
elif [ "$PKG_MANAGER" == "pacman" ]; then
|
||||
|
|
@ -132,142 +116,29 @@ fi
|
|||
|
||||
echo "NTPD removed (if present). Chrony, NMTUI, and Adjtimex installed and configured."
|
||||
|
||||
# --- Configure Chrony to act as a local NTP server ---
|
||||
echo "⚙️ Configuring Chrony to serve local time..."
|
||||
# The path to chrony.conf can vary
|
||||
if [ -f /etc/chrony/chrony.conf ]; then
|
||||
CHRONY_CONF="/etc/chrony/chrony.conf"
|
||||
elif [ -f /etc/chrony.conf ]; then
|
||||
CHRONY_CONF="/etc/chrony.conf"
|
||||
else
|
||||
CHRONY_CONF=""
|
||||
fi
|
||||
|
||||
if [ -n "$CHRONY_CONF" ]; then
|
||||
# Comment out any existing pool, server, or sourcedir lines to prevent syncing with external sources
|
||||
echo "Disabling external NTP sources..."
|
||||
sudo sed -i -E 's/^(pool|server|sourcedir)/#&/' "$CHRONY_CONF"
|
||||
|
||||
# Add settings to the top of the file to serve local clock
|
||||
# Using a temp file to prepend is safer than multiple sed calls
|
||||
TEMP_CONF=$(mktemp)
|
||||
cat <<EOF > "$TEMP_CONF"
|
||||
# Serve the system clock as a reference at stratum 1
|
||||
server 127.127.1.0
|
||||
allow 127.0.0.0/8
|
||||
local stratum 1
|
||||
|
||||
EOF
|
||||
# Append the rest of the original config file after our new lines
|
||||
cat "$CHRONY_CONF" >> "$TEMP_CONF"
|
||||
sudo mv "$TEMP_CONF" "$CHRONY_CONF"
|
||||
|
||||
|
||||
# Add settings to the bottom of the file to allow LAN clients
|
||||
echo "Allowing LAN clients..."
|
||||
sudo tee -a "$CHRONY_CONF" > /dev/null <<EOF
|
||||
|
||||
# Allow LAN clients to connect
|
||||
allow 0.0.0.0/0
|
||||
EOF
|
||||
|
||||
# Restart chrony to apply changes (service name can be chrony or chronyd)
|
||||
echo "Restarting Chrony service..."
|
||||
sudo systemctl restart chrony || sudo systemctl restart chronyd
|
||||
echo "✅ Chrony configured."
|
||||
else
|
||||
echo "⚠️ Warning: chrony.conf not found. Skipping Chrony configuration."
|
||||
fi
|
||||
|
||||
|
||||
# --- Install and configure WiFi hotspot and captive portal ---
|
||||
echo "📡 Installing and configuring WiFi hotspot and captive portal..."
|
||||
|
||||
if [ "$PKG_MANAGER" == "apt" ]; then
|
||||
# Stop the service if it's running from a previous installation to prevent "Text file busy" error.
|
||||
echo "Stopping existing nodogsplash service before installation..."
|
||||
sudo systemctl stop nodogsplash || true
|
||||
|
||||
# We will use dnsmasq for DHCP, as the compiled nodogsplash version may not support the internal DHCP server.
|
||||
# sudo apt-get remove --purge -y dnsmasq || true # This line is no longer needed.
|
||||
|
||||
# Install dependencies for hotspot and for building nodogsplash.
|
||||
sudo apt install -y hostapd dnsmasq git libmicrohttpd-dev libjson-c-dev iptables
|
||||
|
||||
# Force iptables-legacy for nodogsplash
|
||||
echo "Setting iptables-legacy mode for nodogsplash..."
|
||||
sudo update-alternatives --set iptables /usr/sbin/iptables-legacy
|
||||
sudo update-alternatives --set ip6tables /usr/sbin/ip6tables-legacy
|
||||
|
||||
echo "Building and installing nodogsplash from source..."
|
||||
# Create a temporary directory for the build
|
||||
BUILD_DIR=$(mktemp -d)
|
||||
git clone https://github.com/nodogsplash/nodogsplash.git "$BUILD_DIR"
|
||||
|
||||
# Run the build in a subshell to isolate the directory change
|
||||
(
|
||||
cd "$BUILD_DIR"
|
||||
make
|
||||
sudo make install
|
||||
|
||||
# Manually install the systemd service file as 'make install' might not do it.
|
||||
# This makes the script more robust.
|
||||
if [ -f "debian/nodogsplash.service" ]; then
|
||||
echo "Manually installing systemd service file..."
|
||||
sudo cp debian/nodogsplash.service /etc/systemd/system/nodogsplash.service
|
||||
# Reload systemd to recognize the new service
|
||||
sudo systemctl daemon-reload
|
||||
else
|
||||
echo "⚠️ Warning: nodogsplash.service file not found in source. Cannot set up as a service."
|
||||
fi
|
||||
)
|
||||
|
||||
# Clean up the build directory
|
||||
sudo rm -rf "$BUILD_DIR"
|
||||
echo "✅ nodogsplash installed successfully."
|
||||
|
||||
# Disable the standalone hostapd service to let NetworkManager manage it.
|
||||
sudo systemctl disable hostapd
|
||||
sudo systemctl mask hostapd
|
||||
sudo apt install -y hostapd dnsmasq nodogsplash
|
||||
sudo systemctl unmask hostapd
|
||||
sudo systemctl enable hostapd
|
||||
sudo systemctl enable nodogsplash
|
||||
else
|
||||
echo "This script is designed for Debian-based systems like Raspberry Pi OS."
|
||||
echo "Skipping WiFi hotspot setup."
|
||||
fi
|
||||
|
||||
# Stop services to configure
|
||||
# Ensure services exist before trying to stop them
|
||||
sudo systemctl stop hostapd || true
|
||||
sudo systemctl stop dnsmasq || true
|
||||
sudo systemctl stop hostapd
|
||||
sudo systemctl stop dnsmasq
|
||||
sudo systemctl stop nodogsplash
|
||||
|
||||
# Ensure NetworkManager is managing wlan0 by removing any conflicting configurations.
|
||||
# This is the critical fix for the "No suitable device" error.
|
||||
echo "Ensuring NetworkManager is managing wlan0..."
|
||||
sudo rm -f /etc/NetworkManager/conf.d/99-unmanaged-wlan0.conf
|
||||
sudo systemctl reload NetworkManager
|
||||
|
||||
# Configure static IP for wlan0 using NetworkManager (nmcli)
|
||||
echo "Configuring static IP for wlan0 using NetworkManager..."
|
||||
|
||||
# Define the connection name
|
||||
CON_NAME="TimeTurner-AP"
|
||||
|
||||
# If a connection with this name already exists, delete it to ensure a clean slate.
|
||||
if nmcli c show --active | grep -q "$CON_NAME"; then
|
||||
sudo nmcli c down "$CON_NAME" || true
|
||||
fi
|
||||
if nmcli c show | grep -q "$CON_NAME"; then
|
||||
echo "Deleting existing '$CON_NAME' connection profile..."
|
||||
sudo nmcli c delete "$CON_NAME" || true
|
||||
fi
|
||||
|
||||
# Create a new connection profile for the Access Point with a static IP.
|
||||
echo "Creating new '$CON_NAME' connection profile..."
|
||||
sudo nmcli c add type wifi ifname wlan0 con-name "$CON_NAME" autoconnect yes ssid "TimeTurner"
|
||||
sudo nmcli c modify "$CON_NAME" 802-11-wireless.mode ap 802-11-wireless.band bg
|
||||
sudo nmcli c modify "$CON_NAME" 802-11-wireless-security.key-mgmt wpa-psk
|
||||
sudo nmcli c modify "$CON_NAME" 802-11-wireless-security.psk "harry-ron-hermione"
|
||||
sudo nmcli c modify "$CON_NAME" ipv4.method manual ipv4.addresses 10.0.252.1/24
|
||||
# Configure static IP for wlan0
|
||||
echo "Configuring static IP for wlan0..."
|
||||
sudo sed -i '/^interface wlan0/d' /etc/dhcpcd.conf
|
||||
sudo tee -a /etc/dhcpcd.conf > /dev/null <<EOF
|
||||
interface wlan0
|
||||
static ip_address=10.0.252.1/24
|
||||
nohook wpa_supplicant
|
||||
EOF
|
||||
|
||||
# Configure dnsmasq for DHCP
|
||||
echo "Configuring dnsmasq..."
|
||||
|
|
@ -277,64 +148,44 @@ dhcp-range=10.0.252.10,10.0.252.50,255.255.255.0,24h
|
|||
address=/#/10.0.252.1
|
||||
EOF
|
||||
|
||||
# Configure hostapd
|
||||
echo "Configuring hostapd..."
|
||||
sudo tee /etc/hostapd/hostapd.conf > /dev/null <<EOF
|
||||
interface=wlan0
|
||||
driver=nl80211
|
||||
ssid=TimeTurner
|
||||
hw_mode=g
|
||||
channel=7
|
||||
wmm_enabled=0
|
||||
macaddr_acl=0
|
||||
auth_algs=1
|
||||
ignore_broadcast_ssid=0
|
||||
wpa=2
|
||||
wpa_passphrase=harry-ron-hermione
|
||||
wpa_key_mgmt=WPA-PSK
|
||||
rsn_pairwise=CCMP
|
||||
EOF
|
||||
|
||||
sudo sed -i 's|#DAEMON_CONF=""|DAEMON_CONF="/etc/hostapd/hostapd.conf"|' /etc/default/hostapd
|
||||
|
||||
# Configure nodogsplash for captive portal
|
||||
echo "Configuring nodogsplash..."
|
||||
sudo tee /etc/nodogsplash/nodogsplash.conf > /dev/null <<EOF
|
||||
GatewayInterface wlan0
|
||||
GatewayAddress 10.0.252.1
|
||||
MaxClients 250
|
||||
AuthIdleTimeout 480
|
||||
MaxClients 50
|
||||
ClientIdleTimeout 3600
|
||||
FirewallRuleSet preauthenticated-users {
|
||||
FirewallRule allow tcp port 80
|
||||
FirewallRule allow tcp port 53
|
||||
FirewallRule allow udp port 53
|
||||
}
|
||||
RedirectURL http://10.0.252.1/static/index.html
|
||||
EOF
|
||||
|
||||
# Restart services in the correct order and add delays to prevent race conditions
|
||||
echo "Restarting services..."
|
||||
# Bring up the new AP connection using nmcli
|
||||
sudo nmcli c up "$CON_NAME"
|
||||
|
||||
# Wait for the interface to come up and get the IP address
|
||||
echo "Waiting for wlan0 to be configured..."
|
||||
IP_CHECK=""
|
||||
# Loop for up to 30 seconds waiting for the IP
|
||||
for i in {1..15}; do
|
||||
# The '|| true' prevents the script from exiting if grep finds no match
|
||||
IP_CHECK=$(ip -4 addr show wlan0 | grep -oP '(?<=inet\s)\d+(\.\d+){3}' || true)
|
||||
if [ "$IP_CHECK" == "10.0.252.1" ]; then
|
||||
break
|
||||
fi
|
||||
echo "Still waiting for IP..."
|
||||
sleep 2
|
||||
done
|
||||
|
||||
# Check for the IP address before starting nodogsplash
|
||||
if [ "$IP_CHECK" == "10.0.252.1" ]; then
|
||||
echo "✅ wlan0 configured with IP $IP_CHECK."
|
||||
sudo systemctl restart dnsmasq
|
||||
if command -v nodogsplash &> /dev/null; then
|
||||
echo "Attempting to start nodogsplash service..."
|
||||
if ! sudo systemctl restart nodogsplash; then
|
||||
echo "❌ nodogsplash service failed to start. Displaying logs..."
|
||||
# Give a moment for logs to be written
|
||||
sleep 2
|
||||
sudo journalctl -u nodogsplash.service --no-pager -n 50
|
||||
echo ""
|
||||
echo "To debug further, run nodogsplash in the foreground with this command:"
|
||||
echo " sudo /usr/bin/nodogsplash -f -d 7"
|
||||
echo ""
|
||||
exit 1
|
||||
fi
|
||||
echo "✅ nodogsplash service started successfully."
|
||||
fi
|
||||
else
|
||||
echo "❌ Error: wlan0 failed to get the static IP 10.0.252.1. Found: '$IP_CHECK'."
|
||||
echo "Please check 'sudo nmcli c show \"$CON_NAME\"' and 'ip addr show wlan0'."
|
||||
exit 1
|
||||
fi
|
||||
# Restart services
|
||||
sudo systemctl restart dhcpcd
|
||||
sudo systemctl restart dnsmasq
|
||||
sudo systemctl restart hostapd
|
||||
sudo systemctl restart nodogsplash
|
||||
|
||||
echo "✅ WiFi hotspot and captive portal configured. SSID: TimeTurner, IP: 10.0.252.1"
|
||||
echo "Clients will be redirected to http://10.0.252.1/static/index.html"
|
||||
|
|
@ -361,7 +212,6 @@ sudo ln -sf $INSTALL_DIR/timeturner $BIN_DIR/timeturner
|
|||
echo "✅ Binary and assets installed to $INSTALL_DIR, and binary linked to $BIN_DIR."
|
||||
|
||||
# 4. Install systemd service file
|
||||
# Only needed for Linux systems (e.g., Raspberry Pi OS)
|
||||
if [[ "$(uname)" == "Linux" ]]; then
|
||||
echo "⚙️ Installing systemd service for Linux..."
|
||||
sudo cp timeturner.service /etc/systemd/system/
|
||||
|
|
@ -376,20 +226,14 @@ echo ""
|
|||
echo "--- Setup Complete ---"
|
||||
echo "The TimeTurner daemon is now installed."
|
||||
echo "The working directory is $INSTALL_DIR."
|
||||
# Copy default config.yml from repo if it exists
|
||||
if [ -f config.yml ]; then
|
||||
sudo cp config.yml $INSTALL_DIR/
|
||||
echo "Default 'config.yml' installed to $INSTALL_DIR."
|
||||
else
|
||||
echo "⚠️ No default 'config.yml' found in repository. Please add one if needed."
|
||||
fi
|
||||
echo "A default 'config.yml' will be created there on first run."
|
||||
echo ""
|
||||
if [[ "$(uname)" == "Linux" ]]; then
|
||||
echo "To start the service, run:"
|
||||
echo " sudo systemctl start timeturner.service"
|
||||
echo ""
|
||||
echo "To view live logs, run:"
|
||||
echo " journalctl -u tim_turner.service -f"
|
||||
echo " journalctl -u timeturner.service -f"
|
||||
echo ""
|
||||
fi
|
||||
echo "To run the interactive TUI instead, simply run from the project directory:"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue