]> ruderich.org/simon Gitweb - safcm/safcm.git/blobdiff - remote/sync/packages_debian.go
Move implementation of cmd/safcm-remote/ to remote/
[safcm/safcm.git] / remote / sync / packages_debian.go
diff --git a/remote/sync/packages_debian.go b/remote/sync/packages_debian.go
new file mode 100644 (file)
index 0000000..2e330b9
--- /dev/null
@@ -0,0 +1,108 @@
+// MsgSyncReq: install packages on the remote host (Debian)
+
+// Copyright (C) 2021  Simon Ruderich
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU 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 General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+package sync
+
+import (
+       "fmt"
+       "os"
+       "os/exec"
+       "strings"
+
+       "ruderich.org/simon/safcm"
+)
+
+func (s *Sync) syncPackagesDebian() error {
+       s.log.Debugf("packages: checking %s (debian detected)",
+               strings.Join(s.req.Packages, " "))
+       installed, err := s.debianInstalledPackages()
+       if err != nil {
+               return err
+       }
+       var install []string
+       for _, x := range s.req.Packages {
+               if !installed[x] {
+                       install = append(install, x)
+               }
+       }
+       if len(install) == 0 {
+               return nil
+       }
+
+       for _, x := range install {
+               s.resp.PackageChanges = append(s.resp.PackageChanges,
+                       safcm.PackageChange{
+                               Name: x,
+                       })
+       }
+
+       if s.req.DryRun {
+               return nil
+       }
+
+       s.log.Verbosef("packages: installing %s", strings.Join(install, " "))
+       cmd := exec.Command("/usr/bin/apt-get", append([]string{"install",
+               // Don't require further acknowledgment; this won't perform
+               // dangerous actions
+               "--assume-yes",
+               // Don't perform upgrades
+               "--no-upgrade",
+               // Nobody needs those
+               "--no-install-recommends",
+               // Don't overwrite existing config files
+               "-o", "Dpkg::Options::=--force-confdef",
+               "-o", "Dpkg::Options::=--force-confold",
+       }, install...)...)
+       cmd.Env = append(os.Environ(),
+               // Don't ask questions during installation
+               "DEBIAN_FRONTEND=noninteractive",
+       )
+       _, err = s.cmd.CombinedOutputCmd("packages", cmd)
+       if err != nil {
+               return err
+       }
+
+       return nil
+}
+
+func (s *Sync) debianInstalledPackages() (map[string]bool, error) {
+       out, _, err := s.cmd.Run("packages",
+               "/usr/bin/dpkg-query",
+               "--show",
+               `--showformat=${Status}\t${Package}\n`,
+       )
+       if err != nil {
+               return nil, err
+       }
+       lines := strings.Split(strings.TrimSpace(string(out)), "\n")
+
+       res := make(map[string]bool)
+       for _, line := range lines {
+               xs := strings.Split(line, "\t")
+               if len(xs) != 2 {
+                       return nil, fmt.Errorf("invalid dpkg-query line %q",
+                               line)
+               }
+               // We only care if the package is currently successfully
+               // installed (last two fields). If a package is on hold (first
+               // field) this is fine as well.
+               if strings.HasSuffix(xs[0], " ok installed") {
+                       res[xs[1]] = true
+               }
+       }
+       return res, nil
+}