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:
2025-10-24 16:52:08 +07:00
parent d07cb82772
commit 81731ce4a4
5 changed files with 219 additions and 38 deletions

View File

@@ -141,6 +141,18 @@ This will test:
### 7. Run the System ### 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 ```bash
python3 rotary_phone_web.py python3 rotary_phone_web.py
``` ```
@@ -149,6 +161,46 @@ The web interface will be available at:
- `http://localhost:8080` - `http://localhost:8080`
- `http://<raspberry-pi-ip>: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 ## Usage
### Web Interface ### Web Interface
@@ -282,6 +334,8 @@ wedding-phone/
├── rotary_phone_web.py # Main application ├── rotary_phone_web.py # Main application
├── test_complete.py # Audio testing script ├── test_complete.py # Audio testing script
├── configure_hifiberry.sh # HiFiBerry setup 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) ├── config.example.json # Example configuration (copy to config.json)
├── pyproject.toml # UV/pip package configuration ├── pyproject.toml # UV/pip package configuration
├── AUDIO_FIX.md # Audio configuration guide ├── 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 3. Check all paths exist or can be created
4. Verify audio device index is correct 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 ## API Endpoints
The system provides REST API endpoints: The system provides REST API endpoints:

127
install_service.sh Normal file
View 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"

View File

@@ -11,6 +11,10 @@ dependencies = [
"RPi.GPIO>=0.7.1", "RPi.GPIO>=0.7.1",
] ]
[project.scripts]
wedding-phone = "rotary_phone_web:main"
wedding-phone-test = "test_complete:main"
[project.optional-dependencies] [project.optional-dependencies]
dev = [ dev = [
"pytest>=7.0.0", "pytest>=7.0.0",
@@ -22,3 +26,7 @@ build-backend = "hatchling.build"
[tool.uv] [tool.uv]
dev-dependencies = [] dev-dependencies = []
[tool.uv.scripts]
start = "python rotary_phone_web.py"
test = "python test_complete.py"

View File

@@ -873,12 +873,13 @@ def get_local_ip():
except: except:
return "127.0.0.1" return "127.0.0.1"
if __name__ == "__main__": def main():
"""Main entry point for the wedding phone application"""
# Create templates directory and HTML template # Create templates directory and HTML template
script_dir = os.path.dirname(os.path.abspath(__file__)) script_dir = os.path.dirname(os.path.abspath(__file__))
templates_dir = os.path.join(script_dir, 'templates') templates_dir = os.path.join(script_dir, 'templates')
os.makedirs(templates_dir, exist_ok=True) os.makedirs(templates_dir, exist_ok=True)
# Create the HTML template if it doesn't exist # Create the HTML template if it doesn't exist
template_file = os.path.join(templates_dir, 'index.html') template_file = os.path.join(templates_dir, 'index.html')
if not os.path.exists(template_file): if not os.path.exists(template_file):
@@ -1983,3 +1984,6 @@ if __name__ == "__main__":
# Start Flask web server # Start Flask web server
app.run(host='0.0.0.0', port=WEB_PORT, debug=False, threaded=True) app.run(host='0.0.0.0', port=WEB_PORT, debug=False, threaded=True)
if __name__ == "__main__":
main()

24
wedding-phone.service Normal file
View 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