Implement connectivity store services (wireguard-ext, openvpn-client, tor)
Unit Tests / test (push) Successful in 11m31s
Unit Tests / test (push) Successful in 11m31s
- ConnectivityManager: move config dirs to data_dir/services/<id>/config so Docker can bind-mount them into store-service containers (Docker resolves bind-mount paths on the host, not inside the API container). Add _migrate_legacy_configs to copy existing files from the old config_dir location on first boot. - manifest_validator: add allow_host_network parameter to validate_rendered_compose. When True, waives the external-network requirement, permits network_mode: host, and allows devices: — all needed by VPN/Tor containers that must share the host network namespace to create tun/wg interfaces. Non-host services are unaffected. - service_composer: read requires_host_network from the manifest and pass allow_host_network=True to validate_rendered_compose for connectivity services. - Tests: update file-path assertions to new data_dir layout; add TestMigrateLegacyConfigs, TestValidateRenderedComposeHostNetwork, and two TestWriteCompose cases for the host-network path. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -80,19 +80,56 @@ class ConnectivityManager(BaseServiceManager):
|
||||
self.config_manager = config_manager
|
||||
self.peer_registry = peer_registry
|
||||
|
||||
# Config file directories
|
||||
self.connectivity_config_dir = os.path.join(config_dir, 'connectivity')
|
||||
self.wireguard_ext_dir = os.path.join(self.connectivity_config_dir, 'wireguard_ext')
|
||||
self.openvpn_dir = os.path.join(self.connectivity_config_dir, 'openvpn')
|
||||
# Connectivity configs live under the per-service data dir so that
|
||||
# ${PIC_DATA_DIR}/services/<id>/config bind mounts in store compose
|
||||
# templates can read them (Docker daemon resolves paths on the HOST,
|
||||
# so they must be reachable via data_dir, not config_dir).
|
||||
services_dir = os.path.join(data_dir, 'services')
|
||||
self.wireguard_ext_dir = os.path.join(services_dir, 'wireguard-ext', 'config')
|
||||
self.openvpn_dir = os.path.join(services_dir, 'openvpn-client', 'config')
|
||||
|
||||
for d in (self.connectivity_config_dir, self.wireguard_ext_dir, self.openvpn_dir):
|
||||
for d in (self.wireguard_ext_dir, self.openvpn_dir):
|
||||
self.safe_makedirs(d)
|
||||
|
||||
# One-shot migration from the legacy config_dir/connectivity/ location.
|
||||
_legacy_base = os.path.join(config_dir, 'connectivity')
|
||||
self._migrate_legacy_configs(_legacy_base)
|
||||
|
||||
# Subscribe to ServiceBus CONFIG_CHANGED events so routes are
|
||||
# reapplied if the underlying network changes. Done lazily —
|
||||
# service_bus is a singleton imported at app startup.
|
||||
self._subscribe_to_events()
|
||||
|
||||
# ── Legacy migration ──────────────────────────────────────────────────
|
||||
|
||||
def _migrate_legacy_configs(self, legacy_base: str) -> None:
|
||||
"""Copy files from the old config_dir/connectivity/ tree to the new data_dir locations.
|
||||
|
||||
The old layout stored WireGuard and OpenVPN configs under the API container's
|
||||
config_dir, which Docker cannot bind-mount into store-service containers. Files
|
||||
are copied (not moved) so the legacy location still works until the operator
|
||||
removes it manually.
|
||||
"""
|
||||
import shutil
|
||||
|
||||
pairs = (
|
||||
(os.path.join(legacy_base, 'wireguard_ext'), self.wireguard_ext_dir),
|
||||
(os.path.join(legacy_base, 'openvpn'), self.openvpn_dir),
|
||||
)
|
||||
for src_dir, dst_dir in pairs:
|
||||
if not os.path.isdir(src_dir):
|
||||
continue
|
||||
try:
|
||||
for fname in os.listdir(src_dir):
|
||||
src_file = os.path.join(src_dir, fname)
|
||||
dst_file = os.path.join(dst_dir, fname)
|
||||
if os.path.isfile(src_file) and not os.path.exists(dst_file):
|
||||
shutil.copy2(src_file, dst_file)
|
||||
os.chmod(dst_file, 0o600)
|
||||
logger.info('connectivity: migrated %s → %s', src_file, dst_file)
|
||||
except OSError as e:
|
||||
logger.warning('connectivity: migration from %s failed: %s', src_dir, e)
|
||||
|
||||
# ── Event wiring ──────────────────────────────────────────────────────
|
||||
|
||||
def _subscribe_to_events(self) -> None:
|
||||
|
||||
Reference in New Issue
Block a user