def set_env(key, value): """Set a .env variable. The value will be added to the .env file. You can set new variables with this. """ utils.check_config() utils.confirm_mode() utils.setenv(key, value)
def skip_confirm(value): """Auto-answer 'yes' when prompted to confirm commands. This sets the 'BREWBLOX_SKIP_CONFIRM' variable in .env. You can still use the `brewblox-ctl [--dry-run|--verbose] COMMAND` arguments. """ utils.check_config() utils.confirm_mode() utils.setenv(const.SKIP_CONFIRM_KEY, value.lower())
def ports(http, https, mqtt): """Update used ports""" utils.check_config() utils.confirm_mode() cfg = { const.HTTP_PORT_KEY: http, const.HTTPS_PORT_KEY: https, const.MQTT_PORT_KEY: mqtt, } utils.info('Writing port settings to .env...') for key, val in cfg.items(): utils.setenv(key, val)
def test_setenv(mocked_ext, mocked_opts): set_mock = mocked_ext['set_key'] utils.setenv('key', 'val') set_mock.assert_called_with(path.abspath('.env'), 'key', 'val', quote_mode='never') utils.setenv('key', 'other', '.other-env') set_mock.assert_called_with('.other-env', 'key', 'other', quote_mode='never') mocked_opts.dry_run = True utils.setenv('key', 'val-dry') assert set_mock.call_count == 2
def install(ctx: click.Context, snapshot_file): """Install Brewblox and its dependencies. Brewblox can be installed multiple times on the same computer. Settings and databases are stored in a Brewblox directory. This command also installs system-wide dependencies. A reboot is required after installing docker, or adding the user to the 'docker' group. By default, `brewblox-ctl install` attempts to download packages using the apt package manager. If you are using a system without apt (eg. Synology NAS), this step will be skipped. You will need to manually install any missing libraries. When using the `--snapshot ARCHIVE` option, no dir is created. Instead, the directory in the snapshot is extracted. It will be renamed to the desired name of the Brewblox directory. \b Steps: - Ask confirmation for installation steps. - Install apt packages. - Install docker. - Add user to 'docker' group. - Fix host IPv6 settings. - Disable host-wide mDNS reflection. - Set variables in .env file. - If snapshot provided: - Load configuration from snapshot. - Else: - Check for port conflicts. - Create docker-compose configuration files. - Create datastore (Redis) directory. - Create history (Victoria) directory. - Create gateway (Traefik) directory. - Create SSL certificates. - Create eventbus (Mosquitto) directory. - Set version number in .env file. - Pull docker images. - Reboot if needed. """ utils.confirm_mode() user = utils.getenv('USER') opts = InstallOptions() opts.check_confirm_opts() opts.check_system_opts() opts.check_docker_opts() opts.check_reboot_opts() if not snapshot_file: opts.check_init_opts() # Install Apt packages if opts.apt_install: utils.info('Installing apt packages...') apt_deps = ' '.join(const.APT_DEPENDENCIES) sh([ 'sudo apt-get update', 'sudo apt-get upgrade -y', f'sudo apt-get install -y {apt_deps}', ]) else: utils.info('Skipped: apt-get install.') # Install docker if opts.docker_install: utils.info('Installing docker...') sh('curl -sL get.docker.com | sh', check=False) else: utils.info('Skipped: docker install.') # Add user to 'docker' group if opts.docker_group_add: utils.info(f"Adding {user} to 'docker' group...") sh('sudo usermod -aG docker $USER') else: utils.info(f"Skipped: adding {user} to 'docker' group.") # Always apply actions actions.disable_ssh_accept_env() actions.fix_ipv6(None, False) actions.edit_avahi_config() actions.add_particle_udev_rules() actions.uninstall_old_ctl_package() actions.deploy_ctl_wrapper() # Set variables in .env file # Set version number to 0.0.0 until snapshot load / init is done utils.info('Setting .env values...') utils.setenv(const.CFG_VERSION_KEY, '0.0.0') utils.setenv(const.SKIP_CONFIRM_KEY, str(opts.skip_confirm)) for key, default_val in const.ENV_DEFAULTS.items(): utils.setenv(key, utils.getenv(key, default_val)) # Install process splits here # Either load all config files from snapshot or run init sudo = utils.optsudo() if snapshot_file: ctx.invoke(snapshot.load, file=snapshot_file) else: release = utils.getenv('BREWBLOX_RELEASE') utils.info('Checking for port conflicts...') actions.check_ports() utils.info('Copying docker-compose.shared.yml...') sh(f'cp -f {const.CONFIG_DIR}/docker-compose.shared.yml ./') if opts.init_compose: utils.info('Copying docker-compose.yml...') sh(f'cp -f {const.CONFIG_DIR}/docker-compose.yml ./') # Stop after we're sure we have a compose file utils.info('Stopping services...') sh(f'{sudo}docker-compose down') if opts.init_datastore: utils.info('Creating datastore directory...') sh('sudo rm -rf ./redis/; mkdir ./redis/') if opts.init_history: utils.info('Creating history directory...') sh('sudo rm -rf ./victoria/; mkdir ./victoria/') if opts.init_gateway: utils.info('Creating gateway directory...') sh('sudo rm -rf ./traefik/; mkdir ./traefik/') utils.info('Creating SSL certificate...') actions.makecert('./traefik', release) if opts.init_eventbus: utils.info('Creating mosquitto config directory...') sh('sudo rm -rf ./mosquitto/; mkdir ./mosquitto/') # Always copy cert config to traefik dir sh(f'cp -f {const.CONFIG_DIR}/traefik-cert.yaml ./traefik/') # Init done - now set CFG version utils.setenv(const.CFG_VERSION_KEY, const.CURRENT_VERSION) if opts.docker_pull: utils.info('Pulling docker images...') sh(f'{sudo}docker-compose pull') utils.info('All done!') # Reboot if opts.reboot_needed: if opts.prompt_reboot: utils.info('Press ENTER to reboot.') input() else: utils.info('Rebooting in 10 seconds...') sleep(10) sh('sudo reboot')
def check_env_vars(): utils.info('Checking .env variables...') for (key, default_value) in const.ENV_DEFAULTS.items(): current_value = utils.getenv(key) if current_value is None: utils.setenv(key, default_value)
def update(update_ctl, update_ctl_done, pull, update_system, migrate, prune, from_version): """Download and apply updates. This is the one-stop-shop for updating your Brewblox install. You can use any of the options to fine-tune the update by enabling or disabling subroutines. By default, all options are enabled. --update-ctl/--no-update-ctl: Whether to download and install new versions of of brewblox-ctl. If this flag is set, update will download the new version and then restart itself. This way, the migrate is done with the latest version of brewblox-ctl. If you're using dry run mode, you'll notice the hidden option --update-ctl-done. You can use it to watch the rest of the update: it\'s a flag to avoid endless loops. --pull/--no-pull. Whether to pull docker images. This is useful if any of your services is using a local image (not from Docker Hub). --update-system/--no-update-system determines whether --migrate/--no-migrate. Updates regularly require changes to configuration. Required changes are applied here. --prune/--no-prune. Updates to docker images can leave unused old versions on your system. These can be pruned to free up disk space. This includes all images and volumes on your system, and not just those created by Brewblox. \b Steps: - Check whether any system fixes must be applied. - Update brewblox-ctl. - Stop services. - Update Avahi config. - Update system packages. - Migrate configuration files. - Pull Docker images. - Prune unused Docker images and volumes. - Start services. - Migrate service configuration. - Write version number to .env file. """ utils.check_config() utils.confirm_mode() sudo = utils.optsudo() prev_version = StrictVersion(from_version) check_version(prev_version) if update_ctl and not update_ctl_done: utils.info('Updating brewblox-ctl...') utils.pip_install('pip') actions.install_ctl_package() # Restart update - we just replaced the source code sh(' '.join( ['python3 -m brewblox_ctl', *const.ARGS[1:], '--update-ctl-done'])) return if update_ctl: actions.uninstall_old_ctl_package() actions.deploy_ctl_wrapper() utils.info('Stopping services...') sh(f'{sudo}docker-compose down') if update_system: actions.update_system_packages() if migrate: downed_migrate(prev_version) if pull: utils.info('Pulling docker images...') sh(f'{sudo}docker-compose pull') if prune: utils.info('Pruning unused images...') sh(f'{sudo}docker image prune -f > /dev/null') utils.info('Pruning unused volumes...') sh(f'{sudo}docker volume prune -f > /dev/null') utils.info('Starting services...') sh(f'{sudo}docker-compose up -d') if migrate: upped_migrate(prev_version) utils.info( f'Configuration version: {prev_version} -> {const.CURRENT_VERSION}' ) utils.setenv(const.CFG_VERSION_KEY, const.CURRENT_VERSION)
def install(use_defaults, apt_install, docker_install, docker_user, no_reboot, dir, release): """Create Brewblox directory; install system dependencies; reboot. Brewblox can be installed multiple times on the same computer. Settings and databases are stored in a Brewblox directory (default: ./brewblox). This command also installs system-wide dependencies (docker). After `brewblox-ctl install`, run `brewblox-ctl setup` in the created Brewblox directory. A reboot is required after installing docker, or adding the user to the 'docker' group. By default, `brewblox-ctl install` attempts to download packages using the apt package manager. If you are using a system without apt (eg. Synology NAS), this step will be skipped. You will need to manually install any missing libraries. \b Steps: - Install apt packages. - Install docker. - Add user to 'docker' group. - Create Brewblox directory (default ./brewblox). - Set variables in .env file. - Reboot. """ utils.confirm_mode() apt_deps = 'curl net-tools libssl-dev libffi-dev' user = utils.getenv('USER') default_dir = path.abspath('./brewblox') prompt_reboot = True if use_defaults is None: use_defaults = utils.confirm('Do you want to install with default settings?') # Check if packages should be installed if not utils.command_exists('apt'): utils.info('Apt is not available. You may need to find another way to install dependencies.') utils.info('Apt packages: "{}"'.format(apt_deps)) apt_install = False if apt_install is None: if use_defaults: apt_install = True else: apt_install = utils.confirm('Do you want to install apt packages "{}"?'.format(apt_deps)) # Check if docker should be installed if utils.command_exists('docker'): utils.info('Docker is already installed.') docker_install = False if docker_install is None: if use_defaults: docker_install = True else: docker_install = utils.confirm('Do you want to install docker?') # Check if user should be added to docker group if utils.is_docker_user(): utils.info('{} already belongs to the docker group.'.format(user)) docker_user = False if docker_user is None: if use_defaults: docker_user = True else: docker_user = utils.confirm('Do you want to run docker commands without sudo?') # Check used directory if dir is None: if use_defaults or utils.confirm("The default directory is '{}'. Do you want to continue?".format(default_dir)): dir = default_dir else: return if utils.path_exists(dir): if not utils.confirm('{} already exists. Do you want to continue?'.format(dir)): return if not no_reboot: prompt_reboot = utils.confirm('A reboot is required after installation. ' + 'Do you want to be prompted before that happens?') # Install Apt packages if apt_install: utils.info('Installing apt packages...') sh([ 'sudo apt update', 'sudo apt upgrade -y', 'sudo apt install -y {}'.format(apt_deps), ]) else: utils.info('Skipped: apt install.') # Install docker if docker_install: utils.info('Installing docker...') sh('curl -sL get.docker.com | sh') else: utils.info('Skipped: docker install.') # Add user to 'docker' group if docker_user: utils.info("Adding {} to 'docker' group...".format(user)) sh('sudo usermod -aG docker $USER') else: utils.info("Skipped: adding {} to 'docker' group.".format(user)) # Create install directory utils.info('Creating Brewblox directory ({})...'.format(dir)) sh('mkdir -p {}'.format(dir)) # Set variables in .env file utils.info('Setting variables in .env file...') dotenv_path = path.abspath('{}/.env'.format(dir)) sh('touch {}'.format(dotenv_path)) utils.setenv(const.RELEASE_KEY, release, dotenv_path) utils.setenv(const.CFG_VERSION_KEY, '0.0.0', dotenv_path) utils.setenv(const.SKIP_CONFIRM_KEY, str(use_defaults), dotenv_path) utils.info('Done!') # Reboot if not no_reboot: if prompt_reboot: utils.info('Press ENTER to reboot.') input() else: utils.info('Rebooting in 10 seconds...') sleep(10) sh('sudo reboot') else: utils.info('Skipped: reboot.')