# PAM Tunnel Non-Interactive Modes

## PAM Tunnel — Non-Interactive Modes

Non-interactive modes for `pam tunnel start` let you run PAM tunnels from scripts, CI/CD jobs, headless servers, and process supervisors such as **systemd**, without requiring an interactive terminal session. The feature shipped in PR #1848 (initial, 2026-03-06) and was finalized in PR #1993 (supersedes #1848, 2026-04-24).

***

### Overview

Interactive tunnel workflows assume a user at a TTY. Automation scenarios—batch jobs, remote servers without a login shell, pipelines, and supervised services—need tunnels that **start predictably**, **signal readiness**, **survive or detach** according to policy, and **tear down** when work completes.

Commander adds optional flags to `pam tunnel start` so the same command can:

* Run in the **foreground** until stopped or timed out.
* **Detach** into a background child process while the parent exits immediately.
* **Wrap a single shell command**: bring the tunnel up, run the command, tear the tunnel down, and exit with the wrapped command’s exit code.

A **file-based tunnel registry** supports cross-process visibility and shutdown: other Commander instances can list tunnels started elsewhere and stop them via signals. Inside **`keeper shell`** (interactive mode), `--foreground` and `--background` are **gracefully skipped**, because the tunnel already persists for that session.

Commander also **auto-detects** non-interactive environments (no TTY) when applying these behaviors.

**Primary audiences**

* **Headless servers** where no interactive terminal owns the tunnel lifecycle.
* **CI/CD** pipelines that must bring a tunnel up for a bounded step and tear it down reliably.
* **systemd** services (and similar supervisors) that expect a main PID, PID files, and signal-driven shutdown.
* **Automation scripts** that wrap operational commands against services exposed on **localhost** through the tunnel port.

**Implementation note**

PR #1993 rebased the feature cleanly onto the release branch and kept the final change set narrow (**three files**), superseding the earlier PR #1848 implementation while preserving the same operator-facing behavior described here.

***

### New flags reference (`pam tunnel start`)

**Syntax (conceptual)**

The following illustrates only the **new** options documented on this page; other existing `pam tunnel start` arguments (such as `--port`) behave as they did previously:

```
keeper pam tunnel start <UID> --port <local-port> [--foreground | --background | --run "<command>"] [--timeout <seconds>] [--pid-file <path>]
```

| Flag           | Short | Type              | Default  | Description                                                                                                                        |
| -------------- | ----- | ----------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| `--foreground` | `-fg` | Boolean           | `false`  | Run the tunnel in the **current process**. Stays alive until **Ctrl+C** or **`--timeout`** (WebRTC wait).                          |
| `--background` | `-bg` | Boolean           | `false`  | **Spawn a detached child**; parent returns immediately. Readiness is determined by **polling** the file-based tunnel registry.     |
| `--run`        | `-R`  | String            | *(none)* | After the tunnel is up, **exec** the given shell command; on exit, tear down the tunnel and **propagate the command’s exit code**. |
| `--timeout`    | —     | Integer (seconds) | `30`     | Maximum time to wait for the **WebRTC** connection when starting the tunnel.                                                       |
| `--pid-file`   | —     | Path string       | *(none)* | On successful connect, write the process **PID** to this file; the file is **removed** when the tunnel stops.                      |

All new flags are **optional**. `--foreground`, `--background`, and `--run` are **mutually exclusive** (see Mutual exclusivity rules).

***

### `--foreground` (`-fg`)

#### How it works

The tunnel runs in the **same process** as the `keeper` invocation. It remains active until the process receives **SIGINT** (e.g. Ctrl+C) or until the **WebRTC** connection wait exceeds **`--timeout`**.

In practical terms:

1. The tunnel shares fate with the **`keeper` process**—no detached child owns the WebRTC session.
2. **Interactive interrupt** (Ctrl+C) ends the process in the usual terminal-driven way (SIGINT).
3. If the tunnel cannot become ready within **`--timeout`**, startup fails according to the same timeout semantics used by the other modes.

#### When to use

Use foreground mode when a **supervisor** or interactive operator should own the tunnel lifecycle—typical for **terminals**, **containers** with a single main process, or **`systemd`** units that track `MAINPID`.

#### Example — stay alive until killed

```
keeper pam tunnel start <UID> --port 5432 --foreground
```

#### Example — foreground with PID file

Write the PID after connect so external tools can signal the process:

```
keeper pam tunnel start <UID> --port 5432 --foreground --pid-file /tmp/tunnel.pid
```

***

### `--background` (`-bg`)

#### How it works

Commander **starts a detached child process** that owns the tunnel. The **parent exits immediately** once startup proceeds. Readiness is observed by **polling** the **file-based tunnel registry** (JSON session files under `<tmp>/keeper-tunnel-sessions/`).

In practical terms:

1. **Parent / child split**: the **child** continues running the tunnel; the **parent** returns to your shell or CI runner immediately after initiating startup.
2. **Readiness**: rather than blocking interactively, Commander consults the registry files to determine when the tunnel registration reflects a live session.
3. **Shutdown**: you may stop the tunnel by signaling the PID recorded with **`--pid-file`**, or by using **`pam tunnel stop`** (including cross-process discovery—see below).

#### When to use

Use background mode when a script should **return control** immediately—e.g. start the tunnel, then run client commands in the same script, then stop via PID file or `pam tunnel stop`.

#### Example — daemonize, run client, stop by PID

```
keeper pam tunnel start <UID> --port 5432 --background --pid-file /tmp/tunnel.pid
pg_dump -h localhost -p 5432 mydb > backup.sql
kill -SIGTERM $(cat /tmp/tunnel.pid)
```

***

### `--run` (`-R`)

#### How it works

Commander **starts the tunnel**, then **executes** the supplied shell command. When that command exits, Commander **stops the tunnel** and the overall process **exits with the same exit code** as the wrapped command.

Lifecycle summary:

| Phase     | Behavior                                                                                               |
| --------- | ------------------------------------------------------------------------------------------------------ |
| Start     | Establish the tunnel and wait up to **`--timeout`** for **WebRTC** readiness (default **30** seconds). |
| Execute   | Run **`"<command>"`** with normal shell semantics after the tunnel is usable.                          |
| Stop      | Tear the tunnel down automatically when the wrapped command finishes—success or failure.               |
| Exit code | Propagate the wrapped command’s exit code to the caller (CI systems, scripts, systemd).                |

#### When to use

Use `--run` for **one-shot automation**: database dumps, migrations, health checks, or any CLI that needs localhost access through the tunnel without managing start/stop manually.

#### Example — single command with tunnel lifecycle

```
keeper pam tunnel start <UID> --port 5432 --run "pg_dump -h localhost -p 5432 mydb > backup.sql"
```

#### Example — custom WebRTC wait before running the command

```
keeper pam tunnel start <UID> --port 5432 --run "my_command" --timeout 60
```

***

### `--timeout` and `--pid-file`

#### `--timeout <seconds>`

Controls how long Commander waits for the **WebRTC** connection when bringing the tunnel up. **Default: `30`** seconds. Increase this value on slow or high-latency networks when using **`--run`** or other modes.

The timeout applies to **connection establishment**, not to how long the tunnel remains up afterward. For long-running tunnels, prefer **`--foreground`** or **`--background`** and manage lifetime externally (signals, **`pam tunnel stop`**, or supervisor semantics).

#### `--pid-file <path>`

After the tunnel **connects**, Commander writes the tunnel process **PID** to the given path. The file is **removed** when the tunnel **stops**. This pairs naturally with **`--foreground`** (signal the main process) or **`--background`** (signal the detached owner).

**Operational notes**

* The PID file is a stable integration point for **`kill -SIGTERM $(cat /path/to/pid)`** style orchestration (as shown in the PR examples).
* Combined with **`systemd`**, align **`PIDFile=`** with **`--pid-file`** so **`$MAINPID`** and **`ExecStop`** target the same process (see systemd service example).

**Examples**

```
keeper pam tunnel start <UID> --port 5432 --foreground --pid-file /tmp/tunnel.pid
```

```
keeper pam tunnel start <UID> --port 5432 --background --pid-file /tmp/tunnel.pid
```

***

### Cross-process tunnel registry

The module **`keepercommander/commands/tunnel_registry.py`** implements a **file-based registry** so tunnels are visible **across processes**:

* Session metadata is stored as **atomic JSON writes** under:

  ```
  <tmp>/keeper-tunnel-sessions/<pid>.json
  ```
* The sessions directory uses **`0o700` permissions** on **POSIX** systems.
* **Stale entries** are **cleaned up** automatically.
* **Duplicate bind detection** helps avoid conflicting tunnel endpoints.

#### Filename and layout

Each JSON file is keyed by **`<pid>.json`**, where **`pid`** identifies the owning tunnel process for that registry entry. The parent directory **`keeper-tunnel-sessions`** lives under the platform temporary directory (**`<tmp>`**), which resolves according to OS conventions for temp paths.

#### Why this exists

Without a cross-process view, only the originating Commander process could reason about active tunnels. The registry makes **background** startup observable and lets **list**/**stop** remain coherent when tunnels outlive the shell that started them—critical for **scripts**, **services**, and **remote automation**.

This registry is what enables **`pam tunnel list`** and **`pam tunnel stop`** to interoperate with tunnels started by **other Commander processes**.

***

### `pam tunnel list` and `pam tunnel stop` (cross-process)

#### `pam tunnel list`

The list command now surfaces tunnels from **other processes** by reading the **file registry**, not only tunnels owned by the current session. **Stale** registry entries are **auto-cleaned**.

#### `pam tunnel stop`

Stop falls back to the **file registry** to terminate **cross-process** tunnels using **SIGTERM** on POSIX or **TerminateProcess** on Windows. The **`--all`** option also stops **cross-process** tunnels discovered via the registry.

**Operator cheat sheet**

| Goal                                              | Approach                                                                                                                         |
| ------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
| Stop using the PID file pattern from the examples | Read **`--pid-file`** and send **SIGTERM** (for example **`kill -SIGTERM $(cat /tmp/tunnel.pid)`**).                             |
| Stop cross-process tunnels via Commander          | Run **`pam tunnel stop`**—it **falls back** to the file registry and signals remote owners (**SIGTERM** / **TerminateProcess**). |
| Stop everything                                   | **`pam tunnel stop --all`** also stops **cross-process** tunnels found through the registry.                                     |
| See tunnels from other processes                  | Run **`pam tunnel list`**—registry-backed sessions appear here; **stale** entries are **auto-cleaned**.                          |

***

### systemd service example

PR #1993 included the following commented illustration for **`[Service]`** wiring—expanded here into a complete unit file you can install under **`/etc/systemd/system/`** (paths and **`keeper`** location may differ):

```
# [Service]
# ExecStart=/usr/bin/keeper pam tunnel start <UID> --port 5432 --foreground --pid-file /run/keeper-tunnel.pid
# ExecStop=/bin/kill -SIGTERM $MAINPID
# PIDFile=/run/keeper-tunnel.pid
```

The following unit runs the tunnel in the **foreground** under systemd supervision, writes a **PID file** after connect, and stops cleanly via **`SIGTERM`**:

```ini
[Unit]
Description=Keeper PAM Tunnel
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
ExecStart=/usr/bin/keeper pam tunnel start <UID> --port 5432 --foreground --pid-file /run/keeper-tunnel.pid
ExecStop=/bin/kill -SIGTERM $MAINPID
PIDFile=/run/keeper-tunnel.pid
Restart=on-failure

[Install]
WantedBy=multi-user.target
```

Adjust **`ExecStart`** paths, **`<UID>`**, **`--port`**, and **`PIDFile`** for your environment.

***

### Mutual exclusivity rules

The following modes **cannot be combined**:

| Mode        | Flags                  |
| ----------- | ---------------------- |
| Foreground  | `--foreground` / `-fg` |
| Background  | `--background` / `-bg` |
| Run wrapper | `--run` / `-R`         |

Choose **exactly one** of these three for a given invocation.

**Additional behavior**

* Commander **auto-detects** non-interactive use when there is **no TTY**.
* Inside **`keeper shell`** (**interactive** session), **`--foreground`** and **`--background`** are **gracefully skipped**—the tunnel **already persists** in that context.

#### Interaction summary

| Context                                                            | `--foreground` / `--background`                                                                                        |
| ------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------- |
| Where Commander **auto-detects** non-interactive mode (**no TTY**) | Behaviors apply as documented in each section above.                                                                   |
| Interactive **`keeper shell`**                                     | **`--foreground`** and **`--background`** are **gracefully skipped**—the tunnel **already persists** for that session. |

***

### Backward compatibility

All new flags default to **`false`**, **`None`**, or **`30`** (for `--timeout`) as described in the reference table. **If you omit them**, `pam tunnel start` behaves **the same as before** these changes—existing scripts and muscle memory remain valid without modification.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.keeper.io/keeperpam/commander-cli/command-reference/keeperpam-commands/pam-tunnel-non-interactive-modes.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
