Repository: goreleaser/nfpm (go)

Issue:
Add support for creating relocatable RPM packages using nfpm
### Is your feature request related to a problem? Please describe.

I am currently using nfpm to create RPM packages, but I have encountered a limitation with the tool: it does not appear to support creating relocatable RPM packages. This restricts the flexibility and usability of the RPM packages generated by nfpm, as users are not able to easily relocate the package to a different installation prefix.

### Describe the solution you'd like

I would like to request the addition of a feature that enables the creation of relocatable RPM packages with nfpm. By supporting relocatable packages, users would have the flexibility to install the RPM in a custom location of their choice.

According to the RPM documentation, a relocatable RPM package is defined as follows:

> "A relocatable package is an RPM package that can be installed in a different location (or directory) than the default one. This can be useful when a user wants to have the same package installed in multiple locations or when the user does not have write access to the default installation location. To create a relocatable package, the RPM must be built to allow relocation, and the user must specify the installation prefix during the installation."

To implement this feature, nfpm should provide an option or configuration setting that allows users to specify whether the generated RPM package should be relocatable or not. Additionally, it should include the necessary metadata within the RPM package to enable relocation during installation.

### Describe alternatives you've considered

An alternative would be to manually create and modify the RPM package to support relocation, but this is not as efficient or convenient as having native support within nfpm.

### Search

- [X] I did search for other open and closed issues before opening this.

### Code of Conduct

- [X] I agree to follow this project's Code of Conduct

### Additional context

This feature would greatly enhance the usability and flexibility of the RPM packages generated by nfpm, allowing users to have more control over their installations and making the tool even more powerful.

Thank you for considering this feature request.

Pull request:
feat(rpm): support relocatable packages
closes #640

Patch:
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index cb2575c..7a443c9 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, windows-latest]
     runs-on: ${{ matrix.os }}
     steps:
       - uses: actions/checkout@v3
@@ -60,11 +60,50 @@ jobs:
         with:
           token: ${{ secrets.CODECOV_TOKEN }}
           file: ./coverage.txt
+  build-pkgs-on-windows:
+    runs-on: windows-latest
+    steps:
+      - uses: actions/checkout@v3
+      - uses: actions/setup-go@v4
+        with:
+          go-version: stable
+      - run: mkdir dist
+      - 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: go run ./cmd/nfpm/... pkg -f ./testdata/acceptance/core.complex.yaml -p deb -t ./dist/foo.deb
+      - run: go run ./cmd/nfpm/... pkg -f ./testdata/acceptance/core.complex.yaml -p rpm -t ./dist/foo.rpm
+      - run: go run ./cmd/nfpm/... pkg -f ./testdata/acceptance/core.complex.yaml -p apk -t ./dist/foo.apk
+      - run: go run ./cmd/nfpm/... pkg -f ./testdata/acceptance/core.complex.yaml -p archlinux -t ./dist/foo.pkg.tar.zst
+  install-pkgs-built-on-windows:
+    runs-on: ubuntu-latest
+    needs: [build-pkgs-on-windows]
+    steps:
+      - uses: docker/setup-qemu-action@v2
+      - uses: actions/checkout@v3
+      - 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: ls -lha dist
+      - run: docker run --rm --workdir /tmp -v $PWD/dist:/tmp fedora rpm -ivh foo.rpm
+      - run: docker run --rm --workdir /tmp -v $PWD/dist:/tmp ubuntu dpkg -i foo.deb
+      - run: docker run --rm --workdir /tmp -v $PWD/dist:/tmp archlinux pacman --noconfirm -U foo.pkg.tar.zst
+      - run: docker run --rm --workdir /tmp -v $PWD/dist:/tmp alpine apk add --allow-untrusted foo.apk
   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/nfpm.go b/nfpm.go
index da20ba2..f46d291 100644
--- a/nfpm.go
+++ b/nfpm.go
@@ -336,6 +336,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 9c5c625..1da8bc7 100644
--- a/rpm/rpm.go
+++ b/rpm/rpm.go
@@ -240,6 +240,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,
diff --git a/www/docs/configuration.md b/www/docs/configuration.md
index f942b20..7c89b47 100644
--- a/www/docs/configuration.md
+++ b/www/docs/configuration.md
@@ -326,6 +326,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


Test patch:
diff --git a/rpm/rpm_test.go b/rpm/rpm_test.go
index 84e803a..6b403e9 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",
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/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