upright 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE.md +10 -0
- data/README.md +455 -0
- data/Rakefile +6 -0
- data/app/assets/stylesheets/upright/_global.css +104 -0
- data/app/assets/stylesheets/upright/artifact.css +148 -0
- data/app/assets/stylesheets/upright/base.css +68 -0
- data/app/assets/stylesheets/upright/buttons.css +21 -0
- data/app/assets/stylesheets/upright/dashboard.css +287 -0
- data/app/assets/stylesheets/upright/forms.css +104 -0
- data/app/assets/stylesheets/upright/header.css +124 -0
- data/app/assets/stylesheets/upright/layout.css +100 -0
- data/app/assets/stylesheets/upright/map.css +25 -0
- data/app/assets/stylesheets/upright/pagination.css +45 -0
- data/app/assets/stylesheets/upright/probes.css +72 -0
- data/app/assets/stylesheets/upright/reset.css +26 -0
- data/app/assets/stylesheets/upright/tables.css +63 -0
- data/app/assets/stylesheets/upright/typography.css +27 -0
- data/app/assets/stylesheets/upright/uptime-bars.css +154 -0
- data/app/controllers/concerns/upright/authentication.rb +21 -0
- data/app/controllers/concerns/upright/subdomain_scoping.rb +18 -0
- data/app/controllers/upright/alertmanager_proxy_controller.rb +21 -0
- data/app/controllers/upright/application_controller.rb +12 -0
- data/app/controllers/upright/artifacts_controller.rb +5 -0
- data/app/controllers/upright/dashboards/uptimes_controller.rb +6 -0
- data/app/controllers/upright/jobs_controller.rb +4 -0
- data/app/controllers/upright/probe_results_controller.rb +17 -0
- data/app/controllers/upright/prometheus_proxy_controller.rb +62 -0
- data/app/controllers/upright/sessions_controller.rb +29 -0
- data/app/controllers/upright/sites_controller.rb +5 -0
- data/app/helpers/upright/application_helper.rb +11 -0
- data/app/helpers/upright/dashboards_helper.rb +31 -0
- data/app/helpers/upright/probe_results_helper.rb +49 -0
- data/app/javascript/upright/application.js +2 -0
- data/app/javascript/upright/controllers/application.js +5 -0
- data/app/javascript/upright/controllers/form_controller.js +7 -0
- data/app/javascript/upright/controllers/index.js +4 -0
- data/app/javascript/upright/controllers/popover_controller.js +15 -0
- data/app/javascript/upright/controllers/probe_results_chart_controller.js +79 -0
- data/app/javascript/upright/controllers/results_table_controller.js +16 -0
- data/app/javascript/upright/controllers/sites_map_controller.js +33 -0
- data/app/jobs/upright/application_job.rb +2 -0
- data/app/jobs/upright/probe_check_job.rb +42 -0
- data/app/models/concerns/upright/exception_recording.rb +38 -0
- data/app/models/concerns/upright/playwright/form_authentication.rb +27 -0
- data/app/models/concerns/upright/playwright/helpers.rb +7 -0
- data/app/models/concerns/upright/playwright/lifecycle.rb +44 -0
- data/app/models/concerns/upright/playwright/logging.rb +87 -0
- data/app/models/concerns/upright/playwright/otel_tracing.rb +137 -0
- data/app/models/concerns/upright/playwright/video_recording.rb +60 -0
- data/app/models/concerns/upright/probe_yaml_source.rb +10 -0
- data/app/models/concerns/upright/probeable.rb +125 -0
- data/app/models/concerns/upright/staggerable.rb +22 -0
- data/app/models/concerns/upright/traceroute/otel_tracing.rb +108 -0
- data/app/models/upright/application_record.rb +3 -0
- data/app/models/upright/artifact.rb +61 -0
- data/app/models/upright/current.rb +9 -0
- data/app/models/upright/http/request.rb +59 -0
- data/app/models/upright/http/response.rb +55 -0
- data/app/models/upright/playwright/authenticator/base.rb +128 -0
- data/app/models/upright/playwright/storage_state.rb +31 -0
- data/app/models/upright/probe_result.rb +31 -0
- data/app/models/upright/probes/http_probe.rb +102 -0
- data/app/models/upright/probes/playwright/base.rb +48 -0
- data/app/models/upright/probes/smtp_probe.rb +48 -0
- data/app/models/upright/probes/traceroute_probe.rb +32 -0
- data/app/models/upright/probes/uptime/summary.rb +36 -0
- data/app/models/upright/probes/uptime.rb +36 -0
- data/app/models/upright/traceroute/hop.rb +49 -0
- data/app/models/upright/traceroute/ip_metadata_lookup.rb +107 -0
- data/app/models/upright/traceroute/mtr_parser.rb +47 -0
- data/app/models/upright/traceroute/result.rb +57 -0
- data/app/models/upright/user.rb +14 -0
- data/app/views/layouts/upright/_header.html.erb +23 -0
- data/app/views/layouts/upright/application.html.erb +25 -0
- data/app/views/upright/active_storage/attachments/_attachment.html.erb +21 -0
- data/app/views/upright/alertmanager_proxy/show.html.erb +1 -0
- data/app/views/upright/artifacts/show.html.erb +9 -0
- data/app/views/upright/dashboards/_uptime_bars.html.erb +17 -0
- data/app/views/upright/dashboards/_uptime_probe_row.html.erb +22 -0
- data/app/views/upright/dashboards/uptimes/show.html.erb +17 -0
- data/app/views/upright/jobs/show.html.erb +1 -0
- data/app/views/upright/probe_results/_pagination.html.erb +19 -0
- data/app/views/upright/probe_results/index.html.erb +72 -0
- data/app/views/upright/prometheus_proxy/show.html.erb +1 -0
- data/app/views/upright/sessions/new.html.erb +6 -0
- data/app/views/upright/sites/index.html.erb +22 -0
- data/config/brakeman.ignore +39 -0
- data/config/ci.rb +7 -0
- data/config/importmap.rb +18 -0
- data/config/routes.rb +41 -0
- data/db/migrate/20250114000001_create_upright_probe_results.rb +19 -0
- data/lib/generators/upright/install/install_generator.rb +83 -0
- data/lib/generators/upright/install/templates/alertmanager.yml +14 -0
- data/lib/generators/upright/install/templates/deploy.yml +118 -0
- data/lib/generators/upright/install/templates/development_alertmanager.yml +11 -0
- data/lib/generators/upright/install/templates/development_prometheus.yml +12 -0
- data/lib/generators/upright/install/templates/docker-compose.yml +38 -0
- data/lib/generators/upright/install/templates/http_probes.yml +14 -0
- data/lib/generators/upright/install/templates/omniauth.rb +8 -0
- data/lib/generators/upright/install/templates/otel_collector.yml +24 -0
- data/lib/generators/upright/install/templates/prometheus.yml +10 -0
- data/lib/generators/upright/install/templates/puma.rb +40 -0
- data/lib/generators/upright/install/templates/sites.yml +26 -0
- data/lib/generators/upright/install/templates/smtp_probes.yml +9 -0
- data/lib/generators/upright/install/templates/upright.rb +21 -0
- data/lib/generators/upright/install/templates/upright.rules.yml +256 -0
- data/lib/generators/upright/playwright_probe/playwright_probe_generator.rb +30 -0
- data/lib/generators/upright/playwright_probe/templates/authenticator.rb.tt +14 -0
- data/lib/generators/upright/playwright_probe/templates/probe.rb.tt +14 -0
- data/lib/omniauth/strategies/static_credentials.rb +57 -0
- data/lib/tasks/upright_tasks.rake +4 -0
- data/lib/upright/configuration.rb +106 -0
- data/lib/upright/engine.rb +157 -0
- data/lib/upright/metrics.rb +62 -0
- data/lib/upright/playwright/collect_performance_metrics.js +36 -0
- data/lib/upright/site.rb +49 -0
- data/lib/upright/tracing.rb +49 -0
- data/lib/upright/version.rb +3 -0
- data/lib/upright.rb +68 -0
- metadata +513 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 68ab49fec5d7d1e512a1f639de64f0ff2d856d6b753cf9ff8593fb8d2011f4dd
|
|
4
|
+
data.tar.gz: 4841c873eadfa45a515f15bd6c1a12648cc923b72d93bb6efec1e62f3601e94c
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: e69af1adfbf82119f843ade06fe6c06b5ee3bc7c595fe0e338351d7ad13383f0e10223391bbde959c1437649fe176d3807034ef325c7ab1178cf24f4f1f58de7
|
|
7
|
+
data.tar.gz: '011039880bc90c861b67a8c315e9ad1b3e25379db5dcf0bce931bb47ce5625d98ba967659b879773cd8c773c2c5728f08d9ea80c09b64377cf52e0623292e63c'
|
data/LICENSE.md
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# O'Saasy License Agreement
|
|
2
|
+
|
|
3
|
+
Copyright © 2026, 37signals LLC.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
6
|
+
|
|
7
|
+
1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
8
|
+
2. No licensee or downstream recipient may use the Software (including any modified or derivative versions) to directly compete with the original Licensor by offering it to third parties as a hosted, managed, or Software-as-a-Service (SaaS) product or cloud service where the primary value of the service is the functionality of the Software itself.
|
|
9
|
+
|
|
10
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,455 @@
|
|
|
1
|
+
# Upright
|
|
2
|
+
|
|
3
|
+
Upright is a self-hosted synthetic monitoring system. It provides a framework for running health check probes from multiple geographic sites and reporting metrics via Prometheus. Alerts can then be configured with AlertManager.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Playwright Probes** - Browser-based probes for user flows with video recording and logs
|
|
8
|
+
- **HTTP Probes** - Simple HTTP health checks with configurable expected status codes
|
|
9
|
+
- **SMTP Probes** - EHLO handshake verification for mail servers
|
|
10
|
+
- **Traceroute Probes** - Network path analysis with hop-by-hop latency tracking
|
|
11
|
+
- **Multi-Site Support** - Run probes from multiple geographic locations with staggered scheduling
|
|
12
|
+
- **Observability** - OTLP compatible, Prometheus metrics, OpenTelemetry tracing, and AlertManager support
|
|
13
|
+
- **Configurable Authentication** - OmniAuth integration with support for any OIDC provider
|
|
14
|
+
|
|
15
|
+
### Not Included
|
|
16
|
+
|
|
17
|
+
- **Dashboards** - Instead, Grafana is suggested for monitoring the Prometheus metrics generated here
|
|
18
|
+
- **Notifications** - Instead, Alertmanager is included for alerting and notifications
|
|
19
|
+
- **Hosting** - Instead, you can use a VPS from DigitalOcean, Hetzner, etc.
|
|
20
|
+
|
|
21
|
+
### Components
|
|
22
|
+
|
|
23
|
+
- Rails engine
|
|
24
|
+
- SQLite
|
|
25
|
+
- Solid Queue for background and recurring jobs
|
|
26
|
+
- Mission Control - Jobs to monitor Solid Queue and manually enqueue probes
|
|
27
|
+
- Kamal for deployments
|
|
28
|
+
- Prometheus metrics for uptime queries and alerting
|
|
29
|
+
- AlertManager for notifications
|
|
30
|
+
- Open Telemetry Collector - logs, metrics and traces can be shipped to any OTLP compatible endpoint
|
|
31
|
+
|
|
32
|
+
## Installation
|
|
33
|
+
|
|
34
|
+
Upright is designed to be run in it's own Rails app and deployed with Kamal.
|
|
35
|
+
|
|
36
|
+
### Quick Start (New Project)
|
|
37
|
+
|
|
38
|
+
Create a new Rails application and install Upright:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
rails new my-upright --database=sqlite3 --skip-test
|
|
42
|
+
cd my-upright
|
|
43
|
+
bundle add upright --github=basecamp/upright
|
|
44
|
+
bin/rails generate upright:install
|
|
45
|
+
bin/rails db:setup
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Start the server:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
bin/dev
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Visit http://app.my-upright.localhost:3000 to see your Upright instance.
|
|
55
|
+
|
|
56
|
+
> **Note**: Upright uses subdomain-based routing. The `app` subdomain is the admin interface, while site-specific subdomains (e.g., `nyc`, `lon`) show probe results for each location. The `.localhost` TLD resolves to 127.0.0.1 on most systems.
|
|
57
|
+
|
|
58
|
+
### What the Generator Creates
|
|
59
|
+
|
|
60
|
+
The `upright:install` generator creates:
|
|
61
|
+
|
|
62
|
+
- `config/initializers/upright.rb` - Engine configuration
|
|
63
|
+
- `config/sites.yml` - Site definitions for each VPS you host Upright on
|
|
64
|
+
- `config/prometheus/prometheus.yml` - Prometheus configuration
|
|
65
|
+
- `config/alertmanager/alertmanager.yml` - AlertManager configuration
|
|
66
|
+
- `config/otel_collector.yml` - OpenTelemetry Collector configuration
|
|
67
|
+
- `probes/` - Directory for all HTTP, SMTP, Traceroute YAML config as well as Playwright probe classes
|
|
68
|
+
|
|
69
|
+
It also mounts the engine at `/` in your routes.
|
|
70
|
+
|
|
71
|
+
## Configuration
|
|
72
|
+
|
|
73
|
+
### Basic Setup
|
|
74
|
+
|
|
75
|
+
See `config/initializers/upright.rb`
|
|
76
|
+
|
|
77
|
+
### Hostname Configuration
|
|
78
|
+
|
|
79
|
+
Upright uses subdomain-based routing. Configure your production hostname:
|
|
80
|
+
|
|
81
|
+
```ruby
|
|
82
|
+
# config/initializers/upright.rb
|
|
83
|
+
Upright.configure do |config|
|
|
84
|
+
config.hostname = "upright.com"
|
|
85
|
+
end
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
For local development, the hostname defaults to `{service_name}.localhost` (e.g., `upright.localhost`).
|
|
89
|
+
|
|
90
|
+
### Site Configuration
|
|
91
|
+
|
|
92
|
+
Define your monitoring locations in `config/sites.yml`:
|
|
93
|
+
|
|
94
|
+
```yaml
|
|
95
|
+
shared:
|
|
96
|
+
sites:
|
|
97
|
+
- code: nyc
|
|
98
|
+
city: New York City
|
|
99
|
+
country: US
|
|
100
|
+
geohash: dr5reg
|
|
101
|
+
provider: digitalocean
|
|
102
|
+
|
|
103
|
+
- code: ams
|
|
104
|
+
city: Amsterdam
|
|
105
|
+
country: NL
|
|
106
|
+
geohash: u17982
|
|
107
|
+
provider: digitalocean
|
|
108
|
+
|
|
109
|
+
- code: sfo
|
|
110
|
+
city: San Francisco
|
|
111
|
+
country: US
|
|
112
|
+
geohash: 9q8yy
|
|
113
|
+
provider: hetzner
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Each site node identifies itself via the `SITE_SUBDOMAIN` environment variable, configured in your Kamal deploy.yml.
|
|
117
|
+
|
|
118
|
+
### Authentication
|
|
119
|
+
|
|
120
|
+
#### Static Credentials
|
|
121
|
+
|
|
122
|
+
Upright uses static credentials by default with username `admin` and password `upright`.
|
|
123
|
+
|
|
124
|
+
> [!WARNING]
|
|
125
|
+
> Change the default password before deploying to production by setting the `ADMIN_PASSWORD` environment variable.
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
#### OpenID Connect
|
|
129
|
+
|
|
130
|
+
For production environments, Upright supports OpenID Connect (Logto, Keycloak, Duo, Okta, etc.):
|
|
131
|
+
|
|
132
|
+
```ruby
|
|
133
|
+
# config/initializers/upright.rb
|
|
134
|
+
Upright.configure do |config|
|
|
135
|
+
config.auth_provider = :openid_connect
|
|
136
|
+
config.auth_options = {
|
|
137
|
+
issuer: "https://your-tenant.logto.app/oidc",
|
|
138
|
+
client_id: ENV["OIDC_CLIENT_ID"],
|
|
139
|
+
client_secret: ENV["OIDC_CLIENT_SECRET"]
|
|
140
|
+
}
|
|
141
|
+
end
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## Defining Probes
|
|
145
|
+
|
|
146
|
+
### HTTP Probes
|
|
147
|
+
|
|
148
|
+
Add probes to `probes/http_probes.yml`:
|
|
149
|
+
|
|
150
|
+
```yaml
|
|
151
|
+
- name: Main Website
|
|
152
|
+
url: https://example.com
|
|
153
|
+
expected_status: 200
|
|
154
|
+
|
|
155
|
+
- name: API Health
|
|
156
|
+
url: https://api.example.com/health
|
|
157
|
+
expected_status: 200
|
|
158
|
+
|
|
159
|
+
- name: Admin Panel
|
|
160
|
+
url: https://admin.example.com
|
|
161
|
+
basic_auth_credentials: admin_auth # Key in Rails credentials
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### SMTP Probes
|
|
165
|
+
|
|
166
|
+
Add probes to `probes/smtp_probes.yml`:
|
|
167
|
+
|
|
168
|
+
```yaml
|
|
169
|
+
- name: Primary Mail Server
|
|
170
|
+
host: mail.example.com
|
|
171
|
+
|
|
172
|
+
- name: Backup Mail Server
|
|
173
|
+
host: mail2.example.com
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### Playwright Probes
|
|
177
|
+
|
|
178
|
+
Generate a new browser-based probe:
|
|
179
|
+
|
|
180
|
+
```bash
|
|
181
|
+
bin/rails generate upright:playwright_probe MyServiceAuth
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
This creates a probe class:
|
|
185
|
+
|
|
186
|
+
```ruby
|
|
187
|
+
# probes/my_service_auth_probe.rb
|
|
188
|
+
class Probes::Playwright::MyServiceAuthProbe < Upright::Probes::Playwright::Base
|
|
189
|
+
# Optionally authenticate before running
|
|
190
|
+
# authenticate_with_form :my_service
|
|
191
|
+
|
|
192
|
+
def check
|
|
193
|
+
page.goto("https://app.example.com")
|
|
194
|
+
page.fill('[name="email"]', "test@example.com")
|
|
195
|
+
page.click('button[type="submit"]')
|
|
196
|
+
page.wait_for_selector(".dashboard")
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
See https://playwright-ruby-client.vercel.app/docs/api/page for how to create Playwright tests.
|
|
202
|
+
|
|
203
|
+
#### Creating Authenticators
|
|
204
|
+
|
|
205
|
+
For probes that require authentication, create an authenticator:
|
|
206
|
+
|
|
207
|
+
```ruby
|
|
208
|
+
# probes/authenticators/my_service.rb
|
|
209
|
+
class Playwright::Authenticator::MyService < Upright::Playwright::Authenticator::Base
|
|
210
|
+
def signin_redirect_url = "https://app.example.com/dashboard"
|
|
211
|
+
def signin_path = "/login"
|
|
212
|
+
def service_name = :my_service
|
|
213
|
+
|
|
214
|
+
def authenticate
|
|
215
|
+
page.goto("https://app.example.com/login")
|
|
216
|
+
page.get_by_label("Email").fill(credentials.my_service.email)
|
|
217
|
+
page.get_by_label("Password").fill(credentials.my_service.password)
|
|
218
|
+
page.get_by_role("button", name: "Sign in").click
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
## Scheduling
|
|
224
|
+
|
|
225
|
+
Configure probe scheduling with Solid Queue in `config/recurring.yml`:
|
|
226
|
+
|
|
227
|
+
```yaml
|
|
228
|
+
production:
|
|
229
|
+
http_probes:
|
|
230
|
+
class: Upright::ProbeCheckJob
|
|
231
|
+
args: [http]
|
|
232
|
+
schedule: every 30 seconds
|
|
233
|
+
|
|
234
|
+
smtp_probes:
|
|
235
|
+
class: Upright::ProbeCheckJob
|
|
236
|
+
args: [smtp]
|
|
237
|
+
schedule: every 30 seconds
|
|
238
|
+
|
|
239
|
+
my_service_auth:
|
|
240
|
+
class: Upright::ProbeCheckJob
|
|
241
|
+
args: [playwright, MyServiceAuth]
|
|
242
|
+
schedule: every 15 minutes
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
## System Requirements
|
|
246
|
+
|
|
247
|
+
### Minimum VM Specifications
|
|
248
|
+
|
|
249
|
+
| Resource | Minimum | Recommended |
|
|
250
|
+
|----------|---------|-------------|
|
|
251
|
+
| CPU | 2 vCPU | 2 vCPU |
|
|
252
|
+
| RAM | 2 GB | 4 GB |
|
|
253
|
+
| Disk | 25 GB | 50 GB |
|
|
254
|
+
|
|
255
|
+
Playwright browser automation is memory-intensive. For sites running many Playwright probes concurrently, consider 4 GB RAM.
|
|
256
|
+
|
|
257
|
+
### Software Requirements
|
|
258
|
+
|
|
259
|
+
- **OS**: Ubuntu 24.04+ or Debian 12+ (any Linux with Docker support)
|
|
260
|
+
- **Docker**: 24.0+ (installed automatically by Kamal)
|
|
261
|
+
- **Ruby**: 3.4+ (for local development only; production runs in Docker)
|
|
262
|
+
- **Rails**: 8.0+
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
### Firewall Rules
|
|
266
|
+
|
|
267
|
+
Open the following ports:
|
|
268
|
+
|
|
269
|
+
| Port | Protocol | Direction | Purpose |
|
|
270
|
+
|------|----------|-----------|---------|
|
|
271
|
+
| 22 | TCP | Inbound | SSH access |
|
|
272
|
+
| 80 | TCP | Inbound | HTTP (redirects to HTTPS) |
|
|
273
|
+
| 443 | TCP | Inbound | HTTPS |
|
|
274
|
+
| 25 | TCP | Outbound | SMTP probes (if used) |
|
|
275
|
+
|
|
276
|
+
### SMTP Port 25 Note
|
|
277
|
+
|
|
278
|
+
Most cloud providers block outbound port 25 by default to prevent spam. If you plan to use SMTP probes, you must request port 25 to be unblocked.
|
|
279
|
+
|
|
280
|
+
This is not required for HTTP or Playwright probes.
|
|
281
|
+
|
|
282
|
+
## DNS Setup
|
|
283
|
+
|
|
284
|
+
For multiple geographic locations, use subdomains for each site. Each subdomain points to a different server:
|
|
285
|
+
|
|
286
|
+
```
|
|
287
|
+
; Primary dashboard
|
|
288
|
+
app.upright.example.com A 203.0.113.10
|
|
289
|
+
|
|
290
|
+
; Monitoring nodes
|
|
291
|
+
ams.upright.example.com A 203.0.113.10 ; Amsterdam
|
|
292
|
+
nyc.upright.example.com A 198.51.100.20 ; New York
|
|
293
|
+
sfo.upright.example.com A 192.0.2.30 ; San Francisco
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
## Deployment with Kamal
|
|
297
|
+
|
|
298
|
+
### Example `config/deploy.yml`
|
|
299
|
+
|
|
300
|
+
```yaml
|
|
301
|
+
service: upright
|
|
302
|
+
image: your-org/upright
|
|
303
|
+
|
|
304
|
+
servers:
|
|
305
|
+
web:
|
|
306
|
+
hosts:
|
|
307
|
+
- nyc.upright.example.com
|
|
308
|
+
env:
|
|
309
|
+
tags:
|
|
310
|
+
SITE_SUBDOMAIN: nyc
|
|
311
|
+
|
|
312
|
+
hosts:
|
|
313
|
+
- ams.upright.example.com
|
|
314
|
+
env:
|
|
315
|
+
tags:
|
|
316
|
+
SITE_SUBDOMAIN: ams
|
|
317
|
+
|
|
318
|
+
hosts:
|
|
319
|
+
- sfo.upright.example.com
|
|
320
|
+
env:
|
|
321
|
+
tags:
|
|
322
|
+
SITE_SUBDOMAIN: sfo
|
|
323
|
+
|
|
324
|
+
registry:
|
|
325
|
+
server: ghcr.io
|
|
326
|
+
username: your-org
|
|
327
|
+
password:
|
|
328
|
+
- KAMAL_REGISTRY_PASSWORD
|
|
329
|
+
|
|
330
|
+
env:
|
|
331
|
+
clear:
|
|
332
|
+
RAILS_ENV: production
|
|
333
|
+
RAILS_LOG_TO_STDOUT: true
|
|
334
|
+
secret:
|
|
335
|
+
- RAILS_MASTER_KEY
|
|
336
|
+
- OIDC_CLIENT_ID
|
|
337
|
+
- OIDC_CLIENT_SECRET
|
|
338
|
+
|
|
339
|
+
accessories:
|
|
340
|
+
playwright:
|
|
341
|
+
image: mcr.microsoft.com/playwright:v1.55.0-noble
|
|
342
|
+
cmd: npx -y playwright run-server --port 53333
|
|
343
|
+
host: nyc.upright.example.com
|
|
344
|
+
port: 53333
|
|
345
|
+
env:
|
|
346
|
+
clear:
|
|
347
|
+
DEBUG: pw:api
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
## Observability
|
|
351
|
+
|
|
352
|
+
### Prometheus
|
|
353
|
+
|
|
354
|
+
The engine exposes metrics at `/metrics`. Configure Prometheus to scrape:
|
|
355
|
+
|
|
356
|
+
```yaml
|
|
357
|
+
scrape_configs:
|
|
358
|
+
- job_name: upright
|
|
359
|
+
static_configs:
|
|
360
|
+
- targets: ['localhost:3000']
|
|
361
|
+
metrics_path: /metrics
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
### Metrics Exposed
|
|
365
|
+
|
|
366
|
+
- `upright_probe_duration_seconds` - Probe execution duration
|
|
367
|
+
- `upright_probe_success` - Probe success/failure (1/0)
|
|
368
|
+
- `upright_probe_status_code` - HTTP status code for HTTP probes
|
|
369
|
+
|
|
370
|
+
Labels include: `probe_name`, `probe_type`, `site_code`, `site_city`, `site_country`
|
|
371
|
+
|
|
372
|
+
### AlertManager
|
|
373
|
+
|
|
374
|
+
Example alert rules (`prometheus/rules/upright.rules`):
|
|
375
|
+
|
|
376
|
+
```yaml
|
|
377
|
+
groups:
|
|
378
|
+
- name: upright
|
|
379
|
+
rules:
|
|
380
|
+
- alert: ProbeDown
|
|
381
|
+
expr: upright_probe_success == 0
|
|
382
|
+
for: 5m
|
|
383
|
+
labels:
|
|
384
|
+
severity: critical
|
|
385
|
+
annotations:
|
|
386
|
+
summary: "Probe {{ $labels.probe_name }} is down"
|
|
387
|
+
|
|
388
|
+
- alert: ProbeSlow
|
|
389
|
+
expr: upright_probe_duration_seconds > 10
|
|
390
|
+
for: 5m
|
|
391
|
+
labels:
|
|
392
|
+
severity: warning
|
|
393
|
+
annotations:
|
|
394
|
+
summary: "Probe {{ $labels.probe_name }} is slow"
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
### OpenTelemetry
|
|
398
|
+
|
|
399
|
+
Traces are automatically created for each probe execution. Configure your collector endpoint:
|
|
400
|
+
|
|
401
|
+
```ruby
|
|
402
|
+
Upright.configure do |config|
|
|
403
|
+
config.otel_endpoint = "https://otel.example.com:4318"
|
|
404
|
+
end
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
## Local Development
|
|
408
|
+
|
|
409
|
+
### Setup
|
|
410
|
+
|
|
411
|
+
```bash
|
|
412
|
+
bin/setup
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
This installs dependencies, prepares the database, and starts the dev server.
|
|
416
|
+
|
|
417
|
+
### Running Services
|
|
418
|
+
|
|
419
|
+
Start supporting Docker services (Playwright server, etc.):
|
|
420
|
+
|
|
421
|
+
```bash
|
|
422
|
+
bin/services
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
### Running the Server
|
|
426
|
+
|
|
427
|
+
```bash
|
|
428
|
+
bin/dev
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
Visit http://app.upright.localhost:3000 and sign in with:
|
|
432
|
+
- **Username**: `admin`
|
|
433
|
+
- **Password**: `upright` (or value of `ADMIN_PASSWORD` env var)
|
|
434
|
+
|
|
435
|
+
### Testing Playwright Probes
|
|
436
|
+
|
|
437
|
+
Run probes with a visible browser window:
|
|
438
|
+
|
|
439
|
+
```bash
|
|
440
|
+
LOCAL_PLAYWRIGHT=1 bin/rails console
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
```ruby
|
|
444
|
+
Probes::Playwright::MyServiceAuthProbe.check
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
### Running Tests
|
|
448
|
+
|
|
449
|
+
```bash
|
|
450
|
+
bin/rails test
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
## License
|
|
454
|
+
|
|
455
|
+
The gem is available under the terms of the [O'Saasy License](LICENSE.md).
|
data/Rakefile
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
@layer reset, base, components, utilities;
|
|
2
|
+
|
|
3
|
+
:root {
|
|
4
|
+
/* Spacing */
|
|
5
|
+
--inline-space: 1ch;
|
|
6
|
+
--block-space: 1rem;
|
|
7
|
+
|
|
8
|
+
/* Text */
|
|
9
|
+
--font-sans: -apple-system, BlinkMacSystemFont, sans-serif;
|
|
10
|
+
--font-mono: "IBM Plex Mono", "JetBrains Mono", ui-monospace, "Cascadia Code", "Fira Code", monospace;
|
|
11
|
+
|
|
12
|
+
--text-x-small: 0.7rem;
|
|
13
|
+
--text-small: 0.8rem;
|
|
14
|
+
--text-normal: 0.9rem;
|
|
15
|
+
--text-large: 1.25rem;
|
|
16
|
+
--text-x-large: 1.5rem;
|
|
17
|
+
|
|
18
|
+
/* OKLCH colors: Primitives */
|
|
19
|
+
--lch-black: 0% 0 0;
|
|
20
|
+
--lch-white: 100% 0 0;
|
|
21
|
+
--lch-canvas: var(--lch-white);
|
|
22
|
+
--lch-canvas-deep: var(--lch-white);
|
|
23
|
+
--lch-ink-inverted: var(--lch-white);
|
|
24
|
+
|
|
25
|
+
--lch-ink-darkest: 26% 0.05 264;
|
|
26
|
+
--lch-ink-darker: 40% 0.026 262;
|
|
27
|
+
--lch-ink-dark: 56% 0.014 260;
|
|
28
|
+
--lch-ink-medium: 66% 0.008 258;
|
|
29
|
+
--lch-ink-light: 84% 0.005 256;
|
|
30
|
+
--lch-ink-lighter: 92% 0.003 254;
|
|
31
|
+
--lch-ink-lightest: 96% 0.002 252;
|
|
32
|
+
|
|
33
|
+
--lch-blue-dark: 57.02% 0.1895 260.46;
|
|
34
|
+
--lch-blue-lighter: 92% 0.026 254;
|
|
35
|
+
|
|
36
|
+
--lch-green-dark: 55% 0.162 147;
|
|
37
|
+
--lch-red-dark: 59% 0.19 38;
|
|
38
|
+
|
|
39
|
+
/* Colors: Semantic */
|
|
40
|
+
--color-black: oklch(var(--lch-black));
|
|
41
|
+
--color-white: oklch(var(--lch-white));
|
|
42
|
+
--color-canvas: oklch(var(--lch-canvas));
|
|
43
|
+
--color-ink: oklch(var(--lch-ink-darkest));
|
|
44
|
+
--color-ink-dark: oklch(var(--lch-ink-dark));
|
|
45
|
+
--color-ink-medium: oklch(var(--lch-ink-medium));
|
|
46
|
+
--color-ink-light: oklch(var(--lch-ink-light));
|
|
47
|
+
--color-ink-lighter: oklch(var(--lch-ink-lighter));
|
|
48
|
+
--color-ink-lightest: oklch(var(--lch-ink-lightest));
|
|
49
|
+
--color-ink-inverted: oklch(var(--lch-ink-inverted));
|
|
50
|
+
|
|
51
|
+
/* Colors: Abstractions */
|
|
52
|
+
--color-link: oklch(var(--lch-blue-dark));
|
|
53
|
+
--color-positive: oklch(var(--lch-green-dark));
|
|
54
|
+
--color-negative: oklch(var(--lch-red-dark));
|
|
55
|
+
--color-selected: oklch(var(--lch-blue-lighter));
|
|
56
|
+
|
|
57
|
+
/* Borders & Shadows */
|
|
58
|
+
--border: 1px solid var(--color-ink-lighter);
|
|
59
|
+
--shadow: 0 0 0 1px oklch(var(--lch-black) / 5%),
|
|
60
|
+
0 0.2em 0.2em oklch(var(--lch-black) / 5%),
|
|
61
|
+
0 0.4em 0.4em oklch(var(--lch-black) / 5%),
|
|
62
|
+
0 0.8em 0.8em oklch(var(--lch-black) / 5%);
|
|
63
|
+
|
|
64
|
+
/* Focus rings */
|
|
65
|
+
--focus-ring-color: var(--color-link);
|
|
66
|
+
--focus-ring-size: 2px;
|
|
67
|
+
|
|
68
|
+
/* Layout */
|
|
69
|
+
--main-padding: clamp(var(--inline-space), 3vw, calc(var(--inline-space) * 3));
|
|
70
|
+
--main-width: 1400px;
|
|
71
|
+
|
|
72
|
+
/* Grain */
|
|
73
|
+
--grain-opacity: 0.04;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/* Dark mode: Update primitive values only */
|
|
77
|
+
@media (prefers-color-scheme: dark) {
|
|
78
|
+
:root {
|
|
79
|
+
--lch-canvas: 18% 0.04 250;
|
|
80
|
+
--lch-canvas-deep: 12% 0.035 255;
|
|
81
|
+
--lch-ink-inverted: var(--lch-black);
|
|
82
|
+
|
|
83
|
+
--lch-ink-darkest: 92% 0.01 250;
|
|
84
|
+
--lch-ink-darker: 80% 0.015 250;
|
|
85
|
+
--lch-ink-dark: 65% 0.02 250;
|
|
86
|
+
--lch-ink-medium: 50% 0.025 250;
|
|
87
|
+
--lch-ink-light: 35% 0.03 250;
|
|
88
|
+
--lch-ink-lighter: 24% 0.035 250;
|
|
89
|
+
--lch-ink-lightest: 20% 0.04 250;
|
|
90
|
+
|
|
91
|
+
--lch-blue-dark: 70% 0.14 250;
|
|
92
|
+
--lch-blue-lighter: 25% 0.05 250;
|
|
93
|
+
|
|
94
|
+
--lch-green-dark: 75% 0.15 145;
|
|
95
|
+
--lch-red-dark: 70% 0.16 42;
|
|
96
|
+
|
|
97
|
+
--shadow: 0 0 0 1px oklch(var(--lch-black) / 0.4),
|
|
98
|
+
0 .2em 1.6em -0.8em oklch(var(--lch-black) / 0.5),
|
|
99
|
+
0 .4em 2.4em -1em oklch(var(--lch-black) / 0.6),
|
|
100
|
+
0 .4em .8em -1.2em oklch(var(--lch-black) / 0.7);
|
|
101
|
+
|
|
102
|
+
--grain-opacity: 0.1;
|
|
103
|
+
}
|
|
104
|
+
}
|