C:\GRIFFIN\PROJECTS\SOFTWARE\SCANTA>

[@] MAP
###C############
#...........$..#
#.[]..[]..[]...P
#......@.......#
#.[]..[]..[]...#
#..............#
######H#########
SOFTWARE/SCANTA 

Doors are hotkeys. Or click the screen and walk with the arrow keys.

Scanta

A self-hosted photo-scanning pipeline that resumes interrupted imports from a ledger without re-uploading completed files.

Status
Working private tool
Stack
C#/.NET · Microservices · Authentication · ASP.NET Core · Self-hosted

At a glance

  • Outcome: Gets a stack of scanned family prints from a Windows desktop into a self-hosted Immich library behind a home firewall, without the PC or the public server ever holding the archive’s credentials.
  • Status: Milestones M1 through M8 are deployed on the author’s own infrastructure: OIDC-protected intake on a VPS and the importer beside Immich on a homelab node, where it has imported real scanned photos into Immich. Automatic crop and deskew are not yet reliable, so the app falls back to manual crop when they lack confidence.
  • Role: Built every component solo.
  • Contribution: I wrote the design doc, the ADRs, the runbooks and the shared contracts every service compiles against, then built and deployed the four services.
  • Stack & libraries: C#/.NET, Avalonia + NAPS2, ASP.NET Core, .NET Worker, EF Core/SQLite, Tailscale, ntfy.
  • Source: Five private MIT-licensed repositories: an umbrella and four services.
  • Validation: xUnit and WebApplicationFactory HTTP and contract tests POST canonical Scanta.Contracts fixtures verbatim; two opt-in tests run against a real Immich, one covering the whole pipeline. A forced mid-import crash against a development Immich resumed from the ledger.
  • Limitations: Date and album handling, including approximate sorting and contributor sharing, has not been tested end to end, and a device asset ID has not been replayed against a production server.

Scanta moves scanned family photos from a Windows PC into Immich, a private photo library running on a home server. You scan, note roughly when each photo was taken, pick an album and upload. Dates can be vague, like “summer, sometime in the seventies,” so a photo can be dated to a day, month, season, year or decade, or left blank. Each photo sorts to the middle of its date range instead of January 1. For important batches, Scanta also keeps the original scan next to the cleaned-up copy.

The home server stays off the public internet, and the family PC never holds the library’s password. Uploads go first to a separate drop-off server online. It checks the sender and the batch, then holds it. It never gets the library’s password. A program on the home server reaches out over a private connection, picks up the batch and adds the photos. All three pieces share one C# definition of a batch, so if one piece changes it without the others, the code won’t build. That definition was first planned as a language-neutral format with generated code, when some pieces might have been written in Go or TypeScript. Once everything was C#, a shared C# package was simpler and just as safe.

The photo library can’t undo a half-finished import. Instead of rolling back after a crash, the importer keeps a list of every step it finished. A retry skips those steps. Each photo’s ID is based on its contents, which means the same file always gets the same ID. If the library already has a photo, that counts as success. The tradeoff is speed: one batch imports at a time.

The drop-off server runs online and the importer runs at home next to the library. It has imported real scanned photos. Alerts go to the owner’s phone without flooding it. Backups have been tested by restoring them into a spare copy of the library. Next up: better automatic cropping and straightening, to remove the manual crop step, and full testing of dates and albums.