> ## Documentation Index
> Fetch the complete documentation index at: https://docs.hiroleague.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Advanced Setup

> Deploy server on a VPS, Hardening Local Setup, Multiple Profiles

The [Quickstart](/task-manager/get-started/quickstart) covers One Local Setup on a single machine. For everything else, stay on this page.

## Concepts you need first

### Credentials

| Credential                | Who uses it                        | Where it lives                                                                                                           | How to manage                                                                                                               |
| ------------------------- | ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------- |
| **Web passphrase**        | Browser users                      | `auth.json` under the profile's auth dir (`~/.taskmanager/profiles/<name>/auth/`)                                        | Set in the web UI on first visit, gated by a one-time **bootstrap setup token** the launcher prints in the server terminal. |
| **Recovery key**          | Browser users                      | `auth.json` under the profile's auth dir (`~/.taskmanager/profiles/<name>/auth/`)                                        | Set in the web UI on first visit, gated by a one-time **bootstrap setup token** the launcher prints in the server terminal. |
| **CLI API key** (`tmk-…`) | `hirotm` and remote scripts/agents | Hash in `cli-api-keys.json` (server-side, same auth dir as `auth.json`); raw key in the client profile's `api_key` field | `hirotaskmanager server api-key generate / list / revoke`.                                                                  |

A CLI API key authenticates as the **CLI principal** only. It can never be used to log into the web UI, change CLI access policy, or manage other API keys. See [CLI access policy](/task-manager/cli/cli-access-policy).

## Server/Client setup

Deploy the API server on a VPS behind HTTPS, then point your laptop's CLI at it. The instructions below target a Debian 11+ VPS (e.g. a Google Cloud VM) on `x86_64`; adapt package commands for other distros.

### A - Prerequisites

<Steps>
  <Step title="Point a hostname at the VPS">
    Create a public DNS **A record** (e.g. `hirotm.example.com`) pointing to the VPS's public IP, then confirm from your laptop:

    ```bash theme={null}
    dig +short hirotm.example.com
    ```

    <Warning>If DNS doesn't resolve, the reverse proxy in **Configure HTTPS** cannot issue a TLS certificate and the web UI won't load.</Warning>
  </Step>

  <Step title="Update system packages">
    On the VPS:

    ```bash theme={null}
    sudo apt update && sudo apt upgrade -y
    sudo apt install -y curl unzip ca-certificates debian-keyring debian-archive-keyring apt-transport-https ufw
    ```
  </Step>

  <Step title="Create a dedicated user (recommended)">
    Don't run the server as `root`. Give it its own home so Bun and the `~/.taskmanager/` profiles stay isolated.

    ```bash theme={null}
    sudo useradd --system --create-home --shell /bin/bash --home-dir /home/taskmanager taskmanager
    sudo -iu taskmanager
    ```

    Your prompt should now be `taskmanager@<hostname>:~$`. Run every command in the next two sections as this user unless the snippet starts with `sudo`.
  </Step>
</Steps>

***

### B - Install Task Manager

<Steps>
  <Step title="Install Bun">
    As the `taskmanager` user:

    ```bash theme={null}
    curl -fsSL https://bun.sh/install | bash
    ```

    Add Bun to your shell's PATH:

    ```bash theme={null}
    echo 'export BUN_INSTALL="$HOME/.bun"' >> ~/.bashrc
    echo 'export PATH="$BUN_INSTALL/bin:$PATH"' >> ~/.bashrc
    exec bash -l
    ```

    Verify Bun is installed:

    ```bash theme={null}
    bun --version
    ```

    <Note>You can use npm instead of Bun, but Bun is recommended for cold-start performance.</Note>
  </Step>

  <Step title="Install the hirotaskmanager package">
    ```bash theme={null}
    bun install -g @hiroleague/taskmanager
    hirotaskmanager --version
    ```
  </Step>
</Steps>

***

### C - Configure HTTPS

The Task Manager API listens on `127.0.0.1:3001` (or the port you specified in the setup wizard). The public internet only needs SSH, HTTP, and HTTPS — a reverse proxy terminates TLS and forwards to the loopback port.

<Steps>
  <Step title="Restrict the firewall to 80/443">
    ```bash theme={null}
    sudo ufw allow OpenSSH
    sudo ufw allow 80/tcp
    sudo ufw allow 443/tcp
    sudo ufw enable
    sudo ufw status
    ```
  </Step>

  <Step title="Install a TLS reverse proxy">
    Pick an option below or use your own.

    <Note>Replace `hirotm.example.com` with the hostname you specified in the prerequisites. Replace `3001`  with the port you intend to use in the setup wizard.</Note>

    <Tabs>
      <Tab title="Caddy (recommended)">
        Minimal config, automatic certificates out of the box.

        ```bash theme={null}
        curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' \
          | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
        curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' \
          | sudo tee /etc/apt/sources.list.d/caddy-stable.list
        sudo apt update
        sudo apt install -y caddy
        ```

        Write the site config:

        ```bash theme={null}
        sudo tee /etc/caddy/Caddyfile >/dev/null <<'EOF'
        hirotm.example.com {
            encode zstd gzip
            reverse_proxy 127.0.0.1:3001
        }
        EOF

        sudo systemctl reload caddy
        ```

        Watch the first reload to confirm the cert was issued, then `Ctrl+C`:

        ```bash theme={null}
        sudo journalctl -u caddy -f
        ```

        Look for `certificate obtained successfully`.
      </Tab>

      <Tab title="nginx">
        ```bash theme={null}
        sudo apt install -y nginx certbot python3-certbot-nginx
        ```

        Create the vhost (HTTP only — Certbot will rewrite it later):

        Drop the file where your distro expects.

        ```bash theme={null}
        sudo tee /etc/nginx/conf.d/hirotm.conf >/dev/null <<'EOF'
        server {
            listen 80;
            listen [::]:80;
            server_name hirotm.example.com;

            location / {
                proxy_pass http://127.0.0.1:3001;
                proxy_http_version 1.1;
                proxy_set_header Host              $host;
                proxy_set_header X-Real-IP         $remote_addr;
                proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
                proxy_set_header X-Forwarded-Proto $scheme;
                proxy_set_header Upgrade           $http_upgrade;
                proxy_set_header Connection        "upgrade";
                proxy_read_timeout 60s;
            }
        }
        EOF

        sudo nginx -t && sudo systemctl reload nginx
        ```

        Issue the certificate and let Certbot wire HTTPS into the same vhost:

        ```bash theme={null}
        sudo certbot --nginx -d hirotm.example.com
        sudo systemctl status certbot.timer --no-pager
        ```

        A smoke test from the VPS will return `502` until the next section starts the server — that's expected:

        ```bash theme={null}
        curl -I https://hirotm.example.com
        ```
      </Tab>
    </Tabs>
  </Step>
</Steps>

***

### D - Initialize the server

<Steps>
  <Step title="Run the setup wizard">
    As the `taskmanager` user:

    ```bash theme={null}
    hirotaskmanager --setup-server --profile vps
    ```

    Answer the prompts:

    | Prompt                                                 | Value                 | Notes                                                                       |
    | ------------------------------------------------------ | --------------------- | --------------------------------------------------------------------------- |
    | Port                                                   | `3001`                | Pick any internal port                                                      |
    | Data dir                                               | `~/.taskmanager/data` | Location of the database                                                    |
    | Accept connections from other machines on the network? | `N`                   | Reverse proxy handles internet traffic                                      |
    | Require a CLI API key for local connections too?       | `Y`                   | Not required, but more secure                                               |
    | Open browser on start?                                 | `N`                   | Headless server                                                             |
    | Mint a CLI API key now?                                | `Y`                   | Save it for your client profile later                                       |
    | Set as default profile?                                | `Y`                   | easier, does not require adding `--profile` argument in local CLI commands. |
    | Start the server now?                                  | `Y`                   | The server starts in the background.                                        |

    <Warning>The wizard prints the `tmk-…` CLI API key **once**. You will need it for your client profile later. </Warning>
  </Step>

  <Step title="Open the first-time setup link">
    The setup wizard prints a deep-link of the form `https://127.0.0.1:3001/?setupToken=<token>`. replace ip:port with your hostname and paste it into your client browser

    * Pick a **passphrase** for the web UI.
    * Back to the server terminal and save the **recovery key** in a safe place, not on your client machine.
    * Log in with your passphrase.

    <Note>The setup token is single-use. Once a passphrase is created the token is destroyed</Note>
  </Step>

  <Step title="Confirm the server is healthy">
    From the VPS:

    ```bash theme={null}
    hirotaskmanager server status --profile vps
    curl -I https://hirotm.example.com
    ```

    The status command should report `"running": true` with `"api_url": "http://127.0.0.1:3001"`, and the `curl` should return `200`.
  </Step>
</Steps>

***

### E - Connect a CLI client

Run these on **your laptop**, not the VPS.

<Steps>
  <Step title="Install hirotaskmanager locally">
    ```bash theme={null}
    bun install -g @hiroleague/taskmanager
    ```
  </Step>

  <Step title="Run the client setup wizard">
    ```bash theme={null}
    hirotaskmanager --setup-client --profile remote
    #   api_url : https://hirotm.example.com
    #   api_key : tmk-…   (paste the key from the previous section)
    #   set as default profile? : Y
    ```

    Verify the connection:

    ```bash theme={null}
    hirotm boards list
    ```
  </Step>

  <Step title="Install Skills">
    Install Hiro Task Manager skills on your client machine - This is where your AI agents will run.

    ```bash theme={null}
    bunx skills add hiro-league/hirotaskmanager
    ```
  </Step>
</Steps>

***

## Run Server as a service

This is optional, but useful if you are using Hiro Task Manager frequently.
If you need your hirotaskmanager server to survive a system reboot or a failure, you can run it as a service.
Instructions may vary depending on your \*nix distro.

<Steps>
  <Step title="Stop the hirotaskmanager server">
    As your taskmanager user `sudo -iu taskmanager`:

    ```bash theme={null}
    hirotaskmanager server stop --profile vps
    ```
  </Step>

  <Step title="Create the systemd unit">
    As your sudo user

    ```bash theme={null}
    sudo tee /etc/systemd/system/taskmanager.service >/dev/null <<'EOF'
    [Unit]
    Description=Hiro Task Manager
    After=network.target

    [Service]
    Type=simple
    User=taskmanager
    Group=taskmanager
    Environment=PATH=/home/taskmanager/.bun/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
    ExecStart=/home/taskmanager/.bun/bin/hirotaskmanager server start --profile vps --foreground
    Restart=on-failure
    RestartSec=5

    NoNewPrivileges=true
    ProtectSystem=strict
    ProtectHome=read-only
    ReadWritePaths=/home/taskmanager/.taskmanager
    PrivateTmp=true

    [Install]
    WantedBy=multi-user.target
    EOF

    sudo systemctl daemon-reload
    ```
  </Step>

  <Step title="Enable and start the service">
    ```bash theme={null}
    sudo systemctl enable --now taskmanager
    sudo systemctl status taskmanager --no-pager
    sudo journalctl -u taskmanager -f
    ```

    The journal should show the server bound to `127.0.0.1:3001`. Press `Ctrl+C` to stop tailing.
  </Step>
</Steps>

***

## Hardening local setup

Useful on shared workstations or production parity testing on one machine: the server stays on `127.0.0.1` but the CLI must still present a key.

```bash theme={null}
hirotaskmanager --setup-server --profile main
#   accept connections from other machines?     : N
#   require a CLI API key for local connections?: Y      (defense in depth)
#   mint a first CLI API key now?               : Y      (saved to profile via --save-to-profile)
#   set as default profile?                     : Y
#   start server now?                           : Y

hirotm boards list   # local CLI authenticates via api_key in the same profile
```

Because the wizard ran `server api-key generate --save-to-profile`, the freshly minted key is written into the same profile's `api_key` field. The local CLI authenticates immediately, with no extra prompts.

Other local users on the same machine cannot reach the API just by connecting to `127.0.0.1:3001` — they need a valid key.

***

## Wizard Options Explained

| Prompt                                                 | Default   | Notes                                                                                                  |
| ------------------------------------------------------ | --------- | ------------------------------------------------------------------------------------------------------ |
| Profile name                                           | `default` | run using --profile \<name> if it is not set as default profile                                        |
| Port                                                   | `3001`    | default port for the server                                                                            |
| Accept connections from other machines on the network? | **N**     | No for both `Local Setup` and `Server/Client Setup`                                                    |
| Create API key for CLI?                                | **N**     | select Yes for `Server/Client Setup`. No for `Local Setup` unless you want tightened hardening locally |
| Open browser on start?                                 | **Y**     | The web UI opens automatically.                                                                        |
| Set as default profile?                                | **Y**     | Default Profile doesn't need adding `--profile` argument.                                              |
| Start server now?                                      | **Y**     | The server starts in the background.                                                                   |

***

## Common Profile config errors

Errors related to profile configuration manual edits.

| Situation                                                                                                          | Error                                                                                                                                                                    |
| ------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Server profile sets `api_url`                                                                                      | Server profiles **must not** set `api_url` — it is auto-derived from `port`.                                                                                             |
| Client profile is missing `api_url`                                                                                | Client profiles **require** an absolute `api_url`.                                                                                                                       |
| Client profile is missing `api_key`                                                                                | Client profiles **require** an `api_key`.                                                                                                                                |
| Client profile sets server-only fields (`port`, `data_dir`, `open_browser`, `bind_address`, `require_cli_api_key`) | Client profiles must contain only client fields.                                                                                                                         |
| `bind_address` is non-loopback (e.g. `0.0.0.0`) **and** `require_cli_api_key: false`                               | Public exposure without a key is rejected — set `require_cli_api_key: true` (or unset it; the validator forces it).                                                      |
| Server profile has `require_cli_api_key: true` but no `api_key`                                                    | **Warning** (not an error). The local CLI will fail with `auth_cli_key_required` until you provide a key — either set it in the profile or pass `--api-key` per command. |

***

## Hiro Developers Only

Working on the Task Manager source, you typically want a **dev profile** that uses `--dev` (port 3002, dev CORS, no built `dist/`) without overwriting your installed default profile.

**Run a dev profile**

```bash theme={null}
# In the repo root:
bun run dev                                # full dev stack (Vite + API on port 3002)
# or just the API on the dev profile:
hirotm server start --dev --profile dev --background
```

The `dev` profile is a regular profile name with no special meaning — it just keeps your dev work in `~/.taskmanager/profiles/dev/` so it can't collide with an installed `main` profile.

**`--profile dev` in commands**

The repo's `AGENTS.md` instructs agents and humans to use `--profile dev` for every command in the source tree:

```bash theme={null}
hirotm server status --profile dev
hirotm server start --profile dev
hirotm boards list --profile dev
```

That way, the default-profile pointer set by your installed `hirotaskmanager` is unaffected.

***

## Related

* [Quickstart](/task-manager/get-started/quickstart) — same-machine path.
* [Profiles](/task-manager/hiro-developers/profiles) — schema, roles, default-profile pointer.
* [Server operations](/task-manager/cli/server) — `server start/stop/status` and `server api-key …`.
* [CLI access policy](/task-manager/cli/cli-access-policy) — what the CLI principal can and cannot do.
* [Errors & exit codes](/task-manager/cli/error-codes) — `auth_cli_key_required`, `invalid_config`, and friends.
