From 96b24abccb7a5ea7f4ed0a44e135cc1e92f0d027 Mon Sep 17 00:00:00 2001 From: Boris Rybalkin Date: Fri, 17 Jul 2026 18:09:19 +0100 Subject: [PATCH] devicestate: allow auto-refresh when registration is skipped (noregister) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit canAutoRefresh only returns true early when ensureOperationalAttempts >= 3 ("we tried to get a serial for a while, allow refresh anyway"); otherwise it requires a serial. Syncloud has no serial vault, so before PR #6 snapd kept attempting registration, failed, and after 3 attempts that fallback enabled auto-refresh — the perpetual "Initialize device" error was ugly but it is what kept auto-refresh alive. PR #6 (snapd 666) added the /run/snapd/noregister marker so ensureOperational returns before ever attempting registration. That removed the error but left ensureOperationalAttempts at 0, so the >= 3 fallback never fires and, with no serial, canAutoRefresh returns false permanently. Every device that upgraded to snapd 666 stopped auto-refreshing (snap refresh --time shows next: n/a); a reboot does not help because the marker is recreated each boot and attempts stays 0. Treat the noregister marker as an explicit opt-out of registration that should still permit auto-refresh, mirroring the marker check in ensureOperational. --- overlord/devicestate/devicestate.go | 10 ++++++++ overlord/devicestate/devicestate_test.go | 31 ++++++++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/overlord/devicestate/devicestate.go b/overlord/devicestate/devicestate.go index f7f7977f10c..f6391109a13 100644 --- a/overlord/devicestate/devicestate.go +++ b/overlord/devicestate/devicestate.go @@ -33,6 +33,7 @@ import ( "github.com/snapcore/snapd/asserts" "github.com/snapcore/snapd/boot" + "github.com/snapcore/snapd/dirs" "github.com/snapcore/snapd/gadget" "github.com/snapcore/snapd/i18n" "github.com/snapcore/snapd/logger" @@ -129,6 +130,15 @@ func canAutoRefresh(st *state.State) (bool, error) { return false, nil } + // Syncloud runs no serial vault and deliberately skips registration via + // the noregister marker, so ensureOperational never attempts registration + // and ensureOperationalAttempts stays at 0. Without this the serial check + // below would disable auto-refresh forever. Allow it when registration was + // intentionally opted out. + if osutil.FileExists(filepath.Join(dirs.SnapRunDir, "noregister")) { + return true, nil + } + // Either we have a serial or we try anyway if we attempted // for a while to get a serial, this would allow us to at // least upgrade core if that can help. diff --git a/overlord/devicestate/devicestate_test.go b/overlord/devicestate/devicestate_test.go index 897883c322c..719563de818 100644 --- a/overlord/devicestate/devicestate_test.go +++ b/overlord/devicestate/devicestate_test.go @@ -1036,6 +1036,37 @@ func (s *deviceMgrSuite) TestCanAutoRefreshNoSerialFallback(c *C) { c.Check(canAutoRefresh(), Equals, true) } +func (s *deviceMgrSuite) TestCanAutoRefreshNoRegister(c *C) { + s.state.Lock() + defer s.state.Unlock() + + canAutoRefresh := func() bool { + ok, err := devicestate.CanAutoRefresh(s.state) + c.Assert(err, IsNil) + return ok + } + + // seeded, model, no serial, no registration attempts -> no auto-refresh + s.state.Set("seeded", true) + devicestatetest.SetDevice(s.state, &auth.DeviceState{ + Brand: "canonical", + Model: "pc", + }) + s.makeModelAssertionInState(c, "canonical", "pc", map[string]interface{}{ + "architecture": "amd64", + "kernel": "pc-kernel", + "gadget": "pc", + }) + c.Check(devicestate.EnsureOperationalAttempts(s.state), Equals, 0) + c.Check(canAutoRefresh(), Equals, false) + + // registration deliberately skipped via the noregister marker + // -> auto-refresh without a serial or any registration attempts + c.Assert(os.MkdirAll(dirs.SnapRunDir, 0755), IsNil) + c.Assert(ioutil.WriteFile(filepath.Join(dirs.SnapRunDir, "noregister"), nil, 0644), IsNil) + c.Check(canAutoRefresh(), Equals, true) +} + func (s *deviceMgrSuite) TestCanAutoRefreshOnClassic(c *C) { release.OnClassic = true