diff --git a/rotary_phone_web.py b/rotary_phone_web.py
index 7eb65be..34c312c 100644
--- a/rotary_phone_web.py
+++ b/rotary_phone_web.py
@@ -151,7 +151,7 @@ BACKUP_ON_WRITE = BACKUP_CONFIG.get('backup_on_write', True)
WEB_PORT = SYS_CONFIG['web']['port']
# Template version - increment this when HTML template changes
-TEMPLATE_VERSION = "1.8.0" # Updated: Added recording sorting by date, name, duration, size
+TEMPLATE_VERSION = "1.9.0" # Updated: Added system shutdown/restart controls
# Flask app
app = Flask(__name__)
@@ -975,6 +975,28 @@ def api_backup_test():
except Exception as e:
return jsonify({"success": False, "error": str(e)}), 500
+@app.route('/api/system/shutdown', methods=['POST'])
+def api_system_shutdown():
+ """Shutdown the Raspberry Pi"""
+ try:
+ import subprocess
+ # Schedule shutdown in 1 minute to allow response to be sent
+ subprocess.Popen(['sudo', 'shutdown', '-h', '+1'])
+ return jsonify({"success": True, "message": "System will shutdown in 1 minute"})
+ except Exception as e:
+ return jsonify({"success": False, "error": str(e)}), 500
+
+@app.route('/api/system/restart', methods=['POST'])
+def api_system_restart():
+ """Restart the Raspberry Pi"""
+ try:
+ import subprocess
+ # Schedule restart to allow response to be sent
+ subprocess.Popen(['sudo', 'reboot'])
+ return jsonify({"success": True, "message": "System will restart shortly"})
+ except Exception as e:
+ return jsonify({"success": False, "error": str(e)}), 500
+
@app.route('/upload_greeting', methods=['POST'])
def upload_greeting():
"""Upload a new greeting message"""
@@ -1869,6 +1891,30 @@ def main():
+
+
+
⚙️ System Control
+
+
+
+ Safely shutdown or restart the Raspberry Pi
+
+
+
+
+
+
+
+
+ ⚠️ System will shutdown/restart after confirmation
+
+
+
+
🎵 Sound Assignment
@@ -2473,6 +2519,53 @@ def main():
}, 5000);
}
+ // System Control Functions
+ function shutdownSystem() {
+ if (confirm('⚠️ Are you sure you want to SHUTDOWN the system?\n\nThe Raspberry Pi will power off in 1 minute.')) {
+ fetch('/api/system/shutdown', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' }
+ })
+ .then(response => response.json())
+ .then(data => {
+ if (data.success) {
+ showAlert('🔌 System shutting down in 1 minute...', 'warning');
+ setTimeout(() => {
+ showAlert('System is now shutting down. This page will be unavailable.', 'error');
+ }, 3000);
+ } else {
+ showAlert('Error: ' + data.error, 'error');
+ }
+ })
+ .catch(error => showAlert('Error: ' + error, 'error'));
+ }
+ }
+
+ function restartSystem() {
+ if (confirm('⚠️ Are you sure you want to RESTART the system?\n\nThe Raspberry Pi will reboot shortly.')) {
+ fetch('/api/system/restart', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' }
+ })
+ .then(response => response.json())
+ .then(data => {
+ if (data.success) {
+ showAlert('🔄 System restarting...', 'warning');
+ setTimeout(() => {
+ showAlert('System is rebooting. Page will reload automatically.', 'warning');
+ // Try to reload page after 30 seconds
+ setTimeout(() => {
+ location.reload();
+ }, 30000);
+ }, 3000);
+ } else {
+ showAlert('Error: ' + data.error, 'error');
+ }
+ })
+ .catch(error => showAlert('Error: ' + error, 'error'));
+ }
+ }
+
// USB Backup Functions
function refreshBackupStatus() {
fetch('/api/backup/status')
diff --git a/wedding-phone-shutdown b/wedding-phone-shutdown
new file mode 100644
index 0000000..aa0c141
--- /dev/null
+++ b/wedding-phone-shutdown
@@ -0,0 +1,8 @@
+# Allow wedding-phone user to shutdown and reboot without password
+# Copy this file to /etc/sudoers.d/wedding-phone-shutdown
+# Usage: sudo cp wedding-phone-shutdown /etc/sudoers.d/wedding-phone-shutdown
+# Then: sudo chmod 0440 /etc/sudoers.d/wedding-phone-shutdown
+
+# Allow user to run shutdown and reboot commands without password
+berwn ALL=(ALL) NOPASSWD: /sbin/shutdown
+berwn ALL=(ALL) NOPASSWD: /sbin/reboot