# Install the desktop app

> Downloads for macOS, Windows and Linux, what to expect on first launch, and where your data is stored.

Source: https://triagic.com/docs/desktop/install

Get the installer from the [download page](/download). It detects your platform and
offers the right build first; every build is listed underneath with its size and
SHA256 checksum.

| Platform             | Build                  |
| -------------------- | ---------------------- |
| macOS, Apple Silicon | `.dmg` (arm64)         |
| macOS, Intel         | `.dmg` (x64)           |
| Windows              | `.exe` installer (x64) |
| Linux                | `.AppImage` or `.deb`  |

> **Note:** Pick the right macOS build
>
> Apple Silicon is anything M1 or later. If you are unsure, open the Apple menu →
> **About This Mac** and look at **Chip**. The Intel build will run on Apple Silicon under Rosetta, but
> slower and with more memory.

## Verifying a download [#verifying-a-download]

Each listed build shows a SHA256. To check it before installing:

```bash
shasum -a 256 ~/Downloads/Triagic-*.dmg
```

```powershell
Get-FileHash "$env:USERPROFILE\Downloads\Triagic-Setup.exe" -Algorithm SHA256
```

The output should match the checksum on the download page exactly.

## macOS [#macos]

1. Open the `.dmg` and drag **Triagic** into Applications.
2. Launch it from Applications.

If macOS refuses to open the app because it "cannot verify the developer", the build
you downloaded is unsigned. Right-click the app and choose **Open**, then confirm,
or clear the quarantine flag:

```bash
xattr -dr com.apple.quarantine /Applications/Triagic.app
```

Only do this for a build you downloaded from the official download page and whose
checksum you verified.

## Windows [#windows]

Run the installer. If SmartScreen warns about an unrecognized publisher, choose
**More info → Run anyway**. Again, only for a verified download.

## Linux [#linux]

**AppImage**: make it executable and run it:

```bash
chmod +x Triagic-*.AppImage && ./Triagic-*.AppImage
```

**Debian / Ubuntu**:

```bash
sudo apt install ./triagic_*_amd64.deb
```

## First launch [#first-launch]

The app starts a local server bound to loopback and opens the UI against it. Nothing
listens on a public interface.

On first run it creates its data directory and generates a local encryption key. That
key is what protects every credential stored on this machine.

| Platform | Data directory                          |
| -------- | --------------------------------------- |
| macOS    | `~/Library/Application Support/Triagic` |
| Windows  | `%APPDATA%\Triagic`                     |
| Linux    | `~/.config/Triagic`                     |

> **Warning:** Back up the encryption key, or accept re-entering credentials
>
> Losing the data directory means losing the key, and stored secrets can no longer be
> decrypted. Affected integrations will report `cannot decrypt stored secrets` and
> have to be re-entered. If your organization uses shared integrations, this matters
> less: those re-sync from the portal on the next pull.

## Runtimes are bundled [#runtimes-are-bundled]

There is nothing to install first. Most integrations run an MCP server as a local
process, and those servers need either Node or Python's `uv` to launch. So the
installer ships a portable Node LTS and a portable `uv` inside the app itself.

They live in the app's own resources, go at the front of the PATH the app hands its
child processes, and are never installed into your system or onto your PATH. The
front matters: every integration runs on the same Node and `uv` on every machine, so
a Node or `uv` you installed yourself, or a version manager's shim, is neither needed
nor used by them. Anything else an integration needs, Docker or a kubeconfig's
credential plugin, still comes from your own PATH.

The first time an integration starts on a machine its server package is downloaded
from npm or PyPI. On a slow link that can take a few minutes, during which the
integration shows as *starting*; after that it starts in seconds.

> **Note:** Docker is the exception
>
> The three Docker-based integrations (GitHub, Grafana, Terraform) still need Docker
> Desktop or Engine installed. Docker needs elevation, a licence acceptance and often a
> reboot; it cannot be bundled honestly. Everything else is covered.

An install that predates the bundling, or one whose bundled copies are somehow
unusable, downloads the same two runtimes into its data directory on first need
instead. Either way the outcome is the same and neither asks you for anything.

## Proxies and private CAs [#proxies-and-private-cas]

On a network that forces outbound traffic through a proxy, or that terminates TLS with
an internal certificate authority, configure it in one place and everything follows:
its own calls to the portal and to AI providers, the npm and PyPI downloads that fetch
integration servers, and every integration process it starts. No restart, though the
app rechecks the settings at most every 30 seconds, so give a change that long.

Drop a `network.json` into the data directory listed above. This is the file to push
from MDM, because a Mac app launched from the Dock inherits no shell environment at
all, so environment variables alone would only ever work on Windows and in a terminal.

```json
{
  "proxyUrl": "http://proxy.corp:8080",
  "noProxy": "localhost,127.0.0.1,.corp",
  "caBundlePath": "/etc/ssl/certs/corp-root.pem",
  "npmRegistry": "https://artifactory.corp/api/npm/npm/",
  "pypiIndex": "https://artifactory.corp/api/pypi/pypi/simple"
}
```

| Key            | What it does                                                                                  |
| -------------- | --------------------------------------------------------------------------------------------- |
| `proxyUrl`     | `http://` or `https://` proxy for every outbound request. Basic auth in the URL is supported. |
| `noProxy`      | Comma-separated hosts and suffixes to reach directly.                                         |
| `caBundlePath` | PEM bundle trusted in addition to the system store, by the app and by every integration.      |
| `npmRegistry`  | Registry the Node-based integration servers are fetched from.                                 |
| `pypiIndex`    | Index the Python-based integration servers are fetched from.                                  |

Every key is optional. Without the file, the app reads these from its environment
instead: `HTTPS_PROXY` or `HTTP_PROXY` (the lowercase `https_proxy` and `http_proxy`
work too), `NO_PROXY` or `no_proxy`, `NODE_EXTRA_CA_CERTS`, `NPM_CONFIG_REGISTRY`, and
`UV_INDEX_URL`, `UV_DEFAULT_INDEX` or `PIP_INDEX_URL`.

The file wins as a whole: as soon as it sets one of the five keys above, the
environment is not consulted for any of them. A file that is missing, unreadable, not
valid JSON, empty, or that sets none of those five keys is ignored, and the
environment is read as if it were not there.

> **Note:** A change to network.json needs no restart
>
> Adding, editing or removing `network.json` on a running install is picked up on its
> own. The ticket and AI connectors use it on their next call; the app's own calls to the
> portal and its runtime downloads use it once the settings are rechecked, which happens
> at most every 30 seconds. An integration process that is already running keeps the
> environment it was started with, so restart that integration to move it across too.

> **Note:** Check what is configured
>
> The desktop console's status response has a `network` block naming the source
> (`file`, `env` or `none`), the settings in effect with credentials stripped, and a
> `problem` line when something is set but unusable, such as a `caBundlePath` that
> cannot be read. A CA path that points at nothing is otherwise silent: TLS just keeps
> failing with an error that names no file. The block reports the file the moment it
> lands, so read it as "this is what is configured": the outbound calls catch up within
> the recheck window above. It is shown to organization admins only.

## Paths in integration settings [#paths-in-integration-settings]

Integration fields that ask for a file accept three forms:

* An absolute path on **this** machine.
* A path written against your home directory: `~/certs/ca.pem`, `${HOME}/certs/ca.pem`
  or `%USERPROFILE%\certs\ca.pem`. All three land in the right place on any platform,
  which is what makes one shared organization integration work on Mac and Windows
  laptops at once.
* The file contents pasted in. The app writes them to a private file readable only by
  you when it starts the integration.

That covers the file fields on the integrations themselves: CA bundles, client
certificates and keys, kubeconfigs, service account keys. Their help text ends with
"Path on each machine running Triagic, or paste the file itself", which is how you can
tell one at a glance.

Four **CA certificate path** fields are path-only. They read whatever you type as a
filename, so a pasted certificate is taken as the name of a file that does not exist:

* On an AI provider (OpenAI, Anthropic, Ollama).
* On the Jira ticket source.
* On an issue tracker connection.
* On GitHub or GitLab code review.

Give those an absolute path to a bundle that is already on the machine running
Triagic. To trust one internal CA everywhere at once, set `caBundlePath` in
`network.json` instead and leave these blank.

The file has to exist on the machine running Triagic, not on the machine you typed the
path from. A path that is missing is reported before the integration starts, naming the
field and the resolved location.

## Updates [#updates]

The app checks for updates on its own and installs them on restart. There is nothing
to do.

## Next [#next]

[Sign in and connect to your organization →](/docs/desktop/sign-in)
