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:
Mahmoud-Emad
2025-11-19 15:17:39 +02:00
parent 1452d65f48
commit df462174e5
6 changed files with 113 additions and 175 deletions

View File

@@ -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

View File

@@ -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'

View File

@@ -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()!

View File

@@ -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()!
}

View File

@@ -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)!
}