Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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