diff --git a/Makefile b/Makefile index 6196396..1e509d8 100644 --- a/Makefile +++ b/Makefile @@ -64,4 +64,24 @@ test: ## Run the tests of the project coverage: ## Run the tests of the project and export the coverage $(GOTEST) -cover -covermode=count -coverprofile=profile.cov ./... - $(GOCMD) tool cover -func profile.cov \ No newline at end of file + $(GOCMD) tool cover -func profile.cov + +install-lib: $(SUBDIRS)-install + +$(SUBDIRS)-install: + $(MAKE) -C $(@:-install=) install + +install: install-lib ## Install the binaries + install -Dm755 bin/$(BINARY_NAME) $(DESTDIR)$(PREFIX)/bin/$(BINARY_NAME) + install -Dm755 bin/$(BINARY_NAME_TOOL) $(DESTDIR)$(PREFIX)/bin/$(BINARY_NAME_TOOL) + install -Dm755 scripts/start_calaos_home.sh $(DESTDIR)$(PREFIX)/sbin/start_calaos_home.sh + install -Dm755 scripts/calaos_install.sh $(DESTDIR)$(PREFIX)/sbin/calaos_install.sh + install -Dm755 scripts/calaos_rollback.sh $(DESTDIR)$(PREFIX)/sbin/calaos_rollback.sh + install -Dm755 scripts/init_calaosfs.sh $(DESTDIR)$(PREFIX)/sbin/init_calaosfs.sh + install -Dm755 scripts/haproxy_pre.sh $(DESTDIR)$(PREFIX)/sbin/haproxy_pre.sh + install -Dm755 scripts/mosquitto_pre.sh $(DESTDIR)$(PREFIX)/sbin/mosquitto_pre.sh + install -Dm755 scripts/zigbee2mqtt_pre.sh $(DESTDIR)$(PREFIX)/sbin/zigbee2mqtt_pre.sh + install -Dm755 scripts/load_containers_cache.sh $(DESTDIR)$(PREFIX)/sbin/load_containers_cache.sh + install -Dm755 scripts/arch-chroot $(DESTDIR)$(PREFIX)/sbin/arch-chroot + install -Dm755 scripts/genfstab $(DESTDIR)$(PREFIX)/sbin/genfstab + install -Dm755 scripts/start_z2mqtt.sh $(DESTDIR)$(PREFIX)/sbin/start_z2mqtt.sh \ No newline at end of file diff --git a/app/app.go b/app/app.go index bdadb62..3161517 100644 --- a/app/app.go +++ b/app/app.go @@ -114,6 +114,18 @@ func NewApp() (a *AppServer, err error) { return a.apiUpdateImages(c) }) + api.Post("/update/upgrade-all", func(c *fiber.Ctx) error { + return a.apiUpdateUpgradeAll(c) + }) + + api.Post("/update/upgrade", func(c *fiber.Ctx) error { + return a.apiUpdateUpgrade(c) + }) + + api.Get("/update/status", func(c *fiber.Ctx) error { + return a.apiUpdateStatus(c) + }) + return } diff --git a/app/update.go b/app/update.go index e7c9518..c903608 100644 --- a/app/update.go +++ b/app/update.go @@ -35,3 +35,45 @@ func (a *AppServer) apiUpdateImages(c *fiber.Ctx) (err error) { return c.Status(fiber.StatusOK).JSON(m) } + +func (a *AppServer) apiUpdateUpgradeAll(c *fiber.Ctx) (err error) { + + err = models.UpgradeAll() + if err != nil { + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ + "error": true, + "msg": err.Error(), + }) + } + + return c.Status(fiber.StatusOK).JSON(fiber.Map{ + "msg": "Upgrade done", + }) +} + +func (a *AppServer) apiUpdateUpgrade(c *fiber.Ctx) (err error) { + + err = models.Upgrade(c.Query("package")) + if err != nil { + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ + "error": true, + "msg": err.Error(), + }) + } + + return c.Status(fiber.StatusOK).JSON(fiber.Map{ + "msg": "Upgrade done", + }) +} + +func (a *AppServer) apiUpdateStatus(c *fiber.Ctx) (err error) { + s, err := models.UpdateStatus() + if err != nil { + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ + "error": true, + "msg": err.Error(), + }) + } + + return c.Status(fiber.StatusOK).JSON(s) +} diff --git a/apt/Makefile b/apt/Makefile index 015eaed..1bfaa63 100644 --- a/apt/Makefile +++ b/apt/Makefile @@ -1,6 +1,7 @@ .PHONY: libcalaos-apt.so all: + @rm -fr build meson setup build --prefix=/usr meson compile -C build meson install -C build diff --git a/apt/apt.cpp b/apt/apt.cpp index de853a9..c1d1613 100644 --- a/apt/apt.cpp +++ b/apt/apt.cpp @@ -106,7 +106,7 @@ Pkg *aptCacheArrayGet(void *arr, int idx) PkgList *plist = reinterpret_cast(arr); if (!plist) return nullptr; - if (idx < 0 || idx > plist->size()) + if (idx < 0 || (size_t)idx > plist->size()) return nullptr; return &plist->at(idx); @@ -117,7 +117,7 @@ void aptCacheArrayFree(void *arr) PkgList *plist = reinterpret_cast(arr); if (!plist) return; - for (int i = 0;i < plist->size();i++) + for (size_t i = 0;i < plist->size();i++) { free((*plist)[i].name); free((*plist)[i].version_current); diff --git a/cmd/calaos-os/api/api.go b/cmd/calaos-os/api/api.go index 008a3db..4d21be9 100644 --- a/cmd/calaos-os/api/api.go +++ b/cmd/calaos-os/api/api.go @@ -10,7 +10,7 @@ import ( "net/url" "time" - "github.com/calaos/calaos-container/models/images" + "github.com/calaos/calaos-container/models/structs" ) const ( @@ -182,14 +182,14 @@ func (capi *CalaosApi) makeHttpCall( } // UpdateCheck forces an update check -func (capi *CalaosApi) UpdateCheck(token string) (imgs *images.ImageMap, err error) { +func (capi *CalaosApi) UpdateCheck(token string) (imgs *structs.ImageMap, err error) { _, body, err := capi.callWithToken("GET", "/api/update/check", token, nil, nil) if err != nil { return } - imgs = &images.ImageMap{} + imgs = &structs.ImageMap{} if err = json.Unmarshal(body, imgs); err != nil { return nil, fmt.Errorf("UpdateCheck failed: %v", err) } @@ -198,14 +198,14 @@ func (capi *CalaosApi) UpdateCheck(token string) (imgs *images.ImageMap, err err } // UpdateAvailable returns available updates -func (capi *CalaosApi) UpdateAvailable(token string) (imgs *images.ImageMap, err error) { +func (capi *CalaosApi) UpdateAvailable(token string) (imgs *structs.ImageMap, err error) { _, body, err := capi.callWithToken("GET", "/api/update/available", token, nil, nil) if err != nil { return } - imgs = &images.ImageMap{} + imgs = &structs.ImageMap{} if err = json.Unmarshal(body, imgs); err != nil { return nil, fmt.Errorf("UpdateAvailable failed: %v", err) } @@ -214,17 +214,59 @@ func (capi *CalaosApi) UpdateAvailable(token string) (imgs *images.ImageMap, err } // UpdateImages returns currently installed images -func (capi *CalaosApi) UpdateImages(token string) (imgs *images.ImageMap, err error) { +func (capi *CalaosApi) UpdateImages(token string) (imgs *structs.ImageMap, err error) { _, body, err := capi.callWithToken("GET", "/api/update/images", token, nil, nil) if err != nil { return } - imgs = &images.ImageMap{} + imgs = &structs.ImageMap{} if err = json.Unmarshal(body, imgs); err != nil { return nil, fmt.Errorf("UpdateImages failed: %v", err) } return } + +// UpgradePackages upgrades all packages +func (capi *CalaosApi) UpgradePackages(token string) (err error) { + + _, _, err = capi.callWithToken("POST", "/api/update/upgrade-all", token, nil, nil) + if err != nil { + return + } + + return +} + +// UpdatePackage upgrades a single package +func (capi *CalaosApi) UpdatePackage(token string, pkg string) (err error) { + + params := url.Values{ + "package": []string{pkg}, + } + + _, _, err = capi.callWithToken("POST", "/api/update/upgrade", token, params, nil) + if err != nil { + return + } + + return +} + +// UpgradeStatus returns the status of the upgrade +func (capi *CalaosApi) UpgradeStatus(token string) (status *structs.Status, err error) { + + _, body, err := capi.callWithToken("GET", "/api/update/status", token, nil, nil) + if err != nil { + return + } + + status = &structs.Status{} + if err = json.Unmarshal(body, status); err != nil { + return nil, fmt.Errorf("UpgradeStatus failed: %v", err) + } + + return +} diff --git a/cmd/calaos-os/calaos-os.go b/cmd/calaos-os/calaos-os.go index b319396..2a709fe 100644 --- a/cmd/calaos-os/calaos-os.go +++ b/cmd/calaos-os/calaos-os.go @@ -7,11 +7,13 @@ import ( "fmt" "os" "strings" + "time" "github.com/calaos/calaos-container/cmd/calaos-os/api" "github.com/fatih/color" cli "github.com/jawher/mow.cli" "github.com/jedib0t/go-pretty/v6/table" + "github.com/raoulh/go-progress" ) const ( @@ -96,7 +98,7 @@ func cmdList(cmd *cli.Cmd) { func cmdCheck(cmd *cli.Cmd) { cmd.Spec = "" cmd.Action = func() { - fmt.Printf("%s Checking for updates...\n", CharArrow) + fmt.Printf("%s Checking for updates...\n", cyan(CharArrow)) a := api.NewCalaosApi(api.CalaosCtHost) token, err := getToken() @@ -110,7 +112,7 @@ func cmdCheck(cmd *cli.Cmd) { } if len(*imgs) == 0 { - fmt.Printf("%s Already up to date.\n", CharCheck) + fmt.Printf("%s Already up to date.\n", green(CharCheck)) return } @@ -135,7 +137,64 @@ func cmdCheck(cmd *cli.Cmd) { } func cmdUpgrade(cmd *cli.Cmd) { - cmd.Spec = "" + cmd.Spec = "[PKG]" + pkg := cmd.StringArg("PKG", "", "Package to upgrade. If not specified, all packages will be updated") + cmd.Action = func() { + if (*pkg) != "" { + fmt.Printf("%s Upgrading package %s...\n", cyan(CharArrow), *pkg) + } else { + fmt.Printf("%s Upgrading all packages...\n", cyan(CharArrow)) + } + + a := api.NewCalaosApi(api.CalaosCtHost) + + token, err := getToken() + if err != nil { + exit(err, 1) + } + + if (*pkg) != "" { + err = a.UpdatePackage(token, *pkg) + } else { + err = a.UpgradePackages(token) + } + + //get status from API and wait until it returns idle + bar := progress.New(100) + bar.Format = progress.ProgressFormats[0] + bar.ShowNumeric = false + bar.ShowTextSuffix = true + + currentPkg := "" + + for { + status, err := a.UpgradeStatus(token) + if err != nil { + exit(err, 1) + } + if status.Status == "idle" { + break + } + + if currentPkg != status.CurrentPkg { + bar.SetTextSuffix(fmt.Sprintf("\t %s %s installed", green(CharCheck), status.CurrentPkg)) + bar.Set(100) + fmt.Println() + + currentPkg = status.CurrentPkg + } + + bar.SetTextSuffix(fmt.Sprintf("\t Installing %s", status.CurrentPkg)) + bar.Set(status.Progress * 100 / status.ProgressTotal) + + time.Sleep(500 * time.Millisecond) + } + + if err != nil { + exit(err, 1) + } + + fmt.Printf("%s Done.\n", green(CharCheck)) } } diff --git a/debian/calaos-container.install b/debian/calaos-container.install index 65f2832..c3d1e0b 100644 --- a/debian/calaos-container.install +++ b/debian/calaos-container.install @@ -1,17 +1,5 @@ -bin/calaos-container usr/bin -bin/calaos-os usr/bin -scripts/start_calaos_home.sh usr/bin calaos-container.toml etc env/calaos.sh etc/profile.d -scripts/calaos_install.sh usr/sbin -scripts/calaos_rollback.sh usr/sbin -scripts/init_calaosfs.sh usr/sbin -scripts/haproxy_pre.sh usr/sbin -scripts/mosquitto_pre.sh usr/sbin -scripts/zigbee2mqtt_pre.sh usr/sbin -scripts/load_containers_cache.sh usr/sbin -scripts/arch-chroot usr/sbin -scripts/genfstab usr/sbin initramfs/hooks/grub-btrfs-overlay usr/share/initramfs-tools/hooks/grub-btrfs-overlay initramfs/scripts/local-bottom/grub-btrfs-overlay usr/share/initramfs-tools/scripts/local-bottom/grub-btrfs-overlay grub-btrfs/41_snapshots-btrfs etc/grub.d @@ -20,5 +8,4 @@ grub-btrfs/grub-btrfsd usr/bin repository/calaos.gpg etc/apt/trusted.gpg.d repository/calaos.list etc/apt/sources.list.d debian/btrfs-scrub@.service lib/systemd/system -debian/btrfs-scrub@.timer lib/systemd/system -scripts/start_z2mqtt.sh usr/bin \ No newline at end of file +debian/btrfs-scrub@.timer lib/systemd/system \ No newline at end of file diff --git a/debian/calaos-home.service b/debian/calaos-home.service index 7389c98..469488f 100644 --- a/debian/calaos-home.service +++ b/debian/calaos-home.service @@ -18,7 +18,7 @@ Type=notify NotifyAccess=all SyslogIdentifier=%N -ExecStart=/usr/bin/start_calaos_home.sh %t/%N.cid +ExecStart=/usr/sbin/start_calaos_home.sh %t/%N.cid RestartSec=2 diff --git a/debian/zigbee2mqtt.service b/debian/zigbee2mqtt.service index 409fcc2..6c48793 100644 --- a/debian/zigbee2mqtt.service +++ b/debian/zigbee2mqtt.service @@ -18,7 +18,7 @@ Delegate=yes Type=notify NotifyAccess=all SyslogIdentifier=%N -ExecStart=/usr/bin/start_z2mqtt.sh %t/%N.cid +ExecStart=/usr/sbin/start_z2mqtt.sh %t/%N.cid [Install] WantedBy=multi-user.target diff --git a/go.mod b/go.mod index 5575560..136caea 100644 --- a/go.mod +++ b/go.mod @@ -2,15 +2,17 @@ module github.com/calaos/calaos-container go 1.20 -require github.com/containers/podman/v4 v4.5.1 +require ( + github.com/containers/podman/v4 v4.5.1 + github.com/gofiber/fiber/v2 v2.47.0 + github.com/jawher/mow.cli v1.2.0 + github.com/knadh/koanf v1.5.0 +) require ( github.com/andybalholm/brotli v1.0.5 // indirect - github.com/gofiber/fiber/v2 v2.47.0 // indirect github.com/gofiber/utils v0.0.10 // indirect github.com/gorilla/schema v1.2.0 // indirect - github.com/jawher/mow.cli v1.2.0 // indirect - github.com/knadh/koanf v1.5.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.19 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect @@ -23,6 +25,7 @@ require ( github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasthttp v1.47.0 // indirect github.com/valyala/tcplisten v1.0.0 // indirect + modernc.org/libc v1.30.0 // indirect ) require ( @@ -46,7 +49,7 @@ require ( github.com/containers/ocicrypt v1.1.7 // indirect github.com/containers/psgo v1.8.0 // indirect github.com/containers/storage v1.46.1 // indirect - github.com/coreos/go-systemd/v22 v22.5.0 // indirect + github.com/coreos/go-systemd/v22 v22.5.0 github.com/cyberphone/json-canonicalization v0.0.0-20220623050100-57a0ce2678a7 // indirect github.com/cyphar/filepath-securejoin v0.2.3 // indirect github.com/disiqueira/gotree/v3 v3.0.2 // indirect @@ -109,11 +112,12 @@ require ( github.com/pkg/errors v0.9.1 // indirect github.com/pkg/sftp v1.13.5 // indirect github.com/proglottis/gpgme v0.1.3 // indirect + github.com/raoulh/go-progress v0.0.0-20231105105338-a628dd5ae283 github.com/rivo/uniseg v0.4.4 // indirect github.com/sigstore/fulcio v1.2.0 // indirect github.com/sigstore/rekor v1.1.0 // indirect github.com/sigstore/sigstore v1.6.0 // indirect - github.com/sirupsen/logrus v1.9.0 // indirect + github.com/sirupsen/logrus v1.9.0 github.com/spf13/pflag v1.0.5 // indirect github.com/stefanberger/go-pkcs11uri v0.0.0-20201008174630-78d3cae3a980 // indirect github.com/sylabs/sif/v2 v2.11.1 // indirect @@ -130,13 +134,13 @@ require ( go.opencensus.io v0.24.0 // indirect golang.org/x/crypto v0.8.0 // indirect golang.org/x/exp v0.0.0-20230321023759-10a507213a29 // indirect - golang.org/x/mod v0.9.0 // indirect - golang.org/x/net v0.9.0 // indirect - golang.org/x/sync v0.1.0 // indirect - golang.org/x/sys v0.9.0 // indirect - golang.org/x/term v0.7.0 // indirect + golang.org/x/mod v0.10.0 // indirect + golang.org/x/net v0.10.0 // indirect + golang.org/x/sync v0.2.0 // indirect + golang.org/x/sys v0.14.0 // indirect + golang.org/x/term v0.8.0 // indirect golang.org/x/text v0.9.0 // indirect - golang.org/x/tools v0.7.0 // indirect + golang.org/x/tools v0.9.3 // indirect google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4 // indirect google.golang.org/grpc v1.54.0 // indirect google.golang.org/protobuf v1.30.0 // indirect diff --git a/go.sum b/go.sum index 7b84418..aa9b1ad 100644 --- a/go.sum +++ b/go.sum @@ -879,6 +879,8 @@ github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4O github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/raoulh/go-progress v0.0.0-20231105105338-a628dd5ae283 h1:AiEIynZ6XQzR7J0aZ2eEWffdLLJRlVvGyESJOE2+55Q= +github.com/raoulh/go-progress v0.0.0-20231105105338-a628dd5ae283/go.mod h1:8UDeaSI37y0jzBcWw3otmKoHtbaHrtTRZ1cAXj0vpuM= github.com/rhnvrm/simples3 v0.6.1/go.mod h1:Y+3vYm2V7Y4VijFoJHHTrja6OgPrJ2cBti8dPGkC3sA= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= @@ -1120,6 +1122,8 @@ golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91 golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs= golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk= +golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1169,6 +1173,8 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM= golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= +golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1189,6 +1195,8 @@ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI= +golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1293,11 +1301,15 @@ golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU= golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s= golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q= +golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= golang.org/x/term v0.7.0 h1:BEvjmm5fURWqcfbSKTdpkDXYBrUS1c0m8agp14W48vQ= golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= +golang.org/x/term v0.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1372,6 +1384,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ= golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4= golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= +golang.org/x/tools v0.9.3 h1:Gn1I8+64MsuTb/HpH+LmQtNas23LhUVr3rYZ0eKuaMM= +golang.org/x/tools v0.9.3/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -1542,6 +1556,8 @@ k8s.io/kube-openapi v0.0.0-20200805222855-6aeccd4b50c6/go.mod h1:UuqjUnNftUyPE5H k8s.io/kube-openapi v0.0.0-20201113171705-d219536bb9fd/go.mod h1:WOJ3KddDSol4tAGcJo0Tvi+dK12EcqSLqcWsryKMpfM= k8s.io/kubernetes v1.13.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk= k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +modernc.org/libc v1.30.0 h1:tw+o+UObwSE4Bfu3+Ztz9NW/Olqp7nTL/vcaEY/x4rc= +modernc.org/libc v1.30.0/go.mod h1:SUKVISl2sU6aasM35Y0v4SsSBTt89uDKrvxgXkvsC/4= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/models/command.go b/models/command.go index b6a9b63..4062cea 100644 --- a/models/command.go +++ b/models/command.go @@ -20,6 +20,10 @@ func RunCommand(command string, args ...string) (string, error) { var stderr bytes.Buffer cmd.Stderr = &stderr + //add environment variables DEBIAN_FRONTEND="noninteractive" for apt-get + cmd.Env = os.Environ() + cmd.Env = append(cmd.Env, "DEBIAN_FRONTEND=noninteractive") + err := cmd.Run() if err != nil { diff --git a/models/models.go b/models/models.go index 113adff..c83a2db 100644 --- a/models/models.go +++ b/models/models.go @@ -7,7 +7,7 @@ import ( "sync" logger "github.com/calaos/calaos-container/log" - cimg "github.com/calaos/calaos-container/models/images" + cimg "github.com/calaos/calaos-container/models/structs" "github.com/sirupsen/logrus" @@ -78,3 +78,15 @@ func StopUnit(unit string) (err error) { return err } + +func StartUnit(unit string) (err error) { + conn, err := dbus.NewWithContext(context.Background()) + if err != nil { + return err + } + defer conn.Close() + + _, err = conn.StartUnitContext(context.Background(), unit, "replace", nil) + + return err +} diff --git a/models/snapper.go b/models/snapper.go new file mode 100644 index 0000000..84bceb9 --- /dev/null +++ b/models/snapper.go @@ -0,0 +1,40 @@ +package models + +import ( + "strconv" + "strings" +) + +func createSnapperPreSnapshot(description string) (num int) { + out, err := RunCommand("snapper", "create", "-d", description, "-p", "-c", "number", "-t", "pre", "-p") + if err != nil { + logging.Errorln("Error creating snapper pre snapshot:", out) + return -1 + } + + //Parse output + num, err = strconv.Atoi(strings.TrimSpace(out)) + if err != nil { + logging.Errorf("Error parsing snapper output %s: %v", out, err) + num = -1 + } + + out, err = RunCommand("snapper", "cleanup", "number") + if err != nil { + logging.Errorln("Error cleaning up snapper snapshots:", out) + } + + return num +} + +func createSnapperPostSnapshot(snap_num int, description string) { + out, err := RunCommand("snapper", "create", "-d", description, "-p", "-c", "number", "-t", "post", "--pre-number="+strconv.Itoa(snap_num)) + if err != nil { + logging.Errorln("Error creating snapper post snapshot:", out) + } + + out, err = RunCommand("snapper", "cleanup", "number") + if err != nil { + logging.Errorln("Error cleaning up snapper snapshots:", out) + } +} diff --git a/models/structs/images.go b/models/structs/images.go new file mode 100644 index 0000000..055efa5 --- /dev/null +++ b/models/structs/images.go @@ -0,0 +1,14 @@ +package structs + +type Image struct { + Name string `json:"name"` + Source string `json:"source"` + Version string `json:"version"` + CurrentVerion string `json:"current_version"` +} + +type ImageList struct { + Images []Image `json:"images"` +} + +type ImageMap map[string]Image diff --git a/models/structs/status.go b/models/structs/status.go new file mode 100644 index 0000000..dee7887 --- /dev/null +++ b/models/structs/status.go @@ -0,0 +1,8 @@ +package structs + +type Status struct { + Status string `json:"status"` + CurrentPkg string `json:"current_pkg,omitempty"` + Progress int `json:"progress,omitempty"` + ProgressTotal int `json:"progress_total,omitempty"` +} diff --git a/models/update.go b/models/update.go index 08e8dcb..a1bb5db 100644 --- a/models/update.go +++ b/models/update.go @@ -2,14 +2,16 @@ package models import ( "encoding/json" + "fmt" "io" "net/http" "os" + "strings" "time" "github.com/calaos/calaos-container/apt" "github.com/calaos/calaos-container/config" - "github.com/calaos/calaos-container/models/images" + "github.com/calaos/calaos-container/models/structs" ) func checkForUpdatesLoop() { @@ -79,7 +81,7 @@ func checkForUpdates() error { return err } - NewVersions = compareVersions(localImageMap, urlImageMap) + NewVersions = compareCtVersions(localImageMap, urlImageMap) logging.Info("New Versions:") for name, newVersion := range NewVersions { @@ -101,7 +103,7 @@ func checkForUpdates() error { for _, p := range pkgs { logging.Infof("%s: %s --> %s\n", p.Name, p.VersionCurrent, p.VersionNew) - NewVersions[p.Name] = images.Image{ + NewVersions["dpkg/"+p.Name] = structs.Image{ Name: p.Name, Source: "dpkg", Version: p.VersionNew, @@ -112,11 +114,11 @@ func checkForUpdates() error { return nil } -func LoadFromDisk(filePath string) (images.ImageMap, error) { +func LoadFromDisk(filePath string) (structs.ImageMap, error) { _, err := os.Stat(filePath) if err != nil { // File does not exist, return an empty ImageMap without error - return make(images.ImageMap), nil + return make(structs.ImageMap), nil } data, err := os.ReadFile(filePath) @@ -124,12 +126,12 @@ func LoadFromDisk(filePath string) (images.ImageMap, error) { return nil, err } - var imageList images.ImageList + var imageList structs.ImageList if err := json.Unmarshal(data, &imageList); err != nil { return nil, err } - imageMap := make(images.ImageMap) + imageMap := make(structs.ImageMap) for _, img := range imageList.Images { imageMap[img.Name] = img } @@ -137,7 +139,7 @@ func LoadFromDisk(filePath string) (images.ImageMap, error) { return imageMap, nil } -func downloadFromURL(url string) (images.ImageMap, error) { +func downloadFromURL(url string) (structs.ImageMap, error) { resp, err := http.Get(url) if err != nil { return nil, err @@ -149,12 +151,12 @@ func downloadFromURL(url string) (images.ImageMap, error) { return nil, err } - var imageList images.ImageList + var imageList structs.ImageList if err := json.Unmarshal(data, &imageList); err != nil { return nil, err } - imageMap := make(images.ImageMap) + imageMap := make(structs.ImageMap) for _, img := range imageList.Images { imageMap[img.Name] = img } @@ -162,17 +164,125 @@ func downloadFromURL(url string) (images.ImageMap, error) { return imageMap, nil } -func compareVersions(localMap, urlMap images.ImageMap) images.ImageMap { - newVersions := make(images.ImageMap) +func compareCtVersions(localMap, urlMap structs.ImageMap) structs.ImageMap { + newVersions := make(structs.ImageMap) for name, urlImage := range urlMap { localImage, found := localMap[name] if !found || localImage.Version != urlImage.Version { img := urlImage img.CurrentVerion = localImage.Version - newVersions[name] = img + newVersions["docker/"+name] = img } } return newVersions } + +func upgradeDpkg(pkg string) error { + logging.Debugln("Running: apt-get -qq install", pkg) + out, err := RunCommand("apt-get", "-qq", "install", pkg) + logging.Debugln(out) + return err +} + +func upgradeDocker(pkg string) error { + logging.Debugln("Running: podman pull", pkg) + err := Pull(pkg) + if err != nil { + logging.Errorln("Error pulling image:", err) + } + + //Stop container + logging.Debugln("Stopping container", pkg) + err = StopUnit(pkg) + if err != nil { + logging.Errorln("Error stopping container:", err) + } + + //Start container again + logging.Debugln("Starting container", pkg) + err = StartUnit(pkg) + if err != nil { + logging.Errorln("Error starting container:", err) + } + + return err +} + +type MultiError struct { + Errors []error +} + +func (m *MultiError) Error() string { + var errs []string + for _, err := range m.Errors { + errs = append(errs, err.Error()) + } + return strings.Join(errs, ", ") +} + +func Upgrade(pkg string) error { + found := false + //search for package in cache + for name := range NewVersions { + if name == pkg { + //found package, upgrade it + found = true + } + } + + if !found { + return fmt.Errorf("package not found") + } + + img := NewVersions[pkg] + + snapperNum := createSnapperPreSnapshot("Calaos upgrade " + pkg + " " + img.Version) + defer createSnapperPostSnapshot(snapperNum, "Calaos upgrade "+pkg+" "+img.Version) + + if img.Source == "dpkg" { + return upgradeDpkg(img.Name) + } else { //docker image + return upgradeDocker(img.Name) + } +} + +func UpgradeAll() error { + //For full upgrade, first update all dkpg packages before containers. + //This is done to upgrade first calaos-container package that includes all services units + + snapperNum := createSnapperPreSnapshot("Calaos upgrade all") + defer createSnapperPostSnapshot(snapperNum, "Calaos upgrade all") + + //Upgrade dpkg packages + out, err := RunCommand("apt-get", "-qq", "dist-upgrade") + logging.Debugln(out) + + if err != nil { + return err + } + + var multiErr MultiError + + //Upgrade all docker images + for name, img := range NewVersions { + if img.Source == "docker" { + err = upgradeDocker(name) + if err != nil { + multiErr.Errors = append(multiErr.Errors, fmt.Errorf("error upgrading %s: %v", name, err)) + } + } + } + + if len(multiErr.Errors) > 0 { + return &multiErr + } + + return nil +} + +func UpdateStatus() (st *structs.Status, err error) { + st = &structs.Status{} + return st, nil +}