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

# Huginn

> Huginn runs agents that watch events online and trigger automated actions.

export const UseCaseNavigation = () => {
  useLayoutEffect(() => {
    const storageKey = "towbar-use-case-open-groups";
    const groupSelector = "#sidebar li[data-title] > button[aria-expanded]";
    let restoring = false;
    let frame = 0;
    function storedGroups() {
      try {
        return new Set(JSON.parse(sessionStorage.getItem(storageKey) || "[]"));
      } catch {
        return new Set();
      }
    }
    function save(groups) {
      try {
        sessionStorage.setItem(storageKey, JSON.stringify([...groups]));
      } catch {}
    }
    function groupName(button) {
      return button.parentElement?.dataset.title;
    }
    function rememberOpenGroups() {
      const groups = new Set();
      for (const button of document.querySelectorAll(groupSelector)) {
        if (button.getAttribute("aria-expanded") === "true") {
          const name = groupName(button);
          if (name) groups.add(name);
        }
      }
      save(groups);
    }
    function restoreOpenGroups() {
      frame = 0;
      const groups = storedGroups();
      if (!groups.size) return;
      restoring = true;
      for (const button of document.querySelectorAll(groupSelector)) {
        if (groups.has(groupName(button)) && button.getAttribute("aria-expanded") === "false") {
          button.click();
        }
      }
      restoring = false;
    }
    function scheduleRestore() {
      if (!frame) frame = requestAnimationFrame(restoreOpenGroups);
    }
    function onClick(event) {
      if (restoring || !(event.target instanceof Element)) return;
      const button = event.target.closest(groupSelector);
      if (button) {
        const name = groupName(button);
        if (!name) return;
        const groups = storedGroups();
        if (button.getAttribute("aria-expanded") === "true") groups.delete(name); else groups.add(name);
        save(groups);
        return;
      }
      if (event.target.closest('a[href^="/docs/use-cases/"]')) {
        rememberOpenGroups();
      }
    }
    const observer = new MutationObserver(scheduleRestore);
    document.addEventListener("click", onClick, true);
    observer.observe(document.getElementById("sidebar") || document.body, {
      childList: true,
      subtree: true
    });
    scheduleRestore();
    return () => {
      document.removeEventListener("click", onClick, true);
      observer.disconnect();
      cancelAnimationFrame(frame);
    };
  }, []);
  return null;
};

<UseCaseNavigation />

**Upstream source:** [Current installation guide](https://github.com/huginn/huginn/blob/master/doc/docker/install.md).

The official multi-process Huginn image runs its web app and agent workers together. Point it at a Towbar MySQL Datastore so the built-in database is not lost with the container.

## Towbar manifests

<CodeGroup>
  ```yaml title=".towbar/datastores/huginn-mysql.datastore.yml" theme={"system"}
  id: huginn-mysql
  name: Huginn MySQL
  type: mysql
  container:
    network: application
    networkAlias: huginn-mysql
    resources:
      cpus: 1
      memory: 1g
  secrets:
    runtime:
      - MYSQL_ROOT_PASSWORD
      - MYSQL_DATABASE
      - MYSQL_USER
      - MYSQL_PASSWORD
  environments:
    production:
      server: 192.0.2.10
  ```

  ```yaml title=".towbar/services/huginn.service.yml" theme={"system"}
  id: huginn
  name: Huginn
  buildServer: null
  deployment:
    type: image
    image: ghcr.io/huginn/huginn@sha256:5bb5bdd17b49b7cf0ccd7ffdd5535abe1c8a684272037b34b438c4739908bf01
  container:
    port: 3000
    network: application
  environments:
    production:
      server: 192.0.2.10
  secrets:
    runtime:
      - HUGINN_DATABASE_ADAPTER
      - HUGINN_DATABASE_HOST
      - HUGINN_DATABASE_PORT
      - HUGINN_DATABASE_NAME
      - HUGINN_DATABASE_USERNAME
      - HUGINN_DATABASE_PASSWORD
      - DO_NOT_CREATE_DATABASE
      - SEED_USERNAME
      - SEED_PASSWORD
      - APP_SECRET_TOKEN
      - DOMAIN
  domains:
    primary: huginn.example.com
  tls:
    mode: direct
  ```
</CodeGroup>

## Configure

1. Prepare the example server, connect the repository, and map `production` to the branch containing these manifests. Replace the example server IP and domain. Commit the manifests, then sync the repository and inspect the resolved configuration.
2. Save the initialization values in the table below for the mysql Datastore under **Datastore → Settings → Secrets**. Deploy it first and wait for readiness. These values create the database and user only on an empty volume.
3. Set the Service's declared runtime values under **Service → Settings → Secrets** using the table below. Replace descriptions and placeholders with actual values; do not commit passwords or keys.
4. Deploy the Service, then perform the checks below before enabling auto-deploy.

## Datastore values

Save these in each Datastore's **Settings → Secrets** before deploying it. Use the suggested names or choose your own, then use the same names and passwords in the Service connection values below.

| Datastore    | Key                   | Suggested value                                                                           |
| ------------ | --------------------- | ----------------------------------------------------------------------------------------- |
| Huginn mysql | `MYSQL_DATABASE`      | huginn                                                                                    |
| Huginn mysql | `MYSQL_USER`          | huginn                                                                                    |
| Huginn mysql | `MYSQL_PASSWORD`      | Run `openssl rand -hex 32`; save its output and reuse it in the Service connection value. |
| Huginn mysql | `MYSQL_ROOT_PASSWORD` | Run `openssl rand -hex 32` again for a separate root password.                            |

## Runtime values

Save these values on the Service after the repository sync. The manifest declares required keys, not their values.

| Key                        | Value to save                                                               |
| -------------------------- | --------------------------------------------------------------------------- |
| `HUGINN_DATABASE_ADAPTER`  | mysql2                                                                      |
| `HUGINN_DATABASE_HOST`     | huginn-mysql                                                                |
| `HUGINN_DATABASE_PORT`     | 3306                                                                        |
| `HUGINN_DATABASE_NAME`     | Same value as MYSQL\_DATABASE.                                              |
| `HUGINN_DATABASE_USERNAME` | Same value as MYSQL\_USER.                                                  |
| `HUGINN_DATABASE_PASSWORD` | Same value as MYSQL\_PASSWORD.                                              |
| `DO_NOT_CREATE_DATABASE`   | true                                                                        |
| `SEED_USERNAME`            | Choose the first administrator username.                                    |
| `SEED_PASSWORD`            | Generate the first administrator password.                                  |
| `APP_SECRET_TOKEN`         | Generate with `openssl rand -hex 64` and keep it unchanged across restarts. |
| `DOMAIN`                   | huginn.example.com                                                          |

## Verify

Sign in with the seeded account, create a test agent, and confirm it runs after a redeploy. Back up the MySQL Datastore.

For field constraints, see [Service manifest](/docs/services/manifest) and [Datastore manifest](/docs/datastores/manifest).
