feat(agent): report isDocker so the web shows a docker pull command
The binary self-update hard-stops inside a container (internal/upgrade refuses when /.dockerenv exists), so the web's in-app 'force update' button is futile for Docker agents. Report whether we run in a container via a new RunningInDocker() helper (UNARR_DOCKER env baked into the image, /.dockerenv fallback for podman/containerd) on every register + sync, so the web can swap the button for a copy-paste 'docker compose pull' command.
This commit is contained in:
parent
ccd50e7c8e
commit
2148b0e2cc
5 changed files with 42 additions and 0 deletions
|
|
@ -148,6 +148,7 @@ func (d *Daemon) Register(ctx context.Context) error {
|
|||
VPNMode: d.vpnMode,
|
||||
VPNServer: d.vpnServer,
|
||||
FunnelURL: d.funnelURL,
|
||||
IsDocker: RunningInDocker(),
|
||||
}
|
||||
if free, total, err := DiskInfo(d.cfg.DownloadDir); err == nil {
|
||||
req.DiskFreeBytes = free
|
||||
|
|
|
|||
26
internal/agent/docker.go
Normal file
26
internal/agent/docker.go
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
package agent
|
||||
|
||||
import "os"
|
||||
|
||||
// RunningInDocker reports whether the agent process is running inside a Docker
|
||||
// (or compatible OCI) container. The web uses this to swap the in-app "force
|
||||
// update" button — which drives the binary self-update path that hard-stops
|
||||
// inside a container (see internal/upgrade) — for a copy-paste `docker pull`
|
||||
// command instead.
|
||||
//
|
||||
// Detection order:
|
||||
// 1. UNARR_DOCKER env truthy — baked into the official image's Dockerfile, so
|
||||
// it also covers podman/containerd running our image (which don't create
|
||||
// /.dockerenv).
|
||||
// 2. /.dockerenv exists — the standard marker Docker writes into every
|
||||
// container, covering images that didn't set the env.
|
||||
func RunningInDocker() bool {
|
||||
switch os.Getenv("UNARR_DOCKER") {
|
||||
case "1", "true", "yes":
|
||||
return true
|
||||
}
|
||||
if _, err := os.Stat("/.dockerenv"); err == nil {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
|
@ -175,6 +175,7 @@ func (sc *SyncClient) buildRequest() SyncRequest {
|
|||
LanIP: sc.cfg.LanIP,
|
||||
TailscaleIP: sc.cfg.TailscaleIP,
|
||||
CanDelete: sc.cfg.CanDelete,
|
||||
IsDocker: RunningInDocker(),
|
||||
}
|
||||
if sc.GetTaskStates != nil {
|
||||
req.Tasks = sc.GetTaskStates()
|
||||
|
|
|
|||
|
|
@ -51,6 +51,11 @@ type RegisterRequest struct {
|
|||
// CloudFlare Quick Tunnel hostname when enabled; the web prefers it over
|
||||
// Tailscale/LAN for in-browser playback because it works on any network.
|
||||
FunnelURL string `json:"funnelUrl,omitempty"`
|
||||
// IsDocker tells the web the agent runs inside a container, so it shows a
|
||||
// `docker pull` command instead of the in-app update button (the binary
|
||||
// self-update refuses to run in Docker). No omitempty: false (a binary
|
||||
// install) is a meaningful state the server must see to keep the button.
|
||||
IsDocker bool `json:"isDocker"`
|
||||
}
|
||||
|
||||
// RegisterResponse is returned by the server after registration.
|
||||
|
|
@ -395,6 +400,9 @@ type SyncRequest struct {
|
|||
VPNServer string `json:"vpnServer,omitempty"`
|
||||
// CloudFlare Quick Tunnel hostname when enabled, else empty.
|
||||
FunnelURL string `json:"funnelUrl,omitempty"`
|
||||
// IsDocker — see RegisterRequest.IsDocker. Sent every sync so the web keeps
|
||||
// the flag fresh even if the agent migrated binary↔docker between restarts.
|
||||
IsDocker bool `json:"isDocker"`
|
||||
}
|
||||
|
||||
// ControlAction represents a server-side control signal for a task.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue