From 80c26244546fab4133ad4e0f564b637afe403ff6 Mon Sep 17 00:00:00 2001 From: Slavi Pantaleev Date: Sun, 12 Jul 2026 16:08:09 +0300 Subject: [PATCH] Add add-inventory-host command for bootstrapping the inventory Running `just add-inventory-host example.com 1.2.3.4` (or `make add-inventory-host domain=example.com ip=1.2.3.4`) adds a new host to the inventory, creating inventory/hosts and inventory/host_vars/matrix.DOMAIN/vars.yml from the example files, with strong secrets generated automatically. Existing configuration is never overwritten. The command refuses to run if the host is already in the inventory, so it can also be used for adding more hosts later. Based on the idea proposed in https://github.com/spantaleev/matrix-docker-ansible-deploy/pull/4682 by @Ser5 Co-Authored-By: Claude Fable 5 --- Makefile | 5 +- bin/add-inventory-host.sh | 120 ++++++++++++++++++++++++++++++++++++++ docs/quick-start.md | 18 +++++- justfile | 4 ++ 4 files changed, 145 insertions(+), 2 deletions(-) create mode 100755 bin/add-inventory-host.sh diff --git a/Makefile b/Makefile index 9ac77bb07..137e17062 100644 --- a/Makefile +++ b/Makefile @@ -2,11 +2,14 @@ # # SPDX-License-Identifier: AGPL-3.0-or-later -.PHONY: roles lint +.PHONY: roles lint add-inventory-host help: ## Show this help. @grep -F -h "##" $(MAKEFILE_LIST) | grep -v grep | sed -e 's/\\$$//' | sed -e 's/##//' +add-inventory-host: ## Adds a new host to the inventory, creating the inventory files if necessary (e.g. `make add-inventory-host domain=example.com ip=1.2.3.4`) + @./bin/add-inventory-host.sh "$(domain)" "$(ip)" + roles: ## Pull roles rm -rf roles/galaxy ansible-galaxy install -r requirements.yml -p roles/galaxy/ --force diff --git a/bin/add-inventory-host.sh b/bin/add-inventory-host.sh new file mode 100755 index 000000000..9c1bedd2d --- /dev/null +++ b/bin/add-inventory-host.sh @@ -0,0 +1,120 @@ +#!/usr/bin/env bash + +# SPDX-FileCopyrightText: 2026 MDAD project contributors +# SPDX-FileCopyrightText: 2026 Slavi Pantaleev +# +# SPDX-License-Identifier: AGPL-3.0-or-later + +# Adds a new host to the inventory, based on the example files in `examples/`: +# - creates `inventory/hosts` (or adds the host to it, if it already exists) +# - creates `inventory/host_vars/matrix.DOMAIN/vars.yml` with strong secrets generated automatically +# +# Existing configuration for the same host is never overwritten - the script refuses to run instead. +# +# Usage: bin/add-inventory-host.sh +# +# - is the base domain (`example.com`), not the Matrix server hostname (`matrix.example.com`) +# - is the server's external IP address or domain name + +set -euo pipefail + +base_path="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" + +if [ $# -ne 2 ]; then + echo "Usage: $0 " >&2 + echo "Example: $0 example.com 1.2.3.4" >&2 + exit 1 +fi + +domain="$1" +server_address="$2" + +if ! printf '%s' "${domain}" | grep -Eq '^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?)+$'; then + echo "Error: '${domain}' does not look like a valid domain name" >&2 + exit 1 +fi + +if ! printf '%s' "${server_address}" | grep -Eq '^[A-Za-z0-9.:_-]+$'; then + echo "Error: '${server_address}' does not look like a valid server address (IP address or domain name)" >&2 + exit 1 +fi + +case "${domain}" in + matrix.*) + echo "Warning: you likely need to pass your base domain (example.com), not the Matrix server hostname (matrix.example.com)." >&2 + echo "Proceeding anyway. Your Matrix server hostname will be: matrix.${domain}" >&2 + ;; +esac + +matrix_hostname="matrix.${domain}" +hosts_file="${base_path}/inventory/hosts" +vars_dir="${base_path}/inventory/host_vars/${matrix_hostname}" +vars_file="${vars_dir}/vars.yml" +hosts_entry="${matrix_hostname} ansible_host=${server_address} ansible_ssh_user=root" + +if [ -e "${vars_dir}" ]; then + echo "Error: ${vars_dir} already exists. Refusing to overwrite it." >&2 + exit 1 +fi + +if [ -f "${hosts_file}" ]; then + if ! grep -q '^\[matrix_servers\]' "${hosts_file}"; then + echo "Error: ${hosts_file} exists, but does not contain a [matrix_servers] section." >&2 + echo "Unrecognized inventory format. Add the host to it manually:" >&2 + echo "${hosts_entry}" >&2 + exit 1 + fi + + matrix_hostname_pattern="$(printf '%s' "${matrix_hostname}" | sed 's|\.|\\.|g')" + if grep -Eq "^${matrix_hostname_pattern}([[:space:]]|$)" "${hosts_file}"; then + echo "Error: ${hosts_file} already contains an entry for ${matrix_hostname}. Refusing to modify it." >&2 + exit 1 + fi +fi + +generate_secret() { + if command -v pwgen >/dev/null 2>&1; then + pwgen -s 64 1 + elif command -v openssl >/dev/null 2>&1; then + openssl rand -base64 192 | LC_ALL=C tr -dc 'A-Za-z0-9' | head -c 64 + else + head -c 4096 /dev/urandom | LC_ALL=C tr -dc 'A-Za-z0-9' | head -c 64 + fi +} + +generic_secret_key="$(generate_secret)" +postgres_password="$(generate_secret)" + +for secret in "${generic_secret_key}" "${postgres_password}"; do + if [ "${#secret}" -lt 64 ]; then + echo "Error: failed to generate a secret" >&2 + exit 1 + fi +done + +mkdir -p "${vars_dir}" + +sed \ + -e "s|^matrix_domain:.*|matrix_domain: ${domain}|" \ + -e "s|^matrix_homeserver_generic_secret_key:.*|matrix_homeserver_generic_secret_key: '${generic_secret_key}'|" \ + -e "s|^postgres_connection_password:.*|postgres_connection_password: '${postgres_password}'|" \ + "${base_path}/examples/vars.yml" > "${vars_file}" + +if [ -f "${hosts_file}" ]; then + # Insert the new host right after the [matrix_servers] section header. + hosts_file_tmp="$(mktemp "${hosts_file}.XXXXXX")" + awk -v entry="${hosts_entry}" '{print} $0 ~ /^\[matrix_servers\]/ && !done {print entry; done=1}' \ + "${hosts_file}" > "${hosts_file_tmp}" + mv "${hosts_file_tmp}" "${hosts_file}" +else + sed \ + -e "s|^matrix\.example\.com .*|${hosts_entry}|" \ + "${base_path}/examples/hosts" > "${hosts_file}" +fi + +echo "Added host ${matrix_hostname} to the inventory:" +echo "- ${hosts_file}" +echo "- ${vars_file}" +echo "" +echo "Secrets were generated automatically for matrix_homeserver_generic_secret_key and postgres_connection_password." +echo "Review and adjust these files before installing." diff --git a/docs/quick-start.md b/docs/quick-start.md index e895134f3..8ad864c81 100644 --- a/docs/quick-start.md +++ b/docs/quick-start.md @@ -89,7 +89,23 @@ To install Matrix services with this playbook, you would at least need 2 configu For your convenience, we have prepared example files of them ([`vars.yml`](../examples/vars.yml) and [`hosts`](../examples/hosts)). -To start quickly based on these example files, go into the `matrix-docker-ansible-deploy` directory and follow the instructions below: +To start quickly based on these example files, go into the `matrix-docker-ansible-deploy` directory and initialize your configuration, either automatically or manually. + +To initialize it automatically (with the base domain and server address pre-filled and secrets generated for you), run: + +- either: `just add-inventory-host example.com 1.2.3.4` (if you have the [`just`](just.md) tool) +- or: `make add-inventory-host domain=example.com ip=1.2.3.4` (if you have the `make` program) + +… where `example.com` is your "base domain" (not `matrix.example.com`) and `1.2.3.4` is your server's external IP address (or domain name). + +Given a base domain of `example.com`, this creates: + +- an entry for `matrix.example.com` (the Matrix server's default hostname: `matrix.` + your base domain) in the inventory hosts file (`inventory/hosts`) +- a configuration file (`inventory/host_vars/matrix.example.com/vars.yml`) containing `matrix_domain: example.com` and automatically generated secrets + +Afterward, edit these 2 files to adjust them further, as necessary. Existing configuration is never overwritten (the command refuses to run if the host is already in your inventory), so it can also be used for adding more hosts later. + +To initialize it manually, follow the instructions below: 1. Create a directory to hold your configuration: `mkdir -p inventory/host_vars/matrix.example.com` where `example.com` is your "base domain" 2. Copy the sample configuration file: `cp examples/vars.yml inventory/host_vars/matrix.example.com/vars.yml` diff --git a/justfile b/justfile index b92a8a52f..022159e58 100644 --- a/justfile +++ b/justfile @@ -13,6 +13,10 @@ prek_home := env("PREK_HOME", justfile_directory() / "var/prek") default: @{{ just_executable() }} --list --justfile "{{ justfile() }}" +# Adds a new host to the inventory, creating the inventory files if necessary (e.g. `just add-inventory-host example.com 1.2.3.4`) +add-inventory-host domain server_address: + @{{ justfile_directory() }}/bin/add-inventory-host.sh {{ quote(domain) }} {{ quote(server_address) }} + # Pulls external Ansible roles roles: #!/usr/bin/env sh