Skip to main content

Remote Configuration

GhostPour serves JSON config files to your iOS app via GET /v1/config/{name}. This lets you update prompts, model lists, and capabilities without App Store releases.

How It Works

  1. JSON files live in config/remote/{slug}.json, each with a top-level "version" integer
  2. On startup, baseline configs are seeded into a persistent directory on the Docker volume. Dashboard edits are preserved across restarts and redeploys.
  3. All configs are loaded into memory at startup
  4. Your iOS app calls GET /v1/config/{slug} on every launch
  5. If the client sends X-Config-Version matching the server version, returns {"changed": false}
  6. Otherwise returns the full JSON payload with the new version header
Why not HTTP 304?

GhostPour returns 200 with {"changed": false} instead of 304 Not Modified because Nginx Proxy Manager mangles bare 304 responses (no cached body to serve) into 404s for downstream clients.

Config Persistence

Configs live in two places:

LocationPurpose
config/remote/Baked-in baseline, shipped with the Docker image
data/remote-config/Persistent runtime copy on the mounted Docker volume

On startup, any baseline configs that don't exist in the persistent directory are copied over. This means:

  • New configs added via git appear on the next deploy automatically
  • Dashboard edits are preserved across container restarts
  • Dashboard edits take precedence over baked-in versions for the same file

Managing Configs

Via admin dashboard: Edit in the Configs tab and click Save. Changes take effect immediately and persist across restarts. No deploy needed.

Via code: Edit the JSON in config/remote/, bump the version integer, and redeploy. If the file has been edited via the dashboard, the baked-in version won't overwrite it.

Updating a Config

{
"version": 5,
"prompts": {
"welcome": "How can I help you today?",
"summary": "Summarize the key points from this conversation."
}
}

Bump the version, redeploy (or edit via dashboard), and your iOS app picks up changes on its next launch.

Adding a New Config

Drop a .json file with a "version" field into config/remote/ and redeploy. The slug is the filename without .json. No code changes needed.

You can also create configs directly from the admin dashboard without a deploy.

Localization

The config endpoint supports localized variants via the standard Accept-Language header. Your iOS app sends this automatically based on the device locale, so no client-side changes are needed to pick up new languages.

How It Works

  1. The server parses the Accept-Language header to extract the primary language code (e.g., es-MX,es;q=0.9,en;q=0.8 resolves to es)
  2. It looks for {slug}.{lang}.json in the config directory
  3. If the locale variant exists, it's returned with X-Config-Locale and X-Config-Resolved response headers
  4. If not, the base config (English) is returned as a fallback

Adding a Language

Create a locale variant file following the naming convention {slug}.{lang}.json:

config/remote/my-prompts.json       # Base (English)
config/remote/my-prompts.es.json # Spanish
config/remote/my-prompts.fr.json # French
config/remote/my-prompts.de.json # German

Each locale variant has its own independent version number, so you can update translations on their own schedule.

You can also use the admin dashboard's language picker to create new locale variants. It copies the base config as a starting template and hot-reloads immediately.

Supported Languages

Any two-letter language code works. The system is extensible by simply adding new files. No code changes or app updates required.

Multi-App Deployments

If you're running a single GhostPour instance for multiple iOS apps, prefix config slugs with the app name to avoid collisions:

config/remote/myapp-prompts.json
config/remote/myapp-providers.json
config/remote/otherapp-prompts.json

This is a naming convention, not enforced by code. Single-app deployments can use unprefixed slugs.