git / moonbite-network / storage.py · raw
"""Where persistent state lives.A container's filesystem is thrown away on every redeploy. Anything writtenbeside the code — the block store, the wall, wallet history — thereforesurvives only until the next deploy, which is how the chain kept resetting toheight 0 and the wall kept forgetting its certificates.The fix is a mounted volume, and the only thing the application needs to knowis where it is. That is this module: one directory, resolved once, that everydurable file hangs off.Resolution order:1. ``MOONBITE_DATA_DIR`` — an explicit answer always wins.2. ``/data`` if it exists — the conventional mount point on Railway and most container hosts, so mounting a volume there is the whole configuration.3. The application directory — the local-development case, where there is no volume and files beside the code are exactly what a developer expects.Per-file environment overrides (``MOONBITE_WALL_DB`` and friends) still takeprecedence over all of this, so an existing deployment that sets them keepsworking unchanged."""from __future__ import annotationsimport os_APP_DIR = os.path.dirname(os.path.abspath(__file__))# The conventional volume mount point. Checked for existence rather than# assumed: on a host without a volume this directory simply is not there, and# writing to the container root instead would silently look like it worked._CONVENTIONAL_MOUNT = "/data"def data_dir() -> str: """The directory durable files belong in, created if it does not exist.""" explicit = os.environ.get("MOONBITE_DATA_DIR", "").strip() if explicit: base = explicit elif os.path.isdir(_CONVENTIONAL_MOUNT): base = _CONVENTIONAL_MOUNT else: base = _APP_DIR os.makedirs(base, exist_ok=True) return basedef is_persistent() -> bool: """True when a volume looks configured, rather than the app directory. Lets a caller default persistence on in production without turning it on for every local run and test, where a chain that survives between runs is a surprise rather than a feature. """ if os.environ.get("MOONBITE_DATA_DIR", "").strip(): return True return os.path.isdir(_CONVENTIONAL_MOUNT)def data_path(filename: str, env_override: str | None = None) -> str: """Resolve one durable file, honouring its own environment override first. Parent directories are created because a path pointing into a volume subdirectory is a perfectly reasonable thing to configure, and sqlite's failure when the directory is missing ("unable to open database file") says nothing about which file or why. """ if env_override: override = os.environ.get(env_override, "").strip() if override: parent = os.path.dirname(os.path.abspath(override)) os.makedirs(parent, exist_ok=True) return override return os.path.join(data_dir(), filename)