diff --git a/.env.example b/.env.example index 8b9a630..8cb0bfe 100644 --- a/.env.example +++ b/.env.example @@ -20,3 +20,5 @@ MAIL_TO=ie@asxp.io # Datacenter bingo hostname (same process, routed by Host). Unset = bingo off. # BINGO_HOST=bingo.localhost +# Optional second hostname serving the same card. +# BINGO_ALT_HOST=bingo2.localhost diff --git a/.forgejo/workflows/deploy.yaml b/.forgejo/workflows/deploy.yaml index 2bc2ed3..1b469e0 100644 --- a/.forgejo/workflows/deploy.yaml +++ b/.forgejo/workflows/deploy.yaml @@ -140,6 +140,7 @@ jobs: LTC_ADDRESS=${LTC_ADDRESS} CRYPTO_ADDRESSES=${CRYPTO_ADDRESSES} BINGO_HOST=datacenterbingo.xyz + BINGO_ALT_HOST=bingo.asxp.io EOF chmod 600 /opt/asxpio/.env diff --git a/bingo_web.rb b/bingo_web.rb index c85cac2..600259c 100644 --- a/bingo_web.rb +++ b/bingo_web.rb @@ -2,13 +2,15 @@ # AsxpioWeb (config.ru dispatches by Host). Read-only: no session, no CSRF. # Content is managed from asxp.io/admin/bingo (lib/bingo_admin.rb). class BingoWeb < Sinatra::Base + HOSTS = [ENV['BINGO_HOST'], ENV['BINGO_ALT_HOST']].compact.reject(&:empty?).freeze + configure do set :root, $root set :views, File.join($root, 'views', 'bingo') set :erb, layout: :layout, escape_html: true set :show_exceptions, $env == 'development' set :host_authorization, - { permitted_hosts: $env == 'production' && ENV['BINGO_HOST'] ? [ENV['BINGO_HOST']] : [] } + { permitted_hosts: $env == 'production' ? HOSTS : [] } end helpers do diff --git a/config.ru b/config.ru index 3f1c1f3..7698a33 100644 --- a/config.ru +++ b/config.ru @@ -2,16 +2,19 @@ require 'sinatra/base' require './asxpio.rb' require './bingo_web.rb' -# One process, two sites: BINGO_HOST gets the bingo, everything else asxp.io. -asxpio = AsxpioWeb.new -bingo = BingoWeb.new -bingo_host = ENV['BINGO_HOST'] +# One process, two sites: the bingo hosts get BingoWeb, everything else +# asxp.io. BINGO_HOST is canonical (www. redirects to it); BINGO_ALT_HOST +# serves the same card, e.g. while the canonical domain's DNS settles. +asxpio = AsxpioWeb.new +bingo = BingoWeb.new +bingo_host = ENV['BINGO_HOST'] +bingo_hosts = BingoWeb::HOSTS run lambda { |env| req = Rack::Request.new(env) if bingo_host && req.host == "www.#{bingo_host}" [301, { 'location' => "https://#{bingo_host}#{req.fullpath}" }, []] else - (bingo_host && req.host == bingo_host ? bingo : asxpio).call(env) + (bingo_hosts.include?(req.host) ? bingo : asxpio).call(env) end } diff --git a/docker-compose.yml b/docker-compose.yml index 9a22d6a..8ee718f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -19,8 +19,9 @@ services: - "traefik.http.routers.asxpio.service=asxpio" - "traefik.http.services.asxpio.loadbalancer.server.port=3000" # Datacenter bingo: same container, routed by Host in config.ru. - # BINGO_HOST is interpolated from /opt/asxpio/.env (written by deploy). - - "traefik.http.routers.asxpio-bingo.rule=Host(`${BINGO_HOST}`) || Host(`www.${BINGO_HOST}`)" + # BINGO_HOST / BINGO_ALT_HOST are interpolated from /opt/asxpio/.env + # (written by deploy). + - "traefik.http.routers.asxpio-bingo.rule=Host(`${BINGO_HOST}`) || Host(`www.${BINGO_HOST}`) || Host(`${BINGO_ALT_HOST}`)" - "traefik.http.routers.asxpio-bingo.entrypoints=websecure" - "traefik.http.routers.asxpio-bingo.tls=true" - "traefik.http.routers.asxpio-bingo.tls.certresolver=letsencrypt" diff --git a/views/bingo/index.erb b/views/bingo/index.erb index 681f008..bf809f1 100644 --- a/views/bingo/index.erb +++ b/views/bingo/index.erb @@ -62,7 +62,10 @@ - + <% b.cells.each do |dc| %> diff --git a/views/bingo/layout.erb b/views/bingo/layout.erb index af36788..5a1d402 100644 --- a/views/bingo/layout.erb +++ b/views/bingo/layout.erb @@ -93,6 +93,7 @@ dialog ul { padding-left: 18px; } dialog button { font: inherit; padding: 6px 14px; border-radius: 6px; border: 1px solid var(--line); background: var(--bg); color: var(--ink); cursor: pointer; } footer { margin-top: 32px; color: var(--muted); font-size: 13px; } + footer p { margin: 0 0 4px; } @media (max-width: 560px) { .cell { padding: 5px; border-radius: 6px; } .cell .sub, .cell .n, .cell .tag { display: none; }