# Headless Styrene Integration

Design for running Styrene as a headless virtual appliance in the brutus cluster.

## Current State: Reticulum Hub

The existing `reticulum-hub` deployment provides infrastructure services:

| Component | Role | Purpose |
|-----------|------|---------|
| **rnsd** | Transport/Routing Node | Routes packets, provides public backbone (port 4242) |
| **LXMF Propagation Node** | Message Store-and-Forward | Holds messages for offline devices |
| **NomadNet** | Bulletin Board System | Community pages, microblog, file sharing |

**Key Insight**: The hub is infrastructure—it provides transport and messaging services. Styrene should **use** this infrastructure, not duplicate it.

## Vision: Styrene as Control Plane

Styrene runs alongside the hub as a separate deployment, providing:

1. **Device Discovery** - Listen for announces, maintain live mesh topology
2. **Control Plane** - Expose endpoints for management operations
3. **Self-Announcement** - Broadcast presence as Styrene node
4. **API Layer** - HTTP API for TUI and external tools to query mesh state

## Architecture

### Deployment Structure

```
brutus cluster:
├── reticulum namespace:
│   └── reticulum-hub (existing)
│       ├── rnsd (transport)
│       ├── lxmf-propagation (messaging)
│       └── nomadnet (BBS)
│
└── styrene namespace (new):
    └── styrene-node
        ├── styrene-service (Python daemon)
        ├── styrene-api (HTTP API)
        └── shared RNS instance
```

### Connection Model

**Styrene connects TO the hub**, not duplicates it:

```python
# styrene-service Reticulum config
[reticulum]
enable_transport = false  # Don't route—use hub's transport
share_instance = true

[interfaces]
[[TCP Client Interface]]
type = TCPClientInterface
enabled = true
target_host = reticulum-hub.reticulum.svc.cluster.local
target_port = 4242
```

This way:
- Styrene uses the hub's transport layer
- No duplicate routing infrastructure
- Styrene focuses on control plane duties

## Components

### 1. Styrene Service (Daemon)

**Python service running headless:**

```python
#!/usr/bin/env python3
"""Styrene headless service."""

import asyncio
import logging
from styrene.services.lifecycle import StyreneLifecycle
from styrene.services.reticulum import start_discovery

logger = logging.getLogger(__name__)


async def main():
    """Run styrene service."""
    logger.info("Starting Styrene headless service")

    # Initialize Styrene
    lifecycle = StyreneLifecycle()
    if not lifecycle.initialize_reticulum():
        logger.error("Failed to initialize Reticulum")
        return 1

    logger.info(f"Operator identity: {lifecycle.operator_identity}")

    # Start device discovery
    def on_device_discovered(device):
        logger.info(f"Discovered: {device.name} ({device.device_type.value})")

    start_discovery(callback=on_device_discovered)

    # Create Styrene destination and announce
    destination = lifecycle.create_styrene_destination(
        capabilities=["hub", "control", "api"]
    )

    if destination:
        logger.info(f"Announcing as: {destination.hash.hex()}")
        destination.announce()

    # Keep service running
    logger.info("Styrene service ready")
    while True:
        await asyncio.sleep(300)  # Re-announce every 5 minutes
        if destination:
            destination.announce()


if __name__ == "__main__":
    logging.basicConfig(level=logging.INFO)
    asyncio.run(main())
```

### 2. Styrene API (HTTP Interface)

**FastAPI service for TUI and external tools:**

```python
from fastapi import FastAPI
from styrene.services.reticulum import discover_devices, get_styrene_devices

app = FastAPI()

@app.get("/api/mesh/devices")
async def list_devices():
    """Get all discovered mesh devices."""
    devices = discover_devices()
    return [
        {
            "identity": d.identity,
            "name": d.name,
            "type": d.device_type.value,
            "status": d.status.value,
            "last_announce": d.last_announce,
        }
        for d in devices
    ]

@app.get("/api/mesh/styrene")
async def list_styrene_nodes():
    """Get only Styrene nodes."""
    devices = get_styrene_devices()
    return [...]
```

### 3. Dockerfile

```dockerfile
FROM python:3.11-slim

WORKDIR /app

# Install Styrene
COPY pyproject.toml .
COPY src/ src/
RUN pip install -e .

# Entrypoint
CMD ["python", "-m", "styrene.daemon"]
```

## Kubernetes Manifests

### Namespace

```yaml
apiVersion: v1
kind: Namespace
metadata:
  name: styrene
```

### ConfigMap (Reticulum Config)

```yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: styrene-rns-config
  namespace: styrene
data:
  config: |
    [reticulum]
    enable_transport = false
    share_instance = true

    [interfaces]
    [[TCP Client Interface]]
    type = TCPClientInterface
    enabled = true
    target_host = reticulum-hub.reticulum.svc.cluster.local
    target_port = 4242
```

### Deployment

```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: styrene-node
  namespace: styrene
spec:
  replicas: 1
  selector:
    matchLabels:
      app: styrene-node
  template:
    metadata:
      labels:
        app: styrene-node
    spec:
      containers:
      - name: styrene-service
        image: ghcr.io/styrene-lab/styrene:latest
        command: ["python", "-m", "styrene.daemon"]
        volumeMounts:
        - name: rns-config
          mountPath: /root/.reticulum
        - name: styrene-data
          mountPath: /root/.config/styrene
        env:
        - name: STYRENE_MODE
          value: "headless"
        - name: LOG_LEVEL
          value: "INFO"
        resources:
          limits:
            cpu: 200m
            memory: 256Mi

      - name: styrene-api
        image: ghcr.io/styrene-lab/styrene:latest
        command: ["uvicorn", "styrene.api:app", "--host", "0.0.0.0", "--port", "8000"]
        ports:
        - containerPort: 8000
          name: http
        resources:
          limits:
            cpu: 200m
            memory: 256Mi

      volumes:
      - name: rns-config
        configMap:
          name: styrene-rns-config
      - name: styrene-data
        persistentVolumeClaim:
          claimName: styrene-data
```

### Service

```yaml
apiVersion: v1
kind: Service
metadata:
  name: styrene-api
  namespace: styrene
spec:
  selector:
    app: styrene-node
  ports:
  - port: 8000
    targetPort: 8000
    name: http
  type: ClusterIP
```

### Ingress (Optional)

```yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: styrene-api
  namespace: styrene
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
  ingressClassName: traefik
  tls:
  - hosts:
    - styrene.vanderlyn.house
    secretName: styrene-tls
  rules:
  - host: styrene.vanderlyn.house
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: styrene-api
            port:
              number: 8000
```

## Implementation Phases

### Phase 1: Daemon Structure
1. Create `src/styrene/daemon.py` - Main service entrypoint
2. Create `src/styrene/api.py` - FastAPI application
3. Add announce logic to `StyreneLifecycle`
4. Test locally with existing reticulum-hub

### Phase 2: Containerization
1. Create Dockerfile
2. Build and push to `ghcr.io/styrene-lab/styrene:latest`
3. Test container locally

### Phase 3: Kubernetes Deployment
1. Create manifests in `apps/styrene/k8s/`
2. Deploy to brutus cluster
3. Verify connection to reticulum-hub
4. Check device discovery

### Phase 4: TUI Integration
1. Update TUI to optionally connect to API instead of local RNS
2. Add `--remote` flag: `styrene --remote https://styrene.vanderlyn.house`
3. API-first architecture for remote management

## Benefits

### Separation of Concerns
- **Hub**: Infrastructure (transport, messaging, BBS)
- **Styrene**: Control plane (discovery, management, orchestration)

### Scalability
- Multiple Styrene nodes can connect to one hub
- TUI can connect to remote Styrene API
- Hub provides transport for all devices

### Flexibility
- Styrene can run anywhere (local TUI, cluster, edge device)
- All Styrene instances share mesh visibility via announces
- API enables integration with other tools (web dashboards, automation)

## Future: Multi-Node Mesh

When you have multiple devices running Styrene:

```
┌──────────────┐
│ brutus       │
│ styrene-node │  ← Headless, API-enabled
└──────────────┘
       ↓ mesh
┌──────────────┐
│ edge-rpi-01  │
│ styrene bond │  ← Headless, announce-only
└──────────────┘
       ↓ mesh
┌──────────────┐
│ laptop       │
│ styrene TUI  │  ← Interactive, connects to brutus API
└──────────────┘
```

All three discover each other via announces. The TUI can manage remote nodes through the brutus API or direct LXMF RPC (future phase).

## Next Steps

1. **Create daemon module** - `src/styrene/daemon.py`
2. **Create API module** - `src/styrene/api.py`
3. **Add announce to lifecycle** - Update `StyreneLifecycle.create_styrene_destination()`
4. **Test locally** - Run against existing hub
5. **Deploy to cluster** - Push container, apply manifests
