zinit 0.3.9

Process supervisor with dependency management
Documentation
# Service Port Checking in Zinit

Zinit supports port declarations for services to detect and prevent port conflicts at startup.

## Overview

Services can declare which TCP ports they use. When a service is started, zinit checks if those ports are already in use by another running service. If a conflict is detected, the start fails with a clear error message.

## Usage

### CLI: ServiceConfigBuilder

```rust
use zinit::client::client::ServiceConfigBuilder;

let config = ServiceConfigBuilder::new("my-service")
    .exec("/usr/bin/my-app")
    .port(8080)      // Single port
    .port(8081)      // Can be called multiple times
    .port(9000)      // Add as many as needed
    .build();

// Start the service - zinit will check if ports are available
client.service_set(config);
```

### Rhai Scripting

```rhai
#!/usr/bin/env zinit

let z = new ZinitFactory().connect();

let config = new ServiceBuilder()
    .name("web-server")
    .exec("/usr/bin/nginx")
    .port(80)   // HTTP
    .port(443)  // HTTPS
    .port(8080) // Admin API
    .to_config();

z.service_set(config);
```

### TOML Configuration

Services can be configured via TOML files:

```toml
[service]
name = "web-app"
exec = "/usr/bin/my-app"
ports = [8080, 8081, 9000]  # List of TCP ports used by service

[lifecycle]
restart = "on-failure"
```

## How It Works

### Port Declaration

Services declare the TCP ports they bind to:

```rhai
ServiceBuilder::new("app")
    .port(3000)
    .port(3001)
    .port(9000)
```

### Conflict Detection

When a service is started:

1. Zinit collects all ports from the service definition
2. For each port, it checks if another running service has claimed it
3. If a conflict is found, the start operation fails immediately
4. A clear error message indicates which service owns the conflicting port

### Error Handling

```
Error: Cannot start service 'web-app': Port 8080 already in use by 'api-server'
```

This prevents silent failures and makes debugging port issues straightforward.

## Benefits

1. **Prevents Port Conflicts** - Ensures no two services bind to the same port
2. **Clear Error Messages** - Identifies exactly which service is using a port
3. **Easy Configuration** - Single `.port()` call per port (can be chained)
4. **Works Everywhere** - Supported in CLI, Rhai, and TOML configs
5. **Dependency Alternative** - Can be used instead of service dependencies to express port requirements

## Examples

### Web Server Cluster

```rhai
// Web servers on different ports
for i in range(0, 3) {
    let port = 8000 + i;
    let config = ServiceBuilder::new("web-" + i)
        .exec("/usr/bin/nginx")
        .port(port)
        .to_config();
    z.service_set(config);
}
```

### Multi-Service Application

```rhai
// Frontend
z.service_set(ServiceBuilder::new("frontend")
    .exec("/app/frontend/server")
    .port(3000)
    .port(3001)  // WebSocket
    .to_config());

// Backend API
z.service_set(ServiceBuilder::new("backend")
    .exec("/app/backend/api")
    .port(5000)
    .port(5001)  // Health check
    .to_config());

// Database
z.service_set(ServiceBuilder::new("db")
    .exec("postgres")
    .port(5432)
    .critical(true)
    .to_config());
```

### Conditional Port Assignment

```rhai
let config = ServiceBuilder::new("app");

// Dynamic port selection
if env::get("ENVIRONMENT") == "production" {
    config.port(8080);
    config.port(8081);
} else {
    config.port(9000);
}

z.service_set(config.to_config());
```

## Implementation Details

### Port Storage

Ports are stored as a vector of `u16` in `ServiceDef`:

```rust
pub struct ServiceDef {
    pub name: String,
    pub exec: String,
    // ... other fields ...
    pub ports: Vec<u16>,
}
```

### Method Signatures

**Client CLI:**
```rust
pub fn port(mut self, port: u16) -> Self {
    self.service.ports.push(port);
    self
}
```

**Rhai:**
```rhai
pub fn port(mut self, port: i64) -> Self {
    // Validates port is 1-65535 before adding
    if port > 0 && port <= 65535 {
        self.ports.push(port as u16);
    }
    self
}
```

### Configuration

Ports can be specified in TOML:

```toml
[service]
name = "app"
ports = [8080, 8081, 9000]
```

## Limitations

- **Static Declaration** - Ports must be known at service creation time
- **No Port Ranges** - Individual ports only, not port ranges
- **TCP Only** - Currently TCP ports; UDP support could be added
- **No Service Dependencies** - Ports don't create automatic dependencies (use `requires` for that)

## Future Enhancements

- Port range support: `.ports([8000, 8099])`
- UDP port checking
- Service dependencies based on ports
- Port mapping/translation
- Dynamic port discovery

## Troubleshooting

### "Port already in use" Error

Check which service owns the port:

```bash
zinit list  # List all services
```

Or use Rhai:

```rhai
let z = new ZinitFactory().connect();
for service in z.list() {
    let status = z.status(service);
    if status.state == "running" {
        print(service + " is running");
    }
}
```

### Ports Not Being Checked

Ensure ports are declared before service creation:

```rhai
// Wrong: ports added after creation
let config = ServiceBuilder::new("app").to_config();
config.port(8080);  // Too late!

// Correct: ports added before to_config()
let config = ServiceBuilder::new("app")
    .port(8080)
    .to_config();
```

## See Also

- [Rhai Scripting Guide]RHAI_SCRIPTING.md
- [Service Configuration]CONFIG.md
- Dependencies: Use `requires`, `wants`, `conflicts` for service relationships