diff --git a/internal/pipe/nfpm/nfpm.go b/internal/pipe/nfpm/nfpm.go index 7162ef8f75f..c507cafa46f 100644 --- a/internal/pipe/nfpm/nfpm.go +++ b/internal/pipe/nfpm/nfpm.go @@ -5,6 +5,7 @@ import ( "fmt" "os" "path/filepath" + "strings" "github.com/apex/log" "github.com/goreleaser/nfpm" @@ -173,6 +174,43 @@ func create(ctx *context.Context, fpm config.NFPM, format, arch string, binaries PreRemove: overridden.Scripts.PreRemove, PostRemove: overridden.Scripts.PostRemove, }, + Deb: nfpm.Deb{ + Scripts: nfpm.DebScripts{ + Rules: overridden.Deb.Scripts.Rules, + Templates: overridden.Deb.Scripts.Templates, + }, + Triggers: nfpm.DebTriggers{ + Interest: overridden.Deb.Triggers.Interest, + InterestAwait: overridden.Deb.Triggers.InterestAwait, + InterestNoAwait: overridden.Deb.Triggers.InterestNoAwait, + Activate: overridden.Deb.Triggers.Activate, + ActivateAwait: overridden.Deb.Triggers.ActivateAwait, + ActivateNoAwait: overridden.Deb.Triggers.ActivateNoAwait, + }, + Breaks: overridden.Deb.Breaks, + VersionMetadata: overridden.Deb.VersionMetadata, + Signature: nfpm.DebSignature{ + KeyFile: overridden.Deb.Signature.KeyFile, + KeyPassphrase: getPassphraseFromEnv(ctx, "DEB", fpm.ID), + Type: overridden.Deb.Signature.Type, + }, + }, + RPM: nfpm.RPM{ + Group: overridden.RPM.Group, + Compression: overridden.RPM.Compression, + ConfigNoReplaceFiles: overridden.RPM.ConfigNoReplaceFiles, + Signature: nfpm.RPMSignature{ + KeyFile: overridden.RPM.Signature.KeyFile, + KeyPassphrase: getPassphraseFromEnv(ctx, "RPM", fpm.ID), + }, + }, + APK: nfpm.APK{ + Signature: nfpm.APKSignature{ + KeyFile: overridden.APK.Signature.KeyFile, + KeyPassphrase: getPassphraseFromEnv(ctx, "APK", fpm.ID), + KeyName: overridden.APK.Signature.KeyName, + }, + }, }, } @@ -214,3 +252,22 @@ func create(ctx *context.Context, fpm config.NFPM, format, arch string, binaries }) return nil } + +func getPassphraseFromEnv(ctx *context.Context, packager string, nfpmID string) string { + var passphrase string + + nfpmID = strings.ToUpper(nfpmID) + packagerSpecificPassphrase := ctx.Env[fmt.Sprintf( + "NFPM_%s_%s_PASSPHRASE", + nfpmID, + packager, + )] + if packagerSpecificPassphrase != "" { + passphrase = packagerSpecificPassphrase + } else { + generalPassphrase := ctx.Env[fmt.Sprintf("NFPM_%s_PASSPHRASE", nfpmID)] + passphrase = generalPassphrase + } + + return passphrase +} diff --git a/internal/pipe/nfpm/nfpm_test.go b/internal/pipe/nfpm/nfpm_test.go index a6cac11aac2..f99ef426375 100644 --- a/internal/pipe/nfpm/nfpm_test.go +++ b/internal/pipe/nfpm/nfpm_test.go @@ -347,6 +347,220 @@ func TestOverrides(t *testing.T) { require.Equal(t, "bar", merged.FileNameTemplate) } +func TestDebSpecificConfig(t *testing.T) { + folder, err := ioutil.TempDir("", "archivetest") + require.NoError(t, err) + var dist = filepath.Join(folder, "dist") + require.NoError(t, os.Mkdir(dist, 0755)) + require.NoError(t, os.Mkdir(filepath.Join(dist, "mybin"), 0755)) + var binPath = filepath.Join(dist, "mybin", "mybin") + _, err = os.Create(binPath) + require.NoError(t, err) + var ctx = context.New(config.Project{ + ProjectName: "mybin", + Dist: dist, + NFPMs: []config.NFPM{ + { + ID: "someid", + Builds: []string{"default"}, + Formats: []string{"deb"}, + NFPMOverridables: config.NFPMOverridables{ + PackageName: "foo", + Files: map[string]string{ + "./testdata/testfile.txt": "/usr/share/testfile.txt", + }, + Deb: config.NFPMDeb{ + Signature: config.NFPMDebSignature{ + KeyFile: "./testdata/privkey.gpg", + }, + }, + }, + }, + }, + }) + ctx.Version = "1.0.0" + ctx.Git = context.GitInfo{CurrentTag: "v1.0.0"} + for _, goos := range []string{"linux", "darwin"} { + for _, goarch := range []string{"amd64", "386"} { + ctx.Artifacts.Add(&artifact.Artifact{ + Name: "mybin", + Path: binPath, + Goarch: goarch, + Goos: goos, + Type: artifact.Binary, + Extra: map[string]interface{}{ + "ID": "default", + }, + }) + } + } + + t.Run("no passphrase set", func(t *testing.T) { + require.Contains( + t, + Pipe{}.Run(ctx).Error(), + `key is encrypted but no passphrase was provided`, + ) + }) + + t.Run("general passphrase set", func(t *testing.T) { + ctx.Env = map[string]string{ + "NFPM_SOMEID_PASSPHRASE": "hunter2", + } + require.NoError(t, Pipe{}.Run(ctx)) + }) + + t.Run("packager specific passphrase set", func(t *testing.T) { + ctx.Env = map[string]string{ + "NFPM_SOMEID_DEB_PASSPHRASE": "hunter2", + } + require.NoError(t, Pipe{}.Run(ctx)) + }) +} + +func TestRPMSpecificConfig(t *testing.T) { + folder, err := ioutil.TempDir("", "archivetest") + require.NoError(t, err) + var dist = filepath.Join(folder, "dist") + require.NoError(t, os.Mkdir(dist, 0755)) + require.NoError(t, os.Mkdir(filepath.Join(dist, "mybin"), 0755)) + var binPath = filepath.Join(dist, "mybin", "mybin") + _, err = os.Create(binPath) + require.NoError(t, err) + var ctx = context.New(config.Project{ + ProjectName: "mybin", + Dist: dist, + NFPMs: []config.NFPM{ + { + ID: "someid", + Builds: []string{"default"}, + Formats: []string{"rpm"}, + NFPMOverridables: config.NFPMOverridables{ + PackageName: "foo", + Files: map[string]string{ + "./testdata/testfile.txt": "/usr/share/testfile.txt", + }, + RPM: config.NFPMRPM{ + Signature: config.NFPMRPMSignature{ + KeyFile: "./testdata/privkey.gpg", + }, + }, + }, + }, + }, + }) + ctx.Version = "1.0.0" + ctx.Git = context.GitInfo{CurrentTag: "v1.0.0"} + for _, goos := range []string{"linux", "darwin"} { + for _, goarch := range []string{"amd64", "386"} { + ctx.Artifacts.Add(&artifact.Artifact{ + Name: "mybin", + Path: binPath, + Goarch: goarch, + Goos: goos, + Type: artifact.Binary, + Extra: map[string]interface{}{ + "ID": "default", + }, + }) + } + } + + t.Run("no passphrase set", func(t *testing.T) { + require.Contains( + t, + Pipe{}.Run(ctx).Error(), + `key is encrypted but no passphrase was provided`, + ) + }) + + t.Run("general passphrase set", func(t *testing.T) { + ctx.Env = map[string]string{ + "NFPM_SOMEID_PASSPHRASE": "hunter2", + } + require.NoError(t, Pipe{}.Run(ctx)) + }) + + t.Run("packager specific passphrase set", func(t *testing.T) { + ctx.Env = map[string]string{ + "NFPM_SOMEID_RPM_PASSPHRASE": "hunter2", + } + require.NoError(t, Pipe{}.Run(ctx)) + }) +} + +func TestAPKSpecificConfig(t *testing.T) { + folder, err := ioutil.TempDir("", "archivetest") + require.NoError(t, err) + var dist = filepath.Join(folder, "dist") + require.NoError(t, os.Mkdir(dist, 0755)) + require.NoError(t, os.Mkdir(filepath.Join(dist, "mybin"), 0755)) + var binPath = filepath.Join(dist, "mybin", "mybin") + _, err = os.Create(binPath) + require.NoError(t, err) + var ctx = context.New(config.Project{ + ProjectName: "mybin", + Dist: dist, + NFPMs: []config.NFPM{ + { + ID: "someid", + Maintainer: "me@me", + Builds: []string{"default"}, + Formats: []string{"apk"}, + NFPMOverridables: config.NFPMOverridables{ + PackageName: "foo", + Files: map[string]string{ + "./testdata/testfile.txt": "/usr/share/testfile.txt", + }, + APK: config.NFPMAPK{ + Signature: config.NFPMAPKSignature{ + KeyFile: "./testdata/rsa.priv", + }, + }, + }, + }, + }, + }) + ctx.Version = "1.0.0" + ctx.Git = context.GitInfo{CurrentTag: "v1.0.0"} + for _, goos := range []string{"linux", "darwin"} { + for _, goarch := range []string{"amd64", "386"} { + ctx.Artifacts.Add(&artifact.Artifact{ + Name: "mybin", + Path: binPath, + Goarch: goarch, + Goos: goos, + Type: artifact.Binary, + Extra: map[string]interface{}{ + "ID": "default", + }, + }) + } + } + + t.Run("no passphrase set", func(t *testing.T) { + require.Contains( + t, + Pipe{}.Run(ctx).Error(), + `key is encrypted but no passphrase was provided`, + ) + }) + + t.Run("general passphrase set", func(t *testing.T) { + ctx.Env = map[string]string{ + "NFPM_SOMEID_PASSPHRASE": "hunter2", + } + require.NoError(t, Pipe{}.Run(ctx)) + }) + + t.Run("packager specific passphrase set", func(t *testing.T) { + ctx.Env = map[string]string{ + "NFPM_SOMEID_APK_PASSPHRASE": "hunter2", + } + require.NoError(t, Pipe{}.Run(ctx)) + }) +} + func TestSeveralNFPMsWithTheSameID(t *testing.T) { var ctx = &context.Context{ Config: config.Project{ diff --git a/internal/pipe/nfpm/testdata/privkey.gpg b/internal/pipe/nfpm/testdata/privkey.gpg new file mode 100644 index 00000000000..5a657cf10be Binary files /dev/null and b/internal/pipe/nfpm/testdata/privkey.gpg differ diff --git a/internal/pipe/nfpm/testdata/rsa.priv b/internal/pipe/nfpm/testdata/rsa.priv new file mode 100644 index 00000000000..6c15579ca2a --- /dev/null +++ b/internal/pipe/nfpm/testdata/rsa.priv @@ -0,0 +1,30 @@ +-----BEGIN RSA PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: AES-128-CBC,F681796F2F5F6592720D154E441631AF + +U4aCJ0bGTN+/l162THI8CQdVmn62QX7+CxGOrxz8vbPOcMic8ppBD/h4lcUmca1L +B3q9gBV+nM4KWOUjKDsmXJ7PeHrQJxR30RbGKGOP8WZSlf7oqICHX9WfjzsaLmMC +zr7z7osO4DhW0nkr+CJATplKxjF5kry2jsBohmG7AJKVB6JRenpyz/icLrwfeywo +dM1aZEw2Tm6Aso+un+rdmRxvYG5SEVLVh5M334MvOwhKHNGGfNOUJG38uym/sYXM +WbBRyOUhQXabbuHlJdr5V7mnxm+KLi+hZQAIoRsFTS7tkIUpFjtvCS0RI0L6MtkL +3N76xiW82n3SxrPdZdJCGy9AWor66QTmr25yTZpUJMYqpHaXc2pKqZOWZ/U0bAbA +Oyf/CCTwI9lLT1a+CtGTHYTXHHKhQ9n1TI2FI4DRhnDcdDix2S/shF8aeu7mcKF1 +SqS8r+DTKzmbohUVa0/Ad9Z6R8Q1Ed+fl2/l0/eb3tI7c7hH+AxV+AqSAgLsZ1/T +SQoIjUX4Kna0U2wLi7wEWlwrQQE2lugoJ2tOH6F33tEd4FFh4CjTw4LKKFEIgjPj +yNKJ5CC+5jQPdyN/utR6Q+PNlxnT5jU02dAupdGfS/On2ecONmBKFCHHT+Sh3F21 +Q8zKZPKTUgIqG8JrjIaO1akjJqGgr6c0kefVTYpHEH3HNNkqUvzywIMR7IooPJrD +XQYLMgTuE4ccJk7JN5OCHlSkTHPcrkuD6rRmi7v9zIOMEmwfkN0Ea3HgNJ/7W8gL +mnv47HtwEuuXpyhyQ5xpaJcNWfSaUvSHBr33Ni8rDL2iJu22zEeZXlOU1t4h7wCA +VGd3GBk4n5PILABwILDWKP16bvB3I491QK6m7LehawJNMGDC4Yk379Havcl6BzSX +Rs6NOFcAGyM0VS97HYj3ju7fecdvFwmd86CA92CoK6JbafsQ6o/sOZOhxnrg6nLu +bS26TPwLoqNHrAzg2vTLosbiG3ezge7VuL2GBbQcZm4SsotNLbfpZIhn3JV9/Ivg +rkiUNOEExd52+VImeAJ15Pl6lOb2uAdXwZNsPXfBbanfc5qoggJJlU/xCnQyrPNW +xCi2y3+SQjjlEqdoQbmEEHrU4zzOSj505qDz/Md18W/aih5n1VdzI7Z1YeMAzlpe +X7cGHrQD83AsAtWWv5af5qkHCRYn3fQDGf4svJprsbYxz7e8oAoyjXpKPVbfZcfk +hqlR0T7yN91upFwHhq8q3ZY/CLsZbLecT28M3G7NgE+LtrF0bKcPrDfOCeWyyVBN +wtxPB0P62Nr4jHs5GVO5D1qyCiDMLsS3mwSz3IZn2xf0pit7+O2uu5yIUeGhUN8k +1Y4RwZyGf1e5y7Ulhb5OAbeDYtUTnvgWOqwj5grmACQhhWmKiXX6qUHHn54xyC9P +Yn5f0jdGGVRKINgA+KsipDs3OjMY0D9Dyx3ao646wX+1ypNkUq+hZhykuKlTgqi6 +29LdVwXTbcYjc25hbIj9TrXZS7swmwtUhpLwtFyjqrCSMjHW6f6n27Vq8cvL788L +7SbWmpZlxaZA2lj3yjjYnY2bSQBfmA45fF7tBZfHq/ObWFbpvCOU5LMwKjffoPpC +-----END RSA PRIVATE KEY----- diff --git a/pkg/config/config.go b/pkg/config/config.go index 7959f299e23..8fb2a9b40d0 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -323,6 +323,71 @@ type NFPMScripts struct { PostRemove string `yaml:"postremove,omitempty"` } +type NFPMRPMSignature struct { + // PGP secret key, can be ASCII-armored + KeyFile string `yaml:"key_file,omitempty"` + KeyPassphrase string `yaml:"-"` // populated from environment variable +} + +// NFPMRPM is custom configs that are only available on RPM packages. +type NFPMRPM struct { + Group string `yaml:"group,omitempty"` + Compression string `yaml:"compression,omitempty"` + // https://www.cl.cam.ac.uk/~jw35/docs/rpm_config.html + ConfigNoReplaceFiles map[string]string `yaml:"config_noreplace_files,omitempty"` + Signature NFPMRPMSignature `yaml:"signature,omitempty"` +} + +// NFPMDebScripts is scripts only available on deb packages. +type NFPMDebScripts struct { + Rules string `yaml:"rules,omitempty"` + Templates string `yaml:"templates,omitempty"` +} + +// NFPMDebTriggers contains triggers only available for deb packages. +// https://wiki.debian.org/DpkgTriggers +// https://man7.org/linux/man-pages/man5/deb-triggers.5.html +type NFPMDebTriggers struct { + Interest []string `yaml:"interest,omitempty"` + InterestAwait []string `yaml:"interest_await,omitempty"` + InterestNoAwait []string `yaml:"interest_noawait,omitempty"` + Activate []string `yaml:"activate,omitempty"` + ActivateAwait []string `yaml:"activate_await,omitempty"` + ActivateNoAwait []string `yaml:"activate_noawait,omitempty"` +} + +// NFPMDebSignature contains config for signing deb packages created by nfpm. +type NFPMDebSignature struct { + // PGP secret key, can be ASCII-armored + KeyFile string `yaml:"key_file,omitempty"` + KeyPassphrase string `yaml:"-"` // populated from environment variable + // origin, maint or archive (defaults to origin) + Type string `yaml:"type,omitempty"` +} + +// NFPMDeb is custom configs that are only available on deb packages. +type NFPMDeb struct { + Scripts NFPMDebScripts `yaml:"scripts,omitempty"` + Triggers NFPMDebTriggers `yaml:"triggers,omitempty"` + Breaks []string `yaml:"breaks,omitempty"` + VersionMetadata string `yaml:"metadata,omitempty"` // Deprecated: Moved to Info + Signature NFPMDebSignature `yaml:"signature,omitempty"` +} + +// NFPMAPKSignature contains config for signing apk packages created by nfpm. +type NFPMAPKSignature struct { + // RSA private key in PEM format + KeyFile string `yaml:"key_file,omitempty"` + KeyPassphrase string `yaml:"-"` // populated from environment variable + // defaults to .rsa.pub + KeyName string `yaml:"key_name,omitempty"` +} + +// NFPMAPK is custom config only available on apk packages. +type NFPMAPK struct { + Signature NFPMAPKSignature `yaml:"signature,omitempty"` +} + // NFPMOverridables is used to specify per package format settings. type NFPMOverridables struct { FileNameTemplate string `yaml:"file_name_template,omitempty"` @@ -338,6 +403,9 @@ type NFPMOverridables struct { Files map[string]string `yaml:",omitempty"` ConfigFiles map[string]string `yaml:"config_files,omitempty"` Scripts NFPMScripts `yaml:"scripts,omitempty"` + RPM NFPMRPM `yaml:"rpm,omitempty"` + Deb NFPMDeb `yaml:"deb,omitempty"` + APK NFPMAPK `yaml:"apk,omitempty"` } // Sign config. diff --git a/www/docs/customization/nfpm.md b/www/docs/customization/nfpm.md index f72ae1349d3..a906f6198b3 100644 --- a/www/docs/customization/nfpm.md +++ b/www/docs/customization/nfpm.md @@ -156,6 +156,83 @@ nfpms: "tmp/app_generated.conf": "/etc/app-rpm.conf" scripts: preinstall: "scripts/preinstall-rpm.sh" + + # Custon configuration applied only to the RPM packager. + rpm: + # The package group. This option is deprecated by most distros + # but required by old distros like CentOS 5 / EL 5 and earlier. + group: Unspecified + + # Compression algorithm. + compression: lzma + + # These config files will not be replaced by new versions if they were + # changed by the user. Corresponds to %config(noreplace). + config_noreplace_files: + path/to/local/bar.con: /etc/bar.conf + + # The package is signed if a key_file is set + signature: + # PGP secret key (can also be ASCII-armored). The passphrase is taken + # from the environment variable $NFPM_ID_RPM_PASSPHRASE with a fallback + # to $NFPM_ID_PASSPHRASE, where ID is the id of the current nfpm config. + # The id will be transformed to uppercase. + # E.g. If your nfpm id is 'default' then the rpm-specific passphrase + # should be set as $NFPM_DEFAULT_RPM_PASSPHRASE + key_file: key.gpg + + # Custom configuration applied only to the Deb packager. + deb: + # Custom deb rules script. + scripts: + rules: foo.sh + # Deb templates file, when using debconf. + templates: templates + + # Custom deb triggers + triggers: + # register interrest on a trigger activated by another package + # (also available: interest_await, interest_noawait) + interest: + - some-trigger-name + # activate a trigger for another package + # (also available: activate_await, activate_noawait) + activate: + - another-trigger-name + + # Packages which would break if this package would be installed. + # The installation of this package is blocked if `some-package` + # is already installed. + breaks: + - some-package + + # The package is signed if a key_file is set + signature: + # PGP secret key (can also be ASCII-armored). The passphrase is taken + # from the environment variable $NFPM_ID_DEB_PASSPHRASE with a fallback + # to $NFPM_ID_PASSPHRASE, where ID is the id of the current nfpm config. + # The id will be transformed to uppercase. + # E.g. If your nfpm id is 'default' then the deb-specific passphrase + # should be set as $NFPM_DEFAULT_DEB_PASSPHRASE + key_file: key.gpg + # The type describes the signers role, possible values are "origin", + # "maint" and "archive". If unset, the type defaults to "origin". + type: origin + + apk: + # The package is signed if a key_file is set + signature: + # RSA private key in the PEM format. The passphrase is taken + # from the environment variable $NFPM_ID_APK_PASSPHRASE with a fallback + # to $NFPM_ID_PASSPHRASE, where ID is the id of the current nfpm config. + # The id will be transformed to uppercase. + # E.g. If your nfpm id is 'default' then the deb-specific passphrase + # should be set as $NFPM_DEFAULT_APK_PASSPHRASE + key_file: key.gpg + # The name of the signing key. When verifying a package, the signature + # is matched to the public key store in /etc/apk/keys/.rsa.pub. + # If unset, it defaults to the maintainer email address. + key_name: origin ``` !!! tip