v0.66.0: userdata layout + shared-storage ownership convention

appbackup/userdata.go: EnsureUserdataDir (MkdirAll + explicit setgid Chmod 2775 +
chown gid 1000), UserdataSkeleton, EnsureUserdataSkeleton; linux chown/StatGID +
non-linux stubs. stackEnv injects USERDATA_PATH=<HDD_PATH>/userdata. Skeleton
pre-created on register + FileBrowser sync; deploy belt (composeExecCustomEnv on
'up') pre-creates every ${USERDATA_PATH} bind source. FileBrowser mounts userdata
(was appdata) — uid 1000 can now write into 2775 setgid. #8: migrate merge walk +
copyFile preserve source setgid+group so the convention survives MigrateAll.
Non-hollow tests incl. Linux setgid assertions + migration-preserve companion.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-14 21:58:49 +02:00
parent cbaa53f565
commit c48f95fe06
17 changed files with 523 additions and 34 deletions
+16 -16
View File
@@ -21,7 +21,7 @@ type Metadata struct {
AppInfo AppInfo `yaml:"app_info" json:"app_info"`
OptionalConfig []OptionalConfigGroup `yaml:"optional_config" json:"optional_config"`
HealthCheck *HealthCheckConfig `yaml:"healthcheck,omitempty" json:"healthcheck,omitempty"`
Integrations []IntegrationDef `yaml:"integrations,omitempty" json:"integrations,omitempty"`
Integrations []IntegrationDef `yaml:"integrations,omitempty" json:"integrations,omitempty"`
}
// AppInfo holds detailed app information for the info page.
@@ -52,25 +52,25 @@ type OptionalConfigField struct {
// ResourceHints describe what the app needs.
type ResourceHints struct {
MemRequest string `yaml:"mem_request" json:"mem_request"`
MemLimit string `yaml:"mem_limit" json:"mem_limit"`
PiCompatible bool `yaml:"pi_compatible" json:"pi_compatible"`
NeedsHDD bool `yaml:"needs_hdd" json:"needs_hdd"`
HungarianUI bool `yaml:"hungarian_ui" json:"hungarian_ui"`
MemRequest string `yaml:"mem_request" json:"mem_request"`
MemLimit string `yaml:"mem_limit" json:"mem_limit"`
PiCompatible bool `yaml:"pi_compatible" json:"pi_compatible"`
NeedsHDD bool `yaml:"needs_hdd" json:"needs_hdd"`
HungarianUI bool `yaml:"hungarian_ui" json:"hungarian_ui"`
}
// DeployField defines one configuration field shown during first deployment.
type DeployField struct {
EnvVar string `yaml:"env_var" json:"env_var"`
Label string `yaml:"label" json:"label"`
Type string `yaml:"type" json:"type"` // domain, subdomain, secret, password, path, text, select, boolean
Generate string `yaml:"generate" json:"generate"` // e.g., "password:24", "hex:32", "static:admin"
Default string `yaml:"default" json:"default"`
Required bool `yaml:"required" json:"required"`
Placeholder string `yaml:"placeholder" json:"placeholder"`
Description string `yaml:"description" json:"description"`
EnvVar string `yaml:"env_var" json:"env_var"`
Label string `yaml:"label" json:"label"`
Type string `yaml:"type" json:"type"` // domain, subdomain, secret, password, path, text, select, boolean
Generate string `yaml:"generate" json:"generate"` // e.g., "password:24", "hex:32", "static:admin"
Default string `yaml:"default" json:"default"`
Required bool `yaml:"required" json:"required"`
Placeholder string `yaml:"placeholder" json:"placeholder"`
Description string `yaml:"description" json:"description"`
LockedAfterDeploy bool `yaml:"locked_after_deploy" json:"locked_after_deploy"`
Options []SelectOption `yaml:"options" json:"options,omitempty"`
Options []SelectOption `yaml:"options" json:"options,omitempty"`
// DataKey marks a field as a DATA-ENCRYPTING key (e.g. AdventureLog's "Titkosítási kulcs"):
// the app encrypts stored data with it, so regenerating it would render restored data
// unreadable. It is a fail-closed annotation only — the recovery unit never stores secrets;
@@ -113,7 +113,7 @@ type HealthCheckConfig struct {
// HealthCheckItem defines a single health check probe.
type HealthCheckItem struct {
Type string `yaml:"type" json:"type"` // "http", "api", "tcp"
Type string `yaml:"type" json:"type"` // "http", "api", "tcp"
Port int `yaml:"port" json:"port"`
Path string `yaml:"path" json:"path"` // for http/api; default "/"
Method string `yaml:"method" json:"method"` // for api; default "GET"