# 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