From 4d0b4c417fd1c7364e11218e21c998beebd6f781 Mon Sep 17 00:00:00 2001 From: Michael Harp Date: Sat, 3 Oct 2026 10:40:28 -0400 Subject: [PATCH] Add an OpenVox 8 to 9 upgrade guide to the 9.x docs Add "Upgrading from OpenVox 8 to OpenVox 9", an upgrade-planning page that sits beside the package-mechanics page (upgrade_minor) and covers what to review and change before moving a production deployment. Every claim was checked against the OpenVox 9 sources, the published packages and repository metadata, and where possible a lab run, rather than copied from Puppet Core's guide. The page covers: - The component bump (Ruby 4.0, OpenSSL 3.5, OpenFact 6, JRuby 10.1, Java 21 or 25, resource_api 2.0, curl no longer bundled) and the new major versions of the vendored *_core modules. - Platform coverage at GA and the six-month support window for 8. - Ruby 4.0 review for facts, functions, types, providers, and gems, including Kernel#open pipes, Net::HTTP's dropped default Content-Type, and the libraries no longer shipped (base64, multi_json, cgi, gettext). - Reinstalling gems after the gem directory moves to 4.0.0. - OpenFact 6 changes that affect fact code. - Behavior changes: deferred functions preprocessed by default, reports off by default, no default server (with both error texts), checksum- like file content now literal, the removed settings and other removals, and the systemd provider's invoke-rc.d fallback. - Server and OpenVoxDB: Java packaging and the launcher, filebucket read authorization and the kept auth.conf caveat, Jetty 12 and the bootstrap.cfg pitfall, PostgreSQL versions, package dependencies, and the removed start/stop subcommands. - Windows agents defaulting to UTF-8 and the MSYS2 build. - A test-then-upgrade checklist with the openvox9-release repository switch and the rule that every OpenVox package on a server host upgrades in one transaction. Also add the nav entry, cross-link the page from upgrade_minor, and add the one-transaction caveat to its recommended order. Co-Authored-By: Claude Fable 5.1 Signed-off-by: Michael Harp --- _data/nav/openvox_9x.yml | 2 + docs/_openvox_9x/upgrade_major.md | 210 ++++++++++++++++++++++++++++++ docs/_openvox_9x/upgrade_minor.md | 16 ++- 3 files changed, 225 insertions(+), 3 deletions(-) create mode 100644 docs/_openvox_9x/upgrade_major.md diff --git a/_data/nav/openvox_9x.yml b/_data/nav/openvox_9x.yml index 544df48fa..9c6b69e40 100644 --- a/_data/nav/openvox_9x.yml +++ b/_data/nav/openvox_9x.yml @@ -37,6 +37,8 @@ link: "install_osx.html" - text: What gets installed and where (agent) link: "install_what_and_where.html" + - text: Upgrading from OpenVox 8 to 9 + link: "upgrade_major.html" - text: Upgrading OpenVox 9 link: "upgrade_minor.html" - text: Configuration diff --git a/docs/_openvox_9x/upgrade_major.md b/docs/_openvox_9x/upgrade_major.md new file mode 100644 index 000000000..b600fb6cb --- /dev/null +++ b/docs/_openvox_9x/upgrade_major.md @@ -0,0 +1,210 @@ +--- +layout: default +title: "Upgrading from OpenVox 8 to OpenVox 9" +--- + +OpenVox 9 is a major release, but it does not change the Puppet language. Its focus is updating the platform underneath OpenVox: newer Ruby, OpenSSL, JRuby, and Java, plus the removal of settings and behaviors deprecated in OpenVox 8. Most Puppet code that runs cleanly on OpenVox 8 runs unchanged on OpenVox 9, but review the component upgrades and removals below before upgrading a production deployment. + +This page covers what to check and change before the upgrade. For the package mechanics of the upgrade itself, see [Upgrading OpenVox 9](upgrade_minor.html). + +OpenVox 8 stays available in the `openvox8` repositories and receives high-priority and security fixes for at least six months after the 9.0.0 release, so the upgrade does not have to happen on release day. Check the [known issues](known_issues.html) page before you start. + +## What changes in OpenVox 9 + +The major version bump comes from the underlying components: + +| Component | OpenVox 8 | OpenVox 9 | +| --------- | --------- | --------- | +| Ruby (bundled with `openvox-agent`) | 3.2 | 4.0 | +| OpenSSL (bundled with `openvox-agent`) | 3.0 | 3.5 | +| OpenFact (bundled with `openvox-agent`) | 5.x | 6.x | +| JRuby (bundled with `openvox-server`) | 9.4 | 10.1 | +| Java (required by `openvox-server` and `openvoxdb`) | 17 or 21 | 21 or 25 | +| curl (bundled with `openvox-agent`) | 8.x | Not bundled | +| puppet-resource_api (bundled with `openvox-agent`) | 1.9 | 2.0 | + +See [Component versions in recent releases](component_versions.html) for the exact versions in each release. + +The vendored `*_core` modules (`augeas_core`, `cron_core`, `host_core`, `scheduled_task`, `selinux_core`, `sshkeys_core`, `yumrepo_core`, and `zfs_core`) also move to new major versions. If you pin any of them in a Puppetfile, check their changelogs: they were updated for the newer Ruby and may have dropped older Puppet versions. + +OpenSSL 3.5 adds the post-quantum algorithms ML-KEM, ML-DSA, and SLH-DSA. Agent-to-server TLS between OpenVox components is not affected, but if an external CA, a TLS-terminating proxy, or a hardware security module sits in the path, check that it accepts the cipher suites and key types the new OpenSSL negotiates. + +OpenVox 9 no longer ships its own curl. Scripts that call `/opt/puppetlabs/puppet/bin/curl`, or that put `/opt/puppetlabs/puppet/bin` ahead of the system directories in `PATH` to pick it up, need the system `curl` instead. + +## Before you upgrade + +1. Upgrade your deployment to the latest OpenVox 8 release first and resolve any deprecation warnings in agent and server logs. Most of what OpenVox 9 removes was already deprecated in the 8.x series. +2. Read the release notes for each component: [OpenVox 9](release_notes.html), [OpenVox Server 9](/openvox-server/9.x/release_notes.html), [OpenVoxDB 9](/openvoxdb/9.x/release_notes.html), and [OpenFact 6](/openfact/6.x/release_notes.html). Each links to the GitHub release page with the full list of changes. +3. Check that packages exist for your platforms on the [supported platforms](supported_platforms.html) page. Compared with OpenVox 8, no OpenVox 9 packages are built for Enterprise Linux 7, Amazon Linux 2, Fedora 42, Debian 11, or Ubuntu 25.04, and Debian 12 gets `openvox-agent` only, because OpenVox Server 9 and OpenVoxDB 9 need Java 21 or 25 and Debian 12 provides neither. +4. Back up `/etc/puppetlabs/` on your servers and take a database backup of OpenVoxDB with `pg_dump` before starting. + +## Review Ruby code for Ruby 4.0 + +The agent's bundled Ruby moves from 3.2 to 4.0. Everything that runs inside the agent's Ruby must be compatible with Ruby 4.0: + +- Custom facts, functions, types, and providers in your modules +- Gems you install into the agent with `puppet_gem` or `/opt/puppetlabs/puppet/bin/gem` + +Ruby 4.0 removes APIs that were deprecated during the Ruby 3.x series. A common one in older facts and providers is spawning a subprocess with `Kernel#open` and a pipe argument (`open("|command")`), which no longer works; use `IO.popen` or, better, OpenFact's `Facter::Core::Execution.execute` for fact code. Run your module unit tests on Ruby 4.0 to find problems before the upgrade. + +OpenVox Server 9 bundles JRuby 10.1, which targets the same Ruby 4.0 language level. Ruby code that runs on the server, such as report processors, custom indirector termini, and gems installed with `puppetserver gem`, needs the same review. + +Ruby 4.0's `Net::HTTP` no longer adds a default `Content-Type: application/x-www-form-urlencoded` header to requests that have a body. Code that sends a `POST` or `PUT` with `Net::HTTP` and relied on that default, such as a custom report processor that posts to a web service, now sends no `Content-Type` header. Set the header explicitly. + +Three libraries that OpenVox 8 agents provided are gone: the agent package no longer vendors the `base64` and `multi_json` gems, and Ruby 4.0 ships only `cgi/escape` in place of the full `cgi` library. +Check that a fact or provider which requires one of them still loads on an OpenVox 9 agent, for example with `/opt/puppetlabs/puppet/bin/ruby -e 'require "multi_json"'`, and install the gem if it does not. On the server, the `gettext` gem is no longer vendored with the JRuby gems. + +If you install OpenVox as a gem rather than from packages, the `openvox` gem now requires at least Ruby 3.2. + +## Reinstall gems added to the agent's Ruby + +Gems are installed into a directory named after the Ruby minor version, so gems you added to the agent's Ruby on OpenVox 8 live under `/opt/puppetlabs/puppet/lib/ruby/gems/3.2.0/`. OpenVox 9's Ruby 4.0 looks in `/opt/puppetlabs/puppet/lib/ruby/gems/4.0.0/` and does not see them. +The upgrade neither migrates nor removes the old directory, and the gems' command wrappers in `/opt/puppetlabs/puppet/bin/` stay behind, so a tool such as `r10k` installed with `/opt/puppetlabs/puppet/bin/gem install` still appears to exist but fails: + +```console +$ /opt/puppetlabs/puppet/bin/r10k version +.../rubygems.rb:265:in 'Gem.find_spec_for_exe': + can't find gem r10k (>= 0.a) with executable r10k (Gem::GemNotFoundException) +``` + +Before upgrading, list what you added so you can put it back afterwards: + +```console +/opt/puppetlabs/puppet/bin/gem list --local +ls /opt/puppetlabs/puppet/lib/ruby/gems/*/gems +``` + +After upgrading, reinstall each gem with `sudo /opt/puppetlabs/puppet/bin/gem install `. Gems that Puppet manages with the `puppet_gem` package provider are reinstalled by the first agent run on OpenVox 9, because the provider no longer finds them; gems installed by hand are not. Once nothing depends on it, the old `3.2.0` gem directory can be deleted. + +Gems installed into OpenVox Server with `puppetserver gem` are not affected: their directory, `/opt/puppetlabs/server/data/puppetserver/jruby-gems`, is not tied to a Ruby version, so they carry over the JRuby 9.4 to 10.1 upgrade. Review them for Ruby 4.0 compatibility as described above. + +## Review custom facts for OpenFact 6 + +`openvox-agent` 9 bundles and requires OpenFact 6, a major version bump from the 5.x series bundled with OpenVox 8. Test your custom and external facts against OpenFact 6. The changes most likely to affect fact code: + +- OpenFact 6 requires Ruby 3.0 or later, which the agent's Ruby 4.0 satisfies; fact code that also runs elsewhere needs the same floor. +- `Facter::Core::Execution.exec`, `Facter::Util::Resolution.exec`, and `Facter::Util::Resolution.which` now log a deprecation warning ahead of removal. Use `Facter::Core::Execution.execute` and `Facter::Core::Execution.which`. +- The `time_limit` and `limit` option keys for `execute` are deprecated aliases; use `timeout`. +- The deprecated `ldapname` fact option is removed. A resolution that still passes it does not raise: OpenFact logs `Unable to add resolve nil for fact '': Invalid resolution options [:ldapname]` at `ERROR` level and the fact resolves to nothing, so check the log for facts that have gone missing rather than waiting for a failure. +- When a fact calls a bare command name, OpenFact now also searches `/opt/puppetlabs/bin`, so facts that call `puppet`, `puppetserver`, or `puppetdb` resolve when the agent runs as a service. + +See the [OpenFact 6 release notes](/openfact/6.x/release_notes.html) for the full list. + +## Deferred functions are preprocessed again by default + +OpenVox 8 evaluated deferred functions (functions called through the `Deferred` data type) lazily, at the moment the catalog applied the resource that used them. OpenVox 9 returns to the older behavior of resolving all deferred functions up front, before catalog application starts. + +This matters if a deferred function depends on something the same run puts in place, for example a package or library installed earlier in the catalog. Under preprocessing, the function runs before that prerequisite exists and the run fails. If you depend on lazy evaluation, set `preprocess_deferred = false` in the agent's `puppet.conf` to keep the OpenVox 8 behavior. + +## Report storage is now opt-in + +The default for the [`reports` setting](configuration.html#reports) changed from `store` to `none`, so an upgraded server stops writing YAML report files to the reports directory. If you rely on stored reports, or on tooling that reads them, set the value explicitly on the server: + +```ini +[server] +reports = store +``` + +Report submission from agents is unchanged; only the server-side default for processing them changed. + +## Agents no longer default to a server named `puppet` + +OpenVox 8 agents fell back to contacting a host named `puppet` when no server was configured. OpenVox 9 removes this fallback. An agent with nothing that tells it which server to contact fails, whether it runs as root or not: + +```text +Error: OpenVox does not default to `server=puppet` as of version 9.0. Please update your configuration appropriately by providing a specific server of your choice. + +You can update the server setting in puppet.conf by running a command similar to: + puppet config --section main set server YOUR_SERVER_NAME +``` + +A non-root run fails the same way but with different text: a warning that OpenVox no longer defaults to `server=puppet` when running as a non-privileged user, followed by: + +```text +Error: Neither `server` nor `ca_server` is specified. +``` + +Any of these satisfies the check: + +- The [`server` setting](configuration.html#server). +- The [`server_list` setting](configuration.html#server_list). +- DNS SRV records, enabled with `use_srv_records`. +- `ca_server` and `report_server`, each for its own service only. Catalog and file requests still need one of the first three. + +If any nodes still rely on the fallback, set `server` before upgrading them: + +```console +puppet config set server openvox.example.com --section main +``` + +## Removed settings + +These settings are gone in OpenVox 9. Remove them from `puppet.conf` and from any scripts or tooling that reference them before you upgrade. OpenVox 8 warned about each of them; OpenVox 9 ignores a leftover setting without any message, so nothing after the upgrade tells you it is still there. + +- `configprint`: use `puppet config print ` instead of `puppet agent --configprint `. +- `pluginsync`: plugins always sync; the setting had been deprecated since Puppet 6. +- `data_binding_terminus` and `environment_data_provider`: the classic `hiera` indirector and pluggable data bindings are removed. Automatic class parameter lookup always uses the modern lookup system. + A version 3 `hiera.yaml` named by `hiera_config` still loads on OpenVox 9, with a deprecation warning that it should be converted to version 5, so that migration does not have to happen before the upgrade. + When you do it, see [Migrating your Hiera configuration](hiera_migrate.html); Hiera 3 backends keep working through a version 5 `hiera.yaml`. + +## Other removals + +- A `file` resource whose `content` value looks like a checksum, such as `{sha256}` followed by a digest, now writes that value to the file as text. OpenVox 8 logged a deprecation warning and tried to fetch the matching content from the filebucket. If you still depend on that, use the `source` attribute or [static catalogs](static_catalogs.html) instead. +- The `regsubst` function no longer accepts its deprecated encoding argument. +- On Debian and Ubuntu, the `systemd` service provider no longer falls back to `invoke-rc.d` and init script inspection to decide whether a service is enabled; it relies on `systemctl` alone. Check `service` resources for daemons that ship only a SysV init script. +- The `pe_serverversion` fact is removed. +- The `zone_core` module for Solaris zones is no longer vendored with the agent. Install [`puppetlabs-zone_core`](https://forge.puppet.com/modules/puppetlabs/zone_core) from the Forge if you manage `zone` resources. +- The agent runtime no longer ships Java keystore files, and legacy PAL script-evaluation APIs are removed. See the [release notes](release_notes.html) if you depend on either. + +## Server and OpenVoxDB changes + +- **Java:** OpenVox Server 9 and OpenVoxDB 9 drop support for Java 17 and depend on a Java 25 or Java 21 runtime package, so a normal package upgrade installs one. Java 25 is used wherever the platform provides it; Enterprise Linux 8 provides Java 21 only, and the FIPS packages run on Java 21 only. + A service that does start under Java 17, for example through a `JAVA_BIN` override, crashes early with `ClassNotFoundException: java.util.SequencedCollection`. +- **Filebucket reads need an administrative certificate:** the default `auth.conf` in OpenVox Server 9 lets agents store filebucket content (`HEAD` and `PUT`) but restricts reading it back (`GET` and `POST`) to client certificates with the `pp_cli_auth: "true"` extension. + If you restore or diff filebucket content remotely with an ordinary agent certificate, add an `auth.conf` rule for that certname as part of the upgrade. See [auth.conf](/openvox-server/9.x/config_file_auth.html). +- **A modified `auth.conf` keeps its OpenVox 8 rules:** `/etc/puppetlabs/puppetserver/conf.d/auth.conf` is a package configuration file. If you have edited it, or a module manages it, the upgrade keeps your copy and puts the OpenVox Server 9 version beside it, as `auth.conf.rpmnew` on RPM-based platforms or `auth.conf.dpkg-dist` on Debian and Ubuntu. + The server keeps running with the rules in your copy: every agent certificate can still read filebucket content, as on OpenVox 8, and the rule that lets a `pp_cli_auth` certificate clear the environment cache (`DELETE /puppet-admin-api/v1/environment-cache`) is missing. Nothing fails and nothing is logged. + Before the upgrade, check whether the file is modified with `rpm -V openvox-server` or `dpkg -V openvox-server`. After the upgrade, compare your copy with the new file and carry the two rule changes over, together with any rule you add for the previous item. If a module manages the file, change the rules there, because the module rewrites the file on its next run. + To confirm that the restriction is in effect, run `puppet filebucket get ` on an ordinary agent. With the OpenVox Server 9 rules, the server answers `Error 403 on SERVER: Forbidden request`. +- **Jetty 12:** both OpenVox Server 9 and OpenVoxDB 9 move to Jetty 12. If you customized `webserver` settings beyond host and port, review them after the upgrade. + For OpenVoxDB, if you upgrade from 8.14.0 or earlier and have modified `/etc/puppetlabs/puppetdb/bootstrap.cfg`, the package manager keeps your copy and the service fails to start because it still loads `jetty10-service`; the [OpenVoxDB 9 release notes](/openvoxdb/9.x/release_notes.html) have the fix. +- **PostgreSQL:** OpenVoxDB 9 requires PostgreSQL 14 or later, the same minimum as the last 8.x releases, and is tested against PostgreSQL 15, 16, and 18. +- **Packaging:** the `openvox-server` 9 and `openvoxdb` 9 packages require `openvox-agent` 9 on the same host, so the agent on those hosts upgrades along with them. The `openvoxdb-termini` 9 package depends on `openvox-agent` without a version, so a host that has only the termini, such as a `puppet apply` node that writes to OpenVoxDB, keeps its OpenVox 8 agent until you upgrade it. +- **Service management:** OpenVox Server 9 removes the `puppetserver start` and `puppetserver stop` subcommands; systemd starts the service through a Java launcher that the package installs. Replace any scripts that call them with `systemctl start puppetserver` and `systemctl stop puppetserver`. `systemctl reload puppetserver` still works on every platform. + OpenVoxDB 9 packages use the same launcher. Both services run the first installed Java from the versions the package supports (25, then 21), and both the service and the CLI commands use that same Java. + `JAVA_BIN` in `/etc/default/puppetserver`, `/etc/default/puppetdb`, or their `/etc/sysconfig/` equivalents is honored when it points at one of those versions; a leftover `JAVA_BIN="/usr/bin/java"` from an earlier package counts as unset. `JAVA_ARGS` still applies on both services. + +## Windows agents default to UTF-8 + +The Ruby bundled with OpenVox 8 agents on Windows was patched to keep the system locale code page, such as Windows-1252, as Ruby's default external encoding. OpenVox 9 drops that patch, so its Ruby 4.0 behaves like any other Windows Ruby since 3.0 and defaults to UTF-8. +Most Windows systems already use UTF-8, in which case nothing changes. If you have configuration files, resource titles, external facts, or command output that contain non-ASCII characters encoded as Windows-1252 or ISO-8859-1, convert them to UTF-8 before upgrading those agents, because OpenVox 9 reads them as UTF-8. + +The Windows package is also built with MSYS2 and the UCRT toolchain, the same `x64-mingw-ucrt` platform as upstream Ruby, so precompiled gems such as `nokogiri` install on Windows nodes without build tools. The 9.x installers are in the `openvox9` directory at [downloads.voxpupuli.org](https://downloads.voxpupuli.org/windows/openvox9/). + +## Test, then upgrade + +1. Run your module unit tests on Ruby 4.0 and fix any failures. [Unit testing](/ecosystem/latest/devkit/unit_testing.html) in the DevKit guide covers the test setup. +2. In each module's `metadata.json`, raise the upper bound of the `openvox` entry under `requirements` so that it admits 9.x, for example `>= 8.19.0 < 10.0.0`. The Vox Pupuli test tooling builds its test matrix from this entry, so a module that still declares `< 9.0.0` is never tested on OpenVox 9. + + Do not widen a `puppet` entry to cover 9.x: that entry describes Puppet, whose last release with open packages was 8.10, and Vox Pupuli modules have [dropped it](https://github.com/voxpupuli/community-triage/issues/59). Remove it, or cap it at `<= 8.10.0` if a tool you use still needs it to exist. +3. Validate your manifests with `puppet parser validate`. +4. Stand up an OpenVox 9 server in a test environment, point test agents at it, and compare `puppet agent --test --noop` output against OpenVox 8 for unexpected changes. +5. Switch each host to the OpenVox 9 repository. The `openvox8-release` package configures only the 8.x repository, so a host still using it stays on 8.x no matter what you upgrade. Install the `openvox9-release` package for the platform from [apt.voxpupuli.org](https://apt.voxpupuli.org) or [yum.voxpupuli.org](https://yum.voxpupuli.org). + On Debian and Ubuntu, remove `openvox8-release` first: both packages ship `/etc/apt/preferences.d/openvox-release.pref`, and `dpkg` refuses to install the second one over it. + + ```bash + sudo apt remove openvox8-release + wget https://apt.voxpupuli.org/openvox9-release-ubuntu24.04.deb + sudo dpkg -i openvox9-release-ubuntu24.04.deb + sudo apt update + ``` + + On EL, the two release packages can be installed side by side and the package manager prefers the 9.x packages; remove `openvox8-release` once the host is upgraded. + + ```bash + sudo dnf install https://yum.voxpupuli.org/openvox9-release-el-9.noarch.rpm + ``` + + If you wrote the repository definition yourself, for example to use a mirror, change `openvox8` to `openvox9` in it instead. +6. Upgrade the server hosts first, then the agents. On each server host, upgrade every installed OpenVox package in one package manager transaction: the 8.x `openvox-server` and `openvoxdb` packages require `openvox-agent` below 9.0.0 and the 9.x packages require 9.0.0 or newer, so a host that runs both OpenVox Server and OpenVoxDB cannot upgrade one of them and keep the other on 8.x. + Where OpenVox Server and OpenVoxDB run on separate hosts, upgrade the server host before the database host. OpenVox 8 agents can keep checking in to an upgraded OpenVox 9 server while you roll out agent upgrades. [Upgrading OpenVox 9](upgrade_minor.html) has the package commands. diff --git a/docs/_openvox_9x/upgrade_minor.md b/docs/_openvox_9x/upgrade_minor.md index 905820ded..bb66a8032 100644 --- a/docs/_openvox_9x/upgrade_minor.md +++ b/docs/_openvox_9x/upgrade_minor.md @@ -7,8 +7,9 @@ Use this page for routine OpenVox 9 upgrades and for in-place migrations from th legacy Puppet packages to OpenVox packages. > **OpenVox 9 is a major version** and includes breaking changes relative to OpenVox -> 8 — see the [release notes](./release_notes.html) before upgrading a production -> host. The steps below cover the mechanics of the upgrade itself. +> 8 — see [Upgrading from OpenVox 8 to 9](upgrade_major.html) and the +> [release notes](./release_notes.html) before upgrading a production host. The +> steps below cover the mechanics of the upgrade itself. The main migration rule is that a host cannot have both Puppet and OpenVox packages installed at the same time. Back up `/etc/puppetlabs/` before you start. @@ -22,10 +23,19 @@ Upgrade in this order: 3. `openvoxdb-termini` on server nodes 4. `openvox-agent` on managed nodes -This keeps the central services ahead of the agents they serve. +This keeps the central services ahead of the agents they serve. On a host that runs +more than one of these, upgrade all of its OpenVox packages in one package manager +transaction, as the commands below do. When upgrading from OpenVox 8 this is required: +the 8.x server and database packages require `openvox-agent` below 9.0.0 while the 9.x +packages require 9.0.0 or newer, so they cannot be upgraded one at a time. ## Upgrading Linux packages +If the host still has the `openvox8-release` package, it is subscribed to the 8.x +repository and the commands below leave it on 8.x. Install `openvox9-release` first; +the [upgrade guide](upgrade_major.html#test-then-upgrade) has the commands for each +platform. + On apt-based systems: ```bash