refactor: Refactor Mycelium configuration and dependencies
- Flatten MyceliumConfig struct into HeroPods - Remove Mycelium installer and service management logic - Update Mycelium initialization to check for prerequisites only - Adjust peers configuration to be comma-separated string
This commit is contained in:
@@ -1,5 +1,24 @@
|
||||
# Mycelium IPv6 Overlay Network Integration for HeroPods
|
||||
|
||||
## Prerequisites
|
||||
|
||||
**Mycelium must be installed on your system before using this feature.** HeroPods does not install Mycelium automatically.
|
||||
|
||||
### Installing Mycelium
|
||||
|
||||
Download and install Mycelium from the official repository:
|
||||
|
||||
- **GitHub**: <https://github.com/threefoldtech/mycelium>
|
||||
- **Releases**: <https://github.com/threefoldtech/mycelium/releases>
|
||||
|
||||
For detailed installation instructions, see the [Mycelium documentation](https://github.com/threefoldtech/mycelium/tree/master/docs).
|
||||
|
||||
After installation, verify that the `mycelium` command is available:
|
||||
|
||||
```bash
|
||||
mycelium -V
|
||||
```
|
||||
|
||||
## Overview
|
||||
|
||||
HeroPods now supports Mycelium IPv6 overlay networking, providing end-to-end encrypted IPv6 connectivity for containers across the internet.
|
||||
@@ -18,7 +37,7 @@ Mycelium is an IPv6 overlay network that provides:
|
||||
### Components
|
||||
|
||||
1. **mycelium.v** - Core Mycelium integration logic
|
||||
- Installation and service management
|
||||
- Service management (start/stop)
|
||||
- Container IPv6 configuration
|
||||
- veth pair creation for IPv6 routing
|
||||
|
||||
@@ -61,11 +80,7 @@ All parameters are **required** when enabling Mycelium:
|
||||
version:'v0.5.6'
|
||||
ipv6_range:'400::/7'
|
||||
key_path:'~/hero/cfg/priv_key.bin'
|
||||
peers:[
|
||||
'tcp://185.69.166.8:9651',
|
||||
'quic://[2a02:1802:5e:0:ec4:7aff:fe51:e36b]:9651',
|
||||
'tcp://65.109.18.113:9651'
|
||||
]
|
||||
peers:'tcp://185.69.166.8:9651,quic://[2a02:1802:5e:0:ec4:7aff:fe51:e36b]:9651,tcp://65.109.18.113:9651'
|
||||
```
|
||||
|
||||
### Configuration Parameters
|
||||
@@ -75,7 +90,7 @@ All parameters are **required**:
|
||||
- `version` (string): Mycelium version to install (e.g., 'v0.5.6')
|
||||
- `ipv6_range` (string): Mycelium IPv6 address range (e.g., '400::/7')
|
||||
- `key_path` (string): Path to Mycelium private key (e.g., '~/hero/cfg/priv_key.bin')
|
||||
- `peers` (array of strings): Array of Mycelium peer addresses
|
||||
- `peers` (string): Comma-separated list of Mycelium peer addresses (e.g., 'tcp://185.69.166.8:9651,quic://[2a02:1802:5e:0:ec4:7aff:fe51:e36b]:9651')
|
||||
|
||||
### Default Public Peers
|
||||
|
||||
@@ -111,10 +126,7 @@ See `examples/virt/heropods/container_mycelium.heroscript` for a complete exampl
|
||||
version:'v0.5.6'
|
||||
ipv6_range:'400::/7'
|
||||
key_path:'~/hero/cfg/priv_key.bin'
|
||||
peers:[
|
||||
'tcp://185.69.166.8:9651',
|
||||
'quic://[2a02:1802:5e:0:ec4:7aff:fe51:e36b]:9651'
|
||||
]
|
||||
peers:'tcp://185.69.166.8:9651,quic://[2a02:1802:5e:0:ec4:7aff:fe51:e36b]:9651'
|
||||
|
||||
// Create and start container
|
||||
!!heropods.container_new
|
||||
|
||||
@@ -155,7 +155,7 @@ pub fn (mut self Container) start() ! {
|
||||
}
|
||||
|
||||
// Setup Mycelium IPv6 overlay network if enabled
|
||||
if self.factory.mycelium_config.enabled {
|
||||
if self.factory.mycelium_enabled {
|
||||
container_pid := self.pid()!
|
||||
self.factory.mycelium_setup_container(self.name, container_pid) or {
|
||||
self.factory.logger.log(
|
||||
@@ -442,7 +442,7 @@ fn (mut self Container) cleanup_network() ! {
|
||||
factory.network_cleanup_container(self.name)!
|
||||
|
||||
// Cleanup Mycelium IPv6 overlay network if enabled
|
||||
if factory.mycelium_config.enabled {
|
||||
if factory.mycelium_enabled {
|
||||
factory.mycelium_cleanup_container(self.name) or {
|
||||
factory.logger.log(
|
||||
cat: 'container'
|
||||
|
||||
@@ -42,22 +42,20 @@ pub mut:
|
||||
|
||||
pub fn new(args ArgsGet) !&HeroPods {
|
||||
mut obj := HeroPods{
|
||||
name: args.name
|
||||
reset: args.reset
|
||||
use_podman: args.use_podman
|
||||
network_config: NetworkConfig{
|
||||
name: args.name
|
||||
reset: args.reset
|
||||
use_podman: args.use_podman
|
||||
network_config: NetworkConfig{
|
||||
bridge_name: args.bridge_name
|
||||
subnet: args.subnet
|
||||
gateway_ip: args.gateway_ip
|
||||
dns_servers: args.dns_servers
|
||||
}
|
||||
mycelium_config: MyceliumConfig{
|
||||
enabled: args.enable_mycelium
|
||||
version: args.mycelium_version
|
||||
ipv6_range: args.mycelium_ipv6_range
|
||||
peers: args.mycelium_peers
|
||||
key_path: args.mycelium_key_path
|
||||
}
|
||||
mycelium_enabled: args.enable_mycelium
|
||||
mycelium_version: args.mycelium_version
|
||||
mycelium_ipv6_range: args.mycelium_ipv6_range
|
||||
mycelium_peers: args.mycelium_peers
|
||||
mycelium_key_path: args.mycelium_key_path
|
||||
}
|
||||
set(obj)!
|
||||
return get(name: args.name)!
|
||||
@@ -192,21 +190,22 @@ pub fn play(mut plbook PlayBook) ! {
|
||||
mycelium_key_path := p.get('key_path') or {
|
||||
return error('heropods.enable_mycelium: "key_path" is required (e.g., key_path:\'~/hero/cfg/priv_key.bin\')')
|
||||
}
|
||||
peers_array := p.get_list('peers') or {
|
||||
return error('heropods.enable_mycelium: "peers" is required. Provide array of peer addresses (e.g., peers:[\'tcp://185.69.166.8:9651\', \'quic://[2a02:1802:5e:0:ec4:7aff:fe51:e36b]:9651\'])')
|
||||
mycelium_peers_str := p.get('peers') or {
|
||||
return error('heropods.enable_mycelium: "peers" is required. Provide comma-separated list of peer addresses (e.g., peers:\'tcp://185.69.166.8:9651,quic://[2a02:1802:5e:0:ec4:7aff:fe51:e36b]:9651\')')
|
||||
}
|
||||
|
||||
// Validate peers list is not empty
|
||||
// Parse and validate peers list
|
||||
peers_array := mycelium_peers_str.split(',').map(it.trim_space()).filter(it.len > 0)
|
||||
if peers_array.len == 0 {
|
||||
return error('heropods.enable_mycelium: "peers" cannot be empty. Provide at least one peer address.')
|
||||
}
|
||||
|
||||
// Update Mycelium configuration
|
||||
hp.mycelium_config.enabled = true
|
||||
hp.mycelium_config.version = mycelium_version
|
||||
hp.mycelium_config.ipv6_range = mycelium_ipv6_range
|
||||
hp.mycelium_config.key_path = mycelium_key_path
|
||||
hp.mycelium_config.peers = peers_array
|
||||
hp.mycelium_enabled = true
|
||||
hp.mycelium_version = mycelium_version
|
||||
hp.mycelium_ipv6_range = mycelium_ipv6_range
|
||||
hp.mycelium_key_path = mycelium_key_path
|
||||
hp.mycelium_peers = peers_array
|
||||
|
||||
// Initialize Mycelium if not already done
|
||||
hp.mycelium_init()!
|
||||
|
||||
@@ -12,17 +12,8 @@ pub const version = '0.0.0'
|
||||
const singleton = false
|
||||
const default = true
|
||||
|
||||
// MyceliumConfig holds Mycelium IPv6 overlay network configuration
|
||||
struct MyceliumConfig {
|
||||
pub mut:
|
||||
enabled bool // Whether Mycelium is enabled
|
||||
version string // Mycelium version to install (e.g., 'v0.5.6')
|
||||
ipv6_range string // Mycelium IPv6 address range (e.g., '400::/7')
|
||||
peers []string // Mycelium peer addresses
|
||||
key_path string // Path to Mycelium private key
|
||||
mycelium_ip6 string // Host's Mycelium IPv6 address (cached)
|
||||
interface_name string // Mycelium TUN interface name (e.g., "mycelium0")
|
||||
}
|
||||
// MyceliumConfig holds Mycelium IPv6 overlay network configuration (flattened into HeroPods struct)
|
||||
// Note: These fields are flattened to avoid nested struct serialization issues with encoderhero
|
||||
|
||||
// HeroPods factory for managing containers
|
||||
//
|
||||
@@ -33,18 +24,25 @@ pub mut:
|
||||
@[heap]
|
||||
pub struct HeroPods {
|
||||
pub mut:
|
||||
tmux_session string // tmux session name
|
||||
containers map[string]&Container // name -> container mapping
|
||||
images map[string]&ContainerImage // name -> image mapping
|
||||
crun_configs map[string]&crun.CrunConfig // name -> crun config mapping
|
||||
base_dir string // base directory for all container data
|
||||
reset bool // will reset the heropods
|
||||
use_podman bool = true // will use podman for image management
|
||||
name string // name of the heropods
|
||||
network_config NetworkConfig @[skip; str: skip] // network configuration (automatically initialized, not serialized)
|
||||
network_mutex sync.Mutex @[skip; str: skip] // protects network_config for thread-safe concurrent access
|
||||
mycelium_config MyceliumConfig // mycelium IPv6 overlay network configuration
|
||||
logger logger.Logger @[skip; str: skip] // logger instance for debugging (not serialized)
|
||||
tmux_session string // tmux session name
|
||||
containers map[string]&Container // name -> container mapping
|
||||
images map[string]&ContainerImage // name -> image mapping
|
||||
crun_configs map[string]&crun.CrunConfig // name -> crun config mapping
|
||||
base_dir string // base directory for all container data
|
||||
reset bool // will reset the heropods
|
||||
use_podman bool = true // will use podman for image management
|
||||
name string // name of the heropods
|
||||
network_config NetworkConfig @[skip; str: skip] // network configuration (automatically initialized, not serialized)
|
||||
network_mutex sync.Mutex @[skip; str: skip] // protects network_config for thread-safe concurrent access
|
||||
// Mycelium IPv6 overlay network configuration (flattened fields)
|
||||
mycelium_enabled bool // Whether Mycelium is enabled
|
||||
mycelium_version string // Mycelium version to install (e.g., 'v0.5.6')
|
||||
mycelium_ipv6_range string // Mycelium IPv6 address range (e.g., '400::/7')
|
||||
mycelium_peers []string // Mycelium peer addresses
|
||||
mycelium_key_path string // Path to Mycelium private key
|
||||
mycelium_ip6 string // Host's Mycelium IPv6 address (cached)
|
||||
mycelium_interface_name string // Mycelium TUN interface name (e.g., "mycelium0")
|
||||
logger logger.Logger @[skip; str: skip] // logger instance for debugging (not serialized)
|
||||
}
|
||||
|
||||
// obj_init performs lightweight validation and field normalization only
|
||||
@@ -83,8 +81,8 @@ fn obj_init(mycfg_ HeroPods) !HeroPods {
|
||||
}
|
||||
|
||||
// Initialize Mycelium configuration defaults (only for non-required fields)
|
||||
if mycfg.mycelium_config.interface_name == '' {
|
||||
mycfg.mycelium_config.interface_name = 'mycelium0'
|
||||
if mycfg.mycelium_interface_name == '' {
|
||||
mycfg.mycelium_interface_name = 'mycelium0'
|
||||
}
|
||||
|
||||
return mycfg
|
||||
@@ -123,7 +121,7 @@ fn (mut self HeroPods) initialize() ! {
|
||||
self.network_init()!
|
||||
|
||||
// Initialize Mycelium IPv6 overlay network if enabled
|
||||
if self.mycelium_config.enabled {
|
||||
if self.mycelium_enabled {
|
||||
self.mycelium_init()!
|
||||
}
|
||||
|
||||
|
||||
@@ -2,36 +2,38 @@ module heropods
|
||||
|
||||
import incubaid.herolib.osal.core as osal
|
||||
import incubaid.herolib.clients.mycelium
|
||||
import incubaid.herolib.installers.net.mycelium_installer
|
||||
import time
|
||||
import crypto.sha256
|
||||
|
||||
// Initialize Mycelium for HeroPods
|
||||
//
|
||||
// This method:
|
||||
// 1. Validates required configuration
|
||||
// 2. Installs Mycelium binary if not present
|
||||
// 3. Starts Mycelium service with configured peers
|
||||
// 2. Checks that Mycelium binary is installed
|
||||
// 3. Checks that Mycelium service is running
|
||||
// 4. Retrieves the host's Mycelium IPv6 address
|
||||
//
|
||||
// Prerequisites:
|
||||
// - Mycelium must be installed on the system
|
||||
// - Mycelium service must be running
|
||||
//
|
||||
// Thread Safety:
|
||||
// This is called during HeroPods initialization, before any concurrent operations.
|
||||
fn (mut self HeroPods) mycelium_init() ! {
|
||||
if !self.mycelium_config.enabled {
|
||||
if !self.mycelium_enabled {
|
||||
return
|
||||
}
|
||||
|
||||
// Validate required configuration
|
||||
if self.mycelium_config.version == '' {
|
||||
if self.mycelium_version == '' {
|
||||
return error('Mycelium configuration error: "version" is required. Use heropods.enable_mycelium to configure.')
|
||||
}
|
||||
if self.mycelium_config.ipv6_range == '' {
|
||||
if self.mycelium_ipv6_range == '' {
|
||||
return error('Mycelium configuration error: "ipv6_range" is required. Use heropods.enable_mycelium to configure.')
|
||||
}
|
||||
if self.mycelium_config.key_path == '' {
|
||||
if self.mycelium_key_path == '' {
|
||||
return error('Mycelium configuration error: "key_path" is required. Use heropods.enable_mycelium to configure.')
|
||||
}
|
||||
if self.mycelium_config.peers.len == 0 {
|
||||
if self.mycelium_peers.len == 0 {
|
||||
return error('Mycelium configuration error: "peers" is required. Use heropods.enable_mycelium to configure.')
|
||||
}
|
||||
|
||||
@@ -40,39 +42,34 @@ fn (mut self HeroPods) mycelium_init() ! {
|
||||
log: 'START mycelium_init() - Initializing Mycelium IPv6 overlay network'
|
||||
) or {}
|
||||
|
||||
// Check if Mycelium is already installed and running
|
||||
if mycelium_installed := self.mycelium_check_installed() {
|
||||
if mycelium_installed {
|
||||
self.logger.log(
|
||||
cat: 'mycelium'
|
||||
log: 'Mycelium is already installed'
|
||||
logtype: .stdout
|
||||
) or {}
|
||||
} else {
|
||||
// Install Mycelium
|
||||
self.mycelium_install()!
|
||||
}
|
||||
// Check if Mycelium is installed - it's a prerequisite
|
||||
if !self.mycelium_check_installed()! {
|
||||
return error('Mycelium is not installed. Please install Mycelium first. See: https://github.com/threefoldtech/mycelium')
|
||||
}
|
||||
|
||||
// Start Mycelium service if not running
|
||||
if mycelium_running := self.mycelium_check_running() {
|
||||
if mycelium_running {
|
||||
self.logger.log(
|
||||
cat: 'mycelium'
|
||||
log: 'Mycelium service is already running'
|
||||
logtype: .stdout
|
||||
) or {}
|
||||
} else {
|
||||
self.mycelium_start_service()!
|
||||
}
|
||||
self.logger.log(
|
||||
cat: 'mycelium'
|
||||
log: 'Mycelium binary found'
|
||||
logtype: .stdout
|
||||
) or {}
|
||||
|
||||
// Check if Mycelium service is running - it's a prerequisite
|
||||
if !self.mycelium_check_running()! {
|
||||
return error('Mycelium service is not running. Please start Mycelium service first (e.g., mycelium --key-file ${self.mycelium_key_path} --peers <peers>)')
|
||||
}
|
||||
|
||||
self.logger.log(
|
||||
cat: 'mycelium'
|
||||
log: 'Mycelium service is running'
|
||||
logtype: .stdout
|
||||
) or {}
|
||||
|
||||
// Get and cache the host's Mycelium IPv6 address
|
||||
self.mycelium_get_host_address()!
|
||||
|
||||
self.logger.log(
|
||||
cat: 'mycelium'
|
||||
log: 'END mycelium_init() - Mycelium initialized successfully with address ${self.mycelium_config.mycelium_ip6}'
|
||||
log: 'END mycelium_init() - Mycelium initialized successfully with address ${self.mycelium_ip6}'
|
||||
logtype: .stdout
|
||||
) or {}
|
||||
}
|
||||
@@ -85,67 +82,10 @@ fn (mut self HeroPods) mycelium_check_installed() !bool {
|
||||
// Check if Mycelium service is running
|
||||
fn (mut self HeroPods) mycelium_check_running() !bool {
|
||||
// Try to inspect Mycelium - if it succeeds, it's running
|
||||
mycelium.inspect(key_file_path: self.mycelium_config.key_path) or { return false }
|
||||
mycelium.inspect(key_file_path: self.mycelium_key_path) or { return false }
|
||||
return true
|
||||
}
|
||||
|
||||
// Install Mycelium binary
|
||||
fn (mut self HeroPods) mycelium_install() ! {
|
||||
self.logger.log(
|
||||
cat: 'mycelium'
|
||||
log: 'Installing Mycelium ${self.mycelium_config.version}...'
|
||||
logtype: .stdout
|
||||
) or {}
|
||||
|
||||
// Use the mycelium_installer to install
|
||||
mut installer := mycelium_installer.get(create: true)!
|
||||
installer.peers = self.mycelium_config.peers
|
||||
|
||||
// Install Mycelium using the instance method
|
||||
installer.install(reset: false)!
|
||||
|
||||
self.logger.log(
|
||||
cat: 'mycelium'
|
||||
log: 'Mycelium installed successfully'
|
||||
logtype: .stdout
|
||||
) or {}
|
||||
}
|
||||
|
||||
// Start Mycelium service
|
||||
fn (mut self HeroPods) mycelium_start_service() ! {
|
||||
self.logger.log(
|
||||
cat: 'mycelium'
|
||||
log: 'Starting Mycelium service...'
|
||||
logtype: .stdout
|
||||
) or {}
|
||||
|
||||
// Use the mycelium_installer to start the service
|
||||
mut installer := mycelium_installer.get()!
|
||||
installer.start()!
|
||||
|
||||
// Wait for Mycelium to be ready
|
||||
for i in 0 .. 50 {
|
||||
if self.mycelium_check_running()! {
|
||||
self.logger.log(
|
||||
cat: 'mycelium'
|
||||
log: 'Mycelium service started successfully'
|
||||
logtype: .stdout
|
||||
) or {}
|
||||
return
|
||||
}
|
||||
if i % 10 == 0 {
|
||||
self.logger.log(
|
||||
cat: 'mycelium'
|
||||
log: 'Waiting for Mycelium service to start... (${i}/50)'
|
||||
logtype: .stdout
|
||||
) or {}
|
||||
}
|
||||
time.sleep(100 * time.millisecond)
|
||||
}
|
||||
|
||||
return error('Mycelium service failed to start after 5 seconds')
|
||||
}
|
||||
|
||||
// Get the host's Mycelium IPv6 address
|
||||
fn (mut self HeroPods) mycelium_get_host_address() ! {
|
||||
self.logger.log(
|
||||
@@ -155,17 +95,17 @@ fn (mut self HeroPods) mycelium_get_host_address() ! {
|
||||
) or {}
|
||||
|
||||
// Use mycelium inspect to get the address
|
||||
inspect_result := mycelium.inspect(key_file_path: self.mycelium_config.key_path)!
|
||||
inspect_result := mycelium.inspect(key_file_path: self.mycelium_key_path)!
|
||||
|
||||
if inspect_result.address == '' {
|
||||
return error('Failed to get Mycelium IPv6 address from inspect')
|
||||
}
|
||||
|
||||
self.mycelium_config.mycelium_ip6 = inspect_result.address
|
||||
self.mycelium_ip6 = inspect_result.address
|
||||
|
||||
self.logger.log(
|
||||
cat: 'mycelium'
|
||||
log: 'Host Mycelium IPv6 address: ${self.mycelium_config.mycelium_ip6}'
|
||||
log: 'Host Mycelium IPv6 address: ${self.mycelium_ip6}'
|
||||
logtype: .stdout
|
||||
) or {}
|
||||
}
|
||||
@@ -182,7 +122,7 @@ fn (mut self HeroPods) mycelium_get_host_address() ! {
|
||||
// This is called from container.start() which is already serialized per container.
|
||||
// Multiple containers can be started concurrently, each with their own veth pair.
|
||||
fn (mut self HeroPods) mycelium_setup_container(container_name string, container_pid int) ! {
|
||||
if !self.mycelium_config.enabled {
|
||||
if !self.mycelium_enabled {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -280,12 +220,12 @@ fn (mut self HeroPods) mycelium_setup_container(container_name string, container
|
||||
// Add route in container for Mycelium traffic (400::/7 via link-local)
|
||||
self.logger.log(
|
||||
cat: 'mycelium'
|
||||
log: 'Adding route for ${self.mycelium_config.ipv6_range} via ${veth_host_ll}'
|
||||
log: 'Adding route for ${self.mycelium_ipv6_range} via ${veth_host_ll}'
|
||||
logtype: .stdout
|
||||
) or {}
|
||||
|
||||
osal.exec(
|
||||
cmd: 'nsenter -t ${container_pid} -n ip route add ${self.mycelium_config.ipv6_range} via ${veth_host_ll} dev ${veth_container}'
|
||||
cmd: 'nsenter -t ${container_pid} -n ip route add ${self.mycelium_ipv6_range} via ${veth_host_ll} dev ${veth_container}'
|
||||
stdout: false
|
||||
)!
|
||||
|
||||
@@ -313,14 +253,14 @@ fn (mut self HeroPods) mycelium_setup_container(container_name string, container
|
||||
// Extracts the /64 prefix from the full IPv6 address
|
||||
// Example: "400:1234:5678::1" -> "400:1234:5678:"
|
||||
fn (mut self HeroPods) mycelium_get_ipv6_prefix() !string {
|
||||
if self.mycelium_config.mycelium_ip6 == '' {
|
||||
if self.mycelium_ip6 == '' {
|
||||
return error('Mycelium IPv6 address not set')
|
||||
}
|
||||
|
||||
// Split the address by ':' and take the first 3 parts for /64 prefix
|
||||
parts := self.mycelium_config.mycelium_ip6.split(':')
|
||||
parts := self.mycelium_ip6.split(':')
|
||||
if parts.len < 3 {
|
||||
return error('Invalid Mycelium IPv6 address format: ${self.mycelium_config.mycelium_ip6}')
|
||||
return error('Invalid Mycelium IPv6 address format: ${self.mycelium_ip6}')
|
||||
}
|
||||
|
||||
// Reconstruct the prefix (first 3 parts)
|
||||
@@ -369,7 +309,7 @@ fn (mut self HeroPods) mycelium_get_link_local_address(interface_name string) !s
|
||||
// Thread Safety:
|
||||
// This is called from container.stop() and container.delete() which are serialized per container.
|
||||
fn (mut self HeroPods) mycelium_cleanup_container(container_name string) ! {
|
||||
if !self.mycelium_config.enabled {
|
||||
if !self.mycelium_enabled {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -414,9 +354,9 @@ fn (mut self HeroPods) mycelium_cleanup_container(container_name string) ! {
|
||||
//
|
||||
// Returns the public key and IPv6 address of the Mycelium node
|
||||
pub fn (mut self HeroPods) mycelium_inspect() !mycelium.MyceliumInspectResult {
|
||||
if !self.mycelium_config.enabled {
|
||||
if !self.mycelium_enabled {
|
||||
return error('Mycelium is not enabled')
|
||||
}
|
||||
|
||||
return mycelium.inspect(key_file_path: self.mycelium_config.key_path)!
|
||||
return mycelium.inspect(key_file_path: self.mycelium_key_path)!
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user