Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 91 additions & 0 deletions .claude/skills/lfops-follow-upstream/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
---
name: lfops-follow-upstream
description: Update LFOps after an upstream application release, e.g. a newreleases.io notice ("New Graylog2/graylog2-server releases"), a pasted release mail, or "Graylog 7.3.0 is out". Merges the packaged configuration changes into the versioned templates, adds templates for a new release series, and prepares the CHANGELOG entry and commit message. Use when a release notice or a new upstream version is mentioned.
---

# LFOps follow upstream

Brings the LFOps templates in line with a new upstream release. The deterministic part (fetching the packaged files, the three-way merge, header and supported-versions bookkeeping) is done by `tools/follow-upstream`; this skill covers what the tool cannot judge. Work from the repository root. Never commit: end with a report and a proposed commit message. The only exception is a headless run of the follow-upstream timer, whose prompt says what it may commit and push.


## 1. Collect the releases

Take the releases from whatever the user provides:

* A pasted mail or text: extract upstream project and versions. A newreleases.io mail lists them as `Graylog2/graylog2-server on GitHub ...` followed by `- 7.2.1` lines. Matomo instances send "New Matomo 5.14.0 is available".
* `<app> <version>` as arguments.
* Nothing: if `NOTMUCH_PROFILE=newreleases notmuch count tag:lfops-todo` works, read the mails with `NOTMUCH_PROFILE=newreleases notmuch show --format=json tag:lfops-todo`. Otherwise ask what was released.

Ignore pre-releases (`alpha`, `beta`, `rc`, `-dev`).


## 2. Triage every release

The repository is the state, not the mail: a release already covered needs no work, however often it is reported.

* Find the application in `tools/follow-upstream.yml` by its `upstream` names.
* Not listed: check whether a role deploys the application at all (`grep -ril <name> roles/*/README.md`). No role: report "not used by LFOps" and move on. A role exists: report it as a candidate for `tools/follow-upstream.yml` and ask before adding it (see "Adding an application").
* Listed: run `tools/follow-upstream <app> <version>`, oldest release first. It fetches the RPM and the DEB of the release on every run, so expect a few GB of downloads for large packages. Exit code 0: nothing to review, 1: findings or conflicts, 2: the run aborted.
* `packaging:` lines come first. Each one means upstream changed how it packages the application: a file below /etc that is neither templated nor ignored, a templated file the release no longer ships, or a file that differs between RPM and DEB. Do not resolve these yourself, and do not accept the merge of that release: stop and report them, with the `diff` the tool prints. Whether the role needs a new template, a per-format template or an `ignore` entry is the user's decision.
* A failed fetch (`not found in`) aborts the run. Upstream may have published one format before the other; report it and retry later rather than merging without the check.
* Then one line per template:
* `already covers`, `is identical in`: nothing to do.
* `merged cleanly` / `N conflict(s)`: continue with step 3.
* `no "Based on <package> <version>" line`: see "Bootstrapping a template".
* A new release series (template created from the latest older one): also step 4.


## 3. Review the merge

For every changed template, read the upstream diff the tool prints (`diff <base> <new>`) and the release notes / upgrade notes of the release.

* Resolve conflict markers (`<<<<<<<`). Keep the LFOps change (role variables, Jinja logic, security defaults) and take the upstream text around it.
* New upstream option: leave it as shipped (usually commented out). Only propose a role variable if an administrator plausibly needs it, and ask first.
* Removed or renamed upstream option that a role variable feeds: stop and ask. Renaming or dropping a role variable is a breaking change of the public interface.
* Changed upstream default that a role default overrides: report it.
* Do not reformat, re-wrap or "fix" upstream text. Templates stay as close to the packaged file as possible.
* `copies` in `tools/follow-upstream.yml` receive the same merge result; check that they still match their target platform.


## 4. New release series

The tool created `<series>-<file>.j2` from the latest older template and added the series to `supported_versions`.

* Fix every line the tool lists as still mentioning the old series (e.g. a `go2docs.graylog.org/7-1/` link).
* Check the role for other places that enumerate versions: README (`grep -n <old series> roles/<role>/README.md`), `meta/argument_specs.yml` choices, related `repo_*` roles.
* Read the upgrade notes for breaking changes, required migration steps and dropped platforms. Report them; they may need tasks, preflight checks or a `Breaking Changes` entry.
* Check whether a series has gone EOL upstream. Dropping a series is a separate decision for the user (CONTRIBUTING: "Do not support EOL software versions").


## 5. Test

* Run the Molecule scenario that covers the role if one exists (`grep -rl <role> extensions/molecule/*/converge.yml`), per CONTRIBUTING.md "Testing". Report it if there is none.
* Check for leftover conflict markers: `grep -rn '^<<<<<<<\|^>>>>>>>' roles/<role>/templates`.


## 6. Changelog and commit message

Follow CONTRIBUTING.md "Changelog" and "Changelog Sections", and the existing entries:

* Patch release inside an existing series: no CHANGELOG entry (the deployed file only gains upstream comments). Commit: `chore(roles/<role>): sync the <series> <file> template with <App> <version>`, the body names what upstream changed.
* New series: entry under `### Added` of `## [Unreleased]`, at the top: `* **role:<role>, role:<role>**: Add template for <App> <series>.` Commit: `feat(roles/<role>, roles/<role>): add template for <App> <series>`.
* A change an administrator has to act on: `### Breaking Changes` or `### Changed`, as CONTRIBUTING.md describes.


## 7. Report

Per release: what was done or why nothing was, conflicts and how they were resolved, open questions, test result, proposed commit message(s). With the notmuch source, once the user has accepted the result, offer to retag the mails: `NOTMUCH_PROFILE=newreleases notmuch tag -lfops-todo +lfops-done -- id:<message-id>` (`+lfops-skip` for releases LFOps does not use).


## Bootstrapping a template

`tools/follow-upstream` needs `{# Based on <package> <version> #}` below the version line of the template header. The Jinja comment does not end up in the deployed file, so adding it changes nothing on the hosts and needs no timestamp bump. To find the version:

1. Fetch the packaged file of the candidate releases of the series (the tool caches them under `~/.cache/lfops-follow-upstream/<package>-<version>/root/`). The upstream git tree under `~/git/other` is a good first filter, but packages may rewrite the file (Graylog sets absolute paths), so confirm against the package.
2. Diff each against the template (skipping the header). The release with the fewest differing lines is the base. Where several releases ship an identical file, take the newest.
3. Add the line and rerun the tool.


## Adding an application

Add an entry to `tools/follow-upstream.yml` (format in its header comment). Only for templates that are copies of a packaged file; a drop-in written from scratch has no base. `tools/follow-upstream` needs both a Yum and an APT repository serving every release of a series; an application packaged only for one family, or only by the distributions, needs the tool extended first. Run the tool once for the current release and put every reported file LFOps does not template into `ignore`, after checking that no role deploys it.
1 change: 1 addition & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -514,6 +514,7 @@ roles:
* Mirror the target path below `templates/` (`templates/etc/httpd/sites-available/default.conf.j2`) and always use `.j2`.
* Deploy self-written scripts to `/usr/local/sbin` (SELinux). Helpers run only by a systemd unit (not by an admin, not exec'd by a confined domain) MAY live in `/usr/local/libexec`: as `usr_t`, a root `oneshot` service in `init_t` runs them via `execute_no_trans` without AVC denial on RHEL 8, 9 and 10. Admin-invokable commands stay in `/usr/local/sbin`, and a script a confined domain must exec never goes to `/usr/local/libexec`.
* Keep templates as close to the original file as possible, which eases rpmnew/rpmsave handling.
* A template that is a copy of a packaged file names the package release it was taken from in a Jinja comment right below the version line, `{# Based on graylog-server 7.2.1 #}`, which does not end up on the host. List it in `tools/follow-upstream.yml`; `tools/follow-upstream` uses the line to merge the configuration changes of later releases into the template, see the `lfops-follow-upstream` skill.
* If the role picks a file by the installed version (`<version>-<name>.conf.j2`, `vars/<version>.yml`), list the shipped versions in `vars/main.yml` and assert against them right after reading the version, as `roles/example` does (`__example__supported_versions`), so the admin gets the supported versions instead of a missing-file path. Document the abort under `## Troubleshooting` (pin a supported version, or add the file).
* After deploying a file that may get rpmnew / rpmsave files (or Debian equivalents), include `shared` with `tasks_from: 'remove-rpmnew-rpmsave.yml'` and `shared__remove_rpmnew_rpmsave_config_file`, as in `roles/example`.

Expand Down
1 change: 1 addition & 0 deletions galaxy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ issues: 'https://github.com/linuxfabrik/lfops/issues'
# and '.git' are always filtered
build_ignore:
- '.git*'
- '.claude'
- 'build'
- 'context'
- 'tools'
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# {{ ansible_managed }}
# 2026061001
# 7.0
{# Based on graylog-datanode 7.0.15 #}
# see https://go2docs.graylog.org/7-0/setting_up_graylog/data_node_configuration_file.htm
#####################################
# GRAYLOG DATANODE CONFIGURATION FILE
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# {{ ansible_managed }}
# 2026061001
# 7.1
{# Based on graylog-datanode 7.1.11 #}
# see https://go2docs.graylog.org/7-1/setting_up_graylog/data_node_configuration_file.htm
#####################################
# GRAYLOG DATANODE CONFIGURATION FILE
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# {{ ansible_managed }}
# 2026101401
# 7.2
{# Based on graylog-datanode 7.2.1 #}
# see https://go2docs.graylog.org/7-2/setting_up_graylog/data_node_configuration_file.htm
#####################################
# GRAYLOG DATANODE CONFIGURATION FILE
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# {{ ansible_managed }}
# 2024110601
{# Based on graylog-datanode 7.2.1 #}

# JVM Heap Settings
#
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# {{ ansible_managed }}
# 2024022201
{# Based on graylog-server 7.2.1 #}

# Path to a custom java executable. By default the java executable of the
# bundled JVM is used.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# {{ ansible_managed }}
# 2026100901
# 7.0
{# Based on graylog-server 7.0.15 #}
############################
# GRAYLOG CONFIGURATION FILE
############################
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# {{ ansible_managed }}
# 2026100701
# 2026102001
# 7.1
{# Based on graylog-server 7.1.11 #}
############################
# GRAYLOG CONFIGURATION FILE
############################
Expand Down Expand Up @@ -311,6 +312,14 @@ elasticsearch_max_total_connections_per_route = {{ graylog_server__elasticsearch
#
#default_system_events_index_prefix = gl-system-events

# The refresh interval for the Graylog events indices. This applies to both the events and the
# system events index sets, and only takes effect on newly created or updated index templates.
# Lower values make new events searchable sooner at the cost of indexing throughput. Some managed
# OpenSearch offerings enforce a cluster-wide minimum refresh interval and will reject the index
# template if this value is below it. Must be 1 second or longer.
# Default: 1s
#events_index_refresh_interval = 1s

# Analyzer (tokenizer) to use for message and full_message field. The "standard" filter usually is a good idea.
# All supported analyzers are: standard, simple, whitespace, stop, keyword, pattern, language, snowball, custom
# Elasticsearch documentation: https://www.elastic.co/guide/en/elasticsearch/reference/2.3/analysis.html
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# {{ ansible_managed }}
# 2026101401
# 7.2
{# Based on graylog-server 7.2.1 #}
############################
# GRAYLOG CONFIGURATION FILE
############################
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# {{ ansible_managed }}
# 2024022201
{# Based on graylog-server 7.2.1 #}

# Path to a custom java executable. By default the java executable of the
# bundled JVM is used.
Expand Down
Loading
Loading