func createTree(tarw *tar.Writer, dst string, created map[string]bool) error {
	for _, path := range pathsToCreate(dst) {
		path = normalizePath(path) + "/"
		if created[path] {
			continue
		}
		if err := tarw.WriteHeader(&tar.Header{
			Name:     path,
			Mode:     0o755,
			Typeflag: tar.TypeDir,
			Format:   tar.FormatGNU,
			ModTime:  time.Now(),
		}); err != nil {
			return fmt.Errorf("failed to create folder: %w", err)
		}
		created[path] = true
	}
	return nil
}
this is needed because the data.tar.gz file should have the empty folders
as well, so we walk through the dst and create all subfolders.
package deb
import (
	"archive/tar"
	"bytes"
	"compress/gzip"
	"crypto/md5"  
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"os"
	"path/filepath"
	"strings"
	"text/template"
	"time"
	"github.com/blakesmith/ar"
	"github.com/goreleaser/chglog"
	"github.com/goreleaser/nfpm/v2"
	"github.com/goreleaser/nfpm/v2/files"
	"github.com/goreleaser/nfpm/v2/internal/sign"
	"github.com/ulikunitz/xz"
)
const packagerName = "deb"
func init() {
	nfpm.RegisterPackager(packagerName, Default)
}
var archToDebian = map[string]string{
	"386":     "i386",
	"arm":     "armhf",
	"arm5":    "armel",
	"arm6":    "armhf",
	"arm7":    "armhf",
	"mipsle":  "mipsel",
	"ppc64le": "ppc64el",
}
var Default = &Deb{}
type Deb struct{}
func (*Deb) ConventionalFileName(info *nfpm.Info) string {
	arch, ok := archToDebian[info.Arch]
	if !ok {
		arch = info.Arch
	}
	version := info.Version
	if info.Prerelease != "" {
		version += "~" + info.Prerelease
	}
	if info.VersionMetadata != "" {
		version += "+" + info.VersionMetadata
	}
	if info.Release != "" {
		version += "-" + info.Release
	}
	return fmt.Sprintf("%s_%s_%s.deb", info.Name, version, arch)
}
var ErrInvalidSignatureType = errors.New("invalid signature type")
func (d *Deb) Package(info *nfpm.Info, deb io.Writer) (err error) {  
	arch, ok := archToDebian[info.Arch]
	if ok {
		info.Arch = arch
	}
	if err = info.Validate(); err != nil {
		return err
	}
	d.SetPackagerDefaults(info)
	dataTarball, md5sums, instSize, dataTarballName, err := createDataTarball(info)
	if err != nil {
		return err
	}
	controlTarGz, err := createControl(instSize, md5sums, info)
	if err != nil {
		return err
	}
	debianBinary := []byte("2.0\n")
	w := ar.NewWriter(deb)
	if err := w.WriteGlobalHeader(); err != nil {
		return fmt.Errorf("cannot write ar header to deb file: %w", err)
	}
	if err := addArFile(w, "debian-binary", debianBinary); err != nil {
		return fmt.Errorf("cannot pack debian-binary: %w", err)
	}
	if err := addArFile(w, "control.tar.gz", controlTarGz); err != nil {
		return fmt.Errorf("cannot add control.tar.gz to deb: %w", err)
	}
	if err := addArFile(w, dataTarballName, dataTarball); err != nil {
		return fmt.Errorf("cannot add data.tar.gz to deb: %w", err)
	}
	if info.Deb.Signature.KeyFile != "" {
		data := io.MultiReader(bytes.NewReader(debianBinary), bytes.NewReader(controlTarGz),
			bytes.NewReader(dataTarball))
		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}
		}
		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 err := addArFile(w, "_gpg"+sigType, sig); err != nil {
			return &nfpm.ErrSigningFailure{
				Err: fmt.Errorf("add signature to ar file: %w", err),
			}
		}
	}
	return nil
}
func (*Deb) SetPackagerDefaults(info *nfpm.Info) {
	if info.Priority == "" {
		info.Priority = "optional"
	}
	if info.Maintainer == "" {
		log.Println("DEPRECATION WARNING: Leaving the 'maintainer' field unset will not be allowed in a future version")
		info.Maintainer = "Unset Maintainer <unset@localhost>"
	}
}
func addArFile(w *ar.Writer, name string, body []byte) error {
	header := ar.Header{
		Name:    files.ToNixPath(name),
		Size:    int64(len(body)),
		Mode:    0o644,
		ModTime: time.Now(),
	}
	if err := w.WriteHeader(&header); err != nil {
		return fmt.Errorf("cannot write file header: %w", err)
	}
	_, err := w.Write(body)
	return err
}
type nopCloser struct {
	io.Writer
}
func (nopCloser) Close() error { return nil }
func createDataTarball(info *nfpm.Info) (dataTarBall, md5sums []byte,
	instSize int64, name string, err error) {
	var (
		dataTarball            bytes.Buffer
		dataTarballWriteCloser io.WriteCloser
	)
	switch info.Deb.Compression {
	case "", "gzip":  
		dataTarballWriteCloser = gzip.NewWriter(&dataTarball)
		name = "data.tar.gz"
	case "xz":
		dataTarballWriteCloser, err = xz.NewWriter(&dataTarball)
		if err != nil {
			return nil, nil, 0, "", err
		}
		name = "data.tar.xz"
	case "none":
		dataTarballWriteCloser = nopCloser{Writer: &dataTarball}
		name = "data.tar"
	default:
		return nil, nil, 0, "", fmt.Errorf("unknown compression algorithm: %s", info.Deb.Compression)
	}
	defer dataTarballWriteCloser.Close()  
	md5sums, instSize, err = fillDataTar(info, dataTarballWriteCloser)
	if err != nil {
		return nil, nil, 0, "", err
	}
	if err := dataTarballWriteCloser.Close(); err != nil {
		return nil, nil, 0, "", fmt.Errorf("closing data tarball: %w", err)
	}
	return dataTarball.Bytes(), md5sums, instSize, name, nil
}
func fillDataTar(info *nfpm.Info, w io.Writer) (md5sums []byte, instSize int64, err error) {
	out := tar.NewWriter(w)
	defer out.Close()  
	created := map[string]bool{}
	if err = createEmptyFoldersInsideDataTar(info, out, created); err != nil {
		return nil, 0, err
	}
	md5buf, instSize, err := createFilesInsideDataTar(info, out, created)
	if err != nil {
		return nil, 0, err
	}
	if err := out.Close(); err != nil {
		return nil, 0, fmt.Errorf("closing data.tar.gz: %w", err)
	}
	return md5buf.Bytes(), instSize, nil
}
func createSymlinkInsideTar(file *files.Content, out *tar.Writer) error {
	return newItemInsideTar(out, []byte{}, &tar.Header{
		Name:     normalizePath(file.Destination),
		Linkname: file.Source,
		Typeflag: tar.TypeSymlink,
		ModTime:  file.FileInfo.MTime,
		Format:   tar.FormatGNU,
	})
}
func createFilesInsideDataTar(info *nfpm.Info, tw *tar.Writer,
	created map[string]bool) (md5buf bytes.Buffer, instSize int64, err error) {
	for _, file := range info.Contents {
		if file.Packager != "" && file.Packager != packagerName {
			continue
		}
		if err = createTree(tw, file.Destination, created); err != nil {
			return md5buf, 0, err
		}
		var size int64  
		switch file.Type {
		case "ghost":
			continue
		case "symlink":
			err = createSymlinkInsideTar(file, tw)
		case "doc":
			fallthrough
		case "licence", "license":
			fallthrough
		case "readme":
			fallthrough
		case "config", "config|noreplace":
			fallthrough
		default:
			size, err = copyToTarAndDigest(file, tw, &md5buf)
		}
		if err != nil {
			return md5buf, 0, err
		}
		instSize += size
	}
	if info.Changelog != "" {
		size, err := createChangelogInsideDataTar(tw, &md5buf, created, info)
		if err != nil {
			return md5buf, 0, err
		}
		instSize += size
	}
	return md5buf, instSize, nil
}
func createEmptyFoldersInsideDataTar(info *nfpm.Info, out *tar.Writer, created map[string]bool) error {
	for _, folder := range info.EmptyFolders {
		if err := createTree(out, files.ToNixPath(filepath.Join(folder, ".nope")), created); err != nil {
			return err
		}
	}
	return nil
}
func copyToTarAndDigest(file *files.Content, tw *tar.Writer, md5w io.Writer) (int64, error) {
	tarFile, err := os.OpenFile(file.Source, os.O_RDONLY, 0o600)  
	if err != nil {
		return 0, fmt.Errorf("could not add tarFile to the archive: %w", err)
	}
	defer tarFile.Close()  
	header, err := tar.FileInfoHeader(file, file.Source)
	if err != nil {
		log.Print(err)
		return 0, err
	}
	header.Format = tar.FormatGNU
	header.Name = normalizePath(file.Destination)
	header.Uname = file.FileInfo.Owner
	header.Gname = file.FileInfo.Group
	if err := tw.WriteHeader(header); err != nil {
		return 0, fmt.Errorf("cannot write header of %s to data.tar.gz: %w", file.Source, err)
	}
	digest := md5.New()  
	if _, err := io.Copy(tw, io.TeeReader(tarFile, digest)); err != nil {
		return 0, fmt.Errorf("failed to copy: %w", err)
	}
	if _, err := fmt.Fprintf(md5w, "%x  %s\n", digest.Sum(nil), header.Name); err != nil {
		return 0, fmt.Errorf("failed to write md5: %w", err)
	}
	return file.Size(), nil
}
func createChangelogInsideDataTar(tarw *tar.Writer, md5w io.Writer,
	created map[string]bool, info *nfpm.Info) (int64, error) {
	var buf bytes.Buffer
	out := gzip.NewWriter(&buf)
	defer out.Close()  
	changelogContent, err := formatChangelog(info)
	if err != nil {
		return 0, err
	}
	if _, err = out.Write([]byte(changelogContent)); err != nil {
		return 0, err
	}
	if err = out.Close(); err != nil {
		return 0, fmt.Errorf("closing changelog.gz: %w", err)
	}
	changelogData := buf.Bytes()
	changelogName := normalizePath(fmt.Sprintf("/usr/share/doc/%s/changelog.gz", info.Name))
	if err = createTree(tarw, changelogName, created); err != nil {
		return 0, err
	}
	digest := md5.New()  
	if _, err = digest.Write(changelogData); err != nil {
		return 0, err
	}
	if _, err = fmt.Fprintf(md5w, "%x  %s\n", digest.Sum(nil), changelogName); err != nil {
		return 0, err
	}
	if err = newFileInsideTar(tarw, changelogName, changelogData); err != nil {
		return 0, err
	}
	return int64(len(changelogData)), nil
}
func formatChangelog(info *nfpm.Info) (string, error) {
	changelog, err := info.GetChangeLog()
	if err != nil {
		return "", err
	}
	tpl, err := chglog.DebTemplate()
	if err != nil {
		return "", err
	}
	formattedChangelog, err := chglog.FormatChangelog(changelog, tpl)
	if err != nil {
		return "", err
	}
	return strings.TrimSpace(formattedChangelog) + "\n", nil
}
func createControl(instSize int64, md5sums []byte, info *nfpm.Info) (controlTarGz []byte, err error) {
	var buf bytes.Buffer
	compress := gzip.NewWriter(&buf)
	out := tar.NewWriter(compress)
	defer out.Close()       
	defer compress.Close()  
	var body bytes.Buffer
	if err = writeControl(&body, controlData{
		Info:          info,
		InstalledSize: instSize / 1024,
	}); err != nil {
		return nil, err
	}
	filesToCreate := map[string][]byte{
		"control":   body.Bytes(),
		"md5sums":   md5sums,
		"conffiles": conffiles(info),
	}
	if info.Changelog != "" {
		changeLogData, err := formatChangelog(info)
		if err != nil {
			return nil, err
		}
		filesToCreate["changelog"] = []byte(changeLogData)
	}
	triggers := createTriggers(info)
	if len(triggers) > 0 {
		filesToCreate["triggers"] = triggers
	}
	for name, content := range filesToCreate {
		if err := newFileInsideTar(out, name, content); err != nil {
			return nil, err
		}
	}
	type fileAndMode struct {
		fileName string
		mode     int64
	}
	specialFiles := map[string]*fileAndMode{}
	specialFiles[info.Scripts.PreInstall] = &fileAndMode{
		fileName: "preinst",
		mode:     0o755,
	}
	specialFiles[info.Scripts.PostInstall] = &fileAndMode{
		fileName: "postinst",
		mode:     0o755,
	}
	specialFiles[info.Scripts.PreRemove] = &fileAndMode{
		fileName: "prerm",
		mode:     0o755,
	}
	specialFiles[info.Scripts.PostRemove] = &fileAndMode{
		fileName: "postrm",
		mode:     0o755,
	}
	specialFiles[info.Overridables.Deb.Scripts.Rules] = &fileAndMode{
		fileName: "rules",
		mode:     0o755,
	}
	specialFiles[info.Overridables.Deb.Scripts.Templates] = &fileAndMode{
		fileName: "templates",
		mode:     0o644,
	}
	specialFiles[info.Overridables.Deb.Scripts.Config] = &fileAndMode{
		fileName: "config",
		mode:     0o755,
	}
	for path, destMode := range specialFiles {
		if path != "" {
			if err := newFilePathInsideTar(out, path, destMode.fileName, destMode.mode); err != nil {
				return nil, err
			}
		}
	}
	if err := out.Close(); err != nil {
		return nil, fmt.Errorf("closing control.tar.gz: %w", err)
	}
	if err := compress.Close(); err != nil {
		return nil, fmt.Errorf("closing control.tar.gz: %w", err)
	}
	return buf.Bytes(), nil
}
func newItemInsideTar(out *tar.Writer, content []byte, header *tar.Header) error {
	if err := out.WriteHeader(header); err != nil {
		return fmt.Errorf("cannot write header of %s file to control.tar.gz: %w", header.Name, err)
	}
	if _, err := out.Write(content); err != nil {
		return fmt.Errorf("cannot write %s file to control.tar.gz: %w", header.Name, err)
	}
	return nil
}
func newFileInsideTar(out *tar.Writer, name string, content []byte) error {
	return newItemInsideTar(out, content, &tar.Header{
		Name:     normalizePath(name),
		Size:     int64(len(content)),
		Mode:     0o644,
		ModTime:  time.Now(),
		Typeflag: tar.TypeReg,
		Format:   tar.FormatGNU,
	})
}
func newFilePathInsideTar(out *tar.Writer, path, dest string, mode int64) error {
	file, err := os.Open(path)  
	if err != nil {
		return err
	}
	content, err := ioutil.ReadAll(file)
	if err != nil {
		return err
	}
	return newItemInsideTar(out, content, &tar.Header{
		Name:     normalizePath(dest),
		Size:     int64(len(content)),
		Mode:     mode,
		ModTime:  time.Now(),
		Typeflag: tar.TypeReg,
		Format:   tar.FormatGNU,
	})
}
func normalizePath(src string) string {
	return "." + files.ToNixPath(filepath.Join("/", src))
}