# konstant

A lightweight, cross-platform Python process manager. Think **PM2** but Python-native and without the Node dependency.

## Features

- Manage long-running Python processes via a simple CLI
- Automatic restart with configurable backoff protection
- Log capture with rotation
- Service registration that survives reboots (systemd on Linux, launchd on macOS, Windows services via pywin32)
- No daemon required — state is stored in flat JSON files
- Works on Windows, macOS, and Linux

## Installation

```bash
pip install konstant
```

## Quick Start

1. Create a `konstant.toml` in your working directory:

```toml
[services.my-worker]
script = "worker.py"
interpreter = "python"
cwd = "/home/user/myapp"
env_file = ".env"
autorestart = true
max_restarts = 10
restart_delay = 5
```

2. Start your services:

```bash
konstant start              # start all services
konstant start my-worker    # start a specific service
```

## CLI Commands

```
konstant start [name]          Start service(s)
konstant stop [name]           Stop service(s)
konstant restart [name]        Restart service(s)
konstant status [name]         Show pid, uptime, restart count, status
konstant logs [name]           Tail logs (default: last 50 lines)
konstant logs [name] --follow  Stream logs live
konstant install [name]        Register as system service (survives reboot)
konstant uninstall [name]      Remove system service registration
```

## Configuration

See `konstant.toml` for all available options:

| Key             | Type   | Default    | Description                         |
|-----------------|--------|------------|-------------------------------------|
| `script`        | str    | *required* | Path to the Python script to run    |
| `interpreter`   | str    | `"python"` | Python interpreter to use           |
| `cwd`           | str    | `"."`      | Working directory for the process   |
| `env_file`      | str    | `""`       | Path to a `.env` file to load       |
| `autorestart`   | bool   | `true`     | Restart on crash                    |
| `max_restarts`  | int    | `10`       | Max restarts within a 60s window    |
| `restart_delay` | int    | `5`        | Seconds to wait before restarting   |
| `args`          | list   | `[]`       | Extra arguments passed to the script|

## Service Registration

`konstant install` registers services so they start automatically on boot. The backend is chosen based on the OS:

| Platform | Backend | Location |
|----------|---------|----------|
| Linux    | systemd (user) | `~/.config/systemd/user/konstant_{name}.service` |
| macOS    | launchd (user) | `~/Library/LaunchAgents/com.konstant.{name}.plist` |
| Windows  | Windows Service (pywin32) | Windows Service Manager |

No root/admin is required on Linux or macOS — services are registered at the user level.

```bash
konstant install my-worker     # register
konstant uninstall my-worker   # remove
```

The `--user` and `--password` flags are only meaningful on Windows.

## Logging

By default, logs are written in plain text with timestamps. You can switch to structured JSON output per-service or globally, and optionally forward logs to Elasticsearch or Logstash.

### JSON structured logs

Set `format = "json"` in a global `[logging]` section or per-service under `[services.<name>.logging]`:

```toml
# Global default — all services emit JSON
[logging]
format = "json"

# Or override per-service
[services.my-worker.logging]
format = "json"
```

Each log line becomes a JSON object with ELK-compatible field names:

```json
{"@timestamp": "2026-03-26T18:20:31Z", "service": "my-worker", "stream": "stdout", "message": "tick 0", "level": "INFO"}
```

`konstant logs` automatically pretty-prints JSON lines for readability. Use `--raw` to see the raw JSON.

### Log forwarding

Forward logs directly to Elasticsearch or Logstash — no Filebeat required:

```toml
# Global forwarding (applies to all services)
[logging.forward]
enabled = true
type = "elasticsearch"        # "elasticsearch" or "logstash"
url = "http://localhost:9200"
index = "konstant-logs"
# Optional auth
username = ""
password = ""
api_key = ""

# Per-service override
[services.my-worker.logging.forward]
enabled = true
type = "logstash"
url = "tcp://localhost:5044"
```

Forwarding runs in a background thread. If the remote is unreachable, batches are retried (3 attempts with backoff) then dropped — local log files are always written regardless.

### Forwarding options

| Key        | Type   | Default                    | Description                          |
|------------|--------|----------------------------|--------------------------------------|
| `enabled`  | bool   | `false`                    | Enable log forwarding                |
| `type`     | str    | `"elasticsearch"`          | `"elasticsearch"` or `"logstash"`    |
| `url`      | str    | `"http://localhost:9200"`  | Elasticsearch URL or Logstash `tcp://host:port` |
| `index`    | str    | `"konstant-logs"`          | Elasticsearch index name             |
| `username` | str    | `""`                       | Basic auth username                  |
| `password` | str    | `""`                       | Basic auth password                  |
| `api_key`  | str    | `""`                       | Elasticsearch API key                |

## License

MIT
