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.
- checksums.yaml +4 -4
- data/README.md +208 -56
- data/Rakefile +22 -0
- data/app/assets/images/upright/.keep +0 -0
- data/app/assets/stylesheets/upright/_global.css +2 -0
- data/app/assets/stylesheets/upright/artifact.css +7 -25
- data/app/assets/stylesheets/upright/flash.css +19 -0
- data/app/assets/stylesheets/upright/header.css +14 -0
- data/app/assets/stylesheets/upright/incidents.css +610 -0
- data/app/assets/stylesheets/upright/map.css +6 -0
- data/app/assets/stylesheets/upright/public_status.css +534 -0
- data/app/assets/stylesheets/upright/uptime-bars.css +10 -1
- data/app/controllers/concerns/.keep +0 -0
- data/app/controllers/concerns/upright/authentication.rb +1 -0
- data/app/controllers/concerns/upright/proxy_authentication.rb +134 -0
- data/app/controllers/upright/alertmanager_proxy_controller.rb +12 -4
- data/app/controllers/upright/artifacts_controller.rb +1 -1
- data/app/controllers/upright/incidents/updates_controller.rb +13 -0
- data/app/controllers/upright/incidents_controller.rb +53 -0
- data/app/controllers/upright/probe_results_controller.rb +1 -0
- data/app/controllers/upright/prometheus_proxy_controller.rb +13 -18
- data/app/controllers/upright/public/base_controller.rb +16 -0
- data/app/controllers/upright/public/incidents_controller.rb +6 -0
- data/app/controllers/upright/public/service_incidents_controller.rb +7 -0
- data/app/controllers/upright/public/services_controller.rb +6 -0
- data/app/controllers/upright/sessions_controller.rb +37 -1
- data/app/controllers/upright/traces_controller.rb +88 -0
- data/app/helpers/upright/application_helper.rb +22 -2
- data/app/helpers/upright/artifacts_helper.rb +20 -0
- data/app/helpers/upright/probe_results_helper.rb +4 -10
- data/app/helpers/upright/public/services_helper.rb +70 -0
- data/app/javascript/upright/application.js +3 -0
- data/app/javascript/upright/controllers/sites_map_controller.js +13 -5
- data/app/javascript/upright/controllers/template_controller.js +11 -0
- data/app/javascript/upright/public.js +5 -0
- data/app/jobs/upright/health_metrics_job.rb +27 -0
- data/app/jobs/upright/incident_reporter_job.rb +7 -0
- data/app/jobs/upright/maintenance_advance_job.rb +8 -0
- data/app/jobs/upright/playwright_video_sweep_job.rb +21 -0
- data/app/jobs/upright/rollups/daily_aggregation_job.rb +14 -0
- data/app/models/concerns/.keep +0 -0
- data/app/models/concerns/upright/incidents/auto_reporting.rb +93 -0
- data/app/models/concerns/upright/incidents/lifecycle.rb +47 -0
- data/app/models/concerns/upright/playwright/form_authentication.rb +3 -5
- data/app/models/concerns/upright/playwright/lifecycle.rb +8 -13
- data/app/models/concerns/upright/playwright/trace_recording.rb +53 -0
- data/app/models/concerns/upright/playwright/video_recording.rb +55 -14
- data/app/models/concerns/upright/probe_result/stale_cleanup.rb +47 -0
- data/app/models/concerns/upright/probeable.rb +16 -2
- data/app/models/concerns/upright/services/live_status.rb +69 -0
- data/app/models/concerns/upright/services/maintenance_status.rb +19 -0
- data/app/models/upright/artifact.rb +2 -1
- data/app/models/upright/http/request.rb +13 -4
- data/app/models/upright/incident/automatic_report.rb +3 -0
- data/app/models/upright/incident.rb +87 -0
- data/app/models/upright/incident_affected_service.rb +9 -0
- data/app/models/upright/incident_update.rb +7 -0
- data/app/models/upright/maintenance.rb +36 -0
- data/app/models/upright/persistent_record.rb +12 -0
- data/app/models/upright/playwright/authenticator/base.rb +50 -70
- data/app/models/upright/probe_result.rb +6 -1
- data/app/models/upright/probes/http_probe.rb +30 -1
- data/app/models/upright/probes/playwright/base.rb +2 -0
- data/app/models/upright/probes/status/probe.rb +8 -0
- data/app/models/upright/probes/status.rb +5 -13
- data/app/models/upright/probes/uptime.rb +5 -12
- data/app/models/upright/rollups/daily_uptime.rb +28 -0
- data/app/models/upright/rollups/probe_rollup.rb +64 -0
- data/app/models/upright/rollups/probe_uptime.rb +9 -0
- data/app/models/upright/rollups/site_uptime.rb +75 -0
- data/app/models/upright/service/daily_status.rb +39 -0
- data/app/models/upright/service/incident_history.rb +24 -0
- data/app/models/upright/service/outage.rb +15 -0
- data/app/models/upright/service/status_page.rb +35 -0
- data/app/models/upright/service.rb +101 -0
- data/app/models/upright/status.rb +21 -0
- data/app/models/upright/traceroute/ip_metadata_lookup.rb +21 -2
- data/app/models/upright/traceroute/result.rb +8 -1
- data/app/views/layouts/upright/_header.html.erb +12 -2
- data/app/views/layouts/upright/application.html.erb +4 -1
- data/app/views/layouts/upright/public.html.erb +18 -0
- data/app/views/layouts/upright/public.rss.builder +1 -0
- data/app/views/upright/active_storage/attachments/_attachment.html.erb +1 -1
- data/app/views/upright/artifacts/show.html.erb +8 -0
- data/app/views/upright/dashboards/_uptime_probe_row.html.erb +5 -3
- data/app/views/upright/dashboards/probe_statuses/show.html.erb +1 -1
- data/app/views/upright/dashboards/uptimes/show.html.erb +1 -1
- data/app/views/upright/incidents/_form.html.erb +93 -0
- data/app/views/upright/incidents/_row.html.erb +15 -0
- data/app/views/upright/incidents/edit.html.erb +64 -0
- data/app/views/upright/incidents/index.html.erb +53 -0
- data/app/views/upright/incidents/new.html.erb +6 -0
- data/app/views/upright/probe_results/index.html.erb +7 -4
- data/app/views/upright/public/_branding.html.erb +3 -0
- data/app/views/upright/public/_footer.html.erb +3 -0
- data/app/views/upright/public/incidents/show.html.erb +29 -0
- data/app/views/upright/public/service_incidents/_incident.html.erb +14 -0
- data/app/views/upright/public/service_incidents/index.html.erb +50 -0
- data/app/views/upright/public/services/_active_events.html.erb +28 -0
- data/app/views/upright/public/services/_degraded_list.html.erb +14 -0
- data/app/views/upright/public/services/_overall_banner.html.erb +4 -0
- data/app/views/upright/public/services/_service.html.erb +34 -0
- data/app/views/upright/public/services/_upcoming_maintenance.html.erb +21 -0
- data/app/views/upright/public/services/_uptime_bar.html.erb +9 -0
- data/app/views/upright/public/services/index.html.erb +13 -0
- data/app/views/upright/public/services/index.rss.builder +29 -0
- data/app/views/upright/sessions/new.html.erb +14 -1
- data/config/ci.rb +2 -0
- data/config/importmap.rb +25 -14
- data/config/initializers/date_formats.rb +5 -0
- data/config/initializers/mime_types.rb +1 -0
- data/config/routes.rb +26 -2
- data/db/migrate/20260629000001_fix_probe_result_duration_precision.rb +14 -0
- data/db/persistent_migrate/20260512000001_create_upright_rollups.rb +16 -0
- data/db/persistent_migrate/20260702000001_create_upright_incidents.rb +34 -0
- data/db/persistent_migrate/20260707000001_add_probe_identity_to_upright_rollups.rb +11 -0
- data/db/persistent_migrate/20260709000001_add_created_by_to_incidents.rb +7 -0
- data/db/persistent_migrate/20260901000001_add_auto_reporting_to_upright_incidents.rb +22 -0
- data/lib/generators/upright/install/database_config.rb +46 -0
- data/lib/generators/upright/install/install_generator.rb +52 -3
- data/lib/generators/upright/install/templates/Dockerfile +80 -0
- data/lib/generators/upright/install/templates/content_security_policy.rb +42 -0
- data/lib/generators/upright/install/templates/deploy.yml +13 -13
- data/lib/generators/upright/install/templates/development_prometheus.yml +1 -1
- data/lib/generators/upright/install/templates/docker-compose.yml +12 -11
- data/lib/generators/upright/install/templates/http_probes.yml +1 -0
- data/lib/generators/upright/install/templates/omniauth.rb +7 -3
- data/lib/generators/upright/install/templates/otel_collector.yml +6 -0
- data/lib/generators/upright/install/templates/recurring.yml +45 -0
- data/lib/generators/upright/install/templates/sites.yml +4 -0
- data/lib/generators/upright/install/templates/smtp_probes.yml +3 -0
- data/lib/generators/upright/install/templates/traceroute_probes.yml +3 -0
- data/lib/generators/upright/install/templates/upright.rb +24 -2
- data/lib/generators/upright/install/templates/upright.rules.yml +14 -7
- data/lib/upright/configuration.rb +127 -6
- data/lib/upright/engine.rb +32 -2
- data/lib/upright/metrics.rb +27 -2
- data/lib/upright/probe_type_registry.rb +33 -0
- data/lib/upright/site.rb +31 -1
- data/lib/upright/version.rb +3 -1
- data/lib/upright.rb +34 -2
- metadata +110 -7
- data/config/credentials/development.key +0 -1
- data/config/credentials/test.key +0 -1
- 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.
|
|
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
|
|
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 = \"
|
|
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
|
-
|
|
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
|
|
@@ -2,7 +2,14 @@ services:
|
|
|
2
2
|
prometheus:
|
|
3
3
|
image: prom/prometheus:v3.4.1
|
|
4
4
|
container_name: <%= app_name %>-prometheus
|
|
5
|
-
|
|
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
|
-
|
|
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
|
-
#
|
|
2
|
-
#
|
|
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" =>
|
|
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
|
|
9
|
-
# config.
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
236
|
+
severity: "{{ $labels.alert_severity }}"
|
|
230
237
|
group: upright
|
|
231
238
|
|
|
232
239
|
- alert: UprightPlaywrightProbeDegraded
|