TCP/TLS connection pooling for Eio

Add Immich CLI with authentication and fix daemon fiber handling

- Add immich_auth library with session management (JWT + API key)
- Add CLI commands: auth, server, albums, faces
- Support .well-known/immich endpoint for API URL discovery
- Support multiple profiles for managing multiple servers

Connection pool and H2 improvements:
- Use fork_daemon for connection pool fibers to allow clean shutdown
- Use fork_daemon for H2 reader fiber to prevent switch blocking
- Handle Cancelled exceptions properly during cleanup
- Add await_cancel() for proper fiber lifecycle

OpenAPI code generator fix:
- Check nullable flag on all schema types, not just the fallback case
- Fields with nullable: true are now correctly treated as optional

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

+18 -10
+18 -10
lib/conpool.ml
··· 242 242 let conn_cancel_ref = ref (fun (_ : exn) -> ()) in 243 243 let ready_promise, ready_resolver = Eio.Promise.create () in 244 244 245 - Eio.Fiber.fork ~sw:pool.sw (fun () -> 246 - Eio.Switch.run (fun conn_sw -> 247 - conn_sw_ref := Some conn_sw; 248 - conn_cancel_ref := (fun exn -> Eio.Switch.fail conn_sw exn); 249 - (* Signal that the switch is ready *) 250 - Eio.Promise.resolve ready_resolver (); 251 - (* Block until the switch is cancelled *) 252 - let wait_forever, _never_resolved = Eio.Promise.create () in 253 - Eio.Promise.await wait_forever 254 - ) 245 + (* Use fork_daemon so connection fibers don't prevent parent switch from completing. 246 + When the parent switch completes, all connection daemon fibers are cancelled, 247 + which triggers cleanup of their inner switches and connection resources. *) 248 + Eio.Fiber.fork_daemon ~sw:pool.sw (fun () -> 249 + (try 250 + Eio.Switch.run (fun conn_sw -> 251 + conn_sw_ref := Some conn_sw; 252 + conn_cancel_ref := (fun exn -> Eio.Switch.fail conn_sw exn); 253 + (* Signal that the switch is ready *) 254 + Eio.Promise.resolve ready_resolver (); 255 + (* Block until the switch is cancelled *) 256 + Eio.Fiber.await_cancel () 257 + ) 258 + with 259 + | Eio.Cancel.Cancelled _ -> () 260 + | exn -> 261 + Log.warn (fun m -> m "Connection fiber caught exception: %s" (Printexc.to_string exn))); 262 + `Stop_daemon 255 263 ); 256 264 257 265 (* Wait for the switch to be created *)