1. The work of putting an existing tool on a site
Integrating Network Check into OSIIX wasn't about building a new tool. The real work was taking check logic that was already running and repositioning it inside OSIIX's look and URL structure.
The first question this kind of work runs into isn't how to implement the functionality. The check processing, API, and tested safety checks already exist. The question is how far it's acceptable to touch them, and where the line should be drawn for what counts as a site-specific change.
This article isn't a feature introduction for Network Check itself. What Network Check does is already explained on the Project page. What this article covers is where the site-specific differences were placed while keeping the existing core intact, and how that boundary affected implementation, verification, and failure isolation.
2. The first decision was what not to change
As far as the Git record shows, at least the following were kept out of scope for change in the OSIIX composition/UI implementation. What Git lets us confirm is not the mindset at the time, but the change boundary itself. This article looks at which responsibilities that boundary left on the existing side, and which differences it placed on the site-specific side, as a result.
- the check core itself
- the API contract
- check IDs
- execution order
- SSRF / TOCTOU protections
- timeout policy
- the usage-metrics implementation
What did change, on the other hand, was composition, templates, CSS, presentation, and the public base path.
One thing worth noting here: the record doesn't let us assert that this design was chosen in advance specifically to prevent regressions. What Git shows is only the fact of what ended up out of scope for change and what changed. That said, it's fair to say this separation ended up making it harder for the change range and regression-check range on the core side to expand. That's not a reconstruction of implementation intent — it's an interpretation that can reasonably be drawn from the facts.
3. Placing OSIIX-specific differences in composition
The OSIIX version isn't a fork of Network Check itself. apps/osiix.py is an OSIIX-specific ASGI composition root, built by calling create_network_check_app(), which the shared apps/composition.py provides.
apps/osiix.py is mainly responsible for three things:
- giving
templates/osiix/priority over the shared templates - calling
create_network_check_app(), provided by the sharedapps/composition.py - excluding the usage-metrics route from the OSIIX composition
In other words, this isn't a case of writing new check processing for OSIIX. The OSIIX-specific way of assembling things lives in apps/osiix.py, which reuses the shared apps/composition.py underneath it. The OSIIX-specific layer ends at apps/osiix.py and presentation-side differences like templates/osiix/; everything below apps/composition.py is a shared implementation used by multiple source entrypoints.
4. A Core / UI split alone doesn't fully explain it
It's tempting to describe everything so far with the simple model of "splitting Core and UI," but representing the implementation accurately requires one more level of distinction.
Individual checks and Domain Multi Check actually take different paths. Not every check passes through the same Application layer.
As the figure shows, Individual checks connect directly to Checks / Core without going through the Application layer, while Domain Multi Check goes through the Application layer to reach Checks / Core. This difference in path is exactly what a simple two-layer model can't express. So what this article is really about isn't a two-layer split between a UI layer and a Core layer — it's placing the necessary composition/application boundary between site-specific presentation and the existing check implementation. At the very least, there's no need to force this implementation into a classification like Clean Architecture or MVC to explain it. Looking directly at the actual paths and responsibilities describes this structure more accurately.
5. URL and base path were part of the design too
Integrating into a site isn't just about appearance. In OSIIX, Network Check sits under the public path /tools/nc/, and this base path ripples through every link in the templates, every static asset, every API call, and the guide route.
tests/test_osiix_composition.py renders with PUBLIC_BASE_PATH=/tools/nc set, and confirms that the result includes at least the following paths.
/tools/nc/static/osiix-network-check.css/tools/nc/api/multi-check/run/tools/nc/static/multi_check.js/tools/nc/network-check/guide/
What this fact shows is that the base path isn't something bolted on after deployment and adjusted only on the nginx side — it's an input to the presentation-side implementation and its tests. Treat the base path as a variable, and keep it in a state where you can test that it propagates correctly into templates and API calls. In a site integration, the URL structure isn't a follow-up detail to adjust after implementation — it becomes a design input that needs to be verified up front.
6. Splitting Core/UI still doesn't finish publication
Even after sorting out the boundary between Core and UI, that alone doesn't guarantee the site is actually in a published state. The real path is a bit longer. Whether the application is correctly assembled in the source code, and whether that application actually starts as a service and is reachable as a public URL through a reverse proxy, are separate things to verify. The correctness of the logic and the correctness of the public route can't both be confirmed by the same test.
7. Application health and public-route health are verified separately
Splitting the Core/UI boundary doesn't, by itself, mean "the public route is healthy too." In fact, OSIIX Network Check's operational records treat application/service health and public-route health as clearly separate items to check. Direct access to the loopback health endpoint, the systemd service's running state, and the results of accessing it over public HTTPS at https://osiix.com/tools/nc/, for example, are each recorded independently.
The compatibility redirect from the uppercase path /tools/NC to the canonical /tools/nc/ is also recorded as one of these public-route checks. Handling the canonical route and its capitalization variant was treated, from the route-contract design stage, as a public-route concern distinct from the application-side check logic.
What matters here is that layers like application, service, reverse proxy, and static/public routing each need to be treated as independently verifiable targets. Accessing the application locally/directly to confirm it's healthy, confirming the service is running, and accessing the public URL through the reverse proxy to check the result — these are separate verification steps. Being able to say how far you've confirmed things are healthy, and from where things are still unconfirmed, is what actually makes failure isolation workable.
8. Separation also narrows the failure-investigation scope
The benefit of keeping boundaries separate isn't limited to bounding the change range. It also lets you think, boundary by boundary, about how far a given confirmation of "healthy" actually extends.
For example, a passing source-side test is evidence about the implementation on the source side, but it doesn't by itself prove the runtime public route is healthy. A passing loopback health check is evidence that the application/service is up and responding, but it doesn't prove the path is healthy beyond the reverse proxy. Being able to reach the public URL successfully is evidence that it's reachable as a public route, but it doesn't prove the content of the check result is semantically correct.
In other words, the verification result at each boundary isn't a single "everything is healthy" verdict — it accumulates as partial evidence, valid only up to that point.
For example, even if the public URL returns a 404, that alone doesn't mean the cause lies in the check logic itself. You need to isolate, one by one, how far along the HTTP path — application route, reverse proxy, static routing — things are still healthy. Conversely, if the content of the check result the API returns looks wrong, you need to go back and check the application layer or the check implementation itself.
Keeping boundaries separate lets you narrow the investigation down to the space between the last boundary confirmed healthy and the first boundary where something abnormal was found. This is a generalized lesson that can be drawn from this implementation — it isn't specific to Network Check. A boundary doesn't just limit the change range at implementation time; it also defines the investigation scope for "where to start suspecting" when something goes wrong.
9. Design principles for integrating an existing tool into another site
Here are the principles that can be drawn out of this implementation experience, stated in a generalized form. These aren't conclusions specific to Network Check — they're organized as a perspective that can be reused when integrating an existing CLI/API/internal tool into another website.
Design time
- Decide the "responsibilities that won't change" first
- Push site-specific differences toward the composition / presentation side
- Treat the URL / base path as an architecture input
Verification time
- Verify application health and public-route health separately
- Treat the reverse proxy and the service as part of the execution architecture too
Operations time
- Don't duplicate existing logic for the convenience of a new UI
- Think about the rollback boundary in the same responsibility units
A note on point 7: this implementation doesn't have a complete rollback procedure in place. What's being raised here is a generalized point — that keeping the boundaries clear makes it easier to think in the same responsibility units when a rollback is eventually designed.
10. Summary
What mattered in this integration wasn't leaving the core untouched as such — it was deciding, for each reason a change was needed, where to place the resulting difference.
What needed to change split into existing-side responsibilities — the check core, API contract, check IDs, execution order, SSRF/TOCTOU protections, timeout policy — and site-specific responsibilities — composition, templates, CSS, the public base path. The URL and base path also became design and test targets as inputs to composition and templates, rather than follow-up details adjusted after deployment. Application health and public-route health were each treated as separate verification targets, which also helped narrow down what to suspect when something went wrong.
Putting an existing tool on another site is, in most cases, less about how to build the UI, and more about deciding where to draw the line for what counts as a site-specific change.
This article is a personal development record. It does not claim to be a general standard or the only correct approach. Real names and infrastructure details for the underlying project (server paths, IP addresses, credentials, etc.) are deliberately omitted. See the site-wide disclaimer for more.