From 81731ce4a48d2f40e7b10c9e6097d6ce0233c7ec Mon Sep 17 00:00:00 2001 From: grabowski Date: Fri, 24 Oct 2025 16:52:08 +0700 Subject: [PATCH] Add UV run support and systemd service installer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **UV Integration:** - Updated pyproject.toml with [tool.uv.scripts] - Added 'uv run start' to launch wedding phone - Added 'uv run test' to run audio tests - Refactored main code into main() function - Added proper entry point for package installation **Systemd Service:** - Created wedding-phone.service template - Service runs with UV for dependency management - Automatic restart on failure - Proper security hardening (NoNewPrivileges, PrivateTmp) - GPIO and audio group access configured **Service Installer:** - Created install_service.sh automated installer - Auto-detects project path and user - Checks for UV installation and dependencies - Configures service file with correct paths - Option to enable and start immediately - Provides helpful command reference **Installer Features:** - Validates config.json exists (creates from example if missing) - Installs UV dependencies automatically - Updates service file paths dynamically - Color-coded output for clarity - Error checking at each step - Clean installation process **Usage:** ```bash # Run directly with UV uv run start # Install as system service ./install_service.sh # Service management sudo systemctl start wedding-phone sudo systemctl stop wedding-phone sudo journalctl -u wedding-phone -f ``` **Documentation:** - Updated README with UV commands - Added service installation guide - Removed old manual systemd instructions - Added service management commands - Updated file structure documentation **Benefits:** - Easier to run (single command) - Automatic startup on boot - Better dependency management - Professional service integration - Simplified installation process 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- README.md | 90 ++++++++++++++++++------------ install_service.sh | 127 ++++++++++++++++++++++++++++++++++++++++++ pyproject.toml | 8 +++ rotary_phone_web.py | 8 ++- wedding-phone.service | 24 ++++++++ 5 files changed, 219 insertions(+), 38 deletions(-) create mode 100644 install_service.sh create mode 100644 wedding-phone.service diff --git a/README.md b/README.md index 35f3ae8..8d22c36 100644 --- a/README.md +++ b/README.md @@ -141,6 +141,18 @@ This will test: ### 7. Run the System +#### Option A: Run Directly with UV (Recommended) + +```bash +# Run the wedding phone system +uv run start + +# Or run the audio test +uv run test +``` + +#### Option B: Run with Python + ```bash python3 rotary_phone_web.py ``` @@ -149,6 +161,46 @@ The web interface will be available at: - `http://localhost:8080` - `http://:8080` +### 8. Install as System Service (Optional) + +To run the wedding phone automatically on boot: + +```bash +# Run the installer script +./install_service.sh +``` + +The installer will: +- Check dependencies +- Install the systemd service +- Configure for your user and project path +- Optionally enable and start the service + +**Manual service commands:** + +```bash +# Enable service to start on boot +sudo systemctl enable wedding-phone + +# Start service now +sudo systemctl start wedding-phone + +# Stop service +sudo systemctl stop wedding-phone + +# Restart service +sudo systemctl restart wedding-phone + +# View logs (live) +sudo journalctl -u wedding-phone -f + +# View status +sudo systemctl status wedding-phone + +# Disable service +sudo systemctl disable wedding-phone +``` + ## Usage ### Web Interface @@ -282,6 +334,8 @@ wedding-phone/ ├── rotary_phone_web.py # Main application ├── test_complete.py # Audio testing script ├── configure_hifiberry.sh # HiFiBerry setup script +├── install_service.sh # Systemd service installer +├── wedding-phone.service # Systemd service file ├── config.example.json # Example configuration (copy to config.json) ├── pyproject.toml # UV/pip package configuration ├── AUDIO_FIX.md # Audio configuration guide @@ -423,42 +477,6 @@ If the script won't start: 3. Check all paths exist or can be created 4. Verify audio device index is correct -## Auto-start on Boot - -Create a systemd service: - -```bash -sudo nano /etc/systemd/system/wedding-phone.service -``` - -Add (replace `/path/to/wedding-phone` with your actual path): - -```ini -[Unit] -Description=Wedding Phone Service -After=network.target sound.target - -[Service] -Type=simple -User=pi -WorkingDirectory=/path/to/wedding-phone -ExecStart=/usr/bin/python3 /path/to/wedding-phone/rotary_phone_web.py -Restart=always -RestartSec=10 - -[Install] -WantedBy=multi-user.target -``` - -Enable and start: - -```bash -sudo systemctl daemon-reload -sudo systemctl enable wedding-phone.service -sudo systemctl start wedding-phone.service -sudo systemctl status wedding-phone.service -``` - ## API Endpoints The system provides REST API endpoints: diff --git a/install_service.sh b/install_service.sh new file mode 100644 index 0000000..8163efb --- /dev/null +++ b/install_service.sh @@ -0,0 +1,127 @@ +#!/bin/bash +# +# Wedding Phone Service Installer +# Installs the wedding phone as a systemd service +# + +set -e + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +echo "==========================================" +echo "Wedding Phone Service Installer" +echo "==========================================" +echo "" + +# Check if running as root +if [ "$EUID" -eq 0 ]; then + echo -e "${RED}ERROR: Do not run this script as root or with sudo${NC}" + echo "This script will prompt for sudo password when needed" + exit 1 +fi + +# Get the directory where this script is located +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +SERVICE_FILE="$SCRIPT_DIR/wedding-phone.service" + +echo "Project directory: $SCRIPT_DIR" +echo "" + +# Check if config.json exists +if [ ! -f "$SCRIPT_DIR/config.json" ]; then + echo -e "${YELLOW}WARNING: config.json not found${NC}" + echo "Creating config.json from config.example.json..." + if [ -f "$SCRIPT_DIR/config.example.json" ]; then + cp "$SCRIPT_DIR/config.example.json" "$SCRIPT_DIR/config.json" + echo -e "${GREEN}✓ Created config.json${NC}" + echo -e "${YELLOW}Please edit config.json to configure your system before starting the service${NC}" + echo "" + else + echo -e "${RED}ERROR: config.example.json not found${NC}" + exit 1 + fi +fi + +# Check if UV is installed +if ! command -v uv &> /dev/null; then + echo -e "${RED}ERROR: UV is not installed${NC}" + echo "Install UV with: curl -LsSf https://astral.sh/uv/install.sh | sh" + exit 1 +fi + +echo -e "${GREEN}✓ UV is installed${NC}" + +# Install dependencies +echo "" +echo "Installing dependencies..." +cd "$SCRIPT_DIR" +uv pip install -e . + +echo -e "${GREEN}✓ Dependencies installed${NC}" +echo "" + +# Update the service file with the correct path +echo "Configuring service file..." +TEMP_SERVICE="/tmp/wedding-phone.service" +cat "$SERVICE_FILE" | sed "s|/home/pi/wedding-phone|$SCRIPT_DIR|g" > "$TEMP_SERVICE" + +# Update User in service file +CURRENT_USER=$(whoami) +sed -i "s|User=pi|User=$CURRENT_USER|g" "$TEMP_SERVICE" +sed -i "s|Group=pi|Group=$CURRENT_USER|g" "$TEMP_SERVICE" + +echo -e "${GREEN}✓ Service file configured for user: $CURRENT_USER${NC}" +echo "" + +# Install the service +echo "Installing systemd service..." +sudo cp "$TEMP_SERVICE" /etc/systemd/system/wedding-phone.service +sudo systemctl daemon-reload + +echo -e "${GREEN}✓ Service installed${NC}" +echo "" + +# Ask if user wants to enable and start the service +read -p "Do you want to enable and start the service now? (y/n) " -n 1 -r +echo "" +if [[ $REPLY =~ ^[Yy]$ ]]; then + echo "Enabling service to start on boot..." + sudo systemctl enable wedding-phone.service + echo -e "${GREEN}✓ Service enabled${NC}" + + echo "" + echo "Starting service..." + sudo systemctl start wedding-phone.service + echo -e "${GREEN}✓ Service started${NC}" + + echo "" + echo "Service status:" + sudo systemctl status wedding-phone.service --no-pager +else + echo "" + echo "Service installed but not started." + echo "To enable and start later, run:" + echo " sudo systemctl enable wedding-phone.service" + echo " sudo systemctl start wedding-phone.service" +fi + +echo "" +echo "==========================================" +echo "Installation Complete!" +echo "==========================================" +echo "" +echo "Useful commands:" +echo " Start service: sudo systemctl start wedding-phone" +echo " Stop service: sudo systemctl stop wedding-phone" +echo " Restart service: sudo systemctl restart wedding-phone" +echo " View logs: sudo journalctl -u wedding-phone -f" +echo " Service status: sudo systemctl status wedding-phone" +echo " Disable service: sudo systemctl disable wedding-phone" +echo "" + +# Clean up +rm -f "$TEMP_SERVICE" diff --git a/pyproject.toml b/pyproject.toml index feb8be3..d841944 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,6 +11,10 @@ dependencies = [ "RPi.GPIO>=0.7.1", ] +[project.scripts] +wedding-phone = "rotary_phone_web:main" +wedding-phone-test = "test_complete:main" + [project.optional-dependencies] dev = [ "pytest>=7.0.0", @@ -22,3 +26,7 @@ build-backend = "hatchling.build" [tool.uv] dev-dependencies = [] + +[tool.uv.scripts] +start = "python rotary_phone_web.py" +test = "python test_complete.py" diff --git a/rotary_phone_web.py b/rotary_phone_web.py index 803970e..796d58d 100644 --- a/rotary_phone_web.py +++ b/rotary_phone_web.py @@ -873,12 +873,13 @@ def get_local_ip(): except: return "127.0.0.1" -if __name__ == "__main__": +def main(): + """Main entry point for the wedding phone application""" # Create templates directory and HTML template script_dir = os.path.dirname(os.path.abspath(__file__)) templates_dir = os.path.join(script_dir, 'templates') os.makedirs(templates_dir, exist_ok=True) - + # Create the HTML template if it doesn't exist template_file = os.path.join(templates_dir, 'index.html') if not os.path.exists(template_file): @@ -1983,3 +1984,6 @@ if __name__ == "__main__": # Start Flask web server app.run(host='0.0.0.0', port=WEB_PORT, debug=False, threaded=True) + +if __name__ == "__main__": + main() diff --git a/wedding-phone.service b/wedding-phone.service new file mode 100644 index 0000000..247f9b4 --- /dev/null +++ b/wedding-phone.service @@ -0,0 +1,24 @@ +[Unit] +Description=Wedding Phone - Vintage Rotary Phone Audio System +After=network.target sound.target +Wants=network.target sound.target + +[Service] +Type=simple +User=pi +Group=pi +WorkingDirectory=/home/pi/wedding-phone +Environment="PATH=/home/pi/.local/bin:/usr/local/bin:/usr/bin:/bin" +ExecStart=/usr/bin/env uv run start +Restart=always +RestartSec=10 + +# Security hardening +NoNewPrivileges=true +PrivateTmp=true + +# Allow GPIO access +SupplementaryGroups=gpio audio + +[Install] +WantedBy=multi-user.target