---
title: "Air-gapped Server Setup"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Air-gapped Server Setup}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{r, include = FALSE}
knitr::opts_chunk$set(
  collapse = TRUE,
  comment = "#>"
)
```

## Scope

This guide covers CohortContrast deployment on air-gapped servers where internet access is not available at runtime.

It applies to both Python-backed workflows:

- `runCohortContrastViewer()` (GUI)
- `precomputeSummary()` (summary-mode artifacts)

## What differs in air-gapped environments

- You cannot use `installPythonDeps()` against online package indexes.
- You might have to provide local wheel files (`.whl`) for all required Python dependencies.
- Wheels must match:
  - target operating system
  - target CPU architecture
  - target Python major/minor version
- You should validate the Python environment before running GUI or precompute.

## Offline setup workflow

### 1. Prepare wheels on a connected machine

Use a machine that matches the target server runtime (OS, architecture, Python version).

```bash
mkdir -p /tmp/ccv_wheels/linux_x86_64
python3 -m pip download \
  --dest /tmp/ccv_wheels/linux_x86_64 \
  dash dash-bootstrap-components dash-ag-grid \
  pandas pyarrow numpy scipy \
  plotly diskcache psutil multiprocess \
  scikit-learn scikit-learn-extra

# Optional: compress for transfer
cd /tmp/ccv_wheels
zip -r linux_x86_64.zip linux_x86_64
```

Supported offline layouts for `installPythonDepsOffline()`:

- `packagesDir/linux_x86_64.zip`
- `packagesDir/linux_x86_64/*.whl`

### 2. Transfer wheels to the air-gapped server

Copy either:

- `linux_x86_64.zip` into a directory such as `/opt/cohortcontrast/packages`, or
- the wheel directory as `/opt/cohortcontrast/packages/linux_x86_64/*.whl`.

### 3. Configure Python in R

If virtual environments are available:

```{r, include = TRUE, eval=FALSE, echo=TRUE}
configurePython(
  pythonPath = "/usr/bin/python3",
  virtualenvName = "r-cohortcontrast-viewer",
  createVenv = TRUE
)
```

If `venv`/`ensurepip` is unavailable on the server:

```{r, include = TRUE, eval=FALSE, echo=TRUE}
configurePython(
  pythonPath = "/usr/bin/python3",
  createVenv = FALSE
)
```

### 4. Install dependencies from local wheels

When pip is available
```{r, include = TRUE, eval=FALSE, echo=TRUE}
installPythonDeps()
```

If there is no pip available
```{r, include = TRUE, eval=FALSE, echo=TRUE}
installPythonDepsOffline(
  packagesDir = "/opt/cohortcontrast/packages",
  platform = "linux_x86_64"
)
```

### 5. Validate before production usage

```{r, include = TRUE, eval=FALSE, echo=TRUE}
checkPythonDeps()
```

Confirm all required modules report as installed.

## Offline smoke test

### Summary precompute

```{r, include = TRUE, eval=FALSE, echo=TRUE}
summaryResult <- precomputeSummary(
  studyPath = "/data/studies/LungCancer_1Y",
  outputPath = "/data/studies/LungCancer_1Y_summary",
  clusterKValues = c(2, 3, 4, 5),
  minibatchKMeansCutoffPatients = 50000
)
```

### GUI in server context

On servers, prefer `mode = "server"` and `openBrowser = FALSE`.

```{r, include = TRUE, eval=FALSE, echo=TRUE}
runCohortContrastViewer(
  dataDir = "/data/studies",
  host = "0.0.0.0",
  port = 8050,
  mode = "server",
  logFile = "/data/studies/contrast_viewer.log",
  openBrowser = FALSE,
  background = TRUE
)
```

## Runtime mode guidance for servers

- `simple`: minimal logging/noise; good for local ad hoc use.
- `server`: file logging and stable hosted operation; recommended on air-gapped servers.
- `debug`: maximum diagnostics; use for troubleshooting only.

Optional export lockdown (common in controlled environments):

```{r, include = TRUE, eval=FALSE, echo=TRUE}
runCohortContrastViewer(
  dataDir = "/data/studies",
  mode = "server",
  allowExports = FALSE,
  openBrowser = FALSE
)
```

## Common failures and fixes

- `not a supported wheel on this platform`:
  - regenerate wheels for the server's exact Python version, OS, and architecture.
- `venv`/`ensurepip` errors:
  - use `createVenv = FALSE` and system Python.
- Missing modules after install:
  - rerun `checkPythonDeps()`, verify wheel directory contents, reinstall with `installPythonDepsOffline()`.
- Wrong interpreter selected:
  - set explicit `pythonPath` in `configurePython(...)`.
- Viewer starts but no logs written:
  - use `mode = "server"` and set `logFile` explicitly.
- Precompute exits with status `137` (killed/OOM):
  - lower `minibatchKMeansCutoffPatients` to force scalable clustering earlier.

## Minimal offline checklist

1. Prepare matching wheelhouse on connected machine.
2. Transfer wheelhouse to server.
3. `configurePython(...)` with explicit interpreter.
4. `installPythonDepsOffline(...)`.
5. `checkPythonDeps()`.
6. Run `precomputeSummary(...)` and `runCohortContrastViewer(...)` smoke tests.
