def edit_avahi_config(): conf = Path(const.AVAHI_CONF) if not conf.exists(): return config = ConfigObj(str(conf), file_error=True) copy = deepcopy(config) config.setdefault('server', {}).setdefault('use-ipv6', 'no') config.setdefault('publish', {}).setdefault('publish-aaaa-on-ipv4', 'no') config.setdefault('reflector', {}).setdefault('enable-reflector', 'yes') if config == copy: return utils.show_data(conf, config.dict()) with NamedTemporaryFile('w') as tmp: config.filename = None lines = config.write() # avahi-daemon.conf requires a 'key=value' syntax tmp.write('\n'.join(lines).replace(' = ', '=') + '\n') tmp.flush() sh(f'sudo chmod --reference={conf} {tmp.name}') sh(f'sudo cp -fp {tmp.name} {conf}') if utils.command_exists('systemctl'): utils.info('Restarting avahi-daemon service...') sh('sudo systemctl restart avahi-daemon') else: utils.warn( '"systemctl" command not found. Please restart your machine to enable Wifi discovery.' )
def rename(ctx, measurement, pattern, replace): ctx.ensure_object(utils.ContextOpts) keys = get_keys(measurement, pattern) if not keys: utils.warn('No keys matching "{}" found'.format(pattern)) return for policy in POLICIES: read_fields(policy, measurement, keys) for policy in POLICIES: write_fields(policy, keys, pattern, replace)
def kill(zombies): """Stop and remove all containers on this host. This includes those not from Brewblox. If the --zombies flag is set, leftover processes that are still claiming a port will be forcibly removed. Use this if you get "port is already allocated" errors after your system crashed. """ utils.confirm_mode() sudo = utils.optsudo() sh(f'{sudo}docker rm --force $({sudo}docker ps -aq)', check=False) if zombies: # We can't use psutil for this, as we need root rights to get pids if not utils.command_exists('netstat'): utils.warn( 'Command `netstat` not found. Please install it by running:') utils.warn('') utils.warn( ' sudo apt-get update && sudo apt-get install net-tools') utils.warn('') return procs = re.findall(r'(\d+)/docker-proxy', sh('sudo netstat -pna', capture=True)) if procs: utils.info(f'Removing {len(procs)} zombies...') sh('sudo service docker stop') sh([f'sudo kill -9 {proc}' for proc in procs]) sh('sudo service docker start')
def remove(ctx, services): """Remove a service.""" utils.check_config() utils.confirm_mode() config = utils.read_compose() for name in services: try: del config['services'][name] utils.info(f"Removed service '{name}'") except KeyError: utils.warn(f"Service '{name}' not found") if services: utils.write_compose(config) restart_services(ctx, compose_args=['--remove-orphans'])
def test_logs(mocker): m_opts = mocker.patch(TESTED + '.ctx_opts').return_value m_secho = mocker.patch(TESTED + '.click.secho') m_opts.quiet = True utils.info('test') assert m_secho.call_count == 0 utils.warn('warning') assert m_secho.call_count == 1 utils.error('error') assert m_secho.call_count == 2 m_opts.quiet = False utils.info('test') assert m_secho.call_count == 3 utils.warn('warning') assert m_secho.call_count == 4 utils.error('error') assert m_secho.call_count == 5
def fix_ipv6(config_file=None, restart=True): utils.info('Fixing Docker IPv6 settings...') if utils.is_wsl(): utils.info('WSL environment detected. Skipping IPv6 config changes.') return # Config is either provided, or parsed from active daemon process if not config_file: default_config_file = '/etc/docker/daemon.json' dockerd_proc = sh('ps aux | grep dockerd', capture=True) proc_match = re.match(r'.*--config-file[\s=](?P<file>.*\.json).*', dockerd_proc, flags=re.MULTILINE) config_file = proc_match and proc_match.group( 'file') or default_config_file utils.info(f'Using Docker config file {config_file}') # Read config. Create file if not exists sh(f"sudo touch '{config_file}'") config = sh(f"sudo cat '{config_file}'", capture=True) if 'fixed-cidr-v6' in config: utils.info('IPv6 settings are already present. Making no changes.') return # Edit and write. Do not overwrite existing values config = json.loads(config or '{}') config.setdefault('ipv6', False) config.setdefault('fixed-cidr-v6', '2001:db8:1::/64') config_str = json.dumps(config, indent=2) sh(f"echo '{config_str}' | sudo tee '{config_file}' > /dev/null") # Restart daemon if restart: if utils.command_exists('service'): utils.info('Restarting Docker service...') sh('sudo service docker restart') else: utils.warn( '"service" command not found. Please restart your machine to apply config changes.' )
def check_ports(): if utils.path_exists('./docker-compose.yml'): utils.info('Stopping services...') sh(f'{utils.optsudo()}docker-compose down') ports = [ int(utils.getenv(key, const.ENV_DEFAULTS[key])) for key in [ const.HTTP_PORT_KEY, const.HTTPS_PORT_KEY, const.MQTT_PORT_KEY, ] ] try: port_connnections = [ conn for conn in psutil.net_connections() if conn.laddr.ip in ['::', '0.0.0.0'] and conn.laddr.port in ports ] except psutil.AccessDenied: utils.warn( 'Unable to read network connections. You need to run `netstat` or `lsof` manually.' ) port_connnections = [] if port_connnections: port_str = ', '.join( set(str(conn.laddr.port) for conn in port_connnections)) utils.warn(f'Port(s) {port_str} already in use.') utils.warn( 'Run `brewblox-ctl service ports` to configure Brewblox ports.') if not utils.confirm('Do you want to continue?'): raise SystemExit(1)
def find_usb_spark() -> usb.core.Device: while True: devices = [ *usb.core.find(find_all=True, idVendor=const.VID_PARTICLE, idProduct=const.PID_PHOTON), *usb.core.find(find_all=True, idVendor=const.VID_PARTICLE, idProduct=const.PID_P1), *usb.core.find(find_all=True, idVendor=const.VID_ESPRESSIF, idProduct=const.PID_ESP32), ] num_devices = len(devices) if num_devices == 0: utils.warn('No USB-connected Spark detected') utils.confirm_usb() elif num_devices == 1: return devices[0] else: utils.warn(f'{len(devices)} USB-connected Sparks detected.') utils.confirm_usb()
def migrate_influxdb( target: str = 'victoria', duration: str = '', services: List[str] = [], offsets: List[Tuple[str, int]] = [], ): """Exports InfluxDB history data. The exported data is either immediately imported to the new history database, or saved to file. """ opts = utils.ctx_opts() sudo = utils.optsudo() date = datetime.now().strftime('%Y%m%d_%H%M') utils.warn('Depending on the amount of data, this may take some hours.') utils.warn( 'You can use your system as normal while the migration is in progress.' ) utils.warn('The migration can safely be stopped and restarted or resumed.') utils.warn( 'For more info, see https://brewblox.netlify.app/dev/migration/influxdb.html' ) if opts.dry_run: utils.info('Dry run. Skipping migration...') return if not utils.path_exists('./influxdb/'): utils.info('influxdb/ dir not found. Skipping migration...') return utils.info('Starting InfluxDB container...') # Stop container in case previous migration was cancelled sh(f'{sudo}docker stop influxdb-migrate > /dev/null', check=False) # Start standalone container # We'll communicate using 'docker exec', so no need to publish a port sh(f'{sudo}docker run ' '--rm -d ' '--name influxdb-migrate ' '-v "$(pwd)/influxdb:/var/lib/influxdb" ' 'influxdb:1.8 ' '> /dev/null') # Do a health check until startup is done inner_cmd = 'curl --output /dev/null --silent --fail http://localhost:8086/health' bash_cmd = f'until $({inner_cmd}); do sleep 1 ; done' sh(f"{sudo}docker exec influxdb-migrate bash -c '{bash_cmd}'") # Determine relevant measurement # Export all of them if not specified by user if not services: services = _influx_measurements() utils.info(f'Exporting services: {", ".join(services)}') # Export data and import to target for svc in services: offset = next((v for v in offsets if v[0] == svc), ('default', 0))[1] _copy_influx_measurement(svc, date, duration, target, offset) # Stop migration container sh(f'{sudo}docker stop influxdb-migrate > /dev/null', check=False)
def add_spark(name, discover_now, device_id, discovery_type, device_host, command, force, release, simulation): """ Create or update a Spark service. If you run brewblox-ctl add-spark without any arguments, it will prompt you for required info, and then create a sensibly configured service. If you want to fine-tune your service configuration, multiple arguments are available. For a detailed explanation: https://brewblox.netlify.com/user/connect_settings.html """ # utils.check_config() utils.confirm_mode() image_name = 'brewblox/brewblox-devcon-spark' sudo = utils.optsudo() config = utils.read_compose() if not force: check_duplicate(config, name) for (nm, svc) in config['services'].items(): img = svc.get('image', '') cmd = svc.get('command', '') if not any([ nm == name, not img.startswith(image_name), '--device-id' in cmd, '--device-host' in cmd, '--simulation' in cmd, ]): utils.warn(f'The existing Spark service `{nm}` does not have any connection settings.') utils.warn('It will connect to any controller it can find.') utils.warn('This may cause multiple services to connect to the same controller.') utils.warn(f'To reconfigure `{nm}`, please run:') utils.warn('') utils.warn(f' brewblox-ctl add-spark -f --name {nm}') utils.warn('') utils.select('Press ENTER to continue or Ctrl-C to exit') if discover_now and not simulation and not device_id: if device_host: dev = find_device_by_host(device_host) else: dev = choose_device(discovery_type, config) if dev: device_id = dev['id'] else: # We have no device ID, and no device host. Avoid a wildcard service click.echo('No valid combination of device ID and device host.') raise SystemExit(1) commands = [ '--name=' + name, '--discovery=' + discovery_type, ] if device_id: commands += ['--device-id=' + device_id] if device_host: commands += ['--device-host=' + device_host] if simulation: commands += ['--simulation'] if command: commands += [command] config['services'][name] = { 'image': f'{image_name}:{utils.docker_tag(release)}', 'privileged': True, 'restart': 'unless-stopped', 'command': ' '.join(commands) } if simulation: mount_dir = f'simulator__{name}' config['services'][name]['volumes'] = [{ 'type': 'bind', 'source': f'./{mount_dir}', 'target': '/app/simulator' }] sh(f'mkdir -m 777 -p {mount_dir}') utils.write_compose(config) click.echo(f'Added Spark service `{name}`.') click.echo('It will automatically show up in the UI.\n') if utils.confirm('Do you want to run `brewblox-ctl up` now?'): sh(f'{sudo}docker-compose up -d')
def upped_migrate(prev_version): """Migration commands to be executed after the services have been started""" if prev_version < StrictVersion('0.6.0'): utils.warn('') utils.warn('Brewblox now uses a new configuration database.') utils.warn('To migrate your data, run:') utils.warn('') utils.warn(' brewblox-ctl database from-couchdb') utils.warn('') if prev_version < StrictVersion('0.7.0'): utils.warn('') utils.warn('Brewblox now uses a new history database.') utils.warn('To migrate your data, run:') utils.warn('') utils.warn(' brewblox-ctl database from-influxdb') utils.warn('')