119 SPDX-FileCopyrightText headers and the README's licence line. The distinction that matters here: LINUXexpert-org appears in this repository in two completely different roles. As a copyright holder in the SPDX headers, which is what changes, and as the GitHub organisation in the module path and 64 import statements, which does not -- the repository still lives at github.com/LINUXexpert-org/stalwart-migrator, and rewriting that would not be a licence change, it would break the build. Both replacements are anchored to their copyright forms, so an import path cannot match either. Import count is 64 before and after, and go.mod is untouched. LICENSE untouched: the FSF's copyright on the GPL text and the "<name of author>" placeholders are not ours to edit. go vet, go build and go test all clean.
50 lines
1.8 KiB
Go
50 lines
1.8 KiB
Go
// SPDX-FileCopyrightText: 2026 Coffey Labs
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"syscall"
|
|
)
|
|
|
|
// matchOwnership gives paths the uid and gid of reference.
|
|
//
|
|
// It exists for the container path, where this tool writes the migrated
|
|
// config onto the host side of a volume and something inside the container
|
|
// has to read it. That something is not root: the official Stalwart image
|
|
// runs as uid 2000, so a config written root-owned and 0640 is a config
|
|
// the server cannot open, and the failure arrives as a recovery boot that
|
|
// will not come up rather than as a permissions error anyone would
|
|
// recognise.
|
|
//
|
|
// The reference is the data directory itself, whose ownership is already
|
|
// whatever the container writes as. Copying it is more reliable than
|
|
// resolving a user out of the image, and it is the same move
|
|
// cutover.installConfig makes for the systemd path with
|
|
// ConfigOwnerReference.
|
|
//
|
|
// ARCHITECTURE.md §4.8 is the reason this is not left to chance: the
|
|
// rollback that was removed from this tool restored every byte correctly
|
|
// and lost ownership, and reported success.
|
|
func matchOwnership(reference string, paths ...string) (string, error) {
|
|
info, err := os.Stat(reference)
|
|
if err != nil {
|
|
return "", fmt.Errorf("stat %s to copy its ownership: %w", reference, err)
|
|
}
|
|
sys, ok := info.Sys().(*syscall.Stat_t)
|
|
if !ok {
|
|
return "ownership unchanged (this platform does not report it)", nil
|
|
}
|
|
uid, gid := int(sys.Uid), int(sys.Gid)
|
|
for _, p := range paths {
|
|
if err := os.Chown(p, uid, gid); err != nil {
|
|
return "", fmt.Errorf(
|
|
"set ownership on %s to %d:%d, which is what owns %s - the server in the container runs as that user and "+
|
|
"cannot read a config it does not own: %w", p, uid, gid, reference, err)
|
|
}
|
|
}
|
|
return fmt.Sprintf("uid %d, gid %d, copied from %s", uid, gid, reference), nil
|
|
}
|