diff --git a/.scripts/packages.list b/.scripts/packages.list
index 124a356d..d3aeb400 100644
--- a/.scripts/packages.list
+++ b/.scripts/packages.list
@@ -47,6 +47,7 @@ L + netutil
* + rand
* + req
* + secstr
+L - setup
* - signal
* + sliceutil
* + sortutil
diff --git a/CHANGELOG.md b/CHANGELOG.md
index dbadec37..9bc89d7e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,11 @@
## Changelog
+### [13.6.0](https://kaos.sh/ek/13.6.0)
+
+- `[setup]` Added package to install/uninstall application as a service
+- `[support/deps]` Improved collecting and filtering dependencies info
+- `[support/kernel]` Added simple globs support for parameter names
+
### [13.5.1](https://kaos.sh/ek/13.5.1)
- `[mathutil]` Added method `FromPerc`
diff --git a/README.md b/README.md
index dd2a547d..3c022c8a 100644
--- a/README.md
+++ b/README.md
@@ -14,7 +14,7 @@
-Auxiliary packages for Go 1.22+.
+Auxiliary packages for [Go 1.22+](https://github.com/essentialkaos/.github/blob/master/GO-VERSION-SUPPORT.md).
### Platform support
diff --git a/go.mod b/go.mod
index e2271e62..6a3c76dd 100644
--- a/go.mod
+++ b/go.mod
@@ -1,17 +1,17 @@
module github.com/essentialkaos/ek/v13
-go 1.21
+go 1.22.8
require (
github.com/essentialkaos/check v1.4.0
- github.com/essentialkaos/depsy v1.3.0
- github.com/essentialkaos/go-linenoise/v3 v3.6.0
- golang.org/x/crypto v0.27.0
- golang.org/x/sys v0.25.0
+ github.com/essentialkaos/depsy v1.3.1
+ github.com/essentialkaos/go-linenoise/v3 v3.6.1
+ golang.org/x/crypto v0.28.0
+ golang.org/x/sys v0.26.0
)
require (
github.com/kr/pretty v0.3.1 // indirect
github.com/kr/text v0.2.0 // indirect
- github.com/rogpeppe/go-internal v1.11.0 // indirect
+ github.com/rogpeppe/go-internal v1.13.1 // indirect
)
diff --git a/go.sum b/go.sum
index cc6de943..ab452beb 100644
--- a/go.sum
+++ b/go.sum
@@ -1,19 +1,19 @@
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/essentialkaos/check v1.4.0 h1:kWdFxu9odCxUqo1NNFNJmguGrDHgwi3A8daXX1nkuKk=
github.com/essentialkaos/check v1.4.0/go.mod h1:LMKPZ2H+9PXe7Y2gEoKyVAwUqXVgx7KtgibfsHJPus0=
-github.com/essentialkaos/depsy v1.3.0 h1:CN7bRgBU2jGTHSkg/Sh38eDUn7cvmaTp2sxFt2HpFeU=
-github.com/essentialkaos/depsy v1.3.0/go.mod h1:kpiTAV17dyByVnrbNaMcZt2jRwvuXClUYOzpyJQwtG8=
-github.com/essentialkaos/go-linenoise/v3 v3.6.0 h1:deLcrodtLIkcHjNyW/MoQpjznXPVqvwlspxk7s/5YeY=
-github.com/essentialkaos/go-linenoise/v3 v3.6.0/go.mod h1:Fi6kLdZdURkXHpRkIiX2nFGORNv81CXTZ2Mn72i/cn0=
+github.com/essentialkaos/depsy v1.3.1 h1:00k9QcMsdPM4IzDaEFHsTHBD/zoM0oxtB5+dMUwbQa8=
+github.com/essentialkaos/depsy v1.3.1/go.mod h1:B5+7Jhv2a2RacOAxIKU2OeJp9QfZjwIpEEPI5X7auWM=
+github.com/essentialkaos/go-linenoise/v3 v3.6.1 h1:VzjakaWNAPfattl/HSIJveWYpfrxAYHzUl8u6DdOjtY=
+github.com/essentialkaos/go-linenoise/v3 v3.6.1/go.mod h1:hpxke5G2eXvFhVnDAiGU9ArH3MTDGGc13afObIbuaJQ=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
-github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
-github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
-golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A=
-golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70=
-golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
-golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
+github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
+golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
+golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
+golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
+golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
diff --git a/req/req.go b/req/req.go
index 3cc49b6a..0a8aa775 100644
--- a/req/req.go
+++ b/req/req.go
@@ -177,7 +177,7 @@ const (
)
// USER_AGENT is default user agent
-const USER_AGENT = "go-ek-req"
+const USER_AGENT = "ek.go"
// ////////////////////////////////////////////////////////////////////////////////// //
@@ -319,7 +319,7 @@ func (e *Engine) Init() *Engine {
}
if e.UserAgent == "" {
- e.SetUserAgent(USER_AGENT, "10")
+ e.SetUserAgent(USER_AGENT, "13")
}
e.dialTimeout = 0
diff --git a/setup/setup.go b/setup/setup.go
new file mode 100644
index 00000000..24706394
--- /dev/null
+++ b/setup/setup.go
@@ -0,0 +1,450 @@
+//go:build linux
+// +build linux
+
+// Package setup provides methods to install/unistall application as a service on the
+// system
+package setup
+
+// ////////////////////////////////////////////////////////////////////////////////// //
+// //
+// Copyright (c) 2024 ESSENTIAL KAOS //
+// Apache License, Version 2.0 //
+// //
+// ////////////////////////////////////////////////////////////////////////////////// //
+
+import (
+ "bytes"
+ "fmt"
+ "os"
+ "os/exec"
+ "strings"
+
+ "github.com/essentialkaos/ek/v13/fsutil"
+ "github.com/essentialkaos/ek/v13/hash"
+ "github.com/essentialkaos/ek/v13/path"
+ "github.com/essentialkaos/ek/v13/strutil"
+ "github.com/essentialkaos/ek/v13/system"
+)
+
+// ////////////////////////////////////////////////////////////////////////////////// //
+
+// App contains basic application configuration
+type App struct {
+ Name string // Application name
+ Options []string // List of options
+ DocsURL string // Documentation URL
+ User string // Service user
+ Identifier string // Syslog identifier
+ WorkingDir string // Working dir
+
+ StopSignal string // Stop signal
+ ReloadSignal string // Reload signal
+
+ WithLog bool // Create directory for logs
+ WithoutPrivateTemp bool // Disable private temp
+
+ Configs []Config // Configuration files
+}
+
+// Config contains configuration file data
+//
+// Note that all configurations are stored in /etc
+type Config struct {
+ Name string // File name
+ Data []byte // Data
+ Mode os.FileMode // File mode
+}
+
+// ////////////////////////////////////////////////////////////////////////////////// //
+
+// binaryInfo contains basic info about binary
+type binaryInfo struct {
+ File string // Path to binary file
+ Name string // Binary file name
+}
+
+// ////////////////////////////////////////////////////////////////////////////////// //
+
+// serviceDir is path to directory with system service files
+var serviceDir = "/usr/lib/systemd/system"
+
+// binaryDir is path to directory with binaries
+var binaryDir = "/usr/bin"
+
+// logDir is path to directory with logs
+var logDir = "/var/log"
+
+// configDir is path to directory with configuration files
+var configDir = "/etc"
+
+// unitComment is comment for service unit
+var unitComment = "# Unit generated by ek.go/setup"
+
+// checksum is current binary checksum
+var checksum string
+
+// ////////////////////////////////////////////////////////////////////////////////// //
+
+// Install installs or reinstalls application on the system
+func (app App) Install() error {
+ bin := getBinaryInfo()
+ err := checkForInstall(app, bin)
+
+ if err != nil {
+ return err
+ }
+
+ err = installFiles(app, bin)
+
+ if err != nil {
+ return err
+ }
+
+ exec.Command("systemctl", "daemon-reload").Run()
+
+ return nil
+}
+
+// Uninstall uninstall unistalls application from the system
+func (app App) Uninstall(full bool) error {
+ bin := getBinaryInfo()
+ err := checkForUninstall(app, bin)
+
+ if err != nil {
+ return err
+ }
+
+ err = uninstallFiles(app, bin, full)
+
+ if err != nil {
+ return err
+ }
+
+ exec.Command("systemctl", "daemon-reload").Run()
+
+ return nil
+}
+
+// ////////////////////////////////////////////////////////////////////////////////// //
+
+// BinPath returns binary installation path
+func (b *binaryInfo) BinInstallPath() string {
+ return path.Join(binaryDir, b.Name)
+}
+
+// LogDir returns logs directory path
+func (b *binaryInfo) LogDir() string {
+ return path.Join(logDir, b.Name)
+}
+
+// ServiceUnitPath returns service unit path
+func (b *binaryInfo) ServiceUnitPath() string {
+ return path.Join(serviceDir, b.Name+".service")
+}
+
+// IsBinInstalled returns true if current binary already installed
+func (b *binaryInfo) IsBinInstalled() bool {
+ return fsutil.IsExist(b.BinInstallPath()) &&
+ b.Checksum() == hash.FileHash(b.BinInstallPath())
+}
+
+// IsServiceInstalled returns true if service unit already installed
+func (b *binaryInfo) IsServiceInstalled() bool {
+ return fsutil.IsExist(b.ServiceUnitPath())
+}
+
+// Checksum returns checksum of current binary
+func (b *binaryInfo) Checksum() string {
+ if checksum != "" {
+ return checksum
+ }
+
+ checksum = hash.FileHash(b.File)
+
+ return checksum
+}
+
+// ////////////////////////////////////////////////////////////////////////////////// //
+
+// getBinary returns basic info about current binary
+func getBinaryInfo() binaryInfo {
+ bin, _ := os.Executable()
+ binFile := path.Clean(bin)
+ binName := path.Base(binFile)
+
+ return binaryInfo{File: binFile, Name: binName}
+}
+
+// checkForInstall checks if app can be installed
+func checkForInstall(app App, bin binaryInfo) error {
+ if len(app.Configs) != 0 {
+ for _, c := range app.Configs {
+ if strings.Contains(c.Name, "/") {
+ return fmt.Errorf("Configuration file name %q is invalid", c.Name)
+ }
+ }
+ }
+
+ user, err := system.CurrentUser()
+
+ if err != nil {
+ return fmt.Errorf("Can't get info about current user: %w", err)
+ }
+
+ if !user.IsRoot() {
+ return fmt.Errorf("You must have superuser (root) privileges to install app as a service")
+ }
+
+ if bin.IsServiceInstalled() {
+ isGenerated, err := isGeneratedUnit(bin.ServiceUnitPath())
+
+ if err != nil {
+ return fmt.Errorf("Can't check systemd unit: %w", err)
+ }
+
+ if !isGenerated {
+ return fmt.Errorf("Can't replace systemd unit, it wasn't created by app")
+ }
+ }
+
+ return nil
+}
+
+// checkForUninstall checks if app can be uninstalled
+func checkForUninstall(app App, bin binaryInfo) error {
+ if len(app.Configs) != 0 {
+ for _, c := range app.Configs {
+ if strings.Contains(c.Name, "/") {
+ return fmt.Errorf("Configuration file name %q is invalid", c.Name)
+ }
+ }
+ }
+
+ if !bin.IsBinInstalled() {
+ return fmt.Errorf("Binary is not installed (new binary?)")
+ }
+
+ if bin.File != bin.BinInstallPath() {
+ return fmt.Errorf("You must use installed binary to uninstall service from the system")
+ }
+
+ user, err := system.CurrentUser()
+
+ if err != nil {
+ return fmt.Errorf("Can't get info about current user: %w", err)
+ }
+
+ if !user.IsRoot() {
+ return fmt.Errorf("You must have superuser (root) privileges to uninstall this app")
+ }
+
+ if bin.IsServiceInstalled() {
+ isGenerated, err := isGeneratedUnit(bin.ServiceUnitPath())
+
+ if err != nil {
+ return fmt.Errorf("Can't check systemd unit: %w", err)
+ }
+
+ if !isGenerated {
+ return fmt.Errorf("Can't uninstall systemd unit, it wasn't created by app")
+ }
+ }
+
+ return nil
+}
+
+// installFiles installs binary and systemd unit file
+func installFiles(app App, bin binaryInfo) error {
+ err := installConfigurationFiles(app)
+
+ if err != nil {
+ return err
+ }
+
+ if !bin.IsBinInstalled() {
+ err := fsutil.CopyFile(bin.File, bin.BinInstallPath(), 0755)
+
+ if err != nil {
+ return fmt.Errorf("Can't copy binary to %s: %w", binaryDir, err)
+ }
+ }
+
+ err = createServiceFile(app, bin)
+
+ if err != nil {
+ return fmt.Errorf("Can't install systemd service: %w", err)
+ }
+
+ if app.WithLog && !fsutil.IsExist(bin.LogDir()) {
+ err := os.Mkdir(bin.LogDir(), 0755)
+
+ if err != nil {
+ return fmt.Errorf("Can't create directory for logs (%s): %w", bin.LogDir(), err)
+ }
+ }
+
+ return nil
+}
+
+// uninstallFiles removes binary and systemd unit file
+func uninstallFiles(app App, bin binaryInfo, full bool) error {
+ if app.WithLog && full {
+ os.RemoveAll(bin.LogDir())
+ }
+
+ err := os.Remove(bin.ServiceUnitPath())
+
+ if err != nil {
+ return fmt.Errorf("Can't remove systemd unit: %w", err)
+ }
+
+ err = os.Remove(bin.BinInstallPath())
+
+ if err != nil {
+ return fmt.Errorf("Can't remove binary: %w", err)
+ }
+
+ if full && len(app.Configs) > 0 {
+ return uninstallConfigurationFiles(app)
+ }
+
+ return nil
+}
+
+// installConfigurationFiles creates all configuration files
+func installConfigurationFiles(app App) error {
+ if len(app.Configs) == 0 {
+ return nil
+ }
+
+ for _, c := range app.Configs {
+ filePath := path.Clean(path.Join(configDir, c.Name))
+
+ // Don't rewrite files
+ if len(c.Data) == 0 || fsutil.IsExist(filePath) {
+ continue
+ }
+
+ fileMode := c.Mode
+
+ if fileMode == 0 {
+ fileMode = 0640
+ }
+
+ err := os.WriteFile(filePath, c.Data, fileMode)
+
+ if err != nil {
+ return fmt.Errorf("Can't create configuration file %q: %w", filePath, err)
+ }
+ }
+
+ return nil
+}
+
+// uninstallConfigurationFiles removes all configuration files
+func uninstallConfigurationFiles(app App) error {
+ if len(app.Configs) == 0 {
+ return nil
+ }
+
+ for _, c := range app.Configs {
+ filePath := path.Clean(path.Join(configDir, c.Name))
+
+ if !fsutil.IsExist(filePath) {
+ continue
+ }
+
+ err := os.Remove(filePath)
+
+ if err != nil {
+ return fmt.Errorf("Can't delete configuration file %q: %w", filePath, err)
+ }
+ }
+
+ return nil
+}
+
+// createServiceFile creates systemd service file
+func createServiceFile(app App, bin binaryInfo) error {
+ serviceUnitData := generateServiceUnit(app, bin)
+
+ err := os.WriteFile(bin.ServiceUnitPath(), serviceUnitData, 0644)
+
+ if err != nil {
+ return fmt.Errorf("Can't create service file (%s): %w", bin.ServiceUnitPath(), err)
+ }
+
+ return nil
+}
+
+// generateServiceUnit generates systemd service file
+func generateServiceUnit(app App, bin binaryInfo) []byte {
+ var buf bytes.Buffer
+
+ buf.WriteString(fmt.Sprintf(
+ "%s (%s)\n\n",
+ unitComment,
+ strutil.Head(bin.Checksum(), 7),
+ ))
+
+ buf.WriteString("[Unit]\n")
+ buf.WriteString(fmt.Sprintf("Description=%s\n", strutil.Q(app.Name, bin.Name)))
+
+ if app.DocsURL != "" {
+ buf.WriteString(fmt.Sprintf("Documentation=%s\n", app.DocsURL))
+ }
+
+ buf.WriteString("After=network-online.target remote-fs.target nss-lookup.target\n")
+ buf.WriteString("Wants=network-online.target\n\n")
+ buf.WriteString("[Service]\n")
+ buf.WriteString("Type=simple\n")
+
+ if app.User != "" {
+ buf.WriteString(fmt.Sprintf("User=%s\n", app.User))
+ }
+
+ if len(app.Options) > 0 {
+ buf.WriteString(fmt.Sprintf(
+ "ExecStart=%s %s\n", path.Join(binaryDir, bin.Name), strings.Join(app.Options, " "),
+ ))
+ } else {
+ buf.WriteString(fmt.Sprintf("ExecStart=%s\n", path.Join(binaryDir, bin.Name)))
+ }
+
+ if app.ReloadSignal != "" {
+ buf.WriteString(fmt.Sprintf(
+ "ExecReload=/bin/kill -s %s $MAINPID\n", app.ReloadSignal,
+ ))
+ }
+
+ if app.StopSignal != "" {
+ buf.WriteString(fmt.Sprintf(
+ "ExecStop=/bin/kill -s %s $MAINPID\n", app.StopSignal,
+ ))
+ }
+
+ if app.WithLog {
+ buf.WriteString(fmt.Sprintf(
+ "StandardError=file:%s/startup.log\n", bin.LogDir(),
+ ))
+ }
+
+ if !app.WithoutPrivateTemp {
+ buf.WriteString("PrivateTmp=true\n")
+ }
+
+ buf.WriteString("\n[Install]\nWantedBy=multi-user.target\n\n")
+
+ return buf.Bytes()
+}
+
+// isGeneratedUnit returns true if given systemd unit generated by this package
+func isGeneratedUnit(file string) (bool, error) {
+ unitData, err := os.ReadFile(file)
+
+ if err != nil {
+ return false, err
+ }
+
+ return bytes.HasPrefix(unitData, []byte(unitComment)), nil
+}
diff --git a/setup/setup_stub.go b/setup/setup_stub.go
new file mode 100644
index 00000000..c0122d77
--- /dev/null
+++ b/setup/setup_stub.go
@@ -0,0 +1,56 @@
+//go:build !linux
+// +build !linux
+
+// Package setup provides methods to install/unistall application as a service on the
+// system
+package setup
+
+// ////////////////////////////////////////////////////////////////////////////////// //
+// //
+// Copyright (c) 2024 ESSENTIAL KAOS //
+// Apache License, Version 2.0 //
+// //
+// ////////////////////////////////////////////////////////////////////////////////// //
+
+import "os"
+
+// ////////////////////////////////////////////////////////////////////////////////// //
+
+// ❗ App contains basic application configuration
+type App struct {
+ Name string // Application name
+ Options []string // List of options
+ DocsURL string // Documentation URL
+ User string // Service user
+ Identifier string // Syslog identifier
+ WorkingDir string // Working dir
+
+ StopSignal string // Stop signal
+ ReloadSignal string // Reload signal
+
+ WithLog bool // Create directory for logs
+ WithoutPrivateTemp bool // Disable private temp
+
+ Configs []Config // Configuration files
+}
+
+// ❗ Config contains configuration file data
+//
+// Note that all configurations are stored in /etc
+type Config struct {
+ Name string // File name
+ Data []byte // Data
+ Mode os.FileMode // File mode
+}
+
+// ////////////////////////////////////////////////////////////////////////////////// //
+
+// ❗ Install installs or reinstalls application on the system
+func (app App) Install() error {
+ panic("UNSUPPORTED")
+}
+
+// ❗ Uninstall uninstall unistalls application from the system
+func (app App) Uninstall(full bool) error {
+ panic("UNSUPPORTED")
+}
diff --git a/setup/setup_test.go b/setup/setup_test.go
new file mode 100644
index 00000000..94e2a0dc
--- /dev/null
+++ b/setup/setup_test.go
@@ -0,0 +1,173 @@
+package setup
+
+// ////////////////////////////////////////////////////////////////////////////////// //
+// //
+// Copyright (c) 2024 ESSENTIAL KAOS //
+// Apache License, Version 2.0 //
+// //
+// ////////////////////////////////////////////////////////////////////////////////// //
+
+import (
+ "errors"
+ "testing"
+
+ . "github.com/essentialkaos/check"
+)
+
+// ////////////////////////////////////////////////////////////////////////////////// //
+
+func Test(t *testing.T) { TestingT(t) }
+
+type SetupSuite struct{}
+
+// ////////////////////////////////////////////////////////////////////////////////// //
+
+var _ = Suite(&SetupSuite{})
+
+// ////////////////////////////////////////////////////////////////////////////////// //
+
+func (s *SetupSuite) TestInstall(c *C) {
+ serviceDir = c.MkDir()
+ binaryDir = c.MkDir()
+ logDir = c.MkDir()
+ configDir = c.MkDir()
+
+ app, bin := s.generateApp(), s.generateBin()
+
+ err := installFiles(app, bin)
+ c.Assert(err, IsNil)
+
+ isGen, _ := isGeneratedUnit(bin.ServiceUnitPath())
+ c.Assert(isGen, Equals, true)
+ _, err = isGeneratedUnit("/_unknown_")
+ c.Assert(err, NotNil)
+
+ app = s.generateApp()
+ app.Options = nil
+
+ err = installFiles(app, bin)
+ c.Assert(err, IsNil)
+
+ c.Assert(bin.IsBinInstalled(), Equals, true)
+ c.Assert(bin.IsServiceInstalled(), Equals, true)
+}
+
+func (s *SetupSuite) TestInstallErrors(c *C) {
+ serviceDir = c.MkDir()
+ binaryDir = c.MkDir()
+
+ app, bin := s.generateApp(), s.generateBin()
+
+ logDir = "/_unknown_"
+ err := installFiles(app, bin)
+ c.Assert(err, NotNil)
+
+ serviceDir = "/_unknown_"
+ err = installFiles(app, bin)
+ c.Assert(err, NotNil)
+
+ binaryDir = "/_unknown_"
+ err = installFiles(app, bin)
+ c.Assert(err, NotNil)
+
+ app.Configs = []Config{
+ {"abcd/test1.knf", []byte("[main]\n test: 1\n\n"), 0},
+ }
+
+ err = installFiles(app, bin)
+ c.Assert(err, NotNil)
+}
+
+func (s *SetupSuite) TestUninstall(c *C) {
+ serviceDir = c.MkDir()
+ binaryDir = c.MkDir()
+ logDir = c.MkDir()
+ configDir = c.MkDir()
+
+ app, bin := s.generateApp(), s.generateBin()
+
+ err := installFiles(app, bin)
+ c.Assert(err, IsNil)
+
+ app.Configs = append(app.Configs, Config{Name: "testX.knf"})
+
+ err = uninstallFiles(app, bin, true)
+ c.Assert(err, IsNil)
+
+ app.Configs = nil
+
+ err = installFiles(app, bin)
+ c.Assert(err, IsNil)
+
+ err = uninstallFiles(app, bin, true)
+ c.Assert(err, IsNil)
+
+ c.Assert(uninstallConfigurationFiles(app), IsNil)
+}
+
+func (s *SetupSuite) TestUninstallErrors(c *C) {
+ serviceDir = c.MkDir()
+ binaryDir = c.MkDir()
+ logDir = c.MkDir()
+
+ app, bin := s.generateApp(), s.generateBin()
+
+ err := installFiles(app, bin)
+ c.Assert(err, IsNil)
+
+ binaryDir = "/_unknown_"
+ err = uninstallFiles(app, bin, true)
+ c.Assert(err, NotNil)
+
+ serviceDir = "/_unknown_"
+ err = uninstallFiles(app, bin, true)
+ c.Assert(err, NotNil)
+}
+
+func (s *SetupSuite) TestAux(c *C) {
+ bin := getBinaryInfo()
+
+ app, bin := s.generateApp(), s.generateBin()
+
+ app.Install()
+ app.Uninstall(false)
+
+ c.Assert(bin.File, Not(Equals), "")
+ c.Assert(bin.Name, Not(Equals), "")
+
+ c.Assert(checkForInstall(app, bin), NotNil)
+ c.Assert(checkForUninstall(app, bin), NotNil)
+
+ app = s.generateApp()
+ app.Configs = []Config{
+ {"abcd/test1.knf", []byte("[main]\n test: 1\n\n"), 0},
+ }
+
+ c.Assert(checkForInstall(app, bin), DeepEquals, errors.New("Configuration file name \"abcd/test1.knf\" is invalid"))
+ c.Assert(checkForUninstall(app, bin), DeepEquals, errors.New("Configuration file name \"abcd/test1.knf\" is invalid"))
+}
+
+// ////////////////////////////////////////////////////////////////////////////////// //
+
+func (s *SetupSuite) generateApp() App {
+ return App{
+ Name: "Test",
+ Options: []string{"--config", "/etc/test.knf"},
+ DocsURL: "https://domain.com",
+ User: "nobody",
+ Identifier: "TEST",
+ WorkingDir: "/srv/test",
+ StopSignal: "TERM",
+ ReloadSignal: "HUP",
+ WithLog: true,
+ WithoutPrivateTemp: false,
+ Configs: []Config{
+ {"test1.knf", []byte("[main]\n test: 1\n\n"), 0},
+ {"test2.knf", []byte("[main]\n test: 2\n\n"), 0644},
+ },
+ }
+}
+
+func (s *SetupSuite) generateBin() binaryInfo {
+ return binaryInfo{File: "/usr/bin/echo", Name: "test1"}
+}
diff --git a/support/deps/deps.go b/support/deps/deps.go
index 3b10c0b0..dbdae68a 100644
--- a/support/deps/deps.go
+++ b/support/deps/deps.go
@@ -9,6 +9,9 @@ package deps
// ////////////////////////////////////////////////////////////////////////////////// //
import (
+ "runtime/debug"
+ "strings"
+
"github.com/essentialkaos/ek/v13/support"
"github.com/essentialkaos/depsy"
@@ -17,16 +20,58 @@ import (
// ////////////////////////////////////////////////////////////////////////////////// //
// Extract extracts dependencies info from gomod data
-func Extract(gomod []byte) []support.Dep {
+func Extract(gomod []byte, withIndirect ...bool) []support.Dep {
+ if len(withIndirect) > 0 && withIndirect[0] {
+ return filterDeps(depsy.Extract(gomod, true))
+ }
+
+ return filterDeps(depsy.Extract(gomod, false))
+}
+
+// ////////////////////////////////////////////////////////////////////////////////// //
+
+// filterDeps filters dependencies from gomod using information from bundled build info
+func filterDeps(deps depsy.Dependencies) []support.Dep {
var result []support.Dep
- for _, dep := range depsy.Extract(gomod, false) {
- result = append(result, support.Dep{
+ buildInfo, _ := debug.ReadBuildInfo()
+
+ for _, dep := range deps {
+ depInfo := support.Dep{
Version: dep.Version,
Path: dep.PrettyPath(),
Extra: dep.Extra,
- })
+ }
+
+ if buildInfo != nil {
+ hasDep, version := hasBuiltDep(dep, buildInfo)
+
+ if !hasDep {
+ continue
+ }
+
+ if version != "" && strings.Contains(version, "(") {
+ depInfo.Version = strings.Trim(version, "()")
+ }
+ }
+
+ result = append(result, depInfo)
}
return result
}
+
+// hasBuiltDep checks if given dependency is present in build info
+func hasBuiltDep(dep depsy.Dependency, buildInfo *debug.BuildInfo) (bool, string) {
+ for _, bDep := range buildInfo.Deps {
+ if bDep.Path == dep.Path {
+ if bDep.Replace != nil {
+ return true, bDep.Replace.Version
+ }
+
+ return true, ""
+ }
+ }
+
+ return false, ""
+}
diff --git a/support/kernel/kernel.go b/support/kernel/kernel.go
index d7f7b2f8..df673f72 100644
--- a/support/kernel/kernel.go
+++ b/support/kernel/kernel.go
@@ -32,9 +32,18 @@ func Collect(params ...string) []support.KernelParam {
var result []support.KernelParam
for _, param := range params {
+ isGlob := strings.HasSuffix(param, "*")
+ param = strings.TrimRight(param, "*")
+
for k, v := range kernelParams {
- if !strings.HasPrefix(k, param) {
- continue
+ if isGlob {
+ if !strings.HasPrefix(k, param) {
+ continue
+ }
+ } else {
+ if k != param {
+ continue
+ }
}
value := strings.ReplaceAll(v, "\t", " ")
diff --git a/support/support.go b/support/support.go
index 62cf369d..72a7b1dc 100644
--- a/support/support.go
+++ b/support/support.go
@@ -32,7 +32,7 @@ Example of collecting maximum information about the application and system:
WithNetwork(network.Collect("https://cloudflare.com/cdn-cgi/trace")).
WithFS(fs.Collect()).
WithResources(resources.Collect()).
- WithKernel(kernel.Collect()).
+ WithKernel(kernel.Collect("vm.nr_hugepages*", "vm.swappiness")).
Print()
Also, you can't encode data to JSON/GOB and send it to your server instead of printing
@@ -49,7 +49,7 @@ it to the console.
WithNetwork(network.Collect("https://cloudflare.com/cdn-cgi/trace")).
WithFS(fs.Collect()).
WithResources(resources.Collect()).
- WithKernel(kernel.Collect())
+ WithKernel(kernel.Collect("vm.nr_hugepages*", "vm.swappiness"))
b, _ := json.Marshal(info)
diff --git a/usage/update/github.go b/usage/update/github.go
index 3748a4e8..89d4a183 100644
--- a/usage/update/github.go
+++ b/usage/update/github.go
@@ -50,24 +50,23 @@ func getLatestGitHubRelease(app, version, repository string) *githubRelease {
engine.SetDialTimeout(3)
engine.SetRequestTimeout(3)
- engine.SetUserAgent(app, version, "GoEK.v13")
- response, err := engine.Get(req.Request{
+ resp, err := engine.Get(req.Request{
URL: githubAPI + "/repos/" + repository + "/releases/latest",
Headers: req.Headers{"X-GitHub-Api-Version": "2022-11-28"},
AutoDiscard: true,
})
- if err != nil || response.StatusCode != 200 {
+ if err != nil || resp.StatusCode != 200 {
return nil
}
- if response.Header.Get("X-RateLimit-Remaining") == "0" {
+ if resp.Header.Get("X-RateLimit-Remaining") == "0" {
return nil
}
release := &githubRelease{}
- err = response.JSON(release)
+ err = resp.JSON(release)
if err != nil {
return nil
diff --git a/usage/update/gitlab.go b/usage/update/gitlab.go
index 47018730..c83784d7 100644
--- a/usage/update/gitlab.go
+++ b/usage/update/gitlab.go
@@ -50,27 +50,26 @@ func getLatestGitLabRelease(app, version, repository string) *gitlabRelease {
engine.SetDialTimeout(3)
engine.SetRequestTimeout(3)
- engine.SetUserAgent(app, version, "GoEK.v13")
if strings.Contains(repository, "/") {
repository = strings.ReplaceAll(repository, "/", "%2F")
}
- response, err := engine.Get(req.Request{
+ resp, err := engine.Get(req.Request{
URL: gitlabAPI + "/projects/" + repository + "/releases/permalink/latest",
AutoDiscard: true,
})
- if err != nil || response.StatusCode != 200 {
+ if err != nil || resp.StatusCode != 200 {
return nil
}
- if response.Header.Get("RateLimit-Remaining") == "0" {
+ if resp.Header.Get("RateLimit-Remaining") == "0" {
return nil
}
release := &gitlabRelease{}
- err = response.JSON(release)
+ err = resp.JSON(release)
if err != nil {
return nil
diff --git a/usage/update/update_test.go b/usage/update/update_test.go
index 30c73cd4..fc17fcad 100644
--- a/usage/update/update_test.go
+++ b/usage/update/update_test.go
@@ -39,8 +39,6 @@ func (s *UpdateSuite) SetUpSuite(c *C) {
s.url = "http://127.0.0.1:" + s.port
go runHTTPServer(s, c)
-
- time.Sleep(time.Second)
}
func (s *UpdateSuite) TearDownSuite(c *C) {
@@ -166,7 +164,7 @@ func runHTTPServer(s *UpdateSuite, c *C) {
server.Handler.(*http.ServeMux).HandleFunc("/github/repos/essentialkaos/limited/releases/latest", githubLimitedHandler)
server.Handler.(*http.ServeMux).HandleFunc("/github/repos/essentialkaos/garbage/releases/latest", githubWrongFormatHandler)
- server.Handler.(*http.ServeMux).HandleFunc("/gitlab/projects/essentialkaos/project/releases/permalink/latest", gitlabInfoHandler)
+ server.Handler.(*http.ServeMux).HandleFunc("/gitlab/projects/essentialkaos%2Fproject/releases/permalink/latest", gitlabInfoHandler)
server.Handler.(*http.ServeMux).HandleFunc("/gitlab/projects/essentialkaos/unknown/releases/permalink/latest", gitlabNotFoundHandler)
server.Handler.(*http.ServeMux).HandleFunc("/gitlab/projects/essentialkaos/limited/releases/permalink/latest", gitlabLimitedHandler)
server.Handler.(*http.ServeMux).HandleFunc("/gitlab/projects/essentialkaos/garbage/releases/permalink/latest", gitlabWrongFormatHandler)
diff --git a/version.go b/version.go
index 9efe2a5a..14137bf4 100644
--- a/version.go
+++ b/version.go
@@ -8,4 +8,4 @@ package ek
// ////////////////////////////////////////////////////////////////////////////////// //
// VERSION is current ek package version
-const VERSION = "13.5.1"
+const VERSION = "13.6.0"