mirror of
https://github.com/TwiN/gatus.git
synced 2026-08-22 15:16:04 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
08c463542f | ||
|
|
0fd2520088 | ||
|
|
7a73be52c5 | ||
|
|
73381e4179 | ||
|
|
b7100620c3 | ||
|
|
ae7ca199aa | ||
|
|
53392dd472 |
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 }}
|
||||
|
||||
@@ -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 }}
|
||||
|
||||
@@ -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 }}
|
||||
|
||||
@@ -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 }}
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
[](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 ./...
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user