1 // MsgSyncReq: install packages on the remote host (Debian)
3 // SPDX-License-Identifier: GPL-3.0-or-later
4 // Copyright (C) 2021-2024 Simon Ruderich
14 "ruderich.org/simon/safcm"
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()
25 for _, x := range s.req.Packages {
27 install = append(install, x)
30 if len(install) == 0 {
34 for _, x := range install {
35 s.resp.PackageChanges = append(s.resp.PackageChanges,
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
50 // Never remove any packages
52 // Don't perform upgrades
55 "--no-install-recommends",
56 // Don't overwrite existing config files
57 "-o", "Dpkg::Options::=--force-confdef",
58 "-o", "Dpkg::Options::=--force-confold",
60 cmd.Env = append(os.Environ(),
61 // Don't ask questions during installation
62 "DEBIAN_FRONTEND=noninteractive",
64 _, err = s.cmd.CombinedOutputCmd("packages", cmd)
72 func (s *Sync) debianInstalledPackages() (map[string]bool, error) {
73 out, _, err := s.cmd.Run("packages",
74 "/usr/bin/dpkg-query",
76 `--showformat=${Status}\t${Package}\n`,
81 lines := strings.Split(strings.TrimSpace(string(out)), "\n")
83 res := make(map[string]bool)
84 for _, line := range lines {
85 xs := strings.Split(line, "\t")
87 return nil, fmt.Errorf("invalid dpkg-query line %q",
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") {