mirror of
https://github.com/agessaman/meshcore-packet-capture.git
synced 2026-04-20 23:23:37 +00:00
- Bump version in install.ps1 and install.sh to 1.2. - Remove unnecessary debug logging in Bluetooth pairing functions. - Enhance error handling in connection health checks for TCP with SDK auto-reconnect. - Streamline MQTT configuration checks in install.sh.
1340 lines
65 KiB
PowerShell
1340 lines
65 KiB
PowerShell
# ============================================================================
|
|
# MeshCore Packet Capture - Interactive Installer for Windows (Fixed Version)
|
|
# ============================================================================
|
|
|
|
param(
|
|
[string]$ConfigUrl = "",
|
|
[string]$Repo = "agessaman/meshcore-packet-capture",
|
|
[string]$Branch = "main"
|
|
)
|
|
|
|
# Script configuration
|
|
$ScriptVersion = "1.2"
|
|
$ErrorActionPreference = "Stop"
|
|
|
|
# Global variables
|
|
$InstallDir = ""
|
|
$ConnectionType = ""
|
|
$SelectedBleDevice = ""
|
|
$SelectedBleName = ""
|
|
$SelectedSerialDevice = ""
|
|
$TcpHost = ""
|
|
$TcpPort = ""
|
|
$Iata = ""
|
|
$ServiceInstalled = $false
|
|
$DockerInstalled = $false
|
|
$UpdatingExisting = $false
|
|
|
|
# Create version info file with installer version and git hash
|
|
function New-VersionInfo {
|
|
$gitHash = "unknown"
|
|
$gitBranch = $Branch
|
|
$gitRepo = $Repo
|
|
|
|
# Try to resolve the branch/tag to a specific commit hash via GitHub API
|
|
try {
|
|
$apiUrl = "https://api.github.com/repos/$gitRepo/commits/$gitBranch"
|
|
$response = Invoke-WebRequest -Uri $apiUrl -UseBasicParsing
|
|
$json = $response.Content | ConvertFrom-Json
|
|
$gitHash = $json.sha.Substring(0, 7)
|
|
}
|
|
catch {
|
|
Write-Host "WARNING: Could not fetch git hash from GitHub API" -ForegroundColor Yellow
|
|
}
|
|
|
|
# Create version info JSON file
|
|
$versionInfo = @{
|
|
installer_version = $ScriptVersion
|
|
git_hash = $gitHash
|
|
git_branch = $gitBranch
|
|
git_repo = $gitRepo
|
|
install_date = (Get-Date).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ")
|
|
} | ConvertTo-Json -Depth 2
|
|
|
|
$versionInfo | Out-File -FilePath (Join-Path $InstallDir ".version_info") -Encoding UTF8
|
|
|
|
Write-Host "INFO: Version info saved: $ScriptVersion-$gitHash ($gitRepo@$gitBranch)" -ForegroundColor Blue
|
|
}
|
|
|
|
# Helper function for Windows Bluetooth API pairing
|
|
function Invoke-BluetoothPairing {
|
|
param(
|
|
[string]$DeviceAddress,
|
|
[string]$Pin
|
|
)
|
|
|
|
try {
|
|
# Use Windows Bluetooth API via .NET
|
|
Add-Type -TypeDefinition @"
|
|
using System;
|
|
using System.Runtime.InteropServices;
|
|
using System.Text;
|
|
|
|
public class BluetoothAPI {
|
|
[DllImport("bthprops.cpl", CharSet = CharSet.Unicode)]
|
|
public static extern int BluetoothAuthenticateDevice(IntPtr hwndParent, IntPtr hRadio, ref BLUETOOTH_DEVICE_INFO pbtdi, string pszPasskey, int ulPasskeyLength);
|
|
|
|
[DllImport("bthprops.cpl", CharSet = CharSet.Unicode)]
|
|
public static extern int BluetoothSetServiceState(IntPtr hRadio, ref BLUETOOTH_DEVICE_INFO pbtdi, ref Guid pGuidService, int dwServiceFlags);
|
|
}
|
|
|
|
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
|
public struct BLUETOOTH_DEVICE_INFO {
|
|
public int dwSize;
|
|
public long Address;
|
|
public int ulClassofDevice;
|
|
public bool fConnected;
|
|
public bool fRemembered;
|
|
public bool fAuthenticated;
|
|
public long ftLastSeen;
|
|
public long ftLastUsed;
|
|
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 248)]
|
|
public string szName;
|
|
}
|
|
"@ -ErrorAction SilentlyContinue
|
|
|
|
# Try to pair using Windows API
|
|
# This is a simplified approach - in practice, you'd need more complex API calls
|
|
# For now, we'll return false to fall back to other methods
|
|
return $false
|
|
|
|
} catch {
|
|
return $false
|
|
}
|
|
}
|
|
|
|
# Helper function for bluetoothctl pairing
|
|
function Invoke-BluetoothctlPairing {
|
|
param(
|
|
[string]$DeviceAddress,
|
|
[string]$Pin
|
|
)
|
|
|
|
try {
|
|
# Use bluetoothctl to pair
|
|
$pairingScript = @"
|
|
#!/bin/bash
|
|
bluetoothctl << EOF
|
|
agent on
|
|
default-agent
|
|
scan on
|
|
sleep 5
|
|
scan off
|
|
pair $DeviceAddress
|
|
$Pin
|
|
trust $DeviceAddress
|
|
untrust $DeviceAddress
|
|
exit
|
|
EOF
|
|
"@
|
|
|
|
# Save script to temp file
|
|
$tempScript = [System.IO.Path]::GetTempFileName() + ".sh"
|
|
$pairingScript | Out-File -FilePath $tempScript -Encoding UTF8
|
|
|
|
# Try to run via WSL or Git Bash
|
|
$wslResult = wsl bash $tempScript 2>&1
|
|
$gitBashResult = bash $tempScript 2>&1
|
|
|
|
# Clean up
|
|
Remove-Item $tempScript -ErrorAction SilentlyContinue
|
|
|
|
if ($wslResult -match "Pairing successful" -or $gitBashResult -match "Pairing successful") {
|
|
return $true
|
|
} else {
|
|
return $false
|
|
}
|
|
|
|
} catch {
|
|
return $false
|
|
}
|
|
}
|
|
|
|
# Function to configure additional MQTT brokers
|
|
function Configure-AdditionalMqttBrokers {
|
|
$envLocal = Join-Path $InstallDir ".env.local"
|
|
|
|
# Find next available broker number
|
|
$nextBroker = 2
|
|
while ((Get-Content $envLocal -ErrorAction SilentlyContinue) -match "PACKETCAPTURE_MQTT${nextBroker}_ENABLED=") {
|
|
$nextBroker++
|
|
}
|
|
|
|
Write-Host ""
|
|
Write-Host "INFO: Configuring Additional MQTT Brokers" -ForegroundColor Blue
|
|
Write-Host ""
|
|
|
|
$numBrokers = Read-Host "How many additional brokers would you like to configure? [1-3]"
|
|
if ($numBrokers -notmatch '^[1-3]$') {
|
|
$numBrokers = "1"
|
|
}
|
|
|
|
for ($i = 1; $i -le [int]$numBrokers; $i++) {
|
|
$brokerNum = $nextBroker + $i - 1
|
|
Configure-SingleMqttBroker $brokerNum
|
|
}
|
|
}
|
|
|
|
# Helper function to read a value from .env.local in the install directory
|
|
# Also checks backup files if the main file doesn't exist, doesn't have the value, or has placeholder values
|
|
function Read-EnvValue {
|
|
param([string]$Key)
|
|
# Ensure we use the install directory, not the working directory
|
|
if (-not $InstallDir) {
|
|
return ""
|
|
}
|
|
$envFile = Join-Path $InstallDir ".env.local"
|
|
$value = ""
|
|
|
|
# First try the main .env.local file
|
|
if (Test-Path $envFile) {
|
|
$line = Get-Content $envFile | Where-Object { $_ -match "^${Key}=" }
|
|
if ($line) {
|
|
$value = $line -replace "^${Key}=", ""
|
|
$value = $value.Trim('"', "'")
|
|
# If we found a value and it's not a placeholder (XXX, empty, etc.), use it
|
|
if ($value -and $value -ne "XXX") {
|
|
return $value
|
|
}
|
|
}
|
|
}
|
|
|
|
# If not found or placeholder value, check backup files (most recent first)
|
|
$backupFiles = Get-ChildItem -Path $InstallDir -Filter ".env.local.backup-*" -ErrorAction SilentlyContinue |
|
|
Sort-Object LastWriteTime -Descending |
|
|
Select-Object -First 1
|
|
if ($backupFiles) {
|
|
foreach ($backupFile in $backupFiles) {
|
|
$line = Get-Content $backupFile.FullName | Where-Object { $_ -match "^${Key}=" }
|
|
if ($line) {
|
|
$backupValue = $line -replace "^${Key}=", ""
|
|
$backupValue = $backupValue.Trim('"', "'")
|
|
# Only return if we have a non-placeholder value
|
|
if ($backupValue -and $backupValue -ne "XXX") {
|
|
return $backupValue
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
# If we got here, return the value from main file (even if XXX) or empty
|
|
# This preserves the behavior for cases where we want to know if it's XXX
|
|
return $value
|
|
}
|
|
|
|
# Function to configure JWT token options (owner public key and client agent)
|
|
function Configure-JwtOptions {
|
|
$envLocal = Join-Path $InstallDir ".env.local"
|
|
|
|
# Read existing values as defaults
|
|
$existingOwnerKey = Read-EnvValue "PACKETCAPTURE_OWNER_PUBLIC_KEY"
|
|
$existingOwnerEmail = Read-EnvValue "PACKETCAPTURE_OWNER_EMAIL"
|
|
|
|
Write-Host ""
|
|
Write-Host "INFO: JWT Token Configuration (Optional)" -ForegroundColor Blue
|
|
Write-Host ""
|
|
Write-Host "INFO: You can optionally configure owner information for JWT tokens:" -ForegroundColor Blue
|
|
Write-Host ""
|
|
Write-Host "INFO: 1. Owner Public Key: The public key of the owner of the MQTT observer" -ForegroundColor Blue
|
|
Write-Host "INFO: (64 hex characters, same length as repeater public key)" -ForegroundColor Blue
|
|
Write-Host ""
|
|
Write-Host "INFO: 2. Owner Email: Email address of the owner for Let's Mesh Analyzer (optional)" -ForegroundColor Blue
|
|
Write-Host ""
|
|
Write-Host "INFO: Note: Client agent is automatically set to the build string (same as status messages)" -ForegroundColor Blue
|
|
Write-Host ""
|
|
|
|
# Prompt for owner public key
|
|
$configureOwner = Read-Host "Would you like to configure an owner public key for JWT tokens? (y/N)"
|
|
if ($configureOwner -match '^[yY]') {
|
|
while ($true) {
|
|
$ownerKey = Read-Host "Enter owner public key (64 hex characters)" $existingOwnerKey
|
|
$ownerKey = $ownerKey.ToUpper().Replace(" ", "").Replace("-", "")
|
|
|
|
if (-not $ownerKey) {
|
|
Write-Host "WARNING: Owner public key cannot be empty" -ForegroundColor Yellow
|
|
$skip = Read-Host "Skip owner public key configuration? (Y/n)"
|
|
if ($skip -notmatch '^[nN]') {
|
|
break
|
|
}
|
|
}
|
|
elseif ($ownerKey.Length -ne 64) {
|
|
Write-Host "ERROR: Owner public key must be exactly 64 hex characters (you entered $($ownerKey.Length))" -ForegroundColor Red
|
|
$tryAgain = Read-Host "Try again? (Y/n)"
|
|
if ($tryAgain -match '^[nN]') {
|
|
break
|
|
}
|
|
}
|
|
elseif ($ownerKey -notmatch '^[0-9A-F]{64}$') {
|
|
Write-Host "ERROR: Owner public key must contain only hexadecimal characters (0-9, A-F)" -ForegroundColor Red
|
|
$tryAgain = Read-Host "Try again? (Y/n)"
|
|
if ($tryAgain -match '^[nN]') {
|
|
break
|
|
}
|
|
}
|
|
else {
|
|
Add-Content -Path $envLocal -Value "PACKETCAPTURE_OWNER_PUBLIC_KEY=$ownerKey"
|
|
Write-Host "SUCCESS: Owner public key configured" -ForegroundColor Green
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
# Prompt for owner email
|
|
Write-Host ""
|
|
$configureEmail = Read-Host "Would you like to configure an owner email for Let's Mesh Analyzer? (y/N)"
|
|
if ($configureEmail -match '^[yY]') {
|
|
while ($true) {
|
|
$email = Read-Host "Enter owner email address" $existingOwnerEmail
|
|
$email = $email.ToLower().Replace(" ", "")
|
|
|
|
if (-not $email) {
|
|
Write-Host "WARNING: Email cannot be empty" -ForegroundColor Yellow
|
|
$skip = Read-Host "Skip email configuration? (Y/n)"
|
|
if ($skip -notmatch '^[nN]') {
|
|
break
|
|
}
|
|
}
|
|
else {
|
|
# Validate email format using regex
|
|
$emailPattern = '^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
|
|
if ($email -match $emailPattern) {
|
|
Add-Content -Path $envLocal -Value "PACKETCAPTURE_OWNER_EMAIL=$email"
|
|
Write-Host "SUCCESS: Owner email configured: $email" -ForegroundColor Green
|
|
break
|
|
}
|
|
else {
|
|
Write-Host "ERROR: Invalid email format. Please enter a valid email address (e.g., user@example.com)" -ForegroundColor Red
|
|
$tryAgain = Read-Host "Try again? (Y/n)"
|
|
if ($tryAgain -match '^[nN]') {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
# Client agent is automatically set to the build string (same as status messages)
|
|
# No user configuration needed
|
|
}
|
|
|
|
# Function to configure a single MQTT broker
|
|
function Configure-SingleMqttBroker {
|
|
param([int]$BrokerNum)
|
|
|
|
$envLocal = Join-Path $InstallDir ".env.local"
|
|
|
|
Write-Host ""
|
|
Write-Host "INFO: Configuring MQTT Broker $BrokerNum" -ForegroundColor Blue
|
|
Write-Host ""
|
|
|
|
# Read existing values from install directory's .env.local as defaults
|
|
$existingServer = Read-EnvValue "PACKETCAPTURE_MQTT${BrokerNum}_SERVER"
|
|
$existingPort = Read-EnvValue "PACKETCAPTURE_MQTT${BrokerNum}_PORT"
|
|
$existingTokenAudience = Read-EnvValue "PACKETCAPTURE_MQTT${BrokerNum}_TOKEN_AUDIENCE"
|
|
$existingUsername = Read-EnvValue "PACKETCAPTURE_MQTT${BrokerNum}_USERNAME"
|
|
$existingPassword = Read-EnvValue "PACKETCAPTURE_MQTT${BrokerNum}_PASSWORD"
|
|
|
|
# Server configuration
|
|
$server = Read-Host "MQTT Server hostname/IP" $existingServer
|
|
if (-not $server) {
|
|
Write-Host "WARNING: Server hostname required - skipping broker $BrokerNum" -ForegroundColor Yellow
|
|
return
|
|
}
|
|
|
|
Add-Content -Path $envLocal -Value ""
|
|
Add-Content -Path $envLocal -Value "# MQTT Broker $BrokerNum"
|
|
Add-Content -Path $envLocal -Value "PACKETCAPTURE_MQTT${BrokerNum}_ENABLED=true"
|
|
Add-Content -Path $envLocal -Value "PACKETCAPTURE_MQTT${BrokerNum}_SERVER=$server"
|
|
|
|
# Port configuration
|
|
$portDefault = if ($existingPort) { $existingPort } else { "1883" }
|
|
$port = Read-Host "Port" $portDefault
|
|
if (-not $port) {
|
|
$port = "1883"
|
|
}
|
|
if (-not ($port -match '^\d+$') -or [int]$port -lt 1 -or [int]$port -gt 65535) {
|
|
Write-Host "WARNING: Invalid port number, using default 1883" -ForegroundColor Yellow
|
|
$port = "1883"
|
|
}
|
|
Add-Content -Path $envLocal -Value "PACKETCAPTURE_MQTT${BrokerNum}_PORT=$port"
|
|
|
|
# Transport configuration
|
|
Write-Host ""
|
|
$useWebsockets = Read-Host "Use WebSockets transport? (y/N)"
|
|
if ($useWebsockets -match '^[yY]') {
|
|
Add-Content -Path $envLocal -Value "PACKETCAPTURE_MQTT${BrokerNum}_TRANSPORT=websockets"
|
|
}
|
|
|
|
# TLS configuration
|
|
Write-Host ""
|
|
$useTls = Read-Host "Use TLS/SSL encryption? (y/N)"
|
|
if ($useTls -match '^[yY]') {
|
|
Add-Content -Path $envLocal -Value "PACKETCAPTURE_MQTT${BrokerNum}_USE_TLS=true"
|
|
|
|
$verifyTls = Read-Host "Verify TLS certificates? (Y/n)"
|
|
if ($verifyTls -match '^[nN]') {
|
|
Add-Content -Path $envLocal -Value "PACKETCAPTURE_MQTT${BrokerNum}_TLS_VERIFY=false"
|
|
}
|
|
}
|
|
|
|
# Authentication configuration
|
|
Write-Host ""
|
|
Write-Host "Authentication method:" -ForegroundColor Blue
|
|
Write-Host " 1) Username/Password" -ForegroundColor Blue
|
|
Write-Host " 2) MeshCore Auth Token" -ForegroundColor Blue
|
|
Write-Host " 3) None (anonymous)" -ForegroundColor Blue
|
|
|
|
$authChoice = Read-Host "Choose authentication method [1-3]"
|
|
|
|
switch ($authChoice) {
|
|
"1" {
|
|
$username = Read-Host "Username" $existingUsername
|
|
if ($username) {
|
|
Add-Content -Path $envLocal -Value "PACKETCAPTURE_MQTT${BrokerNum}_USERNAME=$username"
|
|
$password = Read-Host "Password" $existingPassword
|
|
if ($password) {
|
|
Add-Content -Path $envLocal -Value "PACKETCAPTURE_MQTT${BrokerNum}_PASSWORD=$password"
|
|
}
|
|
}
|
|
}
|
|
"2" {
|
|
Add-Content -Path $envLocal -Value "PACKETCAPTURE_MQTT${BrokerNum}_USE_AUTH_TOKEN=true"
|
|
$tokenAudience = Read-Host "Token audience (optional)" $existingTokenAudience
|
|
if ($tokenAudience) {
|
|
Add-Content -Path $envLocal -Value "PACKETCAPTURE_MQTT${BrokerNum}_TOKEN_AUDIENCE=$tokenAudience"
|
|
}
|
|
}
|
|
"3" {
|
|
# No authentication
|
|
}
|
|
default {
|
|
Write-Host "WARNING: Invalid choice, using username/password" -ForegroundColor Yellow
|
|
$username = Read-Host "Username" $existingUsername
|
|
if ($username) {
|
|
Add-Content -Path $envLocal -Value "PACKETCAPTURE_MQTT${BrokerNum}_USERNAME=$username"
|
|
$password = Read-Host "Password" $existingPassword
|
|
if ($password) {
|
|
Add-Content -Path $envLocal -Value "PACKETCAPTURE_MQTT${BrokerNum}_PASSWORD=$password"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
# Topic configuration
|
|
Write-Host ""
|
|
Write-Host "INFO: MQTT Topic Configuration for Broker $BrokerNum" -ForegroundColor Blue
|
|
Write-Host "INFO: MQTT topics define where different types of data are published." -ForegroundColor Blue
|
|
Write-Host "INFO: You can use template variables: {IATA}, {IATA_lower}, {PUBLIC_KEY}" -ForegroundColor Blue
|
|
Write-Host ""
|
|
Write-Host "Choose topic configuration:" -ForegroundColor Blue
|
|
Write-Host " 1) Default pattern (meshcore/{IATA}/{PUBLIC_KEY}/status, meshcore/{IATA}/{PUBLIC_KEY}/packets)" -ForegroundColor Blue
|
|
Write-Host " 2) Classic pattern (meshcore/status, meshcore/packets, meshcore/raw)" -ForegroundColor Blue
|
|
Write-Host " 3) Custom topics (enter your own)" -ForegroundColor Blue
|
|
Write-Host ""
|
|
|
|
$topicChoice = Read-Host "Select topic configuration [1-3]" "1"
|
|
|
|
switch ($topicChoice) {
|
|
"1" {
|
|
# Default pattern (IATA + PUBLIC_KEY)
|
|
Add-Content -Path $envLocal -Value ""
|
|
Add-Content -Path $envLocal -Value "# MQTT Topics for Broker $BrokerNum - Default Pattern"
|
|
Add-Content -Path $envLocal -Value "PACKETCAPTURE_MQTT${BrokerNum}_TOPIC_STATUS=meshcore/{IATA}/{PUBLIC_KEY}/status"
|
|
Add-Content -Path $envLocal -Value "PACKETCAPTURE_MQTT${BrokerNum}_TOPIC_PACKETS=meshcore/{IATA}/{PUBLIC_KEY}/packets"
|
|
Write-Host "SUCCESS: Default pattern topics configured" -ForegroundColor Green
|
|
}
|
|
"2" {
|
|
# Classic pattern (simple meshcore topics, needed for map.w0z.is)
|
|
Add-Content -Path $envLocal -Value ""
|
|
Add-Content -Path $envLocal -Value "# MQTT Topics for Broker $BrokerNum - Classic Pattern"
|
|
Add-Content -Path $envLocal -Value "PACKETCAPTURE_MQTT${BrokerNum}_TOPIC_STATUS=meshcore/status"
|
|
Add-Content -Path $envLocal -Value "PACKETCAPTURE_MQTT${BrokerNum}_TOPIC_PACKETS=meshcore/packets"
|
|
Add-Content -Path $envLocal -Value "PACKETCAPTURE_MQTT${BrokerNum}_TOPIC_RAW=meshcore/raw"
|
|
Write-Host "SUCCESS: Classic pattern topics configured" -ForegroundColor Green
|
|
}
|
|
"3" {
|
|
# Custom topics
|
|
Write-Host ""
|
|
Write-Host "INFO: Enter custom topic paths (use {IATA}, {IATA_lower}, {PUBLIC_KEY} for templates)" -ForegroundColor Blue
|
|
Write-Host "INFO: You can also manually edit the .env.local file after installation to customize topics" -ForegroundColor Blue
|
|
Write-Host ""
|
|
|
|
# Read existing topic values from install directory's .env.local as defaults
|
|
$existingStatusTopic = Read-EnvValue "PACKETCAPTURE_MQTT${BrokerNum}_TOPIC_STATUS"
|
|
$existingPacketsTopic = Read-EnvValue "PACKETCAPTURE_MQTT${BrokerNum}_TOPIC_PACKETS"
|
|
|
|
$statusTopicDefault = if ($existingStatusTopic) { $existingStatusTopic } else { "meshcore/{IATA}/{PUBLIC_KEY}/status" }
|
|
$packetsTopicDefault = if ($existingPacketsTopic) { $existingPacketsTopic } else { "meshcore/{IATA}/{PUBLIC_KEY}/packets" }
|
|
|
|
$statusTopic = Read-Host "Status topic" $statusTopicDefault
|
|
$packetsTopic = Read-Host "Packets topic" $packetsTopicDefault
|
|
|
|
Add-Content -Path $envLocal -Value ""
|
|
Add-Content -Path $envLocal -Value "# MQTT Topics for Broker $BrokerNum - Custom"
|
|
Add-Content -Path $envLocal -Value "PACKETCAPTURE_MQTT${BrokerNum}_TOPIC_STATUS=$statusTopic"
|
|
Add-Content -Path $envLocal -Value "PACKETCAPTURE_MQTT${BrokerNum}_TOPIC_PACKETS=$packetsTopic"
|
|
Write-Host "SUCCESS: Custom topics configured" -ForegroundColor Green
|
|
}
|
|
default {
|
|
Write-Host "ERROR: Invalid choice, using default pattern" -ForegroundColor Red
|
|
Add-Content -Path $envLocal -Value ""
|
|
Add-Content -Path $envLocal -Value "# MQTT Topics for Broker $BrokerNum - Default Pattern"
|
|
Add-Content -Path $envLocal -Value "PACKETCAPTURE_MQTT${BrokerNum}_TOPIC_STATUS=meshcore/{IATA}/{PUBLIC_KEY}/status"
|
|
Add-Content -Path $envLocal -Value "PACKETCAPTURE_MQTT${BrokerNum}_TOPIC_PACKETS=meshcore/{IATA}/{PUBLIC_KEY}/packets"
|
|
}
|
|
}
|
|
|
|
Write-Host "SUCCESS: Broker $BrokerNum configured" -ForegroundColor Green
|
|
}
|
|
|
|
# Main installation function
|
|
function Start-Installation {
|
|
Write-Host ""
|
|
Write-Host "=======================================================" -ForegroundColor Blue
|
|
Write-Host " MeshCore Packet Capture Installer v$ScriptVersion" -ForegroundColor Blue
|
|
Write-Host "=======================================================" -ForegroundColor Blue
|
|
Write-Host ""
|
|
|
|
Write-Host "This installer will help you set up MeshCore Packet Capture."
|
|
Write-Host ""
|
|
|
|
# Determine installation directory
|
|
$defaultInstallDir = Join-Path $env:USERPROFILE ".meshcore-packet-capture"
|
|
$script:InstallDir = Read-Host "Installation directory" $defaultInstallDir
|
|
|
|
# Use default if empty
|
|
if (-not $script:InstallDir) {
|
|
$script:InstallDir = $defaultInstallDir
|
|
}
|
|
|
|
Write-Host "INFO: Installation directory: $InstallDir" -ForegroundColor Blue
|
|
|
|
# Check if directory exists
|
|
if (Test-Path $InstallDir) {
|
|
$response = Read-Host "Directory already exists. Reinstall/update? (y/N)"
|
|
if ($response -match '^[yY]') {
|
|
Write-Host "INFO: Updating existing installation..." -ForegroundColor Blue
|
|
$script:UpdatingExisting = $true
|
|
}
|
|
else {
|
|
Write-Host "ERROR: Installation cancelled." -ForegroundColor Red
|
|
exit 1
|
|
}
|
|
}
|
|
|
|
# Create installation directory
|
|
New-Item -ItemType Directory -Path $InstallDir -Force | Out-Null
|
|
Set-Location $InstallDir
|
|
|
|
Write-Host ""
|
|
Write-Host "SUCCESS: Installation directory created" -ForegroundColor Green
|
|
|
|
# Check Python
|
|
Write-Host ""
|
|
Write-Host "INFO: Checking Python installation..." -ForegroundColor Blue
|
|
|
|
try {
|
|
$pythonVersion = python --version 2>&1
|
|
if ($LASTEXITCODE -ne 0) {
|
|
throw "Python not found"
|
|
}
|
|
}
|
|
catch {
|
|
Write-Host "ERROR: Python 3 is not installed. Please install Python 3 and try again." -ForegroundColor Red
|
|
exit 1
|
|
}
|
|
Write-Host "SUCCESS: Python 3 found: $pythonVersion" -ForegroundColor Green
|
|
|
|
# Download files first
|
|
Write-Host ""
|
|
Write-Host "INFO: Downloading application files..." -ForegroundColor Blue
|
|
|
|
if ($env:LOCAL_INSTALL) {
|
|
# Local install for testing
|
|
Write-Host "INFO: Installing from local directory: $env:LOCAL_INSTALL" -ForegroundColor Blue
|
|
Copy-Item "$env:LOCAL_INSTALL\packet_capture.py" $InstallDir\
|
|
Copy-Item "$env:LOCAL_INSTALL\auth_token.py" $InstallDir\
|
|
Copy-Item "$env:LOCAL_INSTALL\enums.py" $InstallDir\
|
|
Copy-Item "$env:LOCAL_INSTALL\ble_pairing_helper.py" $InstallDir\
|
|
Copy-Item "$env:LOCAL_INSTALL\requirements.txt" $InstallDir\
|
|
if (Test-Path "$env:LOCAL_INSTALL\.env") {
|
|
Copy-Item "$env:LOCAL_INSTALL\.env" $InstallDir\
|
|
}
|
|
# Create version info file
|
|
New-VersionInfo
|
|
|
|
Write-Host "SUCCESS: Files copied from local directory" -ForegroundColor Green
|
|
}
|
|
else {
|
|
# Download from GitHub
|
|
Write-Host "INFO: Downloading from GitHub ($Repo @ $Branch)..." -ForegroundColor Blue
|
|
|
|
$baseUrl = "https://raw.githubusercontent.com/$Repo/$Branch"
|
|
|
|
# Download files
|
|
$files = @("packet_capture.py", "auth_token.py", "enums.py", "ble_pairing_helper.py", "ble_scan_helper.py", "requirements.txt")
|
|
|
|
foreach ($file in $files) {
|
|
Write-Host "INFO: Downloading $file..." -ForegroundColor Blue
|
|
try {
|
|
Invoke-WebRequest -Uri "$baseUrl/$file" -OutFile (Join-Path $InstallDir $file) -UseBasicParsing
|
|
}
|
|
catch {
|
|
Write-Host "ERROR: Failed to download $file from $Repo/$Branch" -ForegroundColor Red
|
|
Write-Host "ERROR: Please verify the repository and branch exist" -ForegroundColor Red
|
|
exit 1
|
|
}
|
|
}
|
|
|
|
# Create version info file
|
|
New-VersionInfo
|
|
|
|
Write-Host "SUCCESS: Files downloaded and verified" -ForegroundColor Green
|
|
}
|
|
|
|
# Set up virtual environment
|
|
Write-Host ""
|
|
Write-Host "INFO: Setting up Python virtual environment..." -ForegroundColor Blue
|
|
if (-not (Test-Path (Join-Path $InstallDir "venv"))) {
|
|
python -m venv (Join-Path $InstallDir "venv")
|
|
Write-Host "SUCCESS: Virtual environment created" -ForegroundColor Green
|
|
}
|
|
else {
|
|
Write-Host "SUCCESS: Using existing virtual environment" -ForegroundColor Green
|
|
}
|
|
|
|
# Install Python dependencies
|
|
Write-Host "INFO: Installing Python dependencies..." -ForegroundColor Blue
|
|
& (Join-Path $InstallDir "venv\Scripts\Activate.ps1")
|
|
& (Join-Path $InstallDir "venv\Scripts\pip.exe") install --quiet --upgrade pip
|
|
& (Join-Path $InstallDir "venv\Scripts\pip.exe") install --quiet -r (Join-Path $InstallDir "requirements.txt")
|
|
Write-Host "SUCCESS: Python dependencies installed" -ForegroundColor Green
|
|
|
|
# Configuration
|
|
Write-Host ""
|
|
Write-Host "INFO: Setting up configuration..." -ForegroundColor Blue
|
|
|
|
# Connection Type Selection
|
|
Write-Host ""
|
|
Write-Host "INFO: Device Connection Configuration" -ForegroundColor Blue
|
|
Write-Host "INFO: How would you like to connect to your MeshCore device?" -ForegroundColor Blue
|
|
Write-Host ""
|
|
Write-Host " 1) Serial Connection - For devices with USB/serial interface" -ForegroundColor Blue
|
|
Write-Host " - Direct USB or serial cable connection" -ForegroundColor Blue
|
|
Write-Host " - More reliable for continuous operation" -ForegroundColor Blue
|
|
Write-Host ""
|
|
Write-Host " 2) Bluetooth Low Energy (BLE) - For BLE-capable nodes" -ForegroundColor Blue
|
|
Write-Host " - Wireless connection" -ForegroundColor Blue
|
|
Write-Host " - Works with BLE-enabled MeshCore devices" -ForegroundColor Blue
|
|
Write-Host ""
|
|
Write-Host " 3) TCP Connection - For network-connected devices" -ForegroundColor Blue
|
|
Write-Host " - Connect to your node over the network" -ForegroundColor Blue
|
|
Write-Host " - Works with ser2net or other TCP-to-serial bridges" -ForegroundColor Blue
|
|
Write-Host ""
|
|
|
|
# Read existing connection type and map to default choice number
|
|
# Note: PowerShell version has different numbering: 1=serial, 2=ble, 3=tcp
|
|
$existingConnectionType = Read-EnvValue "PACKETCAPTURE_CONNECTION_TYPE"
|
|
$defaultChoice = "1" # Default to Serial
|
|
if ($existingConnectionType) {
|
|
switch ($existingConnectionType.ToLower()) {
|
|
"serial" { $defaultChoice = "1" }
|
|
"ble" { $defaultChoice = "2" }
|
|
"tcp" { $defaultChoice = "3" }
|
|
}
|
|
}
|
|
|
|
$connectionChoice = ""
|
|
while ($connectionChoice -notmatch '^[1-3]$') {
|
|
if ($defaultChoice) {
|
|
$connectionChoice = Read-Host "Select connection type [1-3] [$defaultChoice]"
|
|
if ([string]::IsNullOrWhiteSpace($connectionChoice)) {
|
|
$connectionChoice = $defaultChoice
|
|
}
|
|
} else {
|
|
$connectionChoice = Read-Host "Select connection type [1-3]"
|
|
}
|
|
if ($connectionChoice -notmatch '^[1-3]$') {
|
|
Write-Host "ERROR: Invalid choice. Please enter 1, 2, or 3" -ForegroundColor Red
|
|
}
|
|
}
|
|
|
|
$script:ConnectionType = ""
|
|
$script:SelectedSerialDevice = ""
|
|
$script:SelectedBleDevice = ""
|
|
$script:SelectedBleName = ""
|
|
$script:TcpHost = ""
|
|
$script:TcpPort = ""
|
|
|
|
switch ($connectionChoice) {
|
|
"1" {
|
|
$script:ConnectionType = "serial"
|
|
Write-Host "SUCCESS: Selected Serial Connection" -ForegroundColor Green
|
|
|
|
# Detect serial devices
|
|
Write-Host ""
|
|
Write-Host "INFO: Detecting serial devices..." -ForegroundColor Blue
|
|
|
|
$devices = @()
|
|
try {
|
|
# Get COM ports from WMI
|
|
$comPorts = Get-WmiObject -Class Win32_SerialPort | Where-Object { $_.DeviceID -like "COM*" }
|
|
foreach ($port in $comPorts) {
|
|
$devices += $port.DeviceID
|
|
}
|
|
|
|
# Also check for USB serial adapters
|
|
$usbDevices = Get-WmiObject -Class Win32_PnPEntity | Where-Object {
|
|
$_.Name -like "*USB Serial*" -or
|
|
$_.Name -like "*USB-to-Serial*" -or
|
|
$_.Name -like "*FTDI*" -or
|
|
$_.Name -like "*Prolific*" -or
|
|
$_.Name -like "*Silicon Labs*"
|
|
}
|
|
|
|
foreach ($device in $usbDevices) {
|
|
if ($device.PNPDeviceID -match "COM\d+") {
|
|
$comMatch = [regex]::Match($device.PNPDeviceID, "COM\d+")
|
|
if ($comMatch.Success) {
|
|
$comPort = $comMatch.Value
|
|
if ($devices -notcontains $comPort) {
|
|
$devices += $comPort
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
catch {
|
|
Write-Host "WARNING: Failed to detect serial devices: $($_.Exception.Message)" -ForegroundColor Yellow
|
|
}
|
|
|
|
if ($devices.Count -eq 0) {
|
|
Write-Host "WARNING: No serial devices detected" -ForegroundColor Yellow
|
|
# Read existing serial device from install directory's .env.local as default
|
|
$existingSerialDevice = Read-EnvValue "PACKETCAPTURE_SERIAL_PORTS"
|
|
$serialDeviceDefault = if ($existingSerialDevice) { $existingSerialDevice } else { "COM1" }
|
|
$script:SelectedSerialDevice = Read-Host "Enter serial device path" $serialDeviceDefault
|
|
}
|
|
elseif ($devices.Count -eq 1) {
|
|
Write-Host "INFO: Found 1 serial device: $($devices[0])" -ForegroundColor Blue
|
|
$script:SelectedSerialDevice = $devices[0]
|
|
}
|
|
else {
|
|
Write-Host "INFO: Found $($devices.Count) serial devices:" -ForegroundColor Blue
|
|
for ($i = 0; $i -lt $devices.Count; $i++) {
|
|
Write-Host " $($i + 1)) $($devices[$i])" -ForegroundColor Blue
|
|
}
|
|
Write-Host " $($devices.Count + 1)) Enter path manually" -ForegroundColor Blue
|
|
Write-Host ""
|
|
|
|
while ($true) {
|
|
$choice = Read-Host "Select device [1-$($devices.Count + 1)]"
|
|
if ($choice -match '^\d+$' -and [int]$choice -ge 1 -and [int]$choice -le ($devices.Count + 1)) {
|
|
if ([int]$choice -eq ($devices.Count + 1)) {
|
|
# Manual entry - use existing value as default
|
|
$existingSerialDevice = Read-EnvValue "PACKETCAPTURE_SERIAL_PORTS"
|
|
$serialDeviceDefault = if ($existingSerialDevice) { $existingSerialDevice } else { "COM1" }
|
|
$script:SelectedSerialDevice = Read-Host "Enter serial device path" $serialDeviceDefault
|
|
}
|
|
else {
|
|
$script:SelectedSerialDevice = $devices[([int]$choice - 1)]
|
|
}
|
|
break
|
|
}
|
|
else {
|
|
Write-Host "ERROR: Invalid selection. Please enter a number between 1 and $($devices.Count + 1)" -ForegroundColor Red
|
|
}
|
|
}
|
|
}
|
|
Write-Host "SUCCESS: Serial device configured: $script:SelectedSerialDevice" -ForegroundColor Green
|
|
}
|
|
"2" {
|
|
$script:ConnectionType = "ble"
|
|
Write-Host "SUCCESS: Selected Bluetooth Low Energy (BLE)" -ForegroundColor Green
|
|
|
|
# Scan for BLE devices using Windows-native approach
|
|
Write-Host ""
|
|
Write-Host "INFO: Scanning for BLE devices using Windows Bluetooth..." -ForegroundColor Blue
|
|
Write-Host "INFO: This may take 10-15 seconds..." -ForegroundColor Blue
|
|
|
|
try {
|
|
# Check if Bluetooth is available
|
|
$bluetoothAdapter = Get-PnpDevice -Class Bluetooth -ErrorAction SilentlyContinue | Where-Object { $_.Status -eq "OK" }
|
|
if (-not $bluetoothAdapter) {
|
|
Write-Host "WARNING: No Bluetooth adapter found or Bluetooth is disabled" -ForegroundColor Yellow
|
|
# Read existing values from install directory's .env.local as defaults
|
|
$existingBleDevice = Read-EnvValue "PACKETCAPTURE_BLE_DEVICE"
|
|
$existingBleName = Read-EnvValue "PACKETCAPTURE_BLE_NAME"
|
|
$script:SelectedBleDevice = Read-Host "Enter BLE device MAC address" $existingBleDevice
|
|
$script:SelectedBleName = Read-Host "Enter device name (optional)" $existingBleName
|
|
return
|
|
}
|
|
|
|
|
|
# Start Bluetooth discovery
|
|
Write-Host "INFO: Starting Bluetooth discovery..." -ForegroundColor Blue
|
|
|
|
# Use PowerShell to discover Bluetooth devices
|
|
$discoveredDevices = @()
|
|
$startTime = Get-Date
|
|
$timeout = 15
|
|
|
|
# Check for already paired MeshCore devices first
|
|
Write-Host "INFO: Checking for already paired MeshCore devices..." -ForegroundColor Blue
|
|
$pairedMeshCoreDevices = Get-PnpDevice -Class Bluetooth -ErrorAction SilentlyContinue |
|
|
Where-Object { $_.FriendlyName -like "*MeshCore*" -and $_.Status -eq "OK" }
|
|
|
|
if ($pairedMeshCoreDevices.Count -gt 0) {
|
|
Write-Host "INFO: Found $($pairedMeshCoreDevices.Count) already paired MeshCore device(s):" -ForegroundColor Blue
|
|
for ($i = 0; $i -lt $pairedMeshCoreDevices.Count; $i++) {
|
|
$device = $pairedMeshCoreDevices[$i]
|
|
Write-Host " $($i + 1)) $($device.FriendlyName) (Already Paired)" -ForegroundColor Blue
|
|
}
|
|
Write-Host " $($pairedMeshCoreDevices.Count + 1)) Enter device manually" -ForegroundColor Blue
|
|
Write-Host ""
|
|
|
|
while ($true) {
|
|
$choice = Read-Host "Select device [1-$($pairedMeshCoreDevices.Count + 1)]"
|
|
if ($choice -match '^\d+$' -and [int]$choice -ge 1 -and [int]$choice -le ($pairedMeshCoreDevices.Count + 1)) {
|
|
if ([int]$choice -eq ($pairedMeshCoreDevices.Count + 1)) {
|
|
# Manual entry - use existing values as defaults
|
|
$existingBleDevice = Read-EnvValue "PACKETCAPTURE_BLE_DEVICE"
|
|
$existingBleName = Read-EnvValue "PACKETCAPTURE_BLE_NAME"
|
|
$script:SelectedBleDevice = Read-Host "Enter BLE device MAC address" $existingBleDevice
|
|
$script:SelectedBleName = Read-Host "Enter device name (optional)" $existingBleName
|
|
}
|
|
else {
|
|
$selectedDevice = $pairedMeshCoreDevices[([int]$choice - 1)]
|
|
# Extract MAC address from InstanceId if possible
|
|
$macAddress = $selectedDevice.InstanceId
|
|
if ($macAddress -match 'DEV_([A-F0-9]{12})') {
|
|
$macBytes = $matches[1]
|
|
$macParts = @()
|
|
for ($i = 0; $i -lt $macBytes.Length; $i += 2) {
|
|
$macParts += $macBytes.Substring($i, 2)
|
|
}
|
|
$macAddress = $macParts -join ':'
|
|
} else {
|
|
# Try to get MAC address from Windows Bluetooth registry
|
|
try {
|
|
$deviceName = $selectedDevice.FriendlyName
|
|
$regPath = "HKLM:\SYSTEM\CurrentControlSet\Services\BTHPORT\Parameters\Devices"
|
|
$deviceKeys = Get-ChildItem $regPath -ErrorAction SilentlyContinue
|
|
foreach ($key in $deviceKeys) {
|
|
$deviceInfo = Get-ItemProperty $key.PSPath -ErrorAction SilentlyContinue
|
|
if ($deviceInfo -and $deviceInfo.Name -eq $deviceName) {
|
|
$macAddress = $key.PSChildName -replace '(.{2})(.{2})(.{2})(.{2})(.{2})(.{2})', '$1:$2:$3:$4:$5:$6'
|
|
break
|
|
}
|
|
}
|
|
} catch {
|
|
# If registry lookup fails, prompt user for MAC address
|
|
Write-Host "WARNING: Could not extract MAC address automatically" -ForegroundColor Yellow
|
|
$macAddress = Read-Host "Enter the MAC address for $($selectedDevice.FriendlyName) (format: XX:XX:XX:XX:XX:XX)"
|
|
}
|
|
}
|
|
$script:SelectedBleDevice = $macAddress
|
|
$script:SelectedBleName = $selectedDevice.FriendlyName
|
|
}
|
|
break
|
|
}
|
|
else {
|
|
Write-Host "ERROR: Invalid selection. Please enter a number between 1 and $($pairedMeshCoreDevices.Count + 1)" -ForegroundColor Red
|
|
}
|
|
}
|
|
} else {
|
|
# Start discovery in background for unpaired devices
|
|
Write-Host "INFO: No paired MeshCore devices found, scanning for unpaired devices..." -ForegroundColor Blue
|
|
|
|
$discoveryJob = Start-Job -ScriptBlock {
|
|
# Import Bluetooth module if available
|
|
try {
|
|
Import-Module -Name Bluetooth -ErrorAction SilentlyContinue
|
|
} catch {}
|
|
|
|
# Get discovered devices
|
|
$devices = @()
|
|
$endTime = (Get-Date).AddSeconds(15)
|
|
|
|
while ((Get-Date) -lt $endTime) {
|
|
try {
|
|
# Try different methods to get Bluetooth devices
|
|
$btDevices = Get-PnpDevice -Class Bluetooth -ErrorAction SilentlyContinue |
|
|
Where-Object { $_.Status -eq "OK" -and $_.FriendlyName -like "*MeshCore*" }
|
|
|
|
foreach ($device in $btDevices) {
|
|
if ($device.FriendlyName -and $device.FriendlyName -like "*MeshCore*") {
|
|
$devices += @{
|
|
Name = $device.FriendlyName
|
|
Address = $device.InstanceId
|
|
Status = $device.Status
|
|
}
|
|
}
|
|
}
|
|
|
|
Start-Sleep -Seconds 2
|
|
} catch {
|
|
# Continue scanning
|
|
}
|
|
}
|
|
|
|
return $devices
|
|
}
|
|
|
|
# Wait for discovery to complete
|
|
$completed = Wait-Job $discoveryJob -Timeout $timeout
|
|
|
|
if ($completed) {
|
|
$discoveredDevices = Receive-Job $discoveryJob
|
|
} else {
|
|
Stop-Job $discoveryJob
|
|
Write-Host "WARNING: Bluetooth discovery timed out" -ForegroundColor Yellow
|
|
}
|
|
Remove-Job $discoveryJob
|
|
|
|
# Filter and display results
|
|
$meshcoreDevices = $discoveredDevices | Where-Object { $_.Name -like "*MeshCore*" } | Sort-Object Name -Unique
|
|
|
|
if ($meshcoreDevices.Count -gt 0) {
|
|
Write-Host "INFO: Found $($meshcoreDevices.Count) MeshCore device(s):" -ForegroundColor Blue
|
|
for ($i = 0; $i -lt $meshcoreDevices.Count; $i++) {
|
|
$device = $meshcoreDevices[$i]
|
|
Write-Host " $($i + 1)) $($device.Name) ($($device.Address))" -ForegroundColor Blue
|
|
}
|
|
Write-Host " $($meshcoreDevices.Count + 1)) Enter device manually" -ForegroundColor Blue
|
|
Write-Host ""
|
|
|
|
while ($true) {
|
|
$choice = Read-Host "Select device [1-$($meshcoreDevices.Count + 1)]"
|
|
if ($choice -match '^\d+$' -and [int]$choice -ge 1 -and [int]$choice -le ($meshcoreDevices.Count + 1)) {
|
|
if ([int]$choice -eq ($meshcoreDevices.Count + 1)) {
|
|
# Manual entry - use existing values as defaults
|
|
$existingBleDevice = Read-EnvValue "PACKETCAPTURE_BLE_DEVICE"
|
|
$existingBleName = Read-EnvValue "PACKETCAPTURE_BLE_NAME"
|
|
$script:SelectedBleDevice = Read-Host "Enter BLE device MAC address" $existingBleDevice
|
|
$script:SelectedBleName = Read-Host "Enter device name (optional)" $existingBleName
|
|
}
|
|
else {
|
|
$selectedDevice = $meshcoreDevices[([int]$choice - 1)]
|
|
# Extract MAC address from Address if it's a Windows device ID
|
|
$macAddress = $selectedDevice.Address
|
|
if ($macAddress -match 'DEV_([A-F0-9]{12})') {
|
|
$macBytes = $matches[1]
|
|
$macParts = @()
|
|
for ($i = 0; $i -lt $macBytes.Length; $i += 2) {
|
|
$macParts += $macBytes.Substring($i, 2)
|
|
}
|
|
$macAddress = $macParts -join ':'
|
|
}
|
|
$script:SelectedBleDevice = $macAddress
|
|
$script:SelectedBleName = $selectedDevice.Name
|
|
}
|
|
break
|
|
}
|
|
else {
|
|
Write-Host "ERROR: Invalid selection. Please enter a number between 1 and $($meshcoreDevices.Count + 1)" -ForegroundColor Red
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
Write-Host "WARNING: No MeshCore BLE devices found" -ForegroundColor Yellow
|
|
Write-Host "INFO: Make sure your MeshCore device is:" -ForegroundColor Blue
|
|
Write-Host " - Powered on and within range" -ForegroundColor Blue
|
|
Write-Host " - In pairing mode (if not already paired)" -ForegroundColor Blue
|
|
Write-Host " - Not connected to another device" -ForegroundColor Blue
|
|
Write-Host ""
|
|
# Fallback to manual entry - use existing values as defaults
|
|
$existingBleDevice = Read-EnvValue "PACKETCAPTURE_BLE_DEVICE"
|
|
$existingBleName = Read-EnvValue "PACKETCAPTURE_BLE_NAME"
|
|
$script:SelectedBleDevice = Read-Host "Enter BLE device MAC address" $existingBleDevice
|
|
$script:SelectedBleName = Read-Host "Enter device name (optional)" $existingBleName
|
|
}
|
|
}
|
|
}
|
|
catch {
|
|
Write-Host "WARNING: BLE scanning failed: $($_.Exception.Message)" -ForegroundColor Yellow
|
|
Write-Host "INFO: Falling back to manual device entry" -ForegroundColor Blue
|
|
# Use existing values as defaults
|
|
$existingBleDevice = Read-EnvValue "PACKETCAPTURE_BLE_DEVICE"
|
|
$existingBleName = Read-EnvValue "PACKETCAPTURE_BLE_NAME"
|
|
$script:SelectedBleDevice = Read-Host "Enter BLE device MAC address" $existingBleDevice
|
|
$script:SelectedBleName = Read-Host "Enter device name (optional)" $existingBleName
|
|
}
|
|
|
|
if ($script:SelectedBleDevice) {
|
|
Write-Host "SUCCESS: BLE device configured: $script:SelectedBleName ($script:SelectedBleDevice)" -ForegroundColor Green
|
|
|
|
# Attempt BLE pairing using Windows-native approach
|
|
Write-Host ""
|
|
Write-Host "INFO: Checking BLE device pairing status..." -ForegroundColor Blue
|
|
|
|
try {
|
|
# Check if device is already paired
|
|
$pairedDevice = Get-PnpDevice -Class Bluetooth -ErrorAction SilentlyContinue |
|
|
Where-Object { $_.FriendlyName -like "*$script:SelectedBleName*" -or $_.InstanceId -like "*$script:SelectedBleDevice*" }
|
|
|
|
if ($pairedDevice -and $pairedDevice.Status -eq "OK") {
|
|
Write-Host "SUCCESS: Device is already paired and ready to use" -ForegroundColor Green
|
|
}
|
|
else {
|
|
Write-Host "INFO: Device requires pairing" -ForegroundColor Blue
|
|
Write-Host "INFO: Attempting to pair programmatically..." -ForegroundColor Blue
|
|
|
|
# Try programmatic pairing
|
|
try {
|
|
$pairingSuccess = $false
|
|
|
|
# Method 1: Try using PowerShell Bluetooth cmdlets (if available)
|
|
try {
|
|
Import-Module -Name Bluetooth -ErrorAction SilentlyContinue
|
|
if (Get-Command -Name "Add-BluetoothDevice" -ErrorAction SilentlyContinue) {
|
|
Write-Host "INFO: Using PowerShell Bluetooth cmdlets..." -ForegroundColor Blue
|
|
|
|
# Get the PIN from user
|
|
$pin = Read-Host "Enter the 6-digit PIN displayed on your MeshCore device"
|
|
if ($pin -match '^\d{6}$') {
|
|
$pairingResult = Add-BluetoothDevice -Address $script:SelectedBleDevice -Pin $pin -ErrorAction Stop
|
|
if ($pairingResult) {
|
|
Write-Host "SUCCESS: Device paired successfully using PowerShell cmdlets" -ForegroundColor Green
|
|
$pairingSuccess = $true
|
|
}
|
|
} else {
|
|
Write-Host "ERROR: PIN must be 6 digits" -ForegroundColor Red
|
|
}
|
|
}
|
|
} catch {
|
|
# PowerShell Bluetooth cmdlets not available, try next method
|
|
}
|
|
|
|
# Method 2: Try using Windows Bluetooth API via .NET
|
|
if (-not $pairingSuccess) {
|
|
try {
|
|
Write-Host "INFO: Using Windows Bluetooth API..." -ForegroundColor Blue
|
|
|
|
# Get the PIN from user
|
|
$pin = Read-Host "Enter the 6-digit PIN displayed on your MeshCore device"
|
|
if ($pin -match '^\d{6}$') {
|
|
$pairingSuccess = Invoke-BluetoothPairing -DeviceAddress $script:SelectedBleDevice -Pin $pin
|
|
if ($pairingSuccess) {
|
|
Write-Host "SUCCESS: Device paired successfully using Windows API" -ForegroundColor Green
|
|
}
|
|
} else {
|
|
Write-Host "ERROR: PIN must be 6 digits" -ForegroundColor Red
|
|
}
|
|
} catch {
|
|
# Windows Bluetooth API failed, try next method
|
|
}
|
|
}
|
|
|
|
# Method 3: Try using bluetoothctl (if available via WSL or installed)
|
|
if (-not $pairingSuccess) {
|
|
try {
|
|
Write-Host "INFO: Trying bluetoothctl approach..." -ForegroundColor Blue
|
|
|
|
# Check if bluetoothctl is available
|
|
$bluetoothctlPath = Get-Command bluetoothctl -ErrorAction SilentlyContinue
|
|
if ($bluetoothctlPath) {
|
|
$pin = Read-Host "Enter the 6-digit PIN displayed on your MeshCore device"
|
|
if ($pin -match '^\d{6}$') {
|
|
$pairingSuccess = Invoke-BluetoothctlPairing -DeviceAddress $script:SelectedBleDevice -Pin $pin
|
|
if ($pairingSuccess) {
|
|
Write-Host "SUCCESS: Device paired successfully using bluetoothctl" -ForegroundColor Green
|
|
}
|
|
} else {
|
|
Write-Host "ERROR: PIN must be 6 digits" -ForegroundColor Red
|
|
}
|
|
}
|
|
} catch {
|
|
# bluetoothctl approach failed, try next method
|
|
}
|
|
}
|
|
|
|
# Fallback to manual pairing if all methods failed
|
|
if (-not $pairingSuccess) {
|
|
Write-Host "WARNING: Programmatic pairing failed, falling back to manual pairing" -ForegroundColor Yellow
|
|
Write-Host "INFO: You'll need to pair the device manually using Windows Bluetooth settings" -ForegroundColor Blue
|
|
Write-Host ""
|
|
Write-Host "To pair manually:" -ForegroundColor Blue
|
|
Write-Host " 1. Open Windows Settings > Devices > Bluetooth & other devices" -ForegroundColor Blue
|
|
Write-Host " 2. Click 'Add Bluetooth or other device'" -ForegroundColor Blue
|
|
Write-Host " 3. Select 'Bluetooth'" -ForegroundColor Blue
|
|
Write-Host " 4. Look for your MeshCore device in the list" -ForegroundColor Blue
|
|
Write-Host " 5. Click on it and enter the PIN when prompted" -ForegroundColor Blue
|
|
Write-Host " 6. Do NOT check 'Connect automatically' (we want manual connection)" -ForegroundColor Blue
|
|
Write-Host ""
|
|
Write-Host "INFO: After pairing, the device will be available for the packet capture application" -ForegroundColor Blue
|
|
|
|
# Ask if user wants to continue
|
|
$continue = Read-Host "Continue with installation? (y/N)"
|
|
if ($continue -notmatch '^[yY]') {
|
|
Write-Host "INFO: Installation paused. Please pair your device and run the installer again." -ForegroundColor Blue
|
|
exit 0
|
|
}
|
|
}
|
|
} catch {
|
|
Write-Host "WARNING: Pairing attempt failed: $($_.Exception.Message)" -ForegroundColor Yellow
|
|
Write-Host "INFO: Please pair the device manually using Windows Bluetooth settings" -ForegroundColor Blue
|
|
}
|
|
}
|
|
}
|
|
catch {
|
|
Write-Host "WARNING: Could not check pairing status: $($_.Exception.Message)" -ForegroundColor Yellow
|
|
Write-Host "INFO: You may need to pair the device manually" -ForegroundColor Blue
|
|
}
|
|
}
|
|
else {
|
|
Write-Host "WARNING: No BLE device configured" -ForegroundColor Yellow
|
|
}
|
|
}
|
|
"3" {
|
|
$script:ConnectionType = "tcp"
|
|
Write-Host "SUCCESS: Selected TCP Connection" -ForegroundColor Green
|
|
|
|
# Read existing values from install directory's .env.local as defaults
|
|
$existingTcpHost = Read-EnvValue "PACKETCAPTURE_TCP_HOST"
|
|
$existingTcpPort = Read-EnvValue "PACKETCAPTURE_TCP_PORT"
|
|
|
|
# Use existing values as defaults, or fall back to standard defaults
|
|
$tcpHostDefault = if ($existingTcpHost) { $existingTcpHost } else { "localhost" }
|
|
$tcpPortDefault = if ($existingTcpPort) { $existingTcpPort } else { "5000" }
|
|
|
|
$script:TcpHost = Read-Host "TCP host/address" $tcpHostDefault
|
|
$script:TcpPort = Read-Host "TCP port" $tcpPortDefault
|
|
|
|
# Validate port number
|
|
if (-not ($script:TcpPort -match '^\d+$') -or [int]$script:TcpPort -lt 1 -or [int]$script:TcpPort -gt 65535) {
|
|
Write-Host "ERROR: Invalid port number. Using default port 5000" -ForegroundColor Red
|
|
$script:TcpPort = "5000"
|
|
}
|
|
Write-Host "SUCCESS: TCP connection configured: $($script:TcpHost):$($script:TcpPort)" -ForegroundColor Green
|
|
}
|
|
}
|
|
|
|
# Create basic .env.local file
|
|
$envLocal = Join-Path $InstallDir ".env.local"
|
|
$configContent = @"
|
|
# MeshCore Packet Capture Configuration
|
|
# This file contains your local overrides to the defaults in .env
|
|
|
|
# Update source (configured by installer)
|
|
PACKETCAPTURE_UPDATE_REPO=$Repo
|
|
PACKETCAPTURE_UPDATE_BRANCH=$Branch
|
|
|
|
# Connection Configuration
|
|
PACKETCAPTURE_CONNECTION_TYPE=$script:ConnectionType
|
|
"@
|
|
|
|
# Add device-specific configuration
|
|
switch ($script:ConnectionType) {
|
|
"ble" {
|
|
if ($script:SelectedBleDevice) {
|
|
$configContent += "`nPACKETCAPTURE_BLE_DEVICE=$script:SelectedBleDevice"
|
|
}
|
|
if ($script:SelectedBleName) {
|
|
$configContent += "`nPACKETCAPTURE_BLE_NAME=$script:SelectedBleName"
|
|
}
|
|
}
|
|
"serial" {
|
|
$configContent += "`nPACKETCAPTURE_SERIAL_PORTS=$script:SelectedSerialDevice"
|
|
}
|
|
"tcp" {
|
|
$configContent += "`nPACKETCAPTURE_TCP_HOST=$script:TcpHost"
|
|
$configContent += "`nPACKETCAPTURE_TCP_PORT=$script:TcpPort"
|
|
}
|
|
}
|
|
|
|
$configContent += @"
|
|
|
|
# Location Code
|
|
PACKETCAPTURE_IATA=XXX
|
|
|
|
# Advert Settings
|
|
PACKETCAPTURE_ADVERT_INTERVAL_HOURS=11
|
|
|
|
# Packet Type Filtering (comma-separated list of packet type numbers to upload to MQTT)
|
|
# Leave commented out to upload all packet types
|
|
# Example: PACKETCAPTURE_UPLOAD_PACKET_TYPES=2,4 (upload only TXT_MSG and ADVERT)
|
|
#PACKETCAPTURE_UPLOAD_PACKET_TYPES=
|
|
|
|
# Logging Settings
|
|
PACKETCAPTURE_LOG_LEVEL=INFO
|
|
"@
|
|
|
|
Set-Content -Path $envLocal -Value $configContent
|
|
|
|
# Always prompt for IATA (allows changing during reconfiguration)
|
|
# Get existing IATA from config (including backup files) to use as default
|
|
$existingIata = Read-EnvValue "PACKETCAPTURE_IATA"
|
|
if ($existingIata) {
|
|
$existingIata = $existingIata.Trim()
|
|
}
|
|
# Clear default if it's XXX or empty
|
|
if (-not $existingIata -or $existingIata -eq "XXX") {
|
|
$existingIata = ""
|
|
}
|
|
|
|
Write-Host ""
|
|
Write-Host "INFO: IATA code is a 3-letter airport code identifying your geographic region" -ForegroundColor Blue
|
|
Write-Host "INFO: Example: SEA (Seattle), LAX (Los Angeles), NYC (New York), LON (London)" -ForegroundColor Blue
|
|
Write-Host ""
|
|
|
|
$script:Iata = ""
|
|
while (-not $script:Iata -or $script:Iata -eq "XXX") {
|
|
if ($existingIata) {
|
|
$script:Iata = Read-Host "Enter your IATA code (3 letters) [$existingIata]"
|
|
if ([string]::IsNullOrWhiteSpace($script:Iata)) {
|
|
$script:Iata = $existingIata
|
|
}
|
|
} else {
|
|
$script:Iata = Read-Host "Enter your IATA code (3 letters)"
|
|
}
|
|
$script:Iata = $script:Iata.ToUpper().Trim()
|
|
|
|
if (-not $script:Iata) {
|
|
Write-Host "ERROR: IATA code cannot be empty" -ForegroundColor Red
|
|
}
|
|
elseif ($script:Iata -eq "XXX") {
|
|
Write-Host "ERROR: Please enter your actual IATA code, not XXX" -ForegroundColor Red
|
|
}
|
|
elseif ($script:Iata.Length -ne 3) {
|
|
Write-Host "WARNING: IATA code should be 3 letters, you entered: $script:Iata" -ForegroundColor Yellow
|
|
$response = Read-Host "Use '$script:Iata' anyway? (y/N)"
|
|
if ($response -notmatch '^[yY]') {
|
|
$script:Iata = "XXX" # Reset to force re-prompt
|
|
}
|
|
}
|
|
}
|
|
|
|
# Update IATA in config
|
|
$content = Get-Content $envLocal
|
|
$content = $content -replace "^PACKETCAPTURE_IATA=.*", "PACKETCAPTURE_IATA=$script:Iata"
|
|
Set-Content -Path $envLocal -Value $content
|
|
Write-Host "SUCCESS: IATA code set to: $script:Iata" -ForegroundColor Green
|
|
|
|
# Configure JWT options (owner public key and email) - global settings
|
|
$ownerKeyExists = (Get-Content $envLocal -ErrorAction SilentlyContinue) -match "^PACKETCAPTURE_OWNER_PUBLIC_KEY="
|
|
$ownerEmailExists = (Get-Content $envLocal -ErrorAction SilentlyContinue) -match "^PACKETCAPTURE_OWNER_EMAIL="
|
|
if (-not $ownerKeyExists -and -not $ownerEmailExists) {
|
|
Configure-JwtOptions
|
|
}
|
|
|
|
# Configure MQTT brokers
|
|
Write-Host ""
|
|
Write-Host "INFO: MQTT Broker Configuration" -ForegroundColor Blue
|
|
Write-Host "INFO: Enable the LetsMesh.net Packet Analyzer (mqtt-us-v1.letsmesh.net) broker?" -ForegroundColor Blue
|
|
Write-Host " • Real-time packet analysis and visualization" -ForegroundColor Blue
|
|
Write-Host " • Network health monitoring" -ForegroundColor Blue
|
|
Write-Host " • Uses device signing (Python signing as fallback)" -ForegroundColor Blue
|
|
Write-Host ""
|
|
|
|
$response = Read-Host "Enable LetsMesh Packet Analyzer? (y/N)"
|
|
if ($response -match '^[yY]') {
|
|
$letsMeshConfig = @"
|
|
|
|
# MQTT Broker 1 - LetsMesh.net Packet Analyzer
|
|
PACKETCAPTURE_MQTT1_ENABLED=true
|
|
PACKETCAPTURE_MQTT1_SERVER=mqtt-us-v1.letsmesh.net
|
|
PACKETCAPTURE_MQTT1_PORT=443
|
|
PACKETCAPTURE_MQTT1_TRANSPORT=websockets
|
|
PACKETCAPTURE_MQTT1_USE_TLS=true
|
|
PACKETCAPTURE_MQTT1_USE_AUTH_TOKEN=true
|
|
PACKETCAPTURE_MQTT1_TOKEN_AUDIENCE=mqtt-us-v1.letsmesh.net
|
|
PACKETCAPTURE_MQTT1_KEEPALIVE=120
|
|
"@
|
|
Add-Content -Path $envLocal -Value $letsMeshConfig
|
|
Write-Host "SUCCESS: LetsMesh Packet Analyzer enabled" -ForegroundColor Green
|
|
|
|
# Configure topics for LetsMesh
|
|
Write-Host ""
|
|
Write-Host "INFO: MQTT Topic Configuration for Broker 1" -ForegroundColor Blue
|
|
Write-Host "INFO: MQTT topics define where different types of data are published." -ForegroundColor Blue
|
|
Write-Host "INFO: You can use template variables: {IATA}, {IATA_lower}, {PUBLIC_KEY}" -ForegroundColor Blue
|
|
Write-Host ""
|
|
Write-Host "Choose topic configuration:" -ForegroundColor Blue
|
|
Write-Host " 1) Default pattern (meshcore/{IATA}/{PUBLIC_KEY}/status, meshcore/{IATA}/{PUBLIC_KEY}/packets)" -ForegroundColor Blue
|
|
Write-Host " 2) Classic pattern (meshcore/status, meshcore/packets, meshcore/raw)" -ForegroundColor Blue
|
|
Write-Host " 3) Custom topics (enter your own)" -ForegroundColor Blue
|
|
Write-Host ""
|
|
|
|
$topicChoice = Read-Host "Select topic configuration [1-3]" "1" "1"
|
|
|
|
switch ($topicChoice) {
|
|
"1" {
|
|
# Default pattern (IATA + PUBLIC_KEY)
|
|
Add-Content -Path $envLocal -Value ""
|
|
Add-Content -Path $envLocal -Value "# MQTT Topics for Broker 1 - Default Pattern"
|
|
Add-Content -Path $envLocal -Value "PACKETCAPTURE_MQTT1_TOPIC_STATUS=meshcore/{IATA}/{PUBLIC_KEY}/status"
|
|
Add-Content -Path $envLocal -Value "PACKETCAPTURE_MQTT1_TOPIC_PACKETS=meshcore/{IATA}/{PUBLIC_KEY}/packets"
|
|
Write-Host "SUCCESS: Default pattern topics configured" -ForegroundColor Green
|
|
}
|
|
"2" {
|
|
# Classic pattern (simple meshcore topics, needed for map.w0z.is)
|
|
Add-Content -Path $envLocal -Value ""
|
|
Add-Content -Path $envLocal -Value "# MQTT Topics for Broker 1 - Classic Pattern"
|
|
Add-Content -Path $envLocal -Value "PACKETCAPTURE_MQTT1_TOPIC_STATUS=meshcore/status"
|
|
Add-Content -Path $envLocal -Value "PACKETCAPTURE_MQTT1_TOPIC_PACKETS=meshcore/packets"
|
|
Add-Content -Path $envLocal -Value "PACKETCAPTURE_MQTT1_TOPIC_RAW=meshcore/raw"
|
|
Write-Host "SUCCESS: Classic pattern topics configured" -ForegroundColor Green
|
|
}
|
|
"3" {
|
|
# Custom topics
|
|
Write-Host ""
|
|
Write-Host "INFO: Enter custom topic paths (use {IATA}, {IATA_lower}, {PUBLIC_KEY} for templates)" -ForegroundColor Blue
|
|
Write-Host "INFO: You can also manually edit the .env.local file after installation to customize topics" -ForegroundColor Blue
|
|
Write-Host ""
|
|
|
|
# Read existing topic values from install directory's .env.local as defaults
|
|
$existingStatusTopic = Read-EnvValue "PACKETCAPTURE_MQTT1_TOPIC_STATUS"
|
|
$existingPacketsTopic = Read-EnvValue "PACKETCAPTURE_MQTT1_TOPIC_PACKETS"
|
|
|
|
$statusTopicDefault = if ($existingStatusTopic) { $existingStatusTopic } else { "meshcore/{IATA}/{PUBLIC_KEY}/status" }
|
|
$packetsTopicDefault = if ($existingPacketsTopic) { $existingPacketsTopic } else { "meshcore/{IATA}/{PUBLIC_KEY}/packets" }
|
|
|
|
$statusTopic = Read-Host "Status topic" $statusTopicDefault
|
|
$packetsTopic = Read-Host "Packets topic" $packetsTopicDefault
|
|
|
|
Add-Content -Path $envLocal -Value ""
|
|
Add-Content -Path $envLocal -Value "# MQTT Topics for Broker 1 - Custom"
|
|
Add-Content -Path $envLocal -Value "PACKETCAPTURE_MQTT1_TOPIC_STATUS=$statusTopic"
|
|
Add-Content -Path $envLocal -Value "PACKETCAPTURE_MQTT1_TOPIC_PACKETS=$packetsTopic"
|
|
Write-Host "SUCCESS: Custom topics configured" -ForegroundColor Green
|
|
}
|
|
default {
|
|
Write-Host "ERROR: Invalid choice, using default pattern" -ForegroundColor Red
|
|
Add-Content -Path $envLocal -Value ""
|
|
Add-Content -Path $envLocal -Value "# MQTT Topics for Broker 1 - Default Pattern"
|
|
Add-Content -Path $envLocal -Value "PACKETCAPTURE_MQTT1_TOPIC_STATUS=meshcore/{IATA}/{PUBLIC_KEY}/status"
|
|
Add-Content -Path $envLocal -Value "PACKETCAPTURE_MQTT1_TOPIC_PACKETS=meshcore/{IATA}/{PUBLIC_KEY}/packets"
|
|
}
|
|
}
|
|
|
|
# Ask if user wants to configure additional MQTT brokers
|
|
Write-Host ""
|
|
$addMoreBrokers = Read-Host "Would you like to configure additional MQTT brokers? (y/N)"
|
|
if ($addMoreBrokers -match '^[yY]') {
|
|
Configure-AdditionalMqttBrokers
|
|
}
|
|
}
|
|
else {
|
|
Write-Host "INFO: No MQTT brokers configured - you'll need to edit .env.local manually" -ForegroundColor Yellow
|
|
}
|
|
|
|
Write-Host "SUCCESS: Configuration file created" -ForegroundColor Green
|
|
|
|
# Final summary
|
|
Write-Host ""
|
|
Write-Host "=======================================================" -ForegroundColor Blue
|
|
Write-Host " Installation Complete!" -ForegroundColor Blue
|
|
Write-Host "=======================================================" -ForegroundColor Blue
|
|
Write-Host ""
|
|
Write-Host "Installation directory: $InstallDir"
|
|
Write-Host ""
|
|
Write-Host "Configuration file: $InstallDir\.env.local"
|
|
Write-Host ""
|
|
Write-Host "To run manually: cd $InstallDir; .\venv\Scripts\python.exe packet_capture.py"
|
|
Write-Host ""
|
|
Write-Host "SUCCESS: Installation complete!" -ForegroundColor Green
|
|
}
|
|
|
|
# Run main installation
|
|
Start-Installation
|