--- /dev/null
+// 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: detected debian")
+
+ installed, err := s.debianInstalledPackages()
+ if err != nil {
+ return err
+ }
+
+ s.log.Debugf("packages: checking %s",
+ strings.Join(s.req.Packages, " "))
+ 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
+}