Add UV run support and systemd service installer
**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 <noreply@anthropic.com>
This commit is contained in:
90
README.md
90
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://<raspberry-pi-ip>: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:
|
||||
|
||||
127
install_service.sh
Normal file
127
install_service.sh
Normal file
@@ -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"
|
||||
@@ -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"
|
||||
|
||||
@@ -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()
|
||||
|
||||
24
wedding-phone.service
Normal file
24
wedding-phone.service
Normal file
@@ -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
|
||||
Reference in New Issue
Block a user