Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cli/GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ Our backend connection brokers have a reputation system that is used to make sur
## Guidance for Conduit station infrastructure:

- Spread your Conduits out across data centers/locations, IP diversity is very important.
- Don't overload your Conduit. Watch resource utilization, and reduce the --max-clients if you are seeing any signs of resource contention.
- Don't overload your Conduit. Watch resource utilization, and reduce the --max-common-clients if you are seeing any signs of resource contention.
- Keep your station online consistently so that it has a strong reputation with the tunnel connection broker.

We have observed that Conduit can support around 150-350 concurrent users for every 1 CPU and 2GB RAM. Factors like the CPU clock speed and network speed will determine where in this range your Conduit will perform best.
Expand Down
2 changes: 1 addition & 1 deletion cli/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ setup: check-go
git clone --depth 1 --branch $(PSIPHON_BRANCH) $(PSIPHON_REPO) psiphon-tunnel-core; \
else \
echo "psiphon-tunnel-core already exists, updating..."; \
cd psiphon-tunnel-core && git fetch origin $(PSIPHON_BRANCH) && git checkout $(PSIPHON_BRANCH) && git pull; \
cd psiphon-tunnel-core && git fetch origin $(PSIPHON_BRANCH) && git checkout -B $(PSIPHON_BRANCH) FETCH_HEAD; \
fi
$(GO) mod tidy

Expand Down
212 changes: 151 additions & 61 deletions cli/README.md
Original file line number Diff line number Diff line change
@@ -1,118 +1,208 @@
# Conduit CLI

Command-line interface for running a Psiphon Conduit node - a volunteer-run proxy that relays traffic for users in censored regions.
Command-line interface for running a Psiphon Conduit node, a volunteer-run proxy that relays traffic for users in censored regions.

## Quick Start

Want to run a Conduit station? Get the latest CLI release: https://github.com/Psiphon-Inc/conduit/releases
Want to run a Conduit station? Get the latest CLI release:
https://github.com/Psiphon-Inc/conduit/releases

Our official CLI releases include an embedded psiphon config.
Official CLI releases include an embedded Psiphon config.

Contact Psiphon (conduit-oss@psiphon.ca) to discuss custom configuration values.
Contact Psiphon (`conduit-oss@psiphon.ca`) to discuss custom configuration values.

Conduit deployment guide: [GUIDE.md](./GUIDE.md)

### Common-only mode

```bash
conduit start
```

### Personal compartment mode

```bash
# Generate and persist a personal compartment ID, and print a share token
conduit new-compartment-id --name my-station

# Enable personal clients (can be combined with common clients)
conduit start --max-common-clients 50 --max-personal-clients 10
```

If you do not have `personal_compartment.json` in your data directory yet, you can also pass a compartment ID or share token directly:

```bash
conduit start --max-personal-clients 10 --compartment-id "<id-or-token>"
```

## Docker

Use the official Docker image, which includes an embedded Psiphon config. Docker Compose is a convenient way to run Conduit if you prefer a declarative setup.
Use the official Docker image, which includes an embedded Psiphon config.

```bash
docker compose up
```

The compose file enables Prometheus metrics on `:9090` inside the container. To scrape from the host, publish the port or run Prometheus on the same Docker network and scrape `conduit:9090`.

## Building From Source
## Commands

```bash
# First time setup (clones required dependencies)
make setup
- `conduit start` - start the Conduit inproxy service
- `conduit new-compartment-id` - create and persist a personal compartment ID and output a share token
- `conduit ryve-claim` - output Conduit claim data for Ryve

# Build
make build
## Start Command Flags

# Run
./dist/conduit start --psiphon-config /path/to/psiphon_config.json
```
| Flag | Default | Description |
| -------------------------- | ------- | --------------------------------------------------------------------------------- |
| `--psiphon-config, -c` | - | Path to Psiphon network config file (required when no embedded config is present) |
| `--max-common-clients, -m` | `50` | Maximum common proxy clients (`0-1000`) |
| `--max-personal-clients` | `0` | Maximum personal proxy clients (`0-1000`) |
| `--compartment-id` | - | Personal compartment ID or share token |
| `--bandwidth, -b` | `40` | Total bandwidth limit in Mbps (`-1` for unlimited) |
| `--set` | - | Override allowlisted config keys (`key=value`), repeatable |
| `--metrics-addr` | - | Prometheus metrics listen address (for example, `:9090`) |

## Requirements
Global flags:

- **Go 1.24.x** (Go 1.25+ is not supported due to psiphon-tls compatibility)
- Psiphon network configuration file (JSON)
- `--data-dir, -d` (default `./data`)
- `--verbose, -v` (repeatable count flag)

The Makefile will automatically install Go 1.24.12 if not present.
At least one of `--max-common-clients` or `--max-personal-clients` must be greater than `0`.

## Usage
## Personal Compartments

```bash
# Start with default settings
conduit start
When `--max-personal-clients` is greater than 0, Conduit needs a personal compartment ID.

Use `conduit new-compartment-id` to:

1. Generate a personal compartment ID.
2. Save it to `personal_compartment.json` in your data directory.
3. Print a v1 share token (for pairing).

`--name` is limited to 32 characters.

# Customize limits
conduit start --max-clients 20 --bandwidth 10
You can provide `--compartment-id` as either:

# Verbose output (info messages)
conduit start -v
- a raw compartment ID, or
- a share token (the CLI extracts and validates the ID)

## `--set` Overrides

`--set` supports a strict allowlist of config keys:

- `EmitDiagnosticNotices`
- `EmitInproxyProxyActivity`
- `InproxyLimitDownstreamBytesPerSecond`
- `InproxyLimitUpstreamBytesPerSecond`
- `InproxyMaxClients`
- `InproxyMaxCommonClients`
- `InproxyMaxPersonalClients`
- `InproxyProxyPersonalCompartmentID`
- `InproxyReducedEndTime`
- `InproxyReducedLimitDownstreamBytesPerSecond`
- `InproxyReducedLimitUpstreamBytesPerSecond`
- `InproxyReducedMaxCommonClients`
- `InproxyReducedStartTime`

Example:

```bash
conduit start \
--set EmitInproxyProxyActivity=true \
--set InproxyReducedMaxCommonClients=10
```

### Options
## Metrics

Enable metrics with `--metrics-addr`, then scrape `/metrics`.

| Flag | Default | Description |
| ---------------------- | -------- | ------------------------------------------ |
| `--psiphon-config, -c` | - | Path to Psiphon network configuration file |
| `--max-clients, -m` | 50 | Maximum concurrent clients |
| `--bandwidth, -b` | 40 | Bandwidth limit per peer in Mbps |
| `--data-dir, -d` | `./data` | Directory for keys and state |
| `--metrics-addr` | - | Prometheus metrics listen address |
| `-v` | - | Verbose output |
The CLI exports core gauges such as:

- `conduit_announcing`
- `conduit_connecting_clients`
- `conduit_connected_clients`
- `conduit_is_live`
- `conduit_max_common_clients`
- `conduit_max_personal_clients`
- `conduit_bytes_uploaded`
- `conduit_bytes_downloaded`

It also exports per-region activity gauges labeled by `scope` (`common` or `personal`) and `region`:

- `conduit_region_bytes_uploaded`
- `conduit_region_bytes_downloaded`
- `conduit_region_connecting_clients`
- `conduit_region_connected_clients`

## Traffic Throttling

For bandwidth-constrained environments (e.g., VPS with monthly quotas), Conduit supports automatic throttling via a separate supervisor monitor.
For bandwidth-constrained environments (for example, VPS with monthly quotas), Conduit supports automatic throttling via the `conduit-monitor` supervisor.

To use traffic throttling with Docker, use the `limited-bandwidth` compose file:
To use throttling with Docker:

```bash
docker compose -f docker-compose.limited-bandwidth.yml up -d
```

### Configuration

Edit `docker-compose.limited-bandwidth.yml` to set your limits:
Edit `docker-compose.limited-bandwidth.yml` to set limits:

```yaml
command:
[
"--traffic-limit", "500", # Total quota in GB
"--traffic-period", "30", # Time period in days
"--bandwidth-threshold", "80", # Throttle at 80% usage
"--min-connections", "10", # Reduced capacity when throttled
"--min-bandwidth", "10", # Reduced bandwidth when throttled
"--", # Separator
"start", # Conduit command
... # Conduit flags
command: [
"--traffic-limit",
"500", # Total quota in GB
"--traffic-period",
"30", # Time period in days
"--bandwidth-threshold",
"80", # Throttle at 80% usage
"--min-connections",
"10", # Reduced common clients when throttled
"--min-bandwidth",
"10", # Reduced bandwidth when throttled
"--", # Separator
"start", # Conduit command
..., # Conduit flags
]
```

### How It Works
How it works:

The supervisor monitors bandwidth usage and:
1. Runs Conduit at full capacity initially.
2. When the threshold is reached (e.g., 400GB of 500GB), it restarts Conduit with reduced capacity.
3. When the period ends, it resets usage and restarts Conduit at full capacity.
4. Ensures minimum limits (100GB/7days) to protect reputation.
1. Runs Conduit at full capacity.
2. When threshold is reached, restarts Conduit with reduced capacity.
3. At period end, resets usage and restores normal capacity.
4. Enforces minimum limits (`100GB` / `7 days`) to protect reputation.

## Data Directory

Keys and state are stored in the data directory (default: `./data`):

- `conduit_key.json` - Node identity keypair
The Psiphon broker tracks proxy reputation by key. Always use a persistent volume to preserve your key across container restarts, otherwise you'll start with zero reputation and may not receive client connections for some time.
- `conduit_key.json` - node identity keypair
- `personal_compartment.json` - persisted personal compartment ID
- `traffic_state.json` - traffic usage state (when throttling is enabled)

The Psiphon broker tracks proxy reputation by key. Always use persistent storage for your data directory so your key and reputation survive restarts.

## Building From Source

```bash
# First time setup (clones required dependencies)
make setup

# Build Conduit
make build

# Run (when not using embedded config)
./dist/conduit start --psiphon-config /path/to/psiphon_config.json
```

## Requirements

- Go `1.24.x` (Go `1.25+` is not supported due to `psiphon-tls` compatibility)
- Psiphon network configuration file (JSON), unless using an embedded-config build

- `traffic_state.json` - Traffic usage tracking (when throttling is enabled)
Tracks current period start time, bytes used, and throttle state. Persists across restarts.
The Makefile automatically installs Go `1.24.12` if not present.

## Building
## Build Targets

```bash
# Build for current platform
Expand Down
71 changes: 71 additions & 0 deletions cli/cmd/new_compartment_id.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package cmd

import (
"fmt"
"os"
"strings"
"unicode/utf8"

"github.com/Psiphon-Inc/conduit/cli/internal/config"
"github.com/Psiphon-Inc/conduit/cli/internal/logging"
"github.com/spf13/cobra"
)

var (
compartmentNameDefault string
compartmentName string
defaultNameFromHost bool
)

var newCompartmentIDCmd = &cobra.Command{
Use: "new-compartment-id",
Short: "Create and persist a personal compartment ID",
Long: "Generate a personal compartment ID, save it to the data directory, and output a share token.",
RunE: runNewCompartmentID,
}

func init() {
compartmentNameDefault = "conduit"
if host, err := os.Hostname(); err == nil && host != "" {
compartmentNameDefault = host
defaultNameFromHost = true
}

rootCmd.AddCommand(newCompartmentIDCmd)
newCompartmentIDCmd.Flags().StringVarP(&compartmentName, "name", "n", compartmentNameDefault, "display name in share token (max 32 chars)")
}

func runNewCompartmentID(cmd *cobra.Command, args []string) error {
dataDir := GetDataDir()

name := strings.TrimSpace(compartmentName)
nameSetByUser := cmd.Flags().Changed("name")

if !nameSetByUser && defaultNameFromHost {
logging.Printf("[INFO] Defaulting --name to hostname %q (use --name to override)\n", name)
}

if utf8.RuneCountInString(name) > config.PersonalPairingNameMaxLength {
return fmt.Errorf("--name must be at most %d characters", config.PersonalPairingNameMaxLength)
}

compartmentID, err := config.GeneratePersonalCompartmentID()
if err != nil {
return err
}

if err := config.SavePersonalCompartmentID(dataDir, compartmentID); err != nil {
return err
}

shareToken, err := config.BuildPersonalPairingToken(compartmentID, name)
if err != nil {
return err
}

fmt.Printf("Saved compartment ID to %s\n", config.PersonalCompartmentFilePath(dataDir))
fmt.Println("Share token:")
fmt.Println(shareToken)

return nil
}
Loading
Loading