Metadata-Version: 2.4
Name: gcp-lite
Version: 2.0.3
Summary: Portable GCP tooling in pure Python
Author-email: Ohad Livne <libohad-dev@proton.me>
License-Expression: GPL-3.0-or-later
Keywords: compute-engine,gcloud,gcp,google-cloud,iap,ssh,tunnel
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: System Administrators
Classifier: Operating System :: POSIX
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: System :: Networking
Classifier: Topic :: System :: Systems Administration
Classifier: Typing :: Typed
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
License-File: LICENSES/CC-BY-SA-4.0.txt
License-File: LICENSES/CC0-1.0.txt
License-File: LICENSES/GPL-3.0-or-later.txt
Requires-Dist: websockets>=14.0
Provides-Extra: sa
Requires-Dist: cryptography>=41.0; extra == "sa"
Dynamic: license-file

<!--
SPDX-FileCopyrightText: 2026 Ohad Livne <libohad-dev@proton.me>

SPDX-License-Identifier: CC-BY-SA-4.0
-->

# gcp-lite

Portable GCP tooling in pure Python. Replaces common `gcloud` commands — SSH
tunneling through IAP, VM start/stop, and machine type changes — on platforms
where the full Cloud SDK is unavailable (e.g. Termux on Android/ARM).

## How it works

`gcp-lite` authenticates with GCP via standard OAuth2, then talks directly to
GCP APIs using only Python and a single dependency
([websockets](https://pypi.org/project/websockets/)):

- **IAP tunneling:** Opens a WebSocket to Google's IAP tunnel relay
  (`tunnel.cloudproxy.app`) and relays stdin/stdout, acting as an SSH
  `ProxyCommand`.
- **VM management:** Calls the Compute Engine REST API to start/stop instances
  and change machine types.

## GCP-side prerequisites

Before using `gcp-lite`, ensure the following are in place on your GCP project:

### IAP tunnel access

The user or service account must have the **IAP-Secured Tunnel User** role
(`roles/iap.tunnelResourceAccessor`) on the project, folder, or specific VM
instance. This role grants the `iap.tunnelInstances.accessViaIAP` permission
that authorizes the WebSocket connection.

You can grant it via the GCP console (Security > Identity-Aware Proxy) or with:

```
gcloud projects add-iam-policy-binding PROJECT_ID \
    --member='user:you@example.com' \
    --role='roles/iap.tunnelResourceAccessor'
```

### Firewall rule for IAP

GCP must be able to reach your VM on port 22 from the IAP IP range. The default
`default-allow-ssh` rule may already cover this. If not, create a firewall rule
allowing ingress on TCP port 22 from `35.235.240.0/20` (Google's IAP range).

### SSH key on the VM

`gcp-lite` does not manage SSH keys. You need to ensure the VM has your public
key in `~/.ssh/authorized_keys` for the target user. Options:

- **Project or instance metadata:** Add your public key to the `ssh-keys`
  metadata field via the GCP console (Compute Engine > Metadata > SSH Keys). The
  VM's guest agent will sync it to `authorized_keys` automatically.
- **Manual:** If you have existing access (e.g. from another machine), append
  your public key to `~/.ssh/authorized_keys` directly.
- **Serial console:** As a last resort, use the GCP console serial port to log
  in and add the key.

### VM with IAP enabled

The target VM must be in a project with the IAP API enabled
(`iap.googleapis.com`). The VM itself does not need a public IP address — that's
the point.

### Compute Engine permissions (for VM management)

To use `start`, `stop`, and `set-machine-type`, the authenticated user or
service account needs permissions to manage instances. The **Compute Instance
Admin (v1)** role (`roles/compute.instanceAdmin.v1`) covers all three operations.

## Installation

```
pip install .
```

For service account authentication (optional):

```
pip install '.[sa]'
```

This adds the `cryptography` package, needed for JWT/RS256 signing.

## Quick start

### 1. Authenticate (one-time)

```
gcp-lite login
```

This initiates a browser-based OAuth2 flow. It prints the URL to the terminal —
open it in your browser. After you consent, the browser redirects to a local
listener and `gcp-lite` saves a refresh token to
`~/.config/gcp-lite/credentials.json`.

### 2. Configure SSH

Add an entry to `~/.ssh/config`:

```
Host gcp-vm
    User your-username
    IdentityFile ~/.ssh/id_ed25519
    ProxyCommand gcp-lite iap --project my-project --zone us-central1-a --instance my-vm
```

### 3. Connect

```
ssh gcp-vm
```

## Commands

### `gcp-lite login`

Runs an interactive OAuth2 Authorization Code flow with PKCE:

1. Starts a temporary HTTP server on `127.0.0.1` (random port)
2. Prints an authorization URL to stderr
3. Waits for the browser redirect with the authorization code
4. Exchanges the code for a refresh token
5. Saves credentials to `~/.config/gcp-lite/credentials.json`

The refresh token is long-lived. You only need to re-run `login` if you revoke
access or the token expires.

### `gcp-lite iap`

Opens an IAP tunnel and relays stdin/stdout, designed for use as an SSH
`ProxyCommand`.

| Option | Required | Default | Description |
|---|---|---|---|
| `--project` | yes | | GCP project ID |
| `--zone` | yes | | Compute Engine zone (e.g. `us-central1-a`) |
| `--instance` | yes | | VM instance name |
| `--port` | no | `22` | Target port on the VM |
| `--interface` | no | `nic0` | Network interface |
| `--credentials` | no | | Path to a credentials JSON file (see below) |

### `gcp-lite status`

Prints the current status of a VM instance (e.g. `RUNNING`, `STOPPED`,
`TERMINATED`, `STAGING`) to stdout.

| Option | Required | Default | Description |
|---|---|---|---|
| `--project` | yes | | GCP project ID |
| `--zone` | yes | | Compute Engine zone |
| `--instance` | yes | | VM instance name |
| `--credentials` | no | | Path to a credentials JSON file |

### `gcp-lite metadata`

Prints the full instance description as JSON to stdout. This includes machine
type, network interfaces, disks, metadata, scheduling, status, and all other
fields from the Compute Engine API.

| Option | Required | Default | Description |
|---|---|---|---|
| `--project` | yes | | GCP project ID |
| `--zone` | yes | | Compute Engine zone |
| `--instance` | yes | | VM instance name |
| `--credentials` | no | | Path to a credentials JSON file |

### `gcp-lite start`

Starts a stopped VM instance. By default, waits for the operation to complete.

| Option | Required | Default | Description |
|---|---|---|---|
| `--project` | yes | | GCP project ID |
| `--zone` | yes | | Compute Engine zone |
| `--instance` | yes | | VM instance name |
| `--credentials` | no | | Path to a credentials JSON file |
| `--no-wait` | no | | Return immediately after the API call succeeds |

Exits with code 4 if the instance is already running.

### `gcp-lite stop`

Stops a running VM instance. By default, waits for the operation to complete.

| Option | Required | Default | Description |
|---|---|---|---|
| `--project` | yes | | GCP project ID |
| `--zone` | yes | | Compute Engine zone |
| `--instance` | yes | | VM instance name |
| `--credentials` | no | | Path to a credentials JSON file |
| `--no-wait` | no | | Return immediately after the API call succeeds |

Exits with code 4 if the instance is already stopped.

### `gcp-lite set-machine-type`

Changes the machine type of a VM instance. The instance must be stopped.

| Option | Required | Default | Description |
|---|---|---|---|
| `--project` | yes | | GCP project ID |
| `--zone` | yes | | Compute Engine zone |
| `--instance` | yes | | VM instance name |
| `--machine-type` | yes | | New machine type (e.g. `e2-standard-4`) |
| `--credentials` | no | | Path to a credentials JSON file |
| `--no-wait` | no | | Return immediately after the API call succeeds |
| `--force` | no | | Stop the instance, change the type, and restart it |

`--force` and `--no-wait` are mutually exclusive (exit code 3 if combined).

Without `--force`, exits with code 4 if the instance is not stopped, printing
instructions to stop it first or use `--force`.

With `--force`, the full lifecycle is: stop (if running) -> set machine type ->
start. Each step waits for the previous to complete.

## Authentication

### Credential resolution

All subcommands except `login` resolve credentials in this order:

1. **`--credentials <path>`** — Explicit path to a JSON credentials file
2. **`GOOGLE_APPLICATION_CREDENTIALS` environment variable** — Standard GCP env
   var pointing to a credentials JSON file
3. **`~/.config/gcp-lite/credentials.json`** — Default location, written by
   `gcp-lite login`

### Supported credential types

The credentials JSON file must have a `"type"` field:

- **`"authorized_user"`** — Contains a refresh token, client ID, and client
  secret. This is what `gcp-lite login` produces. The refresh token is exchanged
  for a short-lived access token on each invocation.

- **`"service_account"`** — A GCP service account key file (downloaded from the
  console). Requires the optional `cryptography` dependency (`pip install
  'gcp-lite[sa]'`). A JWT is signed locally and exchanged for an access token.

### Using existing gcloud credentials

If you have `gcloud` configured on another machine, you can copy its Application
Default Credentials file to `~/.config/gcp-lite/credentials.json`:

```
# On the machine with gcloud:
cat ~/.config/gcloud/application_default_credentials.json

# Copy the output to ~/.config/gcp-lite/credentials.json on your device
```

## Configuration files

| Path | Purpose |
|---|---|
| `~/.config/gcp-lite/credentials.json` | Stored OAuth2 credentials (written by `gcp-lite login`) |
| `~/.ssh/config` | SSH client configuration with `ProxyCommand` |

## Environment variables

| Variable | Purpose |
|---|---|
| `GOOGLE_APPLICATION_CREDENTIALS` | Fallback path to a credentials JSON file (standard GCP convention) |

## Requirements

- Python >= 3.11
- `websockets` >= 14.0
- `cryptography` >= 41.0 (optional, for service account auth only)

## Exit codes

| Code | Meaning |
|---|---|
| 0 | Success |
| 1 | Authentication error (invalid or missing credentials) |
| 2 | IAP tunnel error (connection closed by server) |
| 3 | Invalid invocation (e.g. `--force` combined with `--no-wait`) |
| 4 | Invalid state (e.g. starting an already-running instance) |
| 5 | Operation failed (server-side error during a Compute Engine operation) |

## Troubleshooting

Common IAP tunnel errors:

| Error | Meaning |
|---|---|
| Not authorized | Missing `roles/iap.tunnelResourceAccessor` on the project or instance |
| Instance not found | Wrong project, zone, or instance name |
| Failed to connect to backend VM | The VM may be stopped, or port 22 is blocked by a firewall rule |
| Backend VM closed the connection after accept | sshd is not running on the VM, an OS-level firewall reset the connection, or SSH is on a non-default port |
| Write to backend VM failed | sshd terminated the connection mid-stream — check `journalctl -u ssh` on the VM |
| Reauthentication required | OAuth refresh token revoked or expired; run `gcp-lite login` again |
| Session ID already in use | Another client is holding the same tunnel session; close it or wait for it to expire |

Common Compute Engine errors:

| Error | Meaning |
|---|---|
| Permission denied | Missing `roles/compute.instanceAdmin.v1` or equivalent |
| Instance is already running (exit 4) | Attempted `start` on a running VM |
| Instance is already stopped (exit 4) | Attempted `stop` on a stopped VM |
| Instance must be stopped (exit 4) | Attempted `set-machine-type` on a running VM without `--force` |
