(* The production server. State is durable: trainees, their active routine, the workout in progress, and history all live in SQLite. There is no server-wide in-memory state. Configuration comes from the environment: - HITO_DB Caqti database URI. Default: sqlite3:hito.sqlite - HITO_SECRET Secret for signing session cookies and CSRF tokens. When unset, a random secret is generated for this run only, so sessions do not survive a restart. Set it in production. - HITO_PORT TCP port. Default: 8080 - HITO_SEED_PASSWORD Password for the seeded [blendux] account. Required; the server refuses to start when unset. Temporary, until public registration returns. The socket binds loopback only. Authentication is local username and password; sessions and CSRF tokens are signed with the secret. Public sign-up is closed: [Handlers.make] leaves [registration_open] at its [false] default, and a single account ([blendux]) is seeded on startup with the password from [HITO_SEED_PASSWORD]. Both are temporary until public registration returns. *) module Handlers = Hito_web.Handlers.Make (Hito_app.Sqlite_repo) module Service = Hito_app.Service.Make (Hito_app.Sqlite_repo) let getenv name default = match Sys.getenv_opt name with Some v when v <> "" -> v | _ -> default (* Public sign-up is closed for now, so the deployment runs on a single pre-seeded account. The password comes from the environment, never a hardcoded default. Seeding is idempotent: a username already present is a success, not an error — so rotating the password means updating the store, not re-running this. Remove this once public registration returns. *) let seed_single_user repo ~password = let service = Service.make ~repo in Service.register service ~username:"blendux" ~password |> Lwt.map (function | Ok _ | Error `Username_taken -> () | Error (`Username e) -> Format.eprintf "hito: cannot seed user: %a@." Hito_app.Trainee.pp_username_error e; exit 1) let () = let db_uri = getenv "HITO_DB" "sqlite3:hito.sqlite" in let port = int_of_string (getenv "HITO_PORT" "8080") in let secret = match Sys.getenv_opt "HITO_SECRET" with | Some s when s <> "" -> s | _ -> prerr_endline "hito: HITO_SECRET is not set; using a random secret. Sessions will \ not survive a restart."; Dream.to_base64url (Dream.random 32) in match Lwt_main.run (Hito_app.Sqlite_repo.connect db_uri) with | Error error -> Printf.eprintf "hito: cannot open database %S: %s\n" db_uri (Caqti_error.show error); exit 1 | Ok repo -> let seed_password = match Sys.getenv_opt "HITO_SEED_PASSWORD" with | Some p when p <> "" -> p | _ -> prerr_endline "hito: HITO_SEED_PASSWORD is not set; refusing to seed the \ account with a default. Set it and restart."; exit 1 in Lwt_main.run (seed_single_user repo ~password:seed_password); let handlers = Handlers.make ~repo () in Dream.run ~interface:"localhost" ~port ~error_handler:Handlers.error_handler @@ Dream.logger @@ Dream.set_secret secret @@ Dream.sql_pool db_uri @@ Dream.sql_sessions @@ Dream.router (Handlers.routes handlers)