First of all, huge kudos for considering adding a new service to the Harbor! 🎉

To add a new service, you'll need to consider a few things:

- [Service runtime and connections](#service-runtime-and-connections)
- [Service configuration](#service-configuration)

## Service runtime and connections

See the guide on [dynamic multi-file](./Harbor-Compose-Setup.md#dynamic-multi-file-configuration) and [cross-file](./Harbor-Compose-Setup.md#cross-service-file) configurations.

### 0. Pick a service Handle!

The service handle is a unique identifier for the service. It should be unique, represent the service well and be easy(ish) to type.

This handle has to be used in:
- compose file names
- compose service names
- container names
- folder names
- env variables
- config files

```bash
# Good
sop
postner
litellm
webui

# Meh
lm-evaluation-harness
service-51
service_1
```


### 1. Add new service file(s)

> [!TIP] You can use the [scaffold script](https://github.com/av/harbor/blob/main/.scripts/scaffold.ts) to scaffold the files.

Create a new file with the service handle in the name. If service will need to interact with other existing services, use the cross-file rule. Also consider platform-specific files, if relevant.

```bash
# Main service definitions
compose.service.yml

# Extension for CUDA toolkit
# and access to the GPU
compose.service.nvidia.yml

# Cross-service file, will be included
# if both "service" and "otherservice" are running
compose.x.service.otherservice.yml
```

#### Compose file linting

- Service name should be the service handle
- Container name should be `${HARBOR_CONTAINER_PREFIX}.<service handle>`
- Main `.env` file should be connected
- Ensure to only include what's necessary for the service to run standalone to go into the main service file
  - Anything outlining a dependency should go into cross-service files
- See Harbor's `.env` for the shared things like folder locations, tokens, etc. that might be needed

You'll find plenty of examples in the repo, but here's a simple one:

```yaml
services:
  service:
    # It's important to keep Harbor's container prefix
    container_name: ${HARBOR_CONTAINER_PREFIX}.service
    # Prefer to make version customizable
    image: repo/image:${HARBOR_SERVICE_VERSION}
    ports:
    # When multiple ports are exposed, the one with lowest
    # in-container port will be the "main" one
      - ${HARBOR_SERVICE_HOST_PORT}:80
    volumes:
    # Service local workspace
      - ./service/data:/app/data
    # Global service cache/config folder should be
    # set via a variable
      - ${HARBOR_SERVICE_CACHE_CACHE}:/app/cache
    env_file:
    # Always add the main .env
      - ./.env
    # If service supports it, add an override too
      - ./service/override.env
```

### 3. Service volumes

Keep in mind that service might want to persist some files, or be integrated with global config or global caches. Reflect on that in the main service definition.

If persistence is needed, ensure to use the same naming as a service handle.

```
harbor/
  otherservice/
  service/
```

Please, add any persistent data to the `.gitignore`.

## Service configuration

Then, we're ready to move onto providing the configuration necessary for the service. It might come via:
- [Configuration files](#4-configuration-files)
- [Environment variables](#5-environment-variables)
- [CLI arguments](#6-cli-arguments)

See the guides on [config merging](./Harbor-Compose-Setup.md#config-merging) and [config interpolation](./Harbor-Compose-Setup.md#config-interpolation)

### 4. Configuration files

Using the same folder as for the persisted data, specify the "main" configuration file and any "cross-service" configuration files.

```bash
service/
  configs/
    # Base config, added whenever the service is running
    config.yml
    # Added whenever the service is running alongside the "tts" service
    config.tts.yml
``` # Added whenever the service is running alongside the "litellm" and "langfuse" services
    config.x.litellm.langfuse.yml
```

In order to setup the config merging:
- Create necessary compose files replicating the structure of the config files
- Replace service entrypoint with a custom one
- Mount shared utils with the config merging logic
- In the custom entrypoint - merge configs together and store as a unified config file for the service to consume (preferably at service's default location)

See examples: [litellm](https://github.com/av/harbor/tree/main/litellm), [webui](https://github.com/av/harbor/tree/main/open-webui/configs)

### 5. Environment variables

You can pass environment variables in multiple ways:

#### 5.1 Compose File

```yaml
services:
  service:
    env_file: ./.env
    environment:
      - ENV_VAR=${HARBOR_ENV_VAR}
```

Useful when you want to ensure that in the end service will receive a specific value, or when the env variable name is overly abstract.

#### 5.2 `.env` file

```bash
# .env
SERVICE_CUSTOM_VARIABLE="value"
```

Should be reserved for cases where a given variable is persistent, global and generally doesn't change.

#### 5.3 Config Interpolation

```bash
# service/configs/config.json
{
  "key": "${HARBOR_ENV_VAR}"
}
```

> [!NOTE]
> It doesn't work for any mounted files, only when using the config merging logic.

An alternative to in-compose env vars, also for cases when service doesn't support env vars for a given configuration natively.

## 6. CLI Arguments

Typical pattern for the Harbor is to have `service args` CLI to set the "extra" arguments to be passed to the service. It can be added in a few steps:

### 6.1. Add `HARBOR_` env var

```bash
# .env
HARBOR_SERVICE_EXTRA_ARGS=""
```

It automatically becomes manageable via the `harbor` env manager:
```bash
harbor config set service.extra_args "--some-arg 123"
```

### 6.2. Add to the service definition

Add this to the `service.compose.yml`:

```yaml
services:
  service:
    command: >
      ${HARBOR_SERVICE_EXTRA_ARGS}
      # Other service command
```

### 6.3. Service sub-CLI

If the service has its own sub-cli, i.e. `harbor service`, ensure to also add the shortcuts for arg management there.

You can use two helpers for quickly mapping sub-cli sections to the configuration:
- `env_manager_alias` - for string values (get/set syntax depending on the value presence + help)
- `env_manager_arr` - for array value (ls/rm/add syntax + help)

Example:
```bash
# Service sub-cli should be a dedicated bash function
# with the "run_<service>_command" name
run_service_command() {
    case "$1" in
        version)
            shift
            env_manager_alias service.version "$@"
            ;;
        tags)
            shift
            env_manager_arr service.tags --on-set update_main_key "$@"
            ;;
        *)
            echo "Usage: harbor service {version|tags}"
            # When nothing matched at a service level
            # return the special exit code to indicate
            # that this command could be retried with a
            # different order of arguments
            return $scramble_exit_code
            ;;
    esac
}

main_entrypoint() {
    case "$1" in
        # Add the service to the main CLI router
        # you can use aliases where semantically correct
        newservice)
            shift
            run_service_command "$@"
            ;;
        # Other cases...
    esac
}
```

## 7. Add service to the Harbor App

Harbor App has a [mini-registry](https://github.com/av/harbor/blob/main/app/src/serviceMetadata.tsx#L37) of services with additional tags for filtering. The newly added service needs to be present there to align the presentation with already existing services.

```typescript
export const serviceMetadata = {
  // ...
  newservice: {
    tags: [HST.satellite, HST.api],
    wikiUrl: // URL in Harbor's wiki
  }
  // ...
}
```

The metadata must have at least one primary tag (`frontend`, `backend`, `satellite`). Presence of the `cli` tag will block the "launch" functionality in the UI.

## 8. Test the service

```bash
# Start the service to debug
harbor up service

# See the logs
harbor logs service

# Check if the port mapping is correct
harbor open service
harbor url service
harbor url -i service
harbor url -a service
harbor qr service

# Inspect/debug the container if needed
# 1. Start the shell in service container
harbor shell service
# 2. Run arbitrary commands in running service container
harbor exec service <...>
# 3. Run one-off container with service image
harbor run service <...>
```

## 9. Document the new service

Add some information about the service to:
- `show_help`
- `harbor.prompt` files across the repo
- Wiki - Services, with examples and instructions
- Service Roaster section in the README
