]> ruderich.org/simon Gitweb - safcm/safcm.git/blob - remote/sync/packages_debian.go
Use SPDX license identifiers
[safcm/safcm.git] / remote / sync / packages_debian.go
1 // MsgSyncReq: install packages on the remote host (Debian)
2
3 // SPDX-License-Identifier: GPL-3.0-or-later
4 // Copyright (C) 2021-2024  Simon Ruderich
5
6 package sync
7
8 import (
9         "fmt"
10         "os"
11         "os/exec"
12         "strings"
13
14         "ruderich.org/simon/safcm"
15 )
16
17 func (s *Sync) syncPackagesDebian() error {
18         s.log.Debugf("packages: checking %s (debian detected)",
19                 strings.Join(s.req.Packages, " "))
20         installed, err := s.debianInstalledPackages()
21         if err != nil {
22                 return err
23         }
24         var install []string
25         for _, x := range s.req.Packages {
26                 if !installed[x] {
27                         install = append(install, x)
28                 }
29         }
30         if len(install) == 0 {
31                 return nil
32         }
33
34         for _, x := range install {
35                 s.resp.PackageChanges = append(s.resp.PackageChanges,
36                         safcm.PackageChange{
37                                 Name: x,
38                         })
39         }
40
41         if s.req.DryRun {
42                 return nil
43         }
44
45         s.log.Verbosef("packages: installing %s", strings.Join(install, " "))
46         cmd := exec.Command("/usr/bin/apt-get", append([]string{"install",
47                 // Don't require further acknowledgment; this won't perform
48                 // dangerous actions
49                 "--assume-yes",
50                 // Never remove any packages
51                 "--no-remove",
52                 // Don't perform upgrades
53                 "--no-upgrade",
54                 // Nobody needs those
55                 "--no-install-recommends",
56                 // Don't overwrite existing config files
57                 "-o", "Dpkg::Options::=--force-confdef",
58                 "-o", "Dpkg::Options::=--force-confold",
59         }, install...)...)
60         cmd.Env = append(os.Environ(),
61                 // Don't ask questions during installation
62                 "DEBIAN_FRONTEND=noninteractive",
63         )
64         _, err = s.cmd.CombinedOutputCmd("packages", cmd)
65         if err != nil {
66                 return err
67         }
68
69         return nil
70 }
71
72 func (s *Sync) debianInstalledPackages() (map[string]bool, error) {
73         out, _, err := s.cmd.Run("packages",
74                 "/usr/bin/dpkg-query",
75                 "--show",
76                 `--showformat=${Status}\t${Package}\n`,
77         )
78         if err != nil {
79                 return nil, err
80         }
81         lines := strings.Split(strings.TrimSpace(string(out)), "\n")
82
83         res := make(map[string]bool)
84         for _, line := range lines {
85                 xs := strings.Split(line, "\t")
86                 if len(xs) != 2 {
87                         return nil, fmt.Errorf("invalid dpkg-query line %q",
88                                 line)
89                 }
90                 // We only care if the package is currently successfully
91                 // installed (last two fields). If a package is on hold (first
92                 // field) this is fine as well.
93                 if strings.HasSuffix(xs[0], " ok installed") {
94                         res[xs[1]] = true
95                 }
96         }
97         return res, nil
98 }