Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[teleport-update] Add systemd setup #49174

Merged
merged 4 commits into from
Nov 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 116 additions & 0 deletions lib/autoupdate/agent/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
/*
* Teleport
* Copyright (C) 2024 Gravitational, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

package agent

import (
"context"
"log/slog"
"os"
"path/filepath"
"text/template"

"github.com/google/renameio/v2"
"github.com/gravitational/trace"
)

const (
updateServiceTemplate = `# teleport-update
[Unit]
Description=Teleport auto-update service

[Service]
Type=oneshot
ExecStart={{.LinkDir}}/bin/teleport-update update
`
updateTimerTemplate = `# teleport-update
[Unit]
Description=Teleport auto-update timer unit

[Timer]
OnActiveSec=1m
OnUnitActiveSec=5m
RandomizedDelaySec=1m

[Install]
WantedBy=teleport.service
`
)

// Setup installs service and timer files for the teleport-update binary.
// Afterwords, Setup reloads systemd and enables the timer with --now.
func Setup(ctx context.Context, log *slog.Logger, linkDir, dataDir string) error {
err := writeConfigFiles(linkDir, dataDir)
zmb3 marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
return trace.Errorf("failed to write teleport-update systemd config files: %w", err)
}
svc := &SystemdService{
ServiceName: "teleport-update.timer",
Log: log,
}
if err := svc.Sync(ctx); err != nil {
return trace.Errorf("failed to sync systemd config: %w", err)
}
if err := svc.Enable(ctx, true); err != nil {
return trace.Errorf("failed to enable teleport-update systemd timer: %w", err)
}
return nil
}

func writeConfigFiles(linkDir, dataDir string) error {
servicePath := filepath.Join(linkDir, serviceDir, updateServiceName)
err := writeTemplate(servicePath, updateServiceTemplate, linkDir, dataDir)
if err != nil {
return trace.Wrap(err)
}
timerPath := filepath.Join(linkDir, serviceDir, updateTimerName)
err = writeTemplate(timerPath, updateTimerTemplate, linkDir, dataDir)
if err != nil {
return trace.Wrap(err)
}
return nil
}

func writeTemplate(path, t, linkDir, dataDir string) error {
dir, file := filepath.Split(path)
if err := os.MkdirAll(dir, systemDirMode); err != nil {
return trace.Wrap(err)
}
opts := []renameio.Option{
renameio.WithPermissions(configFileMode),
renameio.WithExistingPermissions(),
}
f, err := renameio.NewPendingFile(path, opts...)
if err != nil {
return trace.Wrap(err)
}
defer f.Cleanup()

tmpl, err := template.New(file).Parse(t)
if err != nil {
return trace.Wrap(err)
}
err = tmpl.Execute(f, struct {
LinkDir string
DataDir string
}{linkDir, dataDir})
if err != nil {
return trace.Wrap(err)
}
return trace.Wrap(f.CloseAtomicallyReplace())
}
65 changes: 65 additions & 0 deletions lib/autoupdate/agent/config_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
* Teleport
* Copyright (C) 2024 Gravitational, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

package agent

import (
"bytes"
"os"
"path/filepath"
"testing"

"github.com/stretchr/testify/require"

libdefaults "github.com/gravitational/teleport/lib/defaults"
"github.com/gravitational/teleport/lib/utils/golden"
)

func TestWriteConfigFiles(t *testing.T) {
t.Parallel()
linkDir := t.TempDir()
dataDir := t.TempDir()
err := writeConfigFiles(linkDir, dataDir)
require.NoError(t, err)

for _, p := range []string{
filepath.Join(linkDir, serviceDir, updateServiceName),
filepath.Join(linkDir, serviceDir, updateTimerName),
} {
t.Run(filepath.Base(p), func(t *testing.T) {
data, err := os.ReadFile(p)
require.NoError(t, err)
data = replaceValues(data, map[string]string{
DefaultLinkDir: linkDir,
libdefaults.DataDir: dataDir,
})
if golden.ShouldSet() {
golden.Set(t, data)
}
require.Equal(t, string(golden.Get(t)), string(data))
})
}
}

func replaceValues(data []byte, m map[string]string) []byte {
for k, v := range m {
data = bytes.ReplaceAll(data, []byte(v),
[]byte(k))
}
return data
}
22 changes: 9 additions & 13 deletions lib/autoupdate/agent/installer.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,15 @@ const (
systemDirMode = 0755
)

var (
const (
// serviceDir contains the relative path to the Teleport SystemD service dir.
serviceDir = filepath.Join("lib", "systemd", "system")
serviceDir = "lib/systemd/system"
// serviceName contains the name of the Teleport SystemD service file.
serviceName = "teleport.service"
// updateServiceName contains the name of the Teleport Update Systemd service
updateServiceName = "teleport-update.service"
// updateTimerName contains the name of the Teleport Update Systemd timer
updateTimerName = "teleport-update.timer"
)

// LocalInstaller manages the creation and removal of installations
Expand Down Expand Up @@ -431,7 +435,7 @@ func (li *LocalInstaller) Link(ctx context.Context, version string) (revert func
return revert, nil
}

// LinkSystem links the system (package) version into the system LinkBinDir and LinkServiceDir.
// LinkSystem links the system (package) version into LinkBinDir and LinkServiceDir.
// The revert function restores the previous linking.
// See Installer interface for additional specs.
func (li *LocalInstaller) LinkSystem(ctx context.Context) (revert func(context.Context) bool, err error) {
Expand Down Expand Up @@ -539,7 +543,7 @@ func (li *LocalInstaller) forceLinks(ctx context.Context, binDir, svcDir string)
dst := filepath.Join(li.LinkServiceDir, serviceName)
orig, err := forceCopy(dst, src, maxServiceFileSize)
if err != nil && !errors.Is(err, os.ErrExist) {
return revert, trace.Errorf("failed to create file for %s: %w", serviceName, err)
return revert, trace.Errorf("failed to write file %s: %w", serviceName, err)
}
if orig != nil {
revertFiles = append(revertFiles, *orig)
Expand Down Expand Up @@ -782,13 +786,5 @@ func (li *LocalInstaller) isLinked(versionDir string) (bool, error) {
return true, nil
}
}
linkData, err := readFileN(filepath.Join(li.LinkServiceDir, serviceName), maxServiceFileSize)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: removing this logic fixes linked-version detection via isLinked. Two versions can have the same file contents.

if err != nil {
return false, nil
}
versionData, err := readFileN(filepath.Join(versionDir, serviceDir, serviceName), maxServiceFileSize)
if err != nil {
return false, nil
}
return bytes.Equal(linkData, versionData), nil
return false, nil
}
18 changes: 18 additions & 0 deletions lib/autoupdate/agent/process.go
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,24 @@ func (s SystemdService) Sync(ctx context.Context) error {
if code != 0 {
return trace.Errorf("unable to reload systemd configuration")
}
s.Log.InfoContext(ctx, "Systemd configuration synced.", unitKey, s.ServiceName)
return nil
}

// Enable the systemd service.
func (s SystemdService) Enable(ctx context.Context, now bool) error {
if err := s.checkSystem(ctx); err != nil {
return trace.Wrap(err)
}
args := []string{"enable", s.ServiceName}
if now {
args = append(args, "--now")
}
code := s.systemctl(ctx, slog.LevelError, args...)
if code != 0 {
return trace.Errorf("unable to enable systemd service")
}
s.Log.InfoContext(ctx, "Service enabled.", unitKey, s.ServiceName)
return nil
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# teleport-update
[Unit]
Description=Teleport auto-update service

[Service]
Type=oneshot
ExecStart=/usr/local/bin/teleport-update update
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# teleport-update
[Unit]
Description=Teleport auto-update timer unit

[Timer]
OnActiveSec=1m
OnUnitActiveSec=5m
RandomizedDelaySec=1m

[Install]
WantedBy=teleport.service
Loading
Loading