Files
matrix-docker-ansible-deploy/justfile
T
Slavi PantaleevandClaude Opus 5.5 bcb862c7ff Install roles without throwing away local changes
agru replaces an installed role whenever its version differs from the
pinned one, and also when the role has no install information. It
replaces a symlinked role with a fresh copy. ansible-galaxy was run on
an emptied roles/galaxy every time. Roles being worked on in
roles/galaxy could thus silently lose their changes.

bin/install-roles.py (run through bin/ansible-python.sh, so PyYAML
needs no separate installation) now decides which roles need to be
(re)installed and only passes those to agru or ansible-galaxy:

- missing roles are installed, and outdated roles are replaced
- outdated roles which were changed since they were installed are kept,
  with a warning. Changes are detected by comparing the role's contents
  with a checksum recorded at installation, in run/installed-roles.yml.
  Roles installed before (without a checksum) are considered unchanged.
- outdated roles which were not installed by agru or ansible-galaxy
  are kept, with a warning
- symlinked roles and roles containing a `.git` directory are never
  touched

`just roles --force` replaces changed roles too. `just update -u` now
only updates the pinned versions with agru and leaves installing the
roles to the script, so that it keeps changed roles as well.

The same script in mash-playbook (where its tests live, and where it
also deletes the roles of services no longer in use):
https://github.com/mother-of-all-self-hosting/mash-playbook/commit/d5c5e0ce
https://github.com/mother-of-all-self-hosting/mash-playbook/commit/608a7943

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-25 09:41:34 +02:00

171 lines
8.0 KiB
Makefile

# SPDX-FileCopyrightText: 2023 - 2024 Nikita Chernyi
# SPDX-FileCopyrightText: 2023 - 2024 Slavi Pantaleev
# SPDX-FileCopyrightText: 2024 Suguru Hirahara
#
# SPDX-License-Identifier: AGPL-3.0-or-later
# Set inventory from $ANSIBLE_INVENTORY if set, otherwise set the default
inventory := env("ANSIBLE_INVENTORY", "inventory/hosts")
# mise (dev tool version manager)
mise_data_dir := env("MISE_DATA_DIR", justfile_directory() / "var/mise")
mise_trusted_config_paths := justfile_directory() / "mise.toml"
prek_home := env("PREK_HOME", justfile_directory() / "var/prek")
# Versions and checksums of the roles installed into roles/galaxy, for detecting local changes to them
installed_roles_file_path := justfile_directory() / "run/installed-roles.yml"
# Shows help
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 (--force also replaces roles with local changes)
roles *flags:
@echo "[NOTE] This command just updates the roles, but if you want to update everything at once (playbook, roles, etc.) - use 'just update'"
@{{ just_executable() }} --justfile "{{ justfile() }}" _install-roles {{ flags }}
# Updates the playbook and installs the necessary Ansible roles pinned in requirements.yml. If a -u flag is passed, also updates the requirements.yml file with new role versions (if available)
update *flags:
#!/usr/bin/env sh
set -eu
if [ -n "{{ flags }}" ] && [ "{{ flags }}" != "-u" ]; then
echo "Unknown flags passed: {{ flags }}" >&2
exit 1
fi
if [ "{{ flags }}" = "-u" ] && ! [ -x "$(command -v agru)" ]; then
echo "Updating role versions (-u) requires the agru tool: https://github.com/etkecc/agru#where-to-get" >&2
exit 1
fi
{{ just_executable() }} --justfile "{{ justfile() }}" update-playbook-only
if [ "{{ flags }}" = "-u" ]; then
# Only updates the versions pinned in requirements.yml. The roles are installed below.
echo "Pinning new role versions in requirements.yml…"
agru -p roles/galaxy/ -no-tui -i=false -u
fi
{{ just_executable() }} --justfile "{{ justfile() }}" _install-roles
# Updates the playbook without installing/updating Ansible roles
update-playbook-only:
@echo "Updating playbook…"
@git stash -q
@git pull -q
@-git stash pop -q
# Invokes mise with the project-local data directory
mise *args: _ensure_mise_data_directory
#!/bin/sh
export MISE_DATA_DIR="{{ mise_data_dir }}"
export MISE_TRUSTED_CONFIG_PATHS="{{ mise_trusted_config_paths }}"
export MISE_YES=1
export PREK_HOME="{{ prek_home }}"
mise {{ args }}
# Runs prek (pre-commit hooks manager) with the given arguments
prek *args: _ensure_mise_tools_installed
@{{ just_executable() }} --justfile "{{ justfile() }}" mise exec -- prek {{ args }}
# Runs pre-commit hooks on staged files
prek-run-on-staged *args: _ensure_mise_tools_installed
@{{ just_executable() }} --justfile "{{ justfile() }}" prek run {{ args }}
# Runs pre-commit hooks on all files
prek-run-on-all *args: _ensure_mise_tools_installed
@{{ just_executable() }} --justfile "{{ justfile() }}" prek run --all-files {{ args }}
# Installs the git pre-commit hook
prek-install-git-pre-commit-hook: _ensure_mise_tools_installed
#!/usr/bin/env sh
set -eu
{{ just_executable() }} --justfile "{{ justfile() }}" mise exec -- prek install
# The installed git hooks run later under Git, outside this just/mise environment,
# so they need to be told how to find their tooling:
#
# - PREK_HOME keeps prek's cache under var/prek instead of a global home dir.
# - MISE_DATA_DIR / MISE_TRUSTED_CONFIG_PATHS make mise resolve against this project's
# own data directory. Without them mise falls back to the global one and silently
# installs a second copy of the tool there.
# - prek bakes the full path of the currently installed version into the hook
# (var/mise/installs/prek/<version>/...), which stops working as soon as the pinned
# version changes or old versions are pruned. Pointing at mise's shim instead makes
# the hook resolve whatever mise.toml pins, at the time it runs.
#
# Which hook files prek installs depends on `default_install_hook_types` in
# .pre-commit-config.yaml, so patch every hook file that prek generated.
for hook in "{{ justfile_directory() }}"/.git/hooks/*; do
[ -f "$hook" ] || continue
grep -q 'generated by prek' "$hook" || continue
grep -q '^export PREK_HOME=' "$hook" || sed -i '2iexport PREK_HOME="{{ prek_home }}"' "$hook"
grep -q '^export MISE_DATA_DIR=' "$hook" || sed -i '3iexport MISE_DATA_DIR="{{ mise_data_dir }}"' "$hook"
grep -q '^export MISE_TRUSTED_CONFIG_PATHS=' "$hook" || sed -i '4iexport MISE_TRUSTED_CONFIG_PATHS="{{ mise_trusted_config_paths }}"' "$hook"
sed -i 's#^PREK=".*"$#PREK="{{ mise_data_dir }}/shims/prek"#' "$hook"
done
# Runs the playbook with --tags=install-all,ensure-matrix-users-created,start and optional arguments
install-all *extra_args: (run-tags "install-all,ensure-matrix-users-created,start" extra_args)
# Runs installation tasks for a single service
install-service service *extra_args:
{{ just_executable() }} --justfile "{{ justfile() }}" run \
--tags=install-{{ service }},start-group \
--extra-vars=group={{ service }} {{ extra_args }}
# Runs the playbook with --tags=setup-all,ensure-matrix-users-created,start and optional arguments
setup-all *extra_args: (run-tags "setup-all,ensure-matrix-users-created,start" extra_args)
# Runs the playbook with the given list of arguments
run +extra_args:
ansible-playbook -i {{ inventory }} setup.yml {{ extra_args }}
# Runs the playbook with the given list of comma-separated tags and optional arguments
run-tags tags *extra_args:
{{ just_executable() }} --justfile "{{ justfile() }}" run --tags={{ tags }} {{ extra_args }}
# Runs the playbook in user-registration mode
register-user username password admin_yes_or_no *extra_args:
ansible-playbook -i {{ inventory }} setup.yml --tags=register-user --extra-vars="username={{ username }} password={{ password }} admin={{ admin_yes_or_no }}" {{ extra_args }}
# Starts all services
start-all *extra_args: (run-tags "start-all" extra_args)
# Starts a specific service group
start-group group *extra_args:
@{{ just_executable() }} --justfile "{{ justfile() }}" run-tags start-group --extra-vars="group={{ group }}" {{ extra_args }}
# Stops all services
stop-all *extra_args: (run-tags "stop-all" extra_args)
# Stops a specific service group
stop-group group *extra_args:
@{{ just_executable() }} --justfile "{{ justfile() }}" run-tags stop-group --extra-vars="group={{ group }}" {{ extra_args }}
# Runs a role's Molecule scenario locally (no argument lists the roles that have one)
molecule *args:
@{{ justfile_directory() }}/bin/molecule.sh {{ args }}
# Removes the Molecule virtualenv and per-role Ansible homes from var/
molecule-clean *args:
@{{ justfile_directory() }}/bin/molecule-clean.sh {{ args }}
# Internal - installs the roles in requirements.yml, keeping roles with local changes
_install-roles *flags:
@{{ justfile_directory() }}/bin/ansible-python.sh {{ justfile_directory() }}/bin/install-roles.py \
--requirements-path={{ justfile_directory() }}/requirements.yml \
--roles-path={{ justfile_directory() }}/roles/galaxy \
--state-path={{ quote(installed_roles_file_path) }} \
{{ flags }}
# Internal - ensures var/mise and var/prek directories exist
_ensure_mise_data_directory:
@mkdir -p "{{ mise_data_dir }}"
@mkdir -p "{{ prek_home }}"
# Internal - ensures mise tools are installed
_ensure_mise_tools_installed: _ensure_mise_data_directory
@{{ just_executable() }} --justfile "{{ justfile() }}" mise install --quiet