Files

393 lines
8.3 KiB
Markdown

# Service Orchestration: slcompose
## Overview
**slcompose** is the central service orchestrator for SilverLinux. It manages all Docker services in `/srv/docker/` with automated secret injection from Infisical.
## Architecture
```
System Boot
systemd: slcompose.service
/usr/local/bin/slcompose boot
For each service in /srv/docker/:
├─ Load Infisical Token from /etc/infisical/token
├─ Discover docker-compose.yml
├─ Extract service name from directory (e.g., gitea)
├─ Inject secrets: infisical run --path=/gitea
└─ Execute: docker compose up -d
```
## Installation
Copy `slcompose.sh` to `/usr/local/bin/slcompose` and make executable:
```bash
sudo cp docs/slcompose.sh /usr/local/bin/slcompose
sudo chmod +x /usr/local/bin/slcompose
```
Copy systemd service file:
```bash
sudo cp docs/slcompose.service /etc/systemd/system/slcompose.service
sudo systemctl daemon-reload
sudo systemctl enable slcompose.service
```
## Usage
### List Services
```bash
slcompose list
```
Output:
```
Available services:
====================
- gitea
- portainer
- postgres
- mssql
- openproject
- jitsi
- nginx-proxy-manager
- postgres
- baget
- dbgate
- nextcloud
```
### Boot All Services
```bash
slcompose boot
```
This is automatically called by systemd on server startup.
### Start a Service
```bash
slcompose up gitea
```
- Loads Infisical token
- Injects secrets from Infisical path: `/gitea`
- Runs: `docker compose up -d` in `/srv/docker/gitea`
### Stop a Service
```bash
slcompose down gitea
```
### Restart a Service
```bash
slcompose restart gitea
```
### View Logs
Stream live logs (Ctrl+C to exit):
```bash
slcompose logs gitea
```
View last N lines (default 200):
```bash
slcompose logs-tail gitea
slcompose logs-tail gitea 500
```
### View Injected Environment Variables
View all environment variables injected into a specific service from Infisical:
```bash
slcompose env gitea
```
Output (color-coded):
```
========================================
Environment Variables for: gitea
========================================
DB_HOST = postgres
DB_NAME = gitea_db
DB_PASSWD = secure_password_123
DB_TYPE = postgres
DB_USER = gitea
DOMAIN = git.silveressence.net
...
```
*(Blue: variable names | Green: values)*
View injected environment variables for ALL services:
```bash
slcompose env-all
```
Output (color-coded):
```
========================================
All Services and Their Environment Variables
========================================
--- gitea ---
DB_HOST = postgres
DB_NAME = gitea_db
...
--- portainer ---
ADMIN_PASSWORD = secure_password
...
--- postgres ---
POSTGRES_DB = main_db
...
```
*(Blue: variable names | Green: values)*
**Use Cases:**
- Verify secrets are properly injected
- Debug missing or incorrect environment variables
- Audit which services have access to which secrets
- Troubleshoot authentication or configuration issues
## How Secret Injection Works
### Prerequisites
1. **Infisical Token**: Stored at `/etc/infisical/token`
2. **Infisical Domain** (optional): Stored at `/etc/infisical/domain`
3. **Secrets Path**: Each service has secrets at path `/<SERVICE_NAME>` in Infisical
Example: For `gitea` service, secrets must be at `/gitea` in Infisical.
### Injection Process
For each service, slcompose executes:
```bash
infisical run \
--token "$INFISICAL_TOKEN" \
--env prod \
--path="/$SERVICE_NAME" \
--recursive \
-- docker compose "$@"
```
This:
1. Authenticates with Infisical using the token
2. Fetches all secrets from the specified path
3. Recursively includes nested secrets
4. Sets them as environment variables
5. Passes them to the docker compose command
### Environment Variables
All secrets from Infisical are available as environment variables inside the docker compose execution context and are used by `docker-compose.yml` files.
Example `docker-compose.yml`:
```yaml
services:
gitea:
image: gitea/gitea:latest
environment:
DB_TYPE: postgres
DB_HOST: postgres
DB_NAME: ${DB_NAME} # Injected from Infisical
DB_USER: ${DB_USER} # Injected from Infisical
DB_PASSWD: ${DB_PASSWD} # Injected from Infisical
```
## Service Directory Structure
Each service is a subdirectory in `/srv/docker/` containing:
```
/srv/docker/gitea/
├── docker-compose.yml
├── README.md (optional)
└── app.ini (optional, service-specific config)
```
Services are discovered by the presence of `docker-compose.yml`.
## Systemd Integration
### Service File: `/etc/systemd/system/slcompose.service`
```ini
[Unit]
Description=SilverLinux Docker Orchestrator (slCompose)
After=docker.service
Requires=docker.service
[Service]
Type=oneshot
ExecStart=/usr/local/bin/slcompose boot
RemainAfterExit=yes
User=root
[Install]
WantedBy=multi-user.target
```
**Key Details:**
- `Type=oneshot` — Runs once and exits (doesn't stay running)
- `RemainAfterExit=yes` — Systemd remembers the service as "active" after boot completes
- `After=docker.service` — Waits for Docker to start first
- `Requires=docker.service` — Fails if Docker is not available
- `WantedBy=multi-user.target` — Starts during normal multi-user boot
### Enable Auto-Boot
```bash
sudo systemctl enable slcompose.service
```
### Check Status
```bash
sudo systemctl status slcompose.service
```
### View Boot Logs
```bash
sudo journalctl -u slcompose.service -n 100
```
## Error Handling
### Missing Infisical Token
If `/etc/infisical/token` doesn't exist, slcompose exits with:
```
ERROR: Missing Infisical token at /etc/infisical/token
```
### Service Not Found
If you try to manage a non-existent service:
```bash
slcompose up nonexistent
```
Output:
```
ERROR: Service 'nonexistent' not found at /srv/docker/nonexistent
```
### Docker Not Running
If Docker isn't available, slcompose waits and retries:
```
Waiting for Docker...
Waiting for Docker...
```
## Troubleshooting
### Services not starting on boot
1. Check if systemd service is enabled:
```bash
sudo systemctl is-enabled slcompose.service
```
2. View boot logs:
```bash
sudo journalctl -u slcompose.service -n 50
```
3. Test slcompose manually:
```bash
slcompose boot
```
### Secrets not injected
1. Verify Infisical token exists:
```bash
cat /etc/infisical/token
```
2. Test Infisical access:
```bash
infisical run --token "$(cat /etc/infisical/token)" --env prod --path=/gitea -- env | grep -v '^_'
```
3. Check service paths in Infisical match service directory names
### Service fails to start
1. View detailed logs:
```bash
slcompose logs gitea
```
2. Check docker-compose.yml for syntax errors:
```bash
cd /srv/docker/gitea && docker-compose config
```
3. View full docker compose output:
```bash
cd /srv/docker/gitea && infisical run --token "$(cat /etc/infisical/token)" --env prod --path=/gitea -- docker compose up
```
## Adding New Services
1. Create service directory:
```bash
mkdir -p /srv/docker/myservice
```
2. Add `docker-compose.yml`:
```bash
cp template/docker-compose.yml /srv/docker/myservice/
```
3. Create secrets in Infisical at path `/myservice`
4. Test:
```bash
slcompose up myservice
slcompose logs myservice
slcompose down myservice
```
5. Service will automatically boot on next system startup
## Performance
- **Boot Time**: ~2 minutes for all 10+ services (includes Docker startup)
- **Per-Service**: ~5-10 seconds per service startup
- **Memory Overhead**: Minimal (orchestrator is bash script)
## Security
- **Secrets**: Never logged or displayed (only injected into containers)
- **Tokens**: Read from files with appropriate permissions
- **Docker Access**: Requires root/docker group membership
- **Service Isolation**: Uses Docker networks to isolate services
## Related Files
- [slcompose.sh](slcompose.sh) — Orchestrator script
- [slcompose.service](slcompose.service) — Systemd service file
- [AI_CONTEXT.md](AI_CONTEXT.md) — Infrastructure context
- [../README.md](../README.md) — Repository overview