Repository: goreleaser/nfpm
Pull request #675 (2023-06-07 18:54:27)

feat: expand env variables in homepage field
For example, GitLab CI has `CI_PROJECT_URL` which can be useful here.

scop: Aside, I'm beginning to wonder why not expand env vars in all fields :thinking: 

Patch:
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index cb2575c..3880ea6 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -7,20 +7,20 @@ on:
     branches:
       - main
     paths:
-      - 'go.*'
-      - '**/*.go'
-      - 'Taskfile.yml'
-      - 'Dockerfile'
-      - '.github/workflows/build.yml'
-      - 'testdata/**'
+      - "go.*"
+      - "**/*.go"
+      - "Taskfile.yml"
+      - "Dockerfile"
+      - ".github/workflows/build.yml"
+      - "testdata/**"
   pull_request:
     paths:
-      - 'go.*'
-      - '**/*.go'
-      - 'Taskfile.yml'
-      - 'Dockerfile'
-      - '.github/workflows/build.yml'
-      - 'testdata/'
+      - "go.*"
+      - "**/*.go"
+      - "Taskfile.yml"
+      - "Dockerfile"
+      - ".github/workflows/build.yml"
+      - "testdata/"
 
 permissions:
   contents: read
@@ -36,11 +36,11 @@ jobs:
     uses: caarlos0/meta/.github/workflows/ruleguard.yml@main
     with:
       go-version: stable
-      args: '-disable largeloopcopy'
+      args: "-disable largeloopcopy"
   unit-tests:
     strategy:
       matrix:
-        os: [ ubuntu-latest, macos-latest, windows-latest ]
+        os: [ubuntu-latest, macos-latest]
     runs-on: ${{ matrix.os }}
     steps:
       - uses: actions/checkout@v3
@@ -63,8 +63,8 @@ jobs:
   acceptance-tests:
     strategy:
       matrix:
-        pkgFormat: [ deb, rpm, apk, archlinux ]
-        pkgPlatform: [ amd64, arm64, 386, ppc64le, armv6, armv7, s390x ]
+        pkgFormat: [deb, rpm, apk, archlinux]
+        pkgPlatform: [amd64, arm64, 386, ppc64le, armv6, armv7, s390x]
     runs-on: ubuntu-latest
     env:
       DOCKER_CLI_EXPERIMENTAL: "enabled"
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index b4cdea5..743819e 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -34,8 +34,8 @@ jobs:
       - uses: arduino/setup-task@v1
         with:
           repo-token: ${{ secrets.GITHUB_TOKEN }}
-      - uses: sigstore/cosign-installer@v3.0.5
-      - uses: anchore/sbom-action/download-syft@v0.14.2
+      - uses: sigstore/cosign-installer@v3.1.1
+      - uses: anchore/sbom-action/download-syft@v0.14.3
       - uses: docker/setup-qemu-action@v2
       - uses: docker/setup-buildx-action@v2
       - run: task setup
diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml
new file mode 100644
index 0000000..3ba9807
--- /dev/null
+++ b/.github/workflows/windows.yml
@@ -0,0 +1,77 @@
+name: windows
+
+on:
+  push:
+    tags:
+      - v*
+    branches:
+      - main
+    paths:
+      - "go.*"
+      - "**/*.go"
+      - "Taskfile.yml"
+      - ".github/workflows/windows.yml"
+  pull_request:
+    paths:
+      - "go.*"
+      - "**/*.go"
+      - "Taskfile.yml"
+      - ".github/workflows/windows.yml"
+
+permissions:
+  contents: read
+
+jobs:
+  test:
+    runs-on: windows-latest
+    steps:
+      - uses: actions/checkout@v3
+      - uses: actions/setup-go@v4
+        with:
+          go-version: stable
+      - uses: arduino/setup-task@v1
+        with:
+          repo-token: ${{ secrets.GITHUB_TOKEN }}
+      - name: setup-tparse
+        run: go install github.com/mfridman/tparse@latest
+      - run: task setup
+      - name: test
+        shell: bash
+        run: ./scripts/test.sh test windows-latest
+  build-pkgs:
+    runs-on: windows-latest
+    steps:
+      - uses: actions/checkout@v3
+      - uses: actions/setup-go@v4
+        with:
+          go-version: stable
+      - uses: arduino/setup-task@v1
+        with:
+          repo-token: ${{ secrets.GITHUB_TOKEN }}
+      - shell: bash
+        run: |
+          echo "sha_short=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
+      - uses: actions/cache@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 # v3
+        with:
+          path: dist/
+          key: ${{ env.sha_short }}
+          enableCrossOsArchive: true
+      - run: task acceptance:windows:package
+  install-pkgs:
+    runs-on: ubuntu-latest
+    needs: [build-pkgs]
+    steps:
+      - uses: docker/setup-qemu-action@v2
+      - uses: actions/checkout@v3
+      - uses: arduino/setup-task@v1
+        with:
+          repo-token: ${{ secrets.GITHUB_TOKEN }}
+      - shell: bash
+        run: |
+          echo "sha_short=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
+      - uses: actions/cache@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 # v3
+        with:
+          path: dist/
+          key: ${{ env.sha_short }}
+          enableCrossOsArchive: true
+      - run: task acceptance:windows:install
diff --git a/.gitignore b/.gitignore
index 5f0850b..96d31df 100644
--- a/.gitignore
+++ b/.gitignore
@@ -18,3 +18,4 @@ completions/
 cosign.*
 manpages
 output.json
+!acceptance_test.go
diff --git a/.goreleaser.yml b/.goreleaser.yml
index 63373f2..c5c3416 100644
--- a/.goreleaser.yml
+++ b/.goreleaser.yml
@@ -13,26 +13,26 @@ gomod:
   proxy: true
 
 builds:
-- main: ./cmd/nfpm
-  env:
-  - CGO_ENABLED=0
-  goos:
-  - linux
-  - darwin
-  - windows
-  goarch:
-  - amd64
-  - arm64
-  mod_timestamp: '{{ .CommitTimestamp }}'
-  flags:
-    - -trimpath
-  ldflags:
-    - -s -w -X main.version={{ .Version }} -X main.commit={{ .Commit }} -X main.date={{ .CommitDate }} -X main.builtBy=goreleaser
+  - main: ./cmd/nfpm
+    env:
+      - CGO_ENABLED=0
+    goos:
+      - linux
+      - darwin
+      - windows
+    goarch:
+      - amd64
+      - arm64
+    mod_timestamp: "{{ .CommitTimestamp }}"
+    flags:
+      - -trimpath
+    ldflags:
+      - -s -w -X main.version={{ .Version }} -X main.commit={{ .Commit }} -X main.date={{ .CommitDate }} -X main.builtBy=goreleaser -X main.treeState={{ .IsGitDirty }}
 
 dockers:
   - image_templates:
-      - 'goreleaser/nfpm:{{ .Tag }}-amd64'
-      - 'ghcr.io/goreleaser/nfpm:{{ .Tag }}-amd64'
+      - "goreleaser/nfpm:{{ .Tag }}-amd64"
+      - "ghcr.io/goreleaser/nfpm:{{ .Tag }}-amd64"
     dockerfile: Dockerfile
     use: buildx
     build_flag_templates:
@@ -44,8 +44,8 @@ dockers:
       - "--label=org.opencontainers.image.source={{.GitURL}}"
       - "--platform=linux/amd64"
   - image_templates:
-      - 'goreleaser/nfpm:{{ .Tag }}-arm64v8'
-      - 'ghcr.io/goreleaser/nfpm:{{ .Tag }}-arm64v8'
+      - "goreleaser/nfpm:{{ .Tag }}-arm64v8"
+      - "ghcr.io/goreleaser/nfpm:{{ .Tag }}-arm64v8"
     dockerfile: Dockerfile
     use: buildx
     build_flag_templates:
@@ -59,119 +59,119 @@ dockers:
     goarch: arm64
 
 docker_manifests:
-  - name_template: 'goreleaser/nfpm:{{ .Tag }}'
+  - name_template: "goreleaser/nfpm:{{ .Tag }}"
     image_templates:
-      - 'goreleaser/nfpm:{{ .Tag }}-amd64'
-      - 'goreleaser/nfpm:{{ .Tag }}-arm64v8'
-  - name_template: 'ghcr.io/goreleaser/nfpm:{{ .Tag }}'
+      - "goreleaser/nfpm:{{ .Tag }}-amd64"
+      - "goreleaser/nfpm:{{ .Tag }}-arm64v8"
+  - name_template: "ghcr.io/goreleaser/nfpm:{{ .Tag }}"
     image_templates:
-      - 'ghcr.io/goreleaser/nfpm:{{ .Tag }}-amd64'
-      - 'ghcr.io/goreleaser/nfpm:{{ .Tag }}-arm64v8'
-  - name_template: 'goreleaser/nfpm:latest'
+      - "ghcr.io/goreleaser/nfpm:{{ .Tag }}-amd64"
+      - "ghcr.io/goreleaser/nfpm:{{ .Tag }}-arm64v8"
+  - name_template: "goreleaser/nfpm:latest"
     image_templates:
-      - 'goreleaser/nfpm:{{ .Tag }}-amd64'
-      - 'goreleaser/nfpm:{{ .Tag }}-arm64v8'
-  - name_template: 'ghcr.io/goreleaser/nfpm:latest'
+      - "goreleaser/nfpm:{{ .Tag }}-amd64"
+      - "goreleaser/nfpm:{{ .Tag }}-arm64v8"
+  - name_template: "ghcr.io/goreleaser/nfpm:latest"
     image_templates:
-      - 'ghcr.io/goreleaser/nfpm:{{ .Tag }}-amd64'
-      - 'ghcr.io/goreleaser/nfpm:{{ .Tag }}-arm64v8'
+      - "ghcr.io/goreleaser/nfpm:{{ .Tag }}-amd64"
+      - "ghcr.io/goreleaser/nfpm:{{ .Tag }}-arm64v8"
 
 archives:
-- rlcp: true
-  name_template: >-
+  - rlcp: true
+    name_template: >-
       {{ .ProjectName }}_
       {{- .Version }}_
       {{- title .Os }}_
       {{- if eq .Arch "amd64" }}x86_64
       {{- else if eq .Arch "386" }}i386
       {{- else }}{{ .Arch }}{{ end }}
-  format_overrides:
-    - goos: windows
-      format: zip
-  files:
-    - README.md
-    - LICENSE.md
-    - completions/*
-    - manpages/*
+    format_overrides:
+      - goos: windows
+        format: zip
+    files:
+      - README.md
+      - LICENSE.md
+      - completions/*
+      - manpages/*
 
 brews:
-- tap:
-    owner: goreleaser
-    name: homebrew-tap
-  folder: Formula
-  homepage: https://nfpm.goreleaser.com
-  description: nFPM is a simple, 0-dependencies, deb, rpm and apk packager.
-  license: MIT
-  test: |
-    system "#{bin}/nfpm -v"
-  install: |-
-    bin.install "nfpm"
-    bash_completion.install "completions/nfpm.bash" => "nfpm"
-    zsh_completion.install "completions/nfpm.zsh" => "_nfpm"
-    fish_completion.install "completions/nfpm.fish"
-    man1.install "manpages/nfpm.1.gz"
+  - tap:
+      owner: goreleaser
+      name: homebrew-tap
+    folder: Formula
+    homepage: https://nfpm.goreleaser.com
+    description: nFPM is a simple, 0-dependencies, deb, rpm and apk packager.
+    license: MIT
+    test: |
+      system "#{bin}/nfpm -v"
+    install: |-
+      bin.install "nfpm"
+      bash_completion.install "completions/nfpm.bash" => "nfpm"
+      zsh_completion.install "completions/nfpm.zsh" => "_nfpm"
+      fish_completion.install "completions/nfpm.fish"
+      man1.install "manpages/nfpm.1.gz"
 
 scoops:
-- bucket:
-    owner: goreleaser
-    name: scoop-bucket
-  homepage: https://nfpm.goreleaser.com
-  folder: bucket
-  description: nFPM is a simple, 0-dependencies, deb, rpm and apk packager.
-  license: MIT
+  - bucket:
+      owner: goreleaser
+      name: scoop-bucket
+    homepage: https://nfpm.goreleaser.com
+    folder: bucket
+    description: nFPM is a simple, 0-dependencies, deb, rpm and apk packager.
+    license: MIT
 
 nfpms:
-- file_name_template: '{{ .ConventionalFileName }}'
-  homepage: https://nfpm.goreleaser.com
-  description: |-
-    Simple, 0-dependencies, deb, rpm and apk packager.
-    nFPM (not FPM) is configurable via YAML and does not need any
-    packaging software installed.
-  maintainer: Carlos Alexandro Becker <carlos@becker.software>
-  license: MIT
-  vendor: GoReleaser
-  formats:
-  - apk
-  - deb
-  - rpm
-  bindir: /usr/bin
-  section: utils
-  contents:
-    - src: ./completions/nfpm.bash
-      dst: /usr/share/bash-completion/completions/nfpm
-      file_info:
-        mode: 0644
-    - src: ./completions/nfpm.fish
-      dst: /usr/share/fish/vendor_completions.d/nfpm.fish
-      file_info:
-        mode: 0644
-    - src: ./completions/nfpm.zsh
-      dst:  /usr/share/zsh/vendor-completions/_nfpm
-      file_info:
-        mode: 0644
-    - src: ./manpages/nfpm.1.gz
-      dst: /usr/share/man/man1/nfpm.1.gz
-      file_info:
-        mode: 0644
-    - src: ./LICENSE.md
-      dst: /usr/share/doc/nfpm/copyright
-      file_info:
-        mode: 0644
-    - src: .lintian-overrides
-      dst: ./usr/share/lintian/overrides/nfpm
-      packager: deb
-      file_info:
-        mode: 0644
+  - file_name_template: "{{ .ConventionalFileName }}"
+    homepage: https://nfpm.goreleaser.com
+    description: |-
+      Simple, 0-dependencies, deb, rpm and apk packager.
+      nFPM (not FPM) is configurable via YAML and does not need any
+      packaging software installed.
+    maintainer: Carlos Alexandro Becker <carlos@becker.software>
+    license: MIT
+    vendor: GoReleaser
+    formats:
+      - apk
+      - deb
+      - rpm
+    bindir: /usr/bin
+    section: utils
+    contents:
+      - src: ./completions/nfpm.bash
+        dst: /usr/share/bash-completion/completions/nfpm
+        file_info:
+          mode: 0644
+      - src: ./completions/nfpm.fish
+        dst: /usr/share/fish/vendor_completions.d/nfpm.fish
+        file_info:
+          mode: 0644
+      - src: ./completions/nfpm.zsh
+        dst: /usr/share/zsh/vendor-completions/_nfpm
+        file_info:
+          mode: 0644
+      - src: ./manpages/nfpm.1.gz
+        dst: /usr/share/man/man1/nfpm.1.gz
+        file_info:
+          mode: 0644
+      - src: ./LICENSE.md
+        dst: /usr/share/doc/nfpm/copyright
+        file_info:
+          mode: 0644
+      - src: .lintian-overrides
+        dst: ./usr/share/lintian/overrides/nfpm
+        packager: deb
+        file_info:
+          mode: 0644
 
 aurs:
-  - homepage:  https://nfpm.goreleaser.com
+  - homepage: https://nfpm.goreleaser.com
     description: nFPM is a simple, 0-dependencies, deb, rpm and apk packager.
     maintainers:
-      - 'Fernandez Ludovic <lfernandez dot dev at gmail dot com>'
-      - 'Carlos Alexandro Becker <carlos at becker dot software>'
+      - "Fernandez Ludovic <lfernandez dot dev at gmail dot com>"
+      - "Carlos Alexandro Becker <carlos at becker dot software>"
     license: MIT
-    private_key: '{{ .Env.AUR_KEY }}'
-    git_url: 'ssh://aur@aur.archlinux.org/nfpm-bin.git'
+    private_key: "{{ .Env.AUR_KEY }}"
+    git_url: "ssh://aur@aur.archlinux.org/nfpm-bin.git"
     package: |-
       # bin
       install -Dm755 "./nfpm" "${pkgdir}/usr/bin/nfpm"
@@ -191,65 +191,65 @@ furies:
   - account: goreleaser
 
 sboms:
-- artifacts: archive
+  - artifacts: archive
 
 signs:
-- cmd: cosign
-  certificate: '${artifact}.pem'
-  output: true
-  artifacts: checksum
-  args:
-    - sign-blob
-    - '--output-certificate=${certificate}'
-    - '--output-signature=${signature}'
-    - '${artifact}'
-    - --yes
+  - cmd: cosign
+    certificate: "${artifact}.pem"
+    output: true
+    artifacts: checksum
+    args:
+      - sign-blob
+      - "--output-certificate=${certificate}"
+      - "--output-signature=${signature}"
+      - "${artifact}"
+      - --yes
 
 docker_signs:
-- cmd: cosign
-  artifacts: manifests
-  output: true
-  args:
-  - 'sign'
-  - '${artifact}'
-  - --yes
+  - cmd: cosign
+    artifacts: manifests
+    output: true
+    args:
+      - "sign"
+      - "${artifact}"
+      - --yes
 
 changelog:
   sort: asc
   use: github
   filters:
     exclude:
-    - '^test:'
-    - '^chore'
-    - 'merge conflict'
-    - Merge pull request
-    - Merge remote-tracking branch
-    - Merge branch
-    - go mod tidy
+      - "^test:"
+      - "^chore"
+      - "merge conflict"
+      - Merge pull request
+      - Merge remote-tracking branch
+      - Merge branch
+      - go mod tidy
   groups:
     - title: Dependency updates
       regexp: '^.*?(feat|fix)\(deps\)!?:.+$'
       order: 300
-    - title: 'New Features'
+    - title: "New Features"
       regexp: '^.*?feat(\([[:word:]]+\))??!?:.+$'
       order: 100
-    - title: 'Security updates'
+    - title: "Security updates"
       regexp: '^.*?sec(\([[:word:]]+\))??!?:.+$'
       order: 150
-    - title: 'Bug fixes'
+    - title: "Bug fixes"
       regexp: '^.*?fix(\([[:word:]]+\))??!?:.+$'
       order: 200
-    - title: 'Documentation updates'
+    - title: "Documentation updates"
       regexp: ^.*?doc(\([[:word:]]+\))??!?:.+$
       order: 400
-    - title: 'Build process updates'
+    - title: "Build process updates"
       regexp: ^.*?build(\([[:word:]]+\))??!?:.+$
       order: 400
     - title: Other work
       order: 9999
 
 checksum:
-  name_template: 'checksums.txt'
+  name_template: "checksums.txt"
 
 release:
   footer: |
@@ -270,7 +270,6 @@ release:
 
     <a href="https://goreleaser.com"><img src="https://raw.githubusercontent.com/goreleaser/artwork/master/opencollective-header.png" with="100%" alt="GoReleaser logo"></a>
 
-
 milestones:
   - close: true
 
diff --git a/README.md b/README.md
index 2c5b0a8..a9e61ab 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,7 @@
 <p align="center">
   <img alt="GoReleaser Logo" src="https://avatars2.githubusercontent.com/u/24697112?v=3&s=200" height="140" />
   <h3 align="center">nFPM</h3>
-  <p align="center">nFPM is a simple, 0-dependencies, deb, rpm and apk packager.</p>
+  <p align="center">nFPM is a simple and 0-dependencies deb, rpm, apk and arch linux packager written in Go</p>
   <p align="center">
     <a href="https://github.com/goreleaser/nfpm/releases/latest"><img alt="Release" src="https://img.shields.io/github/release/goreleaser/nfpm.svg?style=for-the-badge"></a>
     <a href="/LICENSE.md"><img alt="Software License" src="https://img.shields.io/badge/license-MIT-brightgreen.svg?style=for-the-badge"></a>
diff --git a/Taskfile.yml b/Taskfile.yml
index 3fe5916..35ffc83 100644
--- a/Taskfile.yml
+++ b/Taskfile.yml
@@ -1,6 +1,6 @@
 # https://taskfile.dev
 
-version: '3'
+version: "3"
 
 env:
   GO111MODULE: on
@@ -30,6 +30,25 @@ tasks:
     cmds:
       - go build ./cmd/nfpm
 
+  acceptance:windows:package:
+    desc: Build packages for testing
+    vars:
+      SRC: "./testdata/acceptance/core.complex.yaml"
+    cmds:
+      - mkdir -p ./dist
+      - go run ./cmd/nfpm/... pkg -f {{.SRC}} -p deb -t ./dist/foo.deb
+      - go run ./cmd/nfpm/... pkg -f {{.SRC}} -p rpm -t ./dist/foo.rpm
+      - go run ./cmd/nfpm/... pkg -f {{.SRC}} -p apk -t ./dist/foo.apk
+      - go run ./cmd/nfpm/... pkg -f {{.SRC}} -p archlinux -t ./dist/foo.pkg.tar.zst
+
+  acceptance:windows:install:
+    desc: Install packages built with package
+    cmds:
+      - docker run --rm --workdir /tmp -v $PWD/dist:/tmp fedora rpm -ivh foo.rpm
+      - docker run --rm --workdir /tmp -v $PWD/dist:/tmp ubuntu dpkg -i foo.deb
+      - docker run --rm --workdir /tmp -v $PWD/dist:/tmp archlinux pacman --noconfirm -U foo.pkg.tar.zst
+      - docker run --rm --workdir /tmp -v $PWD/dist:/tmp alpine apk add --allow-untrusted foo.apk
+
   acceptance:pull:
     desc: Pull acceptance test images
     vars:
@@ -116,13 +135,13 @@ tasks:
     desc: Start documentation server
     cmds:
       - task: docs:generate
-      - '{{.DOCKER}} run --rm -it -p 8000:8000 -v ${PWD}/www:/docs docker.io/squidfunk/mkdocs-material'
+      - "{{.DOCKER}} run --rm -it -p 8000:8000 -v ${PWD}/www:/docs docker.io/squidfunk/mkdocs-material"
 
   docs:build:
     desc: Build docs
     cmds:
       - task: docs:generate
-      - '{{.DOCKER}} run --rm -v ${PWD}/www:/docs docker.io/squidfunk/mkdocs-material build'
+      - "{{.DOCKER}} run --rm -v ${PWD}/www:/docs docker.io/squidfunk/mkdocs-material build"
 
   release:
     desc: Create a new tag
diff --git a/apk/apk.go b/apk/apk.go
index e285a6f..39c1798 100644
--- a/apk/apk.go
+++ b/apk/apk.go
@@ -401,26 +401,24 @@ func createBuilderData(info *nfpm.Info, sizep *int64) func(tw *tar.Writer) error
 
 func createFilesInsideTarGz(info *nfpm.Info, tw *tar.Writer, sizep *int64) (err error) {
 	for _, file := range info.Contents {
-		file.Destination = strings.TrimPrefix(file.Destination, "./")
+		file.Destination = files.AsRelativePath(file.Destination)
 
 		switch file.Type {
 		case files.TypeDir, files.TypeImplicitDir:
 			err = tw.WriteHeader(&tar.Header{
-				Name:     files.AsRelativePath(file.Destination),
+				Name:     file.Destination,
 				Mode:     int64(file.FileInfo.Mode),
 				Typeflag: tar.TypeDir,
-				Format:   tar.FormatGNU,
 				Uname:    file.FileInfo.Owner,
 				Gname:    file.FileInfo.Group,
 				ModTime:  file.FileInfo.MTime,
 			})
 		case files.TypeSymlink:
 			err = newItemInsideTarGz(tw, []byte{}, &tar.Header{
-				Name:     files.AsRelativePath(file.Destination),
+				Name:     file.Destination,
 				Linkname: file.Source,
 				Typeflag: tar.TypeSymlink,
 				ModTime:  file.FileInfo.MTime,
-				Format:   tar.FormatGNU,
 			})
 		default:
 			err = copyToTarAndDigest(file, tw, sizep)
diff --git a/arch/arch.go b/arch/arch.go
index f717b48..685219a 100644
--- a/arch/arch.go
+++ b/arch/arch.go
@@ -586,7 +586,7 @@ func writeScripts(w io.Writer, scripts map[string]string) error {
 			return err
 		}
 
-		fl.Close()
+		_ = fl.Close()
 
 		_, err = io.WriteString(w, "\n}\n\n")
 		if err != nil {
diff --git a/cmd/nfpm/art.txt b/cmd/nfpm/art.txt
new file mode 100644
index 0000000..e82214b
--- /dev/null
+++ b/cmd/nfpm/art.txt
@@ -0,0 +1,5 @@
+       _____ ____  __  __
+ _ __ |  ___|  _ \|  \/  |
+| '_ \| |_  | |_) | |\/| |
+| | | |  _| |  __/| |  | |
+|_| |_|_|   |_|   |_|  |_|
diff --git a/cmd/nfpm/main.go b/cmd/nfpm/main.go
index 29b2a77..28462c7 100644
--- a/cmd/nfpm/main.go
+++ b/cmd/nfpm/main.go
@@ -1,42 +1,56 @@
 package main
 
 import (
-	"fmt"
 	"os"
-	"runtime/debug"
 
+	_ "embed"
+
+	goversion "github.com/caarlos0/go-version"
 	"github.com/goreleaser/nfpm/v2/internal/cmd"
 )
 
 // nolint: gochecknoglobals
 var (
-	version = "dev"
-	commit  = ""
-	date    = ""
-	builtBy = ""
+	version   = "dev"
+	treeState = ""
+	commit    = ""
+	date      = ""
+	builtBy   = ""
 )
 
+const website = "https://nfpm.goreleaser.com"
+
+//go:embed art.txt
+var asciiArt string
+
 func main() {
 	cmd.Execute(
-		buildVersion(version, commit, date, builtBy),
+		buildVersion(version, commit, date, builtBy, treeState),
 		os.Exit,
 		os.Args[1:],
 	)
 }
 
-func buildVersion(version, commit, date, builtBy string) string {
-	result := "nfpm version " + version
-	if commit != "" {
-		result = fmt.Sprintf("%s\ncommit: %s", result, commit)
-	}
-	if date != "" {
-		result = fmt.Sprintf("%s\nbuilt at: %s", result, date)
-	}
-	if builtBy != "" {
-		result = fmt.Sprintf("%s\nbuilt by: %s", result, builtBy)
-	}
-	if info, ok := debug.ReadBuildInfo(); ok && info.Main.Sum != "" {
-		result = fmt.Sprintf("%s\nmodule version: %s, checksum: %s", result, info.Main.Version, info.Main.Sum)
-	}
-	return result
+func buildVersion(version, commit, date, builtBy, treeState string) goversion.Info {
+	return goversion.GetVersionInfo(
+		goversion.WithAppDetails("nfpm", "a simple and 0-dependencies deb, rpm, apk and arch linux packager written in Go", website),
+		goversion.WithASCIIName(asciiArt),
+		func(i *goversion.Info) {
+			if commit != "" {
+				i.GitCommit = commit
+			}
+			if treeState != "" {
+				i.GitTreeState = treeState
+			}
+			if date != "" {
+				i.BuildDate = date
+			}
+			if version != "" {
+				i.GitVersion = version
+			}
+			if builtBy != "" {
+				i.BuiltBy = builtBy
+			}
+		},
+	)
 }
diff --git a/deb/deb.go b/deb/deb.go
index fe6640b..7ee344b 100644
--- a/deb/deb.go
+++ b/deb/deb.go
@@ -451,7 +451,10 @@ func withChangelogIfRequested(info *nfpm.Info) *nfpm.Info {
 	return info
 }
 
-func createChangelogInsideDataTar(tarw *tar.Writer, g io.Writer, info *nfpm.Info,
+func createChangelogInsideDataTar(
+	tarw *tar.Writer,
+	g io.Writer,
+	info *nfpm.Info,
 	fileName string,
 ) (int64, error) {
 	var buf bytes.Buffer
@@ -486,7 +489,12 @@ func createChangelogInsideDataTar(tarw *tar.Writer, g io.Writer, info *nfpm.Info
 		return 0, err
 	}
 
-	if _, err = fmt.Fprintf(g, "%x  %s\n", digest.Sum(nil), files.AsExplicitRelativePath(fileName)); err != nil {
+	if _, err = fmt.Fprintf(
+		g,
+		"%x  %s\n",
+		digest.Sum(nil),
+		files.AsExplicitRelativePath(fileName),
+	); err != nil {
 		return 0, err
 	}
 
@@ -649,7 +657,7 @@ func conffiles(info *nfpm.Info) []byte {
 	for _, file := range info.Contents {
 		switch file.Type {
 		case files.TypeConfig, files.TypeConfigNoReplace:
-			confs = append(confs, filepath.Join("/", file.Destination))
+			confs = append(confs, files.NormalizeAbsoluteFilePath(file.Destination))
 		}
 	}
 	return []byte(strings.Join(confs, "\n") + "\n")
diff --git a/files/files.go b/files/files.go
index 87156e8..829d164 100644
--- a/files/files.go
+++ b/files/files.go
@@ -283,7 +283,11 @@ func PrepareForPackager(rawContents Contents, umask fs.FileMode, packager string
 				return nil, fmt.Errorf("add tree: %w", err)
 			}
 		case TypeConfig, TypeConfigNoReplace, TypeFile, "":
-			globbed, err := glob.Glob(content.Source, content.Destination, disableGlobbing)
+			globbed, err := glob.Glob(
+				filepath.ToSlash(content.Source),
+				filepath.ToSlash(content.Destination),
+				disableGlobbing,
+			)
 			if err != nil {
 				return nil, err
 			}
@@ -474,7 +478,7 @@ func addTree(all map[string]*Content, tree *Content, umask os.FileMode) error {
 				return err
 			}
 
-			c.Source = linkDestination
+			c.Source = filepath.ToSlash(strings.TrimPrefix(linkDestination, filepath.VolumeName(linkDestination)))
 			c.Destination = NormalizeAbsoluteFilePath(destination)
 			c.Type = TypeSymlink
 		default:
@@ -515,42 +519,20 @@ func ToNixPath(path string) string {
 }
 
 // As relative path converts a path to an explicitly relative path starting with
-// a dot (e.g. it converts /foo -> ./foo).
+// a dot (e.g. it converts /foo -> ./foo and foo -> ./foo).
 func AsExplicitRelativePath(path string) string {
-	if path == "/" {
-		return "./"
-	}
-
-	cleanedPath := filepath.Clean(path)
-
-	end := ""
-	if len(cleanedPath) > 1 && strings.HasSuffix(path, "/") {
-		end = "/"
-	}
-
-	if !filepath.IsAbs(cleanedPath) {
-		cleanedPath = filepath.Join("/", cleanedPath)
-	}
-
-	return "." + cleanedPath + end
+	return "./" + AsRelativePath(path)
 }
 
 // AsRelativePath converts a path to a relative path without a "./" prefix. This
 // function leaves trailing slashes to indicate that the path refers to a
-// directory.
+// directory, and converts the path to Unix path.
 func AsRelativePath(path string) string {
-	cleanedPath := filepath.Clean(path)
-
-	end := ""
+	cleanedPath := strings.TrimLeft(ToNixPath(path), "/")
 	if len(cleanedPath) > 1 && strings.HasSuffix(path, "/") {
-		end = "/"
+		return cleanedPath + "/"
 	}
-
-	if filepath.IsAbs(cleanedPath) {
-		return strings.TrimLeft(cleanedPath, "/") + end
-	}
-
-	return cleanedPath + end
+	return cleanedPath
 }
 
 // NormalizeAbsoluteFilePath returns an absolute cleaned path separated by
diff --git a/go.mod b/go.mod
index ae72312..996c372 100644
--- a/go.mod
+++ b/go.mod
@@ -2,22 +2,20 @@ module github.com/goreleaser/nfpm/v2
 
 go 1.19
 
-// DO NOT UPDATE THIS!! needs to be v0.0.0-20210512092938-c05353c2d58c
-require github.com/ProtonMail/go-crypto v0.0.0-20210512092938-c05353c2d58c
-
 require (
 	github.com/AlekSi/pointer v1.2.0
 	github.com/Masterminds/semver/v3 v3.2.1
-	github.com/ProtonMail/gopenpgp/v2 v2.2.2
+	github.com/ProtonMail/go-crypto v0.0.0-20230626094100-7e9e0395ebec
+	github.com/ProtonMail/gopenpgp/v2 v2.7.1
 	github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb
 	github.com/caarlos0/go-rpmutils v0.2.1-0.20211112020245-2cd62ff89b11
-	github.com/cavaliergopher/cpio v1.0.1
-	github.com/google/go-cmp v0.5.9
-	github.com/goreleaser/chglog v0.4.2
+	github.com/caarlos0/go-version v0.1.1
+	github.com/google/rpmpack v0.5.0
+	github.com/goreleaser/chglog v0.5.0
 	github.com/goreleaser/fileglob v1.3.0
 	github.com/imdario/mergo v0.3.16
 	github.com/invopop/jsonschema v0.7.0
-	github.com/klauspost/compress v1.16.5
+	github.com/klauspost/compress v1.16.7
 	github.com/klauspost/pgzip v1.2.6
 	github.com/muesli/mango-cobra v1.2.0
 	github.com/muesli/roff v0.1.0
@@ -31,39 +29,44 @@ require (
 require (
 	github.com/DataDog/zstd v1.4.5 // indirect
 	github.com/Masterminds/goutils v1.1.1 // indirect
-	github.com/Masterminds/semver v1.5.0 // indirect
-	github.com/Masterminds/sprig v2.22.0+incompatible // indirect
-	github.com/Microsoft/go-winio v0.5.1 // indirect
-	github.com/ProtonMail/go-mime v0.0.0-20190923161245-9b5a4261663a // indirect
+	github.com/Masterminds/sprig/v3 v3.2.3 // indirect
+	github.com/Microsoft/go-winio v0.5.2 // indirect
+	github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f // indirect
+	github.com/acomagu/bufpipe v1.0.4 // indirect
+	github.com/cavaliergopher/cpio v1.0.1 // indirect
+	github.com/cloudflare/circl v1.3.3 // indirect
 	github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
 	github.com/davecgh/go-spew v1.1.1 // indirect
-	github.com/emirpasic/gods v1.12.0 // indirect
-	github.com/go-git/gcfg v1.5.0 // indirect
-	github.com/go-git/go-billy/v5 v5.3.1 // indirect
-	github.com/go-git/go-git/v5 v5.2.0 // indirect
+	github.com/emirpasic/gods v1.18.1 // indirect
+	github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
+	github.com/go-git/go-billy/v5 v5.4.1 // indirect
+	github.com/go-git/go-git/v5 v5.7.0 // indirect
 	github.com/gobwas/glob v0.2.3 // indirect
+	github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
 	github.com/google/uuid v1.3.0 // indirect
-	github.com/huandu/xstrings v1.3.2 // indirect
+	github.com/huandu/xstrings v1.3.3 // indirect
 	github.com/iancoleman/orderedmap v0.0.0-20190318233801-ac98e3ecb4b0 // indirect
 	github.com/inconshreveable/mousetrap v1.1.0 // indirect
 	github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
-	github.com/kevinburke/ssh_config v1.1.0 // indirect
+	github.com/kevinburke/ssh_config v1.2.0 // indirect
 	github.com/mitchellh/copystructure v1.2.0 // indirect
-	github.com/mitchellh/go-homedir v1.1.0 // indirect
 	github.com/mitchellh/reflectwalk v1.0.2 // indirect
 	github.com/muesli/mango v0.1.0 // indirect
 	github.com/muesli/mango-pflag v0.1.0 // indirect
+	github.com/pjbgf/sha1cd v0.3.0 // indirect
 	github.com/pkg/errors v0.9.1 // indirect
 	github.com/pmezard/go-difflib v1.0.0 // indirect
 	github.com/russross/blackfriday/v2 v2.1.0 // indirect
 	github.com/sergi/go-diff v1.2.0 // indirect
-	github.com/sirupsen/logrus v1.7.0 // indirect
+	github.com/shopspring/decimal v1.2.0 // indirect
+	github.com/skeema/knownhosts v1.1.1 // indirect
+	github.com/spf13/cast v1.5.1 // indirect
 	github.com/spf13/pflag v1.0.5 // indirect
-	github.com/xanzy/ssh-agent v0.3.1 // indirect
+	github.com/xanzy/ssh-agent v0.3.3 // indirect
 	gitlab.com/digitalxero/go-conventional-commit v1.0.7 // indirect
-	golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e // indirect
-	golang.org/x/net v0.7.0 // indirect
-	golang.org/x/sys v0.5.0 // indirect
-	golang.org/x/text v0.7.0 // indirect
+	golang.org/x/crypto v0.9.0 // indirect
+	golang.org/x/net v0.10.0 // indirect
+	golang.org/x/sys v0.8.0 // indirect
+	golang.org/x/text v0.9.0 // indirect
 	gopkg.in/warnings.v0 v0.1.2 // indirect
 )
diff --git a/go.sum b/go.sum
index 35f3984..8a05984 100644
--- a/go.sum
+++ b/go.sum
@@ -1,76 +1,81 @@
 github.com/AlekSi/pointer v1.2.0 h1:glcy/gc4h8HnG2Z3ZECSzZ1IX1x2JxRVuDzaJwQE0+w=
 github.com/AlekSi/pointer v1.2.0/go.mod h1:gZGfd3dpW4vEc/UlyfKKi1roIqcCgwOIvb0tSNSBle0=
-github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
 github.com/DataDog/zstd v1.4.5 h1:EndNeuB0l9syBZhut0wns3gV1hL8zX8LIu6ZiVHWLIQ=
 github.com/DataDog/zstd v1.4.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo=
 github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
 github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
-github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
-github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
+github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
 github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0=
 github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
-github.com/Masterminds/sprig v2.22.0+incompatible h1:z4yfnGrZ7netVz+0EDJ0Wi+5VZCSYp4Z0m2dk6cEM60=
-github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o=
-github.com/Microsoft/go-winio v0.5.0/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84=
-github.com/Microsoft/go-winio v0.5.1 h1:aPJp2QD7OOrhO5tQXqQoGSJc+DjDtWTGLOmNyAm6FgY=
-github.com/Microsoft/go-winio v0.5.1/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84=
-github.com/ProtonMail/go-crypto v0.0.0-20210512092938-c05353c2d58c h1:bNpaLLv2Y4kslsdkdCwAYu8Bak1aGVtxwi8Z/wy4Yuo=
+github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA=
+github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM=
+github.com/Microsoft/go-winio v0.5.2 h1:a9IhgEQBCUEk6QCdml9CiJGhAws+YwffDHEMp1VMrpA=
+github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
 github.com/ProtonMail/go-crypto v0.0.0-20210512092938-c05353c2d58c/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo=
-github.com/ProtonMail/go-mime v0.0.0-20190923161245-9b5a4261663a h1:W6RrgN/sTxg1msqzFFb+G80MFmpjMw61IU+slm+wln4=
-github.com/ProtonMail/go-mime v0.0.0-20190923161245-9b5a4261663a/go.mod h1:NYt+V3/4rEeDuaev/zw1zCq8uqVEuPHzDPo3OZrlGJ4=
-github.com/ProtonMail/gopenpgp/v2 v2.2.2 h1:u2m7xt+CZWj88qK1UUNBoXeJCFJwJCZ/Ff4ymGoxEXs=
-github.com/ProtonMail/gopenpgp/v2 v2.2.2/go.mod h1:ajUlBGvxMH1UBZnaYO3d1FSVzjiC6kK9XlZYGiDCvpM=
-github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7 h1:uSoVVbwJiQipAclBbw+8quDsfcvFjOpI5iCf4p/cqCs=
-github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs=
-github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA=
-github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
+github.com/ProtonMail/go-crypto v0.0.0-20230321155629-9a39f2531310/go.mod h1:8TI4H3IbrackdNgv+92dI+rhpCaLqM0IfpgCgenFvRE=
+github.com/ProtonMail/go-crypto v0.0.0-20230626094100-7e9e0395ebec h1:vV3RryLxt42+ZIVOFbYJCH1jsZNTNmj2NYru5zfx+4E=
+github.com/ProtonMail/go-crypto v0.0.0-20230626094100-7e9e0395ebec/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0=
+github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f h1:tCbYj7/299ekTTXpdwKYF8eBlsYsDVoggDAuAjoK66k=
+github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f/go.mod h1:gcr0kNtGBqin9zDW9GOHcVntrwnjrK+qdJ06mWYBybw=
+github.com/ProtonMail/gopenpgp/v2 v2.7.1 h1:Awsg7MPc2gD3I7IFac2qE3Gdls0lZW8SzrFZ3k1oz0s=
+github.com/ProtonMail/gopenpgp/v2 v2.7.1/go.mod h1:/BU5gfAVwqyd8EfC3Eu7zmuhwYQpKs+cGD8M//iiaxs=
+github.com/acomagu/bufpipe v1.0.4 h1:e3H4WUzM3npvo5uv95QuJM3cQspFNtFBzvJ2oNjKIDQ=
+github.com/acomagu/bufpipe v1.0.4/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4=
+github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
 github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
-github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
 github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb h1:m935MPodAbYS46DG4pJSv7WO+VECIWUQ7OJYSoTrMh4=
 github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb/go.mod h1:PkYb9DJNAwrSvRx5DYA+gUcOIgTGVMNkfSCbZM8cWpI=
+github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
+github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
 github.com/caarlos0/go-rpmutils v0.2.1-0.20211112020245-2cd62ff89b11 h1:IRrDwVlWQr6kS1U8/EtyA1+EHcc4yl8pndcqXWrEamg=
 github.com/caarlos0/go-rpmutils v0.2.1-0.20211112020245-2cd62ff89b11/go.mod h1:je2KZ+LxaCNvCoKg32jtOIULcFogJKcL1ZWUaIBjKj0=
+github.com/caarlos0/go-version v0.1.1 h1:1bikKHkGGVIIxqCmufhSSs3hpBScgHGacrvsi8FuIfc=
+github.com/caarlos0/go-version v0.1.1/go.mod h1:Ze5Qx4TsBBi5FyrSKVg1Ibc44KGV/llAaKGp86oTwZ0=
 github.com/caarlos0/testfs v0.4.4 h1:3PHvzHi5Lt+g332CiShwS8ogTgS3HjrmzZxCm6JCDr8=
 github.com/caarlos0/testfs v0.4.4/go.mod h1:bRN55zgG4XCUVVHZCeU+/Tz1Q6AxEJOEJTliBy+1DMk=
 github.com/cavaliergopher/cpio v1.0.1 h1:KQFSeKmZhv0cr+kawA3a0xTQCU4QxXF1vhU7P7av2KM=
 github.com/cavaliergopher/cpio v1.0.1/go.mod h1:pBdaqQjnvXxdS/6CvNDwIANIFSP0xRKI16PX4xejRQc=
+github.com/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I=
+github.com/cloudflare/circl v1.3.3 h1:fE/Qz0QdIGqeWfnwq0RE0R7MI51s0M2E4Ga9kq5AEMs=
+github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
 github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
 github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
 github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg=
-github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
-github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
-github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0=
-github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
-github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4=
-github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E=
-github.com/go-git/go-billy/v5 v5.0.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0=
-github.com/go-git/go-billy/v5 v5.3.1 h1:CPiOUAzKtMRvolEKw+bG1PLRpT7D3LIs3/3ey4Aiu34=
-github.com/go-git/go-billy/v5 v5.3.1/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0=
-github.com/go-git/go-git-fixtures/v4 v4.0.2-0.20200613231340-f56387b50c12 h1:PbKy9zOy4aAKrJ5pibIRpVO2BXnK1Tlcg+caKI7Ox5M=
-github.com/go-git/go-git-fixtures/v4 v4.0.2-0.20200613231340-f56387b50c12/go.mod h1:m+ICp2rF3jDhFgEZ/8yziagdT1C+ZpZcrJjappBCDSw=
-github.com/go-git/go-git/v5 v5.2.0 h1:YPBLG/3UK1we1ohRkncLjaXWLW+HKp5QNM/jTli2JgI=
-github.com/go-git/go-git/v5 v5.2.0/go.mod h1:kh02eMX+wdqqxgNMEyq8YgwlIOsDOa9homkUq1PoTMs=
+github.com/elazarl/goproxy v0.0.0-20221015165544-a0805db90819 h1:RIB4cRk+lBqKK3Oy0r2gRX4ui7tuhiZq2SuTtTCi0/0=
+github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
+github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
+github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY=
+github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY=
+github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
+github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
+github.com/go-git/go-billy/v5 v5.4.1 h1:Uwp5tDRkPr+l/TnbHOQzp+tmJfLceOlbVucgpTz8ix4=
+github.com/go-git/go-billy/v5 v5.4.1/go.mod h1:vjbugF6Fz7JIflbVpl1hJsGjSHNltrSw45YK/ukIvQg=
+github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20230305113008-0c11038e723f h1:Pz0DHeFij3XFhoBRGUDPzSJ+w2UcK5/0JvF8DRI58r8=
+github.com/go-git/go-git/v5 v5.7.0 h1:t9AudWVLmqzlo+4bqdf7GY+46SUuRsx59SboFxkq2aE=
+github.com/go-git/go-git/v5 v5.7.0/go.mod h1:coJHKEOk5kUClpsNlXrUvPrDxY3w3gjHvhcZd8Fodw8=
 github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
 github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
-github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
+github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
-github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/rpmpack v0.5.0 h1:L16KZ3QvkFGpYhmp23iQip+mx1X39foEsqszjMNBm8A=
+github.com/google/rpmpack v0.5.0/go.mod h1:uqVAUVQLq8UY2hCDfmJ/+rtO3aw7qyhc90rCVEabEfI=
+github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
 github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
-github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
-github.com/goreleaser/chglog v0.4.2 h1:afmbT1d7lX/q+GF8wv3a1Dofs2j/Y9YkiCpGemWR6mI=
-github.com/goreleaser/chglog v0.4.2/go.mod h1:u/F03un4hMCQrp65qSWCkkC6T+G7YLKZ+AM2mITE47s=
+github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g=
+github.com/goreleaser/chglog v0.5.0 h1:Sk6BMIpx8+vpAf8KyPit34OgWui8c7nKTMHhYx88jJ4=
+github.com/goreleaser/chglog v0.5.0/go.mod h1:Ri46M3lrMuv76FHszs3vtABR8J8k1w9JHYAzxeeOl28=
 github.com/goreleaser/fileglob v1.3.0 h1:/X6J7U8lbDpQtBvGcwwPS6OpzkNVlVEsFUVRx9+k+7I=
 github.com/goreleaser/fileglob v1.3.0/go.mod h1:Jx6BoXv3mbYkEzwm9THo7xbr5egkAraxkGorbJb4RxU=
-github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw=
-github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
+github.com/huandu/xstrings v1.3.3 h1:/Gcsuc1x8JVbJ9/rlye4xZnVAbEkGauT8lbebqcQws4=
+github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
 github.com/iancoleman/orderedmap v0.0.0-20190318233801-ac98e3ecb4b0 h1:i462o439ZjprVSFSZLZxcsoAe592sZB1rci2Z8j4wdk=
 github.com/iancoleman/orderedmap v0.0.0-20190318233801-ac98e3ecb4b0/go.mod h1:N0Wam8K1arqPXNWjMo21EXnBPOPp36vB07FNRdD2geA=
-github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
+github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
 github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4=
 github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY=
 github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
@@ -79,28 +84,28 @@ github.com/invopop/jsonschema v0.7.0 h1:2vgQcBz1n256N+FpX3Jq7Y17AjYt46Ig3zIWyy77
 github.com/invopop/jsonschema v0.7.0/go.mod h1:O9uiLokuu0+MGFlyiaqtWxwqJm41/+8Nj0lD7A36YH0=
 github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
 github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
-github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
 github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
-github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
-github.com/kevinburke/ssh_config v1.1.0 h1:pH/t1WS9NzT8go394IqZeJTMHVm6Cr6ZJ6AQ+mdNo/o=
-github.com/kevinburke/ssh_config v1.1.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
+github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
+github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
 github.com/klauspost/compress v1.11.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
-github.com/klauspost/compress v1.16.5 h1:IFV2oUNUzZaz+XyusxpLzpzS8Pt5rh0Z16For/djlyI=
-github.com/klauspost/compress v1.16.5/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
+github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I=
+github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
 github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU=
 github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
-github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
+github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
 github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
 github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
 github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
+github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA=
 github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE=
 github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
+github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
 github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
 github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
-github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
-github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
+github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
 github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
 github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
 github.com/muesli/mango v0.1.0 h1:DZQK45d2gGbql1arsYA4vfg4d7I9Hfx5rX/GCmzsAvI=
@@ -111,111 +116,133 @@ github.com/muesli/mango-pflag v0.1.0 h1:UADqbYgpUyRoBja3g6LUL+3LErjpsOwaC9ywvBWe
 github.com/muesli/mango-pflag v0.1.0/go.mod h1:YEQomTxaCUp8PrbhFh10UfbhbQrM/xJ4i2PB8VTLLW0=
 github.com/muesli/roff v0.1.0 h1:YD0lalCotmYuF5HhZliKWlIx7IEhiXeSfq7hNjFqGF8=
 github.com/muesli/roff v0.1.0/go.mod h1:pjAHQM9hdUUwm/krAfrLGgJkXJ+YuhtsfZ42kieB2Ig=
-github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
 github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
-github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4=
+github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI=
 github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
 github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
 github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
 github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
-github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
 github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ=
 github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
-github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
-github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM=
+github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=
+github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
 github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
-github.com/smartystreets/assertions v1.2.0 h1:42S6lae5dvLc7BrLu/0ugRtcFVjoJNMC/N3yZFZkDFs=
-github.com/smartystreets/goconvey v1.7.2 h1:9RBaZCeXEQ3UselpuwUQHltGVXvdwm6cv1hgR6gDIPg=
+github.com/skeema/knownhosts v1.1.1 h1:MTk78x9FPgDFVFkDLTrsnnfCJl7g1C/nnKvePgrIngE=
+github.com/skeema/knownhosts v1.1.1/go.mod h1:g4fPeYpque7P0xefxtGzV81ihjC8sX2IqpAoNkjxbMo=
+github.com/smartystreets/assertions v1.13.1 h1:Ef7KhSmjZcK6AVf9YbJdvPYG9avaF0ZxudX+ThRdWfU=
+github.com/smartystreets/goconvey v1.8.0 h1:Oi49ha/2MURE0WexF052Z0m+BNSGirfjg5RL+JXWq3w=
+github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
+github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA=
+github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48=
 github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
 github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
 github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
 github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
 github.com/stretchr/testify v1.3.1-0.20190311161405-34c6fa2dc709/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
+github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
 github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
 github.com/ulikunitz/xz v0.5.9/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
 github.com/ulikunitz/xz v0.5.11 h1:kpFauv27b6ynzBNT/Xy+1k+fK4WswhN/6PN5WhFAGw8=
 github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
-github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4=
-github.com/xanzy/ssh-agent v0.3.1 h1:AmzO1SSWxw73zxFZPRwaMN1MohDw8UyHnmuxyceTEGo=
-github.com/xanzy/ssh-agent v0.3.1/go.mod h1:QIE4lCeL7nkC25x+yA3LBIYfwCc1TFziCtG7cBAac6w=
+github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
+github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
 github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo=
 github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos=
+github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
 gitlab.com/digitalxero/go-conventional-commit v1.0.7 h1:8/dO6WWG+98PMhlZowt/YjuiKhqhGlOCwlIV8SqqGh8=
 gitlab.com/digitalxero/go-conventional-commit v1.0.7/go.mod h1:05Xc2BFsSyC5tKhK0y+P3bs0AwUtNuTp+mTpbCU/DZ0=
 go.uber.org/goleak v1.1.10 h1:z+mqJhf6ss6BSfSM671tgKyZBFPTTJM+HLxnhPC3wu0=
 go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=
-golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
-golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
-golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
-golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e h1:T8NU3HyQ8ClP4SEE+KbFlg6n0NhuTsN4MyznaarGsZM=
-golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
-golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4=
-golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
-golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
+golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
+golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
+golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
+golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
+golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
+golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g=
+golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
 golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs=
 golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
-golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
-golang.org/x/mobile v0.0.0-20200801112145-973feb4309de/go.mod h1:skQtrUTUwhdJvXM/2KKJzY8pDgNr9I/FOMqDVRPBUS4=
-golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
 golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
-golang.org/x/mod v0.1.1-0.20191209134235-331c550502dd/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
+golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
 golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
-golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
-golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
+golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
+golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
+golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
+golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
+golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
+golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20190221075227-b4e8571b14e0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
+golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
+golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
-golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY=
+golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
+golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
+golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
+golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
+golang.org/x/term v0.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
+golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
+golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
 golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
+golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
+golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
+golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
-golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
 golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20200117012304-6edc0a871e69/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU=
+golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
+golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM=
+golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
-gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
 gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
 gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/internal/cmd/root.go b/internal/cmd/root.go
index 5e91eec..dd3097f 100644
--- a/internal/cmd/root.go
+++ b/internal/cmd/root.go
@@ -3,6 +3,7 @@ package cmd
 import (
 	"fmt"
 
+	goversion "github.com/caarlos0/go-version"
 	_ "github.com/goreleaser/nfpm/v2/apk"  // apk packager
 	_ "github.com/goreleaser/nfpm/v2/arch" // archlinux packager
 	_ "github.com/goreleaser/nfpm/v2/deb"  // deb packager
@@ -10,7 +11,7 @@ import (
 	"github.com/spf13/cobra"
 )
 
-func Execute(version string, exit func(int), args []string) {
+func Execute(version goversion.Info, exit func(int), args []string) {
 	newRootCmd(version, exit).Execute(args)
 }
 
@@ -28,20 +29,21 @@ func (cmd *rootCmd) Execute(args []string) {
 	}
 }
 
-func newRootCmd(version string, exit func(int)) *rootCmd {
+func newRootCmd(version goversion.Info, exit func(int)) *rootCmd {
 	root := &rootCmd{
 		exit: exit,
 	}
 	cmd := &cobra.Command{
 		Use:               "nfpm",
-		Short:             "Packages apps on RPM, Deb and APK formats based on a YAML configuration file",
-		Long:              `nFPM is a simple, 0-dependencies, deb, rpm and apk packager.`,
-		Version:           version,
+		Short:             "Packages apps on RPM, Deb, APK and Arch Linux formats based on a YAML configuration file",
+		Long:              `nFPM is a simple and 0-dependencies deb, rpm, apk and arch linux packager written in Go.`,
+		Version:           version.String(),
 		SilenceUsage:      true,
 		SilenceErrors:     true,
 		Args:              cobra.NoArgs,
 		ValidArgsFunction: cobra.NoFileCompletions,
 	}
+	cmd.SetVersionTemplate("{{.Version}}")
 
 	cmd.AddCommand(
 		newInitCmd().cmd,
diff --git a/internal/glob/glob.go b/internal/glob/glob.go
index e6fca62..b78fb05 100644
--- a/internal/glob/glob.go
+++ b/internal/glob/glob.go
@@ -20,7 +20,10 @@ func longestCommonPrefix(strs []string) string {
 	}
 	lcp := strs[0]
 	for _, str := range strs {
-		lcp = strlcp(lcp, str)
+		lcp = strlcp(
+			filepath.ToSlash(lcp),
+			filepath.ToSlash(str),
+		)
 	}
 	return lcp
 }
diff --git a/internal/rpmpack/LICENSE b/internal/rpmpack/LICENSE
deleted file mode 100644
index d645695..0000000
--- a/internal/rpmpack/LICENSE
+++ /dev/null
@@ -1,202 +0,0 @@
-
-                                 Apache License
-                           Version 2.0, January 2004
-                        http://www.apache.org/licenses/
-
-   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
-   1. Definitions.
-
-      "License" shall mean the terms and conditions for use, reproduction,
-      and distribution as defined by Sections 1 through 9 of this document.
-
-      "Licensor" shall mean the copyright owner or entity authorized by
-      the copyright owner that is granting the License.
-
-      "Legal Entity" shall mean the union of the acting entity and all
-      other entities that control, are controlled by, or are under common
-      control with that entity. For the purposes of this definition,
-      "control" means (i) the power, direct or indirect, to cause the
-      direction or management of such entity, whether by contract or
-      otherwise, or (ii) ownership of fifty percent (50%) or more of the
-      outstanding shares, or (iii) beneficial ownership of such entity.
-
-      "You" (or "Your") shall mean an individual or Legal Entity
-      exercising permissions granted by this License.
-
-      "Source" form shall mean the preferred form for making modifications,
-      including but not limited to software source code, documentation
-      source, and configuration files.
-
-      "Object" form shall mean any form resulting from mechanical
-      transformation or translation of a Source form, including but
-      not limited to compiled object code, generated documentation,
-      and conversions to other media types.
-
-      "Work" shall mean the work of authorship, whether in Source or
-      Object form, made available under the License, as indicated by a
-      copyright notice that is included in or attached to the work
-      (an example is provided in the Appendix below).
-
-      "Derivative Works" shall mean any work, whether in Source or Object
-      form, that is based on (or derived from) the Work and for which the
-      editorial revisions, annotations, elaborations, or other modifications
-      represent, as a whole, an original work of authorship. For the purposes
-      of this License, Derivative Works shall not include works that remain
-      separable from, or merely link (or bind by name) to the interfaces of,
-      the Work and Derivative Works thereof.
-
-      "Contribution" shall mean any work of authorship, including
-      the original version of the Work and any modifications or additions
-      to that Work or Derivative Works thereof, that is intentionally
-      submitted to Licensor for inclusion in the Work by the copyright owner
-      or by an individual or Legal Entity authorized to submit on behalf of
-      the copyright owner. For the purposes of this definition, "submitted"
-      means any form of electronic, verbal, or written communication sent
-      to the Licensor or its representatives, including but not limited to
-      communication on electronic mailing lists, source code control systems,
-      and issue tracking systems that are managed by, or on behalf of, the
-      Licensor for the purpose of discussing and improving the Work, but
-      excluding communication that is conspicuously marked or otherwise
-      designated in writing by the copyright owner as "Not a Contribution."
-
-      "Contributor" shall mean Licensor and any individual or Legal Entity
-      on behalf of whom a Contribution has been received by Licensor and
-      subsequently incorporated within the Work.
-
-   2. Grant of Copyright License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      copyright license to reproduce, prepare Derivative Works of,
-      publicly display, publicly perform, sublicense, and distribute the
-      Work and such Derivative Works in Source or Object form.
-
-   3. Grant of Patent License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      (except as stated in this section) patent license to make, have made,
-      use, offer to sell, sell, import, and otherwise transfer the Work,
-      where such license applies only to those patent claims licensable
-      by such Contributor that are necessarily infringed by their
-      Contribution(s) alone or by combination of their Contribution(s)
-      with the Work to which such Contribution(s) was submitted. If You
-      institute patent litigation against any entity (including a
-      cross-claim or counterclaim in a lawsuit) alleging that the Work
-      or a Contribution incorporated within the Work constitutes direct
-      or contributory patent infringement, then any patent licenses
-      granted to You under this License for that Work shall terminate
-      as of the date such litigation is filed.
-
-   4. Redistribution. You may reproduce and distribute copies of the
-      Work or Derivative Works thereof in any medium, with or without
-      modifications, and in Source or Object form, provided that You
-      meet the following conditions:
-
-      (a) You must give any other recipients of the Work or
-          Derivative Works a copy of this License; and
-
-      (b) You must cause any modified files to carry prominent notices
-          stating that You changed the files; and
-
-      (c) You must retain, in the Source form of any Derivative Works
-          that You distribute, all copyright, patent, trademark, and
-          attribution notices from the Source form of the Work,
-          excluding those notices that do not pertain to any part of
-          the Derivative Works; and
-
-      (d) If the Work includes a "NOTICE" text file as part of its
-          distribution, then any Derivative Works that You distribute must
-          include a readable copy of the attribution notices contained
-          within such NOTICE file, excluding those notices that do not
-          pertain to any part of the Derivative Works, in at least one
-          of the following places: within a NOTICE text file distributed
-          as part of the Derivative Works; within the Source form or
-          documentation, if provided along with the Derivative Works; or,
-          within a display generated by the Derivative Works, if and
-          wherever such third-party notices normally appear. The contents
-          of the NOTICE file are for informational purposes only and
-          do not modify the License. You may add Your own attribution
-          notices within Derivative Works that You distribute, alongside
-          or as an addendum to the NOTICE text from the Work, provided
-          that such additional attribution notices cannot be construed
-          as modifying the License.
-
-      You may add Your own copyright statement to Your modifications and
-      may provide additional or different license terms and conditions
-      for use, reproduction, or distribution of Your modifications, or
-      for any such Derivative Works as a whole, provided Your use,
-      reproduction, and distribution of the Work otherwise complies with
-      the conditions stated in this License.
-
-   5. Submission of Contributions. Unless You explicitly state otherwise,
-      any Contribution intentionally submitted for inclusion in the Work
-      by You to the Licensor shall be under the terms and conditions of
-      this License, without any additional terms or conditions.
-      Notwithstanding the above, nothing herein shall supersede or modify
-      the terms of any separate license agreement you may have executed
-      with Licensor regarding such Contributions.
-
-   6. Trademarks. This License does not grant permission to use the trade
-      names, trademarks, service marks, or product names of the Licensor,
-      except as required for reasonable and customary use in describing the
-      origin of the Work and reproducing the content of the NOTICE file.
-
-   7. Disclaimer of Warranty. Unless required by applicable law or
-      agreed to in writing, Licensor provides the Work (and each
-      Contributor provides its Contributions) on an "AS IS" BASIS,
-      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
-      implied, including, without limitation, any warranties or conditions
-      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
-      PARTICULAR PURPOSE. You are solely responsible for determining the
-      appropriateness of using or redistributing the Work and assume any
-      risks associated with Your exercise of permissions under this License.
-
-   8. Limitation of Liability. In no event and under no legal theory,
-      whether in tort (including negligence), contract, or otherwise,
-      unless required by applicable law (such as deliberate and grossly
-      negligent acts) or agreed to in writing, shall any Contributor be
-      liable to You for damages, including any direct, indirect, special,
-      incidental, or consequential damages of any character arising as a
-      result of this License or out of the use or inability to use the
-      Work (including but not limited to damages for loss of goodwill,
-      work stoppage, computer failure or malfunction, or any and all
-      other commercial damages or losses), even if such Contributor
-      has been advised of the possibility of such damages.
-
-   9. Accepting Warranty or Additional Liability. While redistributing
-      the Work or Derivative Works thereof, You may choose to offer,
-      and charge a fee for, acceptance of support, warranty, indemnity,
-      or other liability obligations and/or rights consistent with this
-      License. However, in accepting such obligations, You may act only
-      on Your own behalf and on Your sole responsibility, not on behalf
-      of any other Contributor, and only if You agree to indemnify,
-      defend, and hold each Contributor harmless for any liability
-      incurred by, or claims asserted against, such Contributor by reason
-      of your accepting any such warranty or additional liability.
-
-   END OF TERMS AND CONDITIONS
-
-   APPENDIX: How to apply the Apache License to your work.
-
-      To apply the Apache License to your work, attach the following
-      boilerplate notice, with the fields enclosed by brackets "[]"
-      replaced with your own identifying information. (Don't include
-      the brackets!)  The text should be enclosed in the appropriate
-      comment syntax for the file format. We also recommend that a
-      file or class name and description of purpose be included on the
-      same "printed page" as the copyright notice for easier
-      identification within third-party archives.
-
-   Copyright [yyyy] [name of copyright owner]
-
-   Licensed under the Apache License, Version 2.0 (the "License");
-   you may not use this file except in compliance with the License.
-   You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-   Unless required by applicable law or agreed to in writing, software
-   distributed under the License is distributed on an "AS IS" BASIS,
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-   See the License for the specific language governing permissions and
-   limitations under the License.
diff --git a/internal/rpmpack/dir.go b/internal/rpmpack/dir.go
deleted file mode 100644
index 031cad1..0000000
--- a/internal/rpmpack/dir.go
+++ /dev/null
@@ -1,40 +0,0 @@
-// Copyright 2019 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package rpmpack
-
-// dirIndex holds the index from files to directory names.
-type dirIndex struct {
-	m map[string]uint32
-	l []string
-}
-
-func newDirIndex() *dirIndex {
-	return &dirIndex{m: make(map[string]uint32)}
-}
-
-func (d *dirIndex) Get(value string) uint32 {
-	if idx, ok := d.m[value]; ok {
-		return idx
-	}
-	newIdx := uint32(len(d.l))
-	d.l = append(d.l, value)
-
-	d.m[value] = newIdx
-	return newIdx
-}
-
-func (d *dirIndex) AllDirs() []string {
-	return d.l
-}
diff --git a/internal/rpmpack/file_types.go b/internal/rpmpack/file_types.go
deleted file mode 100644
index 720f733..0000000
--- a/internal/rpmpack/file_types.go
+++ /dev/null
@@ -1,47 +0,0 @@
-package rpmpack
-
-// FileType is the type of a file inside a RPM package.
-type FileType int32
-
-// https://refspecs.linuxbase.org/LSB_3.1.1/LSB-Core-generic/LSB-Core-generic/pkgformat.html#AEN27560
-// The RPMFile.Type tag value shall identify various characteristics of the file in the payload that it describes.
-// It shall be an INT32 value consisting of either the value GenericFile (0) or
-// the bitwise inclusive or of one or more of the following values. Some of these combinations may make no sense
-const (
-	// GenericFile is just a basic file in an RPM
-	GenericFile FileType = 1 << iota >> 1
-	// ConfigFile is a configuration file, and an existing file should be saved during a
-	// package upgrade operation and not removed during a package removal operation.
-	ConfigFile
-	// DocFile is a file that contains documentation.
-	DocFile
-	// DoNotUseFile is reserved for future use; conforming packages may not use this flag.
-	DoNotUseFile
-	// MissingOkFile need not exist on the installed system.
-	MissingOkFile
-	// NoReplaceFile similar to the ConfigFile, this flag indicates that during an upgrade operation
-	// the original file on the system should not be altered.
-	NoReplaceFile
-	// SpecFile is the package specification file.
-	SpecFile
-	// GhostFile is not actually included in the payload, but should still be considered as a part of the package.
-	// For example, a log file generated by the application at run time.
-	GhostFile
-	// LicenceFile contains the license conditions.
-	LicenceFile
-	// ReadmeFile contains high level notes about the package.
-	ReadmeFile
-	// ExcludeFile is not a part of the package, and should not be installed.
-	ExcludeFile
-)
-
-// RPMFile contains a particular file's entry and data.
-type RPMFile struct {
-	Name  string
-	Body  []byte
-	Mode  uint
-	Owner string
-	Group string
-	MTime uint32
-	Type  FileType
-}
diff --git a/internal/rpmpack/header.go b/internal/rpmpack/header.go
deleted file mode 100644
index 8e95cdb..0000000
--- a/internal/rpmpack/header.go
+++ /dev/null
@@ -1,209 +0,0 @@
-// Copyright 2019 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package rpmpack
-
-import (
-	"bytes"
-	"encoding/binary"
-	"fmt"
-	"sort"
-)
-
-const (
-	signatures = 0x3e
-	immutable  = 0x3f
-
-	typeInt16       = 0x03
-	typeInt32       = 0x04
-	typeString      = 0x06
-	typeBinary      = 0x07
-	typeStringArray = 0x08
-)
-
-// Only integer types are aligned. This is not just an optimization - some versions
-// of rpm fail when integers are not aligned. Other versions fail when non-integers are aligned.
-var boundaries = map[int]int{
-	typeInt16: 2,
-	typeInt32: 4,
-}
-
-type IndexEntry struct {
-	rpmtype, count int
-	data           []byte
-}
-
-func (e IndexEntry) indexBytes(tag, contentOffset int) []byte {
-	b := &bytes.Buffer{}
-	if err := binary.Write(b, binary.BigEndian, []int32{int32(tag), int32(e.rpmtype), int32(contentOffset), int32(e.count)}); err != nil {
-		// binary.Write can fail if the underlying Write fails, or the types are invalid.
-		// bytes.Buffer's write never error out, it can only panic with OOM.
-		panic(err)
-	}
-	return b.Bytes()
-}
-
-func intEntry(rpmtype, size int, value interface{}) IndexEntry {
-	b := &bytes.Buffer{}
-	if err := binary.Write(b, binary.BigEndian, value); err != nil {
-		// binary.Write can fail if the underlying Write fails, or the types are invalid.
-		// bytes.Buffer's write never error out, it can only panic with OOM.
-		panic(err)
-	}
-	return IndexEntry{rpmtype, size, b.Bytes()}
-}
-
-func EntryInt16(value []int16) IndexEntry {
-	return intEntry(typeInt16, len(value), value)
-}
-
-func EntryUint16(value []uint16) IndexEntry {
-	return intEntry(typeInt16, len(value), value)
-}
-
-func EntryInt32(value []int32) IndexEntry {
-	return intEntry(typeInt32, len(value), value)
-}
-
-func EntryUint32(value []uint32) IndexEntry {
-	return intEntry(typeInt32, len(value), value)
-}
-
-func EntryString(value string) IndexEntry {
-	return IndexEntry{typeString, 1, append([]byte(value), byte(0o0))}
-}
-
-func EntryBytes(value []byte) IndexEntry {
-	return IndexEntry{typeBinary, len(value), value}
-}
-
-func EntryStringSlice(value []string) IndexEntry {
-	b := [][]byte{}
-	for _, v := range value {
-		b = append(b, []byte(v))
-	}
-	bb := append(bytes.Join(b, []byte{0o0}), byte(0o0))
-	return IndexEntry{typeStringArray, len(value), bb}
-}
-
-type index struct {
-	entries map[int]IndexEntry
-	h       int
-}
-
-func newIndex(h int) *index {
-	return &index{entries: make(map[int]IndexEntry), h: h}
-}
-
-func (i *index) Add(tag int, e IndexEntry) {
-	i.entries[tag] = e
-}
-
-func (i *index) AddEntries(m map[int]IndexEntry) {
-	for t, e := range m {
-		i.Add(t, e)
-	}
-}
-
-func (i *index) sortedTags() []int {
-	t := []int{}
-	for k := range i.entries {
-		t = append(t, k)
-	}
-	sort.Ints(t)
-	return t
-}
-
-func pad(w *bytes.Buffer, rpmtype, offset int) {
-	// We need to align integer entries...
-	if b, ok := boundaries[rpmtype]; ok && offset%b != 0 {
-		if _, err := w.Write(make([]byte, b-offset%b)); err != nil {
-			// binary.Write can fail if the underlying Write fails, or the types are invalid.
-			// bytes.Buffer's write never error out, it can only panic with OOM.
-			panic(err)
-		}
-	}
-}
-
-// Bytes returns the bytes of the index.
-func (i *index) Bytes() ([]byte, error) {
-	w := &bytes.Buffer{}
-	// Even the header has three parts: The lead, the index entries, and the entries.
-	// Because of alignment, we can only tell the actual size and offset after writing
-	// the entries.
-	entryData := &bytes.Buffer{}
-	tags := i.sortedTags()
-	offsets := make([]int, len(tags))
-	for ii, tag := range tags {
-		e := i.entries[tag]
-		pad(entryData, e.rpmtype, entryData.Len())
-		offsets[ii] = entryData.Len()
-		entryData.Write(e.data)
-	}
-	entryData.Write(i.eigenHeader().data)
-
-	// 4 magic and 4 reserved
-	w.Write([]byte{0x8e, 0xad, 0xe8, 0x01, 0, 0, 0, 0})
-	// 4 count and 4 size
-	// We add the pseudo-entry "eigenHeader" to count.
-	if err := binary.Write(w, binary.BigEndian, []int32{int32(len(i.entries)) + 1, int32(entryData.Len())}); err != nil {
-		return nil, fmt.Errorf("failed to write eigenHeader: %w", err)
-	}
-	// Write the eigenHeader index entry
-	w.Write(i.eigenHeader().indexBytes(i.h, entryData.Len()-0x10))
-	// Write all of the other index entries
-	for ii, tag := range tags {
-		e := i.entries[tag]
-		w.Write(e.indexBytes(tag, offsets[ii]))
-	}
-	w.Write(entryData.Bytes())
-	return w.Bytes(), nil
-}
-
-// the eigenHeader is a weird entry. Its index entry is sorted first, but its content
-// is last. The content is a 16 byte index entry, which is almost the same as the index
-// entry except for the offset. The offset here is ... minus the length of the index entry region.
-// Which is always 0x10 * number of entries.
-// I kid you not.
-func (i *index) eigenHeader() IndexEntry {
-	b := &bytes.Buffer{}
-	if err := binary.Write(b, binary.BigEndian, []int32{int32(i.h), int32(typeBinary), -int32(0x10 * (len(i.entries) + 1)), int32(0x10)}); err != nil {
-		// binary.Write can fail if the underlying Write fails, or the types are invalid.
-		// bytes.Buffer's write never error out, it can only panic with OOM.
-		panic(err)
-	}
-
-	return EntryBytes(b.Bytes())
-}
-
-func lead(name, fullVersion string) []byte {
-	// RPM format = 0xedabeedb
-	// version 3.0 = 0x0300
-	// type binary = 0x0000
-	// machine archnum (i386?) = 0x0001
-	// name ( 66 bytes, with null termination)
-	// osnum (linux?) = 0x0001
-	// sig type (header-style) = 0x0005
-	// reserved 16 bytes of 0x00
-	n := []byte(fmt.Sprintf("%s-%s", name, fullVersion))
-	if len(n) > 65 {
-		n = n[:65]
-	}
-	n = append(n, make([]byte, 66-len(n))...)
-	b := []byte{0xed, 0xab, 0xee, 0xdb, 0x03, 0x00, 0x00, 0x00, 0x00, 0x01}
-	b = append(b, n...)
-	b = append(b, []byte{0x00, 0x01, 0x00, 0x05}...)
-	b = append(b, make([]byte, 16)...)
-	return b
-}
diff --git a/internal/rpmpack/rpm.go b/internal/rpmpack/rpm.go
deleted file mode 100644
index 76c893d..0000000
--- a/internal/rpmpack/rpm.go
+++ /dev/null
@@ -1,552 +0,0 @@
-// Copyright 2019 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// Package rpmpack packs files to rpm files.
-// It is designed to be simple to use and deploy, not requiring any filesystem access
-// to create rpm files.
-package rpmpack
-
-import (
-	"bytes"
-	"crypto/sha256"
-	"errors"
-	"fmt"
-	"io"
-	"path"
-	"sort"
-	"strconv"
-	"strings"
-	"time"
-
-	"github.com/cavaliergopher/cpio"
-	"github.com/klauspost/compress/zstd"
-	gzip "github.com/klauspost/pgzip"
-	"github.com/ulikunitz/xz"
-	"github.com/ulikunitz/xz/lzma"
-)
-
-var (
-	// ErrWriteAfterClose is returned when a user calls Write() on a closed rpm.
-	ErrWriteAfterClose = errors.New("rpm write after close")
-	// ErrWrongFileOrder is returned when files are not sorted by name.
-	ErrWrongFileOrder = errors.New("wrong file addition order")
-)
-
-// RPMMetaData contains meta info about the whole package.
-type RPMMetaData struct {
-	Name,
-	Summary,
-	Description,
-	Version,
-	Release,
-	Arch,
-	OS,
-	Vendor,
-	URL,
-	Packager,
-	Group,
-	Licence,
-	BuildHost,
-	Compressor string
-	Epoch     uint32
-	BuildTime time.Time
-	Provides,
-	Obsoletes,
-	Suggests,
-	Recommends,
-	Requires,
-	Conflicts Relations
-}
-
-// RPM holds the state of a particular rpm file. Please use NewRPM to instantiate it.
-type RPM struct {
-	RPMMetaData
-	di                *dirIndex
-	payload           *bytes.Buffer
-	payloadSize       uint
-	cpio              *cpio.Writer
-	basenames         []string
-	dirindexes        []uint32
-	filesizes         []uint32
-	filemodes         []uint16
-	fileowners        []string
-	filegroups        []string
-	filemtimes        []uint32
-	filedigests       []string
-	filelinktos       []string
-	fileflags         []uint32
-	closed            bool
-	compressedPayload io.WriteCloser
-	files             map[string]RPMFile
-	prein             string
-	postin            string
-	preun             string
-	postun            string
-	pretrans          string
-	posttrans         string
-	customTags        map[int]IndexEntry
-	customSigs        map[int]IndexEntry
-	pgpSigner         func([]byte) ([]byte, error)
-}
-
-// NewRPM creates and returns a new RPM struct.
-func NewRPM(m RPMMetaData) (*RPM, error) {
-	var err error
-
-	if m.OS == "" {
-		m.OS = "linux"
-	}
-
-	if m.Arch == "" {
-		m.Arch = "noarch"
-	}
-
-	p := &bytes.Buffer{}
-
-	z, compressorName, err := setupCompressor(m.Compressor, p)
-	if err != nil {
-		return nil, err
-	}
-
-	// only use compressor name for the rpm tag, not the level
-	m.Compressor = compressorName
-
-	rpm := &RPM{
-		RPMMetaData:       m,
-		di:                newDirIndex(),
-		payload:           p,
-		compressedPayload: z,
-		cpio:              cpio.NewWriter(z),
-		files:             make(map[string]RPMFile),
-		customTags:        make(map[int]IndexEntry),
-		customSigs:        make(map[int]IndexEntry),
-	}
-
-	// A package must provide itself...
-	rpm.Provides.addIfMissing(&Relation{
-		Name:    rpm.Name,
-		Version: rpm.FullVersion(),
-		Sense:   SenseEqual,
-	})
-
-	return rpm, nil
-}
-
-func setupCompressor(compressorSetting string, w io.Writer) (wc io.WriteCloser,
-	compressorType string, err error,
-) {
-	parts := strings.Split(compressorSetting, ":")
-	if len(parts) > 2 {
-		return nil, "", fmt.Errorf("malformed compressor setting: %s", compressorSetting)
-	}
-
-	compressorType = parts[0]
-	compressorLevel := ""
-	if len(parts) == 2 {
-		compressorLevel = parts[1]
-	}
-
-	switch compressorType {
-	case "":
-		compressorType = "gzip"
-		fallthrough
-	case "gzip":
-		level := 9
-
-		if compressorLevel != "" {
-			var err error
-
-			level, err = strconv.Atoi(compressorLevel)
-			if err != nil {
-				return nil, "", fmt.Errorf("parse gzip compressor level: %w", err)
-			}
-		}
-
-		wc, err = gzip.NewWriterLevel(w, level)
-	case "lzma":
-		if compressorLevel != "" {
-			return nil, "", fmt.Errorf("no compressor level supported for lzma: %s", compressorLevel)
-		}
-
-		wc, err = lzma.NewWriter(w)
-	case "xz":
-		if compressorLevel != "" {
-			return nil, "", fmt.Errorf("no compressor level supported for xz: %s", compressorLevel)
-		}
-
-		wc, err = xz.NewWriter(w)
-	case "zstd":
-		level := zstd.SpeedBetterCompression
-
-		if compressorLevel != "" {
-			var ok bool
-
-			if intLevel, err := strconv.Atoi(compressorLevel); err == nil {
-				level = zstd.EncoderLevelFromZstd(intLevel)
-			} else {
-				ok, level = zstd.EncoderLevelFromString(compressorLevel)
-				if !ok {
-					return nil, "", fmt.Errorf("invalid zstd compressor level: %s", compressorLevel)
-				}
-			}
-		}
-
-		wc, err = zstd.NewWriter(w, zstd.WithEncoderLevel(level))
-	default:
-		return nil, "", fmt.Errorf("unknown compressor type: %s", compressorType)
-	}
-
-	return wc, compressorType, err
-}
-
-// FullVersion properly combines version and release fields to a version string
-func (r *RPM) FullVersion() string {
-	if r.Release != "" {
-		return fmt.Sprintf("%s-%s", r.Version, r.Release)
-	}
-
-	return r.Version
-}
-
-// AllowListDirs removes all directories which are not explicitly allowlisted.
-func (r *RPM) AllowListDirs(allowList map[string]bool) {
-	for fn, ff := range r.files {
-		if ff.Mode&0o40000 == 0o40000 {
-			if !allowList[fn] {
-				delete(r.files, fn)
-			}
-		}
-	}
-}
-
-// Write closes the rpm and writes the whole rpm to an io.Writer
-func (r *RPM) Write(w io.Writer) error {
-	if r.closed {
-		return ErrWriteAfterClose
-	}
-	// Add all of the files, sorted alphabetically.
-	fnames := []string{}
-	for fn := range r.files {
-		fnames = append(fnames, fn)
-	}
-	sort.Strings(fnames)
-	for _, fn := range fnames {
-		if err := r.writeFile(r.files[fn]); err != nil {
-			return fmt.Errorf("failed to write file %q: %w", fn, err)
-		}
-	}
-	if err := r.cpio.Close(); err != nil {
-		return fmt.Errorf("failed to close cpio payload: %w", err)
-	}
-	if err := r.compressedPayload.Close(); err != nil {
-		return fmt.Errorf("failed to close gzip payload: %w", err)
-	}
-
-	if _, err := w.Write(lead(r.Name, r.FullVersion())); err != nil {
-		return fmt.Errorf("failed to write lead: %w", err)
-	}
-	// Write the regular header.
-	h := newIndex(immutable)
-	r.writeGenIndexes(h)
-
-	// do not write file indexes if there are no files (meta package)
-	// doing so will result in an invalid package
-	if (len(r.files)) > 0 {
-		r.writeFileIndexes(h)
-	}
-
-	if err := r.writeRelationIndexes(h); err != nil {
-		return err
-	}
-	// CustomTags must be the last to be added, because they can overwrite values.
-	h.AddEntries(r.customTags)
-	hb, err := h.Bytes()
-	if err != nil {
-		return fmt.Errorf("failed to retrieve header: %w", err)
-	}
-	// Write the signatures
-	s := newIndex(signatures)
-	if err := r.writeSignatures(s, hb); err != nil {
-		return fmt.Errorf("failed to create signatures: %w", err)
-	}
-
-	s.AddEntries(r.customSigs)
-	sb, err := s.Bytes()
-	if err != nil {
-		return fmt.Errorf("failed to retrieve signatures header: %w", err)
-	}
-
-	if _, err := w.Write(sb); err != nil {
-		return fmt.Errorf("failed to write signature bytes: %w", err)
-	}
-	// Signatures are padded to 8-byte boundaries
-	if _, err := w.Write(make([]byte, (8-len(sb)%8)%8)); err != nil {
-		return fmt.Errorf("failed to write signature padding: %w", err)
-	}
-	if _, err := w.Write(hb); err != nil {
-		return fmt.Errorf("failed to write header body: %w", err)
-	}
-	if _, err := w.Write(r.payload.Bytes()); err != nil {
-		return fmt.Errorf("failed to write payload: %w", err)
-	}
-	return nil
-}
-
-// SetPGPSigner registers a function that will accept the header and payload as bytes,
-// and return a signature as bytes. The function should simulate what gpg does,
-// probably by using golang.org/x/crypto/openpgp or by forking a gpg process.
-func (r *RPM) SetPGPSigner(f func([]byte) ([]byte, error)) {
-	r.pgpSigner = f
-}
-
-// Only call this after the payload and header were written.
-func (r *RPM) writeSignatures(sigHeader *index, regHeader []byte) error {
-	sigHeader.Add(sigSize, EntryInt32([]int32{int32(r.payload.Len() + len(regHeader))}))
-	sigHeader.Add(sigSHA256, EntryString(fmt.Sprintf("%x", sha256.Sum256(regHeader))))
-	sigHeader.Add(sigPayloadSize, EntryInt32([]int32{int32(r.payloadSize)}))
-	if r.pgpSigner != nil {
-		// For sha 256 you need to sign the header and payload separately
-		header := append([]byte{}, regHeader...)
-		headerSig, err := r.pgpSigner(header)
-		if err != nil {
-			return fmt.Errorf("call to signer failed: %w", err)
-		}
-		sigHeader.Add(sigRSA, EntryBytes(headerSig))
-
-		body := append(header, r.payload.Bytes()...)
-		bodySig, err := r.pgpSigner(body)
-		if err != nil {
-			return fmt.Errorf("call to signer failed: %w", err)
-		}
-		sigHeader.Add(sigPGP, EntryBytes(bodySig))
-	}
-	return nil
-}
-
-func (r *RPM) writeRelationIndexes(h *index) error {
-	// add all relation categories
-	if err := r.Provides.AddToIndex(h, tagProvides, tagProvideVersion, tagProvideFlags); err != nil {
-		return fmt.Errorf("failed to add provides: %w", err)
-	}
-	if err := r.Obsoletes.AddToIndex(h, tagObsoletes, tagObsoleteVersion, tagObsoleteFlags); err != nil {
-		return fmt.Errorf("failed to add obsoletes: %w", err)
-	}
-	if err := r.Suggests.AddToIndex(h, tagSuggests, tagSuggestVersion, tagSuggestFlags); err != nil {
-		return fmt.Errorf("failed to add suggests: %w", err)
-	}
-	if err := r.Recommends.AddToIndex(h, tagRecommends, tagRecommendVersion, tagRecommendFlags); err != nil {
-		return fmt.Errorf("failed to add recommends: %w", err)
-	}
-	if err := r.Requires.AddToIndex(h, tagRequires, tagRequireVersion, tagRequireFlags); err != nil {
-		return fmt.Errorf("failed to add requires: %w", err)
-	}
-	if err := r.Conflicts.AddToIndex(h, tagConflicts, tagConflictVersion, tagConflictFlags); err != nil {
-		return fmt.Errorf("failed to add conflicts: %w", err)
-	}
-
-	return nil
-}
-
-// AddCustomTag adds or overwrites a tag value in the index.
-func (r *RPM) AddCustomTag(tag int, e IndexEntry) {
-	r.customTags[tag] = e
-}
-
-// AddCustomSig adds or overwrites a signature tag value.
-func (r *RPM) AddCustomSig(tag int, e IndexEntry) {
-	r.customSigs[tag] = e
-}
-
-func (r *RPM) writeGenIndexes(h *index) {
-	h.Add(tagHeaderI18NTable, EntryString("C"))
-	h.Add(tagSize, EntryInt32([]int32{int32(r.payloadSize)}))
-	h.Add(tagName, EntryString(r.Name))
-	h.Add(tagVersion, EntryString(r.Version))
-	h.Add(tagEpoch, EntryUint32([]uint32{r.Epoch}))
-	h.Add(tagSummary, EntryString(r.Summary))
-	h.Add(tagDescription, EntryString(r.Description))
-	h.Add(tagBuildHost, EntryString(r.BuildHost))
-	if !r.BuildTime.IsZero() {
-		// time.Time zero value is confusing, avoid if not supplied
-		// see https://github.com/google/rpmpack/issues/43
-		h.Add(tagBuildTime, EntryInt32([]int32{int32(r.BuildTime.Unix())}))
-	}
-	h.Add(tagRelease, EntryString(r.Release))
-	h.Add(tagPayloadFormat, EntryString("cpio"))
-	h.Add(tagPayloadCompressor, EntryString(r.Compressor))
-	h.Add(tagPayloadFlags, EntryString("9"))
-	h.Add(tagArch, EntryString(r.Arch))
-	h.Add(tagOS, EntryString(r.OS))
-	h.Add(tagVendor, EntryString(r.Vendor))
-	h.Add(tagLicence, EntryString(r.Licence))
-	h.Add(tagPackager, EntryString(r.Packager))
-	h.Add(tagGroup, EntryString(r.Group))
-	h.Add(tagURL, EntryString(r.URL))
-	h.Add(tagPayloadDigest, EntryStringSlice([]string{fmt.Sprintf("%x", sha256.Sum256(r.payload.Bytes()))}))
-	h.Add(tagPayloadDigestAlgo, EntryInt32([]int32{hashAlgoSHA256}))
-
-	// rpm utilities look for the sourcerpm tag to deduce if this is not a source rpm (if it has a sourcerpm,
-	// it is NOT a source rpm).
-	h.Add(tagSourceRPM, EntryString(fmt.Sprintf("%s-%s.src.rpm", r.Name, r.FullVersion())))
-	if r.pretrans != "" {
-		h.Add(tagPretrans, EntryString(r.pretrans))
-		h.Add(tagPretransProg, EntryString("/bin/sh"))
-	}
-	if r.prein != "" {
-		h.Add(tagPrein, EntryString(r.prein))
-		h.Add(tagPreinProg, EntryString("/bin/sh"))
-	}
-	if r.postin != "" {
-		h.Add(tagPostin, EntryString(r.postin))
-		h.Add(tagPostinProg, EntryString("/bin/sh"))
-	}
-	if r.preun != "" {
-		h.Add(tagPreun, EntryString(r.preun))
-		h.Add(tagPreunProg, EntryString("/bin/sh"))
-	}
-	if r.postun != "" {
-		h.Add(tagPostun, EntryString(r.postun))
-		h.Add(tagPostunProg, EntryString("/bin/sh"))
-	}
-	if r.posttrans != "" {
-		h.Add(tagPosttrans, EntryString(r.posttrans))
-		h.Add(tagPosttransProg, EntryString("/bin/sh"))
-	}
-}
-
-// WriteFileIndexes writes file related index headers to the header
-func (r *RPM) writeFileIndexes(h *index) {
-	h.Add(tagBasenames, EntryStringSlice(r.basenames))
-	h.Add(tagDirindexes, EntryUint32(r.dirindexes))
-	h.Add(tagDirnames, EntryStringSlice(r.di.AllDirs()))
-	h.Add(tagFileSizes, EntryUint32(r.filesizes))
-	h.Add(tagFileModes, EntryUint16(r.filemodes))
-	h.Add(tagFileUserName, EntryStringSlice(r.fileowners))
-	h.Add(tagFileGroupName, EntryStringSlice(r.filegroups))
-	h.Add(tagFileMTimes, EntryUint32(r.filemtimes))
-	h.Add(tagFileDigests, EntryStringSlice(r.filedigests))
-	h.Add(tagFileLinkTos, EntryStringSlice(r.filelinktos))
-	h.Add(tagFileFlags, EntryUint32(r.fileflags))
-
-	inodes := make([]int32, len(r.dirindexes))
-	digestAlgo := make([]int32, len(r.dirindexes))
-	verifyFlags := make([]int32, len(r.dirindexes))
-	fileRDevs := make([]int16, len(r.dirindexes))
-	fileLangs := make([]string, len(r.dirindexes))
-
-	for ii := range inodes {
-		// is inodes just a range from 1..len(dirindexes)? maybe different with hard links
-		inodes[ii] = int32(ii + 1)
-		digestAlgo[ii] = hashAlgoSHA256
-		// With regular files, it seems like we can always enable all of the verify flags
-		verifyFlags[ii] = int32(-1)
-		fileRDevs[ii] = int16(1)
-	}
-	h.Add(tagFileINodes, EntryInt32(inodes))
-	h.Add(tagFileDigestAlgo, EntryInt32(digestAlgo))
-	h.Add(tagFileVerifyFlags, EntryInt32(verifyFlags))
-	h.Add(tagFileRDevs, EntryInt16(fileRDevs))
-	h.Add(tagFileLangs, EntryStringSlice(fileLangs))
-}
-
-// AddPretrans adds a pretrans scriptlet
-func (r *RPM) AddPretrans(s string) {
-	r.pretrans = s
-}
-
-// AddPrein adds a prein scriptlet
-func (r *RPM) AddPrein(s string) {
-	r.prein = s
-}
-
-// AddPostin adds a postin scriptlet
-func (r *RPM) AddPostin(s string) {
-	r.postin = s
-}
-
-// AddPreun adds a preun scriptlet
-func (r *RPM) AddPreun(s string) {
-	r.preun = s
-}
-
-// AddPostun adds a postun scriptlet
-func (r *RPM) AddPostun(s string) {
-	r.postun = s
-}
-
-// AddPosttrans adds a posttrans scriptlet
-func (r *RPM) AddPosttrans(s string) {
-	r.posttrans = s
-}
-
-// AddFile adds an RPMFile to an existing rpm.
-func (r *RPM) AddFile(f RPMFile) {
-	if f.Name == "/" { // rpm does not allow the root dir to be included.
-		return
-	}
-	r.files[f.Name] = f
-}
-
-// writeFile writes the file to the indexes and cpio.
-func (r *RPM) writeFile(f RPMFile) error {
-	dir, file := path.Split(f.Name)
-	r.dirindexes = append(r.dirindexes, r.di.Get(dir))
-	r.basenames = append(r.basenames, file)
-	r.fileowners = append(r.fileowners, f.Owner)
-	r.filegroups = append(r.filegroups, f.Group)
-	r.filemtimes = append(r.filemtimes, f.MTime)
-	r.fileflags = append(r.fileflags, uint32(f.Type))
-
-	links := 1
-	switch {
-	case f.Mode&0o40000 != 0: // directory
-		r.filesizes = append(r.filesizes, 4096)
-		r.filedigests = append(r.filedigests, "")
-		r.filelinktos = append(r.filelinktos, "")
-		links = 2
-	case f.Mode&0o120000 == 0o120000: //  symlink
-		r.filesizes = append(r.filesizes, uint32(len(f.Body)))
-		r.filedigests = append(r.filedigests, "")
-		r.filelinktos = append(r.filelinktos, string(f.Body))
-	default: // regular file
-		f.Mode = f.Mode | 0o100000
-		r.filesizes = append(r.filesizes, uint32(len(f.Body)))
-		r.filedigests = append(r.filedigests, fmt.Sprintf("%x", sha256.Sum256(f.Body)))
-		r.filelinktos = append(r.filelinktos, "")
-	}
-	r.filemodes = append(r.filemodes, uint16(f.Mode))
-
-	// Ghost files have no payload
-	if f.Type == GhostFile {
-		return nil
-	}
-	return r.writePayload(f, links)
-}
-
-func (r *RPM) writePayload(f RPMFile, links int) error {
-	hdr := &cpio.Header{
-		Name:  f.Name,
-		Mode:  cpio.FileMode(f.Mode),
-		Size:  int64(len(f.Body)),
-		Links: links,
-	}
-	if err := r.cpio.WriteHeader(hdr); err != nil {
-		return fmt.Errorf("failed to write payload file header: %w", err)
-	}
-	if _, err := r.cpio.Write(f.Body); err != nil {
-		return fmt.Errorf("failed to write payload file content: %w", err)
-	}
-	r.payloadSize += uint(len(f.Body))
-	return nil
-}
diff --git a/internal/rpmpack/sense.go b/internal/rpmpack/sense.go
deleted file mode 100644
index cf97cad..0000000
--- a/internal/rpmpack/sense.go
+++ /dev/null
@@ -1,168 +0,0 @@
-package rpmpack
-
-import (
-	"fmt"
-	"regexp"
-	"strings"
-)
-
-type rpmSense uint32
-
-// https://github.com/rpm-software-management/rpm/blob/ab01b5eacf9ec6a07a5d9e1991ef476a12d264fd/include/rpm/rpmds.h#L27
-// SenseAny (0) specifies no specific version compare
-// SenseLess (2) specifies less then the specified version
-// SenseGreater (4) specifies greater then the specified version
-// SenseEqual (8) specifies equal to the specified version
-const (
-	SenseAny  rpmSense = 0
-	SenseLess          = 1 << iota
-	SenseGreater
-	SenseEqual
-	SenseRPMLIB rpmSense = 1 << 24
-)
-
-var relationMatch = regexp.MustCompile(`([^=<>\s]*)\s*((?:=|>|<)*)\s*(.*)?`)
-
-// Relation is the structure of rpm sense relationships
-type Relation struct {
-	Name    string
-	Version string
-	Sense   rpmSense
-}
-
-// String return the string representation of the Relation
-func (r *Relation) String() string {
-	return fmt.Sprintf("%s%v%s", r.Name, r.Sense, r.Version)
-}
-
-// Equal compare the equality of two relations
-func (r *Relation) Equal(o *Relation) bool {
-	return r.Name == o.Name && r.Version == o.Version && r.Sense == o.Sense
-}
-
-// Relations is a slice of Relation pointers
-type Relations []*Relation
-
-// String return the string representation of the Relations
-func (r *Relations) String() string {
-	var val []string
-	for _, rel := range *r {
-		val = append(val, rel.String())
-	}
-	return strings.Join(val, ",")
-}
-
-// Set parse a string into a Relation and append it to the Relations slice if it is missing
-// this is used by the flag package
-func (r *Relations) Set(value string) error {
-	relation, err := NewRelation(value)
-	if err != nil {
-		return err
-	}
-	r.addIfMissing(relation)
-
-	return nil
-}
-
-func (r *Relations) addIfMissing(value *Relation) {
-	for _, relation := range *r {
-		if relation.Equal(value) {
-			return
-		}
-	}
-
-	*r = append(*r, value)
-}
-
-// AddToIndex add the relations to the specified category on the index
-func (r *Relations) AddToIndex(h *index, nameTag, versionTag, flagsTag int) error {
-	var (
-		num      = len(*r)
-		names    = make([]string, num)
-		versions = make([]string, num)
-		flags    = make([]uint32, num)
-	)
-
-	if num == 0 {
-		return nil
-	}
-
-	for idx, relation := range *r {
-		names[idx] = relation.Name
-		versions[idx] = relation.Version
-		flags[idx] = uint32(relation.Sense)
-	}
-
-	h.Add(nameTag, EntryStringSlice(names))
-	h.Add(versionTag, EntryStringSlice(versions))
-	h.Add(flagsTag, EntryUint32(flags))
-
-	return nil
-}
-
-// NewRelation parse a string into a Relation
-func NewRelation(related string) (*Relation, error) {
-	var (
-		err   error
-		sense rpmSense
-		name,
-		version string
-	)
-
-	if strings.HasPrefix(related, "(") && strings.HasSuffix(related, ")") {
-		// This is a `rich` dependency which must be parsed at install time
-		// https://rpm-software-management.github.io/rpm/manual/boolean_dependencies.html
-		sense = SenseAny
-		name = related
-	} else {
-		parts := relationMatch.FindStringSubmatch(related)
-		if sense, err = parseSense(parts[2]); err != nil {
-			return nil, err
-		}
-		name = parts[1]
-		version = parts[3]
-	}
-
-	return &Relation{
-		Name:    name,
-		Version: version,
-		Sense:   sense,
-	}, nil
-}
-
-var stringToSense = map[string]rpmSense{
-	"":   SenseAny,
-	"<":  SenseLess,
-	">":  SenseGreater,
-	"=":  SenseEqual,
-	"<=": SenseLess | SenseEqual,
-	">=": SenseGreater | SenseEqual,
-}
-
-// String return the string representation of the rpmSense
-func (r rpmSense) String() string {
-	var (
-		val rpmSense
-		ret string
-	)
-
-	for ret, val = range stringToSense {
-		if r == val {
-			return ret
-		}
-	}
-
-	return "unknown"
-}
-
-func parseSense(sense string) (rpmSense, error) {
-	var (
-		ret rpmSense
-		ok  bool
-	)
-	if ret, ok = stringToSense[sense]; !ok {
-		return SenseAny, fmt.Errorf("unknown sense value: %s", sense)
-	}
-
-	return ret, nil
-}
diff --git a/internal/rpmpack/tags.go b/internal/rpmpack/tags.go
deleted file mode 100644
index 4359929..0000000
--- a/internal/rpmpack/tags.go
+++ /dev/null
@@ -1,101 +0,0 @@
-// Copyright 2019 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package rpmpack
-
-// Define only tags which we actually use
-// https://github.com/rpm-software-management/rpm/blob/master/lib/rpmtag.h
-const (
-	tagHeaderI18NTable = 0x64 // 100
-	// Signature tags are obiously overlapping regular header tags..
-	sigRSA         = 0x010c // 256
-	sigSHA256      = 0x0111 // 273
-	sigSize        = 0x03e8 // 1000
-	sigPGP         = 0x03ea // 1002
-	sigPayloadSize = 0x03ef // 1007
-
-	// https://github.com/rpm-software-management/rpm/blob/92eadae94c48928bca90693ad63c46ceda37d81f/rpmio/rpmpgp.h#L258
-	hashAlgoSHA256 = 0x0008 // 8
-
-	tagName        = 0x03e8 // 1000
-	tagVersion     = 0x03e9 // 1001
-	tagRelease     = 0x03ea // 1002
-	tagEpoch       = 0x03eb // 1003
-	tagSummary     = 0x03ec // 1004
-	tagDescription = 0x03ed // 1005
-	tagBuildTime   = 0x03ee // 1006
-	tagBuildHost   = 0x03ef // 1007
-	tagSize        = 0x03f1 // 1009
-	tagVendor      = 0x03f3 // 1011
-	tagLicence     = 0x03f6 // 1014
-	tagPackager    = 0x03f7 // 1015
-	tagGroup       = 0x03f8 // 1016
-	tagURL         = 0x03fc // 1020
-	tagOS          = 0x03fd // 1021
-	tagArch        = 0x03fe // 1022
-
-	tagPrein  = 0x03ff // 1023
-	tagPostin = 0x0400 // 1024
-	tagPreun  = 0x0401 // 1025
-	tagPostun = 0x0402 // 1026
-
-	tagFileSizes         = 0x0404 // 1028
-	tagFileModes         = 0x0406 // 1030
-	tagFileRDevs         = 0x0409 // 1033
-	tagFileMTimes        = 0x040a // 1034
-	tagFileDigests       = 0x040b // 1035
-	tagFileLinkTos       = 0x040c // 1036
-	tagFileFlags         = 0x040d // 1037
-	tagFileUserName      = 0x040f // 1039
-	tagFileGroupName     = 0x0410 // 1040
-	tagSourceRPM         = 0x0414 // 1044
-	tagFileVerifyFlags   = 0x0415 // 1045
-	tagProvides          = 0x0417 // 1047
-	tagRequireFlags      = 0x0418 // 1048
-	tagRequires          = 0x0419 // 1049
-	tagRequireVersion    = 0x041a // 1050
-	tagConflictFlags     = 0x041d // 1053
-	tagConflicts         = 0x041e // 1054
-	tagConflictVersion   = 0x041f // 1055
-	tagPreinProg         = 0x043d // 1085
-	tagPostinProg        = 0x043e // 1086
-	tagPreunProg         = 0x043f // 1087
-	tagPostunProg        = 0x0440 // 1088
-	tagObsoletes         = 0x0442 // 1090
-	tagFileINodes        = 0x0448 // 1096
-	tagFileLangs         = 0x0449 // 1097
-	tagProvideFlags      = 0x0458 // 1112
-	tagProvideVersion    = 0x0459 // 1113
-	tagObsoleteFlags     = 0x045a // 1114
-	tagObsoleteVersion   = 0x045b // 1115
-	tagDirindexes        = 0x045c // 1116
-	tagBasenames         = 0x045d // 1117
-	tagDirnames          = 0x045e // 1118
-	tagPayloadFormat     = 0x0464 // 1124
-	tagPayloadCompressor = 0x0465 // 1125
-	tagPayloadFlags      = 0x0466 // 1126
-	tagPretrans          = 0x047f // 1151
-	tagPosttrans         = 0x0480 // 1152
-	tagPretransProg      = 0x0481 // 1153
-	tagPosttransProg     = 0x0482 // 1154
-	tagFileDigestAlgo    = 0x1393 // 5011
-	tagRecommends        = 0x13b6 // 5046
-	tagRecommendVersion  = 0x13b7 // 5047
-	tagRecommendFlags    = 0x13b8 // 5048
-	tagSuggests          = 0x13b9 // 5049
-	tagSuggestVersion    = 0x13ba // 5050
-	tagSuggestFlags      = 0x13bb // 5051
-	tagPayloadDigest     = 0x13e4 // 5092
-	tagPayloadDigestAlgo = 0x13e5 // 5093
-)
diff --git a/internal/sign/pgp.go b/internal/sign/pgp.go
index 1c0af46..24d6622 100644
--- a/internal/sign/pgp.go
+++ b/internal/sign/pgp.go
@@ -16,12 +16,6 @@ import (
 	"github.com/goreleaser/nfpm/v2"
 )
 
-// PGPSigner returns a PGP signer that creates a detached non-ASCII-armored
-// signature and is compatible with rpmpack's signature API.
-func PGPSigner(keyFile, passphrase string) func([]byte) ([]byte, error) {
-	return PGPSignerWithKeyID(keyFile, passphrase, nil)
-}
-
 // PGPSignerWithKeyID returns a PGP signer that creates a detached non-ASCII-armored
 // signature and is compatible with rpmpack's signature API.
 func PGPSignerWithKeyID(keyFile, passphrase string, hexKeyID *string) func([]byte) ([]byte, error) {
@@ -38,11 +32,15 @@ func PGPSignerWithKeyID(keyFile, passphrase string, hexKeyID *string) func([]byt
 
 		var signature bytes.Buffer
 
-		err = openpgp.DetachSign(&signature, key, bytes.NewReader(data), &packet.Config{
-			SigningKeyId: keyID,
-			DefaultHash:  crypto.SHA256,
-		})
-		if err != nil {
+		if err := openpgp.DetachSign(
+			&signature,
+			key,
+			bytes.NewReader(data),
+			&packet.Config{
+				SigningKeyId: keyID,
+				DefaultHash:  crypto.SHA256,
+			},
+		); err != nil {
 			return nil, &nfpm.ErrSigningFailure{Err: err}
 		}
 
diff --git a/nfpm.go b/nfpm.go
index da20ba2..64b8df7 100644
--- a/nfpm.go
+++ b/nfpm.go
@@ -193,8 +193,9 @@ func (c *Config) expandEnvVars() {
 	c.Info.Provides = c.expandEnvVarsStringSlice(c.Info.Provides)
 	c.Info.Suggests = c.expandEnvVarsStringSlice(c.Info.Suggests)
 
-	// Maintainer and vendor fields
+	// Basic metadata fields
 	c.Info.Name = os.Expand(c.Info.Name, c.envMappingFunc)
+	c.Info.Homepage = os.Expand(c.Info.Homepage, c.envMappingFunc)
 	c.Info.Maintainer = os.Expand(c.Info.Maintainer, c.envMappingFunc)
 	c.Info.Vendor = os.Expand(c.Info.Vendor, c.envMappingFunc)
 
@@ -336,6 +337,7 @@ type RPM struct {
 	Compression string       `yaml:"compression,omitempty" json:"compression,omitempty" jsonschema:"title=compression algorithm to be used,enum=gzip,enum=lzma,enum=xz,default=gzip:-1"`
 	Signature   RPMSignature `yaml:"signature,omitempty" json:"signature,omitempty" jsonschema:"title=rpm signature"`
 	Packager    string       `yaml:"packager,omitempty" json:"packager,omitempty" jsonschema:"title=organization that actually packaged the software"`
+	Prefixes    []string     `yaml:"prefixes,omitempty" json:"prefixes,omitempty" jsonschema:"title=Prefixes for relocatable packages"`
 }
 
 // RPMScripts represents scripts only available on RPM packages.
diff --git a/rpm/rpm.go b/rpm/rpm.go
index 0d3fa4d..27570d6 100644
--- a/rpm/rpm.go
+++ b/rpm/rpm.go
@@ -7,15 +7,14 @@ import (
 	"fmt"
 	"io"
 	"os"
-	"path/filepath"
 	"strconv"
 	"strings"
 	"time"
 
+	"github.com/google/rpmpack"
 	"github.com/goreleaser/chglog"
 	"github.com/goreleaser/nfpm/v2"
 	"github.com/goreleaser/nfpm/v2/files"
-	"github.com/goreleaser/nfpm/v2/internal/rpmpack"
 	"github.com/goreleaser/nfpm/v2/internal/sign"
 )
 
@@ -121,7 +120,11 @@ func (*RPM) Package(info *nfpm.Info, w io.Writer) (err error) {
 	}
 
 	if info.RPM.Signature.KeyFile != "" {
-		rpm.SetPGPSigner(sign.PGPSignerWithKeyID(info.RPM.Signature.KeyFile, info.RPM.Signature.KeyPassphrase, info.RPM.Signature.KeyID))
+		rpm.SetPGPSigner(sign.PGPSignerWithKeyID(
+			info.RPM.Signature.KeyFile,
+			info.RPM.Signature.KeyPassphrase,
+			info.RPM.Signature.KeyID,
+		))
 	}
 
 	if err = createFilesInsideRPM(info, rpm); err != nil {
@@ -236,6 +239,7 @@ func buildRPMMeta(info *nfpm.Info) (*rpmpack.RPMMetaData, error) {
 		URL:         info.Homepage,
 		Vendor:      info.Vendor,
 		Packager:    defaultTo(info.RPM.Packager, info.Maintainer),
+		Prefixes:    info.RPM.Prefixes,
 		Group:       info.RPM.Group,
 		Provides:    provides,
 		Recommends:  recommends,
@@ -373,7 +377,7 @@ func createFilesInsideRPM(info *nfpm.Info, rpm *rpmpack.RPM) (err error) {
 		}
 
 		// clean assures that even folders do not have a trailing slash
-		file.Name = filepath.Clean(file.Name)
+		file.Name = files.ToNixPath(file.Name)
 		rpm.AddFile(*file)
 
 	}
diff --git a/www/docs/cmd/nfpm.md b/www/docs/cmd/nfpm.md
index 05851dd..1f195b3 100644
--- a/www/docs/cmd/nfpm.md
+++ b/www/docs/cmd/nfpm.md
@@ -1,10 +1,10 @@
 # nfpm
 
-Packages apps on RPM, Deb and APK formats based on a YAML configuration file
+Packages apps on RPM, Deb, APK and Arch Linux formats based on a YAML configuration file
 
 ## Synopsis
 
-nFPM is a simple, 0-dependencies, deb, rpm and apk packager.
+nFPM is a simple and 0-dependencies deb, rpm, apk and arch linux packager written in Go.
 
 ## Options
 
diff --git a/www/docs/cmd/nfpm_completion.md b/www/docs/cmd/nfpm_completion.md
index 010ff59..961caf9 100644
--- a/www/docs/cmd/nfpm_completion.md
+++ b/www/docs/cmd/nfpm_completion.md
@@ -16,7 +16,7 @@ See each sub-command's help for details on how to use the generated script.
 
 ## See also
 
-* [nfpm](/cmd/nfpm/)	 - Packages apps on RPM, Deb and APK formats based on a YAML configuration file
+* [nfpm](/cmd/nfpm/)	 - Packages apps on RPM, Deb, APK and Arch Linux formats based on a YAML configuration file
 * [nfpm completion bash](/cmd/nfpm_completion_bash/)	 - Generate the autocompletion script for bash
 * [nfpm completion fish](/cmd/nfpm_completion_fish/)	 - Generate the autocompletion script for fish
 * [nfpm completion powershell](/cmd/nfpm_completion_powershell/)	 - Generate the autocompletion script for powershell
diff --git a/www/docs/cmd/nfpm_init.md b/www/docs/cmd/nfpm_init.md
index ea07c75..1742e7f 100644
--- a/www/docs/cmd/nfpm_init.md
+++ b/www/docs/cmd/nfpm_init.md
@@ -15,5 +15,5 @@ nfpm init [flags]
 
 ## See also
 
-* [nfpm](/cmd/nfpm/)	 - Packages apps on RPM, Deb and APK formats based on a YAML configuration file
+* [nfpm](/cmd/nfpm/)	 - Packages apps on RPM, Deb, APK and Arch Linux formats based on a YAML configuration file
 
diff --git a/www/docs/cmd/nfpm_jsonschema.md b/www/docs/cmd/nfpm_jsonschema.md
index 67e266b..a6231d4 100644
--- a/www/docs/cmd/nfpm_jsonschema.md
+++ b/www/docs/cmd/nfpm_jsonschema.md
@@ -15,5 +15,5 @@ nfpm jsonschema [flags]
 
 ## See also
 
-* [nfpm](/cmd/nfpm/)	 - Packages apps on RPM, Deb and APK formats based on a YAML configuration file
+* [nfpm](/cmd/nfpm/)	 - Packages apps on RPM, Deb, APK and Arch Linux formats based on a YAML configuration file
 
diff --git a/www/docs/cmd/nfpm_package.md b/www/docs/cmd/nfpm_package.md
index 4990a11..f18bff0 100644
--- a/www/docs/cmd/nfpm_package.md
+++ b/www/docs/cmd/nfpm_package.md
@@ -17,5 +17,5 @@ nfpm package [flags]
 
 ## See also
 
-* [nfpm](/cmd/nfpm/)	 - Packages apps on RPM, Deb and APK formats based on a YAML configuration file
+* [nfpm](/cmd/nfpm/)	 - Packages apps on RPM, Deb, APK and Arch Linux formats based on a YAML configuration file
 
diff --git a/www/docs/configuration.md b/www/docs/configuration.md
index f942b20..bc92998 100644
--- a/www/docs/configuration.md
+++ b/www/docs/configuration.md
@@ -94,6 +94,7 @@ description: Sample package
 vendor: GoReleaser
 
 # Package's homepage.
+# This will expand any env var you set in the field, e.g. homepage: ${CI_PROJECT_URL}
 homepage: https://nfpm.goreleaser.com
 
 # License.
@@ -326,6 +327,10 @@ rpm:
   # Compression algorithm (gzip (default), zstd, lzma or xz).
   compression: zstd
 
+  # Prefixes for relocatable packages.
+  prefixes:
+    - /usr/bin
+
   # The package is signed if a key_file is set
   signature:
     # PGP secret key (can also be ASCII-armored), the passphrase is taken
diff --git a/www/docs/static/schema.json b/www/docs/static/schema.json
index 1c8cb43..668be79 100644
--- a/www/docs/static/schema.json
+++ b/www/docs/static/schema.json
@@ -648,6 +648,13 @@
 					"packager": {
 						"type": "string",
 						"title": "organization that actually packaged the software"
+					},
+					"prefixes": {
+						"items": {
+							"type": "string"
+						},
+						"type": "array",
+						"title": "Prefixes for relocatable packages"
 					}
 				},
 				"additionalProperties": false,


Test patch:
diff --git a/acceptance_test.go b/acceptance_test.go
index 320e667..2127ea7 100644
--- a/acceptance_test.go
+++ b/acceptance_test.go
@@ -259,17 +259,37 @@ func TestDebSpecific(t *testing.T) {
 	}
 }
 
+func TestRPMSign(t *testing.T) {
+	t.Parallel()
+	for _, os := range []string{"centos9", "centos8", "fedora34", "fedora36", "fedora38"} {
+		os := os
+		t.Run(fmt.Sprintf("rpm/amd64/sign/%s", os), func(t *testing.T) {
+			t.Parallel()
+			accept(t, acceptParms{
+				Name:   fmt.Sprintf("sign_%s_amd64", os),
+				Conf:   "core.signed.yaml",
+				Format: "rpm",
+				Docker: dockerParams{
+					File:   fmt.Sprintf("rpm_%s.dockerfile", os),
+					Target: "signed",
+					Arch:   "amd64",
+				},
+			})
+		})
+	}
+}
+
 func TestDebSign(t *testing.T) {
 	t.Parallel()
 	for _, arch := range formatArchs["deb"] {
 		for _, sigtype := range []string{"dpkg-sig", "debsign"} {
 			func(t *testing.T, testSigtype, testArch string) {
-				t.Run(fmt.Sprintf("%s/%s", testArch, testSigtype), func(t *testing.T) {
+				t.Run(fmt.Sprintf("deb/%s/%s", testArch, testSigtype), func(t *testing.T) {
+					t.Parallel()
 					target := "signed"
 					if testSigtype == "dpkg-sig" {
 						target = "dpkg-signed"
 					}
-					t.Parallel()
 					if testArch == "ppc64le" && os.Getenv("NO_TEST_PPC64LE") == "true" {
 						t.Skip("ppc64le arch not supported in pipeline")
 					}
diff --git a/apk/apk_test.go b/apk/apk_test.go
index 0b6cb6c..56043f1 100644
--- a/apk/apk_test.go
+++ b/apk/apk_test.go
@@ -162,6 +162,7 @@ func TestDefaultWithArch(t *testing.T) {
 func TestApkPlatform(t *testing.T) {
 	f, err := os.CreateTemp(t.TempDir(), "test*.apk")
 	require.NoError(t, err)
+	t.Cleanup(func() { require.NoError(t, f.Close()) })
 	info := exampleInfo()
 	info.Platform = "darwin"
 	err = Default.Package(info, f)
diff --git a/arch/arch_test.go b/arch/arch_test.go
index d827eab..2e019de 100644
--- a/arch/arch_test.go
+++ b/arch/arch_test.go
@@ -105,6 +105,7 @@ func TestArch(t *testing.T) {
 func TestArchPlatform(t *testing.T) {
 	f, err := os.CreateTemp(t.TempDir(), "test*.pkg.tar.zstd")
 	require.NoError(t, err)
+	t.Cleanup(func() { require.NoError(t, f.Close()) })
 	info := exampleInfo()
 	info.Platform = "darwin"
 	err = Default.Package(info, f)
@@ -326,7 +327,7 @@ func TestGlob(t *testing.T) {
 			Contents: files.Contents{
 				{
 					Destination: "/usr/share/nfpm-repro",
-					Source:      "../files/*.go",
+					Source:      "../files/testdata/globtest/different-sizes/*/*.txt",
 					FileInfo: &files.ContentFileInfo{
 						Mode: 0o644,
 					},
@@ -361,12 +362,14 @@ func TestGlob(t *testing.T) {
 	require.NoError(t, err)
 
 	expected := map[string][]string{
-		"./.PKGINFO":                           {"mode=644", "size=188", "type=file"},
-		"./usr/":                               {"mode=755", "type=dir"},
-		"./usr/share/":                         {"mode=755", "type=dir"},
-		"./usr/share/nfpm-repro/":              {"mode=755", "type=dir"},
-		"./usr/share/nfpm-repro/files.go":      {"mode=644", "size=15900", "type=file", "md5digest=0c4e339bc678ab6b2b291ec3088e32a2", "sha256digest=27614421b69216e9d8ddd177210f2c9a71d8ff9d76cc2b1654276eccd04e84c2"},
-		"./usr/share/nfpm-repro/files_test.go": {"mode=644", "size=17741", "type=file", "md5digest=52818697d19e885bc2e5b37f2dc4a7fb", "sha256digest=fdbb35f8c11e118508ddd526c0a7797171ea100aa14337caddd7ab249ee91272"},
+		"./.PKGINFO":                     {"mode=644", "size=185", "type=file"},
+		"./usr/":                         {"mode=755", "type=dir"},
+		"./usr/share/":                   {"mode=755", "type=dir"},
+		"./usr/share/nfpm-repro/":        {"mode=755", "type=dir"},
+		"./usr/share/nfpm-repro/a/":      {"mode=755", "type=dir"},
+		"./usr/share/nfpm-repro/a/a.txt": {"mode=644", "size=4", "type=file", "md5digest=d3b07384d113edec49eaa6238ad5ff00", "sha256digest=b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c"},
+		"./usr/share/nfpm-repro/b/":      {"mode=755", "type=dir"},
+		"./usr/share/nfpm-repro/b/b.txt": {"mode=644", "size=7", "type=file", "md5digest=551a67cc6e06de1910061fe318d28f72", "sha256digest=73a2c64f9545172c1195efb6616ca5f7afd1df6f245407cafb90de3998a1c97f"},
 	}
 
 	for _, line := range strings.Split(string(mtreeContentBts), "\n") {
diff --git a/deb/deb_test.go b/deb/deb_test.go
index 63974e7..cc6acda 100644
--- a/deb/deb_test.go
+++ b/deb/deb_test.go
@@ -111,6 +111,7 @@ func TestDeb(t *testing.T) {
 func TestDebPlatform(t *testing.T) {
 	f, err := os.CreateTemp(t.TempDir(), "test*.deb")
 	require.NoError(t, err)
+	t.Cleanup(func() { require.NoError(t, f.Close()) })
 	info := exampleInfo()
 	info.Platform = "darwin"
 	err = Default.Package(info, f)
diff --git a/files/files_test.go b/files/files_test.go
index be8bd35..a674d5d 100644
--- a/files/files_test.go
+++ b/files/files_test.go
@@ -1,8 +1,10 @@
 package files_test
 
 import (
+	"fmt"
 	"os"
 	"path/filepath"
+	"runtime"
 	"strconv"
 	"strings"
 	"sync"
@@ -353,6 +355,9 @@ func withoutImplicitDirs(contents files.Contents) files.Contents {
 }
 
 func TestGlobbingWhenFilesHaveBrackets(t *testing.T) {
+	if runtime.GOOS == "windows" {
+		t.Skip("doesn't work on windows")
+	}
 	result, err := files.PrepareForPackager(files.Contents{
 		{
 			Source:      "./testdata/\\{test\\}/",
@@ -633,7 +638,7 @@ func TestRelevantFiles(t *testing.T) {
 func TestTree(t *testing.T) {
 	results, err := files.PrepareForPackager(files.Contents{
 		{
-			Source:      "testdata/tree",
+			Source:      filepath.Join("testdata", "tree"),
 			Destination: "/base",
 			Type:        files.TypeTree,
 		},
@@ -652,7 +657,7 @@ func TestTree(t *testing.T) {
 			Type:        files.TypeDir,
 		},
 		{
-			Source:      "testdata/tree/files/a",
+			Source:      filepath.Join("testdata", "tree", "files", "a"),
 			Destination: "/base/files/a",
 			Type:        files.TypeFile,
 		},
@@ -662,7 +667,7 @@ func TestTree(t *testing.T) {
 			Type:        files.TypeDir,
 		},
 		{
-			Source:      "testdata/tree/files/b/c",
+			Source:      filepath.Join("testdata", "tree", "files", "b", "c"),
 			Destination: "/base/files/b/c",
 			Type:        files.TypeFile,
 		},
@@ -697,11 +702,14 @@ func withoutFileInfo(contents files.Contents) files.Contents {
 }
 
 func TestAsRelativePath(t *testing.T) {
+	sep := fmt.Sprintf("%c", filepath.Separator)
 	testCases := map[string]string{
 		"/etc/foo/":         "etc/foo/",
 		"./etc/foo":         "etc/foo",
 		"./././foo/../bar/": "bar/",
-		"/":                 "",
+		sep:                 "",
+		sep + sep:           "",
+		sep + strings.Join([]string{"foo", "bar", "zazz"}, sep): "foo/bar/zazz",
 	}
 
 	for input, expected := range testCases {
@@ -710,11 +718,14 @@ func TestAsRelativePath(t *testing.T) {
 }
 
 func TestAsExplicitRelativePath(t *testing.T) {
+	sep := fmt.Sprintf("%c", filepath.Separator)
 	testCases := map[string]string{
 		"/etc/foo/":         "./etc/foo/",
 		"./etc/foo":         "./etc/foo",
 		"./././foo/../bar/": "./bar/",
-		"/":                 "./",
+		sep:                 "./",
+		sep:                 "./",
+		sep + strings.Join([]string{"foo", "bar", "zazz"}, sep): "./foo/bar/zazz",
 	}
 
 	for input, expected := range testCases {
diff --git a/internal/glob/glob_test.go b/internal/glob/glob_test.go
index d21daab..e06fd9d 100644
--- a/internal/glob/glob_test.go
+++ b/internal/glob/glob_test.go
@@ -51,7 +51,7 @@ func TestGlob(t *testing.T) {
 		files, err := Glob("./testdata/dir_a/dir_b/test_b.txt", "/foo/bar/", false)
 		require.NoError(t, err)
 		require.Len(t, files, 1)
-		require.Equal(t, "/foo/bar/test_b.txt", files["testdata/dir_a/dir_b/test_b.txt"])
+		require.Equal(t, "/foo/bar/test_b.txt", filepath.ToSlash(files["testdata/dir_a/dir_b/test_b.txt"]))
 	})
 
 	t.Run("to parent", func(t *testing.T) {
diff --git a/internal/rpmpack/dir_test.go b/internal/rpmpack/dir_test.go
deleted file mode 100644
index 1eadd8e..0000000
--- a/internal/rpmpack/dir_test.go
+++ /dev/null
@@ -1,63 +0,0 @@
-// Copyright 2019 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package rpmpack
-
-import (
-	"testing"
-
-	"github.com/google/go-cmp/cmp"
-)
-
-func TestDirIndex(t *testing.T) {
-	testCases := []struct {
-		name        string
-		before      []string
-		dir         string
-		wantGet     uint32
-		wantAllDirs []string
-	}{{
-		name:        "first",
-		dir:         "/first",
-		wantGet:     0,
-		wantAllDirs: []string{"/first"},
-	}, {
-		name:        "second",
-		dir:         "second",
-		before:      []string{"first"},
-		wantGet:     1,
-		wantAllDirs: []string{"first", "second"},
-	}, {
-		name:        "repeat",
-		dir:         "second",
-		before:      []string{"first", "second", "third"},
-		wantGet:     1,
-		wantAllDirs: []string{"first", "second", "third"},
-	}}
-	for _, tc := range testCases {
-		tc := tc
-		t.Run(tc.name, func(t *testing.T) {
-			d := newDirIndex()
-			for _, b := range tc.before {
-				d.Get(b)
-			}
-			if got := d.Get(tc.dir); got != tc.wantGet {
-				t.Errorf("d.Get(%q) = %d, want: %d", tc.dir, got, tc.wantGet)
-			}
-			if df := cmp.Diff(tc.wantAllDirs, d.AllDirs()); df != "" {
-				t.Errorf("d.AllDirs() diff (want->got):\n%s", df)
-			}
-		})
-	}
-}
diff --git a/internal/rpmpack/file_types_test.go b/internal/rpmpack/file_types_test.go
deleted file mode 100644
index 76f4c79..0000000
--- a/internal/rpmpack/file_types_test.go
+++ /dev/null
@@ -1,32 +0,0 @@
-package rpmpack
-
-import (
-	"testing"
-)
-
-func TestFileTypeSetting(t *testing.T) {
-	f := &RPMFile{
-		Name: "Test",
-	}
-
-	if f.Type != GenericFile {
-		t.Error("New RPMFile.Type should be a generic type")
-	}
-
-	f.Type |= ConfigFile
-	if (f.Type & ConfigFile) == 0 {
-		t.Error("Setting to config file should have the ConfigFile bitmask")
-	}
-}
-
-func TestFileTypeCombining(t *testing.T) {
-	f := &RPMFile{
-		Name: "Test",
-	}
-
-	f.Type |= ConfigFile | NoReplaceFile
-
-	if (f.Type&ConfigFile) == 0 || f.Type&NoReplaceFile == 0 {
-		t.Error("Combining file types should have the bitmask of both")
-	}
-}
diff --git a/internal/rpmpack/header_test.go b/internal/rpmpack/header_test.go
deleted file mode 100644
index 2d45a61..0000000
--- a/internal/rpmpack/header_test.go
+++ /dev/null
@@ -1,114 +0,0 @@
-// Copyright 2019 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package rpmpack
-
-import (
-	"fmt"
-	"testing"
-
-	"github.com/google/go-cmp/cmp"
-)
-
-func TestLead(t *testing.T) {
-	// Only check that the length is always right
-	names := []string{
-		"a",
-		"ab",
-		"abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc",
-		"abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabca",
-		"abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcab",
-		"abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc",
-	}
-	for _, n := range names {
-		if got := len(lead(n, "1-2")); got != 0x60 {
-			t.Errorf("len(lead(%s)) = %#x, want %#x", n, got, 0x60)
-		}
-	}
-}
-
-func TestEntry(t *testing.T) {
-	testCases := []struct {
-		name           string
-		value          interface{}
-		tag            int
-		offset         int
-		wantIndexBytes string
-		wantData       string
-	}{{
-		name:           "simple int",
-		value:          []int32{0x42},
-		tag:            0x010d,
-		offset:         5,
-		wantIndexBytes: "0000010d000000040000000500000001",
-		wantData:       "00000042",
-	}, {
-		name:           "simple string",
-		value:          "simple string",
-		tag:            0x010e,
-		offset:         0x111,
-		wantIndexBytes: "0000010e000000060000011100000001",
-		wantData:       "73696d706c6520737472696e6700",
-	}, {
-		name:           "string array",
-		value:          []string{"string", "array"},
-		tag:            0x010f,
-		offset:         0x222,
-		wantIndexBytes: "0000010f000000080000022200000002",
-		wantData:       "737472696e6700617272617900",
-	}}
-	for _, tc := range testCases {
-		tc := tc
-		t.Run(tc.name, func(t *testing.T) {
-			var e IndexEntry
-			switch v := tc.value.(type) {
-			case []string:
-				e = EntryStringSlice(v)
-			case string:
-				e = EntryString(v)
-			case []int32:
-				e = EntryInt32(v)
-			}
-			gotBytes := e.indexBytes(tc.tag, tc.offset)
-			if d := cmp.Diff(tc.wantIndexBytes, fmt.Sprintf("%x", gotBytes)); d != "" {
-				t.Errorf("entry.indexBytes() unexpected value (want->got):\n%s", d)
-			}
-			if d := cmp.Diff(tc.wantData, fmt.Sprintf("%x", e.data)); d != "" {
-				t.Errorf("entry.data unexpected value (want->got):\n%s", d)
-			}
-		})
-	}
-}
-
-func TestIndex(t *testing.T) {
-	i := newIndex(0x3e)
-	i.AddEntries(map[int]IndexEntry{
-		0x1111: EntryUint16([]uint16{0x4444, 0x8888, 0xcccc}),
-		0x2222: EntryUint32([]uint32{0x3333, 0x5555}),
-	})
-	got, err := i.Bytes()
-	if err != nil {
-		t.Errorf("i.Bytes() returned error: %v", err)
-	}
-	want := "8eade80100000000" + // header lead
-		"0000000300000020" + // count and size
-		"0000003e000000070000001000000010" + // eigen header entry
-		"00001111000000030000000000000003" +
-		"00002222000000040000000800000002" +
-		"44448888cccc00000000333300005555" + // values, with padding
-		"0000003e00000007ffffffd000000010" // eigen header value
-	if d := cmp.Diff(want, fmt.Sprintf("%x", got)); d != "" {
-		t.Errorf("i.Bytes() unexpected value (want-> got): \n%s", d)
-	}
-}
diff --git a/internal/rpmpack/rpm_test.go b/internal/rpmpack/rpm_test.go
deleted file mode 100644
index 47b91b0..0000000
--- a/internal/rpmpack/rpm_test.go
+++ /dev/null
@@ -1,182 +0,0 @@
-package rpmpack
-
-import (
-	"io"
-	"reflect"
-	"testing"
-
-	"github.com/google/go-cmp/cmp"
-
-	"github.com/klauspost/compress/zstd"
-	gzip "github.com/klauspost/pgzip"
-	"github.com/ulikunitz/xz"
-	"github.com/ulikunitz/xz/lzma"
-)
-
-func TestFileOwner(t *testing.T) {
-	r, err := NewRPM(RPMMetaData{})
-	if err != nil {
-		t.Fatalf("NewRPM returned error %v", err)
-	}
-	group := "testGroup"
-	user := "testUser"
-
-	r.AddFile(RPMFile{
-		Name:  "/usr/hello",
-		Body:  []byte("content of the file"),
-		Group: group,
-		Owner: user,
-	})
-
-	if err := r.Write(io.Discard); err != nil {
-		t.Errorf("NewRPM returned error %v", err)
-	}
-	if r.fileowners[0] != user {
-		t.Errorf("File owner shoud be %s but is %s", user, r.fileowners[0])
-	}
-	if r.filegroups[0] != group {
-		t.Errorf("File owner shoud be %s but is %s", group, r.filegroups[0])
-	}
-}
-
-// https://github.com/google/rpmpack/issues/49
-func Test100644(t *testing.T) {
-	r, err := NewRPM(RPMMetaData{})
-	if err != nil {
-		t.Fatalf("NewRPM returned error %v", err)
-	}
-	r.AddFile(RPMFile{
-		Name: "/usr/share/hello",
-		Body: []byte("content of the file"),
-		Mode: 0o100644,
-	})
-
-	if err := r.Write(io.Discard); err != nil {
-		t.Errorf("Write returned error %v", err)
-	}
-	if r.filemodes[0] != 0o100644 {
-		t.Errorf("file mode want 0100644, got %o", r.filemodes[0])
-	}
-	if r.filelinktos[0] != "" {
-		t.Errorf("linktos want empty (not a symlink), got %q", r.filelinktos[0])
-	}
-}
-
-func TestCompression(t *testing.T) {
-	testCases := []struct {
-		Type           string
-		Compressors    []string
-		ExpectedWriter io.Writer
-	}{
-		{
-			Type: "gzip",
-			Compressors: []string{
-				"", "gzip", "gzip:1", "gzip:2", "gzip:3",
-				"gzip:4", "gzip:5", "gzip:6", "gzip:7", "gzip:8", "gzip:9",
-			},
-			ExpectedWriter: &gzip.Writer{},
-		},
-		{
-			Type:           "gzip",
-			Compressors:    []string{"gzip:fast", "gzip:10"},
-			ExpectedWriter: nil, // gzip requires an integer level from -2 to 9
-		},
-		{
-			Type:           "lzma",
-			Compressors:    []string{"lzma"},
-			ExpectedWriter: &lzma.Writer{},
-		},
-		{
-			Type:           "lzma",
-			Compressors:    []string{"lzma:fast", "lzma:1"},
-			ExpectedWriter: nil, // lzma does not support specifying the compression level
-		},
-		{
-			Type:           "xz",
-			Compressors:    []string{"xz"},
-			ExpectedWriter: &xz.Writer{},
-		},
-		{
-			Type:           "xz",
-			Compressors:    []string{"xz:fast", "xz:1"},
-			ExpectedWriter: nil, // xz does not support specifying the compression level
-		},
-		{
-			Type: "zstd",
-			Compressors: []string{
-				"zstd", "zstd:fastest", "zstd:default", "zstd:better",
-				"zstd:best", "zstd:BeSt", "zstd:0", "zstd:4", "zstd:8", "zstd:15",
-			},
-			ExpectedWriter: &zstd.Encoder{},
-		},
-		{
-			Type:           "zstd",
-			Compressors:    []string{"xz:worst"},
-			ExpectedWriter: nil, // only integers levels or one of the pre-defined string values
-		},
-	}
-
-	for _, testCase := range testCases {
-		testCase := testCase
-
-		for _, compressor := range testCase.Compressors {
-			t.Run(compressor, func(t *testing.T) {
-				r, err := NewRPM(RPMMetaData{
-					Compressor: compressor,
-				})
-				if err != nil {
-					if testCase.ExpectedWriter == nil {
-						return // an error is expected
-					}
-
-					t.Fatalf("NewRPM returned error %v", err)
-				}
-
-				if testCase.ExpectedWriter == nil {
-					t.Fatalf("compressor %q should have produced an error", compressor)
-				}
-
-				if r.RPMMetaData.Compressor != testCase.Type {
-					t.Fatalf("expected compressor %q, got %q", compressor,
-						r.RPMMetaData.Compressor)
-				}
-
-				expectedWriterType := reflect.Indirect(reflect.ValueOf(
-					testCase.ExpectedWriter)).String()
-				actualWriterType := reflect.Indirect(reflect.ValueOf(
-					r.compressedPayload)).String()
-
-				if expectedWriterType != actualWriterType {
-					t.Fatalf("expected writer to be %T, got %T instead",
-						testCase.ExpectedWriter, r.compressedPayload)
-				}
-			})
-		}
-	}
-}
-
-func TestAllowListDirs(t *testing.T) {
-	r, err := NewRPM(RPMMetaData{})
-	if err != nil {
-		t.Fatalf("NewRPM returned error %v", err)
-	}
-
-	r.AddFile(RPMFile{
-		Name: "/usr/share/dir1",
-		Mode: 0o40000,
-	})
-	r.AddFile(RPMFile{
-		Name: "/usr/share/dir2",
-		Mode: 0o40000,
-	})
-
-	r.AllowListDirs(map[string]bool{"/usr/share/dir1": true})
-
-	if err := r.Write(io.Discard); err != nil {
-		t.Errorf("NewRPM returned error %v", err)
-	}
-	expected := map[string]RPMFile{"/usr/share/dir1": {Name: "/usr/share/dir1", Mode: 0o40000}}
-	if d := cmp.Diff(expected, r.files); d != "" {
-		t.Errorf("Expected dirs differs (want->got):\n%v", d)
-	}
-}
diff --git a/internal/rpmpack/sense_test.go b/internal/rpmpack/sense_test.go
deleted file mode 100644
index 17f7620..0000000
--- a/internal/rpmpack/sense_test.go
+++ /dev/null
@@ -1,81 +0,0 @@
-package rpmpack
-
-import (
-	"testing"
-)
-
-func TestNewRelation(t *testing.T) {
-	testCases := []struct {
-		input, output string
-		errExpected   bool
-	}{
-		{
-			input:  "python >= 3.7",
-			output: "python>=3.7",
-		},
-		{
-			input:  "python",
-			output: "python",
-		},
-		{
-			input:  "python=2",
-			output: "python=2",
-		},
-		{
-			input:  "python >=3.5",
-			output: "python>=3.5",
-		},
-		{
-			input:       "python >< 3.5",
-			output:      "",
-			errExpected: true,
-		},
-		{
-			input:       "python <> 3.5",
-			output:      "",
-			errExpected: true,
-		},
-		{
-			input:       "python == 3.5",
-			output:      "",
-			errExpected: true,
-		},
-		{
-			input:       "python =< 3.5",
-			output:      "",
-			errExpected: true,
-		},
-		{
-			input:       "python => 3.5",
-			output:      "",
-			errExpected: true,
-		},
-	}
-
-	for _, tc := range testCases {
-		testCase := tc
-		t.Run(testCase.input, func(tt *testing.T) {
-			relation, err := NewRelation(testCase.input)
-			switch {
-			case testCase.errExpected && err == nil:
-				tt.Errorf("%s should have returned an error", testCase.input)
-				return
-			case !testCase.errExpected && err != nil:
-				tt.Errorf("%s should not have returned an error: %v", testCase.input, err)
-				return
-			case testCase.errExpected && err != nil:
-				return
-			}
-
-			if relation == nil {
-				tt.Errorf("%s should not have returned a nil relation", testCase.input)
-				return
-			}
-
-			val := relation.String()
-			if !testCase.errExpected && val != testCase.output {
-				tt.Errorf("%s should have returned %s not %s", testCase.input, testCase.output, val)
-			}
-		})
-	}
-}
diff --git a/internal/sign/pgp_test.go b/internal/sign/pgp_test.go
index 77d5701..81cf03b 100644
--- a/internal/sign/pgp_test.go
+++ b/internal/sign/pgp_test.go
@@ -102,7 +102,7 @@ func TestArmoredDetachSignAndVerify(t *testing.T) {
 }
 
 func TestPGPSignerError(t *testing.T) {
-	_, err := PGPSigner("/does/not/exist", "")([]byte("data"))
+	_, err := PGPSignerWithKeyID("/does/not/exist", "", nil)([]byte("data"))
 	require.Error(t, err)
 
 	var expectedError *nfpm.ErrSigningFailure
diff --git a/nfpm_test.go b/nfpm_test.go
index b3c8b5d..1f866c0 100644
--- a/nfpm_test.go
+++ b/nfpm_test.go
@@ -366,6 +366,7 @@ func TestOptionsFromEnvironment(t *testing.T) {
 		vendor          = "GoReleaser"
 		packager        = "nope"
 		maintainerEmail = "nope@example.com"
+		homepage        = "https://nfpm.goreleaser.com"
 		vcsURL          = "https://github.com/goreleaser/nfpm.git"
 	)
 
@@ -419,6 +420,13 @@ maintainer: '"$GIT_COMMITTER_NAME" <$GIT_COMMITTER_EMAIL>'
 		require.Equal(t, vendor, info.Vendor)
 	})
 
+	t.Run("homepage", func(t *testing.T) {
+		t.Setenv("CI_PROJECT_URL", homepage)
+		info, err := nfpm.Parse(strings.NewReader("name: foo\nhomepage: $CI_PROJECT_URL"))
+		require.NoError(t, err)
+		require.Equal(t, homepage, info.Homepage)
+	})
+
 	t.Run("global passphrase", func(t *testing.T) {
 		t.Setenv("NFPM_PASSPHRASE", globalPass)
 		info, err := nfpm.Parse(strings.NewReader("name: foo"))
diff --git a/rpm/rpm_test.go b/rpm/rpm_test.go
index 84e803a..3c255aa 100644
--- a/rpm/rpm_test.go
+++ b/rpm/rpm_test.go
@@ -77,6 +77,7 @@ func exampleInfo() *nfpm.Info {
 				PostRemove:  "../testdata/scripts/postremove.sh",
 			},
 			RPM: nfpm.RPM{
+				Prefixes: []string{"/opt"},
 				Scripts: nfpm.RPMScripts{
 					PreTrans:  "../testdata/scripts/pretrans.sh",
 					PostTrans: "../testdata/scripts/posttrans.sh",
@@ -145,6 +146,7 @@ func TestRPM(t *testing.T) {
 func TestRPMPlatform(t *testing.T) {
 	f, err := os.CreateTemp(t.TempDir(), "test*.rpm")
 	require.NoError(t, err)
+	t.Cleanup(func() { require.NoError(t, f.Close()) })
 	info := exampleInfo()
 	info.Platform = "darwin"
 	require.NoError(t, Default.Package(info, f))
diff --git a/testdata/acceptance/core.complex.yaml b/testdata/acceptance/core.complex.yaml
index 3b15e66..76330fc 100644
--- a/testdata/acceptance/core.complex.yaml
+++ b/testdata/acceptance/core.complex.yaml
@@ -4,15 +4,15 @@ platform: "linux"
 version: "v1.2.3-beta"
 maintainer: "Foo Bar"
 depends:
-- bash
+  - bash
 provides:
-- fake
+  - fake
 recommends:
-- fish
+  - fish
 replaces:
-- foo
+  - foo
 suggests:
-- zsh
+  - zsh
 description: |
   Foo bar
     Multiple lines
@@ -20,47 +20,47 @@ vendor: "foobar"
 homepage: "https://foobar.org"
 license: "MIT"
 contents:
-- src: ./testdata/fake
-  dst: /usr/bin/fake
-- src: ./testdata/acceptance/folder/*
-  dst: /usr/share/whatever/folder
-- src: ./testdata/whatever.conf
-  dst: /etc/foo/whatever.conf
-  type: config
-- src: ./testdata/something
-  dst: /etc/something
-- dst: /usr/foo/bar/something
-  type: dir
-- dst: /var/log/whatever
-  type: dir
-- dst: /usr/share/foo
-  type: dir
+  - src: ./testdata/fake
+    dst: /usr/bin/fake
+  - src: ./testdata/acceptance/folder/*
+    dst: /usr/share/whatever/folder
+  - src: ./testdata/whatever.conf
+    dst: /etc/foo/whatever.conf
+    type: config
+  - src: ./testdata/something
+    dst: /etc/something
+  - dst: /usr/foo/bar/something
+    type: dir
+  - dst: /var/log/whatever
+    type: dir
+  - dst: /usr/share/foo
+    type: dir
 
-# Packager specifics:
+  # Packager specifics:
 
-- packager: deb
-  src: ./testdata/fake
-  dst: /usr/bin/fake2
-  file_info:
-    mode: 04755
+  - packager: deb
+    src: ./testdata/fake
+    dst: /usr/bin/fake2
+    file_info:
+      mode: 04755
 
-- packager: rpm
-  src: ./testdata/fake
-  dst: /usr/bin/fake2
-  file_info:
-    mode: 04755
+  - packager: rpm
+    src: ./testdata/fake
+    dst: /usr/bin/fake2
+    file_info:
+      mode: 04755
 
-- packager: apk
-  src: ./testdata/fake
-  dst: /usr/bin/fake2
-  file_info:
-    mode: 04755
+  - packager: apk
+    src: ./testdata/fake
+    dst: /usr/bin/fake2
+    file_info:
+      mode: 04755
 
-- packager: archlinux
-  src: ./testdata/fake
-  dst: /usr/bin/fake2
-  file_info:
-    mode: 04755
+  - packager: archlinux
+    src: ./testdata/fake
+    dst: /usr/bin/fake2
+    file_info:
+      mode: 04755
 
 scripts:
   preinstall: ./testdata/acceptance/scripts/preinstall.sh
@@ -68,6 +68,7 @@ scripts:
   preremove: ./testdata/acceptance/scripts/preremove.sh
   postremove: ./testdata/acceptance/scripts/postremove.sh
 rpm:
+  prefixes: ["/usr/bin"]
   scripts:
     pretrans: ./testdata/acceptance/scripts/pretrans.sh
     posttrans: ./testdata/acceptance/scripts/posttrans.sh
diff --git a/testdata/acceptance/core.signed.yaml b/testdata/acceptance/core.signed.yaml
index 23f8764..3a6b06d 100644
--- a/testdata/acceptance/core.signed.yaml
+++ b/testdata/acceptance/core.signed.yaml
@@ -7,8 +7,8 @@ description: This package is signed
 vendor: "FooBarCorp"
 homepage: "http://example.com"
 contents:
-- src: ./testdata/fake
-  dst: /usr/bin/fake
+  - src: ./testdata/fake
+    dst: /usr/bin/fake
 deb:
   signature:
     key_file: ./internal/sign/testdata/privkey_unprotected.asc
diff --git a/testdata/acceptance/deb.dockerfile b/testdata/acceptance/deb.dockerfile
index 9df34a2..e8a5bc0 100644
--- a/testdata/acceptance/deb.dockerfile
+++ b/testdata/acceptance/deb.dockerfile
@@ -1,4 +1,4 @@
-FROM debian AS test_base
+FROM debian:11 AS test_base
 ARG package
 RUN echo "${package}"
 COPY ${package} /tmp/foo.deb
diff --git a/testdata/acceptance/rpm.dockerfile b/testdata/acceptance/rpm.dockerfile
index 837f879..5f517f7 100644
--- a/testdata/acceptance/rpm.dockerfile
+++ b/testdata/acceptance/rpm.dockerfile
@@ -35,11 +35,12 @@ RUN test -f /usr/share/whatever/folder2/file2
 
 
 # ---- complex test ----
-FROM min AS complex
+FROM test_base AS complex
+RUN rpm --prefix=/opt -ivh /tmp/foo.rpm
 RUN test "$(rpm -qp --recommends /tmp/foo.rpm)" = "fish"
 RUN test "$(rpm -qp --suggests /tmp/foo.rpm)" = "zsh"
 RUN test "$(rpm -qp --requires /tmp/foo.rpm)" = "bash"
-RUN test -e /usr/bin/fake
+RUN test -e /opt/fake
 RUN test -f /etc/foo/whatever.conf
 RUN test -d /usr/share/whatever/folder
 RUN test -f /usr/share/whatever/folder/file1
@@ -55,7 +56,7 @@ RUN test -f /etc/something/a
 RUN test -f /etc/something/b
 RUN test -d /etc/something/c
 RUN test -f /etc/something/c/d
-RUN test $(stat -c %a /usr/bin/fake2) -eq 4755
+RUN test $(stat -c %a /opt/fake2) -eq 4755
 RUN test -f /tmp/preinstall-proof
 RUN test -f /tmp/postinstall-proof
 RUN test -f /tmp/pretrans-proof
@@ -65,8 +66,8 @@ RUN test ! -f /tmp/postremove-proof
 RUN echo wat >> /etc/foo/whatever.conf
 RUN rpm -e foo
 RUN test -f /etc/foo/whatever.conf.rpmsave
-RUN test ! -f /usr/bin/fake
-RUN test ! -f /usr/bin/fake2
+RUN test ! -f /opt/fake
+RUN test ! -f /opt/fake2
 RUN test -f /tmp/preremove-proof
 RUN test -f /tmp/postremove-proof
 RUN test ! -d /var/log/whatever
diff --git a/testdata/acceptance/rpm_centos8.dockerfile b/testdata/acceptance/rpm_centos8.dockerfile
new file mode 100644
index 0000000..908f357
--- /dev/null
+++ b/testdata/acceptance/rpm_centos8.dockerfile
@@ -0,0 +1,20 @@
+FROM quay.io/centos/centos:stream8 AS test_base
+ARG package
+RUN yum install -y createrepo yum-utils
+RUN echo "${package}"
+COPY ${package} /tmp/foo.rpm
+
+FROM test_base AS signed
+COPY keys/pubkey.asc /tmp/pubkey.asc
+RUN rpm --import /tmp/pubkey.asc
+RUN rpm -q gpg-pubkey --qf '%{NAME}-%{VERSION}-%{RELEASE}\t%{SUMMARY}\n'
+RUN rpm -vK /tmp/foo.rpm
+RUN rpm -vK /tmp/foo.rpm | grep "RSA/SHA256 Signature, key ID 15bd80b3: OK"
+RUN rpm -K /tmp/foo.rpm
+RUN rpm -K /tmp/foo.rpm | grep -E "(?:pgp|digests signatures) OK"
+
+RUN rm -rf /etc/yum.repos.d/*.repo
+COPY keys/test.rpm.repo /etc/yum.repos.d/test.rpm.repo
+RUN createrepo /tmp
+RUN yum install -y foo
+
diff --git a/testdata/acceptance/rpm_centos9.dockerfile b/testdata/acceptance/rpm_centos9.dockerfile
new file mode 100644
index 0000000..14d1c78
--- /dev/null
+++ b/testdata/acceptance/rpm_centos9.dockerfile
@@ -0,0 +1,20 @@
+FROM quay.io/centos/centos:stream9 AS test_base
+RUN yum install -y createrepo yum-utils
+ARG package
+RUN echo "${package}"
+COPY ${package} /tmp/foo.rpm
+
+FROM test_base AS signed
+COPY keys/pubkey.asc /tmp/pubkey.asc
+RUN rpm --import /tmp/pubkey.asc
+RUN rpm -q gpg-pubkey --qf '%{NAME}-%{VERSION}-%{RELEASE}\t%{SUMMARY}\n'
+RUN rpm -vK /tmp/foo.rpm
+RUN rpm -vK /tmp/foo.rpm | grep "RSA/SHA256 Signature, key ID 15bd80b3: OK"
+RUN rpm -K /tmp/foo.rpm
+RUN rpm -K /tmp/foo.rpm | grep -E "(?:pgp|digests signatures) OK"
+
+RUN rm -rf /etc/yum.repos.d/*.repo
+COPY keys/test.rpm.repo /etc/yum.repos.d/test.rpm.repo
+RUN createrepo /tmp
+RUN yum install -y foo
+
diff --git a/testdata/acceptance/rpm_fedora34.dockerfile b/testdata/acceptance/rpm_fedora34.dockerfile
new file mode 100644
index 0000000..961229e
--- /dev/null
+++ b/testdata/acceptance/rpm_fedora34.dockerfile
@@ -0,0 +1,20 @@
+FROM fedora:34 AS test_base
+RUN yum install -y createrepo yum-utils
+ARG package
+RUN echo "${package}"
+COPY ${package} /tmp/foo.rpm
+
+FROM test_base AS signed
+COPY keys/pubkey.asc /tmp/pubkey.asc
+RUN rpm --import /tmp/pubkey.asc
+RUN rpm -q gpg-pubkey --qf '%{NAME}-%{VERSION}-%{RELEASE}\t%{SUMMARY}\n'
+RUN rpm -vK /tmp/foo.rpm
+RUN rpm -vK /tmp/foo.rpm | grep "RSA/SHA256 Signature, key ID 15bd80b3: OK"
+RUN rpm -K /tmp/foo.rpm
+RUN rpm -K /tmp/foo.rpm | grep -E "(?:pgp|digests signatures) OK"
+
+RUN rm -rf /etc/yum.repos.d/*.repo
+COPY keys/test.rpm.repo /etc/yum.repos.d/test.rpm.repo
+RUN createrepo /tmp
+RUN yum install -y foo
+
diff --git a/testdata/acceptance/rpm_fedora36.dockerfile b/testdata/acceptance/rpm_fedora36.dockerfile
new file mode 100644
index 0000000..855d25a
--- /dev/null
+++ b/testdata/acceptance/rpm_fedora36.dockerfile
@@ -0,0 +1,20 @@
+FROM fedora:36 AS test_base
+RUN yum install -y createrepo yum-utils
+ARG package
+RUN echo "${package}"
+COPY ${package} /tmp/foo.rpm
+
+FROM test_base AS signed
+COPY keys/pubkey.asc /tmp/pubkey.asc
+RUN rpm --import /tmp/pubkey.asc
+RUN rpm -q gpg-pubkey --qf '%{NAME}-%{VERSION}-%{RELEASE}\t%{SUMMARY}\n'
+RUN rpm -vK /tmp/foo.rpm
+RUN rpm -vK /tmp/foo.rpm | grep "RSA/SHA256 Signature, key ID 15bd80b3: OK"
+RUN rpm -K /tmp/foo.rpm
+RUN rpm -K /tmp/foo.rpm | grep -E "(?:pgp|digests signatures) OK"
+
+RUN rm -rf /etc/yum.repos.d/*.repo
+COPY keys/test.rpm.repo /etc/yum.repos.d/test.rpm.repo
+RUN createrepo /tmp
+RUN yum install -y foo
+
diff --git a/testdata/acceptance/rpm_fedora38.dockerfile b/testdata/acceptance/rpm_fedora38.dockerfile
new file mode 100644
index 0000000..ece1dcf
--- /dev/null
+++ b/testdata/acceptance/rpm_fedora38.dockerfile
@@ -0,0 +1,20 @@
+FROM fedora:38 AS test_base
+RUN yum install -y createrepo yum-utils
+ARG package
+RUN echo "${package}"
+COPY ${package} /tmp/foo.rpm
+
+FROM test_base AS signed
+COPY keys/pubkey.asc /tmp/pubkey.asc
+RUN rpm --import /tmp/pubkey.asc
+RUN rpm -q gpg-pubkey --qf '%{NAME}-%{VERSION}-%{RELEASE}\t%{SUMMARY}\n'
+RUN rpm -vK /tmp/foo.rpm
+RUN rpm -vK /tmp/foo.rpm | grep "RSA/SHA256 Signature, key ID 15bd80b3: OK"
+RUN rpm -K /tmp/foo.rpm
+RUN rpm -K /tmp/foo.rpm | grep -E "(?:pgp|digests signatures) OK"
+
+RUN rm -rf /etc/yum.repos.d/*.repo
+COPY keys/test.rpm.repo /etc/yum.repos.d/test.rpm.repo
+RUN createrepo /tmp
+RUN yum install -y foo
+
diff --git a/www/docs/static/latest b/www/docs/static/latest
index 438196f..1423e98 100644
--- a/www/docs/static/latest
+++ b/www/docs/static/latest
@@ -1,1 +1,1 @@
-v2.30.1
+v2.31.0