Sync build v0.9.144

Automated sync from private repository.
Commit: 87d980147e1a74ebe5c32ae47c4179e04cee120b
This commit is contained in:
GitHub Actions Bot
2026-01-18 07:41:22 +00:00
parent 09d7f776af
commit 2f23148ca4
18 changed files with 40 additions and 267 deletions
+1 -1
View File
@@ -1 +1 @@
0.9.143
0.9.144
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
import{j as e,D as s,a2 as t,F as a,r as l,aP as c,at as n,aW as i}from"./vendor-react-O8XH9yVn.js";import{c as r}from"./recharts-BU7auunF.js";import{a3 as o,a4 as x,b as m,a5 as d,p as u,a2 as p,a6 as h,a7 as j}from"./index-Dp0UlTio.js";import{u as g}from"./usePolling-UdPi_fKb.js";import{P as N,b as f,a as v,c as b,L as y}from"./PageLayout-DE5PEtD2.js";import"./vendor-core-WoOfkQwm.js";import"./deckgl-DTsmDcfs.js";const w=l.memo(function({log:s}){return e.jsx("div",{className:"p-3 rounded-2xl bg-white/[0.02] hover:bg-white/[0.04] transition-colors",children:e.jsxs("div",{className:"flex items-baseline gap-3",children:[e.jsx("span",{className:r("type-data-sm w-14 shrink-0",j(s.level)),children:s.level}),e.jsxs("div",{className:"flex-1 min-w-0",children:[e.jsx("p",{className:"type-data-sm text-text-primary break-words whitespace-pre-wrap",children:s.message}),e.jsx("p",{className:"type-data-xs text-text-muted mt-1",children:new Date(s.timestamp).toLocaleString()})]})]})})});function F(){const s=o(),[t,a]=l.useState("INFO"),[x,m]=l.useState(!1),[d,u]=l.useState(null);l.useEffect(()=>{if(s.length>0){const e=s.some(e=>"DEBUG"===e.level);a(e?"DEBUG":"INFO")}},[s]);const p=l.useCallback(async e=>{if(e!==t&&!x){m(!0),u(null);try{const s=await h(e);s.success&&s.data?(a(e),u(s.data.message),setTimeout(()=>u(null),5e3)):(u(s.error??"Failed to change log level"),setTimeout(()=>u(null),3e3))}catch{u("Failed to change log level"),setTimeout(()=>u(null),3e3)}finally{m(!1)}}},[t,x]),j="h-[32px] px-4 rounded-full type-data flex items-center gap-2 transition-colors",g=x&&"opacity-50 cursor-not-allowed";return e.jsxs("div",{className:"flex items-center gap-2",children:[d&&e.jsx("span",{className:"text-xs text-text-muted animate-pulse",children:d}),e.jsxs("div",{className:"flex bg-white/[0.02] rounded-full p-1",children:[e.jsxs("button",{onClick:()=>p("INFO"),disabled:x,className:r(j,"INFO"===t?"bg-accent-primary/20 text-accent-primary":"text-text-muted hover:text-text-secondary",g),children:[e.jsx(c,{className:"w-4 h-4"}),"Info"]}),e.jsxs("button",{onClick:()=>p("DEBUG"),disabled:x,className:r(j,"DEBUG"===t?"bg-amber-500/20 text-amber-400":"text-text-muted hover:text-text-secondary",g),children:[x?e.jsx(n,{className:"w-4 h-4 animate-spin"}):e.jsx(i,{className:"w-4 h-4"}),"Debug"]})]})]})}function k(){const l=o(),c=x(),n=m(),i=d(),h=u();return g(i,p.logs,n),e.jsxs(N,{children:[e.jsx(f,{title:"System Logs",icon:e.jsx(a,{}),controls:e.jsxs(e.Fragment,{children:[e.jsx(F,{}),n&&e.jsxs("div",{className:"flex items-center gap-2 text-sm",children:[e.jsx(s,{className:"w-2 h-2 fill-accent-success text-accent-success animate-pulse"}),e.jsx("span",{className:"text-text-muted",children:"Live"})]}),e.jsxs("button",{onClick:()=>h(!n),className:r("h-[32px] px-4 rounded-full type-data transition-colors","flex items-center gap-2",n?"bg-accent-success/20 text-accent-success":"bg-white/[0.02] text-text-muted hover:text-text-secondary"),children:[e.jsx(t,{className:r("w-4 h-4",n&&"animate-spin")}),e.jsx("span",{className:"hidden xs:inline",children:n?"Live":"Paused"})]})]})}),e.jsxs(v,{noPadding:!0,children:[e.jsx(b,{listHeader:!0,icon:e.jsx(a,{className:"icon-sm"}),title:"Log Entries"}),e.jsx("div",{className:"space-y-2 max-h-[calc(100vh-300px)] sm:max-h-[calc(100vh-250px)] overflow-y-auto p-4",children:c&&0===l.length?e.jsx(y,{count:10}):0===l.length?e.jsx("div",{className:"text-center py-12 text-text-muted",children:"No logs available"}):l.map((s,t)=>e.jsx(w,{log:s},`${s.timestamp}-${t}`))})]})]})}export{k as default};
import{j as e,D as s,a2 as t,F as a,r as l,aP as c,at as n,aW as i}from"./vendor-react-O8XH9yVn.js";import{c as r}from"./recharts-BU7auunF.js";import{a3 as o,a4 as x,b as m,a5 as d,p as u,a2 as p,a6 as h,a7 as j}from"./index-DGrzd8xb.js";import{u as g}from"./usePolling-UdPi_fKb.js";import{P as N,b as f,a as v,c as b,L as y}from"./PageLayout-DE5PEtD2.js";import"./vendor-core-WoOfkQwm.js";import"./deckgl-DTsmDcfs.js";const w=l.memo(function({log:s}){return e.jsx("div",{className:"p-3 rounded-2xl bg-white/[0.02] hover:bg-white/[0.04] transition-colors",children:e.jsxs("div",{className:"flex items-baseline gap-3",children:[e.jsx("span",{className:r("type-data-sm w-14 shrink-0",j(s.level)),children:s.level}),e.jsxs("div",{className:"flex-1 min-w-0",children:[e.jsx("p",{className:"type-data-sm text-text-primary break-words whitespace-pre-wrap",children:s.message}),e.jsx("p",{className:"type-data-xs text-text-muted mt-1",children:new Date(s.timestamp).toLocaleString()})]})]})})});function F(){const s=o(),[t,a]=l.useState("INFO"),[x,m]=l.useState(!1),[d,u]=l.useState(null);l.useEffect(()=>{if(s.length>0){const e=s.some(e=>"DEBUG"===e.level);a(e?"DEBUG":"INFO")}},[s]);const p=l.useCallback(async e=>{if(e!==t&&!x){m(!0),u(null);try{const s=await h(e);s.success&&s.data?(a(e),u(s.data.message),setTimeout(()=>u(null),5e3)):(u(s.error??"Failed to change log level"),setTimeout(()=>u(null),3e3))}catch{u("Failed to change log level"),setTimeout(()=>u(null),3e3)}finally{m(!1)}}},[t,x]),j="h-[32px] px-4 rounded-full type-data flex items-center gap-2 transition-colors",g=x&&"opacity-50 cursor-not-allowed";return e.jsxs("div",{className:"flex items-center gap-2",children:[d&&e.jsx("span",{className:"text-xs text-text-muted animate-pulse",children:d}),e.jsxs("div",{className:"flex bg-white/[0.02] rounded-full p-1",children:[e.jsxs("button",{onClick:()=>p("INFO"),disabled:x,className:r(j,"INFO"===t?"bg-accent-primary/20 text-accent-primary":"text-text-muted hover:text-text-secondary",g),children:[e.jsx(c,{className:"w-4 h-4"}),"Info"]}),e.jsxs("button",{onClick:()=>p("DEBUG"),disabled:x,className:r(j,"DEBUG"===t?"bg-amber-500/20 text-amber-400":"text-text-muted hover:text-text-secondary",g),children:[x?e.jsx(n,{className:"w-4 h-4 animate-spin"}):e.jsx(i,{className:"w-4 h-4"}),"Debug"]})]})]})}function k(){const l=o(),c=x(),n=m(),i=d(),h=u();return g(i,p.logs,n),e.jsxs(N,{children:[e.jsx(f,{title:"System Logs",icon:e.jsx(a,{}),controls:e.jsxs(e.Fragment,{children:[e.jsx(F,{}),n&&e.jsxs("div",{className:"flex items-center gap-2 text-sm",children:[e.jsx(s,{className:"w-2 h-2 fill-accent-success text-accent-success animate-pulse"}),e.jsx("span",{className:"text-text-muted",children:"Live"})]}),e.jsxs("button",{onClick:()=>h(!n),className:r("h-[32px] px-4 rounded-full type-data transition-colors","flex items-center gap-2",n?"bg-accent-success/20 text-accent-success":"bg-white/[0.02] text-text-muted hover:text-text-secondary"),children:[e.jsx(t,{className:r("w-4 h-4",n&&"animate-spin")}),e.jsx("span",{className:"hidden xs:inline",children:n?"Live":"Paused"})]})]})}),e.jsxs(v,{noPadding:!0,children:[e.jsx(b,{listHeader:!0,icon:e.jsx(a,{className:"icon-sm"}),title:"Log Entries"}),e.jsx("div",{className:"space-y-2 max-h-[calc(100vh-300px)] sm:max-h-[calc(100vh-250px)] overflow-y-auto p-4",children:c&&0===l.length?e.jsx(y,{count:10}):0===l.length?e.jsx("div",{className:"text-center py-12 text-text-muted",children:"No logs available"}):l.map((s,t)=>e.jsx(w,{log:s},`${s.timestamp}-${t}`))})]})]})}export{k as default};
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
import{e as t,aw as e}from"./index-Dp0UlTio.js";function o(t){if(Array.isArray(t))return t;if("string"==typeof t&&t.startsWith("["))try{const e=JSON.parse(t);return Array.isArray(e)?e:[]}catch{return[]}return[]}function n(n,r,s){const a=new Map;for(const e of r){const o=t(e);a.has(o)||a.set(o,e)}const c=new Map,i=new Map;for(const t of n){const n=t.route??t.route_type;if(!e(n))continue;const r=o(t.original_path);if(0===r.length)continue;const f=r.map(t=>t.toUpperCase());if(!t.transmitted&&f.length>=2){const t=f[f.length-2];if(t){const e=a.get(t);e&&i.set(e,(i.get(e)??0)+1)}}if(f.includes(s))for(const t of f){if(t===s)continue;const e=a.get(t);e&&c.set(e,(c.get(e)??0)+1)}}let f=0,u=0;for(const t of r)f=Math.max(f,c.get(t)??0),u=Math.max(u,i.get(t)??0);const h=new Map;let g=0,l=0,p=0;for(const t of r){const e=c.get(t)??0,o=i.get(t)??0,n=f>0?Math.round(e/f*100):0,r=u>0?Math.round(o/u*100):0,s=n+r;h.set(t,{hash:t,listenerCount:e,loudCount:o,listenerScore:n,loudScore:r,blendedScore:s}),g=Math.max(g,n),l=Math.max(l,r),p=Math.max(p,s)}return{scores:h,maxListenerScore:g,maxLoudScore:l,maxBlendedScore:p}}const r={YELLOW:"#FBBF24",GREEN:"#719872",RED:"#E12672",GRAY:"#505050"};export{r as L,n as c};
import{e as t,aw as e}from"./index-DGrzd8xb.js";function o(t){if(Array.isArray(t))return t;if("string"==typeof t&&t.startsWith("["))try{const e=JSON.parse(t);return Array.isArray(e)?e:[]}catch{return[]}return[]}function n(n,r,s){const a=new Map;for(const e of r){const o=t(e);a.has(o)||a.set(o,e)}const c=new Map,i=new Map;for(const t of n){const n=t.route??t.route_type;if(!e(n))continue;const r=o(t.original_path);if(0===r.length)continue;const f=r.map(t=>t.toUpperCase());if(!t.transmitted&&f.length>=2){const t=f[f.length-2];if(t){const e=a.get(t);e&&i.set(e,(i.get(e)??0)+1)}}if(f.includes(s))for(const t of f){if(t===s)continue;const e=a.get(t);e&&c.set(e,(c.get(e)??0)+1)}}let f=0,u=0;for(const t of r)f=Math.max(f,c.get(t)??0),u=Math.max(u,i.get(t)??0);const h=new Map;let g=0,l=0,p=0;for(const t of r){const e=c.get(t)??0,o=i.get(t)??0,n=f>0?Math.round(e/f*100):0,r=u>0?Math.round(o/u*100):0,s=n+r;h.set(t,{hash:t,listenerCount:e,loudCount:o,listenerScore:n,loudScore:r,blendedScore:s}),g=Math.max(g,n),l=Math.max(l,r),p=Math.max(p,s)}return{scores:h,maxListenerScore:g,maxLoudScore:l,maxBlendedScore:p}}const r={YELLOW:"#FBBF24",GREEN:"#719872",RED:"#E12672",GRAY:"#505050"};export{r as L,n as c};
+1 -1
View File
@@ -38,7 +38,7 @@
--font-data: 'JetBrains Mono', 'SF Mono', Monaco, monospace;
}
</style>
<script type="module" crossorigin src="/assets/index-Dp0UlTio.js"></script>
<script type="module" crossorigin src="/assets/index-DGrzd8xb.js"></script>
<link rel="modulepreload" crossorigin href="/assets/vendor-core-WoOfkQwm.js">
<link rel="modulepreload" crossorigin href="/assets/vendor-react-O8XH9yVn.js">
<link rel="stylesheet" crossorigin href="/assets/index-DjRKexsi.css">
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "pymc_console",
"version": "0.9.143",
"version": "0.9.144",
"description": "Vite + React Dashboard for pyMC_Repeater",
"private": true,
"type": "module",
+21 -248
View File
@@ -897,17 +897,13 @@ do_install() {
}
# =========================================================================
# Step 4: Apply patches to installed files
# Step 4: Apply log level API patch
# =========================================================================
print_step 4 $total_steps "Applying pyMC Console patches"
print_step 4 $total_steps "Configuring log level API"
# Apply patches to /opt/pymc_repeater (the installed location, not the clone)
print_info "Patching installed files..."
# PATCH 1 & 5 removed - merged upstream in PR #36 (feat/identity branch)
patch_logging_section "$INSTALL_DIR" # PATCH 2: Ensure logging section exists
patch_log_level_api "$INSTALL_DIR" # PATCH 3: Log level toggle API
patch_mesh_cli "$INSTALL_DIR" # PATCH 4: MeshCore CLI parity
patch_private_key_api "$INSTALL_DIR" # PATCH 6: Private key get/set API
# Apply single patch: POST /api/set_log_level endpoint for Logs page toggle
# Will be removed once upstream merges this feature
patch_log_level_api "$INSTALL_DIR"
# =========================================================================
# Step 5: Install dashboard and console extras
@@ -1283,18 +1279,13 @@ Continue?"; then
return 1
}
# Step 4: Apply patches and update dashboard
# Step 4: Apply log level API patch and update dashboard
((step_num++)) || true
print_step $step_num $total_steps "Applying pyMC Console patches & dashboard"
print_step $step_num $total_steps "Updating dashboard & log level API"
# Apply patches to /opt/pymc_repeater (the installed location)
print_info "Patching installed files..."
patch_api_endpoints "$INSTALL_DIR" # PATCH 1: Radio config API endpoint
patch_logging_section "$INSTALL_DIR" # PATCH 2: Ensure logging section exists
patch_log_level_api "$INSTALL_DIR" # PATCH 3: Log level toggle API
patch_mesh_cli "$INSTALL_DIR" # PATCH 4: MeshCore CLI parity
patch_stats_api "$INSTALL_DIR" # PATCH 5: Extend stats API with MeshCore config
patch_private_key_api "$INSTALL_DIR" # PATCH 6: Private key get/set API
# Apply single patch: POST /api/set_log_level endpoint for Logs page toggle
# Will be removed once upstream merges this feature
patch_log_level_api "$INSTALL_DIR"
# Ensure --log-level DEBUG
if [ -f /etc/systemd/system/pymc-repeater.service ]; then
@@ -2288,68 +2279,26 @@ run_upstream_installer() {
# ============================================================================
# PATCH REGISTRY
# ============================================================================
# Core patches that enhance pyMC_Repeater with pyMC Console features.
# These patches are candidates for upstream PR submission.
# Minimal patches for pyMC Console. Most functionality now provided natively
# by upstream pyMC_Repeater dev branch.
#
# REMOVED (Merged Upstream in PR #36 - feat/identity branch):
# - patch_api_endpoints - /api/update_radio_config endpoint
# - patch_stats_api - Extended /api/stats with max_flood_hops, advert_interval_minutes, rx_delay_base
# REMOVED (No longer needed - upstream provides natively):
# - patch_api_endpoints - Merged upstream in PR #36
# - patch_stats_api - Merged upstream in PR #36
# - patch_logging_section - Fixed in upstream dev branch (main.py lines 535-538)
# - patch_mesh_cli - Not essential; Terminal.tsx uses /api/stats data directly
# - patch_private_key_api - Use Identity Management API (/api/identities) instead
#
# Remaining Patches:
#
# 2. patch_logging_section (main.py)
# - Ensures config['logging'] exists before setting level from --log-level arg
# - Fixes KeyError when service starts with --log-level DEBUG
# - PR Status: Pending
# REMAINING (Pending upstream PR):
#
# 3. patch_log_level_api (api_endpoints.py)
# - Adds POST /api/set_log_level endpoint
# - Allows web UI to toggle log level (INFO/DEBUG) and restart service
# - PR Status: Pending
# - PR Status: Pending - will be removed once merged upstream
#
# 4. patch_mesh_cli (mesh_cli.py)
# - Enhances mesh CLI with MeshCore CommonCLI.cpp parity
# - tempradio with auto-revert timer, reboot, stats-*, board, neighbor.remove
# - Implemented via external Python patch script (patches/mesh_cli_enhancements.py)
# - PR Status: Pending
#
# 6. patch_private_key_api (mesh_cli.py)
# - Adds get/set prv.key for private key management via Terminal
# - Stores key in config['mesh']['identity_key']
# - PR Status: Pending
#
# NOTE: patch_static_file_serving was removed
# Upstream's default() method already returns index.html for all unknown routes,
# which is exactly what a true SPA needs. React Router handles client-side routing.
#
# NOTE: GPIO patches (Fix A-D) were removed after discovery that the real issue
# was a race condition in pymc_core's interrupt initialization. Adding --log-level
# DEBUG to the service provides enough delay for the asyncio event loop to
# initialize before interrupt callbacks are registered. See create_backend_service().
#
# To generate clean patches for upstream PR:
# 1. Clone fresh pyMC_Repeater
# 2. Apply patches via manage.sh upgrade
# 3. git diff > patches/feature-name.patch
# NOTE: GPIO timing issue is handled by --log-level DEBUG in service file.
# ============================================================================
# ------------------------------------------------------------------------------
# PATCH 1: Radio Configuration API Endpoint [REMOVED - MERGED UPSTREAM PR #36]
# ------------------------------------------------------------------------------
# This patch was merged upstream in PR #36 to the feat/identity branch.
# The patch is preserved below (commented out) for reference and for users
# who may still be on main/dev branches before the PR is merged there.
# ------------------------------------------------------------------------------
# Patch removed - see PR #36: https://github.com/rightup/pyMC_Repeater/pull/36
patch_api_endpoints() {
# DISABLED: This patch was merged upstream in PR #36 (feat/identity branch)
# See: https://github.com/rightup/pyMC_Repeater/pull/36
# The /api/update_radio_config endpoint is now part of upstream pyMC_Repeater
print_info "API endpoints patch skipped (merged upstream in PR #36)"
return 0
}
# ------------------------------------------------------------------------------
# PATCH 3: Log Level API Endpoint
# ------------------------------------------------------------------------------
@@ -2472,182 +2421,6 @@ PATCHEOF
fi
}
# ------------------------------------------------------------------------------
# PATCH 2: Ensure logging section exists before setting level (main.py)
# ------------------------------------------------------------------------------
patch_logging_section() {
local target_dir="${1:-$CLONE_DIR}"
local main_file="$target_dir/repeater/main.py"
if [ ! -f "$main_file" ]; then
print_warning "main.py not found, skipping logging patch"
return 0
fi
# Check if already patched (upstream may have fixed this)
if grep -q 'if "logging" not in config' "$main_file" 2>/dev/null; then
print_info "Logging section already guarded (upstream fix)"
return 0
fi
# Only patch if the vulnerable pattern exists
if grep -q 'if args.log_level:' "$main_file" 2>/dev/null; then
python3 << PATCHEOF
import io, sys
path = "$main_file"
with open(path, 'r') as f:
s = f.read()
old = """
if args.log_level:
config[\"logging\"][\"level\"] = args.log_level
"""
new = """
if args.log_level:
if \"logging\" not in config:
config[\"logging\"] = {}
config[\"logging\"][\"level\"] = args.log_level
"""
if old in s and new not in s:
s = s.replace(old, new)
else:
# Try a more flexible replacement using lines
lines = s.splitlines(True)
out = []
i = 0
while i < len(lines):
line = lines[i]
if line.strip().startswith("if args.log_level"):
out.append(line)
i += 1
if i < len(lines) and "config[\"logging\"][\"level\"]" in lines[i]:
indent = lines[i].split('c')[0] # leading spaces
out.append(f"{indent}if \"logging\" not in config:\n")
out.append(f"{indent} config[\"logging\"] = {{}}\n")
out.append(lines[i])
i += 1
continue
out.append(line)
i += 1
s = ''.join(out)
with open(path, 'w') as f:
f.write(s)
print("Patched logging section in main.py")
PATCHEOF
# Verify
if grep -q 'if "logging" not in config' "$main_file"; then
print_success "Patched logging section in main.py"
else
print_warning "Logging patch may not have applied"
fi
else
print_info "No log_level handling found - may be older version"
fi
}
# ------------------------------------------------------------------------------
# PATCH 4: MeshCore CLI Parity (mesh_cli.py)
# ------------------------------------------------------------------------------
# File: repeater/handler_helpers/mesh_cli.py
# Purpose: Enhance mesh CLI with MeshCore CommonCLI.cpp parity
# Changes:
# - tempradio with auto-revert timer (saves config, reverts after timeout)
# - reboot via systemctl restart
# - neighbor.remove implementation
# - clear stats implementation
# - stats-packets, stats-radio, stats-core commands
# - board command for platform info
# PR Status: Pending upstream submission
# ------------------------------------------------------------------------------
patch_mesh_cli() {
local target_dir="${1:-$CLONE_DIR}"
local mesh_cli_file="$target_dir/repeater/handler_helpers/mesh_cli.py"
local script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
local patch_script="$script_dir/patches/mesh_cli_enhancements.py"
if [ ! -f "$mesh_cli_file" ]; then
print_warning "mesh_cli.py not found, skipping MeshCore CLI patch"
return 0
fi
# Check if already patched (look for our marker comment)
if grep -q 'pymc_console: tempradio' "$mesh_cli_file" 2>/dev/null; then
print_info "MeshCore CLI patch already applied"
return 0
fi
# Check if patch script exists
if [ ! -f "$patch_script" ]; then
print_warning "Patch script not found: $patch_script"
print_info "Skipping MeshCore CLI enhancements"
return 0
fi
# Run the Python patch script
if python3 "$patch_script" "$mesh_cli_file" 2>/dev/null; then
print_success "Applied MeshCore CLI parity patch"
else
print_warning "MeshCore CLI patch may not have applied correctly"
fi
}
# ------------------------------------------------------------------------------
# PATCH 5: Stats API Extension [REMOVED - MERGED UPSTREAM PR #36]
# ------------------------------------------------------------------------------
# This patch was merged upstream in PR #36 to the feat/identity branch.
# The /api/stats endpoint now includes max_flood_hops, advert_interval_minutes,
# and rx_delay_base in the response.
# ------------------------------------------------------------------------------
# Patch removed - see PR #36: https://github.com/rightup/pyMC_Repeater/pull/36
patch_stats_api() {
# Patch disabled - merged upstream in PR #36 (feat/identity branch)
print_info "Stats API patch skipped (merged upstream in PR #36)"
return 0
}
# ------------------------------------------------------------------------------
# PATCH 6: Private Key API (mesh_cli.py)
# ------------------------------------------------------------------------------
# File: repeater/handler_helpers/mesh_cli.py
# Purpose: Enable get/set prv.key for private key management via Terminal
# Changes:
# - get prv.key: Returns 32-byte Ed25519 signing key seed in hex
# - set prv.key: Stores key in config['mesh']['identity_key'], requires restart
# Security: Only accessible to admin users via authenticated CLI
# PR Status: Pending upstream submission
# ------------------------------------------------------------------------------
patch_private_key_api() {
local target_dir="${1:-$CLONE_DIR}"
local mesh_cli_file="$target_dir/repeater/handler_helpers/mesh_cli.py"
local script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
local patch_script="$script_dir/patches/private_key_api.py"
if [ ! -f "$mesh_cli_file" ]; then
print_warning "mesh_cli.py not found, skipping private key API patch"
return 0
fi
# Check if already patched
if grep -q 'pymc_console: prv.key' "$mesh_cli_file" 2>/dev/null; then
print_info "Private key API patch already applied"
return 0
fi
# Check if patch script exists
if [ ! -f "$patch_script" ]; then
print_warning "Patch script not found: $patch_script"
print_info "Skipping private key API"
return 0
fi
# Run the Python patch script
if python3 "$patch_script" "$mesh_cli_file" 2>/dev/null; then
print_success "Applied private key API patch"
else
print_warning "Private key API patch may not have applied correctly"
fi
}
install_backend_service() {
# Copy upstream's service file as base (from clone directory)
local service_file="$CLONE_DIR/pymc-repeater.service"