Repository: goreleaser/nfpm
Pull request #508 (2022-05-16 20:57:52)

feat: implement dpkg-sig Package signing
As mentioned [here](https://github.com/goreleaser/nfpm/issues/212) Debsign package verification is a PITA.

Herewith my proposal to implement dpkg-sig style package verification.
As mentioned in the original thread, debsign and dpkg-sig are mutually exclusive, but it would be nice to be able to choose between them.

SasSwart: I noticed an issue with how I'm populating the template.
I'm converting the PR to draft for the moment so I can fix that.
SasSwart: Template issue fixed.
Ready for review
caarlos0: Hey! Thanks for the pr! I wonder if we can leave only the new method and remove the deprecated one? Does latest debian supports the new one already? Any major distribution that doesn't?
SasSwart: I have fixed the linting issue and added some more configs. All tests, linting etc. should now pass.

I've also done some more reading and I am convinced that the other signing method will have to be kept.
It still is the default method supported by dpkg.
That being said, I'm pretty sure neither are used much.

Debian/Apt seem to depend far more on signing the Release files in their repositories than they depend on signing packages themselves. Packages are usually only signed when distributed without a repository. Even in those cases, the big boys like Microsoft seem not to bother with signing the package that is available for download. See https://code.visualstudio.com/ for an example.

Debian's package building depends pretty heavily on Source packages, which have their own detached control file (called a .dsc file) which is signed. None of this is as yet supported by nfpm.

I have seen another issue in this repository about supporting Debian source packages. The response seems to have been that you do not think that belongs within the purview of nfpm.

I disagree with this. I think there is a way that we can reasonably support building source packages. I'll attempt a PR and submit one if its possible.

My source for most of the above:
https://debian-handbook.info/browse/stable/sect.source-package-structure.html

Patch:
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 3e09650..9e018cb 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -12,7 +12,7 @@ jobs:
   unit-tests:
     strategy:
       matrix:
-        go-version: [ 1.17 ]
+        go-version: [ 1.18 ]
         os: [ ubuntu-latest, macos-latest, windows-latest ]
     runs-on: ${{ matrix.os }}
     steps:
@@ -44,7 +44,7 @@ jobs:
   Acceptance-Tests:
     strategy:
       matrix:
-        go-version: [ 1.17 ]
+        go-version: [ 1.18 ]
         pkgFormat: [ deb, rpm, apk ]
         pkgPlatform: [ amd64, arm64, 386, ppc64le, armv6, armv7, s390x ]
     runs-on: ubuntu-latest
@@ -76,7 +76,7 @@ jobs:
   goreleaser:
     strategy:
       matrix:
-        go-version: [ 1.17 ]
+        go-version: [ 1.18 ]
     runs-on: ubuntu-latest
     if: startsWith(github.ref, 'refs/tags/')
     needs:
@@ -121,7 +121,7 @@ jobs:
           registry: ghcr.io
           username: ${{ github.repository_owner }}
           password: ${{ secrets.GITHUB_TOKEN }}
-      - uses: goreleaser/goreleaser-action@v2
+      - uses: goreleaser/goreleaser-action@v3
         if: success()
         with:
           version: latest
diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml
index e5360dc..ebdf272 100644
--- a/.github/workflows/docs.yml
+++ b/.github/workflows/docs.yml
@@ -21,7 +21,7 @@ jobs:
           repo-token: ${{ secrets.GITHUB_TOKEN }}
       - uses: actions/setup-go@v3
         with:
-          go-version: 1.17
+          go-version: 1.18
       - run: task docs:build
       - uses: wjdp/htmltest-action@master
         with:
diff --git a/.github/workflows/generate.yml b/.github/workflows/generate.yml
index 0a9232f..ed64edd 100644
--- a/.github/workflows/generate.yml
+++ b/.github/workflows/generate.yml
@@ -21,7 +21,7 @@ jobs:
         name: setup go
         uses: actions/setup-go@v3
         with:
-          go-version: 1.17
+          go-version: 1.18
       -
         name: Set up Task
         uses: arduino/setup-task@v1
diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
index 7283179..d608127 100644
--- a/.github/workflows/lint.yml
+++ b/.github/workflows/lint.yml
@@ -16,7 +16,7 @@ jobs:
     steps:
       - uses: actions/setup-go@v3
         with:
-          go-version: ~1.17
+          go-version: ~1.18
       - uses: actions/checkout@v3
       - name: golangci-lint
         uses: golangci/golangci-lint-action@v3
diff --git a/.golangci.yml b/.golangci.yml
index f1ba717..538f529 100644
--- a/.golangci.yml
+++ b/.golangci.yml
@@ -1,4 +1,6 @@
+run:
+  timeout: 5m
 linters:
   enable:
     - thelper
-    - gofumpt
\ No newline at end of file
+    - gofumpt
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 768efea..3a6375f 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -9,7 +9,7 @@ By participating to this project, you agree to abide our [code of conduct](https
 Prerequisites:
 
 - [Task](https://taskfile.dev/#/installation)
-- [Go 1.17+](https://golang.org/doc/install)
+- [Go 1.18+](https://golang.org/doc/install)
 - [Docker](https://www.docker.com/)
 - `gpg` (probably already installed on your system)
 
diff --git a/apk/apk.go b/apk/apk.go
index 75b4f4d..dd2b504 100644
--- a/apk/apk.go
+++ b/apk/apk.go
@@ -90,16 +90,13 @@ type Apk struct{}
 func (a *Apk) ConventionalFileName(info *nfpm.Info) string {
 	info = ensureValidArch(info)
 	version := info.Version
-	if info.Release != "" {
-		version += "-" + info.Release
-	}
 
 	if info.Prerelease != "" {
-		version += "~" + info.Prerelease
+		version += "" + info.Prerelease
 	}
 
-	if info.VersionMetadata != "" {
-		version += "+" + info.VersionMetadata
+	if info.Release != "" {
+		version += "-" + info.Release
 	}
 
 	return fmt.Sprintf("%s_%s_%s.apk", info.Name, version, info.Arch)
@@ -559,10 +556,9 @@ func pathsToCreate(dst string) []string {
 const controlTemplate = `
 {{- /* Mandatory fields */ -}}
 pkgname = {{.Info.Name}}
-pkgver = {{ if .Info.Epoch}}{{ .Info.Epoch }}:{{ end }}{{.Info.Version}}
+pkgver = {{.Info.Version}}
+		 {{- if .Info.Prerelease}}{{ .Info.Prerelease }}{{- end }}
          {{- if .Info.Release}}-{{ .Info.Release }}{{- end }}
-         {{- if .Info.Prerelease}}~{{ .Info.Prerelease }}{{- end }}
-         {{- if .Info.VersionMetadata}}+{{ .Info.VersionMetadata }}{{- end }}
 arch = {{.Info.Arch}}
 size = {{.InstalledSize}}
 pkgdesc = {{multiline .Info.Description}}
diff --git a/deb/deb.go b/deb/deb.go
index 6f39ebc..2e492e1 100644
--- a/deb/deb.go
+++ b/deb/deb.go
@@ -5,6 +5,7 @@ import (
 	"archive/tar"
 	"bytes"
 	"crypto/md5" // nolint:gas
+	"crypto/sha1"
 	"errors"
 	"fmt"
 	"io"
@@ -128,23 +129,49 @@ func (d *Deb) Package(info *nfpm.Info, deb io.Writer) (err error) { // nolint: f
 	}
 
 	// TODO: refactor this
+	method := "debsign"
+	if info.Deb.Signature.Method != "" {
+		method = info.Deb.Signature.Method
+	}
+
 	if info.Deb.Signature.KeyFile != "" {
-		data := io.MultiReader(bytes.NewReader(debianBinary), bytes.NewReader(controlTarGz),
-			bytes.NewReader(dataTarball))
+		var data io.Reader
+		var sigType string
+		var sig []byte
 
-		sig, err := sign.PGPArmoredDetachSignWithKeyID(data, info.Deb.Signature.KeyFile, info.Deb.Signature.KeyPassphrase, info.Deb.Signature.KeyID)
-		if err != nil {
-			return &nfpm.ErrSigningFailure{Err: err}
-		}
+		if method == "debsign" {
+			data = readDebsignData(debianBinary, controlTarGz, dataTarball)
 
-		sigType := "origin"
-		if info.Deb.Signature.Type != "" {
-			sigType = info.Deb.Signature.Type
-		}
+			sigType = "origin"
+			if info.Deb.Signature.Type != "" {
+				sigType = info.Deb.Signature.Type
+			}
 
-		if sigType != "origin" && sigType != "maint" && sigType != "archive" {
-			return &nfpm.ErrSigningFailure{
-				Err: ErrInvalidSignatureType,
+			if sigType != "origin" && sigType != "maint" && sigType != "archive" {
+				return &nfpm.ErrSigningFailure{
+					Err: ErrInvalidSignatureType,
+				}
+			}
+
+			sig, err = sign.PGPArmoredDetachSignWithKeyID(data, info.Deb.Signature.KeyFile, info.Deb.Signature.KeyPassphrase, info.Deb.Signature.KeyID)
+			if err != nil {
+				return &nfpm.ErrSigningFailure{Err: err}
+			}
+
+		} else if method == "dpkg-sig" {
+			data, err := readDpkgSigData(info, debianBinary, controlTarGz, dataTarball)
+			if err != nil {
+				return &nfpm.ErrSigningFailure{Err: err}
+			}
+
+			sigType = "builder"
+			if info.Deb.Signature.Type != "" {
+				sigType = info.Deb.Signature.Type
+			}
+
+			sig, err = sign.PGPClearSignWithKeyID(data, info.Deb.Signature.KeyFile, info.Deb.Signature.KeyPassphrase, info.Deb.Signature.KeyID)
+			if err != nil {
+				return &nfpm.ErrSigningFailure{Err: err}
 			}
 		}
 
@@ -158,6 +185,66 @@ func (d *Deb) Package(info *nfpm.Info, deb io.Writer) (err error) { // nolint: f
 	return nil
 }
 
+func readDebsignData(debianBinary, controlTarGz, dataTarball []byte) io.Reader {
+	return io.MultiReader(bytes.NewReader(debianBinary), bytes.NewReader(controlTarGz),
+		bytes.NewReader(dataTarball))
+}
+
+// reference: https://manpages.debian.org/jessie/dpkg-sig/dpkg-sig.1.en.html
+const dpkgSigTemplate = `
+Hash: SHA1
+
+Version: 4
+Signer: {{ .Signer }}
+Date: {{ .Date }}
+Role: {{ .Role }}
+Files:
+{{range .Files}}{{ .Md5Sum }} {{ .Sha1Sum }} {{ .Size }} {{ .Name }}{{end}}
+`
+
+type dpkgSigData struct {
+	Signer string
+	Date   time.Time
+	Role   string
+	Files  []dpkgSigFileLine
+	Info   *nfpm.Info
+}
+type dpkgSigFileLine struct {
+	Md5Sum  [16]byte
+	Sha1Sum [20]byte
+	Size    int
+	Name    string
+}
+
+func newDpkgSigFileLine(name string, fileContent []byte) dpkgSigFileLine {
+	return dpkgSigFileLine{
+		Name:    name,
+		Md5Sum:  md5.Sum(fileContent),
+		Sha1Sum: sha1.Sum(fileContent),
+		Size:    len(fileContent),
+	}
+}
+
+func readDpkgSigData(info *nfpm.Info, debianBinary, controlTarGz, dataTarball []byte) (io.Reader, error) {
+	data := dpkgSigData{
+		Signer: info.Deb.Signature.Signer,
+		Date:   time.Now(),
+		Role:   info.Deb.Signature.Type,
+		Files: []dpkgSigFileLine{
+			newDpkgSigFileLine("debian-binary", debianBinary),
+			newDpkgSigFileLine("control.tar.gz", controlTarGz),
+			newDpkgSigFileLine("data.tar.gz", dataTarball),
+		},
+	}
+	temp, _ := template.New("dpkg-sig").Parse(dpkgSigTemplate)
+	buf := &bytes.Buffer{}
+	err := temp.Execute(buf, data)
+	if err != nil {
+		return nil, fmt.Errorf("dpkg-sig template error: %w", err)
+	}
+	return buf, nil
+}
+
 func (*Deb) SetPackagerDefaults(info *nfpm.Info) {
 	// Priority should be set on all packages per:
 	//   https://www.debian.org/doc/debian-policy/ch-archive.html#priorities
diff --git a/go.mod b/go.mod
index f1ea697..73a8e4a 100644
--- a/go.mod
+++ b/go.mod
@@ -1,6 +1,6 @@
 module github.com/goreleaser/nfpm/v2
 
-go 1.17
+go 1.18
 
 require (
 	github.com/AlekSi/pointer v1.2.0
@@ -13,7 +13,7 @@ require (
 	github.com/google/rpmpack v0.0.0-20220314092521-38642b5e571e
 	github.com/goreleaser/chglog v0.1.2
 	github.com/goreleaser/fileglob v1.3.0
-	github.com/imdario/mergo v0.3.12
+	github.com/imdario/mergo v0.3.13
 	github.com/klauspost/pgzip v1.2.5
 	github.com/muesli/mango-cobra v1.1.0
 	github.com/muesli/roff v0.1.0
@@ -21,7 +21,7 @@ require (
 	github.com/stretchr/testify v1.7.1
 	github.com/ulikunitz/xz v0.5.10
 	github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8
-	gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
+	gopkg.in/yaml.v3 v3.0.0
 )
 
 require (
diff --git a/go.sum b/go.sum
index 1121a39..0f455d9 100644
--- a/go.sum
+++ b/go.sum
@@ -175,8 +175,9 @@ github.com/iancoleman/orderedmap v0.2.0 h1:sq1N/TFpYH++aViPcaKjys3bDClUEU7s5B+z6
 github.com/iancoleman/orderedmap v0.2.0/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.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU=
 github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
+github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk=
+github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg=
 github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
 github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
 github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
@@ -323,7 +324,6 @@ github.com/xanzy/ssh-agent v0.3.1/go.mod h1:QIE4lCeL7nkC25x+yA3LBIYfwCc1TFziCtG7
 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/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
-github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
 go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
 go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
 go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
@@ -372,7 +372,6 @@ golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKG
 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.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -391,7 +390,6 @@ golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLL
 golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k=
-golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
 golang.org/x/net v0.0.0-20211007125505-59d4e928ea9d h1:QWMn1lFvU/nZ58ssWqiFJMd3DKIII8NYc4sn708XgKs=
 golang.org/x/net v0.0.0-20211007125505-59d4e928ea9d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@@ -402,7 +400,6 @@ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJ
 golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -428,10 +425,7 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210502180810-71e4cd670f79/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 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.0.0-20211205182925-97ca703d548d h1:FjkYO/PPp4Wi0EAUOVLxePm7qVW4r4ctbWpURyuOD0E=
@@ -443,7 +437,6 @@ golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3
 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.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
 golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
 golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@@ -474,7 +467,6 @@ golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
 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=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
 google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
 google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
@@ -518,8 +510,8 @@ gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
 gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
-gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.0 h1:hjy8E9ON/egN1tAYqKb61G10WtihqetD4sz2H+8nIeA=
+gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
diff --git a/internal/cmd/package.go b/internal/cmd/package.go
index 2dd73f4..044297b 100644
--- a/internal/cmd/package.go
+++ b/internal/cmd/package.go
@@ -23,7 +23,7 @@ func newPackageCmd() *packageCmd {
 	cmd := &cobra.Command{
 		Use:           "package",
 		Aliases:       []string{"pkg", "p"},
-		Short:         "Creates a package based on the given the given config file and flags",
+		Short:         "Creates a package based on the given config file and flags",
 		SilenceUsage:  true,
 		SilenceErrors: true,
 		Args:          cobra.NoArgs,
diff --git a/internal/sign/pgp.go b/internal/sign/pgp.go
index 300c99c..23d3059 100644
--- a/internal/sign/pgp.go
+++ b/internal/sign/pgp.go
@@ -11,6 +11,7 @@ import (
 	"unicode"
 
 	"github.com/ProtonMail/go-crypto/openpgp"
+	"github.com/ProtonMail/go-crypto/openpgp/clearsign"
 	"github.com/ProtonMail/go-crypto/openpgp/packet"
 	"github.com/goreleaser/nfpm/v2"
 )
@@ -79,6 +80,48 @@ func PGPArmoredDetachSignWithKeyID(message io.Reader, keyFile, passphrase string
 	return signature.Bytes(), nil
 }
 
+func PGPClearSignWithKeyID(message io.Reader, keyFile, passphrase string, hexKeyId *string) ([]byte, error) {
+	keyId, err := parseKeyID(hexKeyId)
+	if err != nil {
+		return nil, fmt.Errorf("%v is not a valid key id: %w", hexKeyId, err)
+	}
+
+	key, err := readSigningKey(keyFile, passphrase)
+	if err != nil {
+		return nil, fmt.Errorf("clear sign: %w", err)
+	}
+
+	var signature bytes.Buffer
+
+	writeCloser, err := clearsign.Encode(&signature, key.PrivateKey, &packet.Config{
+		SigningKeyId: keyId,
+		DefaultHash:  crypto.SHA256,
+	})
+	if err != nil {
+		return nil, fmt.Errorf("clear sign: %w", err)
+	}
+
+	messageBytes, err := io.ReadAll(message)
+	if err != nil {
+		return nil, fmt.Errorf("clear sign: %w", err)
+	}
+
+	writtenLength, err := writeCloser.Write(messageBytes)
+	if err != nil {
+		return nil, fmt.Errorf("clear sign: %w", err)
+	}
+	if writtenLength != len(messageBytes) {
+		return nil, fmt.Errorf("partial signature written")
+	}
+	writeCloser.Close()
+
+	if err != nil {
+		return nil, fmt.Errorf("armored detach sign: %w", err)
+	}
+
+	return signature.Bytes(), nil
+}
+
 // PGPVerify is exported for use in tests and verifies an ASCII-armored or non-ASCII-armored
 // signature using an ASCII-armored or non-ASCII-armored public key file. The signer
 // identity is not explicitly checked, other that the obvious fact that the signer's key must
@@ -112,6 +155,32 @@ func PGPVerify(message io.Reader, signature []byte, armoredPubKeyFile string) er
 	return err
 }
 
+func PGPReadMessage(message []byte, armoredPubKeyFile string) error {
+	keyFileContent, err := ioutil.ReadFile(armoredPubKeyFile)
+	if err != nil {
+		return fmt.Errorf("reading armored public key file: %w", err)
+	}
+
+	var keyring openpgp.EntityList
+
+	if isASCII(keyFileContent) {
+		keyring, err = openpgp.ReadArmoredKeyRing(bytes.NewReader(keyFileContent))
+		if err != nil {
+			return fmt.Errorf("decoding armored public key file: %w", err)
+		}
+	} else {
+		keyring, err = openpgp.ReadKeyRing(bytes.NewReader(keyFileContent))
+		if err != nil {
+			return fmt.Errorf("decoding public key file: %w", err)
+		}
+	}
+
+	block, _ := clearsign.Decode(message)
+	_, err = block.VerifySignature(keyring, nil)
+
+	return err
+}
+
 func parseKeyID(hexKeyId *string) (uint64, error) {
 	if hexKeyId == nil || *hexKeyId == "" {
 		return 0, nil
diff --git a/nfpm.go b/nfpm.go
index 42e6baf..1ad66c2 100644
--- a/nfpm.go
+++ b/nfpm.go
@@ -332,8 +332,11 @@ type Deb struct {
 
 type DebSignature struct {
 	PackageSignature `yaml:",inline"`
+	// debsign, or dpkg-sig (defaults to debsign)
+	Method string `yaml:"method,omitempty" jsonschema:"title=method role,enum=debsign,enum=dpkg-sig,default=debsign"`
 	// origin, maint or archive (defaults to origin)
-	Type string `yaml:"type,omitempty" jsonschema:"title=signer role,enum=origin,enum=maint,enum=archive,default=origin"`
+	Type   string `yaml:"type,omitempty" jsonschema:"title=signer role,enum=origin,enum=maint,enum=archive,default=origin"`
+	Signer string `yaml:"signer,omitempty" jsonschema:"title=signer"`
 }
 
 // DebTriggers contains triggers only available for deb packages.
diff --git a/www/docs/overrides/partials/integrations/analytics.html b/www/docs/overrides/partials/integrations/analytics.html
deleted file mode 100644
index d3faf6e..0000000
--- a/www/docs/overrides/partials/integrations/analytics.html
+++ /dev/null
@@ -1,3 +0,0 @@
-{% block analytics %}
-	<script async defer data-domain="nfpm.goreleaser.com" src="https://plausible.io/js/plausible.js"></script>
-{% endblock %}
diff --git a/www/mkdocs.yml b/www/mkdocs.yml
index 82f4e9a..a4a06c0 100644
--- a/www/mkdocs.yml
+++ b/www/mkdocs.yml
@@ -8,7 +8,6 @@ edit_uri: edit/main/www/docs/
 
 theme:
   name: material
-  custom_dir: docs/overrides
   language: en
   logo: static/avatar.png
   favicon: static/favicon.ico


Test patch:
diff --git a/apk/apk_test.go b/apk/apk_test.go
index b943ed5..ebb0333 100644
--- a/apk/apk_test.go
+++ b/apk/apk_test.go
@@ -32,6 +32,7 @@ func exampleInfo() *nfpm.Info {
 		Priority:    "extra",
 		Maintainer:  "Carlos A Becker <pkg@carlosbecker.com>",
 		Version:     "v1.0.0",
+		Prerelease:  "beta1",
 		Release:     "r1",
 		Section:     "default",
 		Homepage:    "http://carlosbecker.com",
@@ -444,20 +445,20 @@ func TestAPKConventionalFileName(t *testing.T) {
 			Expect: "default_1.2.3_x86_64.apk",
 		},
 		{
-			Arch: "386", Version: "1.2.3", Meta: "git",
-			Expect: "default_1.2.3+git_x86.apk",
+			Arch: "386", Version: "1.2.3", Prerelease: "git",
+			Expect: "default_1.2.3git_x86.apk",
 		},
 		{
-			Arch: "386", Version: "1.2.3", Meta: "git", Release: "1",
-			Expect: "default_1.2.3-1+git_x86.apk",
+			Arch: "386", Version: "1.2.3", Prerelease: "git", Release: "1",
+			Expect: "default_1.2.3git-1_x86.apk",
 		},
 		{
 			Arch: "all", Version: "1.2.3",
 			Expect: "default_1.2.3_all.apk",
 		},
 		{
-			Arch: "386", Version: "1.2.3", Release: "1", Prerelease: "5",
-			Expect: "default_1.2.3-1~5_x86.apk",
+			Arch: "386", Version: "1.2.3", Release: "1", Prerelease: "beta",
+			Expect: "default_1.2.3beta-1_x86.apk",
 		},
 	}
 
diff --git a/apk/testdata/TestControl.golden b/apk/testdata/TestControl.golden
index 7205b12..b7a4177 100644
--- a/apk/testdata/TestControl.golden
+++ b/apk/testdata/TestControl.golden
@@ -1,5 +1,5 @@
 pkgname = foo
-pkgver = 1.0.0-r1
+pkgver = 1.0.0beta1-r1
 arch = amd64
 size = 10
 pkgdesc = Foo does things
diff --git a/apk/testdata/TestCreateBuilderControl.golden b/apk/testdata/TestCreateBuilderControl.golden
index 08475d4..549e459 100644
--- a/apk/testdata/TestCreateBuilderControl.golden
+++ b/apk/testdata/TestCreateBuilderControl.golden
@@ -1,5 +1,5 @@
 pkgname = foo
-pkgver = 1.0.0-r1
+pkgver = 1.0.0beta1-r1
 arch = amd64
 size = 12345
 pkgdesc = Foo does things
diff --git a/apk/testdata/TestCreateBuilderControlScripts.golden b/apk/testdata/TestCreateBuilderControlScripts.golden
index 08475d4..549e459 100644
--- a/apk/testdata/TestCreateBuilderControlScripts.golden
+++ b/apk/testdata/TestCreateBuilderControlScripts.golden
@@ -1,5 +1,5 @@
 pkgname = foo
-pkgver = 1.0.0-r1
+pkgver = 1.0.0beta1-r1
 arch = amd64
 size = 12345
 pkgdesc = Foo does things
diff --git a/deb/deb_test.go b/deb/deb_test.go
index 426218d..4f2600c 100644
--- a/deb/deb_test.go
+++ b/deb/deb_test.go
@@ -946,6 +946,36 @@ func TestDebsigsSignatureError(t *testing.T) {
 	require.True(t, errors.As(err, &expectedError))
 }
 
+func TestDpkgSigSignature(t *testing.T) {
+	info := exampleInfo()
+	info.Deb.Signature.KeyFile = "../internal/sign/testdata/privkey.asc"
+	info.Deb.Signature.KeyPassphrase = "hunter2"
+	info.Deb.Signature.Method = "dpkg-sig"
+	info.Deb.Signature.Signer = "bob McRobert"
+
+	var deb bytes.Buffer
+	err := Default.Package(info, &deb)
+	require.NoError(t, err)
+
+	signature := extractFileFromAr(t, deb.Bytes(), "_gpgbuilder")
+
+	err = sign.PGPReadMessage(signature, "../internal/sign/testdata/pubkey.asc")
+	require.NoError(t, err)
+}
+
+func TestDpkgSigSignatureError(t *testing.T) {
+	info := exampleInfo()
+	info.Deb.Signature.KeyFile = "/does/not/exist"
+	info.Deb.Signature.Method = "dpkg-sig"
+
+	var deb bytes.Buffer
+	err := Default.Package(info, &deb)
+	require.Error(t, err)
+
+	var expectedError *nfpm.ErrSigningFailure
+	require.True(t, errors.As(err, &expectedError))
+}
+
 func TestDisableGlobbing(t *testing.T) {
 	info := exampleInfo()
 	info.DisableGlobbing = true
diff --git a/testdata/acceptance/apk.dockerfile b/testdata/acceptance/apk.dockerfile
index 6842484..e234fbc 100644
--- a/testdata/acceptance/apk.dockerfile
+++ b/testdata/acceptance/apk.dockerfile
@@ -95,7 +95,7 @@ RUN command -v zsh
 
 # ---- env-var-version test ----
 FROM min AS env-var-version
-ENV EXPECTVER="foo-1.0.0~0.1.b1+git.abcdefgh description:"
+ENV EXPECTVER="foo-1.0.00.1.b1 description:"
 RUN apk info foo | grep "foo-" | grep " description:" > found
 RUN export FOUND_VER="$(cat found)" && \
 	echo "Expected: '${EXPECTVER}' :: Found: '${FOUND_VER}'" && \