Compare commits

...
Author SHA1 Message Date
Vlad ShulczandGitHub 08c463542f docs: update the postgres bind mount in the compose example (#1761)
Postgres 18 moved its declared volume to /var/lib/postgresql, so the
example container fails to start with the old path.

Signed-off-by: vshulcz <vshulcz@gmail.com>
2026-08-17 21:22:58 -04:00
0fd2520088 docs(alerting): Revise HomeAssistant alerts and add Home Assistant integration info (#1723)
* Revise HomeAssistant alerts and add integration info

Updated HomeAssistant alerts section to indicate legacy status and added Home Assistant integration details.

* Fix typo

* Tweak wording from Legacy to Native

* Tweak on feedback

---------

Co-authored-by: TwiN <twin@linux.com>
2026-08-14 22:09:04 -04:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
7a73be52c5 chore(deps): bump docker/login-action from 4.5.2 to 4.6.0 (#1745)
Bumps [docker/login-action](https://github.com/docker/login-action) from 4.5.2 to 4.6.0.
- [Release notes](https://github.com/docker/login-action/releases)
- [Commits](https://github.com/docker/login-action/compare/v4.5.2...v4.6.0)

---
updated-dependencies:
- dependency-name: docker/login-action
  dependency-version: 4.6.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-08 22:51:58 -04:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
73381e4179 chore(deps): bump docker/login-action from 4 to 4.5.2 (#1739)
Bumps [docker/login-action](https://github.com/docker/login-action) from 4 to 4.5.2.
- [Release notes](https://github.com/docker/login-action/releases)
- [Commits](https://github.com/docker/login-action/compare/v4...v4.5.2)

---
updated-dependencies:
- dependency-name: docker/login-action
  dependency-version: 4.5.2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-01 21:23:33 -04:00
b7100620c3 feat(client): Allow configuration of client cookie storage (#1716)
* Allow configuration of cookie storage

* Update client/config.go

---------

Co-authored-by: TwiN <twin@linux.com>
2026-07-25 17:12:10 -04:00
ae7ca199aa fix(jsonpath): prevent panic on negative array index (#1703)
A jsonpath condition with a negative array index (e.g. [BODY].data[-1]
or [BODY][-1]) reached array[arrayIndex] with a negative index because
the len(array) > arrayIndex bounds checks are satisfied by any negative
value, causing a 'runtime error: index out of range [-1]' panic. Since
condition evaluation runs in the watchdog with no recover(), this would
crash the process.

Guard against negative indices alongside the existing strconv.Atoi error
check so an out-of-range negative index returns nil (treated as an
invalid path) instead of panicking. Adds tests for negative indices on
keyed arrays, root arrays, and negative-index-followed-by-key.

Made by an AI agent (Claude Code, model Claude Opus 4.8).

Co-authored-by: TwiN <twin@linux.com>
2026-07-18 22:45:34 -04:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
53392dd472 chore(deps): bump actions/checkout from 5 to 7 (#1694)
Bumps [actions/checkout](https://github.com/actions/checkout) from 5 to 7.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v5...v7)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-09 22:44:11 -04:00
14 changed files with 139 additions and 16 deletions
@@ -2,7 +2,9 @@ services:
postgres:
image: postgres
volumes:
- ./data/db:/var/lib/postgresql/data
# PostgreSQL 18 and above declare the volume at /var/lib/postgresql.
# For 17 and earlier, mount ./data/db at /var/lib/postgresql/data instead.
- ./data/db:/var/lib/postgresql
ports:
- "5432:5432"
environment:
+1 -1
View File
@@ -25,6 +25,6 @@ jobs:
go-version: 1.26.3
repository: "${{ github.event.inputs.repository || 'TwiN/gatus' }}"
ref: "${{ github.event.inputs.ref || 'master' }}"
- uses: actions/checkout@v5
- uses: actions/checkout@v7
- name: Benchmark
run: go test -bench=. ./storage/store
+2 -2
View File
@@ -19,7 +19,7 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v7
- name: Set up QEMU
uses: docker/setup-qemu-action@v4
- name: Set up Docker Buildx
@@ -27,7 +27,7 @@ jobs:
- name: Get image repository
run: echo GHCR_IMAGE_REPOSITORY=$(echo ghcr.io/${{ github.actor }}/${{ github.event.repository.name }} | tr '[:upper:]' '[:lower:]') >> $GITHUB_ENV
- name: Login to GitHub Container Registry
uses: docker/login-action@v4
uses: docker/login-action@v4.6.0
with:
registry: ghcr.io
username: ${{ github.actor }}
+2 -2
View File
@@ -5,7 +5,7 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v7
- name: Set up QEMU
uses: docker/setup-qemu-action@v4
- name: Set up Docker Buildx
@@ -13,7 +13,7 @@ jobs:
- name: Get image repository
run: echo IMAGE_REPOSITORY=$(echo ${{ secrets.DOCKER_USERNAME }}/${{ github.event.repository.name }} | tr '[:upper:]' '[:lower:]') >> $GITHUB_ENV
- name: Login to Docker Registry
uses: docker/login-action@v4
uses: docker/login-action@v4.6.0
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
+3 -3
View File
@@ -13,7 +13,7 @@ jobs:
if: ${{ (github.event.workflow_run.conclusion == 'success') && (github.event.workflow_run.head_repository.full_name == github.repository) }}
timeout-minutes: 240
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v7
- name: Set up QEMU
uses: docker/setup-qemu-action@v4
- name: Set up Docker Buildx
@@ -23,12 +23,12 @@ jobs:
echo DOCKER_IMAGE_REPOSITORY=$(echo ${{ secrets.DOCKER_USERNAME }}/${{ github.event.repository.name }} | tr '[:upper:]' '[:lower:]') >> $GITHUB_ENV
echo GHCR_IMAGE_REPOSITORY=$(echo ghcr.io/${{ github.actor }}/${{ github.event.repository.name }} | tr '[:upper:]' '[:lower:]') >> $GITHUB_ENV
- name: Login to Docker Registry
uses: docker/login-action@v4
uses: docker/login-action@v4.6.0
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Login to GitHub Container Registry
uses: docker/login-action@v4
uses: docker/login-action@v4.6.0
with:
registry: ghcr.io
username: ${{ github.actor }}
+3 -3
View File
@@ -8,7 +8,7 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 240
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v7
- name: Set up QEMU
uses: docker/setup-qemu-action@v4
- name: Set up Docker Buildx
@@ -20,12 +20,12 @@ jobs:
- name: Get the release
run: echo RELEASE=${GITHUB_REF/refs\/tags\//} >> $GITHUB_ENV
- name: Login to Docker Registry
uses: docker/login-action@v4
uses: docker/login-action@v4.6.0
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Login to GitHub Container Registry
uses: docker/login-action@v4
uses: docker/login-action@v4.6.0
with:
registry: ghcr.io
username: ${{ github.actor }}
@@ -46,7 +46,7 @@ jobs:
core.setOutput('ref', pr.data.head.ref);
core.setOutput('repo', pr.data.head.repo.full_name);
- name: Checkout PR branch
uses: actions/checkout@v6
uses: actions/checkout@v7
with:
repository: ${{ steps.pr.outputs.repo }}
ref: ${{ steps.pr.outputs.ref }}
+1 -1
View File
@@ -13,6 +13,6 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v7
- run: make frontend-install
- run: make frontend-build
+1 -1
View File
@@ -19,7 +19,7 @@ jobs:
- uses: actions/setup-go@v6
with:
go-version: 1.26.3
- uses: actions/checkout@v5
- uses: actions/checkout@v7
- name: Build binary to make sure it works
run: go build
- name: Test
+62
View File
@@ -654,6 +654,7 @@ the client used to send the request.
| `client.tls.renegotiation` | Type of renegotiation support to provide. (`never`, `freely`, `once`). | `"never"` |
| `client.network` | The network to use for ICMP endpoint client (`ip`, `ip4` or `ip6`). | `"ip"` |
| `client.tunnel` | Name of the SSH tunnel to use for this endpoint. See [Tunneling](#tunneling). | `""` |
| `client.store-cookies` | Whether to store cookies between requests. | `false` |
> 📝 Some of these parameters are ignored based on the type of endpoint. For instance, there's no certificate involved
@@ -666,6 +667,7 @@ client:
insecure: false
ignore-redirect: false
timeout: 10s
store-cookies: false
```
Note that this configuration is only available under `endpoints[]`, `alerting.mattermost` and `alerting.custom`.
@@ -1302,6 +1304,10 @@ Here's an example of what the notifications look like:
#### Configuring HomeAssistant alerts
> [!NOTE]
> It is recommended to use the native Home Assistant integration instead (see below).
| Parameter | Description | Default Value |
|:-------------------------------------------|:---------------------------------------------------------------------------------------|:--------------|
| `alerting.homeassistant.url` | HomeAssistant instance URL | Required `""` |
@@ -1376,6 +1382,61 @@ To get your HomeAssistant long-lived access token:
5. Give it a name (e.g., "Gatus")
6. Copy the token - you'll only see it once!
##### Native Home Assistant Integration
Gatus can be integrated into [Home Assistant](https://www.home-assistant.io/) to monitor the status of your endpoints directly from your home automation dashboard.
[![Open your Home Assistant instance and start setting up a new integration.](https://my.home-assistant.io/badges/config_flow_start.svg)](https://my.home-assistant.io/redirect/config_flow_start/?domain=gatus)
<details>
<summary>Configuration & Automation Examples</summary>
###### Configuration
To add the integration, click the button above or:
1. In Home Assistant, navigate to **Settings** > **Devices & Services**.
2. Click **Add Integration** in the bottom-right corner.
3. Search for **Gatus** and select it.
4. Enter the base URL of your Gatus instance (e.g., `http://192.168.1.50:8080`).
Home Assistant will create a binary sensor for each endpoint configured in Gatus. Because these sensors use the `connectivity` device class, they will show as `Connected` (Up) when the endpoint is healthy, and `Disconnected` (Down) when the endpoint is unhealthy.
###### Automation Example
You can easily set up a UI automation in Home Assistant to receive mobile notifications if any endpoint goes down:
1. Navigate to **Settings** > **Automations & Scenes** > **Create Automation** > **Create new automation**.
2. Under **Triggers**, click **Add Trigger** and select **State**.
3. In the **Entity** field, select the Gatus binary sensor(s) you want to monitor.
4. Set the **To** field to `off` (since a state of `off` represents a disconnected/down endpoint).
5. Under **Actions**, click **Add Action** and select **Perform action**.
6. Select your notification service (e.g., `Send a notification via mobile_app_<your_device_name>`).
7. Set the **Message** field. You can use template values to dynamically include the endpoint name:
```yaml
message: "The endpoint {{ trigger.to_state.name }} is down!"
```
For advanced users, here is the YAML representation of the automation:
```yaml
alias: "Notify when Gatus endpoint goes down"
description: "Sends a mobile notification if a Gatus endpoint goes down"
trigger:
- platform: state
entity_id:
- binary_sensor.website
- binary_sensor.make_sure_header_is_rendered
to: "off"
action:
- action: notify.mobile_app_your_device_name
data:
title: "Gatus Alert"
message: "The endpoint {{ trigger.to_state.name }} is down!"
```
</details>
#### Configuring IFTTT alerts
@@ -2906,6 +2967,7 @@ To get more details, please check [chart's configuration](https://github.com/Twi
Gatus can be deployed on Kubernetes using Terraform by using the following module: [terraform-kubernetes-gatus](https://github.com/TwiN/terraform-kubernetes-gatus).
## Running the tests
```console
go test -v ./...
+12
View File
@@ -6,6 +6,7 @@ import (
"errors"
"net"
"net/http"
"net/http/cookiejar"
"net/url"
"regexp"
"strconv"
@@ -34,6 +35,7 @@ var (
IgnoreRedirect: false,
Timeout: defaultTimeout,
Network: "ip",
StoreCookies: false,
}
)
@@ -82,6 +84,9 @@ type Config struct {
// ResolvedTunnel is the resolved SSH tunnel for this specific Config
ResolvedTunnel *sshtunnel.SSHTunnel `yaml:"-"`
// StoreCookies determines whether cookies are stored and included across requests.
StoreCookies bool `yaml:"store-cookies,omitempty"`
httpClient *http.Client
}
@@ -280,6 +285,13 @@ func (c *Config) getHTTPClient() *http.Client {
}
}
}
if c.StoreCookies {
jar, err := cookiejar.New(nil)
if err != nil {
logr.Fatalf("[client.getHTTPClient] Failed to initialize cookie jar: %v", err)
}
c.httpClient.Jar = jar
}
}
return c.httpClient
}
+15
View File
@@ -17,6 +17,9 @@ func TestConfig_getHTTPClient(t *testing.T) {
if insecureClient.Timeout != defaultTimeout {
t.Error("expected Config.Timeout to default the HTTP client to a timeout of 10s")
}
if insecureClient.Jar != nil {
t.Error("expected Config.StoreCookies to default the HTTP client to not store cookies")
}
request, _ := http.NewRequest("GET", "", nil)
if err := insecureClient.CheckRedirect(request, nil); err != nil {
t.Error("expected Config.IgnoreRedirect set to false to cause the HTTP client's CheckRedirect to return nil")
@@ -31,12 +34,24 @@ func TestConfig_getHTTPClient(t *testing.T) {
if secureClient.Timeout != 5*time.Second {
t.Error("expected Config.Timeout to cause the HTTP client to have a timeout of 5s")
}
if insecureClient.Jar != nil {
t.Error("expected Config.StoreCookies to default the HTTP client to not store cookies")
}
request, _ = http.NewRequest("GET", "", nil)
if err := secureClient.CheckRedirect(request, nil); err != http.ErrUseLastResponse {
t.Error("expected Config.IgnoreRedirect set to true to cause the HTTP client's CheckRedirect to return http.ErrUseLastResponse")
}
}
func TestConfig_getHTTPClient_withCookieJar(t *testing.T) {
config := &Config{StoreCookies: true}
config.ValidateAndSetDefaults()
client := config.getHTTPClient()
if client.Jar == nil {
t.Error("expected Config.StoreCookies to cause the HTTP client to have a CookieJar")
}
}
func TestConfig_ValidateAndSetDefaults_withCustomDNSResolver(t *testing.T) {
type args struct {
dnsResolver string
+1 -1
View File
@@ -87,7 +87,7 @@ func extractValue(currentKey string, value interface{}) interface{} {
}
}
arrayIndex, err := strconv.Atoi(index)
if err != nil {
if err != nil || arrayIndex < 0 {
return nil
}
currentKeyWithoutIndex := currentKey[:startOfBracket]
+32
View File
@@ -174,6 +174,38 @@ func TestEval(t *testing.T) {
ExpectedOutputLength: 18,
ExpectedError: false,
},
{
Name: "negative-index-on-keyed-array",
Path: "data[-1]",
Data: `{"data": [1, 2, 3]}`,
ExpectedOutput: "",
ExpectedOutputLength: 0,
ExpectedError: true,
},
{
Name: "negative-index-on-root-array",
Path: "[-1]",
Data: `[1, 2, 3]`,
ExpectedOutput: "",
ExpectedOutputLength: 0,
ExpectedError: true,
},
{
Name: "negative-index-followed-by-key",
Path: "data[-1].name",
Data: `{"data": [{"name": "value"}]}`,
ExpectedOutput: "",
ExpectedOutputLength: 0,
ExpectedError: true,
},
{
Name: "negative-index-nested-array",
Path: "data[0][-1]",
Data: `{"data": [[1, 2, 3]]}`,
ExpectedOutput: "",
ExpectedOutputLength: 0,
ExpectedError: true,
},
}
for _, scenario := range scenarios {
t.Run(scenario.Name, func(t *testing.T) {