Organization
How the repository and the runtime data are laid out, and how a request and a SkyTonight run flow through them. Read this before adding a module; the rules for where new code goes are in EXTENDING.md.
Architecture at a glance
Browser (static/js, one module per feature)
|
v
backend/app.py -> backend/blueprints/*.py (every HTTP route, one Blueprint per domain)
|
+--------------------------+---------------------------+
v v v
feature packages cache/cache_store db/ (SQLite, Alembic)
(skytonight, observation, per-location JSON accounts, configuration,
equipment, weather, ...) slots with a TTL each every per-user record
^ ^
| |
skytonight_scheduler cache_scheduler push_scheduler, mqtt_publisher,
(twice a night, writes (every 25 min, runs only mqtt_subscriber
data/skytonight/) the jobs whose TTL ran out) (background threads)
- Routes never compute anything heavy: they read what the background threads wrote, from the
cache or from
data/skytonight/. - Feature packages never import blueprints; a helper needed by two features lives in
utils/. - Per-user data lives in the database, never in a JSON file (DATABASE.md, PRIVACY.md).
Directory structure
myastroboard/
├── backend/
│ ├── app.py # Flask app factory: settings, extensions, blueprint registration,
│ │ # startup and background threads (no routes)
│ │
│ ├── blueprints/ # HTTP routes, one Blueprint per domain
│ │ ├── admin.py # /api/admin/*, /api/metrics, /api/backup/*, /api/logs/*, /api/config/export
│ │ ├── astrodex.py # /api/astrodex/*, /api/beginner-catalog
│ │ ├── astrodex_stream.py # /api/astrodex/stream/* (signed image URLs for Home Assistant)
│ │ ├── astronomy.py # /api/sky-widget, /api/sun/*, /api/events/*, /api/astro/*, /api/tonight/best-window
│ │ ├── auth.py # /api/auth/*, /api/users/*
│ │ ├── connectors.py # /api/connectors (registry listing, shared config save)
│ │ ├── connectors_allsky.py # /api/connectors/allsky/*
│ │ ├── connectors_mqtt.py # /api/connectors/mqtt/*
│ │ ├── connectors_myastroshine.py # /api/connectors/myastroshine/*, /api/astrodex/integration/*
│ │ ├── equipment.py # /api/equipment/*
│ │ ├── locations.py # /api/config, /api/locations/*
│ │ ├── misc.py # /api/skyquality, /api/convert-coordinates, /api/timezones, /api/health, /api/cache, /api/version
│ │ ├── mqtt_connections.py # /api/mqtt-connections/*
│ │ ├── observation_sessions.py # /api/observation-sessions/*
│ │ ├── plan_my_night.py # /api/plan-my-night/*
│ │ ├── push.py # /api/push/*
│ │ ├── session_analytics.py # /api/session-analytics/*, /api/wishlist/*
│ │ ├── skytonight_api.py # /api/skytonight/*, /api/catalogues
│ │ ├── tracking.py # /api/object/*, /api/object-image/*, /api/iss/*, /api/css/*, /api/spaceflight/*
│ │ └── weather.py # /api/weather/*, /api/moon/*, /api/aurora/predictions, /api/seeing-forecast
│ │
│ ├── skytonight/ # SkyTonight calculation pipeline (docs/SKYTONIGHT.md)
│ │ ├── skytonight_bodies.py # Moon and planets
│ │ ├── skytonight_calculator.py # Observability, AstroScore, difficulty
│ │ ├── skytonight_catalogue_builder.py # Deep-sky dataset assembly from every catalogue
│ │ ├── skytonight_comets.py # Comets (MPC primary, JPL fallback)
│ │ ├── skytonight_models.py # Dataset dataclasses
│ │ ├── skytonight_scheduler.py # Run timing (nautical dawn/dusk, 6 h fallback)
│ │ ├── skytonight_scheduler_manager.py # Multi-worker scheduler coordination
│ │ ├── skytonight_storage.py # Runtime files under data/skytonight/
│ │ └── skytonight_targets.py # Dataset access, name resolution, lookup table
│ │
│ ├── observation/ # Logbook, planning and events
│ │ ├── astrodex.py # Astrodex items and pictures
│ │ ├── astrodex_stream.py # Current frame of a user's (or the shared) slideshow
│ │ ├── beginner_catalog.py # Curated starter list
│ │ ├── catalogue_aliases.py # Cross-catalogue name matching
│ │ ├── catalogue_collection.py # Catalogue Collection (caught / not caught per catalogue)
│ │ ├── hips_cutout.py # Local sky cut-out from HiPS tiles when CDS hips2fits is down
│ │ ├── myastroshine_integration.py # Astrodex <-> MyAstroShine round-trip
│ │ ├── object_info.py # Single-object lookup (SIMBAD, local catalogue)
│ │ ├── observation_sessions.py # Observation Log
│ │ ├── plan_my_night.py # Plan My Night, optimizer, CSV/PDF export
│ │ ├── planetary_events.py # Conjunctions, oppositions, elongations
│ │ ├── session_analytics.py # Your Sky (read-only aggregation of the Observation Log)
│ │ ├── sidereal_time.py # Local sidereal time
│ │ ├── sky_framing.py # Framing simulator: target resolution, sky images
│ │ ├── solar_system_events.py # Meteor showers, comets from the live dataset
│ │ ├── special_phenomena.py # Equinoxes, solstices, zodiacal light, Milky Way
│ │ ├── target_coordinates.py # Frozen target record -> RA/Dec
│ │ ├── visibility_calendar.py # Best months for a fixed target
│ │ └── wishlist.py # Wishlist
│ │
│ ├── equipment/
│ │ ├── equipment_profiles.py # Telescopes, cameras, mounts, filters, accessories, combinations, FOV
│ │ ├── exposure_math.py # Sky background and integration time (docs/EXPOSURE_CALC.md)
│ │ └── framing.py # Sensor geometry of a combination against a target
│ │
│ ├── astroweather/ # Astronomical forecasts
│ │ ├── aurora_predictions.py # Kp forecast, aurora visibility
│ │ ├── horizon_graph.py # Sun and Moon altitude over the day
│ │ ├── moon_astrotonight.py # Best imaging window (3 modes, one pass)
│ │ ├── moon_calendar.py # Monthly Moon phase calendar
│ │ ├── moon_eclipse.py # Next lunar eclipse
│ │ ├── moon_phases.py # Phase, rise/set, dark window
│ │ ├── moon_planner.py # Next nights
│ │ ├── seeing_forecast_7timer.py # 7Timer seeing and transparency
│ │ ├── sun_eclipse.py # Next solar eclipse
│ │ └── sun_phases.py # Sunrise, sunset, twilights
│ │
│ ├── weather/ # Weather and astrophotography conditions (docs/WEATHER.md)
│ │ ├── sky_quality.py # Bortle / SQM
│ │ ├── weather_astro.py # Astro-analysis metrics and alerts
│ │ ├── weather_openmeteo.py # Open-Meteo client
│ │ └── weather_utils.py # Cached Open-Meteo client factory
│ │
│ ├── space/ # Spaceflight (docs/SPACEFLIGHT.md)
│ │ ├── css_passes.py # China Space Station passes and transits (mirror of iss_passes.py)
│ │ ├── iss_passes.py # ISS passes, solar and lunar transits
│ │ └── spaceflight_tracker.py # Launch Library 2: launches, astronauts, events
│ │
│ ├── connectors/ # Bridges to external tools (docs/CONNECTORS.md)
│ │ ├── base_connector.py # Connector contract and registry
│ │ ├── allsky_connector.py # AllSky camera
│ │ ├── astrodex_stream_connector.py # Astrodex Stream
│ │ ├── myastroshine_connector.py # MyAstroShine
│ │ ├── mqtt_connector.py # MQTT / Home Assistant: declaration and broker probe
│ │ ├── mqtt_payloads.py # Home Assistant discovery and state payloads
│ │ ├── mqtt_publisher.py # Publisher thread
│ │ └── mqtt_subscriber.py # Subscriber thread (AllSky sensor data)
│ │
│ ├── cache/ # Background cache (docs/CACHE_SYSTEM.md)
│ │ ├── cache_scheduler.py # Scheduler thread
│ │ ├── cache_store.py # Per-location cache slots, TTLs, change detection
│ │ └── cache_updater.py # One update function per job
│ │
│ ├── db/ # SQLite storage (docs/DATABASE.md)
│ │ ├── engine.py, schema.py # Engine, transactions, table definitions
│ │ ├── migrations/, migrate.py, alembic.ini # Alembic schema migrations
│ │ ├── bootstrap.py # Brings the database up to date at startup
│ │ ├── users_store.py, settings_store.py # Accounts, install-wide settings
│ │ ├── documents.py, collections.py, queries.py # Per-user records
│ │ ├── legacy_import.py, legacy_sources.py, json_layout.py # 1.6 JSON import, backup layout
│ │ ├── health.py # Diagnostics for App settings -> Metrics
│ │ └── manage.py # Recovery command line (reset a password, disable 2FA)
│ │
│ ├── demo/
│ │ └── demo_life.py # Demo data that keeps living (DEMO_MODE=true, docs/DEMO_MODE.md)
│ │
│ ├── utils/ # Cross-cutting helpers
│ │ ├── __init__.py # Generic helpers (JSON encoding, slugify_location_name)
│ │ ├── app_settings.py # Application settings, session key, VAPID keys
│ │ ├── auth.py # Accounts, roles, sessions, preferences
│ │ ├── backup_archive.py # Backup ZIP build and restore
│ │ ├── config_defaults.py # Default configuration
│ │ ├── connector_secrets.py # Connector credentials (kept out of the configuration)
│ │ ├── constants.py # Paths, TTLs, URLs, limits
│ │ ├── constellation_names.py # IAU abbreviation -> full name
│ │ ├── demo_mode.py # Demo mode switches
│ │ ├── events_aggregator.py # Unified upcoming events feed
│ │ ├── file_cache.py # Size cap for on-disk image caches
│ │ ├── file_lock.py # Cross-process lock for files shared by gunicorn workers
│ │ ├── i18n_utils.py # Server-side translations
│ │ ├── image_privacy.py # EXIF stripping of uploads
│ │ ├── ingress.py # Home Assistant ingress prefix
│ │ ├── json_settings_store.py # Small admin-managed settings dicts
│ │ ├── logging_config.py # Centralized logger (get_logger)
│ │ ├── map_tiles.py # OpenStreetMap resources proxy and cache
│ │ ├── metrics_collector.py # System metrics
│ │ ├── mqtt_connections.py # Shared MQTT broker connections
│ │ ├── on_demand_translate.py # DeepL / LibreTranslate for third-party text
│ │ ├── push_manager.py # VAPID keys, Web Push sending
│ │ ├── push_scheduler.py # Push notification triggers N1-N9
│ │ ├── rate_limit.py # Sliding-window counter (sign-in throttle)
│ │ ├── repo_config.py # Configuration load/save, location presets
│ │ ├── route_helpers.py # Active-location resolution for routes
│ │ ├── security_settings.py # Trusted networks, global 2FA switch
│ │ ├── system_info.py # Host summary logged at startup
│ │ ├── txtconf_loader.py # Repository metadata text files
│ │ ├── user_data.py # Per-user files: erasure and personal data export
│ │ └── version_checker.py # GitHub release check
│ │
│ └── catalogues/ # Static catalogue data (JSON): Pensack 500, LBN, Gary Imm, Arp,
│ # Sharpless, Barnard, vdB, Abell PNe, Abell clusters, beginner list
│
├── static/
│ ├── js/ # One module per feature (snake_case), connectors/ for connector cards
│ ├── css/ # bs_main.css plus one bs_<feature>.css per feature
│ ├── i18n/ # en.json (reference), fr, es, de, it, pt
│ ├── data/equipment_presets.json # Gear presets for the wizard and the Equipment tab
│ ├── map-styles/ # Vector basemap styles (built by scripts/build_map_styles.py)
│ ├── vendor/ # Vendored libraries: Bootstrap, Bootstrap Icons, Chart.js, Leaflet,
│ │ # MapLibre GL, Plotly, qrcode (no CDN at runtime)
│ ├── img/, ico/ # Images and app icons
│ ├── manifest*.webmanifest # PWA manifests, one per language
│ ├── offline.html # Offline fallback page
│ └── sw.js # Service worker (app shell cache, Web Push)
│
├── templates/
│ ├── index.html # Main page (every tab)
│ └── login.html # Sign-in page
│
├── tests/ # Mirrors backend/ (tests/skytonight/, tests/observation/...)
│ ├── conftest.py # Shared fixtures, temporary DATA_DIR
│ ├── e2e/ # Browser tests (Playwright)
│ └── test_docs.py # Documentation checks (links, API inventory, punctuation)
│
├── scripts/
│ ├── build_map_styles.py # Basemap styles
│ ├── build_skytonight_catalogue.py # Offline deep-sky dataset build
│ ├── check_cpu_compat.sh, cpu_compat_smoke.py # x86-64 baseline check of the image
│ ├── minify_static.py # CSS/JS minification for the image
│ ├── translate_checker.py, validate_i18n.py # i18n parity checks
│ └── translate_i18n_values.py # Machine translation of new keys
│
├── docs/ # Documentation (index: docs/README.md)
├── CHANGELOG.md, ROADMAP.md, CONTRIBUTING.md, SECURITY.md, DOCKER_HUB.md
├── Dockerfile, entrypoint.sh, docker-compose.yml, docker-compose-dev.yml
├── pyproject.toml # ruff configuration
├── pyrightconfig.json, pytest.ini
├── requirements.txt, requirements-dev.txt
└── VERSION
Runtime data (data/)
data/
├── myastroboard.db # SQLite: accounts, configuration, settings, session and VAPID keys,
│ # Astrodex, equipment, plans, observation log, wishlist
├── myastroboard.log (+ .1 ... .5) # Application log, rotated at 10 MB
├── astrodex/images/ # Astrodex pictures (<user_id>_...)
├── observation_sessions/attachments/ # Observation Log attachments (<user_id>_...)
├── backups/ # Pre-1.7 JSON archive and import report (1.7 upgrade only)
├── cache/
│ ├── astro_cache.json # Shared cache slots ("<job>:<location_id>")
│ ├── iers/, skyfield/ # Earth orientation data, planetary ephemeris
│ ├── map_tiles/ # OpenStreetMap tiles, glyphs, sprites
│ ├── object_images/, framing_images/ # DSS2 sky images (capped at 200 MiB)
│ └── spaceflight_images/ # Launch and astronaut pictures
└── skytonight/
├── catalogues/targets.json # Built deep-sky dataset
├── calculations/ # Tonight's results (dso, bodies, comets, sky map)
├── outputs/ # Altitude-vs-time series per target
├── logs/last_calculation.log # Last run log
└── runtime/ # Scheduler status, trigger file, lock
Never delete data/myastroboard.db (or its -wal / -shm companions while the app runs): it also
holds the session key and the VAPID keys, and a new VAPID key silently breaks every push
subscription.
Key data flows
Browser request
└─ backend/blueprints/<domain>.py
├─ @login_required / @admin_required (utils/auth.py)
├─ location of the request: utils/route_helpers._resolve_active_location()
├─ cache hit -> cached JSON for that location (cache/cache_store.py)
└─ cache miss -> "pending" until the background scheduler fills the slot
cache/cache_scheduler.py (every 25 min)
└─ cache/cache_updater.py: each job x each location whose TTL ran out
├─ astroweather/* (moon, sun, eclipses, aurora, horizon, seeing)
├─ weather/* (Open-Meteo, astro-analysis), space/* (ISS, CSS, launches)
└─ observation/* (planetary events, phenomena, solar system events)
skytonight/skytonight_scheduler.py (startup, 1 h after nautical dawn, 1 h before nautical dusk)
└─ skytonight_calculator.run_calculations()
├─ targets.json + comets + bodies
├─ observability, AstroScore, difficulty per target
└─ data/skytonight/calculations/*.json, outputs/<target_id>_alttime.json
Last updated 2026-11-13