README.md
Rama main · réplica de lectura del repositorio original.
# NimShare
**Self-hosted, brand-styled file sharing on Azure — with signatures, license-key delivery, AI, and a native iOS app.** Upload once, share via personalised links with expiry, password, download limits, subdomains, custom domains, QR codes and file-request (upload) links. Multi-user with fine-grained permissions, native iOS app, EFIGS+NL localised, and AI features that actually help (chat with your files, smart tags, semantic search, humorous greetings).
Built on **ASP.NET Core 8 + Azure App Service + Azure Blob Storage**. Deploy in one click.
[](https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2Fmnimtz%2Fnimshare%2Fmain%2Finfra%2Fazuredeploy.json)
[](http://armviz.io/#/?load=https%3A%2F%2Fraw.githubusercontent.com%2Fmnimtz%2Fnimshare%2Fmain%2Finfra%2Fazuredeploy.json)





---
## What's inside
NimShare grew from a link-sharing MVP into a full file-collaboration platform. Highlights, grouped by area.
### Sharing links (the original superpower)
- **Custom slugs** — `nimshare.com/s/project-x` instead of random tokens.
- **Password protection** — bcrypt-hashed, verified per request.
- **Expiration** — absolute date/time or "burn after N downloads".
- **Download limits** — hard cap per link, atomically counted.
- **Recipient allowlist** — restrict a public link to specific email addresses.
- **Optional email-gate + one-time-passcode** before download.
- **Multiple links per file** — different rules for different audiences.
- **Branded landing page** with optional message, preview, and AI auto-summary.
- **QR code** auto-generated for every link.
- **One-click revoke** without deleting the file.
- **Access statistics** per link — hits, downloads, referrers, country + city, device type, timezone, IP hashes, peak-hour heatmap.
- **Public links** admin-curated, visible to all users under **Meine Links**.
- **Subdomain links** — once a base domain is configured (**Settings → Domains**), any link is also reachable at `https://{slug}.yourdomain.com`, with optional automatic wildcard-DNS provisioning via a Cloudflare API token.
- **Serial number reveal** — attach a fixed license/serial code to a link; released to the visitor only after the same password / email-allowlist + one-time-passcode gate other links use.
- **Sender-identity badge** — attach one of your certificates to a link; the landing shows a discreet "✓ Signed by …" badge with a details popover and a public `.cer` download (a visual trust indicator, not a cryptographic signature of the payload).
- **Emergency direct-download** — for password-less links, generate short-lived (48 h) direct Azure Blob links per file that bypass nimshare.com entirely, for recipients whose corporate proxy blocks the app's domain but trusts Microsoft's.
### Reverse-share (upload requests)
- Generate a public link that lets **someone else send you a file** — no account on their side.
- Same guard rails: expiry, max uploads, password.
- **AI-drafted cover message** personalised per recipient.
- **Recurring upload requests** for regular collections (monthly reports etc.).
### File browser (Seafile-style)
- Three scopes: **Personal**, **Public** (visible to all authenticated users), and **Group** (per-team).
- Full folder hierarchy with breadcrumbs, tree panel, drag-and-drop upload, bulk selection.
- **Grid vs. list view**, right-click context menu, hover actions.
- **Inline preview** for images and PDFs; **Office preview** via LibreOffice-headless for .docx/.xlsx/.pptx.
- **Precise file-format icons** (PDF, DOCX, PNG, MP4, ZIP, …) — same design on web and iOS.
- **Folder ZIP download** — streams recursively as a proper file (temp-file, fault-tolerant per blob).
- **Bulk ZIP** for selected files, or single-file "as ZIP".
- **Move / Copy / Rename / Delete** for both files and folders, with target-folder picker.
- **Favourites**, **Trash / Restore**, **Recent activity feed**, **Pinned items**.
- **Full-text search** across files and folder names.
- **File versioning** — every replace keeps the previous version, restorable.
- **File locking** + **OnlyOffice** integration for concurrent editing.
### Photo & media galleries
- Folders can be typed as photo/video **albums**; independently, any folder share link can render as a **grid + lightbox gallery** instead of a plain file list.
- **GPS map** on the landing page, derived from EXIF data (owner-controlled opt-out toggle per link).
- Thumbnail generation runs on a **persistent background worker queue** — one job per file builds both preview sizes and extracts GPS in a single blob read, reliable even on low-CPU hosts.
- **AI-generated tags** on uploads (gallery included), shown in the visitor's own language.
### Permissions ("Windows-ACL for public folders")
- A public folder can be marked **private** — only creator, admin, and explicitly-granted users see it.
- **Direct-shares** grant read/write to individual users or entire groups.
- **Sub-folder permission overrides** (e.g. cap child folder to read-only even when parent is write).
- **Auto-privatize**: setting a group/user permission on a public folder automatically switches it to private — so "share with group X" actually means "restrict to X", not "additive".
- **🔒 badge** on private folders, permissions modal via right-click.
### Digital signatures (DocuSign-alike)
- Draw signatures with the mouse (web) or **Apple Pencil** (iOS, via PencilKit).
- Visual field placement on the PDF with pdf.js + drag-drop.
- **Sequential delivery** with per-recipient deadlines and auto-reminders.
- **Bring-your-own certificate** (PKCS#12 upload) for cryptographic signing, or platform-managed certificates.
- **Signature audit log** with forensic fields (country, city, device, timezone, IP hash).
- Full **REST API** for signature requests; native iOS Requester + Signer flows.
### Trust & certificates
- Every instance runs its own self-signed **Root CA**; personal signing certificates issued in-app are signed by it (bring-your-own PKCS#12 works too, without the CA).
- Recipients download/import the CA certificate **once** (`/nimshare-root.cer`) and every future sender badge on the instance then validates without security prompts.
- Certificate lists flag **expired / expiring** certificates for the owner.
### License management (Key-Store)
- A per-user CRM for customer license keys: name, email (or an email-domain wildcard), a free-text key type, an encrypted key value, and an optional validity window.
- Put a share link into **Key-Store mode** — the visitor enters their email, and the matching key (by exact address or domain) is revealed automatically. Matching is scoped to the link owner's own customers.
- Attach **documentation** per key type — uploaded PDFs and/or external links — shown as extra buttons next to the revealed key.
- **AI-drafted email templates** for sending a key to a customer, locale-matched to the language the visitor viewed the page in; optional PDF attachments on send.
### AI features (opt-in, provider-agnostic)
Bring your own key for **Gemini, OpenAI or Anthropic**, or leave the AI Gateway disabled.
- **Chat with your files** — RAG over per-file embeddings, cosine-search on the fly.
- **Auto-summary** on public share landing pages.
- **Smart tags** — auto-classify uploads on completion.
- **Semantic search** across your library.
- **Vision-AI for images** via `DescribeImageAsync`.
- **OCR + language detection** for scanned PDFs.
- **AI-drafted share emails** and **upload-request cover messages** personalised per recipient.
- **Content risk detection** on public uploads (flags problematic files for review).
- **AI-drafted email templates** for signature invites.
- **Home-screen greeting** — witty, localised (EFIGS+NL), with today's weather (Open-Meteo, no API key) and optional cloud-status summary from a configured [incident.io](https://incident.io/) status page.
- **Reindex button** to rebuild embeddings after schema changes.
### Contacts, notifications, bookmarks
- **Address book** with import from CSV.
- **Notifications tray** with badge count; per-event settings.
- **Bookmarks** — company-wide curated link collection (admin-managed), with emoji-picker; useful for pinning internal tools and portals.
### Native iOS app (SwiftUI, iOS 17+)
- **Adaptive one-screen home** — tiles auto-size to fill the viewport; personalised greeting + weather live in the greeting card.
- Full file browser with long-press context menu (share, permissions, move, copy, rename, delete, upload-request, sub-folder).
- **Signatures**: request creation, visual field placement, and signing via **PencilKit**.
- Favourites, Trash, Activity, Shared-with-me, Bookmarks — parity with the web UI.
- Custom server URL configurable per user; default `https://nimshare.com`.
- Localised in de/en/fr/it/es/nl; explicit `Accept-Language` so the KI greeting always matches the app UI.
- Two-factor sign-in (TOTP) supported end-to-end.
### Admin tools
- **User management** — invite via email, disable/enable, change role, delete, per-user quota, "last login" column.
- **Backup & Restore** — full DB export (JSON, all 35 entity types, topological FK ordering, IDENTITY-safe on SQL Server), one-click download, upload+restore with typed "RESTORE" confirmation. Self-lockout protection: aborts before wipe if the acting admin isn't in the incoming backup.
- **Email gateway** — SMTP or Resend.
- **AI gateway** — provider, model, temperature, status-page URL + product filter.
- **Office gateway** — OnlyOffice URL.
- **Signature certificates** — upload PKCS#12, view details.
- **API tokens** (scoped) and **webhooks** for integrations.
- **Landing templates** (global admin default + per-user override).
- **Content moderation** — flagged uploads inbox.
- **Blocked users** and **user-reporting** workflow.
### Auth & multi-tenancy
- **Local email + password**, always available. First user gets the Admin role via first-run setup wizard.
- **Microsoft Entra ID** optional, layered on top. Existing local accounts auto-link by email on first Entra sign-in.
- **Two-factor auth (TOTP)** with QR-code setup.
- **Session cookies** for web, **JWT bearer** for API/iOS — every `/api/v1/*` endpoint accepts both.
- **Roles**: `User` and `Administrator`.
- Safety rails prevent demoting/deleting the last active admin.
### Custom domains
- Add `share.your-company.com` to your account under **Settings → Domains**.
- Verify via TXT record (checked via Google DoH, one-click "Verify" button with real result).
- Azure App Service Managed Certificate is issued automatically once DNS is aligned.
- Configure a canonical **`App__PublicBaseUrl`** so generated links always use your custom host, regardless of which host the request came in on.
### Localisation — **EFIGS + NL** by default
Every user-facing string ships in **English, French, Italian, German, Spanish, and Dutch** from day one. Public share landings respect the visitor's `Accept-Language`; the app's own UI has a language switcher that persists per user. AI-generated content (summaries, chat responses, greetings) is produced in the caller's language via ISO code.
### Tungsten-branded UI
Follows the [Tungsten Automation Brand Book](https://tungstenautomation.com/): primary navy `#002854`, accent blue `#00A0FB`, yellow CTAs `#FFC600`, Red Hat Display, blue-domain gradient sidebar. Consistent look-and-feel with sibling internal projects like [printix-tonerwatch](https://github.com/mnimtz/printix-tonerwatch).
---
## Architecture at a glance
```
┌──────────────────────────┐
Web · iOS · Public │ NimShare API │
link visitors │ ASP.NET Core 8 │
────────────────────►│ MVC + Web API + Razor │
└────────────┬─────────────┘
│ 1) Auth (cookie or JWT)
│ 2) Access rules (scope, DirectShare, IsPrivate)
│ 3) Link rules (pw, expiry, count, allowlist, revoked)
│ 4) Issue SAS (≤ 60 s TTL)
▼
┌──────────────────────────────┐
Direct ────────► │ Azure Blob Storage │
upload/download │ files/users/{uid}/{fid}/... │
└──────────────────────────────┘
┌──────────────────┐ ┌──────────────────┐ ┌────────────────┐
│ SQLite │ │ App Insights + │ │ AI Provider │
│ (Azure Files, │ │ Log Analytics │ │ (Gemini/OpenAI│
│ metadata) │ │ │ │ /Anthropic) │
└──────────────────┘ └──────────────────┘ └────────────────┘
```
- **SQLite only** — one clean, always-consistent metadata store on an Azure Files mount, fully covered by Backup/Restore. (No Azure SQL option — removed in v1.11.48 to eliminate an unused, unmaintained code path.)
- **API-first**: every feature has an `/api/v1/*` JSON twin; the iOS app and the Razor MVC UI both use the same endpoints.
- **Streamed uploads/downloads** direct browser/app ↔ Blob Storage via short-lived SAS — mobile data plans aren't burned tunneling through the server.
- **Custom-domain middleware** looks up the incoming host to pick per-tenant branding for share landings.
Deep-dive in [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) and [`docs/CONCEPT.md`](docs/CONCEPT.md).
---
## Deploy
### 1-click on Azure
Click the **Deploy to Azure** button at the top. Only one parameter is required:
| Parameter | Default | Meaning |
|---|---|---|
| `siteName` | *(required)* | Globally unique App Service name → `https://<siteName>.azurewebsites.net` |
| `sku` | `B1` | Plan SKU. `B1` ≈ 10 EUR/month, always-on. `F1` free but sleeps. `S1`+ needed for free managed TLS on custom domains. |
| `location` | resource-group region | Azure region |
| `containerImage` | `ghcr.io/mnimtz/nimshare:latest` | Or pin a specific tag for reproducibility |
| `tz` | `Europe/Berlin` | IANA timezone for link timestamps |
| `defaultLang` | `en` | Fallback UI language (`en`/`de`/`fr`/`it`/`es`/`nl`) |
What the template provisions:
- **Linux App Service** running the container from GHCR (`ghcr.io/mnimtz/nimshare`)
- **Storage Account** with a `files` Blob container for user uploads and a `nimshare-data` File share mounted at `/data` for the SQLite metadata DB
- **Managed identity** with `Storage Blob Data Contributor` — no connection strings, no account keys in app settings
**No SQL Database, no Key Vault, no Entra ID parameters up front.** The app boots and the welcome page renders immediately.
### First run
Open your site → NimShare shows a **First-run setup wizard**. Enter your email, name and password → that account is the first Administrator, you're signed in and land on the dashboard.
### Optional: Entra ID sign-in on top
For "Sign in with Microsoft" alongside the local login, set two App Service application settings:
- `AzureAd__TenantId` — your tenant GUID (or `common` for multi-tenant)
- `AzureAd__ClientId` — the app registration's Application (client) ID
Restart the App Service. The login page then shows both.
### Optional: canonical link host
If you serve NimShare on `nimshare.com` but Azure keeps the `*.azurewebsites.net` host active as a fallback, set an app setting so every generated link uses your domain regardless of which host the request came in on:
```
App__PublicBaseUrl = https://nimshare.com
```
### Optional: connectors (OneDrive Business import)
Since v1.10.163, users can connect an external cloud storage and import folders/files directly into their NimShare Personal area (cloud-to-cloud streaming, no local download). Currently supported: **OneDrive Business** via Microsoft Graph.
1. Register the app in Entra ID: <https://entra.microsoft.com> → App registrations → New → Web client. Redirect URI: `https://YOUR-HOST/settings/connectors/onedrive/callback`.
2. Delegated API permissions: `Files.Read` + `User.Read` + `offline_access`. Grant admin consent for your tenant if you want tenant-wide.
3. Create a Client Secret and copy its value.
4. In Azure App Service → Configuration:
```
Connectors__OneDrive__ClientId = <client-id>
Connectors__OneDrive__ClientSecret = <secret-value>
Connectors__OneDrive__Tenant = common # or a specific tenant GUID
```
5. Restart. Users see `🔌 Connectors` in the sidebar and can add a OneDrive connection.
Without those settings the app runs normally; only the "Connect OneDrive" button responds with a config-missing error.
### iOS app
The iOS app is a SwiftUI project under [`ios/`](ios/). It's distributed via the App Store (bundle `email.nimtz.nimshare`). To build locally:
```bash
cd ios
xcodegen generate # regenerates NimShare.xcodeproj from project.yml
open NimShare.xcodeproj
```
The default server URL is `https://nimshare.com`; each user can change it in **Profil → Server ändern**.
### Cost estimate
- App Service `B1`: ~10 EUR/month
- Storage account (100 GB Blob + 5 GB File share): ~2 EUR/month
- **Total: ~12 EUR/month** at personal-use volumes
See [`docs/COSTS.md`](docs/COSTS.md).
### GHCR image visibility
First deploy: make the container package public at <https://github.com/users/mnimtz/packages/container/nimshare/settings> → *Change visibility* → *Public*. Without this, App Service can't pull the image.
### Local development
```bash
git clone https://github.com/mnimtz/nimshare.git
cd nimshare
~/.dotnet/dotnet restore # SDK 8.0.423 required; check `dotnet --list-sdks`
~/.dotnet/dotnet run --project src/NimShare.Api
```
Open <http://localhost:5099>. Uploads use an Azurite local Blob emulator:
```bash
docker run -d --name azurite -p 10000:10000 -p 10001:10001 -p 10002:10002 mcr.microsoft.com/azure-storage/azurite
```
For pure UI/CSS work, the public share landing (`/s/{slug}`) and the welcome page render without auth.
---
## API
Full OpenAPI spec is served at `/swagger` in Development, JSON at `/openapi/v1.json` in any environment. Quick reference in [`docs/API.md`](docs/API.md).
**All `/api/v1/*` endpoints accept both** the web session cookie AND a JWT bearer token — the iOS app uses the same endpoints as the web UI.
Notable public (unauthenticated) endpoints:
- `GET /s/{slug}` — download-share landing page
- `POST /s/{slug}` — password submit; 302 to a short-lived Blob SAS URL
- `GET /u/{slug}` — upload-request landing page
- `POST /u/{slug}` — receive file via per-request SAS write URL
- `GET /sign/{participantId}?t={token}` — signature-signer flow
- `GET /privacy` · `GET /support` · `GET /imprint` — required legal pages
---
## Repo layout
```
src/
NimShare.Core/ # entities, DbContext, migrations (SQLite)
NimShare.Api/ # ASP.NET Core app (MVC + Web API + Razor)
Controllers/ # /api/v1/*, /settings/*, share/upload landings
Services/ # AiProvider, BlobStorage, FileAccess, Backup, …
Views/ # Razor MVC UI (Tungsten-branded)
Resources/ # 6 .resx files (en/de/fr/it/es/nl)
wwwroot/ # site.css, JS, static assets
ios/
project.yml # xcodegen source of truth
NimShare/Sources/*.swift # SwiftUI iOS app
docs/ # ARCHITECTURE, CONCEPT, API, COSTS, DEV_SETUP
infra/azuredeploy.json # 1-click ARM template
VERSION # single source of truth for the app footer
```
---
## License
MIT — see [`LICENSE`](LICENSE).
**Trademark note:** *Tungsten Automation*, the Tungsten logo, and product names referenced in the design system are property of Tungsten Automation Corporation. This project uses their brand tokens for internal look-and-feel consistency only.