Deployment — Server Requirements¶
What a production server must provide. Every requirement below traces to something in the code or the publish profiles; where the source cannot tell us — hardware sizing, for instance — this page says so rather than guessing.
Platform¶
| Requirement | Why |
|---|---|
| Windows Server | System. 6.0 is Windows-only from .NET 6 onward and is used for images and signatures; PDF export shells out to wkhtmltopdf.; and the code reads and writes hard-coded C:\upload\… paths. |
| IIS with the ASP.NET Core 6.0 Hosting Bundle | The application is published framework-dependent (Self) for net6., so the server supplies the runtime. The Hosting Bundle installs the runtime and the ASP.NET Core Module that IIS uses to host the app in-process (the .NET 6 default; the project does not override Asp). |
| A site of its own, at the root of its host name | The views contain about 130 root-absolute URLs (href="/, action="/, url: "/) across 89 files, and the SignalR client connects to with. Under an IIS sub-application such as https:/ those all point outside the app. |
| Web Deploy (remote agent service) | Both publish profiles use MSDeploy with MSDeploy. See Build & Publish. |
| SQL Server | Three databases — see Installation — Database Setup. |
wkhtmltopdf in wwwroot\Rotativa\ |
Every PDF export. Not in the repository; must be placed by hand (R5-3) — and protected from deletion on publish (R4-3). |
Views/web.config is not the site's web.config
The repository contains NKO.Main/NKO.Main/Views/web.config. It is ASP.NET MVC 5 configuration
(System.Web.Mvc 5.2.3, System.Web.WebPages.Razor) left over from an earlier version of the
application, and ASP.NET Core ignores it. The real web.config at the site root is generated by
dotnet publish.
.NET 6 is out of support
The runtime this application needs stopped receiving security patches in November 2024 (R1-7). Installing the 6.0 Hosting Bundle on a new server is installing an unpatched runtime.
The application pool¶
The three background schedulers — Quartz (UpdateNextUserJob at 03:00), Hangfire, and
BirthdayGreetingsJob — all run inside the web worker process. When IIS stops that process,
they stop with it, and none of them catches up afterwards. The pool must therefore be kept alive.
| Setting | Value | Why |
|---|---|---|
| .NET CLR version | No Managed Code | ASP.NET Core does not use the CLR integrated pipeline |
| Start mode | AlwaysRunning | Start the process without waiting for the first request |
| Idle time-out | 0 (disabled) | The default 20 minutes would stop the nightly job and the birthday mail on a quiet server |
| Site: Preload enabled | True | Runs Startup — and so the schedulers — immediately after a recycle |
| Maximum worker processes | 1 | See One instance only |
| Load User Profile | True | Lets ASP.NET Core persist its Data Protection keys — see below |
These are standard IIS settings recommended for ASP.NET Core apps that run background work; the code-level reason they matter here is that no scheduler in this application has durable state or catch-up (R1-3).
Data Protection keys¶
ASP.NET Core encrypts the sign-in cookie and every anti-forgery token with its Data Protection keys.
The application configures none — there is no AddDataProtection() call — so where the keys
live depends on the host. Under IIS they are persisted only when the app pool loads its user profile
(or the pool's registry hive has been provisioned); otherwise they are kept in memory and a new
set is generated on every recycle. The visible symptom is that every recycle signs every user out
and any form that was open fails its anti-forgery check. Setting Load User Profile avoids this.
Tracked as part of R4-5.
Permissions for the pool identity¶
| Path | Access | Why |
|---|---|---|
| The site folder | Read | Normal hosting |
wwwroot\Rotativa\ |
Read and execute | Runs wkhtmltopdf. |
C:\upload\ (whole tree) |
Read and write | Profile images, exports and templates — see below |
The C:\upload folder tree¶
The code reads and writes these hard-coded paths. Nothing in the application creates them —
there is no Directory.CreateDirectory call anywhere — so each must exist before the feature that
uses it runs, or that feature throws.
| Path | Used by | Direction |
|---|---|---|
C:\upload\nko\files\user |
Employee profile images — Account (upload), Base, Ware, General |
read / write |
C:\upload\nko\templates\e-sign. |
Files — the e-signature request form |
read — file must be placed |
C:\upload\nko\templates\contract. |
Human — employee contract export |
read — file must be placed |
C:\upload\ataa\ware\asset\ |
Asset and stock exports — Asset, Stock, Export |
write |
C:\upload\ware\asset |
Destruction export — Destruction |
write |
C:\upload\ware\asset\ |
Destroyed-assets report — Export |
write |
nko comes from SiteSettings.LowerSiteName; the rest are literal strings.
Three different roots, one of them another product's name — R4-2
The same kind of file lands under C:\upload\nko\…, C:\upload\ware\… or
C:\upload\ataa\ware\…. "Ataa" is not this product — it also appears in .gitignore
alongside Ataa.Main and Ataa.Warehouse, which suggests this code was derived from a sibling
project and those paths were never renamed. They still work, as long as the folders exist.
Network¶
| Direction | Endpoint | Port | Used for |
|---|---|---|---|
| Inbound | the site | 443 (and 80, redirected) | Browsers. Use and — outside Development — Use are on, so a TLS certificate is required. |
| Outbound | SQL Server | 1433 | All three databases |
| Outbound | AWS S3 (the region in Storage) |
443 | Every document upload and download |
| Outbound | smtp. |
587 | All notification e-mail. The host is hard-coded in Email. |
| Loopback | the site's own public hostname | 443 | PDF headers and footers — see below |
No outbound access to data.fixer.io is needed: the exchange-rate client is in the code but is never called (R7-17).
The server must be able to reach itself¶
PDF exports in JobsController (four of them) and AdvanceController pass wkhtmltopdf switches
like:
Url.Action(..., "https") builds an absolute URL from the host name of the current request.
wkhtmltopdf then runs as a separate process on the server and downloads that URL. So:
- the server must resolve its own public host name and be allowed to connect to it (some firewalls and load balancers block this "hairpin" connection);
- the certificate must be one
wkhtmltopdf's own TLS stack accepts; - the site must be served over HTTPS — the scheme is hard-coded.
If any of these fail, those PDFs come out without their header and footer, or fail outright. These
two endpoints are [AllowAnonymous] precisely because wkhtmltopdf has no user session.
WebSockets¶
Real-time notifications use SignalR (/hub). Enable the IIS WebSocket Protocol feature;
without it SignalR still works but falls back to long polling, which holds a request open per
connected user.
One instance only¶
The application keeps state inside its process, so it must run as exactly one process on one server:
| State held in-process | What breaks with two processes |
|---|---|
| Quartz uses in-memory storage | Update fires at 03:00 in every process |
Birthday timer |
Every employee receives the greeting once per process |
| SignalR has no backplane | A notification reaches only users connected to the same process |
| Data Protection keys not shared | A cookie issued by one process is rejected by another — random sign-outs |
That rules out an IIS web garden (Maximum worker processes > 1) as well as a server farm. Tracked as R4-5.
Sizing¶
The source gives no basis for CPU, memory or disk sizing, and there are no load tests. Two facts are
relevant when choosing: documents live in S3, not on the server, so local disk holds only the
C:\upload tree and logs; and the top bar runs several unindexed NextUserId counts on every
page render (R2-2), so database capacity matters more than web-server capacity.