Skip to content
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Automated AUR package updates from a single GitHub repo.

<table>
<tr><th>Package</th><th>Version</th><th>Strategy</th><th>Updated</th><th>Status</th></tr>
<tr><td><em>No packages configured</em></td><td>—</td><td nowrap>—</td><td nowrap>—</td><td nowrap>—</td></tr>
<tr><td><em>No packages configured</em></td><td>-</td><td nowrap>-</td><td nowrap>-</td><td nowrap>-</td></tr>
</table>

aurtomator 0.7.0 <!-- x-release-please-version -->
Expand Down
12 changes: 6 additions & 6 deletions docs/WORKFLOW.md
Original file line number Diff line number Diff line change
Expand Up @@ -302,7 +302,7 @@ upstream:
date_format: '%Y%m%d'
```

`validate-pkg.sh` refuses `date_format` with another `version_source` and a format whose output is not a valid version, such as `%Y-%m-%d`. It refuses `nightly_tag_regex` next to `nightly_tag` or `channel`, and one that bash cannot compile. It refuses `tag_version_regex` with a `version_source` other than `tag`. It refuses a `version_source` other than `tag`, `release_body`, `tag_date` or `published_date`, `release_body` without `version_pattern`, a `version_pattern` that `grep -P` cannot compile, and `version_pattern` with another `version_source`. It refuses `tag_date` with the fixed tag of pattern A: such a tag has no dash and so no `YYYY-MM-DD` date. It refuses `nightly_tag` next to `channel`: `nightly_tag: latest` leaves `channel` unread, and `channel` leaves any other `nightly_tag` unread.
`validate-pkg.sh` refuses `date_format` with another `version_source` and a format whose output is not a valid version, such as `%Y-%m-%d`. It refuses `nightly_tag_regex` next to `nightly_tag` or `channel`, and one that bash cannot compile. It refuses `tag_version_regex` and `tag_version_replace` with a `version_source` other than `tag`. It refuses a `version_source` other than `tag`, `release_body`, `tag_date` or `published_date`, `release_body` without `version_pattern`, a `version_pattern` that `grep -P` cannot compile, and `version_pattern` with another `version_source`. It refuses `tag_date` with the fixed tag of pattern A: such a tag has no dash and so no `YYYY-MM-DD` date. It refuses `nightly_tag` next to `channel`: `nightly_tag: latest` leaves `channel` unread, and `channel` leaves any other `nightly_tag` unread.

**Authentication:** same as `github-release`.

Expand Down Expand Up @@ -610,7 +610,7 @@ strategy: repology
| `upstream.archpkg` | no | Package name in the official repos. Defaults to the package `name`. |
| `upstream.testing` | no | `true` also reads `core-testing`, `extra-testing` and `multilib-testing`. Default `false`. |

**Version:** reads the entries of `core`, `extra` and `multilib` and takes the newest by `vercmp`, the epoch included. It prints its `pkgver`, the version without `epoch` and `pkgrel`: ffmpeg `2:9.0.2-2` gives `9.0.2`. A package without an entry in these repositories fails with `Package not found in core, extra, multilib: <name>`. A rebuild in the official repos raises only `pkgrel`, so it gives no new version, and an update of this strategy never raises `pkgrel` alone, see [SUPPORTED-PACKAGES.md](SUPPORTED-PACKAGES.md#packages-needing-a-pkgrel-bump).
**Version:** reads the entries of `core`, `extra` and `multilib` and takes the newest by `vercmp`, the epoch included. It prints its `pkgver`, the version without `epoch` and `pkgrel`: ffmpeg `2:9.0.2-2` gives `9.0.2`. When the official package raises its epoch, as `1:2.0` to `2:1.0`, the strategy gives `1.0` after `2.0`, and the check skips it as a downgrade, see [4.3](#43-downgrade-behavior). aurtomator does not write an `epoch`: the maintainer raises it in the AUR package, publishes `1.0` by hand and sets `current` to it. A package without an entry in these repositories fails with `Package not found in core, extra, multilib: <name>`. A rebuild in the official repos raises only `pkgrel`, so it gives no new version, and an update of this strategy never raises `pkgrel` alone, see [SUPPORTED-PACKAGES.md](SUPPORTED-PACKAGES.md#packages-needing-a-pkgrel-bump).

With `testing: true` the strategy also reads `core-testing`, `extra-testing` and `multilib-testing`. It takes the build in testing while that build is newer than the stable one, and the stable build otherwise. A build that moves from testing to the stable repository keeps its version and gives no new update. `gnome-unstable` and `kde-unstable` stay out. `validate-pkg.sh` refuses a `testing` that is not `true` or `false`, and `testing` with another strategy.

Expand Down Expand Up @@ -868,7 +868,7 @@ A PKGBUILD is a bash script, and every step that reads it executes it: the audit
2. Clone `https://aur.archlinux.org/<package-name>.git` anonymously with `--no-checkout`. When `HEAD` is not `base`, the AUR repo moved after the update was prepared: exit code 3. The entries at the top of `HEAD` are written to a separate directory with `git ls-tree` and `git cat-file`, so git applies none of the links, attributes or filters of the repo: a regular file with its content, a symlink as a link to nowhere, anything else as an empty directory. The steps below read the AUR copy there.
3. `PKGBUILD` and `.SRCINFO` in the AUR copy must be regular files. The prepared files must hold no NUL byte and pass steps 4 and 5 of publish below, with the prepared `pkgver` as `<version>` and, for a package with `upstream.asset_var`, the value of its line in the prepared `PKGBUILD` as the asset value. From here on, every source URL in the prepared `.SRCINFO` follows from the AUR copy.
4. Each checksum array of the prepared `PKGBUILD` must hold the same values as the same key in the prepared `.SRCINFO`.
5. Every checksum other than `SKIP` must match its source, computed as makepkg does with `<algorithm>sum`. The file is the one makepkg uses. A file in the AUR repo with the name of the source comes first and must be a regular file. Otherwise a remote source is downloaded with curl, retried as makepkg does and limited to `http`, `https` and `ftp`, redirects included. A git source at `#tag=` or `#commit=` is cloned as a mirror over `https` or `http`, and its checksum is that of `git archive --format tar` of the tag or commit, with `export-subst` and `export-ignore` turned off, as makepkg computes it. A checksum for any other source, another VCS for example, cannot be checked and stops the verify.
5. Every checksum other than `SKIP` must match its source, computed as makepkg does with `<algorithm>sum`. The file is the one makepkg uses. A file in the AUR repo with the name of the source comes first and must be a regular file. Otherwise a remote source is downloaded with curl, retried and with the cookie engine on as makepkg does, and limited to `http`, `https` and `ftp`, redirects included. A git source at `#tag=` or `#commit=` is cloned as a mirror over `https` or `http`, and its checksum is that of `git archive --format tar` of the tag or commit, with `export-subst` and `export-ignore` turned off, as makepkg computes it. A checksum for any other source, another VCS for example, cannot be checked and stops the verify.
6. Any mismatch stops the verify with exit code 1. Otherwise the exit code is 0.

**Publish**, `publish-pkg.sh <package-name> <version> <dir>/<package-name>`, never sources the PKGBUILD and never runs `makepkg`. It treats the prepared files as untrusted:
Expand Down Expand Up @@ -909,7 +909,7 @@ alerts:
| `when` | one of `when`, `from`, `match` | `major`: the first number of the new version is greater than the first number of `current`, as in `28+25 → 29+1` |
| `from` | one of `when`, `from`, `match` | A version: the new version is at or above it, and `current` is below it |
| `match` | one of `when`, `from`, `match` | An ERE: the new version matches it, and `current` does not |
| `note` | yes | One line for the maintainer |
| `note` | yes | One line of text for the maintainer. A number, a boolean or a date goes in quotes, as in `note: '2024'` |
| `hold` | no | `true` pauses the update. Default `false` |

A rule fires when the new version meets its condition and `current` does not, so it fires once at each crossing. A `from` rule can stay in the file after the release it names, and a `when: major` rule fires again at the next major. The versions are in `pkgver` form, as `current` holds them, after `pkgver_dash` and `pkgver_format`. `vercmp` compares a version with `from` and the first numbers for `when: major`, so `9 → 10` is a new major. A package without `current` fires no rule.
Expand All @@ -925,7 +925,7 @@ A skipped downgrade or version jump comes first, see [4.3](#43-downgrade-behavio

Every rule that fires writes its note to `.status/_alerts`, and the run opens an issue from it, see [5.6](#56-issues). A held update gets the issue `✋ <package>: update held` with the notes of every rule that fired, because after the hand publish a rule without `hold` no longer fires. The run closes it once the version is in the AUR. A published update gets the issue `🔔 <package>: <version> notice` with its notes, and the maintainer closes it once the edit is done.

`validate-pkg.sh` refuses an entry with any other key, without exactly one of `when`, `from` and `match`, with `when` other than `major`, with a `from` that is not a version, a `match` that bash does not accept as an ERE, a `note` that is not one line, or a `hold` that is not a boolean. It refuses `when: major` together with `pkgver_format`, because the fixed text of a format such as `4.%s` holds the first number of every version. `check-all.sh` reads the rules on every run. A rule it cannot evaluate, or a `note` that is not one line, fails the check of the package and never lets a held update through.
`validate-pkg.sh` refuses an entry with any other key, without exactly one of `when`, `from` and `match`, with `when` other than `major`, with a `from` that is not a version, a `match` that bash does not accept as an ERE, a `note` that is not one line of text, or a `hold` that is not a boolean. It refuses `when: major` together with `pkgver_format`, because the fixed text of a format such as `4.%s` holds the first number of every version. `check-all.sh` reads the rules on every run. A rule it cannot evaluate, or a `note` that is not one line of text, fails the check of the package and never lets a held update through.

### 4.6 Values from release asset names

Expand Down Expand Up @@ -1186,7 +1186,7 @@ GitHub starts a `workflow_run` workflow only from its file on the default branch
`update-readme.sh` writes the version under the package table, and with `newer:` a link to the release:

```text
aurtomator 0.6.0 · [0.7.0 available](https://github.com/staticwire/aurtomator/releases/tag/v0.7.0) <!-- x-release-please-version -->
aurtomator 0.6.0 ([0.7.0 available](https://github.com/staticwire/aurtomator/releases/tag/v0.7.0)) <!-- x-release-please-version -->
```

The comment marks the line for the release PR of aurtomator, which writes the new version there in the README of the upstream repository. Its check run then finds the README as `update-readme.sh` writes it, and has nothing to commit to a `main` that only takes pull requests. A run that could not read the release writes the line without the link.
Expand Down
2 changes: 1 addition & 1 deletion scripts/check-asset.sh
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ if ((${#found[@]} == 0)); then
exit 1
fi
if ((${#found[@]} > 1)); then
log_err "Tags ${found[*]} of ${project} all give version ${PKGVER}"
log_err "Tags ${found[*]} of ${project} all give version ${PKGVER}; set upstream.tag_version_regex to match one of them"
exit 1
fi
tag="${found[0]}"
Expand Down
11 changes: 10 additions & 1 deletion scripts/lib/alerts.sh
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,15 @@ alert_note() {
printf '%s\n' "$note"
}

# Prints the error for a note that alert_note refuses. YAML reads a bare
# number, boolean or date as another type than text.
# Args: $1=package file, $2=index of the alert
alert_note_error() {
local value
value=$(pkg_get "$1" ".alerts[$2].note | select(tag == \"!!int\" or tag == \"!!float\" or tag == \"!!bool\" or tag == \"!!timestamp\")")
printf 'note must be one line of text%s\n' "${value:+: put $value in quotes}"
}

# The alerts of a package that fire on the step from current to the new
# version, one per line in file order: hold or notice, a tab and the note.
# Fails on a rule it cannot evaluate and on a note that is not one line
Expand All @@ -60,7 +69,7 @@ fired_alerts() {
return 1
fi
if ! note=$(alert_note "$file" "$i"); then
log_err "alerts[$i].note must be one line of text"
log_err "alerts[$i].$(alert_note_error "$file" "$i")"
return 1
fi
rc=0
Expand Down
2 changes: 1 addition & 1 deletion scripts/lib/common.sh
Original file line number Diff line number Diff line change
Expand Up @@ -459,7 +459,7 @@ latest_tag_version() {
fi
version=$(tag_versions "$regex" "$replace" <<<"$tags" | newest_version)
if [[ -z "$version" ]]; then
echo "No matching tags found" >&2
echo "No tag gives a version with upstream.tag_version_regex '$regex', tags read: $(grep -c . <<<"$1")" >&2
return 1
fi
echo "$version"
Expand Down
6 changes: 4 additions & 2 deletions scripts/lib/sums.sh
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,11 @@ _source_file() {
return 1
else
file="${3}/$(printf '%s' "$url" | sha256sum | cut -d' ' -f1)"
# Retried as makepkg does. Redirects may only lead to the protocols above.
# Retried and with the cookie engine on, as makepkg does, so a cookie set
# on a redirect goes with the next request. Redirects may only lead to
# the protocols above.
if [[ ! -f "$file" ]] &&
! curl -qgfsSL --retry 3 --retry-delay 3 --proto '=http,https,ftp' \
! curl -qgfsSL -b '' --retry 3 --retry-delay 3 --proto '=http,https,ftp' \
--proto-redir '=http,https,ftp' -o "$file" "$url"; then
rm -f "$file"
log_err "Could not download $url"
Expand Down
4 changes: 2 additions & 2 deletions scripts/setup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -165,8 +165,8 @@ setup_ssh() {
if [[ -z "$AUR_SSH_KEY" ]]; then
AUR_SSH_KEY=$(ask "Path to SSH private key for AUR" "$HOME/.ssh/aur_ed25519")
fi
# Expand ~ to home dir
AUR_SSH_KEY="${AUR_SSH_KEY/#\~/$HOME}"
# Expand ~/ to the home dir. ~user/ stays as it is.
AUR_SSH_KEY="${AUR_SSH_KEY/#\~\//$HOME/}"

check_ssh_key || return 1
ok "SSH key: $AUR_SSH_KEY"
Expand Down
21 changes: 13 additions & 8 deletions scripts/update-readme.sh
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,13 @@ if grep -qxF -- "$BADGES_START" "$README"; then
has_badges=true
fi

# Detect repo from git origin
repo_url=$(git -C "$AURTOMATOR_DIR" remote get-url origin 2>/dev/null | sed 's|.*github.com[:/]||; s|\.git$||' || true)
# Only a GitHub origin names the repo, so the URL of another remote, or a
# local path, never reaches the README
repo_url=""
origin_url=$(git -C "$AURTOMATOR_DIR" remote get-url origin 2>/dev/null || true)
if [[ "$origin_url" =~ [/@]github\.com[:/]([A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+) ]]; then
repo_url="${BASH_REMATCH[1]%.git}"
fi

# =============================================================================
# BADGES SECTION
Expand Down Expand Up @@ -135,9 +140,9 @@ for pkg_file in "${AURTOMATOR_DIR}"/packages/*.yml; do
[[ -f "$pkg_file" ]] || continue

name=$(pkg_get "$pkg_file" .name)
current=$(pkg_get "$pkg_file" '.current // "—"')
current=$(pkg_get "$pkg_file" '.current // "-"')
strategy=$(pkg_get "$pkg_file" .strategy)
last_updated=$(pkg_get "$pkg_file" '.last_updated // "—"')
last_updated=$(pkg_get "$pkg_file" '.last_updated // "-"')

# Determine status from .status/ dir or YAML
status_file="${STATUS_DIR}/${name}"
Expand Down Expand Up @@ -197,9 +202,9 @@ for pkg_file in "${AURTOMATOR_DIR}"/packages/*.yml; do
((pkg_ok++)) || true
;;
esac
elif [[ "$current" == "—" || "$current" == "null" ]]; then
elif [[ "$current" == "-" || "$current" == "null" ]]; then
status="✨ new"
current="—"
current="-"
((pkg_ok++)) || true
else
status="✅ tracking"
Expand Down Expand Up @@ -237,7 +242,7 @@ table+="
${table_header}"

if [[ "$pkg_count" -eq 0 ]]; then
table+=$(table_row "<em>No packages configured</em>" — — — —)
table+=$(table_row "<em>No packages configured</em>" - - - -)
else
table+="$table_rows"
fi
Expand All @@ -253,7 +258,7 @@ if [[ -n "$version" ]]; then
[[ ! -f "${STATUS_DIR}/_upstream" ]] || upstream=$(<"${STATUS_DIR}/_upstream")
table+=$'\n\n'"aurtomator ${version}"
if [[ "$upstream" =~ ^newer:\ ([0-9]+\.[0-9]+\.[0-9]+)$ ]]; then
table+=" · [${BASH_REMATCH[1]} available](https://github.com/${AURTOMATOR_UPSTREAM}/releases/tag/v${BASH_REMATCH[1]})"
table+=" ([${BASH_REMATCH[1]} available](https://github.com/${AURTOMATOR_UPSTREAM}/releases/tag/v${BASH_REMATCH[1]}))"
fi
table+=" <!-- x-release-please-version -->"
fi
Expand Down
31 changes: 25 additions & 6 deletions scripts/validate-pkg.sh
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,18 @@ readonly UPSTREAM_KEYS=(
"kde-tarball: project path"
)

# Succeeds when $1 is one of the words of $2. A substring match would pass
# a key such as "name strategy".
# Args: $1=word, $2=words separated by spaces
in_words() {
local words word
read -ra words <<<"$2"
for word in "${words[@]}"; do
[[ "$word" != "$1" ]] || return 0
done
return 1
}

# Prints the upstream keys of a strategy, or fails when UPSTREAM_KEYS has
# no line for it
# Args: $1=strategy
Expand All @@ -66,7 +78,7 @@ upstream_keys() {
key_strategies() {
local line names=() list i
for line in "${UPSTREAM_KEYS[@]}"; do
[[ " ${line#*: } " != *" $1 "* ]] || names+=("${line%%:*}")
! in_words "$1" "${line#*: }" || names+=("${line%%:*}")
done
case ${#names[@]} in
0) return 1 ;;
Expand Down Expand Up @@ -126,7 +138,7 @@ validate_alert() {
fi

if ! alert_note "$file" "$i" >/dev/null; then
log_err "$rule.note must be one line of text"
log_err "$rule.$(alert_note_error "$file" "$i")"
errors=1
fi

Expand Down Expand Up @@ -191,7 +203,7 @@ validate_pkg() {
local keys key unknown=() list strategy_keys where
mapfile -t keys < <(pkg_get "$file" 'keys | .[]')
for key in "${keys[@]}"; do
[[ " $PACKAGE_KEYS " == *" $key "* ]] || unknown+=("$key")
in_words "$key" "$PACKAGE_KEYS" || unknown+=("$key")
done
if ((${#unknown[@]} > 0)); then
printf -v list '%s, ' "${unknown[@]}"
Expand All @@ -210,7 +222,7 @@ validate_pkg() {
((errors++))
elif ((${#keys[@]} > 0)); then
for key in "${keys[@]}"; do
[[ " $strategy_keys " != *" $key "* ]] || continue
! in_words "$key" "$strategy_keys" || continue
if where=$(key_strategies "$key"); then
log_err "$file: upstream.$key applies to $where only"
else
Expand Down Expand Up @@ -353,7 +365,10 @@ validate_pkg() {
local tag_version_replace
tag_version_replace=$(pkg_get "$file" '.upstream.tag_version_replace // ""')
if [[ -n "$tag_version_replace" ]]; then
if [[ -z "$tag_version_regex" ]]; then
if [[ "$strategy" == "github-nightly" && $(pkg_get "$file" '.upstream.version_source // "tag"') != "tag" ]]; then
log_err "$file: upstream.tag_version_replace applies to version_source tag only"
((errors++))
elif [[ -z "$tag_version_regex" ]]; then
log_err "$file: upstream.tag_version_replace needs upstream.tag_version_regex"
((errors++))
elif [[ ! "$tag_version_replace" =~ ^([A-Za-z0-9._+]|\\[1-9])+$ ]]; then
Expand Down Expand Up @@ -451,7 +466,11 @@ validate_pkg() {
fi
# nightly_tag: latest wins over channel, and channel over any other tag
if [[ $(pkg_get "$file" '.upstream | (has("nightly_tag") and has("channel"))') == true ]]; then
log_err "$file: upstream.channel takes the place of nightly_tag"
if [[ "$nightly_tag" == latest ]]; then
log_err "$file: upstream.nightly_tag latest takes the place of channel"
else
log_err "$file: upstream.channel takes the place of nightly_tag"
fi
((errors++))
fi
fi
Expand Down
9 changes: 9 additions & 0 deletions tests/publish.bats
Original file line number Diff line number Diff line change
Expand Up @@ -875,6 +875,15 @@ commit_aur_state() {
grep -q -- '--proto =http,https,ftp --proto-redir =http,https,ftp .* https://example.com/demo-1.1.0.tar.gz$' "$TEST_TMPDIR/curl.args"
}

# makepkg runs curl with -b "", so a cookie set on a redirect goes with the
# next request
@test "verify-pkg: downloads with the cookie engine on, as makepkg does" {
setup_verify
run "$TEST_TMPDIR/scripts/verify-pkg.sh" demo "$PREPARED"
[ "$status" -eq 0 ]
grep -qF -- ' -b --retry 3 ' "$TEST_TMPDIR/curl.args"
}

@test "verify-pkg: refuses a checksum that does not match the download" {
setup_verify
write_pkgbuild "$PREPARED/PKGBUILD" 1.1.0 1 "sha256sums=('$H3' '$SUM_LOCAL')"
Expand Down
Loading
Loading