[updates] (Linux): custom post-upgrade commands + command to restore SELinux context
This commit is contained in:
@@ -92,7 +92,8 @@ func (sc *ServiceConfig) Init() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getCurrentBinaryFolder() (string, error) {
|
// returns the absolute path of the currently running executable
|
||||||
|
func getCurrentBinaryPath() (string, error) {
|
||||||
// Get the path of the currently running executable
|
// Get the path of the currently running executable
|
||||||
exePath, err := os.Executable()
|
exePath, err := os.Executable()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -105,6 +106,16 @@ func getCurrentBinaryFolder() (string, error) {
|
|||||||
return "", fmt.Errorf("failed to get absolute path: %w", err)
|
return "", fmt.Errorf("failed to get absolute path: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return absPath, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getCurrentBinaryFolder() (string, error) {
|
||||||
|
// Get the absolute path of the currently running executable
|
||||||
|
absPath, err := getCurrentBinaryPath()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
// Get the directory of the executable
|
// Get the directory of the executable
|
||||||
installDir := filepath.Dir(absPath)
|
installDir := filepath.Dir(absPath)
|
||||||
|
|
||||||
@@ -160,6 +171,21 @@ func MakeUpdateConfigs(svcCfg *ServiceConfig) (binaryUpdateConfig, intelUpdateCo
|
|||||||
NeedsRestart: true,
|
NeedsRestart: true,
|
||||||
Notify: true,
|
Notify: true,
|
||||||
}
|
}
|
||||||
|
if binPath, err := getCurrentBinaryPath(); err == nil {
|
||||||
|
binaryUpdateConfig.PostUpgradeCommands = []updates.UpdateCommandConfig{
|
||||||
|
// Restore SELinux context for the new core binary after upgrade
|
||||||
|
// (`restorecon /usr/lib/portmaster/portmaster-core`)
|
||||||
|
{
|
||||||
|
Command: "restorecon",
|
||||||
|
Args: []string{binPath},
|
||||||
|
TriggerArtifactFName: binPath,
|
||||||
|
FailOnError: false, // Ignore error: 'restorecon' may not be available on a non-SELinux systems.
|
||||||
|
},
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return nil, nil, fmt.Errorf("failed to get current binary path: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
intelUpdateConfig = &updates.Config{
|
intelUpdateConfig = &updates.Config{
|
||||||
Name: configure.DefaultIntelIndexName,
|
Name: configure.DefaultIntelIndexName,
|
||||||
Directory: filepath.Join(svcCfg.DataDir, "intel"),
|
Directory: filepath.Join(svcCfg.DataDir, "intel"),
|
||||||
|
|||||||
@@ -51,6 +51,22 @@ var (
|
|||||||
ErrActionRequired = errors.New("action required")
|
ErrActionRequired = errors.New("action required")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// UpdateCommandConfig defines the configuration for a shell command
|
||||||
|
// that is executed when an update is applied
|
||||||
|
type UpdateCommandConfig struct {
|
||||||
|
// Shell command to execute
|
||||||
|
Command string
|
||||||
|
// Arguments to pass to the command
|
||||||
|
Args []string
|
||||||
|
// Execute triggers: if not empty, the command will be executed only if specified file was updated
|
||||||
|
// if empty, the command will be executed always
|
||||||
|
TriggerArtifactFName string
|
||||||
|
// FailOnError defines whether the upgrade should fail if the command fails
|
||||||
|
// true - upgrade will fail if the command fails
|
||||||
|
// false - upgrade will continue even if the command fails
|
||||||
|
FailOnError bool
|
||||||
|
}
|
||||||
|
|
||||||
// Config holds the configuration for the updates module.
|
// Config holds the configuration for the updates module.
|
||||||
type Config struct {
|
type Config struct {
|
||||||
// Name of the updater.
|
// Name of the updater.
|
||||||
@@ -88,6 +104,9 @@ type Config struct {
|
|||||||
// Notify defines whether the user shall be informed about events via notifications.
|
// Notify defines whether the user shall be informed about events via notifications.
|
||||||
// If enabled, disables automatic restart after upgrade.
|
// If enabled, disables automatic restart after upgrade.
|
||||||
Notify bool
|
Notify bool
|
||||||
|
|
||||||
|
// list of shell commands needed to run after the upgrade (if any)
|
||||||
|
PostUpgradeCommands []UpdateCommandConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check looks for obvious configuration errors.
|
// Check looks for obvious configuration errors.
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -32,6 +33,12 @@ func (u *Updater) upgrade(downloader *Downloader, ignoreVersion bool) error {
|
|||||||
|
|
||||||
// Execute the upgrade.
|
// Execute the upgrade.
|
||||||
upgradeError := u.upgradeMoveFiles(downloader)
|
upgradeError := u.upgradeMoveFiles(downloader)
|
||||||
|
if upgradeError == nil {
|
||||||
|
// Files upgraded successfully.
|
||||||
|
// Applying post-upgrade tasks, if any.
|
||||||
|
upgradeError = u.applyPostUpgradeCommands()
|
||||||
|
}
|
||||||
|
|
||||||
if upgradeError == nil {
|
if upgradeError == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -209,3 +216,41 @@ func (u *Updater) deleteUnfinishedFiles(dir string) error {
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u *Updater) applyPostUpgradeCommands() error {
|
||||||
|
// At this point, we assume that the upgrade was successful and all files are in place.
|
||||||
|
// We need to execute the post-upgrade commands, if any.
|
||||||
|
|
||||||
|
if len(u.cfg.PostUpgradeCommands) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// collect full paths to files that were upgraded, required to check the trigger.
|
||||||
|
upgradedFiles := make(map[string]struct{})
|
||||||
|
for _, artifact := range u.index.Artifacts {
|
||||||
|
upgradedFiles[filepath.Join(u.cfg.Directory, artifact.Filename)] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute post-upgrade commands.
|
||||||
|
for _, puCmd := range u.cfg.PostUpgradeCommands {
|
||||||
|
|
||||||
|
// Check trigger to ensure that we need to run this command.
|
||||||
|
if len(puCmd.TriggerArtifactFName) > 0 {
|
||||||
|
if _, ok := upgradedFiles[puCmd.TriggerArtifactFName]; !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debugf("updates/%s: executing post-upgrade command: '%s %s'", u.cfg.Name, puCmd.Command, strings.Join(puCmd.Args, " "))
|
||||||
|
output, err := exec.Command(puCmd.Command, puCmd.Args...).CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
if puCmd.FailOnError {
|
||||||
|
return fmt.Errorf("post-upgrade command '%s %s' failed: %w, output: %s", puCmd.Command, strings.Join(puCmd.Args, " "), err, string(output))
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Warningf("updates/%s: post-upgrade command '%s %s' failed, but ignored. Error: %s", u.cfg.Name, puCmd.Command, strings.Join(puCmd.Args, " "), err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user