diff --git a/lib/autoupdate/agent/config.go b/lib/autoupdate/agent/config.go
index 32c7b90a52dfa..34f8dca1a676a 100644
--- a/lib/autoupdate/agent/config.go
+++ b/lib/autoupdate/agent/config.go
@@ -164,6 +164,7 @@ func NewRevisionFromDir(dir string) (Revision, error) {
}
// Dir returns the directory path name of a Revision.
+// These are unambiguous for semver and may be parsed with NewRevisionFromDir.
func (r Revision) Dir() string {
// Do not change the order of these statements.
// Otherwise, installed versions will no longer match update.yaml.
@@ -178,6 +179,7 @@ func (r Revision) Dir() string {
}
// String returns a human-readable description of a Teleport revision.
+// These are semver-ambiguous and should not be parsed.
func (r Revision) String() string {
if flags := r.Flags.Strings(); len(flags) > 0 {
return fmt.Sprintf("%s+%s", r.Version, strings.Join(flags, "+"))
diff --git a/lib/autoupdate/agent/installer.go b/lib/autoupdate/agent/installer.go
index ae25003e7f639..88820ba911adb 100644
--- a/lib/autoupdate/agent/installer.go
+++ b/lib/autoupdate/agent/installer.go
@@ -36,7 +36,6 @@ import (
"github.com/gravitational/trace"
- "github.com/gravitational/teleport"
"github.com/gravitational/teleport/lib/autoupdate"
"github.com/gravitational/teleport/lib/utils"
)
@@ -61,6 +60,31 @@ const (
serviceName = "teleport.service"
)
+// ServiceFile represents a systemd service file for a Teleport binary.
+//
+// ExampleName and ExampleFunc are used to parse an example configuration
+// file and copy it into the service directory. This mechanism of service
+// file generation is only provided to support downgrading to older versions
+// of the updater that do not install the service during the setup phase using
+// logic from lib/config.
+type ServiceFile struct {
+ // Path is the full path to the linked service file.
+ Path string
+ // Binary is the corresponding linked binary name.
+ Binary string
+ // ExampleName is the name of the example service file.
+ // Deprecated.
+ ExampleName string
+ // ExampleFunc can be used to create the service during linking from
+ // an archived example, instead of creating it during the setup phase.
+ // Deprecated.
+ ExampleFunc ExampleFunc
+}
+
+// A ExampleFunc generates a systemd service by replacing an example file.
+// Deprecated.
+type ExampleFunc func(cfg []byte, path string, flags autoupdate.InstallFlags) []byte
+
// LocalInstaller manages the creation and removal of installations
// of Teleport.
// SetRequiredUmask must be called before any methods are executed.
@@ -68,11 +92,11 @@ type LocalInstaller struct {
// InstallDir contains each installation, named by version.
InstallDir string
// TargetServiceFile contains a copy of the linked installation's systemd service.
- TargetServiceFile string
+ TargetServices []ServiceFile
// SystemBinDir contains binaries for the system (packaged) install of Teleport.
SystemBinDir string
- // SystemServiceFile contains the systemd service file for the system (packaged) install of Teleport.
- SystemServiceFile string
+ // SystemServiceDir contains the systemd service directory for the system (packaged) install of Teleport.
+ SystemServiceDir string
// HTTP is an HTTP client for downloading Teleport.
HTTP *http.Client
// Log contains a logger.
@@ -81,8 +105,6 @@ type LocalInstaller struct {
ReservedFreeTmpDisk uint64
// ReservedFreeInstallDisk is the amount of disk that must remain free in the install directory.
ReservedFreeInstallDisk uint64
- // TransformService transforms the systemd service during copying.
- TransformService func(cfg []byte, pathDir string, flags autoupdate.InstallFlags) []byte
// ValidateBinary returns true if a file is a linkable binary, or
// false if a file should not be linked.
ValidateBinary func(ctx context.Context, path string) (bool, error)
@@ -416,7 +438,7 @@ func (li *LocalInstaller) Link(ctx context.Context, rev Revision, pathDir string
}
revert, err = li.forceLinks(ctx,
filepath.Join(versionDir, "bin"),
- filepath.Join(versionDir, serviceDir, serviceName),
+ filepath.Join(versionDir, serviceDir),
pathDir, force, rev.Flags,
)
if err != nil {
@@ -432,7 +454,7 @@ func (li *LocalInstaller) Link(ctx context.Context, rev Revision, pathDir string
func (li *LocalInstaller) LinkSystem(ctx context.Context) (revert func(context.Context) bool, err error) {
// The system package service file is always removed without flags, so pass
// no flags here to match the behavior.
- revert, err = li.forceLinks(ctx, li.SystemBinDir, li.SystemServiceFile, defaultPathDir, false, 0)
+ revert, err = li.forceLinks(ctx, li.SystemBinDir, li.SystemServiceDir, defaultPathDir, false, 0)
return revert, trace.Wrap(err)
}
@@ -446,7 +468,7 @@ func (li *LocalInstaller) TryLink(ctx context.Context, revision Revision, pathDi
}
return trace.Wrap(li.tryLinks(ctx,
filepath.Join(versionDir, "bin"),
- filepath.Join(versionDir, serviceDir, serviceName),
+ filepath.Join(versionDir, serviceDir),
pathDir, revision.Flags,
))
}
@@ -457,7 +479,7 @@ func (li *LocalInstaller) TryLink(ctx context.Context, revision Revision, pathDi
func (li *LocalInstaller) TryLinkSystem(ctx context.Context) error {
// The system package service file is always removed without flags, so pass
// no flags here to match the behavior.
- return trace.Wrap(li.tryLinks(ctx, li.SystemBinDir, li.SystemServiceFile, defaultPathDir, 0))
+ return trace.Wrap(li.tryLinks(ctx, li.SystemBinDir, li.SystemServiceDir, defaultPathDir, 0))
}
// Unlink unlinks a version from pathDir and TargetServiceFile.
@@ -467,19 +489,13 @@ func (li *LocalInstaller) Unlink(ctx context.Context, rev Revision, pathDir stri
if err != nil {
return trace.Wrap(err)
}
- return trace.Wrap(li.removeLinks(ctx,
- filepath.Join(versionDir, "bin"),
- filepath.Join(versionDir, serviceDir, serviceName),
- pathDir, rev.Flags,
- ))
+ return trace.Wrap(li.removeLinks(ctx, filepath.Join(versionDir, "bin"), pathDir))
}
// UnlinkSystem unlinks the system (package) version from defaultPathDir and TargetServiceFile.
// See Installer interface for additional specs.
func (li *LocalInstaller) UnlinkSystem(ctx context.Context) error {
- // The system package service file is always linked without flags, so pass
- // no flags here to match the behavior.
- return trace.Wrap(li.removeLinks(ctx, li.SystemBinDir, li.SystemServiceFile, defaultPathDir, 0))
+ return trace.Wrap(li.removeLinks(ctx, li.SystemBinDir, defaultPathDir))
}
// symlink from oldname to newname
@@ -500,7 +516,7 @@ type smallFile struct {
// If successful, forceLinks may also be reverted after it returns by calling revert.
// The revert function returns true if reverting succeeds.
// If force is true, non-link files will be overwritten.
-func (li *LocalInstaller) forceLinks(ctx context.Context, srcBinDir, srcSvcFile, dstBinDir string, force bool, flags autoupdate.InstallFlags) (revert func(context.Context) bool, err error) {
+func (li *LocalInstaller) forceLinks(ctx context.Context, srcBinDir, srcSvcDir, dstBinDir string, force bool, flags autoupdate.InstallFlags) (revert func(context.Context) bool, err error) {
// setup revert function
var (
revertLinks []symlink
@@ -552,9 +568,11 @@ func (li *LocalInstaller) forceLinks(ctx context.Context, srcBinDir, srcSvcFile,
if err != nil {
return revert, trace.Wrap(err)
}
- err = os.MkdirAll(filepath.Dir(li.TargetServiceFile), systemDirMode)
- if err != nil {
- return revert, trace.Wrap(err)
+ for _, s := range li.TargetServices {
+ err = os.MkdirAll(filepath.Dir(s.Path), systemDirMode)
+ if err != nil {
+ return revert, trace.Wrap(err)
+ }
}
// create binary links
@@ -588,27 +606,40 @@ func (li *LocalInstaller) forceLinks(ctx context.Context, srcBinDir, srcSvcFile,
return revert, trace.Wrap(ErrNoBinaries)
}
- // create systemd service file
+ // create systemd service files
- orig, err := li.forceCopyService(li.TargetServiceFile, srcSvcFile, maxServiceFileSize, dstBinDir, flags)
- if err != nil && !errors.Is(err, os.ErrExist) {
- return revert, trace.Wrap(err, "failed to copy service")
- }
- if orig != nil {
- revertFiles = append(revertFiles, *orig)
+ for _, s := range li.TargetServices {
+ orig, err := copyService(s, srcSvcDir, dstBinDir, flags)
+ if err != nil && !errors.Is(err, os.ErrExist) {
+ return revert, trace.Wrap(err, "failed to copy service %s", filepath.Base(s.Path))
+ }
+ if orig != nil {
+ revertFiles = append(revertFiles, *orig)
+ }
}
+
return revert, nil
}
-// forceCopyService uses forceCopy to copy a systemd service file from src to dst.
+// copyService copies a systemd service file from src to dst.
// The contents of both src and dst must be smaller than n.
-// See forceCopy for more details.
-func (li *LocalInstaller) forceCopyService(dst, src string, n int64, dstBinDir string, flags autoupdate.InstallFlags) (orig *smallFile, err error) {
- srcData, err := readFileAtMost(src, n)
- if err != nil {
- return nil, trace.Wrap(err)
+//
+// Copied data is processed by s.ExampleFunc.
+// If s.ExampleFunc nil, no data is copied, but the original file contents are still returned.
+//
+// See prepCopy and forceCopy for more details.
+func copyService(s ServiceFile, exampleDir string, dstBinDir string, flags autoupdate.InstallFlags) (orig *smallFile, err error) {
+ const n = maxServiceFileSize
+ if s.ExampleFunc != nil {
+ srcData, err := readFileAtMost(filepath.Join(exampleDir, s.ExampleName), n)
+ if err != nil {
+ return nil, trace.Wrap(err)
+ }
+ orig, err = forceCopy(s.Path, s.ExampleFunc(srcData, dstBinDir, flags), n)
+ return orig, trace.Wrap(err)
}
- return forceCopy(dst, li.TransformService(srcData, dstBinDir, flags), n)
+ orig, err = prepCopy(s.Path, n)
+ return orig, trace.Wrap(err)
}
// forceLink attempts to create a symlink, atomically replacing an existing link if already present.
@@ -642,27 +673,38 @@ func forceLink(oldname, newname string, force bool) (orig string, err error) {
// If an irregular file, too large file, or directory exists in dst already, forceCopy errors.
// If the file is already present with the desired contents, forceCopy returns os.ErrExist.
func forceCopy(dst string, srcData []byte, n int64) (orig *smallFile, err error) {
+ orig, err = prepCopy(dst, n)
+ if err != nil {
+ return orig, trace.Wrap(err)
+ }
+ if orig != nil && bytes.Equal(srcData, orig.data) {
+ return nil, trace.Wrap(os.ErrExist)
+ }
+ err = writeFileAtomicWithinDir(dst, srcData, configFileMode)
+ if err != nil {
+ return orig, trace.Wrap(err)
+ }
+ return orig, nil
+}
+
+// prepCopy validates and returns a preserved original copy of a file with
+// length <= n at the path specified by dst.
+func prepCopy(dst string, n int64) (orig *smallFile, err error) {
fi, err := os.Lstat(dst)
- if err != nil && !errors.Is(err, os.ErrNotExist) {
+ if errors.Is(err, os.ErrNotExist) {
+ return orig, nil
+ }
+ if err != nil {
return nil, trace.Wrap(err)
}
- if err == nil {
- orig = &smallFile{
- name: dst,
- mode: fi.Mode(),
- }
- if !orig.mode.IsRegular() {
- return nil, trace.Errorf("refusing to replace irregular file at %s", dst)
- }
- orig.data, err = readFileAtMost(dst, n)
- if err != nil {
- return nil, trace.Wrap(err)
- }
- if bytes.Equal(srcData, orig.data) {
- return nil, trace.Wrap(os.ErrExist)
- }
+ orig = &smallFile{
+ name: dst,
+ mode: fi.Mode(),
}
- err = writeFileAtomicWithinDir(dst, srcData, configFileMode)
+ if !orig.mode.IsRegular() {
+ return nil, trace.Errorf("refusing to replace irregular file at %s", dst)
+ }
+ orig.data, err = readFileAtMost(dst, n)
if err != nil {
return nil, trace.Wrap(err)
}
@@ -680,8 +722,7 @@ func readFileAtMost(name string, n int64) ([]byte, error) {
return data, trace.Wrap(err)
}
-func (li *LocalInstaller) removeLinks(ctx context.Context, srcBinDir, srcSvcFile, dstBinDir string, flags autoupdate.InstallFlags) error {
- removeService := false
+func (li *LocalInstaller) removeLinks(ctx context.Context, srcBinDir, dstBinDir string) error {
entries, err := os.ReadDir(srcBinDir)
if err != nil {
return trace.Wrap(err, "failed to find Teleport binary directory")
@@ -710,34 +751,41 @@ func (li *LocalInstaller) removeLinks(ctx context.Context, srcBinDir, srcSvcFile
li.Log.ErrorContext(ctx, "Unable to remove link.", "oldname", oldname, "newname", newname, errorKey, err)
continue
}
- if filepath.Base(newname) == teleport.ComponentTeleport {
- removeService = true
+
+ for _, s := range li.TargetServices {
+ if filepath.Base(newname) != s.Binary {
+ continue
+ }
+ // binRev is either the version or "system"
+ binRev := filepath.Base(filepath.Dir(filepath.Dir(oldname)))
+ rev, err := NewRevisionFromDir(binRev)
+ if err != nil {
+ li.Log.DebugContext(ctx, "Service not present.", "path", s.Path)
+ continue
+ }
+ revMarker := genMarker(rev)
+ diskMarker, err := readFileLimit(s.Path, int64(len(revMarker)))
+ if errors.Is(err, os.ErrNotExist) {
+ li.Log.DebugContext(ctx, "Service not present.", "path", s.Path)
+ continue
+ }
+ if err != nil {
+ return trace.Wrap(err)
+ }
+ // Note that old versions of teleport-update will install services without the marker.
+ // Certain version combinations (before and after this commit) may leave services behind
+ // if they are not replaced by the new version of teleport-update. This should only impact
+ // explicit system package unlinking, which is rarely used.
+ if string(diskMarker) != revMarker {
+ li.Log.WarnContext(ctx, "Removed binary link, but skipping removal of custom service that does not match the binary.",
+ "service", filepath.Base(s.Path), "binary", filepath.Base(newname))
+ continue
+ }
+ if err := os.Remove(s.Path); err != nil {
+ return trace.Wrap(err, "error removing copy of %s", filepath.Base(s.Path))
+ }
}
}
- // only remove service if teleport was removed
- if !removeService {
- li.Log.DebugContext(ctx, "Teleport binary not unlinked. Skipping removal of teleport.service.")
- return nil
- }
- srcBytes, err := readFileAtMost(srcSvcFile, maxServiceFileSize)
- if err != nil {
- return trace.Wrap(err)
- }
- dstBytes, err := readFileAtMost(li.TargetServiceFile, maxServiceFileSize)
- if errors.Is(err, os.ErrNotExist) {
- li.Log.DebugContext(ctx, "Service not present.", "path", li.TargetServiceFile)
- return nil
- }
- if err != nil {
- return trace.Wrap(err)
- }
- if !bytes.Equal(li.TransformService(srcBytes, dstBinDir, flags), dstBytes) {
- li.Log.WarnContext(ctx, "Removed teleport binary link, but skipping removal of custom teleport.service: the service file does not match the reference file for this version. The file might have been manually edited.")
- return nil
- }
- if err := os.Remove(li.TargetServiceFile); err != nil {
- return trace.Wrap(err, "error removing copy of %s", filepath.Base(li.TargetServiceFile))
- }
return nil
}
@@ -745,7 +793,7 @@ func (li *LocalInstaller) removeLinks(ctx context.Context, srcBinDir, srcSvcFile
// Existing links that point to files outside binDir or svcDir, as well as existing non-link files, will error.
// tryLinks will not attempt to create any links if linking could result in an error.
// However, concurrent changes to links may result in an error with partially-complete linking.
-func (li *LocalInstaller) tryLinks(ctx context.Context, srcBinDir, srcSvcFile, dstBinDir string, flags autoupdate.InstallFlags) error {
+func (li *LocalInstaller) tryLinks(ctx context.Context, srcBinDir, srcSvcDir, dstBinDir string, flags autoupdate.InstallFlags) error {
// ensure source directory exists
entries, err := os.ReadDir(srcBinDir)
if errors.Is(err, os.ErrNotExist) {
@@ -760,9 +808,11 @@ func (li *LocalInstaller) tryLinks(ctx context.Context, srcBinDir, srcSvcFile, d
if err != nil {
return trace.Wrap(err)
}
- err = os.MkdirAll(filepath.Dir(li.TargetServiceFile), systemDirMode)
- if err != nil {
- return trace.Wrap(err)
+ for _, s := range li.TargetServices {
+ err = os.MkdirAll(filepath.Dir(s.Path), systemDirMode)
+ if err != nil {
+ return trace.Wrap(err)
+ }
}
// validate that we can link all system binaries before attempting linking
@@ -802,10 +852,11 @@ func (li *LocalInstaller) tryLinks(ctx context.Context, srcBinDir, srcSvcFile, d
}
}
- // if any binaries are linked from srcBinDir, always link the service from svcDir
- _, err = li.forceCopyService(li.TargetServiceFile, srcSvcFile, maxServiceFileSize, dstBinDir, flags)
- if err != nil && !errors.Is(err, os.ErrExist) {
- return trace.Wrap(err, "failed to copy service")
+ for _, s := range li.TargetServices {
+ _, err := copyService(s, srcSvcDir, dstBinDir, flags)
+ if err != nil && !errors.Is(err, os.ErrExist) {
+ return trace.Wrap(err, "failed to copy service %s", filepath.Base(s.Path))
+ }
}
return nil
diff --git a/lib/autoupdate/agent/installer_test.go b/lib/autoupdate/agent/installer_test.go
index 80276d90e416d..afd977a92e09f 100644
--- a/lib/autoupdate/agent/installer_test.go
+++ b/lib/autoupdate/agent/installer_test.go
@@ -456,12 +456,17 @@ func TestLocalInstaller_Link(t *testing.T) {
validator := Validator{Log: slog.Default()}
installer := &LocalInstaller{
- InstallDir: versionsDir,
- TargetServiceFile: filepath.Join(linkDir, serviceDir, serviceName),
- Log: slog.Default(),
- TransformService: func(b []byte, pathDir string, flags autoupdate.InstallFlags) []byte {
- return []byte(fmt.Sprintf("[service=%s][path=%s][flags=%s]", string(b), pathDir, flags.Strings()))
+ InstallDir: versionsDir,
+ TargetServices: []ServiceFile{
+ {
+ Path: filepath.Join(linkDir, serviceDir, serviceName),
+ ExampleName: serviceName,
+ ExampleFunc: func(b []byte, pathDir string, flags autoupdate.InstallFlags) []byte {
+ return fmt.Appendf(nil, "[service=%s][path=%s][flags=%s]", string(b), pathDir, flags.Strings())
+ },
+ },
},
+ Log: slog.Default(),
ValidateBinary: validator.IsExecutable,
Template: autoupdate.DefaultCDNURITemplate,
}
@@ -711,12 +716,17 @@ func TestLocalInstaller_TryLink(t *testing.T) {
validator := Validator{Log: slog.Default()}
installer := &LocalInstaller{
- InstallDir: versionsDir,
- TargetServiceFile: filepath.Join(linkDir, serviceDir, serviceName),
- Log: slog.Default(),
- TransformService: func(b []byte, pathDir string, flags autoupdate.InstallFlags) []byte {
- return []byte(fmt.Sprintf("[service=%s][path=%s][flags=%s]", string(b), pathDir, flags.Strings()))
+ InstallDir: versionsDir,
+ TargetServices: []ServiceFile{
+ {
+ Path: filepath.Join(linkDir, serviceDir, serviceName),
+ ExampleName: serviceName,
+ ExampleFunc: func(b []byte, pathDir string, flags autoupdate.InstallFlags) []byte {
+ return fmt.Appendf(nil, "[service=%s][path=%s][flags=%s]", string(b), pathDir, flags.Strings())
+ },
+ },
},
+ Log: slog.Default(),
ValidateBinary: validator.IsExecutable,
}
ctx := context.Background()
@@ -848,12 +858,17 @@ func TestLocalInstaller_Remove(t *testing.T) {
validator := Validator{Log: slog.Default()}
installer := &LocalInstaller{
- InstallDir: versionsDir,
- TargetServiceFile: filepath.Join(linkDir, serviceDir, serviceName),
- Log: slog.Default(),
- TransformService: func(b []byte, pathDir string, flags autoupdate.InstallFlags) []byte {
- return []byte(fmt.Sprintf("[service=%s][path=%s][flags=%s]", string(b), pathDir, flags.Strings()))
+ InstallDir: versionsDir,
+ TargetServices: []ServiceFile{
+ {
+ Path: filepath.Join(linkDir, serviceDir, serviceName),
+ ExampleName: serviceName,
+ ExampleFunc: func(b []byte, pathDir string, flags autoupdate.InstallFlags) []byte {
+ return fmt.Appendf(nil, "[service=%s][path=%s][flags=%s]", string(b), pathDir, flags.Strings())
+ },
+ },
},
+ Log: slog.Default(),
ValidateBinary: validator.IsExecutable,
}
ctx := context.Background()
@@ -918,12 +933,18 @@ func TestLocalInstaller_IsLinked(t *testing.T) {
validator := Validator{Log: slog.Default()}
installer := &LocalInstaller{
- InstallDir: versionsDir,
- TargetServiceFile: filepath.Join(linkDir, serviceDir, serviceName),
- Log: slog.Default(),
- TransformService: func(b []byte, pathDir string, flags autoupdate.InstallFlags) []byte {
- return []byte(fmt.Sprintf("[service=%s][path=%s][flags=%s]", string(b), pathDir, flags.Strings()))
+ InstallDir: versionsDir,
+ TargetServices: []ServiceFile{
+ {
+ Path: filepath.Join(linkDir, serviceDir, serviceName),
+ ExampleName: serviceName,
+ ExampleFunc: func(b []byte, pathDir string, flags autoupdate.InstallFlags) []byte {
+ return fmt.Appendf(nil, "[service=%s][path=%s][flags=%s]", string(b), pathDir, flags.Strings())
+
+ },
+ },
},
+ Log: slog.Default(),
ValidateBinary: validator.IsExecutable,
}
ctx := context.Background()
@@ -963,9 +984,8 @@ func TestLocalInstaller_Unlink(t *testing.T) {
servicePath := filepath.Join(serviceDir, serviceName)
tests := []struct {
- name string
- bins []string
- svcOrig []byte
+ name string
+ bins []string
links []symlink
svcCopy []byte
@@ -974,19 +994,17 @@ func TestLocalInstaller_Unlink(t *testing.T) {
errMatch string
}{
{
- name: "normal",
- bins: []string{"teleport", "tsh"},
- svcOrig: []byte("orig"),
+ name: "normal",
+ bins: []string{"teleport", "tsh"},
links: []symlink{
{oldname: "bin/teleport", newname: "bin/teleport"},
{oldname: "bin/tsh", newname: "bin/tsh"},
},
- svcCopy: []byte("[service=orig][path=bin][flags=[]]"),
+ svcCopy: []byte("# teleport-update " + version + "\n"),
},
{
- name: "different services",
- bins: []string{"teleport", "tsh"},
- svcOrig: []byte("orig"),
+ name: "different services",
+ bins: []string{"teleport", "tsh"},
links: []symlink{
{oldname: "bin/teleport", newname: "bin/teleport"},
{oldname: "bin/tsh", newname: "bin/tsh"},
@@ -995,64 +1013,48 @@ func TestLocalInstaller_Unlink(t *testing.T) {
remaining: []string{servicePath},
},
{
- name: "missing target service",
- bins: []string{"teleport", "tsh"},
- svcOrig: []byte("orig"),
- links: []symlink{
- {oldname: "bin/teleport", newname: "bin/teleport"},
- {oldname: "bin/tsh", newname: "bin/tsh"},
- },
- },
- {
- name: "missing source service",
+ name: "missing target service",
bins: []string{"teleport", "tsh"},
links: []symlink{
{oldname: "bin/teleport", newname: "bin/teleport"},
{oldname: "bin/tsh", newname: "bin/tsh"},
},
- svcCopy: []byte("custom"),
- remaining: []string{servicePath},
- errMatch: "no such",
},
{
- name: "missing teleport link",
- bins: []string{"teleport", "tsh"},
- svcOrig: []byte("orig"),
+ name: "missing teleport link",
+ bins: []string{"teleport", "tsh"},
links: []symlink{
{oldname: "bin/tsh", newname: "bin/tsh"},
},
- svcCopy: []byte("[service=orig][path=bin][flags=[]]"),
+ svcCopy: []byte("# teleport-update " + version + "\n"),
remaining: []string{servicePath},
},
{
- name: "missing other link",
- bins: []string{"teleport", "tsh"},
- svcOrig: []byte("orig"),
+ name: "missing other link",
+ bins: []string{"teleport", "tsh"},
links: []symlink{
{oldname: "bin/teleport", newname: "bin/teleport"},
},
- svcCopy: []byte("[service=orig][path=bin][flags=[]]"),
+ svcCopy: []byte("# teleport-update " + version + "\n"),
},
{
- name: "wrong teleport link",
- bins: []string{"teleport", "tsh"},
- svcOrig: []byte("orig"),
+ name: "wrong teleport link",
+ bins: []string{"teleport", "tsh"},
links: []symlink{
{oldname: "other", newname: "bin/teleport"},
{oldname: "bin/tsh", newname: "bin/tsh"},
},
- svcCopy: []byte("[service=orig][path=bin][flags=[]]"),
+ svcCopy: []byte("# teleport-update " + version + "\n"),
remaining: []string{servicePath, "bin/teleport"},
},
{
- name: "wrong other link",
- bins: []string{"teleport", "tsh"},
- svcOrig: []byte("orig"),
+ name: "wrong other link",
+ bins: []string{"teleport", "tsh"},
links: []symlink{
{oldname: "bin/teleport", newname: "bin/teleport"},
{oldname: "wrong", newname: "bin/tsh"},
},
- svcCopy: []byte("[service=orig][path=bin][flags=[]]"),
+ svcCopy: []byte("# teleport-update " + version + "\n"),
remaining: []string{"bin/tsh"},
},
}
@@ -1073,13 +1075,6 @@ func TestLocalInstaller_Unlink(t *testing.T) {
mode: os.ModePerm,
})
}
- if tt.svcOrig != nil {
- files = append(files, smallFile{
- name: filepath.Join(versionDir, servicePath),
- data: tt.svcOrig,
- mode: os.ModePerm,
- })
- }
if tt.svcCopy != nil {
files = append(files, smallFile{
name: filepath.Join(linkDir, servicePath),
@@ -1104,12 +1099,14 @@ func TestLocalInstaller_Unlink(t *testing.T) {
}
installer := &LocalInstaller{
- InstallDir: versionsDir,
- TargetServiceFile: filepath.Join(linkDir, serviceDir, serviceName),
- Log: slog.Default(),
- TransformService: func(b []byte, pathDir string, flags autoupdate.InstallFlags) []byte {
- return []byte(fmt.Sprintf("[service=%s][path=%s][flags=%s]", string(b), filepath.Base(pathDir), flags.Strings()))
+ InstallDir: versionsDir,
+ TargetServices: []ServiceFile{
+ {
+ Path: filepath.Join(linkDir, serviceDir, serviceName),
+ Binary: "teleport",
+ },
},
+ Log: slog.Default(),
}
ctx := context.Background()
err = installer.Unlink(ctx, NewRevision(version, 0), filepath.Join(linkDir, "bin"))
diff --git a/lib/autoupdate/agent/setup.go b/lib/autoupdate/agent/setup.go
index 732471aba9be1..044271ed26c78 100644
--- a/lib/autoupdate/agent/setup.go
+++ b/lib/autoupdate/agent/setup.go
@@ -22,6 +22,7 @@ import (
"bytes"
"context"
"errors"
+ "fmt"
"io"
"io/fs"
"log/slog"
@@ -35,6 +36,7 @@ import (
"gopkg.in/yaml.v3"
"github.com/gravitational/teleport/lib/autoupdate"
+ "github.com/gravitational/teleport/lib/config/systemd"
"github.com/gravitational/teleport/lib/defaults"
libutils "github.com/gravitational/teleport/lib/utils"
)
@@ -59,9 +61,22 @@ const (
deprecatedServiceName = "teleport-upgrade.service"
)
+// genHeader generates a systemd config file header that starts
+// with the serviceMarker.
+func genHeader(rev Revision) string {
+ return genMarker(rev) +
+ "# DO NOT EDIT THIS FILE\n"
+}
+
+// genMarker generates a systemd config file marker that is the
+// first part of the header for systemd service files.
+// Each revision of Teleport has a unique marker.
+func genMarker(rev Revision) string {
+ return "# teleport-update " + rev.Dir() + "\n"
+}
+
const (
- updateServiceTemplate = `# teleport-update
-# DO NOT EDIT THIS FILE
+ updateServiceTemplate = `
[Unit]
Description=Teleport auto-update service
@@ -69,8 +84,7 @@ Description=Teleport auto-update service
Type=oneshot
ExecStart={{.UpdaterBinary}} --install-suffix={{.InstallSuffix}} "--install-dir={{escape .InstallDir}}" update
`
- updateTimerTemplate = `# teleport-update
-# DO NOT EDIT THIS FILE
+ updateTimerTemplate = `
[Unit]
Description=Teleport auto-update timer unit
@@ -82,15 +96,13 @@ RandomizedDelaySec=1m
[Install]
WantedBy={{.TeleportService}}
`
- teleportDropInTemplate = `# teleport-update
-# DO NOT EDIT THIS FILE
+ teleportDropInTemplate = `
[Service]
Environment="TELEPORT_UPDATE_CONFIG_FILE={{escape .UpdaterConfigFile}}"
Environment="TELEPORT_UPDATE_INSTALL_DIR={{escape .InstallDir}}"
`
- deprecatedDropInTemplate = `# teleport-update
-# DO NOT EDIT THIS FILE
+ deprecatedDropInTemplate = `
[Service]
ExecStart=
ExecStart=-/bin/echo "The teleport-upgrade script has been disabled by teleport-update. Please remove the teleport-ent-updater package."
@@ -224,13 +236,13 @@ func (ns *Namespace) Init() (lockFile string, err error) {
// Setup installs service and timer files for the teleport-update binary.
// Afterwords, Setup reloads systemd and enables the timer with --now.
-func (ns *Namespace) Setup(ctx context.Context, path string) error {
+func (ns *Namespace) Setup(ctx context.Context, path string, rev Revision) error {
if ok, err := hasSystemD(); err == nil && !ok {
ns.log.WarnContext(ctx, "Systemd is not running, skipping updater installation.")
return nil
}
- err := ns.writeConfigFiles(ctx, path)
+ err := ns.writeConfigFiles(ctx, path, rev)
if err != nil {
return trace.Wrap(err, "failed to write teleport-update systemd config files")
}
@@ -277,6 +289,7 @@ func (ns *Namespace) Setup(ctx context.Context, path string) error {
}
// Teardown removes all traces of the auto-updater, including its configuration.
+// Teardown does not verify that the removed files were created by teleport-update.
func (ns *Namespace) Teardown(ctx context.Context) error {
if ok, err := hasSystemD(); err == nil && !ok {
ns.log.WarnContext(ctx, "Systemd is not running, skipping updater removal.")
@@ -344,7 +357,7 @@ func (ns *Namespace) Teardown(ctx context.Context) error {
return nil
}
-func (ns *Namespace) writeConfigFiles(ctx context.Context, path string) error {
+func (ns *Namespace) writeConfigFiles(ctx context.Context, path string, rev Revision) error {
teleportService := filepath.Base(ns.serviceFile)
params := confParams{
TeleportService: teleportService,
@@ -366,7 +379,7 @@ func (ns *Namespace) writeConfigFiles(ctx context.Context, path string) error {
if v.path == "" {
continue
}
- err := writeSystemTemplate(v.path, v.tmpl, params)
+ err := writeSystemTemplate(v.path, genHeader(rev), v.tmpl, params)
if err != nil {
return trace.Wrap(err)
}
@@ -381,7 +394,7 @@ func (ns *Namespace) writeConfigFiles(ctx context.Context, path string) error {
return nil
}
ns.log.InfoContext(ctx, "Disabling needrestart.", unitKey, teleportService)
- err = writeSystemTemplate(ns.needrestartConfFile, needrestartConfTemplate, params)
+ err = writeSystemTemplate(ns.needrestartConfFile, "", needrestartConfTemplate, params)
if err != nil {
ns.log.ErrorContext(ctx, "Unable to disable needrestart.", errorKey, err)
return nil
@@ -391,13 +404,19 @@ func (ns *Namespace) writeConfigFiles(ctx context.Context, path string) error {
// writeSystemTemplate atomically writes a template to a system file, creating any needed directories.
// Temporarily files are stored in the target path to ensure the file has needed SELinux contexts.
-func writeSystemTemplate(path, t string, values any) error {
+func writeSystemTemplate(path, header, t string, values any) error {
dir, file := filepath.Split(path)
if err := os.MkdirAll(dir, systemDirMode); err != nil {
return trace.Wrap(err)
}
return trace.Wrap(writeAtomicWithinDir(path, configFileMode, func(w io.Writer) error {
+ if header != "" {
+ _, err := fmt.Fprint(w, header)
+ if err != nil {
+ return trace.Wrap(err)
+ }
+ }
tmpl, err := template.New(file).Funcs(template.FuncMap{
"replace": func(s, old, new string) string {
return strings.ReplaceAll(s, old, new)
@@ -421,11 +440,35 @@ func writeSystemTemplate(path, t string, values any) error {
}))
}
-// ReplaceTeleportService replaces the default paths in the Teleport service config with namespaced paths.
-func (ns *Namespace) ReplaceTeleportService(cfg []byte, pathDir string, flags autoupdate.InstallFlags) []byte {
+// WriteTeleportService writes the Teleport systemd service for the version of Teleport
+// that matches the version of Teleport compiled into the executing code.
+func (ns *Namespace) WriteTeleportService(_ context.Context, pathDir string, rev Revision) error {
if pathDir == "" {
pathDir = ns.defaultPathDir
}
+ return trace.Wrap(writeAtomicWithinDir(ns.serviceFile, configFileMode, func(w io.Writer) error {
+ _, err := fmt.Fprint(w, genHeader(rev)+"\n")
+ if err != nil {
+ return trace.Wrap(err)
+ }
+ return trace.Wrap(systemd.WriteUnitFile(systemd.Flags{
+ EnvironmentFile: systemd.DefaultEnvironmentFile,
+ PIDFile: ns.pidFile,
+ FileDescriptorLimit: systemd.DefaultFileDescriptorLimit,
+ TeleportInstallationFile: filepath.Join(pathDir, "teleport"),
+ TeleportConfigPath: ns.configFile,
+ FIPS: rev.Flags&autoupdate.FlagFIPS != 0,
+ }, w))
+ }))
+}
+
+// ReplaceTeleportService replaces the default paths in the Teleport service config with namespaced paths.
+// This function is still used for backwards-compatibility, but string-replaced systemd services
+// are always overridden in more recent versions of Teleport.
+func (ns *Namespace) ReplaceTeleportService(cfg []byte, path string, flags autoupdate.InstallFlags) []byte {
+ if path == "" {
+ path = ns.defaultPathDir
+ }
var startFlags []string
if flags&autoupdate.FlagFIPS != 0 {
startFlags = append(startFlags, "--fips")
@@ -435,7 +478,7 @@ func (ns *Namespace) ReplaceTeleportService(cfg []byte, pathDir string, flags au
}{
{
old: "/usr/local/bin/",
- new: pathDir + "/",
+ new: path + "/",
},
{
old: "/etc/teleport.yaml",
diff --git a/lib/autoupdate/agent/setup_test.go b/lib/autoupdate/agent/setup_test.go
index e077d37dd91e7..42311e56fefbd 100644
--- a/lib/autoupdate/agent/setup_test.go
+++ b/lib/autoupdate/agent/setup_test.go
@@ -170,7 +170,7 @@ func TestWriteConfigFiles(t *testing.T) {
ns.teleportDropInFile = rebasePath(filepath.Join(linkDir, serviceDir, filepath.Base(filepath.Dir(ns.teleportDropInFile))), ns.teleportDropInFile)
ns.deprecatedDropInFile = rebasePath(filepath.Join(linkDir, serviceDir, filepath.Base(filepath.Dir(ns.deprecatedDropInFile))), ns.deprecatedDropInFile)
ns.needrestartConfFile = rebasePath(linkDir, filepath.Base(ns.needrestartConfFile))
- err = ns.writeConfigFiles(ctx, linkDir)
+ err = ns.writeConfigFiles(ctx, linkDir, NewRevision("version", 0))
require.NoError(t, err)
for _, tt := range []struct {
@@ -388,6 +388,58 @@ func TestUnversionedTeleportConfig(t *testing.T) {
require.Equal(t, in, out)
}
+func TestWriteTeleportService(t *testing.T) {
+ t.Parallel()
+
+ tests := []struct {
+ name string
+
+ pidFile string
+ configFile string
+ pathDir string
+ flags autoupdate.InstallFlags
+ }{
+ {
+ name: "default",
+ pidFile: "/var/run/teleport.pid",
+ configFile: "/etc/teleport.yaml",
+ pathDir: "/usr/local/bin",
+ },
+ {
+ name: "custom",
+ pidFile: "/some/path/teleport.pid",
+ configFile: "/some/path/teleport.yaml",
+ pathDir: "/some/path/bin",
+ },
+ {
+ name: "FIPS",
+ pidFile: "/var/run/teleport.pid",
+ configFile: "/etc/teleport.yaml",
+ pathDir: "/usr/local/bin",
+ flags: autoupdate.FlagFIPS,
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ serviceFile := filepath.Join(t.TempDir(), "file")
+ ns := &Namespace{
+ log: slog.Default(),
+ configFile: tt.configFile,
+ serviceFile: serviceFile,
+ pidFile: tt.pidFile,
+ }
+ err := ns.WriteTeleportService(context.Background(), tt.pathDir, NewRevision("version", tt.flags))
+ require.NoError(t, err)
+ data, err := os.ReadFile(serviceFile)
+ require.NoError(t, err)
+ if golden.ShouldSet() {
+ golden.Set(t, data)
+ }
+ require.Equal(t, string(golden.Get(t)), string(data))
+ })
+ }
+}
+
func TestReplaceTeleportService(t *testing.T) {
t.Parallel()
diff --git a/lib/autoupdate/agent/testdata/TestWriteConfigFiles/no_namespace/deprecated.golden b/lib/autoupdate/agent/testdata/TestWriteConfigFiles/no_namespace/deprecated.golden
index 3f18b9cdf3065..fcaaae54ce5d0 100644
--- a/lib/autoupdate/agent/testdata/TestWriteConfigFiles/no_namespace/deprecated.golden
+++ b/lib/autoupdate/agent/testdata/TestWriteConfigFiles/no_namespace/deprecated.golden
@@ -1,5 +1,6 @@
-# teleport-update
+# teleport-update version
# DO NOT EDIT THIS FILE
+
[Service]
ExecStart=
ExecStart=-/bin/echo "The teleport-upgrade script has been disabled by teleport-update. Please remove the teleport-ent-updater package."
diff --git a/lib/autoupdate/agent/testdata/TestWriteConfigFiles/no_namespace/dropin.golden b/lib/autoupdate/agent/testdata/TestWriteConfigFiles/no_namespace/dropin.golden
index cb09143fe9fdf..4ca6b61b76342 100644
--- a/lib/autoupdate/agent/testdata/TestWriteConfigFiles/no_namespace/dropin.golden
+++ b/lib/autoupdate/agent/testdata/TestWriteConfigFiles/no_namespace/dropin.golden
@@ -1,5 +1,6 @@
-# teleport-update
+# teleport-update version
# DO NOT EDIT THIS FILE
+
[Service]
Environment="TELEPORT_UPDATE_CONFIG_FILE=/opt/teleport/default/update.yaml"
Environment="TELEPORT_UPDATE_INSTALL_DIR=/opt/teleport"
diff --git a/lib/autoupdate/agent/testdata/TestWriteConfigFiles/no_namespace/service.golden b/lib/autoupdate/agent/testdata/TestWriteConfigFiles/no_namespace/service.golden
index 45d778ec09f25..e3038b3255fc0 100644
--- a/lib/autoupdate/agent/testdata/TestWriteConfigFiles/no_namespace/service.golden
+++ b/lib/autoupdate/agent/testdata/TestWriteConfigFiles/no_namespace/service.golden
@@ -1,5 +1,6 @@
-# teleport-update
+# teleport-update version
# DO NOT EDIT THIS FILE
+
[Unit]
Description=Teleport auto-update service
diff --git a/lib/autoupdate/agent/testdata/TestWriteConfigFiles/no_namespace/timer.golden b/lib/autoupdate/agent/testdata/TestWriteConfigFiles/no_namespace/timer.golden
index d14a43d679e53..df921a1e883f1 100644
--- a/lib/autoupdate/agent/testdata/TestWriteConfigFiles/no_namespace/timer.golden
+++ b/lib/autoupdate/agent/testdata/TestWriteConfigFiles/no_namespace/timer.golden
@@ -1,5 +1,6 @@
-# teleport-update
+# teleport-update version
# DO NOT EDIT THIS FILE
+
[Unit]
Description=Teleport auto-update timer unit
diff --git a/lib/autoupdate/agent/testdata/TestWriteConfigFiles/test_namespace/dropin.golden b/lib/autoupdate/agent/testdata/TestWriteConfigFiles/test_namespace/dropin.golden
index dc6445dc6e7f9..14ce4fd431951 100644
--- a/lib/autoupdate/agent/testdata/TestWriteConfigFiles/test_namespace/dropin.golden
+++ b/lib/autoupdate/agent/testdata/TestWriteConfigFiles/test_namespace/dropin.golden
@@ -1,5 +1,6 @@
-# teleport-update
+# teleport-update version
# DO NOT EDIT THIS FILE
+
[Service]
Environment="TELEPORT_UPDATE_CONFIG_FILE=/opt/teleport/test/update.yaml"
Environment="TELEPORT_UPDATE_INSTALL_DIR=/opt/teleport"
diff --git a/lib/autoupdate/agent/testdata/TestWriteConfigFiles/test_namespace/service.golden b/lib/autoupdate/agent/testdata/TestWriteConfigFiles/test_namespace/service.golden
index f698deec24bb9..2f8b0f13d68a8 100644
--- a/lib/autoupdate/agent/testdata/TestWriteConfigFiles/test_namespace/service.golden
+++ b/lib/autoupdate/agent/testdata/TestWriteConfigFiles/test_namespace/service.golden
@@ -1,5 +1,6 @@
-# teleport-update
+# teleport-update version
# DO NOT EDIT THIS FILE
+
[Unit]
Description=Teleport auto-update service
diff --git a/lib/autoupdate/agent/testdata/TestWriteConfigFiles/test_namespace/timer.golden b/lib/autoupdate/agent/testdata/TestWriteConfigFiles/test_namespace/timer.golden
index f57a3c08055bc..38f8e37a02c72 100644
--- a/lib/autoupdate/agent/testdata/TestWriteConfigFiles/test_namespace/timer.golden
+++ b/lib/autoupdate/agent/testdata/TestWriteConfigFiles/test_namespace/timer.golden
@@ -1,5 +1,6 @@
-# teleport-update
+# teleport-update version
# DO NOT EDIT THIS FILE
+
[Unit]
Description=Teleport auto-update timer unit
diff --git a/lib/autoupdate/agent/testdata/TestWriteTeleportService/FIPS.golden b/lib/autoupdate/agent/testdata/TestWriteTeleportService/FIPS.golden
new file mode 100644
index 0000000000000..fead0e35cf34f
--- /dev/null
+++ b/lib/autoupdate/agent/testdata/TestWriteTeleportService/FIPS.golden
@@ -0,0 +1,20 @@
+# teleport-update version_ent_fips
+# DO NOT EDIT THIS FILE
+
+[Unit]
+Description=Teleport Service
+After=network.target
+
+[Service]
+Type=simple
+Restart=always
+RestartSec=5
+EnvironmentFile=-/etc/default/teleport
+ExecStart=/usr/local/bin/teleport start --fips --config /etc/teleport.yaml --pid-file=/var/run/teleport.pid
+# systemd before 239 needs an absolute path
+ExecReload=/bin/sh -c "exec pkill -HUP -L -F /var/run/teleport.pid"
+PIDFile=/var/run/teleport.pid
+LimitNOFILE=524288
+
+[Install]
+WantedBy=multi-user.target
diff --git a/lib/autoupdate/agent/testdata/TestWriteTeleportService/custom.golden b/lib/autoupdate/agent/testdata/TestWriteTeleportService/custom.golden
new file mode 100644
index 0000000000000..a7ee6334eb88a
--- /dev/null
+++ b/lib/autoupdate/agent/testdata/TestWriteTeleportService/custom.golden
@@ -0,0 +1,20 @@
+# teleport-update version
+# DO NOT EDIT THIS FILE
+
+[Unit]
+Description=Teleport Service
+After=network.target
+
+[Service]
+Type=simple
+Restart=always
+RestartSec=5
+EnvironmentFile=-/etc/default/teleport
+ExecStart=/some/path/bin/teleport start --config /some/path/teleport.yaml --pid-file=/some/path/teleport.pid
+# systemd before 239 needs an absolute path
+ExecReload=/bin/sh -c "exec pkill -HUP -L -F /some/path/teleport.pid"
+PIDFile=/some/path/teleport.pid
+LimitNOFILE=524288
+
+[Install]
+WantedBy=multi-user.target
diff --git a/lib/autoupdate/agent/testdata/TestWriteTeleportService/default.golden b/lib/autoupdate/agent/testdata/TestWriteTeleportService/default.golden
new file mode 100644
index 0000000000000..a1db5d008ddd6
--- /dev/null
+++ b/lib/autoupdate/agent/testdata/TestWriteTeleportService/default.golden
@@ -0,0 +1,20 @@
+# teleport-update version
+# DO NOT EDIT THIS FILE
+
+[Unit]
+Description=Teleport Service
+After=network.target
+
+[Service]
+Type=simple
+Restart=always
+RestartSec=5
+EnvironmentFile=-/etc/default/teleport
+ExecStart=/usr/local/bin/teleport start --config /etc/teleport.yaml --pid-file=/var/run/teleport.pid
+# systemd before 239 needs an absolute path
+ExecReload=/bin/sh -c "exec pkill -HUP -L -F /var/run/teleport.pid"
+PIDFile=/var/run/teleport.pid
+LimitNOFILE=524288
+
+[Install]
+WantedBy=multi-user.target
diff --git a/lib/autoupdate/agent/updater.go b/lib/autoupdate/agent/updater.go
index 6b63482b3210c..2ab1e141befb5 100644
--- a/lib/autoupdate/agent/updater.go
+++ b/lib/autoupdate/agent/updater.go
@@ -35,6 +35,7 @@ import (
"path/filepath"
"runtime"
"slices"
+ "strings"
"time"
"github.com/google/uuid"
@@ -52,6 +53,11 @@ import (
const (
// BinaryName specifies the name of the updater binary.
BinaryName = "teleport-update"
+
+ // SetupVersionEnvVar specifies the Teleport version.
+ SetupVersionEnvVar = "TELEPORT_UPDATE_SETUP_VERSION"
+ // SetupFlagsEnvVar specifies Teleport version flags.
+ SetupFlagsEnvVar = "TELEPORT_UPDATE_SETUP_FLAGS"
)
const (
@@ -131,15 +137,21 @@ func NewLocalUpdater(cfg LocalUpdaterConfig, ns *Namespace) (*Updater, error) {
DefaultProxyAddr: ns.defaultProxyAddr,
DefaultPathDir: ns.defaultPathDir,
Installer: &LocalInstaller{
- InstallDir: filepath.Join(ns.Dir(), versionsDirName),
- TargetServiceFile: ns.serviceFile,
+ InstallDir: filepath.Join(ns.Dir(), versionsDirName),
+ TargetServices: []ServiceFile{
+ {
+ Path: ns.serviceFile,
+ Binary: "teleport",
+ ExampleName: serviceName,
+ ExampleFunc: ns.ReplaceTeleportService,
+ },
+ },
SystemBinDir: filepath.Join(cfg.SystemDir, "bin"),
- SystemServiceFile: filepath.Join(cfg.SystemDir, serviceDir, serviceName),
+ SystemServiceDir: filepath.Join(cfg.SystemDir, serviceDir),
HTTP: client,
Log: cfg.Log,
ReservedFreeTmpDisk: reservedFreeDisk,
ReservedFreeInstallDisk: reservedFreeDisk,
- TransformService: ns.ReplaceTeleportService,
ValidateBinary: validator.IsBinary,
Template: autoupdate.DefaultCDNURITemplate,
},
@@ -149,11 +161,14 @@ func NewLocalUpdater(cfg LocalUpdaterConfig, ns *Namespace) (*Updater, error) {
Ready: debugClient,
Log: cfg.Log,
},
- ReexecSetup: func(ctx context.Context, pathDir string, reload bool) error {
+ WriteTeleportService: ns.WriteTeleportService,
+ ReexecSetup: func(ctx context.Context, pathDir string, rev Revision, reload bool) error {
name := filepath.Join(pathDir, BinaryName)
if cfg.SelfSetup && runtime.GOOS == constants.LinuxOS {
name = "/proc/self/exe"
}
+ // New arguments must never be added here, as older versions of the
+ // updater may be invoked by this logic.
args := []string{
"--install-dir", ns.installDir,
"--install-suffix", ns.name,
@@ -169,6 +184,10 @@ func NewLocalUpdater(cfg LocalUpdaterConfig, ns *Namespace) (*Updater, error) {
cmd := exec.CommandContext(ctx, name, args...)
cmd.Stderr = os.Stderr
cmd.Stdout = os.Stdout
+ cmd.Env = append(slices.Clone(os.Environ()),
+ SetupVersionEnvVar+"="+rev.Version,
+ SetupFlagsEnvVar+"="+strings.Join(rev.Flags.Strings(), "\n"),
+ )
cfg.Log.InfoContext(ctx, "Executing new teleport-update binary to update configuration.")
defer cfg.Log.InfoContext(ctx, "Finished executing new teleport-update binary.")
return trace.Wrap(cmd.Run())
@@ -229,11 +248,14 @@ type Updater struct {
Installer Installer
// Process manages a running instance of Teleport.
Process Process
+ // WriteTeleportService writes the teleport systemd service for the version of Teleport
+ // matching the currently running updater.
+ WriteTeleportService func(ctx context.Context, path string, rev Revision) error
// ReexecSetup re-execs teleport-update with the setup command.
// This configures the updater service, verifies the installation, and optionally reloads Teleport.
- ReexecSetup func(ctx context.Context, path string, reload bool) error
+ ReexecSetup func(ctx context.Context, path string, rev Revision, reload bool) error
// SetupNamespace configures the Teleport updater service for the current Namespace.
- SetupNamespace func(ctx context.Context, path string) error
+ SetupNamespace func(ctx context.Context, path string, rev Revision) error
// TeardownNamespace removes all traces of the updater service in the current Namespace, including Teleport.
TeardownNamespace func(ctx context.Context) error
// LogConfigWarnings logs warnings related to the configuration Namespace.
@@ -1003,38 +1025,18 @@ func (u *Updater) update(ctx context.Context, cfg *UpdateConfig, target Revision
u.Log.ErrorContext(ctx, "Failed to revert Teleport symlinks. Installation likely broken.")
return false
}
- if err := u.SetupNamespace(ctx, cfg.Spec.Path); err != nil {
+ // Note: this version may be inaccurate if the active installation was modified
+ if err := u.SetupNamespace(ctx, cfg.Spec.Path, cfg.Status.Active); err != nil {
u.Log.ErrorContext(ctx, "Failed to revert configuration after failed restart.", errorKey, err)
return false
}
return true
}
- if cfg.Status.Active != target {
- err := u.ReexecSetup(ctx, cfg.Spec.Path, true)
- if errors.Is(err, context.Canceled) {
- return trace.Errorf("check canceled")
- }
- if err != nil {
- // If reloading Teleport at the new version fails, revert and reload.
- u.Log.ErrorContext(ctx, "Reverting symlinks due to failed restart.")
- if ok := revertConfig(ctx); ok {
- if err := u.Process.Reload(ctx); err != nil && !errors.Is(err, ErrNotNeeded) {
- u.Log.ErrorContext(ctx, "Failed to reload Teleport after reverting. Installation likely broken.", errorKey, err)
- } else {
- u.Log.WarnContext(ctx, "Teleport updater detected an error with the new installation and successfully reverted it.")
- }
- }
- return trace.Wrap(err, "failed to start new version %s of Teleport", target)
- }
- u.Log.InfoContext(ctx, "Target version successfully installed.", targetKey, target)
+ // If re-linking the same version, do not attempt to restart services.
- if r := cfg.Status.Active; r.Version != "" {
- cfg.Status.Backup = toPtr(r)
- }
- cfg.Status.Active = target
- } else {
- err := u.ReexecSetup(ctx, cfg.Spec.Path, false)
+ if cfg.Status.Active == target {
+ err := u.ReexecSetup(ctx, cfg.Spec.Path, target, false)
if errors.Is(err, context.Canceled) {
return trace.Errorf("check canceled")
}
@@ -1047,10 +1049,36 @@ func (u *Updater) update(ctx context.Context, cfg *UpdateConfig, target Revision
return trace.Wrap(err, "failed to validate new version %s of Teleport", target)
}
u.Log.InfoContext(ctx, "Target version successfully validated.", targetKey, target)
+ u.cleanup(ctx, cfg, []Revision{
+ target, active, backup,
+ })
+ return nil
}
- if r := deref(cfg.Status.Backup); r.Version != "" {
- u.Log.InfoContext(ctx, "Backup version set.", backupKey, r)
+
+ // If a new version was linked, restart services (including on revert).
+
+ err = u.ReexecSetup(ctx, cfg.Spec.Path, target, true)
+ if errors.Is(err, context.Canceled) {
+ return trace.Errorf("check canceled")
+ }
+ if err != nil {
+ // If reloading Teleport at the new version fails, revert and reload.
+ u.Log.ErrorContext(ctx, "Reverting symlinks due to failed restart.")
+ if ok := revertConfig(ctx); ok {
+ if err := u.Process.Reload(ctx); err != nil && !errors.Is(err, ErrNotNeeded) {
+ u.Log.ErrorContext(ctx, "Failed to reload Teleport after reverting. Installation likely broken.", errorKey, err)
+ } else {
+ u.Log.WarnContext(ctx, "Teleport updater detected an error with the new installation and successfully reverted it.")
+ }
+ }
+ return trace.Wrap(err, "failed to start new version %s of Teleport", target)
+ }
+ u.Log.InfoContext(ctx, "Target version successfully installed.", targetKey, target)
+
+ if r := cfg.Status.Active; r.Version != "" {
+ cfg.Status.Backup = toPtr(r)
}
+ cfg.Status.Active = target
u.cleanup(ctx, cfg, []Revision{
target, active, backup,
})
@@ -1060,10 +1088,17 @@ func (u *Updater) update(ctx context.Context, cfg *UpdateConfig, target Revision
// Setup writes updater configuration and verifies the Teleport installation.
// If restart is true, Setup also restarts Teleport.
// Setup is safe to run concurrently with other Updater commands.
-func (u *Updater) Setup(ctx context.Context, path string, restart bool) error {
+func (u *Updater) Setup(ctx context.Context, path string, rev Revision, restart bool) error {
+
+ // Write Teleport systemd service.
+
+ if err := u.WriteTeleportService(ctx, path, rev); err != nil {
+ return trace.Wrap(err, "failed to write teleport systemd service")
+ }
+
// Setup teleport-updater configuration and sync systemd.
- err := u.SetupNamespace(ctx, path)
+ err := u.SetupNamespace(ctx, path, rev)
if errors.Is(err, context.Canceled) {
return trace.Errorf("sync canceled")
}
@@ -1071,6 +1106,8 @@ func (u *Updater) Setup(ctx context.Context, path string, restart bool) error {
return trace.Wrap(err, "failed to setup updater")
}
+ // Validations
+
present, err := u.Process.IsPresent(ctx)
if errors.Is(err, context.Canceled) {
return trace.Errorf("config check canceled")
@@ -1142,6 +1179,9 @@ func (u *Updater) notices(ctx context.Context) {
// cleanup orphan installations
func (u *Updater) cleanup(ctx context.Context, cfg *UpdateConfig, keep []Revision) {
+ if r := deref(cfg.Status.Backup); r.Version != "" {
+ u.Log.InfoContext(ctx, "Backup version set.", backupKey, r)
+ }
revs, err := u.Installer.List(ctx)
if err != nil {
u.Log.ErrorContext(ctx, "Failed to read installed versions.", errorKey, err)
diff --git a/lib/autoupdate/agent/updater_test.go b/lib/autoupdate/agent/updater_test.go
index f54a230663cef..008987603749c 100644
--- a/lib/autoupdate/agent/updater_test.go
+++ b/lib/autoupdate/agent/updater_test.go
@@ -840,12 +840,12 @@ func TestUpdater_Update(t *testing.T) {
},
}
var restarted bool
- updater.ReexecSetup = func(_ context.Context, path string, reload bool) error {
+ updater.ReexecSetup = func(_ context.Context, path string, rev Revision, reload bool) error {
restarted = reload
setupCalls++
return tt.setupErr
}
- updater.SetupNamespace = func(_ context.Context, path string) error {
+ updater.SetupNamespace = func(_ context.Context, path string, rev Revision) error {
revertSetupCalls++
return nil
}
@@ -1793,12 +1793,12 @@ func TestUpdater_Install(t *testing.T) {
},
}
var restarted bool
- updater.ReexecSetup = func(_ context.Context, path string, reload bool) error {
+ updater.ReexecSetup = func(_ context.Context, path string, rev Revision, reload bool) error {
setupCalls++
restarted = reload
return tt.setupErr
}
- updater.SetupNamespace = func(_ context.Context, path string) error {
+ updater.SetupNamespace = func(_ context.Context, path string, rev Revision) error {
revertSetupCalls++
return nil
}
@@ -1982,13 +1982,18 @@ func TestUpdater_Setup(t *testing.T) {
return tt.present, tt.presentErr
},
}
- updater.SetupNamespace = func(_ context.Context, path string) error {
+ updater.SetupNamespace = func(_ context.Context, path string, rev Revision) error {
require.Equal(t, "test", path)
return tt.setupErr
}
+ updater.WriteTeleportService = func(_ context.Context, path string, rev Revision) error {
+ require.Equal(t, "test", path)
+ require.Equal(t, "version", rev.Version)
+ return tt.setupErr
+ }
ctx := context.Background()
- err = updater.Setup(ctx, "test", tt.restart)
+ err = updater.Setup(ctx, "test", Revision{Version: "version"}, tt.restart)
if tt.errMatch != "" {
require.Error(t, err)
assert.Contains(t, err.Error(), tt.errMatch)
diff --git a/lib/autoupdate/agent/validate.go b/lib/autoupdate/agent/validate.go
index 8ce1732d3d5d1..e2dec446d8e50 100644
--- a/lib/autoupdate/agent/validate.go
+++ b/lib/autoupdate/agent/validate.go
@@ -117,7 +117,7 @@ func isTextScript(data []byte) bool {
return true
}
-// readFileLimit the first n bytes of a file.
+// readFileLimit the first n bytes of a file, or less if shorter.
func readFileLimit(name string, n int64) ([]byte, error) {
f, err := os.Open(name)
if err != nil {
diff --git a/lib/config/systemd.go b/lib/config/systemd/systemd.go
similarity index 65%
rename from lib/config/systemd.go
rename to lib/config/systemd/systemd.go
index 1418e73272227..010318d0ce5f6 100644
--- a/lib/config/systemd.go
+++ b/lib/config/systemd/systemd.go
@@ -16,7 +16,7 @@
* along with this program. If not, see .
*/
-package config
+package systemd
import (
"io"
@@ -29,16 +29,16 @@ import (
)
const (
- // SystemdDefaultEnvironmentFile is the default path to the env file for the systemd unit file config
- SystemdDefaultEnvironmentFile = "/etc/default/teleport"
- // SystemdDefaultPIDFile is the default path to the PID file for the systemd unit file config
- SystemdDefaultPIDFile = "/run/teleport.pid"
- // SystemdDefaultFileDescriptorLimit is the default max number of open file descriptors for the systemd unit file config
- SystemdDefaultFileDescriptorLimit = 524288
+ // DefaultEnvironmentFile is the default path to the env file for the systemd unit file config
+ DefaultEnvironmentFile = "/etc/default/teleport"
+ // DefaultPIDFile is the default path to the PID file for the systemd unit file config
+ DefaultPIDFile = "/run/teleport.pid"
+ // DefaultFileDescriptorLimit is the default max number of open file descriptors for the systemd unit file config
+ DefaultFileDescriptorLimit = 524288
)
-// systemdUnitFileTemplate is the systemd unit file configuration template.
-var systemdUnitFileTemplate = template.Must(template.New("").Parse(`[Unit]
+// unitFileTemplate is the systemd unit file configuration template.
+var unitFileTemplate = template.Must(template.New("").Parse(`[Unit]
Description=Teleport Service
After=network.target
@@ -47,7 +47,7 @@ Type=simple
Restart=always
RestartSec=5
EnvironmentFile=-{{ .EnvironmentFile }}
-ExecStart={{ .TeleportInstallationFile }} start --config {{ .TeleportConfigPath }} --pid-file={{ .PIDFile }}
+ExecStart={{ .TeleportInstallationFile }} start {{ if .FIPS }}--fips {{ end }}--config {{ .TeleportConfigPath }} --pid-file={{ .PIDFile }}
# systemd before 239 needs an absolute path
ExecReload=/bin/sh -c "exec pkill -HUP -L -F {{ .PIDFile }}"
PIDFile={{ .PIDFile }}
@@ -57,8 +57,8 @@ LimitNOFILE={{ .FileDescriptorLimit }}
WantedBy=multi-user.target
`))
-// SystemdFlags specifies configuration parameters for a systemd unit file.
-type SystemdFlags struct {
+// Flags specifies configuration parameters for a systemd unit file.
+type Flags struct {
// EnvironmentFile is the environment file path provided by the user.
EnvironmentFile string
// PIDFile is the process ID (PID) file path provided by the user.
@@ -69,10 +69,12 @@ type SystemdFlags struct {
TeleportInstallationFile string
// TeleportConfigPath is the path to the teleport config file (as set by Teleport defaults)
TeleportConfigPath string
+ // FIPS configures teleport to run in a FIPS compliant mode.
+ FIPS bool
}
// CheckAndSetDefaults checks and sets default values for the flags.
-func (f *SystemdFlags) CheckAndSetDefaults() error {
+func (f *Flags) CheckAndSetDefaults() error {
if f.TeleportInstallationFile == "" {
teleportPath, err := os.Readlink("/proc/self/exe")
if err != nil {
@@ -81,18 +83,19 @@ func (f *SystemdFlags) CheckAndSetDefaults() error {
f.TeleportInstallationFile = teleportPath
}
// set Teleport config path to the default
- f.TeleportConfigPath = defaults.ConfigFilePath
-
+ if f.TeleportConfigPath == "" {
+ f.TeleportConfigPath = defaults.ConfigFilePath
+ }
return nil
}
-// WriteSystemdUnitFile accepts flags and an io.Writer
+// WriteUnitFile accepts flags and an io.Writer
// and writes the systemd unit file configuration to it
-func WriteSystemdUnitFile(flags SystemdFlags, dest io.Writer) error {
+func WriteUnitFile(flags Flags, dest io.Writer) error {
err := flags.CheckAndSetDefaults()
if err != nil {
return trace.Wrap(err)
}
- return trace.Wrap(systemdUnitFileTemplate.Execute(dest, flags))
+ return trace.Wrap(unitFileTemplate.Execute(dest, flags))
}
diff --git a/lib/config/systemd_test.go b/lib/config/systemd/systemd_test.go
similarity index 89%
rename from lib/config/systemd_test.go
rename to lib/config/systemd/systemd_test.go
index 8283a861147f1..19b38aacb57bb 100644
--- a/lib/config/systemd_test.go
+++ b/lib/config/systemd/systemd_test.go
@@ -16,7 +16,7 @@
* along with this program. If not, see .
*/
-package config
+package systemd
import (
"bytes"
@@ -27,16 +27,17 @@ import (
"github.com/gravitational/teleport/lib/utils/testutils/golden"
)
-func TestWriteSystemdUnitFile(t *testing.T) {
- flags := SystemdFlags{
+func TestWriteUnitFile(t *testing.T) {
+ flags := Flags{
EnvironmentFile: "/custom/env/dir/teleport",
PIDFile: "/custom/pid/dir/teleport.pid",
FileDescriptorLimit: 16384,
TeleportInstallationFile: "/custom/install/dir/teleport",
+ FIPS: true,
}
stdout := new(bytes.Buffer)
- err := WriteSystemdUnitFile(flags, stdout)
+ err := WriteUnitFile(flags, stdout)
require.NoError(t, err)
data := stdout.Bytes()
if golden.ShouldSet() {
diff --git a/lib/config/testdata/TestWriteSystemdUnitFile.golden b/lib/config/systemd/testdata/TestWriteUnitFile.golden
similarity index 75%
rename from lib/config/testdata/TestWriteSystemdUnitFile.golden
rename to lib/config/systemd/testdata/TestWriteUnitFile.golden
index 1d3283771b57d..743fa77d0628b 100644
--- a/lib/config/testdata/TestWriteSystemdUnitFile.golden
+++ b/lib/config/systemd/testdata/TestWriteUnitFile.golden
@@ -7,7 +7,7 @@ Type=simple
Restart=always
RestartSec=5
EnvironmentFile=-/custom/env/dir/teleport
-ExecStart=/custom/install/dir/teleport start --config /etc/teleport.yaml --pid-file=/custom/pid/dir/teleport.pid
+ExecStart=/custom/install/dir/teleport start --fips --config /etc/teleport.yaml --pid-file=/custom/pid/dir/teleport.pid
# systemd before 239 needs an absolute path
ExecReload=/bin/sh -c "exec pkill -HUP -L -F /custom/pid/dir/teleport.pid"
PIDFile=/custom/pid/dir/teleport.pid
diff --git a/tool/teleport-update/main.go b/tool/teleport-update/main.go
index 8e6890a5baf2a..039e5b826e219 100644
--- a/tool/teleport-update/main.go
+++ b/tool/teleport-update/main.go
@@ -52,8 +52,10 @@ const (
proxyServerEnvVar = "TELEPORT_PROXY"
// updateGroupEnvVar allows the update group to be specified via env var.
updateGroupEnvVar = "TELEPORT_UPDATE_GROUP"
- // updateVersionEnvVar forces the version to specified value.
+ // updateVersionEnvVar specifies the Teleport version.
updateVersionEnvVar = "TELEPORT_UPDATE_VERSION"
+ // updateFlagsEnvVar specifies Teleport version flags.
+ updateFlagsEnvVar = "TELEPORT_UPDATE_FLAGS"
// updateLockTimeout is the duration commands will wait for update to complete before failing.
updateLockTimeout = 10 * time.Minute
@@ -130,7 +132,7 @@ func Run(args []string) int {
enableCmd.Flag("force-version", "Force the provided version instead of using the version provided by the Teleport cluster.").
Hidden().Short('f').Envar(updateVersionEnvVar).StringVar(&ccfg.ForceVersion)
enableCmd.Flag("force-flag", "Force the provided version flags instead of using the version flags provided by the Teleport cluster.").
- Hidden().StringsVar(&ccfg.ForceFlags)
+ Hidden().Envar(updateFlagsEnvVar).StringsVar(&ccfg.ForceFlags)
enableCmd.Flag("self-setup", "Use the current teleport-update binary to create systemd service config for managed updates.").
Hidden().BoolVar(&ccfg.SelfSetup)
enableCmd.Flag("path", "Directory to link the active Teleport installation's binaries into.").
@@ -152,7 +154,7 @@ func Run(args []string) int {
pinCmd.Flag("force-version", "Force the provided version instead of using the version provided by the Teleport cluster.").
Short('f').Envar(updateVersionEnvVar).StringVar(&ccfg.ForceVersion)
pinCmd.Flag("force-flag", "Force the provided version flags instead of using the version flags provided by the Teleport cluster.").
- Hidden().StringsVar(&ccfg.ForceFlags)
+ Hidden().Envar(updateFlagsEnvVar).StringsVar(&ccfg.ForceFlags)
pinCmd.Flag("self-setup", "Use the current teleport-update binary to create systemd service config for managed updates.").
Hidden().BoolVar(&ccfg.SelfSetup)
pinCmd.Flag("path", "Directory to link the active Teleport installation's binaries into.").
@@ -169,12 +171,18 @@ func Run(args []string) int {
linkCmd := app.Command("link-package", "Link the system installation of Teleport from the Teleport package, if managed updates is disabled.")
unlinkCmd := app.Command("unlink-package", "Unlink the system installation of Teleport from the Teleport package.")
+ // setupCmd is invoked by other versions of the updater, so this contract must be stable.
+ // New flags may be added, but they may only be passed via env vars when setup is invoked via the updater.
setupCmd := app.Command("setup", "Write configuration files that run the update subcommand on a timer and verify the Teleport installation.").
Hidden()
setupCmd.Flag("reload", "Reload the Teleport agent. If not set, Teleport is not reloaded or restarted.").
BoolVar(&ccfg.Reload)
setupCmd.Flag("path", "Directory that the active Teleport installation's binaries are linked into.").
Required().StringVar(&ccfg.Path)
+ setupCmd.Flag("version", "Use the provided version to generate configuration files.").
+ Envar(autoupdate.SetupVersionEnvVar).StringVar(&ccfg.ForceVersion)
+ setupCmd.Flag("flag", "Use the provided flags to generate configuration files.").
+ Envar(autoupdate.SetupFlagsEnvVar).StringsVar(&ccfg.ForceFlags)
statusCmd := app.Command("status", "Show Teleport agent auto-update status.")
statusCmd.Flag("err-if-should-update-now",
@@ -468,7 +476,9 @@ func cmdSetup(ctx context.Context, ccfg *cliConfig) error {
if err != nil {
return trace.Wrap(err)
}
- err = updater.Setup(ctx, ccfg.Path, ccfg.Reload)
+ flags := common.NewInstallFlagsFromStrings(ccfg.ForceFlags)
+ rev := autoupdate.NewRevision(ccfg.ForceVersion, flags)
+ err = updater.Setup(ctx, ccfg.Path, rev, ccfg.Reload)
if err != nil {
return trace.Wrap(err)
}
diff --git a/tool/teleport/common/configurator.go b/tool/teleport/common/configurator.go
index 887f330620d5e..32493f03e390d 100644
--- a/tool/teleport/common/configurator.go
+++ b/tool/teleport/common/configurator.go
@@ -31,6 +31,7 @@ import (
"github.com/gravitational/teleport/api/types"
"github.com/gravitational/teleport/api/utils/prompt"
"github.com/gravitational/teleport/lib/config"
+ "github.com/gravitational/teleport/lib/config/systemd"
"github.com/gravitational/teleport/lib/configurators"
awsconfigurators "github.com/gravitational/teleport/lib/configurators/aws"
"github.com/gravitational/teleport/lib/configurators/configuratorbuilder"
@@ -52,7 +53,7 @@ var awsDatabaseTypes = []string{
}
type installSystemdFlags struct {
- config.SystemdFlags
+ systemd.Flags
// output is the destination to write the systemd unit file to.
output string
}
@@ -76,7 +77,7 @@ func onDumpSystemdUnitFile(flags installSystemdFlags) error {
}
buf := new(bytes.Buffer)
- err := config.WriteSystemdUnitFile(flags.SystemdFlags, buf)
+ err := systemd.WriteUnitFile(flags.Flags, buf)
if err != nil {
return trace.Wrap(err)
}
diff --git a/tool/teleport/common/teleport.go b/tool/teleport/common/teleport.go
index 8f6c8b056208c..53d4140c98949 100644
--- a/tool/teleport/common/teleport.go
+++ b/tool/teleport/common/teleport.go
@@ -43,6 +43,7 @@ import (
debugclient "github.com/gravitational/teleport/lib/client/debug"
awslib "github.com/gravitational/teleport/lib/cloud/aws"
"github.com/gravitational/teleport/lib/config"
+ "github.com/gravitational/teleport/lib/config/systemd"
"github.com/gravitational/teleport/lib/configurators"
awsconfigurators "github.com/gravitational/teleport/lib/configurators/aws"
"github.com/gravitational/teleport/lib/defaults"
@@ -379,9 +380,9 @@ func Run(options Options) (app *kingpin.Application, executedCommand string, con
// "teleport install" command and its subcommands
installCmd := app.Command("install", "Teleport install commands.")
systemdInstall := installCmd.Command("systemd", "Creates a systemd unit file configuration.")
- systemdInstall.Flag("env-file", "Full path to the environment file.").Default(config.SystemdDefaultEnvironmentFile).StringVar(&systemdInstallFlags.EnvironmentFile)
- systemdInstall.Flag("pid-file", "Full path to the PID file.").Default(config.SystemdDefaultPIDFile).StringVar(&systemdInstallFlags.PIDFile)
- systemdInstall.Flag("fd-limit", "Maximum number of open file descriptors.").Default(fmt.Sprintf("%v", config.SystemdDefaultFileDescriptorLimit)).IntVar(&systemdInstallFlags.FileDescriptorLimit)
+ systemdInstall.Flag("env-file", "Full path to the environment file.").Default(systemd.DefaultEnvironmentFile).StringVar(&systemdInstallFlags.EnvironmentFile)
+ systemdInstall.Flag("pid-file", "Full path to the PID file.").Default(systemd.DefaultPIDFile).StringVar(&systemdInstallFlags.PIDFile)
+ systemdInstall.Flag("fd-limit", "Maximum number of open file descriptors.").Default(fmt.Sprintf("%v", systemd.DefaultFileDescriptorLimit)).IntVar(&systemdInstallFlags.FileDescriptorLimit)
systemdInstall.Flag("teleport-path", "Full path to the Teleport binary.").StringVar(&systemdInstallFlags.TeleportInstallationFile)
systemdInstall.Flag("output", `Write to stdout with "--output=stdout" or custom path with --output=file:///path`).Short('o').Default(teleport.SchemeStdout).StringVar(&systemdInstallFlags.output)
systemdInstall.Alias(systemdInstallExamples) // We're using "alias" section to display usage examples.