fix: harden thothctl pi lifecycle

This commit is contained in:
2026-08-04 18:40:50 +02:00
parent 20e59b8d32
commit 8fde1f81c7
8 changed files with 308 additions and 27 deletions
+38
View File
@@ -7,6 +7,7 @@ import (
"fmt"
"os"
"path/filepath"
"runtime"
"time"
)
@@ -30,9 +31,19 @@ type Image struct {
ID string `json:"id"`
Reference string `json:"reference"`
Volumes []string `json:"volumes"`
Mounts []Mount `json:"mounts"`
ConfigurationSHA string `json:"configuration_sha256,omitempty"`
}
// Mount is the complete persistence identity relevant to safe core recreation.
type Mount struct {
Type string `json:"type"`
Name string `json:"name,omitempty"`
Source string `json:"source"`
Destination string `json:"destination"`
RW bool `json:"rw"`
}
// Target records the immutable input selected by the operator. Source is either build or a
// digest-pinned image reference; it intentionally never contains credentials.
type Target struct {
@@ -96,11 +107,38 @@ func writeState(path string, state State) error {
temporary.Close()
return errors.New("could not write update recovery state")
}
if err := temporary.Sync(); err != nil {
temporary.Close()
return errors.New("could not durably write update recovery state")
}
if err := temporary.Close(); err != nil {
return errors.New("could not write update recovery state")
}
if err := os.Rename(temporaryName, path); err != nil {
return errors.New("could not finalize update recovery state")
}
if runtime.GOOS != "windows" {
if directoryHandle, err := os.Open(directory); err == nil {
_ = directoryHandle.Sync()
_ = directoryHandle.Close()
}
}
return nil
}
type updateLock struct{ path string }
func acquireLock(statePath string) (*updateLock, error) {
if err := os.MkdirAll(filepath.Dir(statePath), 0o700); err != nil {
return nil, errors.New("could not create Pi update recovery directory")
}
path := statePath + ".lock"
if err := os.Mkdir(path, 0o700); err != nil {
if errors.Is(err, os.ErrExist) {
return nil, errors.New("another Pi update or rollback is already in progress; recovery lock retained")
}
return nil, errors.New("could not acquire Pi update lock")
}
return &updateLock{path: path}, nil
}
func (l *updateLock) Release() { _ = os.Remove(l.path) }