Manage the list of extra subnets to scan. GET: Return current list + auto-detected primary subnet. POST: Add a new CIDR (body: {"cidr": "10.0.0.0/24"}). DELETE: Remove a CIDR (body: {"cidr": "10.0.0.0/24"}).
()
| 5096 | status['missing_optional'] = health['missing_optional'] |
| 5097 | status['healthy'] = health['healthy'] |
| 5098 | |
| 5099 | # Self-heal a stuck status: if every component is present and no installer |
| 5100 | # process is running, the install is done even when the status file was left |
| 5101 | # mid-flight — e.g. the installer was killed right before writing its terminal |
| 5102 | # "installed" state, or a stale file lingers. Without this the UI stays pinned |
| 5103 | # to "installing" until the 10-minute stale timeout (and disables the swap). |
| 5104 | if ( |
| 5105 | status['state'] in {'installing', 'error', 'not_installed'} |
| 5106 | and health['healthy'] |
| 5107 | and not status['service_active'] |
| 5108 | and not _pwn_install_process_running() |
| 5109 | ): |
| 5110 | status['installing'] = False |
| 5111 | status['state'] = 'installed' |
| 5112 | status['phase'] = 'complete' |
| 5113 | status['message'] = 'Pwnagotchi installed successfully. Use the Ragnar dashboard to launch.' |
| 5114 | status['installed'] = True |
| 5115 | _write_pwn_status_file('installed', status['message'], 'complete', { |
| 5116 | 'target_mode': status.get('target_mode', 'ragnar'), |
| 5117 | 'service_state': status.get('service_state'), |
| 5118 | }) |
| 5119 | |
| 5120 | status['needs_repair'] = bool( |
| 5121 | status['installed'] and not health['healthy'] and not status['installing'] |
| 5122 | ) |
| 5123 | |
| 5124 | config_updates = {} |
| 5125 | if status['installed'] != bool(shared_data.config.get('pwnagotchi_installed', False)): |
| 5126 | config_updates['pwnagotchi_installed'] = status['installed'] |
| 5127 | |
| 5128 | if status.get('mode') and status['mode'] != shared_data.config.get('pwnagotchi_mode'): |
| 5129 | config_updates['pwnagotchi_mode'] = status['mode'] |
| 5130 | |
| 5131 | if status.get('message') and status['message'] != shared_data.config.get('pwnagotchi_last_status'): |
| 5132 | config_updates['pwnagotchi_last_status'] = status['message'] |
| 5133 | |
| 5134 | if persist and config_updates: |
| 5135 | _update_pwn_config(config_updates) |
| 5136 | |
| 5137 | status['discoveries'] = _collect_pwnagotchi_discovery_summary() |
| 5138 | |
| 5139 | return status |
| 5140 | |
| 5141 | |
| 5142 | def _emit_pwn_status_update(status: Optional[dict] = None) -> dict: |
| 5143 | payload = status or _build_pwnagotchi_status() |
| 5144 | try: |
| 5145 | socketio.emit('pwnagotchi_status', payload) |
| 5146 | except Exception as exc: |
| 5147 | logger.debug(f"Failed to emit pwnagotchi_status event: {exc}") |
| 5148 | return payload |
| 5149 | |
| 5150 | |
| 5151 | def _pwn_install_process_running() -> bool: |
| 5152 | script_name = os.path.basename(PWN_INSTALL_SCRIPT) |
| 5153 | try: |
| 5154 | result = subprocess.run(['pgrep', '-f', script_name], capture_output=True, text=True) |
| 5155 | return result.returncode == 0 |
nothing calls this directly
no test coverage detected