How Openship SystemManager Performs Prerequisite Checks and Automated Installations
The Openship SystemManager orchestrates prerequisite validation and automated installation through a declarative rule engine that maps features to components, caches state for fast-path checks, and executes atomic install-verify cycles protected by concurrency locks.
The SystemManager in the oblien/openship repository serves as the core orchestration layer for self-hosting deployments. It abstracts the complexity of system-level dependencies by automating the detection, validation, and installation of required components. Understanding how Openship SystemManager performs prerequisite checks and automated installations reveals a sophisticated architecture designed for reliability and performance.
Declarative Prerequisite Mapping
The manager begins by resolving what components are actually needed for your specific runtime mode. In packages/adapters/src/system/setup.ts, the resolveRules() method (lines 65‑82) returns an array of PrerequisiteRule objects that map high-level features—such as build, deploy, or edge—to their underlying system components like git, docker, or edge.
These rules differ between docker and bare runtime modes, allowing the same manager logic to support containerized and native deployments without code changes. The resolveRequired() method (lines 85‑88) then flattens these rules into a concrete list of components that must be present for the selected mode.
Initialization and State Management
When instantiated, the SystemManager constructor (lines 30‑39) initializes several critical dependencies:
- The runtime mode (
dockerorbare) and a command executor (typicallyLocalCommandExecutor) - Cached rule and component lists to avoid repeated resolution
- A SetupStateStore instance (defaulting to
FileStateStore) for persisting component health across restarts - An optional ProvisionLock to serialize concurrent installation attempts
This design ensures that the manager maintains both volatile in-memory caches (cachedState) and durable persistence for long-running server processes.
Fast-Path Readiness Verification
Performance is prioritized through aggressive caching. The isReady() method (lines 53‑66) implements a fast-path check that reads the cached SetupState. If setupComplete is true and the cache is fresh, it returns true instantly without touching the filesystem or network.
If the cache exceeds the CACHE_TTL_MS (24‑hour TTL), the method triggers a background verify() call while still returning true to avoid blocking hot paths. This stale-while-revalidate pattern ensures that production servers remain responsive while eventually converging on accurate system state.
Component Health Verification
When explicit validation is required, the manager delegates to the low-level check module in packages/adapters/src/system/checks.ts. The checkAll() method (lines 78‑92) iterates every registered component, executes health probes via checkAll(this.executor), and marks missing components in the cached state.
For mode-specific validation, checkRequired() (lines 95‑103) limits the scope to components returned by resolveRequired(). Feature-level granularity is provided by checkFeature() (lines 108‑124), which first consults the cache and falls back to live checkComponents calls only when necessary. Hard dependencies can enforce readiness through requireFeature() (lines 126‑131), which throws immediately if prerequisites are unmet.
Atomic Installation Workflows
Automatic remediation follows a strict check‑install‑re‑validate cycle. The ensureFeature() method (lines 166‑184) checks feature readiness, invokes ensureNamedComponents to install any missing dependencies, and then re-verifies the final state.
The ensureNamedComponents method (lines 445‑486) is the critical section protected by the optional ProvisionLock. It executes:
- Current health assessment
- Bulk installation via
installManyfor missing components - Post-installation verification
- Cache state updates
If any installer fails, the entire transaction throws, preventing partial system states.
Individual component installation is handled by installComponent() (lines 113‑122), which looks up concrete implementations from the COMPONENT_INSTALLERS registry defined in packages/adapters/src/system/installer.ts. This registry pattern enables OS-specific install scripts while keeping the manager agnostic of platform details.
Complete Setup Orchestration
The setup() method (lines 131‑187) serves as the entry point for the dashboard wizard found in apps/dashboard/src/app/(dashboard)/servers/_components/server-setup-wizard.tsx. This method:
- Logs the required component list for the selected mode
- Runs
checkRequired()to discover gaps - Executes
installMany()to provision missing components - Validates the final aggregated state
- Marks the setup as complete via
markSetupComplete()
This orchestration provides the seamless one-click setup experience in the Openship dashboard.
State Persistence Layer
Durability is handled through the SetupStateStore abstraction defined in packages/adapters/src/system/state.ts. The manager synchronizes memory and disk via loadState(), updateStateFromChecks() (lines 224‑260), and component-specific markers like markComponentInstalled(). The default FileStateStore implementation persists JSON state to disk, though the abstraction allows for database-backed stores in clustered deployments.
Practical Implementation Example
// Initialize the manager for Docker-based deployments
import { SystemManager } from "packages/adapters/src/system/setup";
import { LocalCommandExecutor } from "packages/adapters/src/types";
const executor = new LocalCommandExecutor();
const manager = new SystemManager("docker", { executor });
// Fast-path cached check
const ready = await manager.isReady(); // boolean
// Automatic remediation for deployment features
await manager.ensureFeature("deploy", (log) => console.log(log.message));
// Full wizard execution with progress logging
await manager.setup((log) => console.log(`[${log.level}] ${log.message}`));
Summary
- Declarative prerequisite mapping via
PrerequisiteRuleseparates feature requirements from component implementations, supporting both Docker and bare-metal modes without logic duplication. - Cache-first architecture with a 24‑hour TTL provides sub-millisecond readiness checks while guaranteeing eventual consistency through background revalidation.
- Concurrency safety is enforced by an optional
ProvisionLockthat serializes the entire check-install-validate block across multiple processes or requests. - Atomic installation cycles ensure that partial failures never leave the system in an inconsistent state.
- Extensible installer plumbing through the
COMPONENT_INSTALLERSregistry allows platform-specific implementations without modifying core manager logic.
Frequently Asked Questions
What is the difference between checkFeature and ensureFeature?
The checkFeature() method performs read-only validation against cached or live component state, returning a readiness result without side effects. In contrast, ensureFeature() is a write-capable operation that triggers automatic installation of missing components via the ensureNamedComponents critical section, followed by re-validation to confirm success.
How does SystemManager handle concurrent installation requests?
The manager accepts an optional ProvisionLock in its constructor. When provided, ensureNamedComponents() acquires this lock before entering the check-install-validate cycle, ensuring that only one installation process runs at a time per host. This prevents race conditions when multiple deployments target the same server simultaneously.
What happens if a prerequisite check cache is older than 24 hours?
When isReady() detects a stale cache (exceeding CACHE_TTL_MS), it triggers a background verify() task to refresh component health while immediately returning true based on the existing cached state. This stale-while-revalidate approach keeps request latency low while ensuring that long-running servers eventually detect system changes.
Where are the concrete installer implementations defined?
Concrete installers are registered in the COMPONENT_INSTALLERS map within packages/adapters/src/system/installer.ts. Each entry maps a component name (e.g., docker, git) to an async installer function that handles OS-specific logic. The SystemManager invokes these through runInstaller() without hard-coding platform dependencies, enabling support for diverse operating systems.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →