package system import ( "context" "fmt" "github.com/novox/mesh-host/internal/declaration" ) // android is a partial host, and being partial is the point. // // It implements `file`, `directory` and `action` — the shapes that need only a filesystem and a // way to run something — and refuses the other three. That is not a broken host: a declaration // naming a shape this host does not implement is refused whole, the same treatment an unknown // type gets, and the profile tells the control plane which shapes exist so it never sends one // it cannot do (novox/hq ADR 0005). // // What it cannot do, and why: // // - **package** — there is no package manager an ordinary app may drive. Installing software // on Android means the framework installing an APK, which is not something a process asks // for on its own behalf. // - **service** — Android's init reads .rc files from the system partition, which needs root // and an unlocked bootloader. On a normal device nothing can register with it. // - **container** — no container runtime, and no kernel access to give one. // // **This host is EPISODIC** (novox/hq ADR 0005). Everywhere else an init runs the launcher at // boot and the launcher supervises the host. Android grants neither: nothing to register with // without root, and nothing worth supervising, because a supervisor would be killed alongside // what it supervises. // // So it runs when the platform allows and is killed when the platform wants the memory — and // that is **disconnection**, which ADR 0004 already made an ordinary situation rather than an // exception. It needs no keep-alive and no new mechanism: the store is already authoritative // while disconnected, reconcile already happens on start, and the mesh already reports *last // heard from* rather than alarming on silence. // // It also **cannot be the first node** — every step of raising a foundation is a shape it // refuses — and its bundle says so rather than being an empty placeholder. type android struct{} func (android) Name() string { return "android" } func (android) Shapes() []declaration.Type { return portableShapes() } func (android) Confirm(ctx context.Context, run Runner) error { // Ask the property service, which exists on every Android and nowhere else. A file path // check would pass inside a chroot; this asks something only Android answers. if _, err := run(ctx, "getprop", "ro.build.version.sdk"); err != nil { return fmt.Errorf( "this is the android host and the property service does not answer here. Either "+ "this is not Android, or it is a container without it: %w", err) } return nil } // The four below are unreachable through the ordinary path: Check refuses a declaration naming // these shapes before anything is applied. They are here so that "unreachable" fails loudly if // it ever stops being true, rather than a nil applier being called. func (a android) PackageInstalled(context.Context, Runner, string) (bool, error) { return false, fmt.Errorf("%w: package (there is no package manager an app may drive)", ErrUnsupported) } func (a android) InstallPackage(context.Context, Runner, string) error { return fmt.Errorf("%w: package", ErrUnsupported) } func (a android) ServiceState(context.Context, Runner, string) (string, error) { return "", fmt.Errorf("%w: service (init is not reachable without root)", ErrUnsupported) } func (a android) SetServiceState(context.Context, Runner, string, string) error { return fmt.Errorf("%w: service", ErrUnsupported) } func (a android) ServiceBoot(context.Context, Runner, string) (string, error) { return "", fmt.Errorf("%w: service", ErrUnsupported) } func (a android) SetServiceBoot(context.Context, Runner, string, string) error { return fmt.Errorf("%w: service", ErrUnsupported) } // Users are one of the shapes this host refuses. // // Android's user database belongs to the framework and is not something an ordinary app may // write. Refused with a reason rather than attempted, the same as package, service and container // above — and the profile says so, so the control plane never sends one. func (android) CreateUser(context.Context, Runner, string, string, string) error { return fmt.Errorf("this host implements no users: Android's user database belongs to the " + "framework and is not writable by an ordinary process") } func (android) SetUserShell(context.Context, Runner, string, string) error { return fmt.Errorf("this host implements no users") } func (android) AddUserToGroup(context.Context, Runner, string, string) error { return fmt.Errorf("this host implements no users") }