NewFPV / documentation

PWMENU WIKI

The complete guide to installing, configuring, and safely operating A_pwmenu on Pwnagotchi.

Version 1.4.2 Python 3.11 GPL-3.0
Authorized auditing only

Use this plugin only with networks you own or have explicit permission to audit. PCAPs, hashes, passwords, and GPS coordinates can all contain sensitive data.

01 / overview

What PWMenu does

A_pwmenu turns the standard handshakes directory into a complete field console. It indexes captures, combines results from multiple sources, grades PCAP quality, displays locations, and runs external processing without blocking the web interface.

CAPTURE

PCAP management

ESSID/BSSID grouping, individual downloads, ZIP exports, mode 22000 conversion, quality analysis, and precise deletion.

PASSWORDS

One credential view

WPA-sec, OHC, manual entries, and QuickDic results appear in one interface with their source clearly identified.

GEO

GPS map

PwnDroid, browser GPS, and GPSD support, sidecar files, clusters, capture history, and a dedicated no-location list.

AUTOMATION

Persistent OHC queue

Mode 22000 extraction, deduplication, batching, durable queue state, and automatic recovery after API backoff.

SAFETY

Controlled cleanup

Exact candidate previews, report tokens, and a final file and quality check immediately before deletion.

FIELD UI

Phone and desktop

Four focused workspaces, compact mobile navigation, customizable accent color, and direct capture actions.

Version 1.4.2 makes OHC routing and Xray fallback reliable

Automatic mode now uses the direct OHC API while it works, falls back to VLESS only for network or country-block failures, verifies the complete Reality/TLS path instead of only an open local port, and preserves unsuccessful uploads in the durable retry queue.

Version 1.4.2 highlights

  • Adds explicit `auto`, `direct`, and `vless` route policies. Forced VLESS never silently leaks an OHC request through the direct connection.
  • Starts Xray lazily in automatic mode and verifies it with a real OHC HTTPS request through the loopback proxy before declaring the tunnel ready.
  • Restarts a stale or half-open Xray process once, probes the new tunnel, and returns persistent failures to the existing queue and backoff.
  • Shows the active DIRECT or VLESS route, configured policy, and latest route error in the OHC card.
  • Adds Raspberry Pi-friendly startup, stabilization, probe timeout, retry-count, and periodic health-check settings.
  • Keeps the 1.4.1 data format and requires no migration of PCAPs, credentials, maps, history, queues, or backups.

Version 1.4.1 foundation

  • Consumes Pwnagotchi's unfiltered AP list and shows the SSID and recovered password of a currently visible known network on the physical display.
  • Matches visible networks by exact BSSID first. Name-only legacy credentials are used only when the result is unambiguous, and conflicting passwords are never guessed.
  • Rotates several visible recovered networks strongest-signal first, expires stale scan state after a configurable TTL, and falls back to the latest recovered credential.
  • Adds numeric ordering for every card in Other while preserving the existing layout as the default on phone and desktop.
  • Fixes false `Cleanup report changed` rejections caused only by candidate ordering or refreshed human-readable reason text.
  • Keeps strict cleanup safety: a real path, signature, or category change still invalidates confirmation, and every candidate is analyzed again immediately before deletion.

Version 1.4.0 foundation

  • Uses the active `bettercap.handshakes` value instead of device-specific capture paths and starts storage-dependent workers only after the agent configuration is available.
  • Discovers previous PWMenu state across system-account home directories, merges credentials, map/history data and cloud submission memory, saves a pre-migration copy, and records one-time completion markers.
  • Loads the selected workspace first, preloads other tabs when the browser is idle, and transfers the first 24 capture cards before filling reserved placeholders in small fade-in batches.
  • Serves content-hashed CSS and JavaScript with immutable browser caching, ETags and fast gzip while a revision change forces one safe cache refresh.
  • Preloads the map in the background with a dedicated map-loading surface instead of a misleading rectangular text skeleton.
  • Adds portable unencrypted backup/restore for PCAPs, map sidecars, state, credentials, settings, history and cloud deduplication memory with a versioned manifest and per-file SHA-256.
  • Adds a conditional System attention panel, bounded 24-hour Activity History and Conflict Center with copyable reports and local verify-and-fix for password conflicts.
  • Reconciles punctuation-only ESSID aliases, avoids ambiguous zero-BSSID matches, remembers OHC/WPA-sec submissions and exports only one best usable unresolved capture per exact AP.
  • Verifies manual EAPOL and PMKID passwords locally before saving, reports a precise no-hash error, and never treats partial frames without an extractable hash as crackable.
  • Keeps map placement, password actions, whitelist changes and compact notifications asynchronous so normal operations do not require a full page reload.

02 / prerequisites

Requirements

ComponentPurposeRequired
Pwnagotchi 2.xHost, plugin loader, and Flask Web UIYes
Python 3.11The `/home/pi/.pwn` environmentYes
requestsHTTP integrations and time synchronizationYes
websocketsPwnDroid GPS transportFor PwnDroid
hcxpcapngtoolQuality analysis and mode 22000 conversionRecommended
aircrack-ngKnown-password verification and integrated QuickDicFor verification and QuickDic
XrayOptional loopback VLESS proxy used only by OHC requestsOnly for OHC VLESS routing
Internet accessWPA-sec, OHC, Yandex Maps, and time syncFor integrations

Check the environment

/home/pi/.pwn/bin/python3 -c \
  "import requests, websockets; print('Python dependencies OK')"

/usr/bin/hcxpcapngtool --version
/usr/bin/aircrack-ng --help | head -n 1

Install missing optional components:

sudo apt update
sudo apt install -y hcxtools aircrack-ng
sudo /home/pi/.pwn/bin/pip install websockets

03 / install

Quick start

  1. Back up the current fileKeep a known-good plugin copy before replacing it.
  2. Install the tagged releasePin `v1.4.2` so the installed code is reproducible.
  3. Compile before restartNever restart the service with an unchecked Python file.
  4. Enable the pluginAdd one line to `config.toml`, then restart Pwnagotchi.

1. Back up and install

sudo cp /usr/local/share/pwnagotchi/custom-plugins/A_pwmenu.py \
  /root/A_pwmenu.py.backup 2>/dev/null || true

sudo wget -O /usr/local/share/pwnagotchi/custom-plugins/A_pwmenu.py \
  https://raw.githubusercontent.com/newfpv/pwmenu/v1.4.2/A_pwmenu.py

sudo chown root:root /usr/local/share/pwnagotchi/custom-plugins/A_pwmenu.py
sudo chmod 644 /usr/local/share/pwnagotchi/custom-plugins/A_pwmenu.py

2. Compile and enable

/home/pi/.pwn/bin/python3 -m py_compile \
  /usr/local/share/pwnagotchi/custom-plugins/A_pwmenu.py

sudoedit /etc/pwnagotchi/config.toml

Minimum configuration:

main.plugins.A_pwmenu.enabled = true
sudo systemctl restart pwnagotchi
sudo systemctl status pwnagotchi --no-pager -l

Open http://<pwnagotchi-ip>:8080/plugins/A_pwmenu/. The Bluetooth interface address will usually differ from the USB or LAN address.

04 / lifecycle

Update, rollback, and removal

Update to 1.4.2

A normal update replaces only `A_pwmenu.py`. Version 1.4.2 keeps the 1.4.1 storage format and continues reading the capture directory from the active Pwnagotchi `bettercap.handshakes` setting. PCAPs, GPS sidecars, potfiles, map data, history, configuration, durable queues and cloud submission memory remain compatible; no new migration is required.

sudo cp /usr/local/share/pwnagotchi/custom-plugins/A_pwmenu.py \
  /root/A_pwmenu.py.before-1.4.2

sudo wget -O /usr/local/share/pwnagotchi/custom-plugins/A_pwmenu.py \
  https://raw.githubusercontent.com/newfpv/pwmenu/v1.4.2/A_pwmenu.py

/home/pi/.pwn/bin/python3 -m py_compile \
  /usr/local/share/pwnagotchi/custom-plugins/A_pwmenu.py

sudo systemctl restart pwnagotchi
First 1.4.0 start performs a compatibility migration

PWMenu dynamically discovers older storage under system-account home directories. It combines unique potfile lines, state dictionaries, history, map records and the password-free OHC task snapshot. When both old and configured state exist, the newer file wins individual conflicts while unique records from both sides are retained. A one-time pre-migration state copy and per-source completion marker prevent data loss and repeated application.

Verify the configured storage after update

grep '^bettercap.handshakes' /etc/pwnagotchi/config.toml
grep 'Handshake storage\|legacy storage' \
  /etc/pwnagotchi/log/pwnagotchi.log | tail -20

# Replace this example with the value printed above
ls -la /configured/handshake/path/.a_pwmenu*

The migration never deletes the previous directory. Keep it until the Web UI shows the expected passwords, map points, history and cloud status. Completion files named `.a_pwmenu_migration_*.json` contain only migration metadata, not passwords or the original source path.

Rollback

sudo cp /root/A_pwmenu.py.before-1.4.2 \
  /usr/local/share/pwnagotchi/custom-plugins/A_pwmenu.py
sudo systemctl restart pwnagotchi

Remove the plugin

sudo systemctl stop pwnagotchi
sudo rm /usr/local/share/pwnagotchi/custom-plugins/A_pwmenu.py
sudo systemctl start pwnagotchi

Remove `main.plugins.A_pwmenu.*` settings from the configuration separately. Persistent data in the directory configured by `bettercap.handshakes` is deliberately left untouched.

05 / config

Complete configuration

The following is a safe starting template. Replace placeholder keys locally and never publish your real `config.toml`.

# Master switch
main.plugins.A_pwmenu.enabled = true

# Independent modules
main.plugins.A_pwmenu.module_web_enabled = true
main.plugins.A_pwmenu.module_gps_enabled = true
main.plugins.A_pwmenu.module_ohc_enabled = true
main.plugins.A_pwmenu.module_wpa_sec_enabled = true
main.plugins.A_pwmenu.module_quality_enabled = true
main.plugins.A_pwmenu.module_whitelist_enabled = true
main.plugins.A_pwmenu.module_time_sync_enabled = true
main.plugins.A_pwmenu.module_display_password_enabled = true
main.plugins.A_pwmenu.module_quickdic_enabled = true

# Integrated display-password
main.plugins.A_pwmenu.display_password_orientation = "horizontal"
main.plugins.A_pwmenu.display_password_max_length = 22
main.plugins.A_pwmenu.display_password_empty_text = "No cracked passwords"
main.plugins.A_pwmenu.display_password_wpa_sec = true
main.plugins.A_pwmenu.display_password_ohc = true
main.plugins.A_pwmenu.display_password_handshake_lab = true
main.plugins.A_pwmenu.display_password_manual = true
main.plugins.A_pwmenu.display_password_quickdic = true
main.plugins.A_pwmenu.display_password_visible_enabled = true
main.plugins.A_pwmenu.display_password_visible_ttl = 90
main.plugins.A_pwmenu.display_password_visible_cycle_seconds = 8
main.plugins.A_pwmenu.display_password_horizontal_x = -1
main.plugins.A_pwmenu.display_password_horizontal_y = -1
main.plugins.A_pwmenu.display_password_vertical_x = -1
main.plugins.A_pwmenu.display_password_vertical_y = -1

# Other workspace card order (smaller numbers appear first)
main.plugins.A_pwmenu.other_card_order_cleanup = 1
main.plugins.A_pwmenu.other_card_order_identity = 2
main.plugins.A_pwmenu.other_card_order_transfer = 3
main.plugins.A_pwmenu.other_card_order_ohc = 4
main.plugins.A_pwmenu.other_card_order_wpa_sec = 5
main.plugins.A_pwmenu.other_card_order_whitelist = 6
main.plugins.A_pwmenu.other_card_order_activity = 7
main.plugins.A_pwmenu.other_card_order_conflicts = 8
main.plugins.A_pwmenu.other_card_order_credit = 9

# Integrated better_quickdic
main.plugins.A_pwmenu.quickdic_wordlist_folder = "/home/pi/wordlists/"
main.plugins.A_pwmenu.quickdic_recursive = false
main.plugins.A_pwmenu.quickdic_timeout = 300
main.plugins.A_pwmenu.quickdic_max_queue = 32
main.plugins.A_pwmenu.quickdic_face = "(·ω·)"
main.plugins.A_pwmenu.quickdic_update_display = true
main.plugins.A_pwmenu.quickdic_status_seconds = 12
main.plugins.A_pwmenu.quickdic_status_template = "Cracked {essid}: {password}"
main.plugins.A_pwmenu.quickdic_telegram_enabled = false
main.plugins.A_pwmenu.quickdic_telegram_bot_token = ""
main.plugins.A_pwmenu.quickdic_telegram_chat_id = ""
main.plugins.A_pwmenu.quickdic_telegram_timeout = 15

# Integrated WPA-sec
main.plugins.A_pwmenu.wpa_sec_key = ""
main.plugins.A_pwmenu.wpa_sec_api_url = "https://wpa-sec.stanev.org"
main.plugins.A_pwmenu.wpa_sec_auto_upload = true
main.plugins.A_pwmenu.wpa_sec_download_results = true
main.plugins.A_pwmenu.wpa_sec_sync_interval = 3600

# OnlineHashCrack
main.plugins.A_pwmenu.ohc_enabled = false
main.plugins.A_pwmenu.ohc_api_key = ""
main.plugins.A_pwmenu.ohc_auto_upload = true
main.plugins.A_pwmenu.ohc_sync_interval = 3600
main.plugins.A_pwmenu.ohc_retry_poll_interval = 60
main.plugins.A_pwmenu.ohc_reconcile_on_start = false

# Optional OHC-only VLESS route
# auto: direct first, VLESS only after transport or country-block failure
# direct: never start Xray; vless: never fall back to the direct connection
main.plugins.A_pwmenu.ohc_route_mode = "auto"
main.plugins.A_pwmenu.ohc_vless_url = ""
main.plugins.A_pwmenu.ohc_vless_flow = "auto"
main.plugins.A_pwmenu.ohc_xray_binary = "/usr/local/bin/xray"
main.plugins.A_pwmenu.ohc_proxy_port = 10809
main.plugins.A_pwmenu.ohc_vless_startup_timeout = 8
main.plugins.A_pwmenu.ohc_vless_ready_delay = 2
main.plugins.A_pwmenu.ohc_vless_probe_timeout = 12
main.plugins.A_pwmenu.ohc_vless_probe_attempts = 3
main.plugins.A_pwmenu.ohc_vless_probe_interval = 300

# PwnDroid over Bluetooth PAN
main.plugins.A_pwmenu.pwndroid_ws_enabled = true
main.plugins.A_pwmenu.pwndroid_mac = "AA:BB:CC:DD:EE:FF"
main.plugins.A_pwmenu.pwndroid_gateway = ""
main.plugins.A_pwmenu.pwndroid_extra_gateways = []
main.plugins.A_pwmenu.pwndroid_port = 8080

# Browser GPS and optional GPSD fallback
main.plugins.A_pwmenu.phone_gps_enabled = true
main.plugins.A_pwmenu.phone_gps_max_age = 600
main.plugins.A_pwmenu.gps_assign_window = 180
main.plugins.A_pwmenu.gps_stale_seconds = 180
main.plugins.A_pwmenu.gpsd_enabled = false
main.plugins.A_pwmenu.gpsd_host = "127.0.0.1"
main.plugins.A_pwmenu.gpsd_port = 2947
main.plugins.A_pwmenu.gpsd_poll_interval = 10

# Capture analysis, password verification, and limits
main.plugins.A_pwmenu.quality_auto_scan = true
main.plugins.A_pwmenu.quality_scan_delay_ms = 250
main.plugins.A_pwmenu.auto_replace_unusable = true
main.plugins.A_pwmenu.hcxpcapngtool_timeout = 90
main.plugins.A_pwmenu.password_verify_timeout = 45
main.plugins.A_pwmenu.timezone = 0
main.plugins.A_pwmenu.time_sync_interval = 1800
main.plugins.A_pwmenu.import_max_bytes = 2097152
main.plugins.A_pwmenu.archive_memory_limit = 2097152

# Web UI performance and notification timing
main.plugins.A_pwmenu.web_gzip_level = 1
main.plugins.A_pwmenu.web_notification_duration_ms = 2600
main.plugins.A_pwmenu.web_inventory_cache_seconds = 30
main.plugins.A_pwmenu.web_credential_cache_seconds = 10
main.plugins.A_pwmenu.web_snapshot_seconds = 5
main.plugins.A_pwmenu.web_background_preload = true
main.plugins.A_pwmenu.web_model_warmup = true
main.plugins.A_pwmenu.web_page_size = 24
main.plugins.A_pwmenu.web_background_batch_size = 12
main.plugins.A_pwmenu.web_background_batch_delay_ms = 120
main.plugins.A_pwmenu.web_foreground_batch_size = 24
main.plugins.A_pwmenu.web_foreground_batch_delay_ms = 50

# Other workspace card order; lower numbers appear first
main.plugins.A_pwmenu.other_card_order_cleanup = 1
main.plugins.A_pwmenu.other_card_order_identity = 2
main.plugins.A_pwmenu.other_card_order_transfer = 3
main.plugins.A_pwmenu.other_card_order_ohc = 4
main.plugins.A_pwmenu.other_card_order_wpa_sec = 5
main.plugins.A_pwmenu.other_card_order_whitelist = 6
main.plugins.A_pwmenu.other_card_order_activity = 7
main.plugins.A_pwmenu.other_card_order_conflicts = 8
main.plugins.A_pwmenu.other_card_order_credit = 9

# Activity, backup, and conditional health reporting
main.plugins.A_pwmenu.activity_history_max = 200
main.plugins.A_pwmenu.activity_history_hours = 24
main.plugins.A_pwmenu.backup_max_bytes = 2147483648
main.plugins.A_pwmenu.backup_restart_after_restore = true
main.plugins.A_pwmenu.health_memory_warning_mb = 64
main.plugins.A_pwmenu.health_disk_warning_percent = 10
main.plugins.A_pwmenu.health_gps_grace_seconds = 900
main.plugins.A_pwmenu.health_queue_stuck_seconds = 3600

# Disable standalone copies after migration
main.plugins.display-password.enabled = false
main.plugins.better_quickdic.enabled = false
main.plugins.wpa-sec.enabled = false
main.plugins.wpa-sec-list.enabled = false
OptionDefaultWhat it controls
module_*_enabledtrueStarts or stops the named subsystem without disabling the whole plugin.
display_password_visible_enabledtruePrefers a recovered AP from the latest unfiltered scan over the generic latest credential on the physical display.
display_password_visible_ttl90 secondsHow long the last successful visible-AP scan remains eligible before display fallback.
display_password_visible_cycle_seconds8 secondsHow long each recovered visible AP remains on screen when several are in range.
other_card_order_*1 through 9Controls the compact card order on Other. Smaller numbers appear first; the defaults reproduce the standard layout.
wpa_sec_auto_uploadtrueAutomatically uploads one best unresolved capture for each exact BSSID.
wpa_sec_download_resultstrueDownloads WPA-sec results and atomically updates the local potfile.
ohc_auto_uploadtrueAdds new PCAPs to the durable OHC queue.
ohc_reconcile_on_startfalsePerforms a full local/OHC reconciliation at every start. Enable intentionally.
ohc_route_modeauto`auto` tries direct first and falls back to VLESS only when required; `direct` never starts Xray; `vless` forces the protected route without a direct fallback.
ohc_vless_urlemptyPrivate OHC-only VLESS link. Empty makes the effective route direct regardless of `ohc_route_mode`.
ohc_vless_flowautoUses the link flow by default. Set an explicit provider-required flow such as `xtls-rprx-vision` only as an override; an empty string deliberately removes flow.
ohc_vless_startup_timeout8 secondsMaximum time for Xray to start and open its loopback listener.
ohc_vless_ready_delay2 secondsStabilization delay before the first complete OHC tunnel probe.
ohc_vless_probe_timeout12 secondsTimeout for one real HTTPS request to OHC through Xray.
ohc_vless_probe_attempts3Number of end-to-end probes before the tunnel is considered unavailable.
ohc_vless_probe_interval300 secondsHow long a successful proxy verification remains fresh before it is checked again.
pwndroid_gatewayemptyAn empty value enables Android gateway discovery from MAC, ARP, and the default route.
gps_assign_window180 secondsMaximum time difference between the capture and a GPS fix.
auto_replace_unusabletrueArchives an older weak capture after a newer usable capture appears for the same BSSID.
password_verify_timeout45 secondsMaximum time for checking known passwords against one concrete capture.
quickdic_max_queue32Maximum number of new handshakes waiting for the background dictionary worker.
archive_memory_limit2 MiBIn-memory threshold for `SpooledTemporaryFile` ZIP archives.
web_gzip_level1HTML gzip compression level from 1 to 9. Level 1 is recommended on Raspberry Pi because it avoids multi-second CPU compression stalls.
web_background_preloadtrueLoads the selected tab first, then preloads remaining tabs during browser idle time.
web_page_size24Number of Cracked or Handshakes cards transferred in the first server-side page.
web_background_batch_size12Number of reserved card placeholders replaced by each background batch.
web_background_batch_delay_ms120 msPause between background card batches so Bluetooth transfer does not block interaction.
web_foreground_batch_size24Batch size used when the user actively searches or requests more visible records.
web_inventory_cache_seconds30 secondsShort cache for filesystem capture inventory and metadata.
web_credential_cache_seconds10 secondsShort cache for parsed password sources; file changes invalidate it.
web_snapshot_seconds5 secondsReuse window for a fully prepared page model.
web_model_warmuptrueRebuilds an invalidated model in a background worker instead of the next HTTP request.
other_card_order_*1–9Numeric position of Cleanup, Identity, Transfer, OHC, WPA-sec, Whitelist, Activity, Conflicts, and Credit cards. Lower values appear first.
activity_history_max200Maximum retained activity records shown in Other.
activity_history_hours24 hoursTime window retained by Activity History.
backup_max_bytes2 GiBMaximum accepted restore archive size before extraction and manifest validation.
backup_restart_after_restoretrueAllows a completed restore to restart only the Pwnagotchi service.
health_memory_warning_mb64 MiBShows System attention only when available memory falls below this value.
health_disk_warning_percent10%Warns when free space in the configured handshake filesystem is too low.
health_gps_grace_seconds900 secondsGrace period before an enabled but unavailable GPS source becomes actionable.
health_queue_stuck_seconds3600 secondsAge after which an unchanged OHC or WPA-sec queue is reported as stuck.
web_notification_duration_ms2600 msHow long server notifications and transient toast messages remain visible. Values are clamped to 250–60000 ms.

Migrate standalone plugins

Copy the old display length, enabled sources, wordlist folder, face, Telegram values, and WPA-sec key into the matching A_pwmenu options above. Then disable `display-password`, `better_quickdic`, `wpa-sec`, and `wpa-sec-list`. Their files may stay installed for rollback, but they must not remain enabled or Pwnagotchi can display duplicate data or submit the same capture twice.

QuickDic no longer blocks capture handling

A bounded background worker processes new captures. Before reading dictionaries it tests every locally known password for that exact BSSID against the new handshake, so another capture from an already recovered access point is not needlessly cracked again.

06 / ui

Interface

01

Cracked

Known ESSIDs and passwords, result source, reveal and copy controls, plus update and delete actions for local entries.

02

Handshakes

All capture groups, per-file quality, direct PCAP, 22000, OHC and WPA-sec actions, a Map/Move control for every concrete capture, and Delete beside its filename.

03

Map

Yandex Maps when online or the built-in fallback, cracked filter, search, GPS and manually placed clusters, history, the No GPS list, and live password and whitelist actions.

04

Other

Identity, transfer, cloud status, whitelist, activity, conflicts, cleanup, and a configurable compact card order.

Visible recovered network on the physical display

When `display_password_visible_enabled` is true, PWMenu listens to Pwnagotchi's unfiltered access-point refresh. If a visible BSSID already has one unambiguous recovered password, the physical display temporarily shows SSID:password. Because the event occurs before whitelist filtering, an owner network can still be displayed even when Pwnagotchi is configured not to attack it.

Exact BSSID matching has priority. A legacy credential without a real BSSID may match an exact or punctuation-normalized SSID only when it resolves to one password. If two different passwords are attached to the visible BSSID, PWMenu displays neither and leaves the conflict for local verification. Several valid visible networks rotate by signal strength; after `display_password_visible_ttl`, normal latest-credential display resumes.

Visible scan state is temporary

The current AP list, RSSI values, and selected display credential remain in RAM only. They are cleared on plugin unload and are not appended to PWMenu history or normal logs.

Arrange cards in Other

Every Other card has an integer `other_card_order_*` setting. Smaller numbers move upward. Equal numbers retain document order, absent conditional cards do not reserve a gap, and the same order is used on mobile and desktop. The defaults below reproduce the standard layout:

main.plugins.A_pwmenu.other_card_order_cleanup = 1
main.plugins.A_pwmenu.other_card_order_identity = 2
main.plugins.A_pwmenu.other_card_order_transfer = 3
main.plugins.A_pwmenu.other_card_order_ohc = 4
main.plugins.A_pwmenu.other_card_order_wpa_sec = 5
main.plugins.A_pwmenu.other_card_order_whitelist = 6
main.plugins.A_pwmenu.other_card_order_activity = 7
main.plugins.A_pwmenu.other_card_order_conflicts = 8
main.plugins.A_pwmenu.other_card_order_credit = 9

Accent color

The interface provides six presets and a custom color picker. The value is stored in browser `localStorage` and a cookie, not in Pwnagotchi state. Each browser keeps its own preference.

Workspace search

The main search filters the current list by ESSID, BSSID, password, and capture filename. On the Map tab it is synchronized with the map search.

Place a handshake on the map

Choose `Map` or `Move` beside a concrete PCAP. PWMenu opens the Map workspace with a fixed pin at the center of the screen: pan the map under the pin, then confirm with the square check button at the bottom or cancel beside it. The instruction remains at the top so it never covers the controls.

You can also attach the handshake to an existing point or cluster from its details panel. User-placed coordinates are labeled `Map`; coordinates received from PwnDroid, browser geolocation, or GPSD are labeled `GPS`. After the server accepts a manual location, the matching Handshakes card changes to `MAP` and its action changes to `Move` immediately, without a page reload.

Download All Uncracked APs

The ZIP contains every crackable access point that does not yet have a password verified against its concrete capture. A matching ESSID or BSSID is only a password-candidate lookup; it never proves that the capture is solved.

  1. 1
    Reject unusable files

    PWMenu includes only captures from which hcxpcapngtool can produce at least one usable WPA mode 22000 hash.

  2. 2
    Verify known candidates

    Passwords from WPA-sec, OHC, Handshake Lab, Manual, and QuickDic are tested locally with aircrack-ng against each capture. The check cache stores signatures and outcomes, never password material.

  3. 3
    Keep unresolved access points

    If a known password fails a newer handshake, that access point remains unresolved and exportable. Captures without a reliable BSSID stay separate.

  4. 4
    Select one best unresolved capture

    For duplicate captures of one BSSID, quality, usable hashes, authorized exchange evidence, recency, and size select the best remaining file for `uncracked-handshakes.zip`.

Known network does not automatically mean solved capture

If one access point has several handshakes, the known key is verified against every one first. Only a cryptographic match removes that capture from brute-force exports and OHC work.

Fast actions over Bluetooth

Whitelist changes, manual password actions, map placement, and OHC or WPA-sec submissions use compact background requests. The browser keeps the map, selected marker, filters, search text, and details card in place instead of downloading and rendering the entire page again. A short notification reports progress and the final result. Duplicate clicks for the same upload are ignored while its request is active.

Actions that materially replace the current data set, such as importing a file, still use a full reload so the interface is rebuilt from authoritative server state.

07 / capture intelligence

PCAP quality

PWMenu runs the local `hcxpcapngtool`, parses its report, and checks whether a valid mode 22000 hash was produced. The result is cached against the file signature and recalculated whenever the PCAP changes.

Excellent

A usable hash plus evidence of an authorized EAPOL exchange or a written PMKID.

Usable

At least one valid WPA/PMKID hash for Hashcat mode 22000.

Partial

EAPOL frames exist, but no usable mode 22000 hash was generated. The file is uncrackable in its current form.

Unusable

No useful WPA/PMKID material, or the file is only an empty PCAP header.

Hashcat suitability is binary

`Excellent` and `Usable` contain at least one extractable mode 22000 hash and can be tested. `Partial` with zero hashes and `Unusable` cannot be tested by Hashcat. `Partial` only explains that some EAPOL traffic was captured; it does not mean a password candidate can be verified.

Why EAPOL frame counts are not enough

M1, M2, M3, and M4 counters describe frames seen in the PCAP. Hashcat needs a cryptographically compatible pair from the same exchange, with matching nonces, replay counters, MAC identities, and a usable MIC. Frames from different association attempts may raise the counters while still producing zero hashes. PWMenu therefore treats successful mode 22000 extraction—not the raw frame count—as the final suitability test.

A lone M1, M2, M3, or M4 can help diagnose capture behavior, but it cannot verify a password by itself. Recapture the access point until `hcxpcapngtool` produces a hash.

Automatic weak-capture replacement

When a separate, newer PCAP for the same BSSID becomes `Usable` or `Excellent`, an older weak file larger than 24 bytes may be renamed with a .replaced-<timestamp> suffix. This is a reversible archive: the original data remains recoverable but is excluded from the active index.

Empty 24-byte PCAPs are never archived automatically

They require explicit confirmation in Capture Cleanup, preventing an automatic classifier from silently deleting a file.

08 / safe cleanup

Safe capture cleanup

Cleanup is driven by Hashcat suitability. Valid empty PCAP headers, files classified as `Unusable`, and `Partial` files with zero extractable WPA/PMKID hashes become candidates. Nothing is deleted merely because an EAPOL counter looks weak.

  1. 1
    Local suitability check

    `hcxpcapngtool` is run locally. A PCAP is uncrackable when no valid WPA/PMKID mode 22000 hash can be extracted.

  2. 2
    Preview

    The plugin shows filenames, EAPOL counters, and reasons, then creates a canonical SHA-256 token from each resolved path, current file signature, and cleanup category.

  3. 3
    Browser confirmation

    The exact candidate count is shown before the owner confirms. There is no automatic deletion.

  4. 4
    Revalidation and cleanup

    The report token, signature, and current quality are checked again. Only then are the PCAP and related `.gps.json`, `.geo.json`, `.hc22000`, and `.22000` files removed and recorded in history.

Candidate ordering and refreshed human-readable reason text do not invalidate confirmation in 1.4.1. A real path, signature, or cleanup-category change still rejects the old token and requires a fresh review. The broad “nuke all” action has been removed from the Web UI.

09 / whitelist

Network whitelist

The Other workspace manages `main.whitelist` without manual TOML editing. The Map workspace also provides a live Allow/Remove control on every network card. Names are validated, the list is sorted, and configuration is written atomically.

  • Exact network name, up to 128 characters.
  • Hyphens, underscores, spaces, and other valid SSID punctuation are preserved exactly.
  • NUL, carriage return, and newline characters are rejected.
  • `/etc/pwnagotchi/config.toml.pwmenu-whitelist.bak` is created before writing.
  • The original `config.toml` mode, UID, and GID are preserved.
  • The parent directory is synchronized after `os.replace()`.
  • The active agent configuration is updated immediately.

Every whitelist entry remains present in one bounded scrollable list. There is no hidden “show more” group, so phone users can swipe through the complete list without expanding the whole Other workspace.

Excellent-only map groups

A multi-network map marker has one group whitelist button. It selects only ESSIDs with at least one capture currently graded `Excellent`; `Usable`, `Partial`, `Unusable`, and unclassified entries are skipped. Networks already present in the whitelist are not submitted again. When every Excellent network in the group is already allowed, the group button changes to `Allowed`.

This rule is enforced again on the Pwnagotchi, using a fresh capture scan. Modifying the browser request cannot add a network whose current server-side quality is below Excellent.

On an individual map card, `Allow` changes to `Remove` immediately after a successful update, and changes back after removal. These map operations do not reload the page.

A service restart after a large series of whitelist changes is still reasonable if you want to confirm the fully reloaded configuration.

10 / credentials

Password sources and imports

SourceFormatEditable in UI
WPA-sec / OHC potfile`AP_MAC:CLIENT_MAC:ESSID:PASSWORD`Deletion depends on source
Manual`manual.potfile`Yes
Handshake LabVersioned CSV with exact ESSID/BSSID and sourceImported into its own potfile
Integrated QuickDic`*.pcap.cracked`Read-only
OHC JSON/CSVService exportImported into the local potfile

Before import, the potfile is normalized as UTF-8: NUL bytes are removed and credentials are deduplicated by BSSID, ESSID, and password. Writes use a temporary file, `fsync`, and atomic replacement.

Import reports distinguish `added`, `already present`, `duplicate rows`, `ignored`, `invalid`, and the number of saved OHC task identities. `import_max_bytes` limits the upload size.

Manual password verification

Adding or editing a password is not a blind database write. PWMenu finds a capture for the exact BSSID. EAPOL handshakes are checked with `aircrack-ng`; PMKID-only captures are extracted with `hcxpcapngtool` and verified locally using PBKDF2-HMAC-SHA1 and a constant-time PMKID comparison. Passwords are never placed in a verifier process command line.

A wrong password, timeout, or missing capture is rejected. When the PCAP contains no usable WPA/PMKID hash, the UI reports: Password cannot be verified because this capture contains no usable WPA/PMKID hash. Recapture the access point. The password is not stored because the plugin cannot distinguish a correct owner-provided value from an incorrect one without cryptographic material.

Successful add, update, and delete actions refresh both list and map credentials in place without reloading the complete page.

TXT password export

`Export TXT` produces a single UTF-8 TSV block with a byte-order mark, Windows-compatible CRLF lines, and the columns `ESSID`, `BSSID`, `PASSWORD`, and `SOURCE`. Values are sorted and password colons are preserved instead of being split as potfile delimiters.

Review exports before sharing

JSON and CSV files may include SSIDs, BSSIDs, and recovered passwords. PWMenu’s deduplication snapshot stores task identities without passwords.

11 / wpa-sec

WPA-sec

PWMenu now contains the complete WPA-sec workflow: automatic and manual upload, persistent per-BSSID submission history, service-key cookie authentication, status display, and atomic potfile downloads. A separate `wpa-sec` or `wpa-sec-list` plugin is not required.

main.plugins.A_pwmenu.module_wpa_sec_enabled = true
main.plugins.A_pwmenu.wpa_sec_key = "REPLACE_ME"
main.plugins.A_pwmenu.wpa_sec_api_url = "https://wpa-sec.stanev.org"
main.plugins.A_pwmenu.wpa_sec_auto_upload = true
main.plugins.A_pwmenu.wpa_sec_download_results = true
main.plugins.A_pwmenu.wpa_sec_sync_interval = 3600

main.plugins.wpa-sec.enabled = false
main.plugins.wpa-sec-list.enabled = false
  1. 1
    Select

    For each exact unresolved BSSID, PWMenu chooses one best usable capture instead of uploading every duplicate.

  2. 2
    Upload

    New captures are submitted in the background with the configured service key and recorded in durable state.

  3. 3
    Download

    Recovered results are downloaded to a temporary file, validated, synchronized, and atomically replace the local WPA-sec potfile.

  4. 4
    Migrate

    Existing state from the stock WPA-sec plugins is imported so a migration does not resend known captures.

The Handshakes and Map workspaces keep manual upload actions, while the Other workspace provides synchronization and service status. Service links open the real WPA-sec page.

Obtain your key from wpa-sec.stanev.org ↗.

12 / onlinehashcrack

OnlineHashCrack API v2

main.plugins.A_pwmenu.ohc_enabled = true
main.plugins.A_pwmenu.ohc_api_key = "sk_REPLACE_ME"
main.plugins.A_pwmenu.ohc_auto_upload = true
main.plugins.A_pwmenu.ohc_sync_interval = 3600
main.plugins.A_pwmenu.ohc_retry_poll_interval = 60

# Optional: route OHC only through VLESS when direct access fails
main.plugins.A_pwmenu.ohc_route_mode = "auto"
main.plugins.A_pwmenu.ohc_vless_url = ""
main.plugins.A_pwmenu.ohc_vless_flow = "auto"
main.plugins.A_pwmenu.ohc_xray_binary = "/usr/local/bin/xray"
main.plugins.A_pwmenu.ohc_proxy_port = 10809
main.plugins.A_pwmenu.ohc_vless_startup_timeout = 8
main.plugins.A_pwmenu.ohc_vless_ready_delay = 2
main.plugins.A_pwmenu.ohc_vless_probe_timeout = 12
main.plugins.A_pwmenu.ohc_vless_probe_attempts = 3
main.plugins.A_pwmenu.ohc_vless_probe_interval = 300

Submission flow

  1. 1
    Check locally

    `hcxpcapngtool` writes mode 22000 records to a unique temporary file on the Pwnagotchi. If zero records are produced, no request containing that capture is sent to OHC.

  2. 2
    Exclude uncrackable files

    The PCAP is marked with `No usable WPA or PMKID hash found`, counted in the OHC panel, and offered in Capture Cleanup. A per-file OHC action returns this exact reason instead of claiming that an empty upload started.

  3. 3
    Deduplicate

    Exact BSSID and hash identities are compared with local submission history, the latest imported OHC export, and the live `list_tasks` response. Already known work does not enter the pending queue.

  4. 4
    Select and submit

    PWMenu keeps one best unresolved capture per exact BSSID, submits new records in batches of up to 50 hashes, and persists accepted or skipped metadata after every successful batch.

HTTP 429 and durable backoff

`rate_limit_exceeded` does not mean the plugin is broken. PWMenu stores `retry_at`, the reason, and the complete queue, adds a small safety margin to `Retry-After`, suppresses requests until the deadline, and resumes automatically.

grep -E 'OHC|A_pwmenu' \
  /etc/pwnagotchi/log/pwnagotchi.log | tail -100

Send all missing to OHC performs a full local reconciliation. It first requires a locally extractable mode 22000 hash, then checks every unresolved access point, selects only one best capture per exact BSSID, and queues only tasks absent from local history, the latest OHC export, and the current service task list. If OHC reports that a final candidate already exists, PWMenu records it as reported instead of sending it again.

Optional OHC-only VLESS routing

VLESS is not required for normal PWMenu operation. In the recommended `auto` mode PWMenu tries the authenticated OHC API directly first. It falls back to the configured VLESS route only after a network failure, HTTP 403 or 451, or a recognized country-block response. Xray is therefore not started at plugin launch merely because a URL exists. WPA-sec, Yandex Maps, time synchronization, package downloads, and all other Pwnagotchi traffic always stay on the normal route.

ModeBehaviorUse when
autoDirect first; verified VLESS fallback only when direct OHC access is blocked or unreachable.Recommended for most installations.
directNever starts Xray and never attempts VLESS, even when a URL is stored.The authenticated OHC API works directly or Xray must remain disabled.
vlessEvery OHC request requires the verified Xray route; there is no direct fallback.Direct OHC traffic must never be attempted.

If `ohc_vless_url` is empty, the effective route is always direct. The OHC card shows both the active route (`DIRECT` or `VLESS`) and the selected policy. A route failure is displayed next to the persistent retry timer instead of being hidden behind an unexplained queue delay.

What PWMenu and Xray each do

ComponentResponsibility
PWMenuSelects direct or VLESS according to policy, parses the private URL, generates a protected runtime configuration, launches and monitors Xray, verifies the complete route, retries one clean restart, and keeps failed work in its queue.
XrayImplements VLESS, Reality/TLS transport, authentication, encryption, and the remote connection. It is a separate architecture-specific executable.
OHC clientUses the local HTTP proxy only for API v2 calls selected for VLESS; deduplication, batching, imports, results, and retry state remain inside PWMenu.

PWMenu deliberately does not download or update Xray. This avoids silently installing an executable as root and lets the owner select and audit the correct build for the Raspberry Pi architecture.

1. Decide whether VLESS is needed

Start with direct access and use an authenticated task-list synchronization. Opening the public website or receiving a redirect from an unauthenticated API request does not prove that the authenticated API is available. If direct authentication is blocked, install Xray, add the private URL, and use the recommended automatic policy.

main.plugins.A_pwmenu.ohc_route_mode = "auto"
main.plugins.A_pwmenu.ohc_vless_url = ""

2. Check the Raspberry Pi architecture

uname -m
getconf LONG_BIT
cat /etc/os-release | head

Typical values are `armv7l` for 32-bit ARM and `aarch64` for 64-bit ARM. The official XTLS installer recognizes both and downloads the matching release.

3. Install Xray with the official XTLS installer

Install the small download prerequisites, save the official script locally, review it if required, and then run it as root:

sudo apt update
sudo apt install -y ca-certificates curl unzip

cd /tmp
curl -fL \
  https://github.com/XTLS/Xray-install/raw/main/install-release.sh \
  -o xray-install.sh

less /tmp/xray-install.sh
sudo bash /tmp/xray-install.sh install
rm -f /tmp/xray-install.sh

The official installer selects the supported architecture, verifies the downloaded archive, and normally places the executable at `/usr/local/bin/xray`. See XTLS/Xray-install ↗ and the Project X installation guide ↗.

4. Verify the executable

test -x /usr/local/bin/xray
/usr/local/bin/xray version
ls -lh /usr/local/bin/xray

Do not continue until the version command exits successfully. `Exec format error` means the installed executable does not match the operating-system architecture.

5. Disable the standalone Xray service

The official installer can create its own `xray.service`. PWMenu does not use that service: it launches the same binary with a separate temporary configuration. If no other application uses the standalone service, disable it to avoid an unnecessary second Xray process:

sudo systemctl disable --now xray
systemctl is-enabled xray
systemctl is-active xray
Do not disable a shared Xray service blindly

If another application on the Pwnagotchi already depends on `xray.service`, leave it alone. PWMenu only requires the executable path and its own free loopback port.

6. Configure PWMenu

Place the private link only in `/etc/pwnagotchi/config.toml`. Do not add shell quotes inside the link itself and do not split it across several TOML lines.

main.plugins.A_pwmenu.module_ohc_enabled = true
main.plugins.A_pwmenu.ohc_enabled = true
main.plugins.A_pwmenu.ohc_api_key = "sk_REPLACE_ME"

main.plugins.A_pwmenu.ohc_route_mode = "auto"
main.plugins.A_pwmenu.ohc_vless_url = "vless://REPLACE_WITH_YOUR_PRIVATE_LINK"
main.plugins.A_pwmenu.ohc_vless_flow = "auto"
main.plugins.A_pwmenu.ohc_xray_binary = "/usr/local/bin/xray"
main.plugins.A_pwmenu.ohc_proxy_port = 10809
main.plugins.A_pwmenu.ohc_vless_startup_timeout = 8
main.plugins.A_pwmenu.ohc_vless_ready_delay = 2
main.plugins.A_pwmenu.ohc_vless_probe_timeout = 12
main.plugins.A_pwmenu.ohc_vless_probe_attempts = 3
main.plugins.A_pwmenu.ohc_vless_probe_interval = 300
OptionMeaning
ohc_route_mode`auto` tries direct then VLESS, `direct` disables Xray for OHC, and `vless` prohibits a direct fallback.
ohc_vless_urlComplete private VLESS share link. Empty forces the effective route to direct.
ohc_vless_flow`auto` preserves the link's flow. An explicit value overrides it; an empty string removes flow. Do not remove `xtls-rprx-vision` when the provider requires Vision.
ohc_xray_binaryAbsolute path to the executable. The default matches the official installer.
ohc_proxy_portLocal HTTP proxy port bound only to `127.0.0.1`. Change it if another local process already owns the port.
ohc_vless_startup_timeoutSeconds allowed for the Xray process and local listener to start; default 8, accepted range 2–30.
ohc_vless_ready_delayStabilization pause after the listener opens and before the Reality/TLS probe; default 2, accepted range 0–5.
ohc_vless_probe_timeoutTimeout for each real OHC HTTPS probe through Xray; default 12, accepted range 3–60.
ohc_vless_probe_attemptsProbe attempts before the route is rejected; default 3, accepted range 1–5.
ohc_vless_probe_intervalSeconds for which a successful end-to-end result is reused before another health check; default 300, accepted range 30–3600.

After editing configuration on a new installation, compile the plugin and restart only the Pwnagotchi service:

/home/pi/.pwn/bin/python3 -m py_compile \
  /usr/local/share/pwnagotchi/custom-plugins/A_pwmenu.py

sudo systemctl restart pwnagotchi
sudo systemctl status pwnagotchi --no-pager -l

7. Verify the selected route

In `auto` mode Xray is deliberately absent while direct OHC access works. Trigger an OHC synchronization and read the route shown in the OHC card. Expect an Xray process only after automatic fallback or when `ohc_route_mode = "vless"` is forced.

/usr/local/bin/xray version
ss -lntp | grep ':10809'
ps -ef | grep '[x]ray run -c /run/a_pwmenu_ohc_xray.json'
stat -c '%a %U:%G %n' /run/a_pwmenu_ohc_xray.json

grep -E 'A_pwmenu.*(VLESS|Xray|proxy)|OHC' \
  /etc/pwnagotchi/log/pwnagotchi.log | tail -100

When VLESS is active, the generated `/run/a_pwmenu_ohc_xray.json` file should have mode `600`. The proxy must listen on `127.0.0.1`, not on a LAN or public address. PWMenu never needs port 10809 exposed through a router or firewall.

A manual transport check can be made through the local proxy:

curl -I --max-time 20 \
  -x http://127.0.0.1:10809 \
  https://api.onlinehashcrack.com/v2

A redirect or other non-5xx response confirms transport only. PWMenu 1.4.2 performs this end-to-end HTTPS probe itself before using the route, but the final functional test remains a successful authenticated OHC status or `list_tasks` response.

8. Diagnose Reality and flow failures

  1. 1
    Read both route labels

    `AUTO · DIRECT` means direct access still works and Xray does not need to run. `AUTO · VLESS` means PWMenu detected a direct failure and successfully switched routes. The latest route error appears while the queue is backing off.

  2. 2
    Preserve the provider flow

    Keep `ohc_vless_flow = "auto"` first. If the shared URL itself has an incorrect flow, set the provider-required value explicitly. Reality Vision servers normally require `xtls-rprx-vision`; setting an empty string removes the flow and can break TLS.

  3. 3
    Distinguish listener from tunnel

    An open port 10809 proves only that Xray started. PWMenu must also complete its OHC HTTPS probe. A TCP-success/TLS-timeout combination usually points to the remote Reality route, flow, SNI, public key, short ID, or server availability.

  4. 4
    Allow one automatic recovery

    After a VLESS request fails, PWMenu restarts its managed Xray process once, repeats the end-to-end probe, and retries once. Repeated failure is queued with backoff rather than retried in a tight loop.

  5. 5
    Check port, time, and DNS

    If 10809 is occupied, select another high loopback port. Reality/TLS can also fail when system time is wrong, DNS cannot resolve the server, or SNI/public key does not match the provider link.

  6. 6
    Do not edit the private link by hand

    Preserve UUID, host, port, SNI, public key, short ID, fingerprint, transport, flow, and encoded query values exactly as supplied. Override one documented option at a time.

9. Disable VLESS or remove Xray

To keep the private URL stored but prohibit its use, choose direct mode. To remove the route completely, also clear the URL. After changing the configuration, restart only the Pwnagotchi service; PWMenu stops its managed process and removes the runtime configuration:

main.plugins.A_pwmenu.ohc_route_mode = "direct"
main.plugins.A_pwmenu.ohc_vless_url = ""

Remove Xray only after VLESS has been disabled and no other application uses it:

cd /tmp
curl -fL \
  https://github.com/XTLS/Xray-install/raw/main/install-release.sh \
  -o xray-install.sh
sudo bash /tmp/xray-install.sh remove
rm -f /tmp/xray-install.sh
Treat the VLESS URL as a password

It contains connection credentials. Keep it only in root-owned `config.toml`. PWMenu does not copy the link into its persistent state, exports, normal logs, or the repository. Never paste it into an issue, screenshot, Telegram message, shell history, or diagnostic bundle.

Do not delete state to bypass a 429

The server-side limit will remain while your local queue and deduplication context are lost. Wait for the automatic retry.

13 / geo

GPS, PwnDroid, and Bluetooth

Live location priority is PwnDroid WebSocket → Browser Geolocation → GPSD. A recent fix is saved beside a new PCAP as a sidecar file.

Recommended topology

Android phoneBluetooth tetheringMobile internetPwnDroid :8080
Pwnagotchi bnep0Dynamic IPv4Default routeA_pwmenu client

Configure PwnDroid

  1. Pair the phone and Pwnagotchi.
  2. Enable Bluetooth tethering on Android.
  3. Allow Location and WebSocket sharing in PwnDroid.
  4. Find the phone MAC with bluetoothctl devices.
  5. Leave `pwndroid_gateway` empty for dynamic discovery.
main.plugins.A_pwmenu.pwndroid_ws_enabled = true
main.plugins.A_pwmenu.pwndroid_mac = "AA:BB:CC:DD:EE:FF"
main.plugins.A_pwmenu.pwndroid_gateway = ""
main.plugins.A_pwmenu.pwndroid_port = 8080

GPS sidecar example

{
  "Latitude": 53.900000,
  "Longitude": 27.566700,
  "Accuracy": 12.5,
  "Timestamp": 1784550000,
  "CaptureTimestamp": 1784550001,
  "GPSAge": 1,
  "GPSStale": false,
  "Source": "pwndroid"
}

The physical display shows `G C` for a recent coordinate fix and `G -` when no recent fix exists. An open WebSocket without coordinates still shows `G -`.

Why browser GPS may fail

Mobile browsers normally expose Geolocation only in a secure HTTPS context. PwnDroid is generally more reliable when the page is opened through a private `http://10.x.x.x:8080` address.

14 / storage

Files and persistent data

PWMenu has no separate capture-directory option. During `on_ready(agent)` it reads `bettercap.handshakes` from the active Pwnagotchi configuration, resolves the path, rejects the filesystem root, creates the directory when necessary, and only then starts capture analysis, QuickDic, quality scanning or cloud queues.

PathPurpose
/usr/local/share/pwnagotchi/custom-plugins/A_pwmenu.pyPlugin source
<bettercap.handshakes>/*.pcapConcrete capture collection indexed by PWMenu
<bettercap.handshakes>/.a_pwmenu_data.jsonXP, map index, activity, quality cache, OHC/WPA-sec metadata, signatures, conflicts and durable queues
<bettercap.handshakes>/.a_pwmenu_data.json.bakCrash-recovery state snapshot written atomically with the primary file
<bettercap.handshakes>/.a_pwmenu_data.json.pre-config-path-migrationOne-time copy of a pre-existing configured state before legacy data is merged
<bettercap.handshakes>/.a_pwmenu_migration_*.jsonPassword-free per-source completion markers that prevent repeated migration
<bettercap.handshakes>/.a_pwmenu_ohc_export.jsonPassword-free identities from imported OHC tasks, used to prevent duplicate submissions
/run/a_pwmenu_ohc_xray.jsonEphemeral mode-600 Xray configuration generated only when OHC VLESS is enabled
/usr/local/bin/xrayDefault path of the optional external Xray executable
<bettercap.handshakes>/onlinehashcrack.cracked.potfileImported OHC results
<bettercap.handshakes>/handshake-lab.cracked.potfileHandshake Lab results with their source preserved
<bettercap.handshakes>/manual.potfileLocally verified manual passwords
<bettercap.handshakes>/*.gps.json / *.geo.jsonCapture coordinates
<bettercap.handshakes>/*.pcap.crackedQuickDic result
config.toml.pwmenu-whitelist.bakConfiguration backup before a whitelist update

How upgrade migration decides what to keep

  1. DiscoverInspect system-account home directories for an older `handshakes` folder containing PWMenu state or potfiles; no username-specific path list is used.
  2. Merge credentialsCombine potfile lines and deduplicate by exact WPA identity without stripping punctuation from ESSIDs or passwords.
  3. Merge stateKeep unique records from both locations. When both contain the same scalar or nested field, the file with the newer modification time has priority.
  4. PreserveLeave the old directory untouched and save the configured state once as `.pre-config-path-migration` before changing it.
  5. RememberWrite a hashed completion marker so a stale directory cannot overwrite later map, history, queue or XP changes on another restart.
Do not delete legacy storage immediately after an upgrade

First check the Cracked count, map points, Activity History, OHC/WPA-sec status and exported passwords. The marker is deliberately not a replacement for a user backup.

State is written atomically with a recovery copy and parent-directory synchronization. Do not edit the state file while Pwnagotchi is running.

15 / recovery

Backup and restore

Use Download backup in Other for the portable 1.4.2 archive. It contains every PCAP from the configured handshake directory, GPS/MAP sidecars, PWMenu state and activity, all credential potfiles, the password-free OHC export snapshot, configuration, and a manifest. Multiple captures of the same AP are retained in a complete backup; the one-best-capture rule applies to cracking exports, not disaster recovery.

Archive safety model

  • The archive is intentionally unencrypted. Store and transfer it as sensitive data.
  • Large content is streamed through bounded disk-backed temporary files instead of being assembled in Raspberry Pi RAM.
  • The manifest records the backup format version, plugin version, logical destination and SHA-256 of every included file.
  • Restore accepts only a fixed set of destinations, rejects traversal and unknown paths, verifies every size and hash before writing, and uses atomic replacement.
  • Configuration and state recovery copies are created before replacement. A successful restore can restart only the Pwnagotchi service when `backup_restart_after_restore` is enabled.

Restore from the Web UI

  1. Open OtherUse Import and select a PWMenu backup archive.
  2. Wait for validationNo live file is replaced until the complete manifest and every payload digest have passed.
  3. Review the resultConfirm restored file counts, then allow only the Pwnagotchi service to restart if the configuration requires activation.
  4. VerifyCheck Cracked, Handshakes, Map, Activity History and both cloud integration panels.

Additional manual copy

For an offline second copy, first read the real path and then substitute it below:

grep '^bettercap.handshakes' /etc/pwnagotchi/config.toml

sudo systemctl stop pwnagotchi
sudo tar -czf /root/a-pwmenu-manual-backup.tar.gz \
  /usr/local/share/pwnagotchi/custom-plugins/A_pwmenu.py \
  /etc/pwnagotchi/config.toml \
  /configured/handshake/path
sudo systemctl start pwnagotchi

Remove API keys, VLESS links, passwords, PCAPs, GPS data and private SSIDs before sharing any backup archive.

16 / security

Security

Enable Web UI authentication

ui.web.auth = true
ui.web.username = "change-me"
ui.web.password = "use-a-strong-password"
  • Never expose port 8080 directly to the public internet.
  • Never commit real `wpa_sec_key`, `ohc_api_key`, `ohc_vless_url`, device MACs, or coordinates.
  • POST actions use the current Flask session and a CSRF token when Flask-WTF is available.
  • File routes accept only a local `.pcap` basename; traversal, slashes, backslashes, and NUL are rejected.
  • `hcxpcapngtool` is executed with an argument list and never through a shell.
  • The optional OHC proxy listens only on `127.0.0.1`; its generated Xray configuration is mode 600 and removed when no longer needed.
  • Hide SSIDs, BSSIDs, passwords, and locations before publishing screenshots.
# Recommended Git exclusions
config.toml
*.pcap
*.pcapng
*.22000
*.hc22000
*.potfile
*.cracked
*.gps.json
*.geo.json
.a_pwmenu_data.json*
__pycache__/

17 / troubleshooting

Troubleshooting

Basic service health check
sudo systemctl status pwnagotchi --no-pager -l
sudo journalctl -u pwnagotchi -n 100 --no-pager
tail -100 /etc/pwnagotchi/log/pwnagotchi.log

grep -E 'A_pwmenu|OHC|WPA-sec|PwnDroid|GPSD' \
  /etc/pwnagotchi/log/pwnagotchi.log | tail -150
The plugin does not appear in the Web UI
/home/pi/.pwn/bin/python3 -m py_compile \
  /usr/local/share/pwnagotchi/custom-plugins/A_pwmenu.py

grep -n 'main.plugins.A_pwmenu' /etc/pwnagotchi/config.toml
sudo systemctl restart pwnagotchi

The file must be named exactly `A_pwmenu.py` and be readable by the Pwnagotchi service.

The page is slow over Bluetooth
ip -4 addr show bnep0
ip route
ss -lntp | grep ':8080'

# Recommended Raspberry Pi Web UI settings
grep -n 'web_gzip_level\|web_page_size\|web_background_preload' \
  /etc/pwnagotchi/config.toml

Version 1.4.0 sends a small shell first, loads the selected tab on demand, reserves the final list height with stable placeholders, fills the first 24 cards, and continues in small background batches. Other tabs and the map preload during browser idle time. Content-hashed CSS and JavaScript use immutable caching while the UI revision forces one refresh after an update. Keep `web_gzip_level = 1`; reduce `web_page_size` or disable `web_background_preload` only when the Bluetooth link is exceptionally weak.

Passwords, map points, or history look missing after the 1.4.0 update
grep '^bettercap.handshakes' /etc/pwnagotchi/config.toml
grep 'Handshake storage\|legacy storage' \
  /etc/pwnagotchi/log/pwnagotchi.log | tail -30

# Substitute the configured path from the first command
ls -lah /configured/handshake/path/.a_pwmenu*

PWMenu does not delete the former directory. Confirm that the configured path contains the merged state, its `.bak`, the one-time `.pre-config-path-migration` copy and a `.a_pwmenu_migration_*.json` marker. If the configured state is damaged, stop only the Pwnagotchi service, preserve both directories, and restore the pre-migration copy before restarting.

The Web UI does not open over Bluetooth
ip -4 addr show bnep0
ip route
nmcli connection show --active
ss -lntp | grep ':8080'

Use the address assigned to `bnep0`; it is not necessarily `10.0.0.2`. Use `http://` unless TLS was configured separately.

The display shows G - while the phone is connected
ip route show default
ip neigh show dev bnep0
grep 'PwnDroid GPS' /etc/pwnagotchi/log/pwnagotchi.log | tail

Check Android Location permission, WebSocket sharing, battery optimization, the phone MAC, the port, and whether Android currently has a real GPS fix.

A recovered network is visible but its password is not shown on the physical display
grep -n 'display_password_visible\|module_display_password' \
  /etc/pwnagotchi/config.toml

grep -E 'A_pwmenu|Password display' \
  /etc/pwnagotchi/log/pwnagotchi.log | tail -80

PWMenu requires one unambiguous enabled credential for the exact visible BSSID. Conflicting passwords are deliberately skipped. Legacy name-only records work only when their exact or punctuation-normalized SSID resolves to one password. The visible state expires after `display_password_visible_ttl`; the normal latest recovered credential is then shown.

Capture Cleanup keeps saying that the report changed

Install 1.4.1 or later. This release ignores candidate ordering and refreshed presentation text when comparing the reviewed set. A changed PCAP path, file signature, or cleanup category still rejects confirmation intentionally. Reload Other, review the new candidate list, and confirm again only after checking the filenames.

The OHC website opens but the authenticated API returns 403

A website and its authenticated API can apply different jurisdiction rules. First test direct access with an authenticated operation. If the API is restricted, configure the private URL and automatic route:

main.plugins.A_pwmenu.ohc_route_mode = "auto"
main.plugins.A_pwmenu.ohc_vless_url = "vless://REPLACE_WITH_YOUR_PRIVATE_LINK"

ss -lntp | grep ':10809'
curl -I --max-time 20 \
  -x http://127.0.0.1:10809 \
  https://api.onlinehashcrack.com/v2

In `auto` mode the process starts only after PWMenu observes the direct API failure, so trigger OHC synchronization before checking the port. A redirect from unauthenticated `curl` proves transport only; use the OHC card or authenticated task-list result for the final check.

OHC VLESS is configured but Xray does not start

If the OHC card shows `AUTO · DIRECT`, this is expected: direct access works and automatic mode keeps Xray stopped. If fallback was required or `ohc_route_mode = "vless"` is configured, inspect the executable and generated runtime file:

/usr/local/bin/xray version
ls -l /run/a_pwmenu_ohc_xray.json
grep -E 'A_pwmenu.*(VLESS|Xray|proxy)|OHC' \
  /etc/pwnagotchi/log/pwnagotchi.log | tail -100

Confirm that `ohc_xray_binary` is executable and matches the Raspberry Pi architecture. Preserve the link flow with `ohc_vless_flow = "auto"`; if the provider requires Vision, use `xtls-rprx-vision`. Set an empty flow only when the provider explicitly configured the account without flow. Never paste the private URL into logs or an issue.

Xray listens locally but OHC remains queued

A listening port proves only the first half of the path. Reality/TLS or the remote server can still time out. PWMenu 1.4.2 performs a real OHC HTTPS probe, restarts Xray once, then preserves the files and schedules backoff if the route remains unavailable.

ss -lntp | grep ':10809'
grep -E 'OHC VLESS|OHC direct route|OHC synchronization|retry' \
  /etc/pwnagotchi/log/pwnagotchi.log | tail -120

Check the OHC card's route error, provider flow, SNI, public key, short ID, DNS, clock, and remote server availability. Do not delete queue state: the same files will resume automatically after the retry deadline.

OHC is paused or rate limited

This is durable backoff. Inspect the remaining delay:

/home/pi/.pwn/bin/python3 - <<'PY'
import json, time
path = '/configured/handshake/path/.a_pwmenu_data.json'
with open(path) as state_file:
    data = json.load(state_file)
retry_at = float(data.get('ohc_retry_at', 0) or 0)
print('remaining:', max(0, int(retry_at - time.time())), 'seconds')
print('reason:', data.get('ohc_retry_reason', ''))
PY
Mode 22000 output is empty
/usr/bin/hcxpcapngtool \
  -o /tmp/test.22000 \
  /configured/handshake/path/EXAMPLE.pcap

wc -l /tmp/test.22000
rm -f /tmp/test.22000

A valid PCAP container does not guarantee a complete EAPOL exchange or usable PMKID. If the output contains zero lines, the file cannot be tested by Hashcat and PWMenu will not send it to OHC. Raw M1/M2/M3/M4 counters can come from incompatible sessions; recapture the access point and review it again.

A manual password is not saved

PWMenu stores a manual password only after it is cryptographically confirmed against the exact capture. Review the rejection in the Web UI or log:

grep 'Manual password' \
  /etc/pwnagotchi/log/pwnagotchi.log | tail -30

If the message says that no usable WPA/PMKID hash exists, the PCAP is incomplete. The plugin cannot determine whether the entered password is correct, so it rejects the write and asks for a new capture.

The map is empty
  • Check for `.gps.json` or `.geo.json` files.
  • Review the GPS status panel and No GPS list.
  • Check internet access for Yandex Maps; the offline fallback remains available.
  • An old PCAP does not receive the current fix when `gps_assign_window` is exceeded.
The state file is damaged

The plugin first tries the newest valid primary or backup copy. If both are damaged:

sudo systemctl stop pwnagotchi
sudo mv /configured/handshake/path/.a_pwmenu_data.json \
  /configured/handshake/path/.a_pwmenu_data.json.bad
sudo systemctl start pwnagotchi

PCAP and potfile data remain, but XP and cached locations will be reset or rebuilt.

Time synchronization fails
curl -I --max-time 10 \
  http://connectivitycheck.gstatic.com/generate_204
date -u

The plugin reads the HTTP `Date` header and applies it with the system `date` command.

18 / api surface

HTTP routes

Base path: /plugins/A_pwmenu/. Do not call POST routes outside the Web UI without a valid session and CSRF token.

MethodRoutePurpose
GET/Main page
GETassets/<revision>/app.css|app.jsImmutable revisioned interface resources
GETapi/tab/<name>On-demand workspace fragment and paginated card data
GETapi/details/<tab>Compressed background preload of expandable card details
GETapi/detail/<tab>/<id>Race-safe single-card detail fallback
GETapi/historyBounded 24-hour Activity History payload
GETapi/repair-password-conflict/<job>Background verify-and-fix progress
GETdownload/<pcap>Original PCAP
GETdownload-22000/<pcap>Convert and download mode 22000
GETdownload-cluster/<csv>ZIP of selected files
GETdownload-zipZIP of all PCAPs
GETdownload-uncrackedBest crackable unresolved PCAP per exact BSSID after local key verification
GETexport-passwordsCombined password list
GETbackup-exportManifested portable backup stream
POSTbackup-restoreValidate and atomically restore a PWMenu archive
POSTwpa-sec-upload(-cluster)Submit to WPA-sec
POSTwpa-sec-syncUpload missing WPA-sec captures and download current results
POSTohc-upload-clusterSubmit selected files to OHC
POSTohc-upload-all-missingFull reconciliation
POSTphone-gpsReceive browser coordinates
POSTadd/update/delete-passwordVerify and manage local credentials without reloading the workspace
POSTcapture-map-setPlace, move, group, or clear coordinates for one concrete PCAP
POSTrepair-password-conflictStart local verification for one exact BSSID conflict
POSTdelete-fileDelete one PCAP and derivatives
POSTwhitelist-add/removeManage the Pwnagotchi whitelist
POSTwhitelist-add-excellentAdd only server-verified Excellent networks from a map group
POSTclean-capturesRun confirmation-bound cleanup
POSTimportImport OHC JSON or CSV
POSTsync-timeSynchronize system time

ready / field use

Ready for the field

Start with the minimum configuration, then enable integrations one at a time. Check the logs after each step so network failures, API rate limits, and individual PCAP quality problems remain easy to distinguish.