upright 0.2.0 → 0.4.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.
Files changed (145) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +208 -56
  3. data/Rakefile +22 -0
  4. data/app/assets/images/upright/.keep +0 -0
  5. data/app/assets/stylesheets/upright/_global.css +2 -0
  6. data/app/assets/stylesheets/upright/artifact.css +7 -25
  7. data/app/assets/stylesheets/upright/flash.css +19 -0
  8. data/app/assets/stylesheets/upright/header.css +14 -0
  9. data/app/assets/stylesheets/upright/incidents.css +610 -0
  10. data/app/assets/stylesheets/upright/map.css +6 -0
  11. data/app/assets/stylesheets/upright/public_status.css +534 -0
  12. data/app/assets/stylesheets/upright/uptime-bars.css +10 -1
  13. data/app/controllers/concerns/.keep +0 -0
  14. data/app/controllers/concerns/upright/authentication.rb +1 -0
  15. data/app/controllers/concerns/upright/proxy_authentication.rb +134 -0
  16. data/app/controllers/upright/alertmanager_proxy_controller.rb +12 -4
  17. data/app/controllers/upright/artifacts_controller.rb +1 -1
  18. data/app/controllers/upright/incidents/updates_controller.rb +13 -0
  19. data/app/controllers/upright/incidents_controller.rb +53 -0
  20. data/app/controllers/upright/probe_results_controller.rb +1 -0
  21. data/app/controllers/upright/prometheus_proxy_controller.rb +13 -18
  22. data/app/controllers/upright/public/base_controller.rb +16 -0
  23. data/app/controllers/upright/public/incidents_controller.rb +6 -0
  24. data/app/controllers/upright/public/service_incidents_controller.rb +7 -0
  25. data/app/controllers/upright/public/services_controller.rb +6 -0
  26. data/app/controllers/upright/sessions_controller.rb +37 -1
  27. data/app/controllers/upright/traces_controller.rb +88 -0
  28. data/app/helpers/upright/application_helper.rb +22 -2
  29. data/app/helpers/upright/artifacts_helper.rb +20 -0
  30. data/app/helpers/upright/probe_results_helper.rb +4 -10
  31. data/app/helpers/upright/public/services_helper.rb +70 -0
  32. data/app/javascript/upright/application.js +3 -0
  33. data/app/javascript/upright/controllers/sites_map_controller.js +13 -5
  34. data/app/javascript/upright/controllers/template_controller.js +11 -0
  35. data/app/javascript/upright/public.js +5 -0
  36. data/app/jobs/upright/health_metrics_job.rb +27 -0
  37. data/app/jobs/upright/incident_reporter_job.rb +7 -0
  38. data/app/jobs/upright/maintenance_advance_job.rb +8 -0
  39. data/app/jobs/upright/playwright_video_sweep_job.rb +21 -0
  40. data/app/jobs/upright/rollups/daily_aggregation_job.rb +14 -0
  41. data/app/models/concerns/.keep +0 -0
  42. data/app/models/concerns/upright/incidents/auto_reporting.rb +93 -0
  43. data/app/models/concerns/upright/incidents/lifecycle.rb +47 -0
  44. data/app/models/concerns/upright/playwright/form_authentication.rb +3 -5
  45. data/app/models/concerns/upright/playwright/lifecycle.rb +8 -13
  46. data/app/models/concerns/upright/playwright/trace_recording.rb +53 -0
  47. data/app/models/concerns/upright/playwright/video_recording.rb +55 -14
  48. data/app/models/concerns/upright/probe_result/stale_cleanup.rb +47 -0
  49. data/app/models/concerns/upright/probeable.rb +16 -2
  50. data/app/models/concerns/upright/services/live_status.rb +69 -0
  51. data/app/models/concerns/upright/services/maintenance_status.rb +19 -0
  52. data/app/models/upright/artifact.rb +2 -1
  53. data/app/models/upright/http/request.rb +13 -4
  54. data/app/models/upright/incident/automatic_report.rb +3 -0
  55. data/app/models/upright/incident.rb +87 -0
  56. data/app/models/upright/incident_affected_service.rb +9 -0
  57. data/app/models/upright/incident_update.rb +7 -0
  58. data/app/models/upright/maintenance.rb +36 -0
  59. data/app/models/upright/persistent_record.rb +12 -0
  60. data/app/models/upright/playwright/authenticator/base.rb +50 -70
  61. data/app/models/upright/probe_result.rb +6 -1
  62. data/app/models/upright/probes/http_probe.rb +30 -1
  63. data/app/models/upright/probes/playwright/base.rb +2 -0
  64. data/app/models/upright/probes/status/probe.rb +8 -0
  65. data/app/models/upright/probes/status.rb +5 -13
  66. data/app/models/upright/probes/uptime.rb +5 -12
  67. data/app/models/upright/rollups/daily_uptime.rb +28 -0
  68. data/app/models/upright/rollups/probe_rollup.rb +64 -0
  69. data/app/models/upright/rollups/probe_uptime.rb +9 -0
  70. data/app/models/upright/rollups/site_uptime.rb +75 -0
  71. data/app/models/upright/service/daily_status.rb +39 -0
  72. data/app/models/upright/service/incident_history.rb +24 -0
  73. data/app/models/upright/service/outage.rb +15 -0
  74. data/app/models/upright/service/status_page.rb +35 -0
  75. data/app/models/upright/service.rb +101 -0
  76. data/app/models/upright/status.rb +21 -0
  77. data/app/models/upright/traceroute/ip_metadata_lookup.rb +21 -2
  78. data/app/models/upright/traceroute/result.rb +8 -1
  79. data/app/views/layouts/upright/_header.html.erb +12 -2
  80. data/app/views/layouts/upright/application.html.erb +4 -1
  81. data/app/views/layouts/upright/public.html.erb +18 -0
  82. data/app/views/layouts/upright/public.rss.builder +1 -0
  83. data/app/views/upright/active_storage/attachments/_attachment.html.erb +1 -1
  84. data/app/views/upright/artifacts/show.html.erb +8 -0
  85. data/app/views/upright/dashboards/_uptime_probe_row.html.erb +5 -3
  86. data/app/views/upright/dashboards/probe_statuses/show.html.erb +1 -1
  87. data/app/views/upright/dashboards/uptimes/show.html.erb +1 -1
  88. data/app/views/upright/incidents/_form.html.erb +93 -0
  89. data/app/views/upright/incidents/_row.html.erb +15 -0
  90. data/app/views/upright/incidents/edit.html.erb +64 -0
  91. data/app/views/upright/incidents/index.html.erb +53 -0
  92. data/app/views/upright/incidents/new.html.erb +6 -0
  93. data/app/views/upright/probe_results/index.html.erb +7 -4
  94. data/app/views/upright/public/_branding.html.erb +3 -0
  95. data/app/views/upright/public/_footer.html.erb +3 -0
  96. data/app/views/upright/public/incidents/show.html.erb +29 -0
  97. data/app/views/upright/public/service_incidents/_incident.html.erb +14 -0
  98. data/app/views/upright/public/service_incidents/index.html.erb +50 -0
  99. data/app/views/upright/public/services/_active_events.html.erb +28 -0
  100. data/app/views/upright/public/services/_degraded_list.html.erb +14 -0
  101. data/app/views/upright/public/services/_overall_banner.html.erb +4 -0
  102. data/app/views/upright/public/services/_service.html.erb +34 -0
  103. data/app/views/upright/public/services/_upcoming_maintenance.html.erb +21 -0
  104. data/app/views/upright/public/services/_uptime_bar.html.erb +9 -0
  105. data/app/views/upright/public/services/index.html.erb +13 -0
  106. data/app/views/upright/public/services/index.rss.builder +29 -0
  107. data/app/views/upright/sessions/new.html.erb +14 -1
  108. data/config/ci.rb +2 -0
  109. data/config/importmap.rb +25 -14
  110. data/config/initializers/date_formats.rb +5 -0
  111. data/config/initializers/mime_types.rb +1 -0
  112. data/config/routes.rb +26 -2
  113. data/db/migrate/20260629000001_fix_probe_result_duration_precision.rb +14 -0
  114. data/db/persistent_migrate/20260512000001_create_upright_rollups.rb +16 -0
  115. data/db/persistent_migrate/20260702000001_create_upright_incidents.rb +34 -0
  116. data/db/persistent_migrate/20260707000001_add_probe_identity_to_upright_rollups.rb +11 -0
  117. data/db/persistent_migrate/20260709000001_add_created_by_to_incidents.rb +7 -0
  118. data/db/persistent_migrate/20260901000001_add_auto_reporting_to_upright_incidents.rb +22 -0
  119. data/lib/generators/upright/install/database_config.rb +46 -0
  120. data/lib/generators/upright/install/install_generator.rb +52 -3
  121. data/lib/generators/upright/install/templates/Dockerfile +80 -0
  122. data/lib/generators/upright/install/templates/content_security_policy.rb +42 -0
  123. data/lib/generators/upright/install/templates/deploy.yml +13 -13
  124. data/lib/generators/upright/install/templates/development_prometheus.yml +1 -1
  125. data/lib/generators/upright/install/templates/docker-compose.yml +12 -11
  126. data/lib/generators/upright/install/templates/http_probes.yml +1 -0
  127. data/lib/generators/upright/install/templates/omniauth.rb +7 -3
  128. data/lib/generators/upright/install/templates/otel_collector.yml +6 -0
  129. data/lib/generators/upright/install/templates/recurring.yml +45 -0
  130. data/lib/generators/upright/install/templates/sites.yml +4 -0
  131. data/lib/generators/upright/install/templates/smtp_probes.yml +3 -0
  132. data/lib/generators/upright/install/templates/traceroute_probes.yml +3 -0
  133. data/lib/generators/upright/install/templates/upright.rb +24 -2
  134. data/lib/generators/upright/install/templates/upright.rules.yml +14 -7
  135. data/lib/upright/configuration.rb +127 -6
  136. data/lib/upright/engine.rb +32 -2
  137. data/lib/upright/metrics.rb +27 -2
  138. data/lib/upright/probe_type_registry.rb +33 -0
  139. data/lib/upright/site.rb +31 -1
  140. data/lib/upright/version.rb +3 -1
  141. data/lib/upright.rb +34 -2
  142. metadata +110 -7
  143. data/config/credentials/development.key +0 -1
  144. data/config/credentials/test.key +0 -1
  145. data/lib/tasks/upright_tasks.rake +0 -4
@@ -0,0 +1,46 @@
1
+ module Upright
2
+ module Generators
3
+ # Rewrites the single-database development and test entries that `rails new`
4
+ # writes into the three databases Upright uses:
5
+ #
6
+ # primary probe results and Active Storage artifacts
7
+ # persistent rollups, incidents and maintenance windows, which outlive a
8
+ # site's probe data; its migrations ship with the gem
9
+ # queue Solid Queue
10
+ #
11
+ # Pure string transformation so the install generator's edit can be tested
12
+ # without a generated application.
13
+ module DatabaseConfig
14
+ ENVIRONMENTS = %w[development test].freeze
15
+
16
+ PERSISTENT_MIGRATIONS = %(<%= Gem.loaded_specs["upright"].gem_dir %>/db/persistent_migrate)
17
+
18
+ def self.rewrite(yaml)
19
+ ENVIRONMENTS.reduce(yaml) do |rewritten, env|
20
+ rewritten.sub(single_database(env), split_databases(env))
21
+ end
22
+ end
23
+
24
+ def self.single_database(env)
25
+ "#{env}:\n <<: *default\n database: storage/#{env}.sqlite3"
26
+ end
27
+
28
+ def self.split_databases(env)
29
+ <<~YAML.chomp
30
+ #{env}:
31
+ primary:
32
+ <<: *default
33
+ database: storage/#{env}.sqlite3
34
+ persistent:
35
+ <<: *default
36
+ database: storage/#{env}_persistent.sqlite3
37
+ migrations_paths: #{PERSISTENT_MIGRATIONS}
38
+ queue:
39
+ <<: *default
40
+ database: storage/#{env}_queue.sqlite3
41
+ migrations_paths: db/queue_migrate
42
+ YAML
43
+ end
44
+ end
45
+ end
46
+ end
@@ -1,3 +1,5 @@
1
+ require_relative "database_config"
2
+
1
3
  module Upright
2
4
  module Generators
3
5
  class InstallGenerator < Rails::Generators::Base
@@ -5,9 +7,23 @@ module Upright
5
7
 
6
8
  desc "Install Upright engine into your application"
7
9
 
10
+ def set_ruby_version
11
+ ruby_version = RUBY_VERSION
12
+ create_file ".ruby-version", "#{ruby_version}\n"
13
+ create_file "mise.toml", <<~TOML
14
+ [tools]
15
+ ruby = "#{ruby_version}"
16
+ TOML
17
+ end
18
+
8
19
  def copy_initializers
9
20
  template "upright.rb", "config/initializers/upright.rb"
10
21
  template "omniauth.rb", "config/initializers/omniauth.rb"
22
+ # Never force: an app may already run an enforced CSP, and silently
23
+ # replacing it with this commented template would disable it. On a
24
+ # collision Thor prompts instead of overwriting; the recommended policy is
25
+ # documented for the operator to merge.
26
+ template "content_security_policy.rb", "config/initializers/content_security_policy.rb"
11
27
  end
12
28
 
13
29
  def copy_sites_config
@@ -37,12 +53,45 @@ module Upright
37
53
 
38
54
  def copy_deploy_config
39
55
  template "deploy.yml", "config/deploy.yml"
56
+ template "Dockerfile", "Dockerfile"
40
57
  end
41
58
 
42
59
  def copy_puma_config
43
60
  template "puma.rb", "config/puma.rb"
44
61
  end
45
62
 
63
+ def install_solid_queue
64
+ rails_command "solid_queue:install"
65
+ end
66
+
67
+ # Splits the development and test databases into primary, persistent and
68
+ # queue. The persistent database holds rollups, incidents and maintenance
69
+ # windows, and its migrations come from the gem, so database.yml points
70
+ # migrations_paths at the installed gem's db/persistent_migrate.
71
+ def add_persistent_and_queue_databases
72
+ gsub_file "config/database.yml", /\A.*\z/m do |yaml|
73
+ DatabaseConfig.rewrite(yaml)
74
+ end
75
+ end
76
+
77
+ def copy_recurring_config
78
+ template "recurring.yml", "config/recurring.yml", force: true
79
+ end
80
+
81
+ def add_jobs_to_procfile
82
+ procfile = File.join(destination_root, "Procfile.dev")
83
+ if File.exist?(procfile)
84
+ unless File.read(procfile).include?("jobs:")
85
+ append_to_file "Procfile.dev", "jobs: OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES bin/rails solid_queue:start\n"
86
+ end
87
+ else
88
+ create_file "Procfile.dev", <<~PROCFILE
89
+ web: bin/rails server -b '0.0.0.0' -p ${PORT:-3000}
90
+ jobs: OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES bin/rails solid_queue:start
91
+ PROCFILE
92
+ end
93
+ end
94
+
46
95
  def add_routes
47
96
  route 'mount Upright::Engine => "/", as: :upright'
48
97
  end
@@ -60,14 +109,14 @@ module Upright
60
109
  say "Upright has been installed!", :green
61
110
  say ""
62
111
  say "Next steps:"
63
- say " 1. Run migrations: bin/rails db:migrate"
112
+ say " 1. Prepare the databases (primary, persistent and queue): bin/rails db:prepare"
64
113
  say " 2. Configure your servers in config/deploy.yml"
65
114
  say " 3. Configure sites in config/sites.yml"
66
115
  say " 4. Add probes in probes/*.yml"
67
- say " 5. Set ADMIN_PASSWORD env var (default: upright)"
116
+ say " 5. Set the ADMIN_PASSWORD env var — required, there is no default password"
68
117
  say ""
69
118
  say "For production, review config/initializers/upright.rb and update:"
70
- say " config.hostname = \"honcho-upright.com\""
119
+ say " config.hostname = \"example.com\""
71
120
  say ""
72
121
  say "Start dev services (Prometheus, Alertmanager, Playwright):"
73
122
  say " docker compose up -d"
@@ -0,0 +1,80 @@
1
+ # syntax = docker/dockerfile:1
2
+
3
+ # This Dockerfile is designed for production, not development. Use with Kamal or build'n'run by hand:
4
+ # docker build -t my-app .
5
+ # docker run -d -p 80:80 -p 443:443 --name my-app -e RAILS_MASTER_KEY=<value from config/master.key> my-app
6
+
7
+ # Make sure RUBY_VERSION matches the Ruby version in .ruby-version
8
+ ARG RUBY_VERSION=<%= RUBY_VERSION %>
9
+ FROM docker.io/library/ruby:$RUBY_VERSION-slim AS base
10
+
11
+ # Rails app lives here
12
+ WORKDIR /rails
13
+
14
+ # Install base packages and Node.js (for Playwright)
15
+ RUN apt-get update -qq && \
16
+ apt-get install --no-install-recommends -y curl libjemalloc2 libvips sqlite3 iputils-ping mtr-tiny \
17
+ nodejs npm && \
18
+ rm -rf /var/lib/apt/lists /var/cache/apt/archives
19
+
20
+ # Set production environment
21
+ ENV RAILS_ENV="production" \
22
+ BUNDLE_DEPLOYMENT="1" \
23
+ BUNDLE_PATH="/usr/local/bundle" \
24
+ BUNDLE_WITHOUT="development"
25
+
26
+ # Throw-away build stage to reduce size of final image
27
+ FROM base AS build
28
+
29
+ # Install packages needed to build gems
30
+ RUN apt-get update -qq && \
31
+ apt-get install --no-install-recommends -y build-essential git pkg-config libssl-dev libyaml-dev rustc cargo libclang-dev && \
32
+ rm -rf /var/lib/apt/lists /var/cache/apt/archives
33
+
34
+ # Install application gems
35
+ COPY Gemfile Gemfile.lock ./
36
+ RUN --mount=type=secret,id=GITHUB_TOKEN \
37
+ BUNDLE_GITHUB__COM="$(cat /run/secrets/GITHUB_TOKEN):x-oauth-basic" bundle install && \
38
+ rm -rf ~/.bundle/ "${BUNDLE_PATH}"/ruby/*/cache "${BUNDLE_PATH}"/ruby/*/bundler/gems/*/.git && \
39
+ bundle exec bootsnap precompile --gemfile && \
40
+ bundle exec ruby -e "require 'upright/version'; puts Upright::PLAYWRIGHT_VERSION" > /tmp/playwright_version
41
+
42
+ # Copy application code
43
+ COPY . .
44
+
45
+ # Precompile bootsnap code for faster boot times
46
+ RUN bundle exec bootsnap precompile app.rb probes/
47
+
48
+ # Precompiling assets for production without requiring secret RAILS_MASTER_KEY
49
+ RUN SECRET_KEY_BASE_DUMMY=1 ./bin/rails assets:precompile
50
+
51
+ # Final stage for app image
52
+ FROM base
53
+
54
+ # Install Playwright before copying app code so the layer is cached on code-only deploys.
55
+ # The version is extracted to a file in the build stage; BuildKit's content-based caching
56
+ # keeps this layer cached even across Gemfile changes when the version is unchanged.
57
+ COPY --from=build /tmp/playwright_version /tmp/playwright_version
58
+ ENV PLAYWRIGHT_BROWSERS_PATH=/ms-playwright
59
+ RUN PLAYWRIGHT_VERSION=$(cat /tmp/playwright_version) && \
60
+ npm install -g playwright@~${PLAYWRIGHT_VERSION}.0 && \
61
+ playwright install --with-deps chromium && \
62
+ chmod -R o+rx /ms-playwright
63
+
64
+ # Copy built artifacts: gems, application
65
+ COPY --from=build "${BUNDLE_PATH}" "${BUNDLE_PATH}"
66
+ COPY --from=build /rails /rails
67
+
68
+ # Run and own only the runtime files as a non-root user for security
69
+ RUN groupadd --system --gid 1000 rails && \
70
+ useradd rails --uid 1000 --gid 1000 --create-home --shell /bin/bash && \
71
+ mkdir -p storage tmp && \
72
+ chown -R rails:rails storage tmp
73
+ USER 1000:1000
74
+
75
+ # Entrypoint prepares the database.
76
+ ENTRYPOINT ["/rails/bin/docker-entrypoint"]
77
+
78
+ # Start the server by default, this can be overwritten at runtime
79
+ EXPOSE 3000 9394
80
+ CMD ["./bin/rails", "server"]
@@ -0,0 +1,42 @@
1
+ # Be sure to restart your server when you modify this file.
2
+
3
+ # Content Security Policy tuned for Upright. Uncomment the block below to
4
+ # enforce it. Every source the engine actually uses is listed explicitly:
5
+ #
6
+ # * JavaScript dependencies (Turbo, Stimulus, Leaflet, Frappe Charts) are
7
+ # pinned to https://cdn.jsdelivr.net in the engine's config/importmap.rb.
8
+ # * The Leaflet stylesheet is loaded from https://unpkg.com in the engine
9
+ # layout; Turbo injects an inline <style> for its progress bar, which is
10
+ # why style-src keeps :unsafe_inline (styles only — scripts stay strict).
11
+ # * Map tiles come from OpenStreetMap (light) and Carto (dark); Leaflet's
12
+ # default marker icons load from unpkg alongside its stylesheet.
13
+ # * The inline <script type="importmap"> tag is authorized by a per-session
14
+ # nonce via csp_meta_tag, which the engine layouts already render.
15
+ #
16
+ # To verify without breaking anything, start with
17
+ # config.content_security_policy_report_only = true and watch the browser
18
+ # console before enforcing.
19
+
20
+ # Rails.application.configure do
21
+ # config.content_security_policy do |policy|
22
+ # policy.default_src :self
23
+ # policy.font_src :self, :data
24
+ # policy.img_src :self, :data, "https://unpkg.com",
25
+ # "https://*.tile.openstreetmap.org"
26
+ # policy.object_src :none
27
+ # policy.script_src :self, "https://cdn.jsdelivr.net"
28
+ # policy.style_src :self, :unsafe_inline, "https://unpkg.com"
29
+ # policy.connect_src :self
30
+ # policy.frame_ancestors :self
31
+ # policy.base_uri :self
32
+ # # Specify URI for violation reports
33
+ # # policy.report_uri "/csp-violation-report-endpoint"
34
+ # end
35
+ #
36
+ # # Generate session nonces for the inline importmap script tag.
37
+ # config.content_security_policy_nonce_generator = ->(request) { request.session.id.to_s }
38
+ # config.content_security_policy_nonce_directives = %w(script-src)
39
+ #
40
+ # # Report violations without enforcing the policy.
41
+ # # config.content_security_policy_report_only = true
42
+ # end
@@ -48,6 +48,7 @@ env:
48
48
  secret:
49
49
  - RAILS_MASTER_KEY
50
50
  - PROMETHEUS_OTLP_TOKEN
51
+ - ADMIN_PASSWORD
51
52
  tags:
52
53
  london:
53
54
  SITE_SUBDOMAIN: lon
@@ -62,7 +63,13 @@ volumes:
62
63
 
63
64
  accessories:
64
65
  otel_collector:
65
- image: otel/opentelemetry-collector-contrib:0.126.0
66
+ # Pinned by digest so deploys are immutable; the tag is documentation only.
67
+ image: otel/opentelemetry-collector-contrib:0.126.0@sha256:973747f78266a8ffec428417727e6b704559e9a30e4be8c1cca2899facd68689
68
+ # The collector runs unprivileged and only receives OTLP over the private
69
+ # Docker network — it does not mount the Docker socket or host log
70
+ # directories. To collect container logs, prefer a filelog receiver over
71
+ # a read-only mount of the specific log path rather than the Docker socket,
72
+ # which grants root-equivalent control of the host.
66
73
  env:
67
74
  secret:
68
75
  - OTEL_GATEWAY_USERNAME
@@ -71,17 +78,6 @@ accessories:
71
78
  - PROMETHEUS_OTLP_TOKEN
72
79
  files:
73
80
  - config/otel_collector.yml:/etc/otelcol-contrib/config.yaml
74
- volumes:
75
- - /var/run/docker.sock:/var/run/docker.sock
76
- - /var/lib/docker/containers:/var/lib/docker/containers
77
- options:
78
- user: 0
79
- roles:
80
- - jobs
81
-
82
- playwright:
83
- image: jacoblincool/playwright:chromium-server-1.55.0
84
- port: "127.0.0.1:53333:53333"
85
81
  roles:
86
82
  - jobs
87
83
 
@@ -89,12 +85,16 @@ accessories:
89
85
  image: prom/prometheus:v3.2.1
90
86
  hosts:
91
87
  - <%= app_domain %>
88
+ # No published port: Prometheus is only reachable over the private Docker
89
+ # network (the app proxies it). --web.enable-lifecycle is deliberately
90
+ # omitted — the unauthenticated reload/quit endpoints aren't needed since
91
+ # config changes redeploy the accessory; if you add it back, front it with
92
+ # --web.config.file authentication.
92
93
  cmd: >-
93
94
  --config.file=/etc/prometheus/prometheus.yml
94
95
  --storage.tsdb.path=/prometheus
95
96
  --storage.tsdb.retention.time=30d
96
97
  --web.enable-otlp-receiver
97
- --web.enable-lifecycle
98
98
  files:
99
99
  - config/prometheus/prometheus.yml:/etc/prometheus/prometheus.yml
100
100
  - config/prometheus/rules/upright.rules.yml:/etc/prometheus/rules/upright.rules.yml
@@ -9,4 +9,4 @@ scrape_configs:
9
9
  - job_name: "upright"
10
10
  scrape_interval: 5s
11
11
  static_configs:
12
- - targets: ["localhost:9394"]
12
+ - targets: ["host.docker.internal:9394"]
@@ -2,7 +2,14 @@ services:
2
2
  prometheus:
3
3
  image: prom/prometheus:v3.4.1
4
4
  container_name: <%= app_name %>-prometheus
5
- network_mode: host
5
+ # Published on loopback only, so the dev server is not reachable from other
6
+ # machines. host.docker.internal lets Prometheus scrape the app running on
7
+ # the host, on Linux as well as on macOS and Windows Docker Desktop, where
8
+ # network_mode: host does not reach the host's localhost.
9
+ ports:
10
+ - "127.0.0.1:9090:9090"
11
+ extra_hosts:
12
+ - "host.docker.internal:host-gateway"
6
13
  volumes:
7
14
  - ./config/prometheus/development/prometheus.yml:/etc/prometheus/prometheus.yml:ro
8
15
  - ./config/prometheus/rules:/etc/prometheus/rules:ro
@@ -10,14 +17,16 @@ services:
10
17
  command:
11
18
  - '--config.file=/etc/prometheus/prometheus.yml'
12
19
  - '--storage.tsdb.path=/prometheus'
13
- - '--web.enable-lifecycle'
20
+ # --web.enable-lifecycle is omitted: its unauthenticated reload and quit
21
+ # endpoints are not needed (restart the container to reload config). If
22
+ # you add it back, pair it with --web.config.file authentication.
14
23
  - '--web.enable-remote-write-receiver'
15
24
 
16
25
  alertmanager:
17
26
  image: prom/alertmanager:v0.28.1
18
27
  container_name: <%= app_name %>-alertmanager
19
28
  ports:
20
- - "9093:9093"
29
+ - "127.0.0.1:9093:9093"
21
30
  volumes:
22
31
  - ./config/alertmanager/development/alertmanager.yml:/etc/alertmanager/alertmanager.yml:ro
23
32
  - alertmanager_data:/alertmanager
@@ -25,14 +34,6 @@ services:
25
34
  - '--config.file=/etc/alertmanager/alertmanager.yml'
26
35
  - '--storage.path=/alertmanager'
27
36
 
28
- playwright:
29
- image: jacoblincool/playwright:chromium-server-1.56.1
30
- container_name: <%= app_name %>-playwright
31
- ports:
32
- - "53333:53333"
33
- environment:
34
- - DEBUG=true
35
-
36
37
  volumes:
37
38
  prometheus_data:
38
39
  alertmanager_data:
@@ -7,6 +7,7 @@
7
7
  #
8
8
  # Optional fields:
9
9
  # - expected_status: HTTP status code to expect (default: 200-399)
10
+ # - alert_severity: Alert severity when probe fails: medium, high (default), critical
10
11
  # - basic_auth_credentials: Key in Rails credentials for basic auth
11
12
  # - proxy: Key in Rails credentials for proxy settings
12
13
 
@@ -1,8 +1,12 @@
1
- # WARNING: Change the default password before deploying to production!
2
- # Set the ADMIN_PASSWORD environment variable or update the credentials below.
1
+ # Set ADMIN_PASSWORD to enable the built-in admin login. When it is unset the
2
+ # static credential is left unconfigured (fail closed) so the app never ships a
3
+ # well-known default password. Change the username/credentials below to suit, or
4
+ # switch to an external provider (e.g. OpenID Connect) via config.auth_provider.
5
+
6
+ admin_password = ENV["ADMIN_PASSWORD"].presence
3
7
 
4
8
  Rails.application.config.middleware.use OmniAuth::Builder do
5
9
  provider :static_credentials,
6
10
  title: "Sign In",
7
- credentials: { "admin" => ENV.fetch("ADMIN_PASSWORD", "upright") }
11
+ credentials: admin_password ? { "admin" => admin_password } : {}
8
12
  end
@@ -2,6 +2,12 @@ receivers:
2
2
  otlp:
3
3
  protocols:
4
4
  http:
5
+ # The app container reaches the collector over the private Docker
6
+ # network, so the receiver must listen on a non-loopback interface.
7
+ # Kamal does not publish this port to the host (no `port:` on the
8
+ # accessory), so it is only reachable from containers on that network.
9
+ # If you ever publish it, bind a specific internal interface instead
10
+ # of 0.0.0.0 and require authentication.
5
11
  endpoint: 0.0.0.0:4318
6
12
 
7
13
  processors:
@@ -0,0 +1,45 @@
1
+ development:
2
+ http_probes:
3
+ schedule: every minute
4
+ command: "Upright::Probes::HTTPProbe.check_and_record_all_later"
5
+
6
+ smtp_probes:
7
+ schedule: every minute
8
+ command: "Upright::Probes::SMTPProbe.check_and_record_all_later"
9
+
10
+ production:
11
+ http_probes:
12
+ schedule: "*/30 * * * * *"
13
+ command: "Upright::Probes::HTTPProbe.check_and_record_all_later"
14
+
15
+ smtp_probes:
16
+ schedule: "*/30 * * * * *"
17
+ command: "Upright::Probes::SMTPProbe.check_and_record_all_later"
18
+
19
+ clear_solid_queue_finished_jobs:
20
+ command: "SolidQueue::Job.clear_finished_in_batches(sleep_between_batches: 0.3)"
21
+ schedule: every hour at minute 12
22
+
23
+ cleanup_stale_probe_results:
24
+ command: "Upright::ProbeResult.cleanup_stale"
25
+ schedule: every hour at minute 30
26
+
27
+ sweep_playwright_videos:
28
+ class: "Upright::PlaywrightVideoSweepJob"
29
+ schedule: every hour at minute 45
30
+
31
+ health_metrics:
32
+ class: "Upright::HealthMetricsJob"
33
+ schedule: every minute
34
+
35
+ aggregate_rollups:
36
+ class: "Upright::Rollups::DailyAggregationJob"
37
+ schedule: every hour at minute 5
38
+
39
+ advance_maintenances:
40
+ class: "Upright::MaintenanceAdvanceJob"
41
+ schedule: "*/15 * * * * *"
42
+
43
+ report_incidents:
44
+ class: "Upright::IncidentReporterJob"
45
+ schedule: "*/30 * * * * *"
@@ -8,6 +8,8 @@ shared:
8
8
  city: London
9
9
  country: GB
10
10
  geohash: gcpvj0
11
+ stores_metrics: true
12
+ primary: true
11
13
 
12
14
  # Multi-site example:
13
15
  #
@@ -18,6 +20,8 @@ shared:
18
20
  # country: NL
19
21
  # geohash: u17982
20
22
  # provider: digitalocean
23
+ # stores_metrics: true
24
+ # primary: true
21
25
  #
22
26
  # - code: nyc
23
27
  # city: New York City
@@ -4,6 +4,9 @@
4
4
  # Required fields:
5
5
  # - name: Unique identifier for the probe
6
6
  # - host: SMTP server hostname
7
+ #
8
+ # Optional fields:
9
+ # - alert_severity: Alert severity when probe fails: medium, high (default), critical
7
10
 
8
11
  # - name: Example Mail Server
9
12
  # host: mail.example.com
@@ -4,6 +4,9 @@
4
4
  # Required fields:
5
5
  # - name: Unique identifier for the probe
6
6
  # - host: Hostname or IP address to trace
7
+ #
8
+ # Optional fields:
9
+ # - alert_severity: Alert severity when probe fails: medium, high (default), critical
7
10
 
8
11
  # - name: Example Service
9
12
  # host: example.com
@@ -5,8 +5,27 @@ Upright.configure do |config|
5
5
  config.user_agent = "<%= Rails.application.class.module_parent_name.underscore %>/1.0"
6
6
  config.hostname = Rails.env.local? ? "<%= Rails.application.class.module_parent_name.underscore.dasherize %>.localhost" : "<%= Rails.application.class.module_parent_name.underscore.dasherize %>.com"
7
7
 
8
- # Playwright browser server URL
9
- # config.playwright_server_url = ENV["PLAYWRIGHT_SERVER_URL"]
8
+ # Playwright CLI path (defaults to "npx playwright", override with PLAYWRIGHT_CLI_PATH env var)
9
+ # config.playwright_cli_path = "npx playwright"
10
+
11
+ # Token authenticating machine callers: collectors writing metrics and jobs
12
+ # reading a peer site's /prometheus and /alertmanager proxies. Randomly
13
+ # generated at install time — every site must share the same value, so treat
14
+ # it like any other secret when adding sites. Set the PROMETHEUS_OTLP_TOKEN
15
+ # env var (already wired as a Kamal secret in config/deploy.yml) to rotate it
16
+ # without editing this file.
17
+ config.proxy_token = ENV.fetch("PROMETHEUS_OTLP_TOKEN", "<%= SecureRandom.hex(32) %>")
18
+
19
+ # Viewer that trace artifacts link to, https://trace.playwright.dev by
20
+ # default. Upright doesn't serve one: the viewer renders a trace's contents as
21
+ # HTML in its own origin, so a viewer on this hostname would let a probe
22
+ # artifact run script against the admin session, and Upright refuses a URL
23
+ # under config.hostname for that reason.
24
+ #
25
+ # Following a link hands the viewer's origin a URL it can read for 24 hours.
26
+ # Point this at a viewer you host to keep traces to yourself, or assign nil to
27
+ # keep them download-only, for `npx playwright show-trace`.
28
+ # config.trace_viewer_url = "https://traces.example.net/index.html"
10
29
 
11
30
  # OpenTelemetry endpoint
12
31
  # config.otel_endpoint = ENV["OTEL_EXPORTER_OTLP_ENDPOINT"]
@@ -18,4 +37,7 @@ Upright.configure do |config|
18
37
  # client_id: ENV["OIDC_CLIENT_ID"],
19
38
  # client_secret: ENV["OIDC_CLIENT_SECRET"]
20
39
  # }
40
+
41
+ # Register custom probe types (built-in types: http, playwright, smtp, traceroute)
42
+ # config.probe_types.register :ftp_file, name: "FTP File", icon: "📂"
21
43
  end
@@ -6,20 +6,27 @@ groups:
6
6
  interval: 30s
7
7
  rules:
8
8
  # Fraction of regions reporting DOWN (0.0 to 1.0)
9
+ # Keep probe_service so the public status page can read live status per service.
9
10
  - record: upright:probe_down_fraction
10
11
  expr: |
11
- count by (name, type, probe_target) (upright_probe_up == 0)
12
+ (
13
+ count by (name, type, probe_target, alert_severity, probe_service) (upright_probe_up == 0)
14
+ or
15
+ count by (name, type, probe_target, alert_severity, probe_service) (upright_probe_up) * 0
16
+ )
12
17
  /
13
- count by (name, type, probe_target) (upright_probe_up)
18
+ count by (name, type, probe_target, alert_severity, probe_service) (upright_probe_up)
14
19
 
15
20
  # Daily uptime percentage (0.0 to 1.0)
16
21
  # Uptime = percentage of time in past day when majority of sites reported UP
22
+ # Keep probe_service in the grouping so ProbeRollup can tie rollups back
23
+ # to their Service; seed-prometheus emits the same label set.
17
24
  - record: upright:probe_uptime_daily
18
25
  expr: |
19
26
  avg_over_time((
20
- sum by (name, type, probe_target) (upright_probe_up)
27
+ sum by (name, type, probe_target, alert_severity, probe_service) (upright_probe_up)
21
28
  /
22
- count by (name, type, probe_target) (upright_probe_up)
29
+ count by (name, type, probe_target, alert_severity, probe_service) (upright_probe_up)
23
30
  > bool 0.5
24
31
  )[1d:])
25
32
 
@@ -148,7 +155,7 @@ groups:
148
155
  upright: "https://app.<%= app_domain %>/?probe_type={{ $labels.type }}&status=fail&probe_name={{ $labels.name | urlquery }}"
149
156
  expr: upright:probe_down_fraction{type="http"} > 0.5
150
157
  labels:
151
- severity: page
158
+ severity: "{{ $labels.alert_severity }}"
152
159
  group: upright
153
160
 
154
161
  - alert: UprightHTTPProbeDegraded
@@ -187,7 +194,7 @@ groups:
187
194
  upright: "https://app.<%= app_domain %>/?probe_type={{ $labels.type }}&status=fail&probe_name={{ $labels.name | urlquery }}"
188
195
  expr: upright:probe_down_fraction{type="smtp"} > 0.5
189
196
  labels:
190
- severity: page
197
+ severity: "{{ $labels.alert_severity }}"
191
198
  group: upright
192
199
 
193
200
  - alert: UprightSMTPProbeDegraded
@@ -226,7 +233,7 @@ groups:
226
233
  upright: "https://app.<%= app_domain %>/?probe_type={{ $labels.type }}&status=fail&probe_name={{ $labels.name | urlquery }}"
227
234
  expr: upright:probe_down_fraction{type="playwright"} > 0.5
228
235
  labels:
229
- severity: page
236
+ severity: "{{ $labels.alert_severity }}"
230
237
  group: upright
231
238
 
232
239
  - alert: UprightPlaywrightProbeDegraded