From 9517a073e64a88199aa32f253f829f57352fe754 Mon Sep 17 00:00:00 2001 From: Anton Novojilov Date: Thu, 19 Sep 2024 16:21:29 +0300 Subject: [PATCH 01/17] [req] Code refactoring --- req/req.go | 91 ++++++++++++++++++++++++------------------------- req/req_test.go | 12 +++---- 2 files changed, 50 insertions(+), 53 deletions(-) diff --git a/req/req.go b/req/req.go index 3cc49b6a..e3d0f885 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" // ////////////////////////////////////////////////////////////////////////////////// // @@ -285,51 +285,6 @@ func SetRequestTimeout(timeout float64) { // ////////////////////////////////////////////////////////////////////////////////// // -// Init initializes engine -func (e *Engine) Init() *Engine { - if e.initialized { - return e - } - - if e.Dialer == nil { - e.Dialer = &net.Dialer{} - } - - if e.Transport == nil { - e.Transport = &http.Transport{ - DialContext: e.Dialer.DialContext, - Proxy: http.ProxyFromEnvironment, - } - } else { - e.Transport.DialContext = e.Dialer.DialContext - } - - if e.Client == nil { - e.Client = &http.Client{ - Transport: e.Transport, - } - } - - if e.dialTimeout > 0 { - e.SetDialTimeout(e.dialTimeout) - } - - if e.requestTimeout > 0 { - e.SetRequestTimeout(e.requestTimeout) - } - - if e.UserAgent == "" { - e.SetUserAgent(USER_AGENT, "10") - } - - e.dialTimeout = 0 - e.requestTimeout = 0 - - e.initialized = true - - return e -} - // Do sends request and process response func (e *Engine) Do(r Request) (*Response, error) { return e.doRequest(r, "") @@ -556,6 +511,48 @@ func (q Query) Encode() string { // ////////////////////////////////////////////////////////////////////////////////// // +// init is engine lazy initialization +func (e *Engine) init() { + if e == nil || e.initialized { + return + } + + if e.Dialer == nil { + e.Dialer = &net.Dialer{} + } + + if e.Transport == nil { + e.Transport = &http.Transport{ + DialContext: e.Dialer.DialContext, + Proxy: http.ProxyFromEnvironment, + } + } else { + e.Transport.DialContext = e.Dialer.DialContext + } + + if e.Client == nil { + e.Client = &http.Client{ + Transport: e.Transport, + } + } + + if e.dialTimeout > 0 { + e.SetDialTimeout(e.dialTimeout) + } + + if e.requestTimeout > 0 { + e.SetRequestTimeout(e.requestTimeout) + } + + if e.UserAgent == "" { + e.SetUserAgent(USER_AGENT, "13") + } + + e.dialTimeout = 0 + e.requestTimeout = 0 + e.initialized = true +} + // This method has a lot of actions to prepare request for executing, so it is ok to // have so many conditions // codebeat:disable[CYCLO,ABC] @@ -563,7 +560,7 @@ func (q Query) Encode() string { func (e *Engine) doRequest(r Request, method string) (*Response, error) { // Lazy engine initialization if e != nil && !e.initialized { - e.Init() + e.init() } err := checkEngine(e) diff --git a/req/req_test.go b/req/req_test.go index b361c104..695ce8d3 100644 --- a/req/req_test.go +++ b/req/req_test.go @@ -511,10 +511,10 @@ func (s *ReqSuite) TestEngineInit(c *C) { var eng *Engine eng = &Engine{} - eng.Init() + eng.init() eng = &Engine{Transport: &http.Transport{}} - eng.Init() + eng.init() } func (s *ReqSuite) TestEngineErrors(c *C) { @@ -527,7 +527,7 @@ func (s *ReqSuite) TestEngineErrors(c *C) { c.Assert(resp, IsNil) eng = &Engine{} - eng.Init() + eng.init() eng.Dialer = nil @@ -538,7 +538,7 @@ func (s *ReqSuite) TestEngineErrors(c *C) { c.Assert(resp, IsNil) eng = &Engine{} - eng.Init() + eng.init() eng.Transport = nil resp, err = eng.Do(Request{URL: "https://essentialkaos.com"}) @@ -548,8 +548,8 @@ func (s *ReqSuite) TestEngineErrors(c *C) { c.Assert(resp, IsNil) eng = &Engine{} - eng.Init() - eng.Init() + eng.init() + eng.init() eng.Client = nil resp, err = eng.Do(Request{URL: "https://essentialkaos.com"}) From 1ade71dc00724f31979273b4f813e9987519c923 Mon Sep 17 00:00:00 2001 From: Anton Novojilov Date: Thu, 19 Sep 2024 16:36:08 +0300 Subject: [PATCH 02/17] [req] Code refactoring --- req/req.go | 91 +++++++++++++++++++++++++------------------------ req/req_test.go | 12 +++---- 2 files changed, 53 insertions(+), 50 deletions(-) diff --git a/req/req.go b/req/req.go index e3d0f885..03035216 100644 --- a/req/req.go +++ b/req/req.go @@ -177,7 +177,7 @@ const ( ) // USER_AGENT is default user agent -const USER_AGENT = "EK-GO" +const USER_AGENT = "GO-EK" // ////////////////////////////////////////////////////////////////////////////////// // @@ -285,6 +285,51 @@ func SetRequestTimeout(timeout float64) { // ////////////////////////////////////////////////////////////////////////////////// // +// Init initializes engine +func (e *Engine) Init() *Engine { + if e.initialized { + return e + } + + if e.Dialer == nil { + e.Dialer = &net.Dialer{} + } + + if e.Transport == nil { + e.Transport = &http.Transport{ + DialContext: e.Dialer.DialContext, + Proxy: http.ProxyFromEnvironment, + } + } else { + e.Transport.DialContext = e.Dialer.DialContext + } + + if e.Client == nil { + e.Client = &http.Client{ + Transport: e.Transport, + } + } + + if e.dialTimeout > 0 { + e.SetDialTimeout(e.dialTimeout) + } + + if e.requestTimeout > 0 { + e.SetRequestTimeout(e.requestTimeout) + } + + if e.UserAgent == "" { + e.SetUserAgent(USER_AGENT, "13") + } + + e.dialTimeout = 0 + e.requestTimeout = 0 + + e.initialized = true + + return e +} + // Do sends request and process response func (e *Engine) Do(r Request) (*Response, error) { return e.doRequest(r, "") @@ -511,48 +556,6 @@ func (q Query) Encode() string { // ////////////////////////////////////////////////////////////////////////////////// // -// init is engine lazy initialization -func (e *Engine) init() { - if e == nil || e.initialized { - return - } - - if e.Dialer == nil { - e.Dialer = &net.Dialer{} - } - - if e.Transport == nil { - e.Transport = &http.Transport{ - DialContext: e.Dialer.DialContext, - Proxy: http.ProxyFromEnvironment, - } - } else { - e.Transport.DialContext = e.Dialer.DialContext - } - - if e.Client == nil { - e.Client = &http.Client{ - Transport: e.Transport, - } - } - - if e.dialTimeout > 0 { - e.SetDialTimeout(e.dialTimeout) - } - - if e.requestTimeout > 0 { - e.SetRequestTimeout(e.requestTimeout) - } - - if e.UserAgent == "" { - e.SetUserAgent(USER_AGENT, "13") - } - - e.dialTimeout = 0 - e.requestTimeout = 0 - e.initialized = true -} - // This method has a lot of actions to prepare request for executing, so it is ok to // have so many conditions // codebeat:disable[CYCLO,ABC] @@ -560,7 +563,7 @@ func (e *Engine) init() { func (e *Engine) doRequest(r Request, method string) (*Response, error) { // Lazy engine initialization if e != nil && !e.initialized { - e.init() + e.Init() } err := checkEngine(e) diff --git a/req/req_test.go b/req/req_test.go index 695ce8d3..b361c104 100644 --- a/req/req_test.go +++ b/req/req_test.go @@ -511,10 +511,10 @@ func (s *ReqSuite) TestEngineInit(c *C) { var eng *Engine eng = &Engine{} - eng.init() + eng.Init() eng = &Engine{Transport: &http.Transport{}} - eng.init() + eng.Init() } func (s *ReqSuite) TestEngineErrors(c *C) { @@ -527,7 +527,7 @@ func (s *ReqSuite) TestEngineErrors(c *C) { c.Assert(resp, IsNil) eng = &Engine{} - eng.init() + eng.Init() eng.Dialer = nil @@ -538,7 +538,7 @@ func (s *ReqSuite) TestEngineErrors(c *C) { c.Assert(resp, IsNil) eng = &Engine{} - eng.init() + eng.Init() eng.Transport = nil resp, err = eng.Do(Request{URL: "https://essentialkaos.com"}) @@ -548,8 +548,8 @@ func (s *ReqSuite) TestEngineErrors(c *C) { c.Assert(resp, IsNil) eng = &Engine{} - eng.init() - eng.init() + eng.Init() + eng.Init() eng.Client = nil resp, err = eng.Do(Request{URL: "https://essentialkaos.com"}) From ccb84ee46dcd786bf936de103497fcde978975c1 Mon Sep 17 00:00:00 2001 From: Anton Novojilov Date: Tue, 24 Sep 2024 00:54:26 +0300 Subject: [PATCH 03/17] [setup] Add package to install/uninstall application as a service --- .scripts/packages.list | 1 + CHANGELOG.md | 4 + setup/setup.go | 337 +++++++++++++++++++++++++++++++++++++++++ setup/setup_stub.go | 41 +++++ setup/setup_test.go | 125 +++++++++++++++ version.go | 2 +- 6 files changed, 509 insertions(+), 1 deletion(-) create mode 100644 setup/setup.go create mode 100644 setup/setup_stub.go create mode 100644 setup/setup_test.go 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..e64309af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ ## Changelog +### [13.6.0](https://kaos.sh/ek/13.6.0) + +- `[setup]` Added package to install/uninstall application as a service + ### [13.5.1](https://kaos.sh/ek/13.5.1) - `[mathutil]` Added method `FromPerc` diff --git a/setup/setup.go b/setup/setup.go new file mode 100644 index 00000000..edc33ace --- /dev/null +++ b/setup/setup.go @@ -0,0 +1,337 @@ +//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 +} + +// ////////////////////////////////////////////////////////////////////////////////// // + +// 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" + +// unitComment is comment for service unit +var unitComment = "# Unit generated by ek.go/setup" + +// ////////////////////////////////////////////////////////////////////////////////// // + +// Install installs or reinstalls application on the system +func (app App) Install() error { + bin := getBinaryInfo() + err := bin.checkForInstall() + + 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 := bin.checkForUninstall() + + 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()) && + hash.FileHash(b.File) == hash.FileHash(b.BinInstallPath()) +} + +// IsServiceInstalled returns true if service unit already installed +func (b *binaryInfo) IsServiceInstalled() bool { + return fsutil.IsExist(b.ServiceUnitPath()) +} + +// checkForInstall checks if current binary can be installed +func (b *binaryInfo) checkForInstall() error { + 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 b.IsServiceInstalled() { + isGenerated, err := isGeneratedUnit(b.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 current binary can be uninstalled +func (b *binaryInfo) checkForUninstall() error { + if b.IsBinInstalled() { + return fmt.Errorf("Binary is not installed") + } + + if b.File != b.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 b.IsServiceInstalled() { + isGenerated, err := isGeneratedUnit(b.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 +} + +// ////////////////////////////////////////////////////////////////////////////////// // + +// getBinary returns basic info about current binary +func getBinaryInfo() binaryInfo { + binFile := path.Clean(os.Args[0]) + binName := path.Base(binFile) + + return binaryInfo{File: binFile, Name: binName} +} + +// installFiles installs binary and systemd unit file +func installFiles(app App, bin binaryInfo) error { + 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) + } + + 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(unitComment + "\n\n") + 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", bin.Name, strings.Join(app.Options, " "), + )) + } else { + buf.WriteString(fmt.Sprintf("ExecStart=%s\n", 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..f16d9f5d --- /dev/null +++ b/setup/setup_stub.go @@ -0,0 +1,41 @@ +//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 // +// // +// ////////////////////////////////////////////////////////////////////////////////// // + +// ❗ 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 +} + +// ////////////////////////////////////////////////////////////////////////////////// // + +// ❗ 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..413d04b6 --- /dev/null +++ b/setup/setup_test.go @@ -0,0 +1,125 @@ +package setup + +// ////////////////////////////////////////////////////////////////////////////////// // +// // +// Copyright (c) 2024 ESSENTIAL KAOS // +// Apache License, Version 2.0 // +// // +// ////////////////////////////////////////////////////////////////////////////////// // + +import ( + "testing" + + . "github.com/essentialkaos/check" +) + +// ////////////////////////////////////////////////////////////////////////////////// // + +func Test(t *testing.T) { TestingT(t) } + +type SetupSuite struct { + App App + Bin binaryInfo +} + +// ////////////////////////////////////////////////////////////////////////////////// // + +var _ = Suite(&SetupSuite{}) + +// ////////////////////////////////////////////////////////////////////////////////// // + +func (s *SetupSuite) SetUpSuite(c *C) { + s.App = 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, + } + + s.Bin = binaryInfo{File: "/usr/bin/echo", Name: "test1"} +} + +func (s *SetupSuite) TestInstall(c *C) { + serviceDir = c.MkDir() + binaryDir = c.MkDir() + logDir = c.MkDir() + + err := installFiles(s.App, s.Bin) + c.Assert(err, IsNil) + + isGen, _ := isGeneratedUnit(s.Bin.ServiceUnitPath()) + c.Assert(isGen, Equals, true) + _, err = isGeneratedUnit("/_unknown_") + c.Assert(err, NotNil) + + app := s.App + app.Options = nil + + err = installFiles(app, s.Bin) + c.Assert(err, IsNil) + + c.Assert(s.Bin.IsBinInstalled(), Equals, true) + c.Assert(s.Bin.IsServiceInstalled(), Equals, true) +} + +func (s *SetupSuite) TestInstallErrors(c *C) { + serviceDir = c.MkDir() + binaryDir = c.MkDir() + + logDir = "/_unknown_" + err := installFiles(s.App, s.Bin) + c.Assert(err, NotNil) + + serviceDir = "/_unknown_" + err = installFiles(s.App, s.Bin) + c.Assert(err, NotNil) + + binaryDir = "/_unknown_" + err = installFiles(s.App, s.Bin) + c.Assert(err, NotNil) +} + +func (s *SetupSuite) TestUninstall(c *C) { + serviceDir = c.MkDir() + binaryDir = c.MkDir() + logDir = c.MkDir() + + err := installFiles(s.App, s.Bin) + c.Assert(err, IsNil) + + err = uninstallFiles(s.App, s.Bin, true) + c.Assert(err, IsNil) +} + +func (s *SetupSuite) TestUninstallErrors(c *C) { + serviceDir = c.MkDir() + binaryDir = c.MkDir() + logDir = c.MkDir() + + err := installFiles(s.App, s.Bin) + c.Assert(err, IsNil) + + binaryDir = "/_unknown_" + err = uninstallFiles(s.App, s.Bin, true) + c.Assert(err, NotNil) + + serviceDir = "/_unknown_" + err = uninstallFiles(s.App, s.Bin, true) + c.Assert(err, NotNil) +} + +func (s *SetupSuite) TestAux(c *C) { + bin := getBinaryInfo() + + c.Assert(bin.File, Not(Equals), "") + c.Assert(bin.Name, Not(Equals), "") + + c.Assert(s.Bin.checkForInstall(), NotNil) + c.Assert(s.Bin.checkForUninstall(), NotNil) +} 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" From f123e256954ab4a40448b1dea3443213564d084f Mon Sep 17 00:00:00 2001 From: Anton Novojilov Date: Tue, 24 Sep 2024 00:55:21 +0300 Subject: [PATCH 04/17] [req] Change default user-agent --- req/req.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/req/req.go b/req/req.go index 03035216..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" +const USER_AGENT = "ek.go" // ////////////////////////////////////////////////////////////////////////////////// // From 7523cc7c415aec4cfbcb567352cae0e525d33c4c Mon Sep 17 00:00:00 2001 From: Anton Novojilov Date: Wed, 25 Sep 2024 01:52:29 +0300 Subject: [PATCH 05/17] Update minimal required version in go.mod --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index e2271e62..8919d379 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/essentialkaos/ek/v13 -go 1.21 +go 1.21.13 require ( github.com/essentialkaos/check v1.4.0 From 5c8b49e4d7be0f55923459c33d9aafa6016860b8 Mon Sep 17 00:00:00 2001 From: Anton Novojilov Date: Thu, 26 Sep 2024 15:56:30 +0300 Subject: [PATCH 06/17] [support/kernel] Add simple globs support for parameter names --- CHANGELOG.md | 1 + support/kernel/kernel.go | 13 +++++++++++-- support/support.go | 4 ++-- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e64309af..9a30f991 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ### [13.6.0](https://kaos.sh/ek/13.6.0) - `[setup]` Added package to install/uninstall application as a service +- `[support/kernel]` Added simple globs support for parameter names ### [13.5.1](https://kaos.sh/ek/13.5.1) 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) From 5cf1ad125e1540e507a1a463eeda4363f845ffb5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 27 Sep 2024 03:44:36 +0000 Subject: [PATCH 07/17] Bump github.com/essentialkaos/go-linenoise/v3 in the all group Bumps the all group with 1 update: [github.com/essentialkaos/go-linenoise/v3](https://github.com/essentialkaos/go-linenoise). Updates `github.com/essentialkaos/go-linenoise/v3` from 3.6.0 to 3.6.1 - [Release notes](https://github.com/essentialkaos/go-linenoise/releases) - [Commits](https://github.com/essentialkaos/go-linenoise/compare/v3.6.0...v3.6.1) --- updated-dependencies: - dependency-name: github.com/essentialkaos/go-linenoise/v3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: all ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 8919d379..34ac97c3 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.21.13 require ( github.com/essentialkaos/check v1.4.0 github.com/essentialkaos/depsy v1.3.0 - github.com/essentialkaos/go-linenoise/v3 v3.6.0 + github.com/essentialkaos/go-linenoise/v3 v3.6.1 golang.org/x/crypto v0.27.0 golang.org/x/sys v0.25.0 ) diff --git a/go.sum b/go.sum index cc6de943..3036dfa7 100644 --- a/go.sum +++ b/go.sum @@ -3,8 +3,8 @@ github.com/essentialkaos/check v1.4.0 h1:kWdFxu9odCxUqo1NNFNJmguGrDHgwi3A8daXX1n 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/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= From 68fe4c2655f001c464065d5ddbe2329921cfc2f4 Mon Sep 17 00:00:00 2001 From: Anton Novojilov Date: Mon, 30 Sep 2024 01:59:06 +0300 Subject: [PATCH 08/17] [setup] Add configuration setup --- setup/setup.go | 139 ++++++++++++++++++++++++++++++++++++-------- setup/setup_stub.go | 13 +++++ setup/setup_test.go | 122 ++++++++++++++++++++++++++------------ 3 files changed, 214 insertions(+), 60 deletions(-) diff --git a/setup/setup.go b/setup/setup.go index edc33ace..d399bb45 100644 --- a/setup/setup.go +++ b/setup/setup.go @@ -42,6 +42,17 @@ type App struct { 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 } // ////////////////////////////////////////////////////////////////////////////////// // @@ -63,6 +74,9 @@ 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" @@ -71,7 +85,7 @@ var unitComment = "# Unit generated by ek.go/setup" // Install installs or reinstalls application on the system func (app App) Install() error { bin := getBinaryInfo() - err := bin.checkForInstall() + err := checkForInstall(app, bin) if err != nil { return err @@ -91,7 +105,7 @@ func (app App) Install() error { // Uninstall uninstall unistalls application from the system func (app App) Uninstall(full bool) error { bin := getBinaryInfo() - err := bin.checkForUninstall() + err := checkForUninstall(app, bin) if err != nil { return err @@ -136,8 +150,26 @@ func (b *binaryInfo) IsServiceInstalled() bool { return fsutil.IsExist(b.ServiceUnitPath()) } -// checkForInstall checks if current binary can be installed -func (b *binaryInfo) checkForInstall() error { +// ////////////////////////////////////////////////////////////////////////////////// // + +// getBinary returns basic info about current binary +func getBinaryInfo() binaryInfo { + binFile := path.Clean(os.Args[0]) + 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 { @@ -148,8 +180,8 @@ func (b *binaryInfo) checkForInstall() error { return fmt.Errorf("You must have superuser (root) privileges to install app as a service") } - if b.IsServiceInstalled() { - isGenerated, err := isGeneratedUnit(b.ServiceUnitPath()) + if bin.IsServiceInstalled() { + isGenerated, err := isGeneratedUnit(bin.ServiceUnitPath()) if err != nil { return fmt.Errorf("Can't check systemd unit: %w", err) @@ -163,13 +195,21 @@ func (b *binaryInfo) checkForInstall() error { return nil } -// checkForUninstall checks if current binary can be uninstalled -func (b *binaryInfo) checkForUninstall() error { - if b.IsBinInstalled() { +// 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") } - if b.File != b.BinInstallPath() { + if bin.File != bin.BinInstallPath() { return fmt.Errorf("You must use installed binary to uninstall service from the system") } @@ -183,8 +223,8 @@ func (b *binaryInfo) checkForUninstall() error { return fmt.Errorf("You must have superuser (root) privileges to uninstall this app") } - if b.IsServiceInstalled() { - isGenerated, err := isGeneratedUnit(b.ServiceUnitPath()) + if bin.IsServiceInstalled() { + isGenerated, err := isGeneratedUnit(bin.ServiceUnitPath()) if err != nil { return fmt.Errorf("Can't check systemd unit: %w", err) @@ -198,18 +238,14 @@ func (b *binaryInfo) checkForUninstall() error { return nil } -// ////////////////////////////////////////////////////////////////////////////////// // - -// getBinary returns basic info about current binary -func getBinaryInfo() binaryInfo { - binFile := path.Clean(os.Args[0]) - binName := path.Base(binFile) - - return binaryInfo{File: binFile, Name: binName} -} - // 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) @@ -218,7 +254,7 @@ func installFiles(app App, bin binaryInfo) error { } } - err := createServiceFile(app, bin) + err = createServiceFile(app, bin) if err != nil { return fmt.Errorf("Can't install systemd service: %w", err) @@ -253,6 +289,63 @@ func uninstallFiles(app App, bin binaryInfo, full bool) error { 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 } diff --git a/setup/setup_stub.go b/setup/setup_stub.go index f16d9f5d..675bcb0e 100644 --- a/setup/setup_stub.go +++ b/setup/setup_stub.go @@ -12,6 +12,10 @@ package setup // // // ////////////////////////////////////////////////////////////////////////////////// // +import "os" + +// ////////////////////////////////////////////////////////////////////////////////// // + // ❗ App contains basic application configuration type App struct { Name string // Application name @@ -28,6 +32,15 @@ type App struct { WithoutPrivateTemp bool // Disable private temp } +// ❗ 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 diff --git a/setup/setup_test.go b/setup/setup_test.go index 413d04b6..94e2a0dc 100644 --- a/setup/setup_test.go +++ b/setup/setup_test.go @@ -8,6 +8,7 @@ package setup // ////////////////////////////////////////////////////////////////////////////////// // import ( + "errors" "testing" . "github.com/essentialkaos/check" @@ -17,10 +18,7 @@ import ( func Test(t *testing.T) { TestingT(t) } -type SetupSuite struct { - App App - Bin binaryInfo -} +type SetupSuite struct{} // ////////////////////////////////////////////////////////////////////////////////// // @@ -28,60 +26,55 @@ var _ = Suite(&SetupSuite{}) // ////////////////////////////////////////////////////////////////////////////////// // -func (s *SetupSuite) SetUpSuite(c *C) { - s.App = 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, - } - - s.Bin = binaryInfo{File: "/usr/bin/echo", Name: "test1"} -} - func (s *SetupSuite) TestInstall(c *C) { serviceDir = c.MkDir() binaryDir = c.MkDir() logDir = c.MkDir() + configDir = c.MkDir() - err := installFiles(s.App, s.Bin) + app, bin := s.generateApp(), s.generateBin() + + err := installFiles(app, bin) c.Assert(err, IsNil) - isGen, _ := isGeneratedUnit(s.Bin.ServiceUnitPath()) + isGen, _ := isGeneratedUnit(bin.ServiceUnitPath()) c.Assert(isGen, Equals, true) _, err = isGeneratedUnit("/_unknown_") c.Assert(err, NotNil) - app := s.App + app = s.generateApp() app.Options = nil - err = installFiles(app, s.Bin) + err = installFiles(app, bin) c.Assert(err, IsNil) - c.Assert(s.Bin.IsBinInstalled(), Equals, true) - c.Assert(s.Bin.IsServiceInstalled(), Equals, true) + 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(s.App, s.Bin) + err := installFiles(app, bin) c.Assert(err, NotNil) serviceDir = "/_unknown_" - err = installFiles(s.App, s.Bin) + err = installFiles(app, bin) c.Assert(err, NotNil) binaryDir = "/_unknown_" - err = installFiles(s.App, s.Bin) + 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) } @@ -89,12 +82,27 @@ 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(s.App, s.Bin) + err = installFiles(app, bin) c.Assert(err, IsNil) - err = uninstallFiles(s.App, s.Bin, true) + err = uninstallFiles(app, bin, true) c.Assert(err, IsNil) + + c.Assert(uninstallConfigurationFiles(app), IsNil) } func (s *SetupSuite) TestUninstallErrors(c *C) { @@ -102,24 +110,64 @@ func (s *SetupSuite) TestUninstallErrors(c *C) { binaryDir = c.MkDir() logDir = c.MkDir() - err := installFiles(s.App, s.Bin) + app, bin := s.generateApp(), s.generateBin() + + err := installFiles(app, bin) c.Assert(err, IsNil) binaryDir = "/_unknown_" - err = uninstallFiles(s.App, s.Bin, true) + err = uninstallFiles(app, bin, true) c.Assert(err, NotNil) serviceDir = "/_unknown_" - err = uninstallFiles(s.App, s.Bin, true) + 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(s.Bin.checkForInstall(), NotNil) - c.Assert(s.Bin.checkForUninstall(), NotNil) + 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"} } From 44ac79751c4915fcd92cd4842929d0f0d807eb8d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 3 Oct 2024 03:29:22 +0000 Subject: [PATCH 09/17] Bump github.com/essentialkaos/depsy from 1.3.0 to 1.3.1 in the all group Bumps the all group with 1 update: [github.com/essentialkaos/depsy](https://github.com/essentialkaos/depsy). Updates `github.com/essentialkaos/depsy` from 1.3.0 to 1.3.1 - [Release notes](https://github.com/essentialkaos/depsy/releases) - [Commits](https://github.com/essentialkaos/depsy/compare/v1.3.0...v1.3.1) --- updated-dependencies: - dependency-name: github.com/essentialkaos/depsy dependency-type: direct:production update-type: version-update:semver-patch dependency-group: all ... Signed-off-by: dependabot[bot] --- go.mod | 5 +++-- go.sum | 8 ++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 34ac97c3..9da50b77 100644 --- a/go.mod +++ b/go.mod @@ -1,10 +1,11 @@ module github.com/essentialkaos/ek/v13 go 1.21.13 +toolchain go1.22.8 require ( github.com/essentialkaos/check v1.4.0 - github.com/essentialkaos/depsy v1.3.0 + github.com/essentialkaos/depsy v1.3.1 github.com/essentialkaos/go-linenoise/v3 v3.6.1 golang.org/x/crypto v0.27.0 golang.org/x/sys v0.25.0 @@ -13,5 +14,5 @@ require ( 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 3036dfa7..8c6f919f 100644 --- a/go.sum +++ b/go.sum @@ -1,8 +1,8 @@ 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/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= @@ -11,8 +11,8 @@ 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= +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.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= From eff1486e65426da9bad2ed716ac6fbde76e79132 Mon Sep 17 00:00:00 2001 From: Anton Novojilov Date: Sat, 5 Oct 2024 00:13:19 +0300 Subject: [PATCH 10/17] [update] Minor improvements --- usage/update/github.go | 1 - usage/update/gitlab.go | 1 - usage/update/update_test.go | 2 -- 3 files changed, 4 deletions(-) diff --git a/usage/update/github.go b/usage/update/github.go index 3748a4e8..6b3cac1b 100644 --- a/usage/update/github.go +++ b/usage/update/github.go @@ -50,7 +50,6 @@ 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{ URL: githubAPI + "/repos/" + repository + "/releases/latest", diff --git a/usage/update/gitlab.go b/usage/update/gitlab.go index 47018730..c6475b99 100644 --- a/usage/update/gitlab.go +++ b/usage/update/gitlab.go @@ -50,7 +50,6 @@ 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") diff --git a/usage/update/update_test.go b/usage/update/update_test.go index 30c73cd4..8860f5b6 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) { From 2ee7e8612f9a5c2865ec687b7d2d418598d144ea Mon Sep 17 00:00:00 2001 From: Anton Novojilov Date: Sat, 5 Oct 2024 00:15:51 +0300 Subject: [PATCH 11/17] Dependencies update --- README.md | 2 +- go.mod | 7 +++---- go.sum | 8 ++++---- 3 files changed, 8 insertions(+), 9 deletions(-) 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 9da50b77..6a3c76dd 100644 --- a/go.mod +++ b/go.mod @@ -1,14 +1,13 @@ module github.com/essentialkaos/ek/v13 -go 1.21.13 -toolchain go1.22.8 +go 1.22.8 require ( github.com/essentialkaos/check v1.4.0 github.com/essentialkaos/depsy v1.3.1 github.com/essentialkaos/go-linenoise/v3 v3.6.1 - golang.org/x/crypto v0.27.0 - golang.org/x/sys v0.25.0 + golang.org/x/crypto v0.28.0 + golang.org/x/sys v0.26.0 ) require ( diff --git a/go.sum b/go.sum index 8c6f919f..ab452beb 100644 --- a/go.sum +++ b/go.sum @@ -13,7 +13,7 @@ github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsK github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= 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.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= +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= From 7a583ae5e81b6ef4fae867ed0c6d46a27fec4114 Mon Sep 17 00:00:00 2001 From: Anton Novojilov Date: Sat, 5 Oct 2024 00:41:14 +0300 Subject: [PATCH 12/17] [update] Tests fix --- usage/update/update_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/usage/update/update_test.go b/usage/update/update_test.go index 8860f5b6..fc17fcad 100644 --- a/usage/update/update_test.go +++ b/usage/update/update_test.go @@ -164,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) From 3e5f31f35fd2ae331df019d1288ce03a168f057d Mon Sep 17 00:00:00 2001 From: Anton Novojilov Date: Sat, 5 Oct 2024 00:42:26 +0300 Subject: [PATCH 13/17] [usage/update] Code refactoring --- usage/update/github.go | 8 ++++---- usage/update/gitlab.go | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/usage/update/github.go b/usage/update/github.go index 6b3cac1b..89d4a183 100644 --- a/usage/update/github.go +++ b/usage/update/github.go @@ -51,22 +51,22 @@ func getLatestGitHubRelease(app, version, repository string) *githubRelease { engine.SetDialTimeout(3) engine.SetRequestTimeout(3) - 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 c6475b99..c83784d7 100644 --- a/usage/update/gitlab.go +++ b/usage/update/gitlab.go @@ -55,21 +55,21 @@ func getLatestGitLabRelease(app, version, repository string) *gitlabRelease { 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 From 1ffbf683c9ccd1ac7e85d9f62826e5c16a58381f Mon Sep 17 00:00:00 2001 From: Anton Novojilov Date: Tue, 8 Oct 2024 02:20:43 +0300 Subject: [PATCH 14/17] [setup] Fix stubs --- setup/setup_stub.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/setup/setup_stub.go b/setup/setup_stub.go index 675bcb0e..c0122d77 100644 --- a/setup/setup_stub.go +++ b/setup/setup_stub.go @@ -30,6 +30,8 @@ type App struct { WithLog bool // Create directory for logs WithoutPrivateTemp bool // Disable private temp + + Configs []Config // Configuration files } // ❗ Config contains configuration file data From 5bac16ca50b25891e85abe38b7f6b14e9d7304f7 Mon Sep 17 00:00:00 2001 From: Anton Novojilov Date: Tue, 8 Oct 2024 14:19:07 +0300 Subject: [PATCH 15/17] [setup] Fixes --- setup/setup.go | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/setup/setup.go b/setup/setup.go index d399bb45..f7617d87 100644 --- a/setup/setup.go +++ b/setup/setup.go @@ -154,7 +154,8 @@ func (b *binaryInfo) IsServiceInstalled() bool { // getBinary returns basic info about current binary func getBinaryInfo() binaryInfo { - binFile := path.Clean(os.Args[0]) + bin, _ := os.Executable() + binFile := path.Clean(bin) binName := path.Base(binFile) return binaryInfo{File: binFile, Name: binName} @@ -205,8 +206,8 @@ func checkForUninstall(app App, bin binaryInfo) error { } } - if bin.IsBinInstalled() { - return fmt.Errorf("Binary is not installed") + if !bin.IsBinInstalled() { + return fmt.Errorf("Binary is not installed (new binary?)") } if bin.File != bin.BinInstallPath() { @@ -385,10 +386,10 @@ func generateServiceUnit(app App, bin binaryInfo) []byte { if len(app.Options) > 0 { buf.WriteString(fmt.Sprintf( - "ExecStart=%s %s\n", bin.Name, strings.Join(app.Options, " "), + "ExecStart=%s %s\n", path.Join(binaryDir, bin.Name), strings.Join(app.Options, " "), )) } else { - buf.WriteString(fmt.Sprintf("ExecStart=%s\n", bin.Name)) + buf.WriteString(fmt.Sprintf("ExecStart=%s\n", path.Join(binaryDir, bin.Name))) } if app.ReloadSignal != "" { From 8e48de1f9a71fe9d44ffe58499745dbc43bbe7ce Mon Sep 17 00:00:00 2001 From: Anton Novojilov Date: Tue, 8 Oct 2024 14:51:56 +0300 Subject: [PATCH 16/17] [setup] Write binary checksum to service unit --- setup/setup.go | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/setup/setup.go b/setup/setup.go index f7617d87..24706394 100644 --- a/setup/setup.go +++ b/setup/setup.go @@ -80,6 +80,9 @@ 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 @@ -142,7 +145,7 @@ func (b *binaryInfo) ServiceUnitPath() string { // IsBinInstalled returns true if current binary already installed func (b *binaryInfo) IsBinInstalled() bool { return fsutil.IsExist(b.BinInstallPath()) && - hash.FileHash(b.File) == hash.FileHash(b.BinInstallPath()) + b.Checksum() == hash.FileHash(b.BinInstallPath()) } // IsServiceInstalled returns true if service unit already installed @@ -150,6 +153,17 @@ 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 @@ -367,7 +381,12 @@ func createServiceFile(app App, bin binaryInfo) error { func generateServiceUnit(app App, bin binaryInfo) []byte { var buf bytes.Buffer - buf.WriteString(unitComment + "\n\n") + 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))) From 3a42871750abb345ed02bc7cdc8e4837d02b8406 Mon Sep 17 00:00:00 2001 From: Anton Novojilov Date: Wed, 9 Oct 2024 12:32:50 +0300 Subject: [PATCH 17/17] [support/deps] Improve collecting and filtering dependencies info --- CHANGELOG.md | 1 + support/deps/deps.go | 53 ++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 50 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9a30f991..9bc89d7e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ### [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) 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, "" +}