├── log └── .keep ├── app ├── mailers │ └── .keep ├── models │ ├── .keep │ ├── concerns │ │ └── .keep │ ├── application_record.rb │ ├── penalty.rb │ ├── scoreboard.rb │ ├── service.rb │ ├── ticket.rb │ ├── capture.rb │ ├── event.rb │ ├── timer.rb │ ├── service_state.rb │ ├── shell_process.rb │ ├── dingus.rb │ ├── instance.rb │ ├── round_finalizer.rb │ ├── replacement.rb │ ├── token_redistributor.rb │ ├── flag.rb │ ├── team.rb │ ├── redemption.rb │ ├── availability_check.rb │ └── availability.rb ├── assets │ ├── images │ │ ├── .keep │ │ ├── purple-linen.png │ │ ├── crinkled-paper-05x.jpg │ │ └── crinkled-paper-1x.jpg │ ├── stylesheets │ │ ├── scene.sass │ │ ├── nav.sass │ │ ├── howto.sass │ │ ├── tickets.sass │ │ ├── timers.css.scss │ │ ├── defaults.sass │ │ ├── application.sass │ │ ├── mixins.sass │ │ ├── admin.sass │ │ ├── replacements.sass │ │ ├── banner.sass │ │ ├── fonts.sass │ │ ├── dashboard.sass │ │ ├── color.sass │ │ └── scoreboard.sass │ ├── fonts │ │ ├── Michroma.ttf │ │ ├── Ubuntu-B.ttf │ │ ├── Ubuntu-BI.ttf │ │ ├── Ubuntu-C.ttf │ │ ├── Ubuntu-L.ttf │ │ ├── Ubuntu-LI.ttf │ │ ├── Ubuntu-M.ttf │ │ ├── Ubuntu-MI.ttf │ │ ├── Ubuntu-R.ttf │ │ ├── Ubuntu-RI.ttf │ │ ├── Rambla-Bold.ttf │ │ ├── UbuntuMono-B.ttf │ │ ├── UbuntuMono-R.ttf │ │ ├── Rambla-Italic.ttf │ │ ├── Rambla-Regular.ttf │ │ ├── UbuntuMono-BI.ttf │ │ ├── UbuntuMono-RI.ttf │ │ ├── PermanentMarker.ttf │ │ ├── Rambla-BoldItalic.ttf │ │ └── UbuntuMono-Regular.ttf │ └── javascripts │ │ ├── dashboard.js.coffee │ │ ├── tickets.js.coffee │ │ ├── replacements.coffee │ │ ├── application.js │ │ ├── scoreboard.js.coffee │ │ └── timers.js.coffee ├── controllers │ ├── concerns │ │ └── .keep │ ├── admin │ │ ├── root_controller.rb │ │ ├── base_controller.rb │ │ ├── rounds_controller.rb │ │ ├── redemptions_controller.rb │ │ ├── tokens_controller.rb │ │ ├── availabilities_controller.rb │ │ ├── services_controller.rb │ │ ├── instances_controller.rb │ │ ├── teams_controller.rb │ │ ├── services │ │ │ └── availabilities_controller.rb │ │ └── replacements_controller.rb │ ├── timers_controller.rb │ ├── dashboard_controller.rb │ ├── scoreboard_controller.rb │ ├── livectf_controller.rb │ ├── rounds_controller.rb │ ├── redemption_controller.rb │ ├── application_controller.rb │ ├── tickets_controller.rb │ └── replacements_controller.rb ├── helpers │ ├── tickets_helper.rb │ ├── timers_helper.rb │ ├── dashboard_helper.rb │ ├── scoreboard_helper.rb │ ├── replacements_helper.rb │ ├── admin_helper.rb │ └── application_helper.rb └── views │ ├── pages │ └── clouds.html.haml │ ├── tickets │ ├── new.html.haml │ ├── edit.html.haml │ ├── _form.html.haml │ ├── show.html.haml │ └── index.html.haml │ ├── admin │ ├── replacements │ │ ├── new.html.haml │ │ ├── edit.html.haml │ │ ├── _form.html.haml │ │ ├── index.html.haml │ │ └── show.html.haml │ ├── teams │ │ ├── edit.html.haml │ │ ├── index.html.haml │ │ └── show.html.haml │ ├── services │ │ ├── edit.html.haml │ │ ├── index.html.haml │ │ ├── availabilities │ │ │ └── index.html.haml │ │ └── show.html.haml │ ├── base │ │ └── _admin_nav.html.haml │ ├── instances │ │ ├── index.html.haml │ │ └── show.html.haml │ ├── root │ │ └── index.html.haml │ ├── tokens │ │ ├── index.html.haml │ │ └── show.html.haml │ ├── redemptions │ │ ├── index.html.haml │ │ └── show.html.haml │ ├── rounds │ │ ├── index.html.haml │ │ └── show.html.haml │ └── availabilities │ │ ├── index.html.haml │ │ └── show.html.haml │ ├── shared │ ├── _banner.html.haml │ └── _timers.html.haml │ ├── replacements │ ├── show.html.haml │ ├── new.html.haml │ └── index.html.haml │ ├── layouts │ ├── admin.html.haml │ └── application.html.haml │ ├── scoreboard │ └── index.html.haml │ └── dashboard │ └── index.html.haml ├── lib ├── assets │ └── .keep └── tasks │ ├── .keep │ ├── ca.rake │ ├── db.rake │ └── scoreboard.rake ├── public ├── favicon.ico ├── robots.txt ├── 500.html ├── 422.html └── 404.html ├── scripts ├── .gitkeep ├── rubix │ ├── rnd.so │ └── deposit ├── babyecho │ └── deposit ├── half │ └── deposit ├── internet3 │ └── deposit ├── legitbbs │ └── deposit ├── quarter │ └── deposit ├── trackerd │ └── deposit ├── babysfirst │ └── deposit └── picturemgr │ └── deposit ├── test ├── helpers │ ├── .keep │ ├── timers_helper_test.rb │ ├── tickets_helper_test.rb │ ├── dashboard_helper_test.rb │ └── scoreboard_helper_test.rb ├── mailers │ └── .keep ├── models │ ├── .keep │ ├── service_test.rb │ ├── flag_test.rb │ ├── timer_test.rb │ ├── penalty_test.rb │ ├── ticket_test.rb │ ├── replacement_test.rb │ ├── instance_test.rb │ ├── team_test.rb │ ├── capture_test.rb │ ├── redemption_test.rb │ ├── round_test.rb │ ├── availability_test.rb │ ├── round_finalizer_test.rb │ ├── token_test.rb │ └── availability_check_test.rb ├── controllers │ ├── .keep │ ├── timers_controller_test.rb │ ├── dashboard_controller_test.rb │ ├── scoreboard_controller_test.rb │ ├── replacements_controller_test.rb │ └── redemption_controller_test.rb ├── integration │ └── .keep ├── application_system_test_case.rb ├── factories │ ├── penalties.rb │ └── tickets.rb ├── system │ └── replacements_test.rb ├── test_helper.rb └── factories.rb ├── vendor └── assets │ ├── javascripts │ └── .keep │ └── stylesheets │ └── .keep ├── config ├── initializers │ ├── vegas_time.rb │ ├── session_store.rb │ ├── mime_types.rb │ ├── redis.rb │ ├── application_controller_renderer.rb │ ├── filter_parameter_logging.rb │ ├── cookies_serializer.rb │ ├── backtrace_silencers.rb │ ├── wrap_parameters.rb │ ├── new_framework_defaults_5_1.rb │ ├── secret_token.rb │ ├── inflections.rb │ └── assets.rb ├── spring.rb ├── boot.rb ├── environment.rb ├── cable.yml ├── application.rb ├── locales │ └── en.yml ├── routes.rb ├── secrets.yml ├── deploy │ ├── staging.rb │ └── production.rb ├── database.yml ├── environments │ ├── test.rb │ └── development.rb ├── deploy.rb └── puma.rb ├── yarn.lock ├── bin ├── rake ├── bundle ├── rails ├── 2014-final-calculation ├── yarn ├── copy_scripts ├── ssh_a_bunch ├── 2014-day3-downtime ├── 2014-w3stormz-badge ├── 2017-day1-downtime ├── redistribution-reprocess ├── update ├── 2014-day2-downtime ├── 2015-day3-downtime ├── 2015-day3-livectf ├── 2017-day2-downtime ├── setup ├── lazertanz └── state_monitor ├── db └── migrate │ ├── 20140625050618_drop_messages_table.rb │ ├── 20130707040802_add_memo_to_tokens.rb │ ├── 20140705223004_add_nonce_to_round.rb │ ├── 20130720204802_add_address_to_team.rb │ ├── 20150627042831_add_display_to_teams.rb │ ├── 20130714215448_add_status_to_tokens.rb │ ├── 20130719021347_add_uuid_to_redemption.rb │ ├── 20130801204109_add_joe_name_to_teams.rb │ ├── 20130801191327_add_ended_at_to_rounds.rb │ ├── 20130801191345_add_enabled_to_services.rb │ ├── 20170728173501_add_port_to_services.rb │ ├── 20130706202136_add_memo_to_availability.rb │ ├── 20140719141614_add_service_id_to_flags.rb │ ├── 20130706194328_create_rounds.rb │ ├── 20140727220807_add_distribution_to_rounds.rb │ ├── 20130714214756_add_status_to_availabilities.rb │ ├── 20140802013457_add_redemptions_count_to_tokens.rb │ ├── 20140802151953_remove_joe_name_from_teams.rb │ ├── 20140808014759_dingus_is_binary.rb │ ├── 20130706170819_add_certname_to_teams.rb │ ├── 20130706185014_create_services.rb │ ├── 20130706185612_create_flags.rb │ ├── 20140705225220_add_payload_and_signature_to_rounds.rb │ ├── 20130727182203_create_timers.rb │ ├── 20130728180622_create_messages.rb │ ├── 20130629024425_create_teams.rb │ ├── 20130706194342_add_round_to_scoring_models.rb │ ├── 20130706185532_create_tokens.rb │ ├── 20130706194306_create_captures.rb │ ├── 20130707034729_create_instances.rb │ ├── 20140624210737_create_tickets.rb │ ├── 20130707040633_index_round_and_instance_on_scoring_models.rb │ ├── 20130706194517_create_availabilities.rb │ ├── 20140726191136_add_dinguses_to_availabilities.rb │ ├── 20140808010216_make_memo_columns_binary.rb │ ├── 20140727221954_create_penalties.rb │ ├── 20130706183611_create_redemptions.rb │ ├── 20170723131656_create_replacements.rb │ ├── 20170723153202_replacement_indexes.rb │ ├── 20140805223411_add_failed_counters_to_teams.rb │ ├── 20150516180242_scorebaord_materialized_view.rb │ └── 20140807162135_forbid_nulls.rb ├── config.ru ├── pg_dump.sh ├── Rakefile ├── puma.conf ├── Dockerfile ├── .gitignore ├── README.rdoc ├── Capfile ├── docker-compose.yml └── Gemfile /log/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/mailers/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/models/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/assets/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/tasks/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /scripts/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/helpers/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/mailers/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/models/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/assets/images/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/controllers/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/integration/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/models/concerns/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/controllers/concerns/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vendor/assets/javascripts/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vendor/assets/stylesheets/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/helpers/tickets_helper.rb: -------------------------------------------------------------------------------- 1 | module TicketsHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/helpers/timers_helper.rb: -------------------------------------------------------------------------------- 1 | module TimersHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/helpers/dashboard_helper.rb: -------------------------------------------------------------------------------- 1 | module DashboardHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/helpers/scoreboard_helper.rb: -------------------------------------------------------------------------------- 1 | module ScoreboardHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/helpers/replacements_helper.rb: -------------------------------------------------------------------------------- 1 | module ReplacementsHelper 2 | end 3 | -------------------------------------------------------------------------------- /scripts/rubix/rnd.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/legitbs/scorebot/HEAD/scripts/rubix/rnd.so -------------------------------------------------------------------------------- /app/assets/stylesheets/scene.sass: -------------------------------------------------------------------------------- 1 | .content 2 | width: 960px 3 | margin: 0 auto 4 | z-index: 1 5 | -------------------------------------------------------------------------------- /app/assets/fonts/Michroma.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/legitbs/scorebot/HEAD/app/assets/fonts/Michroma.ttf -------------------------------------------------------------------------------- /app/assets/fonts/Ubuntu-B.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/legitbs/scorebot/HEAD/app/assets/fonts/Ubuntu-B.ttf -------------------------------------------------------------------------------- /app/assets/fonts/Ubuntu-BI.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/legitbs/scorebot/HEAD/app/assets/fonts/Ubuntu-BI.ttf -------------------------------------------------------------------------------- /app/assets/fonts/Ubuntu-C.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/legitbs/scorebot/HEAD/app/assets/fonts/Ubuntu-C.ttf -------------------------------------------------------------------------------- /app/assets/fonts/Ubuntu-L.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/legitbs/scorebot/HEAD/app/assets/fonts/Ubuntu-L.ttf -------------------------------------------------------------------------------- /app/assets/fonts/Ubuntu-LI.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/legitbs/scorebot/HEAD/app/assets/fonts/Ubuntu-LI.ttf -------------------------------------------------------------------------------- /app/assets/fonts/Ubuntu-M.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/legitbs/scorebot/HEAD/app/assets/fonts/Ubuntu-M.ttf -------------------------------------------------------------------------------- /app/assets/fonts/Ubuntu-MI.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/legitbs/scorebot/HEAD/app/assets/fonts/Ubuntu-MI.ttf -------------------------------------------------------------------------------- /app/assets/fonts/Ubuntu-R.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/legitbs/scorebot/HEAD/app/assets/fonts/Ubuntu-R.ttf -------------------------------------------------------------------------------- /app/assets/fonts/Ubuntu-RI.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/legitbs/scorebot/HEAD/app/assets/fonts/Ubuntu-RI.ttf -------------------------------------------------------------------------------- /app/views/pages/clouds.html.haml: -------------------------------------------------------------------------------- 1 | - content_for :scene do 2 | .layer#cloudbase 3 | .layer#cloudhighlight 4 | -------------------------------------------------------------------------------- /config/initializers/vegas_time.rb: -------------------------------------------------------------------------------- 1 | Time.zone = 'America/Los_Angeles' 2 | Time::DATE_FORMATS[:ctf] = '%a %R' 3 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/assets/fonts/Rambla-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/legitbs/scorebot/HEAD/app/assets/fonts/Rambla-Bold.ttf -------------------------------------------------------------------------------- /app/assets/fonts/UbuntuMono-B.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/legitbs/scorebot/HEAD/app/assets/fonts/UbuntuMono-B.ttf -------------------------------------------------------------------------------- /app/assets/fonts/UbuntuMono-R.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/legitbs/scorebot/HEAD/app/assets/fonts/UbuntuMono-R.ttf -------------------------------------------------------------------------------- /app/views/tickets/new.html.haml: -------------------------------------------------------------------------------- 1 | %h1 New ticket 2 | 3 | = render 'form' 4 | 5 | = link_to 'Back', tickets_path 6 | -------------------------------------------------------------------------------- /bin/rake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require_relative '../config/boot' 3 | require 'rake' 4 | Rake.application.run 5 | -------------------------------------------------------------------------------- /app/assets/fonts/Rambla-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/legitbs/scorebot/HEAD/app/assets/fonts/Rambla-Italic.ttf -------------------------------------------------------------------------------- /app/assets/fonts/Rambla-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/legitbs/scorebot/HEAD/app/assets/fonts/Rambla-Regular.ttf -------------------------------------------------------------------------------- /app/assets/fonts/UbuntuMono-BI.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/legitbs/scorebot/HEAD/app/assets/fonts/UbuntuMono-BI.ttf -------------------------------------------------------------------------------- /app/assets/fonts/UbuntuMono-RI.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/legitbs/scorebot/HEAD/app/assets/fonts/UbuntuMono-RI.ttf -------------------------------------------------------------------------------- /app/assets/images/purple-linen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/legitbs/scorebot/HEAD/app/assets/images/purple-linen.png -------------------------------------------------------------------------------- /app/models/application_record.rb: -------------------------------------------------------------------------------- 1 | class ApplicationRecord < ActiveRecord::Base 2 | self.abstract_class = true 3 | end 4 | -------------------------------------------------------------------------------- /test/helpers/timers_helper_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class TimersHelperTest < ActionView::TestCase 4 | end 5 | -------------------------------------------------------------------------------- /app/assets/fonts/PermanentMarker.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/legitbs/scorebot/HEAD/app/assets/fonts/PermanentMarker.ttf -------------------------------------------------------------------------------- /app/assets/fonts/Rambla-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/legitbs/scorebot/HEAD/app/assets/fonts/Rambla-BoldItalic.ttf -------------------------------------------------------------------------------- /test/helpers/tickets_helper_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class TicketsHelperTest < ActionView::TestCase 4 | end 5 | -------------------------------------------------------------------------------- /app/assets/fonts/UbuntuMono-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/legitbs/scorebot/HEAD/app/assets/fonts/UbuntuMono-Regular.ttf -------------------------------------------------------------------------------- /app/assets/images/crinkled-paper-05x.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/legitbs/scorebot/HEAD/app/assets/images/crinkled-paper-05x.jpg -------------------------------------------------------------------------------- /app/assets/images/crinkled-paper-1x.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/legitbs/scorebot/HEAD/app/assets/images/crinkled-paper-1x.jpg -------------------------------------------------------------------------------- /app/assets/javascripts/dashboard.js.coffee: -------------------------------------------------------------------------------- 1 | jQuery ($) -> 2 | $('#redemption_form').submit -> 3 | $('#redeem_target').show() 4 | -------------------------------------------------------------------------------- /app/controllers/admin/root_controller.rb: -------------------------------------------------------------------------------- 1 | class Admin::RootController < Admin::BaseController 2 | def index 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /test/helpers/dashboard_helper_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class DashboardHelperTest < ActionView::TestCase 4 | end 5 | -------------------------------------------------------------------------------- /test/helpers/scoreboard_helper_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class ScoreboardHelperTest < ActionView::TestCase 4 | end 5 | -------------------------------------------------------------------------------- /app/views/admin/replacements/new.html.haml: -------------------------------------------------------------------------------- 1 | %h1 New replacement 2 | 3 | = render 'form' 4 | 5 | = link_to 'Back', admin_replacements_path 6 | -------------------------------------------------------------------------------- /app/models/penalty.rb: -------------------------------------------------------------------------------- 1 | class Penalty < ActiveRecord::Base 2 | belongs_to :availability 3 | belongs_to :team 4 | belongs_to :flag 5 | end 6 | -------------------------------------------------------------------------------- /test/models/service_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class ServiceTest < ActiveSupport::TestCase 4 | should have_many :instances 5 | end 6 | -------------------------------------------------------------------------------- /bin/bundle: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) 3 | load Gem.bin_path('bundler', 'bundle') 4 | -------------------------------------------------------------------------------- /config/spring.rb: -------------------------------------------------------------------------------- 1 | %w( 2 | .ruby-version 3 | .rbenv-vars 4 | tmp/restart.txt 5 | tmp/caching-dev.txt 6 | ).each { |path| Spring.watch(path) } 7 | -------------------------------------------------------------------------------- /app/views/tickets/edit.html.haml: -------------------------------------------------------------------------------- 1 | %h1 Editing ticket 2 | 3 | = render 'form' 4 | 5 | = link_to 'Show', @ticket 6 | \| 7 | = link_to 'Back', tickets_path 8 | -------------------------------------------------------------------------------- /config/boot.rb: -------------------------------------------------------------------------------- 1 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) 2 | 3 | require 'bundler/setup' # Set up gems listed in the Gemfile. 4 | -------------------------------------------------------------------------------- /app/controllers/admin/base_controller.rb: -------------------------------------------------------------------------------- 1 | class Admin::BaseController < ApplicationController 2 | before_action :require_legitbs 3 | layout 'admin' 4 | end 5 | -------------------------------------------------------------------------------- /bin/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | APP_PATH = File.expand_path('../config/application', __dir__) 3 | require_relative '../config/boot' 4 | require 'rails/commands' 5 | -------------------------------------------------------------------------------- /db/migrate/20140625050618_drop_messages_table.rb: -------------------------------------------------------------------------------- 1 | class DropMessagesTable < ActiveRecord::Migration 2 | def change 3 | drop_table :messages 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /config/environment.rb: -------------------------------------------------------------------------------- 1 | # Load the Rails application. 2 | require_relative 'application' 3 | 4 | # Initialize the Rails application. 5 | Rails.application.initialize! 6 | -------------------------------------------------------------------------------- /test/models/flag_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class FlagTest < ActiveSupport::TestCase 4 | should belong_to :team 5 | should have_many :captures 6 | end 7 | -------------------------------------------------------------------------------- /test/models/timer_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class TimerTest < ActiveSupport::TestCase 4 | # test "the truth" do 5 | # assert true 6 | # end 7 | end 8 | -------------------------------------------------------------------------------- /db/migrate/20130707040802_add_memo_to_tokens.rb: -------------------------------------------------------------------------------- 1 | class AddMemoToTokens < ActiveRecord::Migration 2 | def change 3 | add_column :tokens, :memo, :text 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20140705223004_add_nonce_to_round.rb: -------------------------------------------------------------------------------- 1 | class AddNonceToRound < ActiveRecord::Migration 2 | def change 3 | add_column :rounds, :nonce, :string 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /test/models/penalty_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class PenaltyTest < ActiveSupport::TestCase 4 | # test "the truth" do 5 | # assert true 6 | # end 7 | end 8 | -------------------------------------------------------------------------------- /test/models/ticket_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class TicketTest < ActiveSupport::TestCase 4 | # test "the truth" do 5 | # assert true 6 | # end 7 | end 8 | -------------------------------------------------------------------------------- /config.ru: -------------------------------------------------------------------------------- 1 | # This file is used by Rack-based servers to start the application. 2 | 3 | require ::File.expand_path('../config/environment', __FILE__) 4 | run Rails.application 5 | -------------------------------------------------------------------------------- /db/migrate/20130720204802_add_address_to_team.rb: -------------------------------------------------------------------------------- 1 | class AddAddressToTeam < ActiveRecord::Migration 2 | def change 3 | add_column :teams, :address, :string 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20150627042831_add_display_to_teams.rb: -------------------------------------------------------------------------------- 1 | class AddDisplayToTeams < ActiveRecord::Migration 2 | def change 3 | add_column :teams, :display, :string 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20130714215448_add_status_to_tokens.rb: -------------------------------------------------------------------------------- 1 | class AddStatusToTokens < ActiveRecord::Migration 2 | def change 3 | add_column :tokens, :status, :integer 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20130719021347_add_uuid_to_redemption.rb: -------------------------------------------------------------------------------- 1 | class AddUuidToRedemption < ActiveRecord::Migration 2 | def change 3 | add_column :redemptions, :uuid, :uuid 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20130801204109_add_joe_name_to_teams.rb: -------------------------------------------------------------------------------- 1 | class AddJoeNameToTeams < ActiveRecord::Migration 2 | def change 3 | add_column :teams, :joe_name, :string 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /test/models/replacement_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class ReplacementTest < ActiveSupport::TestCase 4 | # test "the truth" do 5 | # assert true 6 | # end 7 | end 8 | -------------------------------------------------------------------------------- /config/initializers/session_store.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | Scorebot::Application.config.session_store :cookie_store, key: '_scorebot_session' 4 | -------------------------------------------------------------------------------- /db/migrate/20130801191327_add_ended_at_to_rounds.rb: -------------------------------------------------------------------------------- 1 | class AddEndedAtToRounds < ActiveRecord::Migration 2 | def change 3 | add_column :rounds, :ended_at, :datetime 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20130801191345_add_enabled_to_services.rb: -------------------------------------------------------------------------------- 1 | class AddEnabledToServices < ActiveRecord::Migration 2 | def change 3 | add_column :services, :enabled, :boolean 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20170728173501_add_port_to_services.rb: -------------------------------------------------------------------------------- 1 | class AddPortToServices < ActiveRecord::Migration[5.1] 2 | def change 3 | add_column :services, :port, :integer 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /app/assets/stylesheets/nav.sass: -------------------------------------------------------------------------------- 1 | nav 2 | ul 3 | list-style-type: none 4 | padding: 0 5 | li 6 | display: inline-block 7 | a:link, a:visited 8 | @include button 9 | -------------------------------------------------------------------------------- /db/migrate/20130706202136_add_memo_to_availability.rb: -------------------------------------------------------------------------------- 1 | class AddMemoToAvailability < ActiveRecord::Migration 2 | def change 3 | add_column :availabilities, :memo, :text 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20140719141614_add_service_id_to_flags.rb: -------------------------------------------------------------------------------- 1 | class AddServiceIdToFlags < ActiveRecord::Migration 2 | def change 3 | add_reference :flags, :service, index: true 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /pg_dump.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | pg_dump -U postgres \ 4 | -h db \ 5 | -d scorebot_production \ 6 | -Fc \ 7 | -f $(date +/scorebot/tmp/dumps/%Y%m%d%H%M%S.pgdump) 8 | -------------------------------------------------------------------------------- /app/assets/stylesheets/howto.sass: -------------------------------------------------------------------------------- 1 | .howto 2 | @include prose-mode 3 | pre 4 | @include console 5 | code 6 | font-family: 'Ubuntu Mono', monospace 7 | color: $content_highlight 8 | -------------------------------------------------------------------------------- /app/views/admin/replacements/edit.html.haml: -------------------------------------------------------------------------------- 1 | %h1 Editing replacement 2 | 3 | = render 'form' 4 | 5 | = link_to 'Show', [:admin, @replacement] 6 | \| 7 | = link_to 'Back', admin_replacements_path 8 | -------------------------------------------------------------------------------- /db/migrate/20130706194328_create_rounds.rb: -------------------------------------------------------------------------------- 1 | class CreateRounds < ActiveRecord::Migration 2 | def change 3 | create_table :rounds do |t| 4 | 5 | t.timestamps 6 | end 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /db/migrate/20140727220807_add_distribution_to_rounds.rb: -------------------------------------------------------------------------------- 1 | class AddDistributionToRounds < ActiveRecord::Migration 2 | def change 3 | add_column :rounds, :distribution, :json 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /test/controllers/timers_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class TimersControllerTest < ActionController::TestCase 4 | # test "the truth" do 5 | # assert true 6 | # end 7 | end 8 | -------------------------------------------------------------------------------- /app/controllers/timers_controller.rb: -------------------------------------------------------------------------------- 1 | class TimersController < ApplicationController 2 | def index 3 | @timers = Timer.feed 4 | render json: {timers: @timers, time: Time.now} 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /config/initializers/mime_types.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new mime types for use in respond_to blocks: 4 | # Mime::Type.register "text/richtext", :rtf 5 | -------------------------------------------------------------------------------- /db/migrate/20130714214756_add_status_to_availabilities.rb: -------------------------------------------------------------------------------- 1 | class AddStatusToAvailabilities < ActiveRecord::Migration 2 | def change 3 | add_column :availabilities, :status, :integer 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /test/application_system_test_case.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class ApplicationSystemTestCase < ActionDispatch::SystemTestCase 4 | driven_by :selenium, using: :chrome, screen_size: [1400, 1400] 5 | end 6 | -------------------------------------------------------------------------------- /test/controllers/dashboard_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class DashboardControllerTest < ActionController::TestCase 4 | # test "the truth" do 5 | # assert true 6 | # end 7 | end 8 | -------------------------------------------------------------------------------- /db/migrate/20140802013457_add_redemptions_count_to_tokens.rb: -------------------------------------------------------------------------------- 1 | class AddRedemptionsCountToTokens < ActiveRecord::Migration 2 | def change 3 | add_column :tokens, :redemptions_count, :integer 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /app/assets/stylesheets/tickets.sass: -------------------------------------------------------------------------------- 1 | body.tickets 2 | .content 3 | @include prose-mode 4 | 5 | textarea#ticket_body 6 | width: 24em 7 | height: 4em 8 | 9 | div.ticket-body 10 | @include console 11 | -------------------------------------------------------------------------------- /app/models/scoreboard.rb: -------------------------------------------------------------------------------- 1 | class Scoreboard < ActiveRecord::Base 2 | def self.refresh! 3 | self.connection.execute <<-SQL 4 | REFRESH MATERIALIZED VIEW CONCURRENTLY scoreboard; 5 | SQL 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /config/cable.yml: -------------------------------------------------------------------------------- 1 | development: 2 | adapter: async 3 | 4 | test: 5 | adapter: async 6 | 7 | production: 8 | adapter: redis 9 | url: redis://localhost:6379/1 10 | channel_prefix: scorebot_production 11 | -------------------------------------------------------------------------------- /config/initializers/redis.rb: -------------------------------------------------------------------------------- 1 | redis_config = case Rails.env 2 | when 'production' 3 | { host: 'redis' } 4 | else 5 | { } 6 | end 7 | 8 | $redis = Redis.new redis_config 9 | -------------------------------------------------------------------------------- /app/assets/stylesheets/timers.css.scss: -------------------------------------------------------------------------------- 1 | // Place all the styles related to the timers controller here. 2 | // They will automatically be included in application.css. 3 | // You can use Sass (SCSS) here: http://sass-lang.com/ 4 | -------------------------------------------------------------------------------- /app/views/admin/teams/edit.html.haml: -------------------------------------------------------------------------------- 1 | %h1 #{team.name} 2 | = form_for team, url: admin_team_path(team) do |f| 3 | %p 4 | = f.label :display, "Display Name" 5 | = f.text_field :display 6 | %p 7 | = f.submit 8 | -------------------------------------------------------------------------------- /db/migrate/20140802151953_remove_joe_name_from_teams.rb: -------------------------------------------------------------------------------- 1 | class RemoveJoeNameFromTeams < ActiveRecord::Migration 2 | def change 3 | change_table :teams do |t| 4 | t.remove :joe_name 5 | end 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /db/migrate/20140808014759_dingus_is_binary.rb: -------------------------------------------------------------------------------- 1 | class DingusIsBinary < ActiveRecord::Migration 2 | def up 3 | remove_column :availabilities, :dingus 4 | add_column :availabilities, :dingus, :binary 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /db/migrate/20130706170819_add_certname_to_teams.rb: -------------------------------------------------------------------------------- 1 | class AddCertnameToTeams < ActiveRecord::Migration 2 | def change 3 | add_column :teams, :certname, :string 4 | add_index :teams, :certname, unique: true 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /config/initializers/application_controller_renderer.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # ApplicationController.renderer.defaults.merge!( 4 | # http_host: 'example.org', 5 | # https: false 6 | # ) 7 | -------------------------------------------------------------------------------- /db/migrate/20130706185014_create_services.rb: -------------------------------------------------------------------------------- 1 | class CreateServices < ActiveRecord::Migration 2 | def change 3 | create_table :services do |t| 4 | t.string :name 5 | 6 | t.timestamps 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # See http://www.robotstxt.org/wc/norobots.html for documentation on how to use the robots.txt file 2 | # 3 | # To ban all spiders from the entire site uncomment the next two lines: 4 | # User-agent: * 5 | # Disallow: / 6 | -------------------------------------------------------------------------------- /test/factories/penalties.rb: -------------------------------------------------------------------------------- 1 | # Read about factories at https://github.com/thoughtbot/factory_girl 2 | 3 | FactoryGirl.define do 4 | factory :penalty do 5 | availability nil 6 | team nil 7 | flag nil 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /app/controllers/dashboard_controller.rb: -------------------------------------------------------------------------------- 1 | class DashboardController < ApplicationController 2 | def index 3 | @instances = current_team. 4 | instances. 5 | joins(:service). 6 | where(services: {enabled: true}) 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /db/migrate/20130706185612_create_flags.rb: -------------------------------------------------------------------------------- 1 | class CreateFlags < ActiveRecord::Migration 2 | def change 3 | create_table :flags do |t| 4 | t.belongs_to :team, index: true 5 | 6 | t.timestamps 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /config/initializers/filter_parameter_logging.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Configure sensitive parameters which will be filtered from the log file. 4 | Rails.application.config.filter_parameters += [:password] 5 | -------------------------------------------------------------------------------- /db/migrate/20140705225220_add_payload_and_signature_to_rounds.rb: -------------------------------------------------------------------------------- 1 | class AddPayloadAndSignatureToRounds < ActiveRecord::Migration 2 | def change 3 | add_column :rounds, :payload, :json 4 | add_column :rounds, :signature, :string 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /app/assets/javascripts/tickets.js.coffee: -------------------------------------------------------------------------------- 1 | # Place all the behaviors and hooks related to the matching controller here. 2 | # All this logic will automatically be available in application.js. 3 | # You can use CoffeeScript in this file: http://coffeescript.org/ 4 | -------------------------------------------------------------------------------- /app/controllers/admin/rounds_controller.rb: -------------------------------------------------------------------------------- 1 | class Admin::RoundsController < Admin::BaseController 2 | def index 3 | @rounds = Round.order(created_at: :desc) 4 | end 5 | 6 | def show 7 | @round = Round.find params[:id] 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /test/factories/tickets.rb: -------------------------------------------------------------------------------- 1 | # Read about factories at https://github.com/thoughtbot/factory_girl 2 | 3 | FactoryGirl.define do 4 | factory :ticket do 5 | team nil 6 | body "MyText" 7 | resolved_at "2014-06-24 17:07:37" 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /app/assets/javascripts/replacements.coffee: -------------------------------------------------------------------------------- 1 | # Place all the behaviors and hooks related to the matching controller here. 2 | # All this logic will automatically be available in application.js. 3 | # You can use CoffeeScript in this file: http://coffeescript.org/ 4 | -------------------------------------------------------------------------------- /db/migrate/20130727182203_create_timers.rb: -------------------------------------------------------------------------------- 1 | class CreateTimers < ActiveRecord::Migration 2 | def change 3 | create_table :timers do |t| 4 | t.string :name 5 | t.datetime :ending 6 | 7 | t.timestamps 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /test/controllers/scoreboard_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class ScoreboardControllerTest < ActionController::TestCase 4 | should 'render a scoreboard' 5 | should 'provide json updates' 6 | should 'cut off an hour before game ends' 7 | end 8 | -------------------------------------------------------------------------------- /app/models/service.rb: -------------------------------------------------------------------------------- 1 | class Service < ActiveRecord::Base 2 | has_many :instances 3 | has_many :flags 4 | scope :enabled, -> { where(enabled: true) } 5 | 6 | def total_redemptions 7 | instances.joins(:tokens).sum(:redemptions_count) 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # Add your own tasks in files placed in lib/tasks ending in .rake, 2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. 3 | 4 | require File.expand_path('../config/application', __FILE__) 5 | 6 | Scorebot::Application.load_tasks 7 | -------------------------------------------------------------------------------- /db/migrate/20130728180622_create_messages.rb: -------------------------------------------------------------------------------- 1 | class CreateMessages < ActiveRecord::Migration 2 | def change 3 | create_table :messages do |t| 4 | t.belongs_to :team, index: true 5 | t.text :body 6 | 7 | t.timestamps 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /db/migrate/20130629024425_create_teams.rb: -------------------------------------------------------------------------------- 1 | class CreateTeams < ActiveRecord::Migration 2 | def change 3 | create_table :teams do |t| 4 | t.string :name 5 | t.uuid :uuid 6 | t.index :uuid, unique: true 7 | 8 | t.timestamps 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /test/system/replacements_test.rb: -------------------------------------------------------------------------------- 1 | require "application_system_test_case" 2 | 3 | class ReplacementsTest < ApplicationSystemTestCase 4 | # test "visiting the index" do 5 | # visit replacements_url 6 | # 7 | # assert_selector "h1", text: "Replacement" 8 | # end 9 | end 10 | -------------------------------------------------------------------------------- /db/migrate/20130706194342_add_round_to_scoring_models.rb: -------------------------------------------------------------------------------- 1 | class AddRoundToScoringModels < ActiveRecord::Migration 2 | def change 3 | add_column :tokens, :round_id, :integer 4 | add_column :redemptions, :round_id, :integer 5 | add_column :captures, :round_id, :integer 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /config/initializers/cookies_serializer.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Specify a serializer for the signed and encrypted cookie jars. 4 | # Valid options are :json, :marshal, and :hybrid. 5 | Rails.application.config.action_dispatch.cookies_serializer = :marshal 6 | -------------------------------------------------------------------------------- /db/migrate/20130706185532_create_tokens.rb: -------------------------------------------------------------------------------- 1 | class CreateTokens < ActiveRecord::Migration 2 | def change 3 | create_table :tokens do |t| 4 | t.string :key 5 | t.string :digest 6 | t.belongs_to :instance, index: true 7 | 8 | t.timestamps 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /db/migrate/20130706194306_create_captures.rb: -------------------------------------------------------------------------------- 1 | class CreateCaptures < ActiveRecord::Migration 2 | def change 3 | create_table :captures do |t| 4 | t.belongs_to :redemption, index: true 5 | t.belongs_to :flag, index: true 6 | 7 | t.timestamps 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /db/migrate/20130707034729_create_instances.rb: -------------------------------------------------------------------------------- 1 | class CreateInstances < ActiveRecord::Migration 2 | def change 3 | create_table :instances do |t| 4 | t.belongs_to :team, index: true 5 | t.belongs_to :service, index: true 6 | 7 | t.timestamps 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /test/models/instance_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class InstanceTest < ActiveSupport::TestCase 4 | should belong_to :service 5 | should belong_to :team 6 | should have_many :tokens 7 | should have_many :availabilities 8 | should have_many(:redemptions).through(:tokens) 9 | end 10 | -------------------------------------------------------------------------------- /db/migrate/20140624210737_create_tickets.rb: -------------------------------------------------------------------------------- 1 | class CreateTickets < ActiveRecord::Migration 2 | def change 3 | create_table :tickets do |t| 4 | t.belongs_to :team, index: true 5 | t.text :body 6 | t.datetime :resolved_at 7 | 8 | t.timestamps 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /test/models/team_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class TeamTest < ActiveSupport::TestCase 4 | should have_many :instances 5 | should have_many :redemptions 6 | should have_many :flags 7 | should have_many(:captures).through(:redemptions) 8 | should 'find a team by ssl key guid' 9 | end 10 | -------------------------------------------------------------------------------- /app/models/ticket.rb: -------------------------------------------------------------------------------- 1 | class Ticket < ActiveRecord::Base 2 | belongs_to :team 3 | scope :unresolved, -> { where(resolved_at: nil) } 4 | 5 | def resolve! 6 | update_attribute :resolved_at, Time.now 7 | end 8 | 9 | def unresolve! 10 | update_attribute :resolved_at, nil 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /db/migrate/20130707040633_index_round_and_instance_on_scoring_models.rb: -------------------------------------------------------------------------------- 1 | class IndexRoundAndInstanceOnScoringModels < ActiveRecord::Migration 2 | def change 3 | add_index :tokens, %i{instance_id round_id}, unique: true 4 | add_index :availabilities, %i{instance_id round_id}, unique: true 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /db/migrate/20130706194517_create_availabilities.rb: -------------------------------------------------------------------------------- 1 | class CreateAvailabilities < ActiveRecord::Migration 2 | def change 3 | create_table :availabilities do |t| 4 | t.belongs_to :instance, index: true 5 | t.belongs_to :round, index: true 6 | 7 | t.timestamps 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /db/migrate/20140726191136_add_dinguses_to_availabilities.rb: -------------------------------------------------------------------------------- 1 | class AddDingusesToAvailabilities < ActiveRecord::Migration 2 | def change 3 | add_column :availabilities, :dingus, :string 4 | add_column :availabilities, :token_string, :string 5 | add_reference :availabilities, :token, index: true 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /db/migrate/20140808010216_make_memo_columns_binary.rb: -------------------------------------------------------------------------------- 1 | class MakeMemoColumnsBinary < ActiveRecord::Migration 2 | def up 3 | remove_column :tokens, :memo 4 | add_column :tokens, :memo, :binary 5 | 6 | remove_column :availabilities, :memo 7 | add_column :availabilities, :memo, :binary 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /app/views/shared/_banner.html.haml: -------------------------------------------------------------------------------- 1 | .banner 2 | %h1 DEF CON CTF 2017 3 | %p You're #{current_team.name} 4 | %ul 5 | %li= link_to_unless_current 'Scoreboard', scoreboard_path 6 | %li= link_to_unless_current 'Team Information', dashboard_path 7 | %li= link_to_unless_current 'How to play', howto_path 8 | %br.clear 9 | -------------------------------------------------------------------------------- /lib/tasks/ca.rake: -------------------------------------------------------------------------------- 1 | namespace :ca do 2 | desc "Output JSON for the certificate authority to generate client certs" 3 | task :json => :environment do 4 | out = Team.all.map(&:as_ca_json).to_json 5 | File.open(Rails.root.join('tmp', 'ca_teams.json'), 'w') do |f| 6 | f.write out 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /app/models/capture.rb: -------------------------------------------------------------------------------- 1 | class Capture < ActiveRecord::Base 2 | belongs_to :redemption 3 | belongs_to :flag 4 | belongs_to :round 5 | has_one :team, through: :redemption 6 | 7 | before_create :update_flag_team 8 | 9 | private 10 | def update_flag_team 11 | flag.team = self.team 12 | flag.save 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /app/views/replacements/show.html.haml: -------------------------------------------------------------------------------- 1 | .scene 2 | %h1 Replacement 3 | %p= link_to 'download', download: true 4 | %dl 5 | %dt service 6 | %dd= @replacement.service.name 7 | %dt team 8 | %dd= @replacement.team.name 9 | %dt size 10 | %dd= @replacement.size 11 | %dt digest 12 | %dd= @replacement.digest 13 | -------------------------------------------------------------------------------- /app/controllers/admin/redemptions_controller.rb: -------------------------------------------------------------------------------- 1 | class Admin::RedemptionsController < Admin::BaseController 2 | def index 3 | offset = params[:offset] || 0 4 | @redemptions = Redemption.order(created_at: :desc).limit(200).offset(offset) 5 | end 6 | 7 | def show 8 | @redemption = Redemption.find params[:id] 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /app/views/layouts/admin.html.haml: -------------------------------------------------------------------------------- 1 | !!! 5 2 | %html 3 | %head 4 | %title ctf admin 5 | = stylesheet_link_tag 'application' 6 | = yield :head 7 | = csrf_meta_tag 8 | %body.admin{class: body_class} 9 | = render partial: 'admin_nav' 10 | .content 11 | = yield 12 | 13 | = javascript_include_tag 'application' 14 | -------------------------------------------------------------------------------- /db/migrate/20140727221954_create_penalties.rb: -------------------------------------------------------------------------------- 1 | class CreatePenalties < ActiveRecord::Migration 2 | def change 3 | create_table :penalties do |t| 4 | t.belongs_to :availability, index: true 5 | t.belongs_to :team, index: true 6 | t.belongs_to :flag, index: true 7 | 8 | t.timestamps 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /app/controllers/scoreboard_controller.rb: -------------------------------------------------------------------------------- 1 | class ScoreboardController < ApplicationController 2 | def index 3 | redirect_to dashboard_path unless is_legitbs? 4 | respond_to do |f| 5 | f.html do 6 | @teams = Team.for_scoreboard 7 | end 8 | f.json { render json: Team.as_standings_json } 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /app/views/layouts/application.html.haml: -------------------------------------------------------------------------------- 1 | !!! 5 2 | %html 3 | %head 4 | %title DEF CON CTF 2017 5 | = stylesheet_link_tag 'application' 6 | = yield :head 7 | = csrf_meta_tag 8 | %body{class: body_class} 9 | = render partial: 'shared/banner' 10 | .content 11 | = yield 12 | 13 | = javascript_include_tag 'application' 14 | -------------------------------------------------------------------------------- /db/migrate/20130706183611_create_redemptions.rb: -------------------------------------------------------------------------------- 1 | class CreateRedemptions < ActiveRecord::Migration 2 | def change 3 | create_table :redemptions do |t| 4 | t.belongs_to :team, index: true 5 | t.belongs_to :token, index: true 6 | t.index %i{team_id token_id}, unique: true 7 | 8 | t.timestamps 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /puma.conf: -------------------------------------------------------------------------------- 1 | description "scorebot puma" 2 | 3 | start on runlevel [2] 4 | stop on runlevel [!2] 5 | respawn 6 | 7 | env RAILS_ENV=production 8 | 9 | setuid scorebot 10 | setgid scorebot 11 | 12 | chdir /home/scorebot/production/current 13 | 14 | exec /home/scorebot/.rbenv/shims/bundle exec puma -C /home/scorebot/production/current/config/puma.rb 15 | -------------------------------------------------------------------------------- /app/assets/stylesheets/defaults.sass: -------------------------------------------------------------------------------- 1 | h1 2 | @include permie 3 | font-weight: 100 4 | 5 | input[type='text'], textarea 6 | color: $light_amber 7 | background-color: black 8 | padding: 0.5em 9 | border: 1px solid $light_amber 10 | font-family: "Ubuntu Mono" 11 | font-size: 18px 12 | 13 | input[type='submit'] 14 | @include button 15 | border-width: 2px 16 | -------------------------------------------------------------------------------- /bin/2014-final-calculation: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | $stderr.puts "#{Time.now} Loading 2014 reprocess script" 3 | require ::File.expand_path('../../config/environment', __FILE__) 4 | 5 | puts "if you aren't running this on a copy of the db..." 6 | puts "you already fucked up, hit ctrl-c now" 7 | gets 8 | 9 | 3.times do 10 | r = Round.create 11 | r.finalize! 12 | end 13 | -------------------------------------------------------------------------------- /bin/yarn: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | VENDOR_PATH = File.expand_path('..', __dir__) 3 | Dir.chdir(VENDOR_PATH) do 4 | begin 5 | exec "yarnpkg #{ARGV.join(" ")}" 6 | rescue Errno::ENOENT 7 | $stderr.puts "Yarn executable was not detected in the system." 8 | $stderr.puts "Download Yarn at https://yarnpkg.com/en/docs/install" 9 | exit 1 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /bin/copy_scripts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require ::File.expand_path('../../config/environment', __FILE__) 4 | require 'fileutils' 5 | 6 | Dir. 7 | glob(Rails.root.join('scripts/**/*')). 8 | select{ |n| File.file?(n) }. 9 | each do |f| 10 | dest = f.gsub(%r{production/releases/\d+/}, '') 11 | FileUtils.mkdir_p File.dirname(dest) 12 | FileUtils.cp(f, dest) 13 | end 14 | -------------------------------------------------------------------------------- /bin/ssh_a_bunch: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | $stderr.puts "#{Time.now} Loading ssh_a_bunch" 4 | require ::File.expand_path('../../config/environment', __FILE__) 5 | 6 | @ssh_procs = Team.all.map do |t| 7 | addr = t.address.gsub('10.5', '10.4') 8 | $stderr.puts fork{ `ssh -o "BatchMode yes" root@#{addr}` }.inspect 9 | end 10 | 11 | $stderr.puts 'waiting' 12 | loop { sleep 60 } 13 | -------------------------------------------------------------------------------- /db/migrate/20170723131656_create_replacements.rb: -------------------------------------------------------------------------------- 1 | class CreateReplacements < ActiveRecord::Migration[5.1] 2 | def change 3 | create_table :replacements do |t| 4 | t.belongs_to :team, foreign_key: true 5 | t.belongs_to :service, foreign_key: true 6 | t.belongs_to :round, foreign_key: true 7 | t.string :digest 8 | 9 | t.timestamps 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /db/migrate/20170723153202_replacement_indexes.rb: -------------------------------------------------------------------------------- 1 | class ReplacementIndexes < ActiveRecord::Migration[5.1] 2 | def change 3 | add_column :replacements, :size, :integer 4 | add_index :replacements, :team_id 5 | add_index :replacements, :service_id 6 | add_index :replacements, :round_id 7 | add_index :replacements, %i{team_id service_id round_id}, unique: true 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /app/views/shared/_timers.html.haml: -------------------------------------------------------------------------------- 1 | #timers{'data-timer-path' => timers_path, 'data-timer-interval' => 500} 2 | %h1 Timers 3 | #actual_timers 4 | .game 5 | .desc Game 6 | .timer {{game.h}}:{{game.m}}:{{game.s}} 7 | .today 8 | .desc Today 9 | .timer {{today.h}}:{{today.m}}:{{today.s}} 10 | .round 11 | .desc Round 12 | .timer {{round.h}}:{{round.m}}:{{round.s}} 13 | -------------------------------------------------------------------------------- /app/views/admin/services/edit.html.haml: -------------------------------------------------------------------------------- 1 | %h1= service.name 2 | 3 | = form_for service, url: admin_service_path(service) do |f| 4 | %p 5 | = f.label :enabled 6 | = f.check_box :enabled 7 | %p 8 | = f.label :port 9 | = f.text_field :port 10 | %p 11 | %em Why can't I change the service name? 12 | %br 13 | Because it will break file paths and such, sorry! 14 | %p 15 | = f.submit 16 | -------------------------------------------------------------------------------- /app/controllers/admin/tokens_controller.rb: -------------------------------------------------------------------------------- 1 | class Admin::TokensController < Admin::BaseController 2 | def index 3 | @tokens = [] 4 | if ts = params[:token_string] 5 | @tokens = Token.where_prefixed_by ts 6 | else 7 | @tokens = Token.order(created_at: :desc).limit(Team.count * Service.count) 8 | end 9 | end 10 | 11 | def show 12 | @token = Token.find params[:id] 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ruby:2.4.1 2 | RUN apt-get update -qq && apt-get install -y build-essential libpq-dev nodejs 3 | RUN mkdir /scorebot 4 | WORKDIR /scorebot 5 | ADD Gemfile /scorebot/Gemfile 6 | ADD Gemfile.lock /scorebot/Gemfile.lock 7 | RUN bundle install 8 | RUN mkdir ~/.ssh 9 | ADD tmp/scorebot_rsa /root/.ssh/id_rsa 10 | ADD tmp/scorebot_rsa.pub /root/.ssh/id_rsa.pub 11 | ADD tmp/known_hosts /root/.ssh/known_hosts 12 | ADD . /scorebot 13 | -------------------------------------------------------------------------------- /db/migrate/20140805223411_add_failed_counters_to_teams.rb: -------------------------------------------------------------------------------- 1 | class AddFailedCountersToTeams < ActiveRecord::Migration 2 | def change 3 | add_column :teams, :dupe_ctr, :integer, default: 0 4 | add_column :teams, :old_ctr, :integer, default: 0 5 | add_column :teams, :notfound_ctr, :integer, default: 0 6 | add_column :teams, :self_ctr, :integer, default: 0 7 | add_column :teams, :other_ctr, :integer, default: 0 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /config/initializers/backtrace_silencers.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. 4 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } 5 | 6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. 7 | # Rails.backtrace_cleaner.remove_silencers! 8 | -------------------------------------------------------------------------------- /app/helpers/admin_helper.rb: -------------------------------------------------------------------------------- 1 | module AdminHelper 2 | def describe_instance(i) 3 | link_to [i.team.certname, i.service.name].join(' '), admin_instance_path(i) 4 | end 5 | 6 | def describe_token(t) 7 | link_to "#{t.id} #{t.to_fake_string.truncate(8)}", admin_token_path(t) 8 | end 9 | 10 | def time_ago(t) 11 | time_ago_in_words(t, include_seconds: true) + ' ago' 12 | end 13 | 14 | def none_able(v) 15 | v or content_tag(:em, 'none') 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /app/controllers/admin/availabilities_controller.rb: -------------------------------------------------------------------------------- 1 | class Admin::AvailabilitiesController < Admin::BaseController 2 | def index 3 | @oof = (params[:offset] || 0).to_i 4 | 5 | @availabilities = Availability. 6 | order(created_at: :desc). 7 | limit(200). 8 | offset(@oof). 9 | all 10 | end 11 | 12 | def show 13 | @availability = Availability.find params[:id] 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /test/controllers/replacements_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class ReplacementsControllerTest < ActionDispatch::IntegrationTest 4 | test "should get index" do 5 | get replacements_index_url 6 | assert_response :success 7 | end 8 | 9 | test "should get show" do 10 | get replacements_show_url 11 | assert_response :success 12 | end 13 | 14 | test "should get new" do 15 | get replacements_new_url 16 | assert_response :success 17 | end 18 | 19 | end 20 | -------------------------------------------------------------------------------- /test/models/capture_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class CaptureTest < ActiveSupport::TestCase 4 | should have_one(:team).through(:redemption) 5 | should belong_to :flag 6 | should belong_to :redemption 7 | should belong_to :round 8 | 9 | should 'update the captured flag' do 10 | @flag = FactoryGirl.create :flag 11 | 12 | old_tem = @flag.team 13 | @capture = FactoryGirl.create :capture, flag: @flag 14 | 15 | assert_equal @capture.team, @flag.team 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /app/views/admin/services/index.html.haml: -------------------------------------------------------------------------------- 1 | %h1 Services 2 | 3 | %table 4 | %thead 5 | %tr 6 | %th id 7 | %th name 8 | %th port 9 | %th enabled 10 | %th activities 11 | %tbody 12 | - services.each do |s| 13 | %tr 14 | %td= link_to s.id, admin_service_path(s) 15 | %td= s.name 16 | %td= s.port 17 | %td= !!s.enabled 18 | %td 19 | = link_to 'show', admin_service_path(s) 20 | = link_to 'edit', edit_admin_service_path(s) 21 | -------------------------------------------------------------------------------- /bin/2014-day3-downtime: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | $stderr.puts "#{Time.now} Loading 2014 reprocess script" 3 | require ::File.expand_path('../../config/environment', __FILE__) 4 | 5 | puts "if you aren't running this on a copy of the db..." 6 | puts "you already fucked up, hit ctrl-c now" 7 | gets 8 | 9 | rt = Team.find_by name: 'Routards' 10 | rt_rounds = Round.where id: [228, 229, 230, 231, 232, 233, 234] 11 | 12 | rt_avs = Availability.where(instance: rt.instances, round: rt_rounds) 13 | 14 | rt_avs.each(&:fix_availability) 15 | -------------------------------------------------------------------------------- /app/controllers/livectf_controller.rb: -------------------------------------------------------------------------------- 1 | class LivectfController < ApplicationController 2 | def capture 3 | unless params[:flag].downcase == 4 | 'YouMayNotHaveROPedYetButYouWillNextTime'.downcase 5 | raise ActionController::RoutingError.new('Not Found') 6 | end 7 | 8 | payload = { submitted_at: Time.now.to_f, 9 | submitting_team: current_team 10 | }.to_json 11 | 12 | $redis.append 'livectf_submissions', payload 13 | 14 | render json: payload 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /app/views/replacements/new.html.haml: -------------------------------------------------------------------------------- 1 | .scene 2 | %h1 Replace Service 3 | = form_for @replacement, html_options: {multipart: true} do |f| 4 | - if @replacement.errors.any? 5 | #error_explanation 6 | %ul 7 | - @replacement.errors.full_messages.each do |e| 8 | %li= e 9 | %p 10 | = f.label :service_id, 'Service' 11 | = f.select :service_id, @services.map {|s| [s.name, s.id] } 12 | %p 13 | = f.label :file 14 | = f.file_field :file 15 | %p 16 | = f.submit 17 | -------------------------------------------------------------------------------- /app/views/admin/base/_admin_nav.html.haml: -------------------------------------------------------------------------------- 1 | %nav 2 | %ul 3 | %li legitbs only 4 | %li= link_to 'root', admin_root_path 5 | %li= link_to 'availabilities', admin_availabilities_path 6 | %li= link_to 'instances', admin_instances_path 7 | %li= link_to 'redemptions', admin_redemptions_path 8 | %li= link_to 'replacements', admin_replacements_path 9 | %li= link_to 'rounds', admin_rounds_path 10 | %li= link_to 'services', admin_services_path 11 | %li= link_to 'teams', admin_teams_path 12 | %li= link_to 'tokens', admin_tokens_path 13 | -------------------------------------------------------------------------------- /app/views/admin/instances/index.html.haml: -------------------------------------------------------------------------------- 1 | %h1 Instances 2 | 3 | %table.instances 4 | %tr 5 | %th team \ service 6 | - @services.each do |s| 7 | %th 8 | = s.id 9 | = s.name 10 | - @teams.each do |t| 11 | - scores = t.scores_by_service 12 | %tr 13 | %th 14 | = t.id 15 | = t.certname 16 | - @services.each do |s| 17 | %td 18 | = link_to(t.certname.truncate(8) + ' ' + s.name, admin_instance_path(0, team: t.id, service: s.id)) 19 | %br 20 | = scores[s.id] || 0 21 | -------------------------------------------------------------------------------- /app/views/tickets/_form.html.haml: -------------------------------------------------------------------------------- 1 | = form_for @ticket do |f| 2 | - if @ticket.errors.any? 3 | #error_explanation 4 | %h2= "#{pluralize(@ticket.errors.count, "error")} prohibited this ticket from being saved:" 5 | %ul 6 | - @ticket.errors.full_messages.each do |msg| 7 | %li= msg 8 | 9 | .field 10 | = f.label :body, "Problems? Securely send a message to Legitimate Business Syndicate. Definitely paste any tokens or UUIDs from redemptions." 11 | %br 12 | = f.text_area :body 13 | .actions 14 | = f.submit 'Save' 15 | -------------------------------------------------------------------------------- /app/views/admin/root/index.html.haml: -------------------------------------------------------------------------------- 1 | %h1 stats and gizmos 2 | %table 3 | %tbody 4 | %tr 5 | %th rails root 6 | %td= Rails.root 7 | %tr 8 | %th current machine 9 | %td= `hostname` 10 | %tr 11 | %th revision 12 | %td= File.read Rails.root.join 'REVISION' rescue 'not there' 13 | %tr 14 | %th puma master pid 15 | %td= File.read Rails.root.join 'tmp/pids/puma.pid' rescue 'not there' 16 | %tr 17 | %th service pid 18 | %td= File.read Rails.root.join 'tmp/pids/service.pid' rescue 'not there' 19 | -------------------------------------------------------------------------------- /app/controllers/rounds_controller.rb: -------------------------------------------------------------------------------- 1 | class RoundsController < ApplicationController 2 | helper_method :round 3 | 4 | def show 5 | return redirect_to dashboard_path if round.nil? 6 | 7 | respond_to do |fmt| 8 | fmt.png do 9 | send_data round.qr, type: 'image/png', disposition: 'inline' 10 | end 11 | end 12 | end 13 | 14 | private 15 | def round 16 | if params[:id] == 'latest' 17 | return @round ||= Round.where.not(signature: nil).first 18 | end 19 | @round ||= Round.find_by id: params[:id] 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /config/initializers/wrap_parameters.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # This file contains settings for ActionController::ParamsWrapper which 4 | # is enabled by default. 5 | 6 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. 7 | ActiveSupport.on_load(:action_controller) do 8 | wrap_parameters format: [:json] 9 | end 10 | 11 | # To enable root element in JSON for ActiveRecord objects. 12 | # ActiveSupport.on_load(:active_record) do 13 | # self.include_root_in_json = true 14 | # end 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | # 3 | # If you find yourself ignoring temporary files generated by your text editor 4 | # or operating system, you probably want to add a global ignore instead: 5 | # git config --global core.excludesfile '~/.gitignore_global' 6 | 7 | # Ignore bundler config. 8 | /.bundle 9 | 10 | # Ignore the default SQLite database. 11 | /db/*.sqlite3 12 | /db/*.sqlite3-journal 13 | 14 | # Ignore all logfiles and tempfiles. 15 | /log/*.log 16 | /tmp 17 | erd.pdf 18 | *.pyc 19 | node_modules 20 | public/assets 21 | -------------------------------------------------------------------------------- /app/models/event.rb: -------------------------------------------------------------------------------- 1 | class Event 2 | attr_accessor :redis, :event_name, :event_body 3 | 4 | def initialize(event_name, event_body) 5 | self.redis = $redis 6 | self.event_name = event_name 7 | self.event_body = event_body 8 | end 9 | 10 | def channel_name 11 | "scorebot_#{Rails.env}" 12 | end 13 | 14 | def message_body 15 | { 16 | published_at: Time.now.to_f, 17 | event_name: event_name, 18 | event_body: event_body, 19 | } 20 | end 21 | 22 | def publish! 23 | redis.publish channel_name, message_body.to_json 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /README.rdoc: -------------------------------------------------------------------------------- 1 | == README 2 | 3 | This README would normally document whatever steps are necessary to get the 4 | application up and running. 5 | 6 | Things you may want to cover: 7 | 8 | * Ruby version 9 | 10 | * System dependencies 11 | 12 | * Configuration 13 | 14 | * Database creation 15 | 16 | * Database initialization 17 | 18 | * How to run the test suite 19 | 20 | * Services (job queues, cache servers, search engines, etc.) 21 | 22 | * Deployment instructions 23 | 24 | * ... 25 | 26 | 27 | Please feel free to use a different markup language if you do not plan to run 28 | rake doc:app. 29 | -------------------------------------------------------------------------------- /app/assets/stylesheets/application.sass: -------------------------------------------------------------------------------- 1 | @import 'bourbon' 2 | @import 'fonts' 3 | @import 'mixins' 4 | @import 'color' 5 | 6 | @import 'defaults' 7 | 8 | @import 'banner' 9 | @import 'scene' 10 | @import 'scoreboard' 11 | @import 'dashboard' 12 | @import 'howto' 13 | @import 'replacements' 14 | @import 'tickets' 15 | @import 'nav' 16 | @import 'admin' 17 | 18 | body 19 | width: 100% 20 | height: 100% 21 | margin: 0 22 | background-image: asset_url('purple-linen.png') 23 | background-size: cover 24 | background-color: $superdark_grape 25 | font-family: "Ubuntu" 26 | 27 | 28 | br.clear 29 | clear: both 30 | -------------------------------------------------------------------------------- /app/models/timer.rb: -------------------------------------------------------------------------------- 1 | class Timer < ActiveRecord::Base 2 | 3 | def self.feed 4 | j = proc{|e| e.ending} 5 | { 6 | today: j[today], 7 | game: j[game], 8 | round: j[round] 9 | } 10 | end 11 | 12 | def self.today 13 | Timer.where(name: 'sunday').first 14 | end 15 | def self.game 16 | Timer.where(name: 'game').first 17 | end 18 | def self.round 19 | Timer.where(name: 'round').first 20 | end 21 | 22 | def remaining 23 | ending.to_i - Time.now.to_i 24 | end 25 | 26 | def ended? 27 | return true if ending.nil? 28 | 29 | ending < Time.now 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /app/views/admin/services/availabilities/index.html.haml: -------------------------------------------------------------------------------- 1 | %h1 #{service.name} Availabilities 2 | 3 | %nav 4 | %ul 5 | %li= link_to 'All', rounds: 'all' 6 | %li= link_to 'Most recent', rounds: 'recent' 7 | 8 | %table#availabilities 9 | %thead 10 | %tr 11 | %th 12 | %em round 13 | - @teams.each do |t| 14 | %th= t.certname 15 | %tbody 16 | - @availabilities_rounds.each do |round_id, av_hash| 17 | %tr 18 | %th= round_id 19 | - @teams.each do |t| 20 | - av = av_hash[t.id] 21 | %td{class: av.healthy?.inspect} 22 | = av.healthy? 23 | -------------------------------------------------------------------------------- /app/views/tickets/show.html.haml: -------------------------------------------------------------------------------- 1 | %p#notice= notice 2 | 3 | %p 4 | %b Team: 5 | = @ticket.team.name 6 | .ticket-body 7 | = simple_format @ticket.body 8 | %p 9 | %b Resolved at: 10 | - if @ticket.resolved_at 11 | = @ticket.resolved_at 12 | (#{time_ago_in_words @ticket.resolved_at} ago) 13 | - else 14 | %em unresolved 15 | %p 16 | %b Created at: 17 | = time_ago @ticket.created_at 18 | 19 | %nav 20 | %ul 21 | %li= link_to 'Edit', edit_ticket_path(@ticket) 22 | - if is_legitbs? 23 | %li= link_to 'Resolve', resolve_ticket_path(@ticket), method: :post 24 | %li= link_to 'Back', tickets_path 25 | -------------------------------------------------------------------------------- /app/controllers/admin/services_controller.rb: -------------------------------------------------------------------------------- 1 | class Admin::ServicesController < Admin::BaseController 2 | helper_method :services, :service 3 | def index 4 | end 5 | 6 | def show 7 | 8 | end 9 | 10 | def edit 11 | end 12 | 13 | def update 14 | service.update_attributes(service_params) 15 | redirect_to admin_services_path 16 | end 17 | 18 | private 19 | def services 20 | @services ||= Service.order(name: :asc) 21 | end 22 | 23 | def service 24 | @service ||= Service.find params[:id] 25 | end 26 | 27 | def service_params 28 | params.require(:service).permit(:enabled, :port) 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /app/controllers/admin/instances_controller.rb: -------------------------------------------------------------------------------- 1 | class Admin::InstancesController < Admin::BaseController 2 | def index 3 | @instances = Instance.order(team_id: :asc, service_id: :asc) 4 | @teams = Team.order(id: :asc) 5 | @services = Service.order(id: :asc) 6 | end 7 | 8 | def show 9 | return redirect_instance_find if params[:id] == '0' 10 | @instance = Instance.find params[:id] 11 | end 12 | 13 | private 14 | def redirect_instance_find 15 | @instance = Instance.find_by(service_id: params[:service], 16 | team_id: params[:team]) 17 | redirect_to id: @instance.id, status: :found 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /app/views/admin/tokens/index.html.haml: -------------------------------------------------------------------------------- 1 | %h1 Tokens 2 | 3 | = form_tag admin_tokens_path, method: 'get' do 4 | %p 5 | search by token string 6 | = search_field_tag :token_string, params[:token_string] 7 | = submit_tag 'search', class: 'tiny_search' 8 | 9 | %table 10 | %thead 11 | %tr 12 | %th id 13 | %th instance 14 | %th round 15 | %th key 16 | %th activities 17 | %tbody 18 | - @tokens.each do |t| 19 | %tr 20 | %td= link_to t.id, admin_token_path(t) 21 | %td= describe_instance t.instance 22 | %td= t.round.id 23 | %td= t.key 24 | %td= link_to 'show', admin_token_path(t) 25 | -------------------------------------------------------------------------------- /config/initializers/new_framework_defaults_5_1.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | # 3 | # This file contains migration options to ease your Rails 5.1 upgrade. 4 | # 5 | # Once upgraded flip defaults one by one to migrate to the new default. 6 | # 7 | # Read the Guide for Upgrading Ruby on Rails for more info on each option. 8 | 9 | # Make `form_with` generate non-remote forms. 10 | Rails.application.config.action_view.form_with_generates_remote_forms = false 11 | 12 | # Unknown asset fallback will return the path passed in when the given 13 | # asset is not present in the asset pipeline. 14 | # Rails.application.config.assets.unknown_asset_fallback = false 15 | -------------------------------------------------------------------------------- /app/views/admin/redemptions/index.html.haml: -------------------------------------------------------------------------------- 1 | %h1 Redemptions 2 | 3 | %table 4 | %thead 5 | %tr 6 | %th id 7 | %th team 8 | %th instance 9 | %th token 10 | %th round 11 | %th captured 12 | %th actions 13 | %tbody 14 | - @redemptions.each do |r| 15 | %tr 16 | %td= link_to r.id, admin_redemption_path(r) 17 | %td= link_to r.team.certname, admin_team_path(r.team) 18 | %td= describe_instance r.token.instance 19 | %td= describe_token r.token 20 | %td= link_to r.round.id, admin_round_path(r.round) 21 | %td= r.captures.count 22 | %td 23 | = link_to 'show', admin_redemption_path(r) 24 | -------------------------------------------------------------------------------- /bin/2014-w3stormz-badge: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | $stderr.puts "#{Time.now} Loading 2014 reprocess script" 3 | require ::File.expand_path('../../config/environment', __FILE__) 4 | 5 | puts "if you aren't running this on a copy of the db..." 6 | puts "you already fucked up, hit ctrl-c now" 7 | gets 8 | 9 | t = Team.find_by name: 'w3stormz' 10 | s = Service.find_by name: 'badge' 11 | 12 | i = t.instances.find_by(service: s) 13 | 14 | avs = i.availabilities.where('round_id < 87') 15 | 16 | puts "making #{avs.count} availabilities before round 87 ok" 17 | 18 | avs.each do |av| 19 | av.status = 0 20 | av.memo = "administratively fixed by legitbs <3 vito@legitbs.net" 21 | av.save 22 | end 23 | -------------------------------------------------------------------------------- /app/assets/javascripts/application.js: -------------------------------------------------------------------------------- 1 | // This is a manifest file that'll be compiled into application.js, which will include all the files 2 | // listed below. 3 | // 4 | // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts, 5 | // or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path. 6 | // 7 | // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the 8 | // compiled file. 9 | // 10 | // Read Sprockets README (https://github.com/sstephenson/sprockets#sprockets-directives) for details 11 | // about supported directives. 12 | // 13 | //= require jquery 14 | //= require jquery_ujs 15 | //= require_tree . 16 | -------------------------------------------------------------------------------- /config/initializers/secret_token.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Your secret key is used for verifying the integrity of signed cookies. 4 | # If you change this key, all old signed cookies will become invalid! 5 | 6 | # Make sure the secret is at least 30 characters and all random, 7 | # no regular words or you'll be exposed to dictionary attacks. 8 | # You can use `rake secret` to generate a secure secret key. 9 | 10 | # Make sure your secret_key_base is kept private 11 | # if you're sharing your code publicly. 12 | Scorebot::Application.config.secret_key_base = '62b6291e7829bd228192acac6ea3d208f0266877bf4a7fb96c77d4aae9f1ab7af525ce9f98bfff98d42707b584d9db9a075f83531780d8c056accb96b4bc64cf' 13 | -------------------------------------------------------------------------------- /config/initializers/inflections.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new inflection rules using the following format. Inflections 4 | # are locale specific, and you may define rules for as many different 5 | # locales as you wish. All of these examples are active by default: 6 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 7 | # inflect.plural /^(ox)$/i, '\1en' 8 | # inflect.singular /^(ox)en/i, '\1' 9 | # inflect.irregular 'person', 'people' 10 | # inflect.uncountable %w( fish sheep ) 11 | # end 12 | 13 | # These inflection rules are supported but not enabled by default: 14 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 15 | # inflect.acronym 'RESTful' 16 | # end 17 | -------------------------------------------------------------------------------- /bin/2017-day1-downtime: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | $stderr.puts "#{Time.now} Loading 2017 day1 reprocess script" 3 | require ::File.expand_path('../../config/environment', __FILE__) 4 | 5 | puts "if you aren't running this on a copy of the db..." 6 | puts "you already fucked up, hit ctrl-c now" 7 | gets 8 | 9 | Availability.where(id: 514..515).each(&:fix_availability) 10 | 11 | quarter = Service.find_by(name: 'quarter') 12 | quarter_tokens = Token.where(instance_id: quarter.instances.map(&:id), 13 | round_id: 43) 14 | 15 | hitcon = Team.find_by(certname: 'hitcon') 16 | 17 | quarter_tokens.each do |t| 18 | next if t.instance.team_id == 16 19 | Redemption.create(token: t, team: hitcon, round_id: 43) 20 | end 21 | -------------------------------------------------------------------------------- /app/assets/stylesheets/mixins.sass: -------------------------------------------------------------------------------- 1 | @mixin mono 2 | font-family: 'Ubuntu Mono', monospace 3 | @mixin permie 4 | font-family: 'Permanent Marker' 5 | @mixin prose-mode 6 | width: 900px 7 | background-color: $content_background 8 | color: $content_text 9 | border: 1px solid $content_highlight 10 | margin: 8px auto 80px 11 | padding: 1em 12 | font-size: 1.1em 13 | @mixin button 14 | border: 1px solid $amber 15 | color: $light_amber 16 | border-radius: 4px 17 | padding: 0.5em 18 | background-color: $dark_amber 19 | text-decoration: none 20 | font-size: 16px 21 | @mixin console 22 | @include mono 23 | background-color: black 24 | border: 1px solid $content_highlight 25 | padding: 1em 26 | width: 40em 27 | color: $content_highlight 28 | -------------------------------------------------------------------------------- /app/views/admin/teams/index.html.haml: -------------------------------------------------------------------------------- 1 | %h1 Teams 2 | 3 | %nav 4 | %ul 5 | %li= link_to 'numeric order', order: 'num' 6 | %li= link_to 'alphabetic order', order: 'alpha' 7 | %li= link_to 'score order (or "scorder" for short)', order: 'winning' 8 | 9 | %table 10 | %thead 11 | %tr 12 | %th id 13 | %th name 14 | %th display 15 | %th certname 16 | %th flags 17 | %th activities 18 | %tbody 19 | - @teams.each do |t| 20 | %tr 21 | %td= link_to t.id, admin_team_path(t) 22 | %td= t.name 23 | %td= t.display 24 | %td= t.certname 25 | %td= t.flags.count 26 | %td 27 | = link_to 'show', admin_team_path(t) 28 | = link_to 'edit', edit_admin_team_path(t) 29 | -------------------------------------------------------------------------------- /app/views/admin/rounds/index.html.haml: -------------------------------------------------------------------------------- 1 | %h1 Rounds 2 | %p all #{@rounds.count} rounds so far, newest to oldest 3 | 4 | %table 5 | %thead 6 | %th id 7 | %th started at 8 | %th ended at 9 | %th length 10 | %th activities 11 | %tbody 12 | - @rounds.each do |r| 13 | %tr 14 | %td= link_to r.id, admin_round_path(r) 15 | %td{title: r.created_at} 16 | = r.created_at.to_formatted_s(:ctf) 17 | = time_ago r.created_at 18 | %td{title: r.ended_at} 19 | = r.ended_at.try(:to_formatted_s, :ctf) or 'ongoing' 20 | - if r.ended_at 21 | = time_ago r.ended_at 22 | %td= r.ended_at.try(:-, r.created_at) or 'ongoing' 23 | %td 24 | = link_to 'show', admin_round_path(r) 25 | -------------------------------------------------------------------------------- /app/views/admin/replacements/_form.html.haml: -------------------------------------------------------------------------------- 1 | = form_for [:admin, @replacement] do |f| 2 | - if @replacement.errors.any? 3 | #error_explanation 4 | %h2= "#{pluralize(@replacement.errors.count, "error")} prohibited this replacement from being saved:" 5 | %ul 6 | - @replacement.errors.full_messages.each do |message| 7 | %li= message 8 | 9 | .field 10 | = f.label :team 11 | = f.select :team_id, Team.all.map{|t| [t.certname, t.id]} 12 | .field 13 | = f.label :service_id, 'Service' 14 | = f.select :service_id, Service.all.map {|s| [s.name, s.id] } 15 | .field 16 | = f.label :round_id 17 | = f.text_field :round_id 18 | .field 19 | = f.label :file 20 | = f.file_field :file 21 | .actions 22 | = f.submit 'Save' 23 | -------------------------------------------------------------------------------- /config/initializers/assets.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Version of your assets, change this if you want to expire all your assets. 4 | Rails.application.config.assets.version = '1.0' 5 | 6 | # Add additional assets to the asset load path. 7 | # Rails.application.config.assets.paths << Emoji.images_path 8 | # Add Yarn node_modules folder to the asset load path. 9 | Rails.application.config.assets.paths << Rails.root.join('node_modules') 10 | Rails.application.config.assets.paths << Rails.root.join('app', 'assets', 'fonts') 11 | 12 | # Precompile additional assets. 13 | # application.js, application.css, and all non-JS/CSS in the app/assets 14 | # folder are already added. 15 | Rails.application.config.assets.precompile += %w( application.css ) 16 | -------------------------------------------------------------------------------- /app/models/service_state.rb: -------------------------------------------------------------------------------- 1 | class ServiceState 2 | attr_accessor :redis, :state_name, :state_body 3 | 4 | def initialize(state_name, state_body={}) 5 | self.redis = $redis 6 | self.state_name = state_name 7 | self.state_body = { entered_at: Time.now.to_f }.merge state_body 8 | end 9 | 10 | def channel_name 11 | "scorebot_service_state_#{Rails.env}" 12 | end 13 | 14 | def key_name 15 | "scorebot_service_current_state_#{Rails.env}" 16 | end 17 | 18 | def message_body 19 | { 20 | published_at: Time.now.to_f, 21 | state_name: state_name, 22 | state_body: state_body, 23 | } 24 | end 25 | 26 | def publish! 27 | redis.publish channel_name, message_body.to_json 28 | redis.set key_name, message_body.to_json 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /scripts/babyecho/deposit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | my $SERVICE = 'babyecho'; 4 | 5 | if ($#ARGV+1 != 3) { 6 | print "Usage: deposit-script \n"; 7 | exit(-1); 8 | } 9 | 10 | $SIG{ALRM} = sub { print "timeout\n"; }; 11 | alarm(10); 12 | 13 | my $TEAM = $ARGV[0]; 14 | my $IP = "10.5.$TEAM.2"; 15 | my $TOKEN = $ARGV[1]; 16 | 17 | `logger -t "$SERVICE-Deposit" "Attempting token deposit on $IP"`; 18 | 19 | #`ssh -o ConnectTimeout=2 root\@$IP "echo '$TOKEN' > /home/$SERVICE/flag; chown root /home/$SERVICE/flag; chgrp $SERVICE /home/$SERVICE/flag; chmod 640 /home/$SERVICE/flag"`; 20 | `ssh -o ConnectTimeout=2 root\@$IP "echo '$TOKEN' > /home/$SERVICE/flag"`; 21 | 22 | if ($? == 0) { 23 | `logger -t "$SERVICE-Deposit" "Success"`; 24 | } else { 25 | `logger -t "$SERVICE-Deposit" "Fail"`; 26 | } 27 | -------------------------------------------------------------------------------- /app/helpers/application_helper.rb: -------------------------------------------------------------------------------- 1 | module ApplicationHelper 2 | def body_class(options = {}) 3 | extra_body_classes_symbol = options[:extra_body_classes_symbol] || :extra_body_classes 4 | qualified_controller_name = controller.controller_path.gsub('/','-') 5 | basic_body_class = "#{qualified_controller_name} #{qualified_controller_name}-#{controller.action_name}" 6 | 7 | if content_for?(extra_body_classes_symbol) 8 | [basic_body_class, content_for(extra_body_classes_symbol)].join(' ') 9 | else 10 | basic_body_class 11 | end 12 | end 13 | 14 | def team_logo(team) 15 | team_id = team if team.is_a? Integer 16 | team_id = team if team.is_a? String 17 | team_id = team.id if team.is_a? Team 18 | 19 | image_tag "teams/#{team_id}.png", class: 'team_logo' 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /app/views/admin/availabilities/index.html.haml: -------------------------------------------------------------------------------- 1 | %h1 Availabilities 2 | 3 | %nav 4 | = link_to 'older', admin_availabilities_path(offset: @oof + 200) 5 | = link_to 'newer', admin_availabilities_path(offset: @oof - 200) 6 | 7 | %table 8 | %thead 9 | %tr 10 | %th id 11 | %th instance 12 | %th when 13 | %th healthy? 14 | %th legit? 15 | %th activities 16 | %tbody 17 | - @availabilities.each do |a| 18 | %tr 19 | %td= link_to a.id, admin_availability_path(a) 20 | %td= describe_instance a.instance 21 | %td{title: a.created_at} 22 | = time_ago_in_words a.created_at, include_seconds: true 23 | ago 24 | %td= a.healthy? 25 | %td= a.legit_dingus?.pretty_inspect 26 | %td 27 | = link_to 'show', admin_availability_path(a) 28 | -------------------------------------------------------------------------------- /scripts/half/deposit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | my $SERVICE = 'half'; 4 | 5 | if ($#ARGV+1 != 3) { 6 | print "Usage: deposit-script \n"; 7 | exit(-1); 8 | } 9 | 10 | $SIG{ALRM} = sub { print "timeout\n"; }; 11 | alarm(10); 12 | 13 | my $TEAM = $ARGV[0]; 14 | my $IP = "10.5.$TEAM.2"; 15 | my $TOKEN = $ARGV[1]; 16 | 17 | `logger -t "$SERVICE-Deposit" "Attempting token deposit on $IP"`; 18 | 19 | #`ssh -o ConnectTimeout=2 root\@$IP "echo '$TOKEN' > /home/$SERVICE/flag; chown root /home/$SERVICE/flag; chgrp $SERVICE /home/$SERVICE/flag; chmod 640 /home/$SERVICE/flag"`; 20 | `ssh -o ConnectTimeout=2 root\@$IP "echo '$TOKEN' > /home/$SERVICE/flag"`; 21 | 22 | if ($? == 0) { 23 | `logger -t "$SERVICE-Deposit" "Success"`; 24 | } else { 25 | `logger -t "$SERVICE-Deposit" "Fail"`; 26 | } 27 | -------------------------------------------------------------------------------- /scripts/rubix/deposit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | my $SERVICE = 'rubix'; 4 | 5 | if ($#ARGV+1 != 3) { 6 | print "Usage: deposit-script \n"; 7 | exit(-1); 8 | } 9 | 10 | $SIG{ALRM} = sub { print "timeout\n"; }; 11 | alarm(10); 12 | 13 | my $TEAM = $ARGV[0]; 14 | my $IP = "10.5.$TEAM.2"; 15 | my $TOKEN = $ARGV[1]; 16 | 17 | `logger -t "$SERVICE-Deposit" "Attempting token deposit on $IP"`; 18 | 19 | #`ssh -o ConnectTimeout=2 root\@$IP "echo '$TOKEN' > /home/$SERVICE/flag; chown root /home/$SERVICE/flag; chgrp $SERVICE /home/$SERVICE/flag; chmod 640 /home/$SERVICE/flag"`; 20 | `ssh -o ConnectTimeout=2 root\@$IP "echo '$TOKEN' > /home/$SERVICE/flag"`; 21 | 22 | if ($? == 0) { 23 | `logger -t "$SERVICE-Deposit" "Success"`; 24 | } else { 25 | `logger -t "$SERVICE-Deposit" "Fail"`; 26 | } 27 | -------------------------------------------------------------------------------- /scripts/internet3/deposit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | my $SERVICE = 'internet3'; 4 | 5 | if ($#ARGV+1 != 3) { 6 | print "Usage: deposit-script \n"; 7 | exit(-1); 8 | } 9 | 10 | $SIG{ALRM} = sub { print "timeout\n"; }; 11 | alarm(10); 12 | 13 | my $TEAM = $ARGV[0]; 14 | my $IP = "10.5.$TEAM.2"; 15 | my $TOKEN = $ARGV[1]; 16 | 17 | `logger -t "$SERVICE-Deposit" "Attempting token deposit on $IP"`; 18 | 19 | #`ssh -o ConnectTimeout=2 root\@$IP "echo '$TOKEN' > /home/$SERVICE/flag; chown root /home/$SERVICE/flag; chgrp $SERVICE /home/$SERVICE/flag; chmod 640 /home/$SERVICE/flag"`; 20 | `ssh -o ConnectTimeout=2 root\@$IP "echo '$TOKEN' > /home/$SERVICE/flag"`; 21 | 22 | if ($? == 0) { 23 | `logger -t "$SERVICE-Deposit" "Success"`; 24 | } else { 25 | `logger -t "$SERVICE-Deposit" "Fail"`; 26 | } 27 | -------------------------------------------------------------------------------- /scripts/legitbbs/deposit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | my $SERVICE = 'legitbbs'; 4 | 5 | if ($#ARGV+1 != 3) { 6 | print "Usage: deposit-script \n"; 7 | exit(-1); 8 | } 9 | 10 | $SIG{ALRM} = sub { print "timeout\n"; }; 11 | alarm(10); 12 | 13 | my $TEAM = $ARGV[0]; 14 | my $IP = "10.5.$TEAM.2"; 15 | my $TOKEN = $ARGV[1]; 16 | 17 | `logger -t "$SERVICE-Deposit" "Attempting token deposit on $IP"`; 18 | 19 | #`ssh -o ConnectTimeout=2 root\@$IP "echo '$TOKEN' > /home/$SERVICE/flag; chown root /home/$SERVICE/flag; chgrp $SERVICE /home/$SERVICE/flag; chmod 640 /home/$SERVICE/flag"`; 20 | `ssh -o ConnectTimeout=2 root\@$IP "echo '$TOKEN' > /home/$SERVICE/flag"`; 21 | 22 | if ($? == 0) { 23 | `logger -t "$SERVICE-Deposit" "Success"`; 24 | } else { 25 | `logger -t "$SERVICE-Deposit" "Fail"`; 26 | } 27 | -------------------------------------------------------------------------------- /scripts/quarter/deposit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | my $SERVICE = 'quarter'; 4 | 5 | if ($#ARGV+1 != 3) { 6 | print "Usage: deposit-script \n"; 7 | exit(-1); 8 | } 9 | 10 | $SIG{ALRM} = sub { print "timeout\n"; }; 11 | alarm(10); 12 | 13 | my $TEAM = $ARGV[0]; 14 | my $IP = "10.5.$TEAM.2"; 15 | my $TOKEN = $ARGV[1]; 16 | 17 | `logger -t "$SERVICE-Deposit" "Attempting token deposit on $IP"`; 18 | 19 | #`ssh -o ConnectTimeout=2 root\@$IP "echo '$TOKEN' > /home/$SERVICE/flag; chown root /home/$SERVICE/flag; chgrp $SERVICE /home/$SERVICE/flag; chmod 640 /home/$SERVICE/flag"`; 20 | `ssh -o ConnectTimeout=2 root\@$IP "echo '$TOKEN' > /home/$SERVICE/flag"`; 21 | 22 | if ($? == 0) { 23 | `logger -t "$SERVICE-Deposit" "Success"`; 24 | } else { 25 | `logger -t "$SERVICE-Deposit" "Fail"`; 26 | } 27 | -------------------------------------------------------------------------------- /scripts/trackerd/deposit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | my $SERVICE = 'trackerd'; 4 | 5 | if ($#ARGV+1 != 3) { 6 | print "Usage: deposit-script \n"; 7 | exit(-1); 8 | } 9 | 10 | $SIG{ALRM} = sub { print "timeout\n"; }; 11 | alarm(10); 12 | 13 | my $TEAM = $ARGV[0]; 14 | my $IP = "10.5.$TEAM.2"; 15 | my $TOKEN = $ARGV[1]; 16 | 17 | `logger -t "$SERVICE-Deposit" "Attempting token deposit on $IP"`; 18 | 19 | #`ssh -o ConnectTimeout=2 root\@$IP "echo '$TOKEN' > /home/$SERVICE/flag; chown root /home/$SERVICE/flag; chgrp $SERVICE /home/$SERVICE/flag; chmod 640 /home/$SERVICE/flag"`; 20 | `ssh -o ConnectTimeout=2 root\@$IP "echo '$TOKEN' > /home/$SERVICE/flag"`; 21 | 22 | if ($? == 0) { 23 | `logger -t "$SERVICE-Deposit" "Success"`; 24 | } else { 25 | `logger -t "$SERVICE-Deposit" "Fail"`; 26 | } 27 | -------------------------------------------------------------------------------- /scripts/babysfirst/deposit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | my $SERVICE = 'babysfirst'; 4 | 5 | if ($#ARGV+1 != 3) { 6 | print "Usage: deposit-script \n"; 7 | exit(-1); 8 | } 9 | 10 | $SIG{ALRM} = sub { print "timeout\n"; }; 11 | alarm(10); 12 | 13 | my $TEAM = $ARGV[0]; 14 | my $IP = "10.5.$TEAM.2"; 15 | my $TOKEN = $ARGV[1]; 16 | 17 | `logger -t "$SERVICE-Deposit" "Attempting token deposit on $IP"`; 18 | 19 | #`ssh -o ConnectTimeout=2 root\@$IP "echo '$TOKEN' > /home/$SERVICE/flag; chown root /home/$SERVICE/flag; chgrp $SERVICE /home/$SERVICE/flag; chmod 640 /home/$SERVICE/flag"`; 20 | `ssh -o ConnectTimeout=2 root\@$IP "echo '$TOKEN' > /home/$SERVICE/flag"`; 21 | 22 | if ($? == 0) { 23 | `logger -t "$SERVICE-Deposit" "Success"`; 24 | } else { 25 | `logger -t "$SERVICE-Deposit" "Fail"`; 26 | } 27 | -------------------------------------------------------------------------------- /scripts/picturemgr/deposit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | my $SERVICE = 'picturemgr'; 4 | 5 | if ($#ARGV+1 != 3) { 6 | print "Usage: deposit-script \n"; 7 | exit(-1); 8 | } 9 | 10 | $SIG{ALRM} = sub { print "timeout\n"; }; 11 | alarm(10); 12 | 13 | my $TEAM = $ARGV[0]; 14 | my $IP = "10.5.$TEAM.2"; 15 | my $TOKEN = $ARGV[1]; 16 | 17 | `logger -t "$SERVICE-Deposit" "Attempting token deposit on $IP"`; 18 | 19 | #`ssh -o ConnectTimeout=2 root\@$IP "echo '$TOKEN' > /home/$SERVICE/flag; chown root /home/$SERVICE/flag; chgrp $SERVICE /home/$SERVICE/flag; chmod 640 /home/$SERVICE/flag"`; 20 | `ssh -o ConnectTimeout=2 root\@$IP "echo '$TOKEN' > /home/$SERVICE/flag"`; 21 | 22 | if ($? == 0) { 23 | `logger -t "$SERVICE-Deposit" "Success"`; 24 | } else { 25 | `logger -t "$SERVICE-Deposit" "Fail"`; 26 | } 27 | -------------------------------------------------------------------------------- /app/views/admin/replacements/index.html.haml: -------------------------------------------------------------------------------- 1 | %h1 Listing replacements 2 | 3 | %table 4 | %thead 5 | %tr 6 | %th id 7 | %th Team 8 | %th Service 9 | %th Round 10 | %th Digest 11 | %th 12 | %th 13 | %th 14 | 15 | %tbody 16 | - @replacements.each do |replacement| 17 | %tr 18 | %td= replacement.id 19 | %td= replacement.team.name 20 | %td= replacement.service.name 21 | %td= replacement.round.id 22 | %td= replacement.digest.truncate(16) 23 | %td= link_to 'Show', admin_replacement_path(replacement) 24 | %td= link_to 'Edit', edit_admin_replacement_path(replacement) 25 | %td= link_to 'Destroy', replacement, method: :delete, data: { confirm: 'Are you sure?' } 26 | 27 | %br 28 | 29 | = link_to 'New Replacement', new_admin_replacement_path 30 | -------------------------------------------------------------------------------- /app/views/admin/redemptions/show.html.haml: -------------------------------------------------------------------------------- 1 | %h1 Redemption 2 | 3 | %table 4 | %tbody 5 | %tr 6 | %th team 7 | %td= link_to @redemption.team.name, admin_team_path(@redemption.team) 8 | %tr 9 | %th token 10 | %td= describe_token @redemption.token 11 | %tr 12 | %th instance 13 | %td= describe_instance @redemption.token.instance 14 | %tr 15 | %th round 16 | %td= link_to @redemption.round.id, admin_round_path(@redemption.round) 17 | %tr 18 | %th created_at 19 | %td{title: @redemption.created_at} 20 | = @redemption.created_at.to_formatted_s(:ctf) 21 | = time_ago @redemption.created_at 22 | 23 | %h2 Captures 24 | 25 | %table 26 | %thead 27 | %tr 28 | %th id 29 | %th activities 30 | %tbody 31 | - @redemption.captures.each do |c| 32 | %tr 33 | %th= c.id 34 | -------------------------------------------------------------------------------- /app/assets/stylesheets/admin.sass: -------------------------------------------------------------------------------- 1 | body.admin 2 | color: $light_amber 3 | nav 4 | width: 960px 5 | margin: 0 auto 6 | font-family: Ubuntu 7 | ul li 8 | color: white 9 | a:link 10 | font-family: Ubuntu 11 | .content 12 | background-color: $dark_amber 13 | border: 1px solid $light_amber 14 | padding: 1em 15 | box-sizing: content-box 16 | 17 | [title] 18 | border-bottom: 1px dotted $light_amber 19 | 20 | input[type='submit'].tiny_search 21 | padding: 2px 22 | border-width: 1px 23 | font-size: 11px 24 | 25 | table.instances 26 | td 27 | padding: 4px 28 | 29 | table#availabilities 30 | td.false 31 | background-color: #400 32 | 33 | body.admin-services-availabilities-index 34 | table#availabilities 35 | th, td 36 | width: 50px 37 | max-width: 50px 38 | min-width: 50px 39 | overflow: hidden 40 | -------------------------------------------------------------------------------- /bin/redistribution-reprocess: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | $stderr.puts "#{Time.now} Loading reprocess script" 3 | require ::File.expand_path('../../config/environment', __FILE__) 4 | 5 | puts "if you aren't running this on a copy of the db..." 6 | puts "you already fucked up, hit ctrl-c now" 7 | gets 8 | 9 | livectf_services = %w{ 10 | livectf_quals livectf_finals 11 | }.map do |service_name| 12 | Service.find_by(name: service_name) 13 | end 14 | 15 | Flag.delete_all 16 | Flag.initial_distribution 17 | Flag.collect_livectf_flags livectf_services 18 | 19 | Capture.delete_all 20 | Penalty.delete_all 21 | Round.update_all distribution: nil, signature: nil, payload: nil 22 | 23 | rounds = Round.order(id: :asc).all 24 | services = Service.all 25 | 26 | puts "preparing to reprocess #{rounds.count} rounds" 27 | 28 | rounds.each do |r| 29 | print "\r#{r.id}" 30 | r.finalize! 31 | end 32 | puts 33 | -------------------------------------------------------------------------------- /bin/update: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'pathname' 3 | require 'fileutils' 4 | include FileUtils 5 | 6 | # path to your application root. 7 | APP_ROOT = Pathname.new File.expand_path('../../', __FILE__) 8 | 9 | def system!(*args) 10 | system(*args) || abort("\n== Command #{args} failed ==") 11 | end 12 | 13 | chdir APP_ROOT do 14 | # This script is a way to update your development environment automatically. 15 | # Add necessary update steps to this file. 16 | 17 | puts '== Installing dependencies ==' 18 | system! 'gem install bundler --conservative' 19 | system('bundle check') || system!('bundle install') 20 | 21 | puts "\n== Updating database ==" 22 | system! 'bin/rails db:migrate' 23 | 24 | puts "\n== Removing old logs and tempfiles ==" 25 | system! 'bin/rails log:clear tmp:clear' 26 | 27 | puts "\n== Restarting application server ==" 28 | system! 'bin/rails restart' 29 | end 30 | -------------------------------------------------------------------------------- /app/models/shell_process.rb: -------------------------------------------------------------------------------- 1 | require 'open3' 2 | class ShellProcess 3 | def initialize(directory, *args) 4 | @directory = directory 5 | @args = args.map(&:to_s) 6 | end 7 | 8 | def success? 9 | guard_run 10 | @status.success? 11 | end 12 | 13 | def output 14 | guard_run 15 | @output 16 | end 17 | 18 | def status 19 | guard_run 20 | @status 21 | end 22 | 23 | private 24 | def guard_run 25 | has_run? || run 26 | end 27 | 28 | def has_run? 29 | @status || false 30 | end 31 | 32 | def run 33 | return if has_run? 34 | 35 | Scorebot.log "running #{@args.join ' '}" 36 | 37 | @output = '' 38 | 39 | cmd = "./#{@args.join ' '}" 40 | 41 | Dir.chdir @directory do 42 | IO.popen cmd, 'r', err: %i{child out} do |stdout| 43 | @output << stdout.read 44 | end 45 | end 46 | 47 | @status = $? 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | ENV["RAILS_ENV"] ||= "test" 2 | require File.expand_path('../../config/environment', __FILE__) 3 | require 'rails/test_help' 4 | 5 | class ActiveSupport::TestCase 6 | ActiveRecord::Migration.check_pending! 7 | 8 | include Shoulda::Matchers::ActiveRecord 9 | extend Shoulda::Matchers::ActiveRecord 10 | include Shoulda::Matchers::ActiveModel 11 | extend Shoulda::Matchers::ActiveModel 12 | 13 | # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order. 14 | # 15 | # Note: You'll currently still have to declare fixtures explicitly in integration tests 16 | # -- they do not yet inherit this setting 17 | # fixtures :all 18 | 19 | # Add more helper methods to be used by all tests here... 20 | 21 | def assert_uniqueness_constraint(&block) 22 | assert_raises(ActiveRecord::RecordNotUnique, &block) 23 | end 24 | end 25 | 26 | require 'mocha/setup' 27 | -------------------------------------------------------------------------------- /app/views/admin/tokens/show.html.haml: -------------------------------------------------------------------------------- 1 | %h1 Token 2 | 3 | %table 4 | %tr 5 | %th instance 6 | %td= describe_instance @token.instance 7 | %tr 8 | %th round 9 | %td= link_to @token.round.id, admin_round_path(@token.round) 10 | %tr 11 | %th key 12 | %td= @token.key 13 | %tr 14 | %th looks like 15 | %td= @token.to_fake_string 16 | 17 | %h2 Redemptions 18 | %table 19 | %thead 20 | %th id 21 | %th team 22 | %th round 23 | %th captured 24 | %th actions 25 | %tbody 26 | - @token.redemptions.each do |r| 27 | %tr 28 | %td= link_to r.id, admin_redemption_path(r) 29 | %td= link_to r.team.certname, admin_team_path(r.team) 30 | %td= link_to r.round.id, admin_round_path(r.round) 31 | %td= r.captures.count 32 | %td= link_to 'show', admin_redemption_path(r) 33 | 34 | %h2 Deposit 35 | %p Status: #{@token.status} 36 | 37 | %pre= @token.memo 38 | -------------------------------------------------------------------------------- /db/migrate/20150516180242_scorebaord_materialized_view.rb: -------------------------------------------------------------------------------- 1 | class ScorebaordMaterializedView < ActiveRecord::Migration 2 | def up 3 | Team.connection.execute <<-SQL 4 | CREATE MATERIALIZED VIEW scoreboards AS 5 | SELECT t.id as id, t.name as name, count(f.id) as score 6 | FROM teams as t 7 | left JOIN flags AS f 8 | ON f.team_id = t.id 9 | WHERE t.certname != 'legitbs' 10 | GROUP BY t.id, t.name 11 | ORDER BY 12 | score desc, 13 | t.name asc 14 | WITH DATA 15 | SQL 16 | 17 | Team.connection.execute <<-SQL 18 | CREATE UNIQUE INDEX ON scoreboards (id) 19 | SQL 20 | end 21 | 22 | def down 23 | Team.connection.execute "DROP TRIGGER IF EXISTS scoreboard_update_trigger ON flags" 24 | Team.connection.execute "DROP MATERIALIZED VIEW IF EXISTS scoreboards" 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /Capfile: -------------------------------------------------------------------------------- 1 | # Load DSL and Setup Up Stages 2 | require 'capistrano/setup' 3 | 4 | # Includes default deployment tasks 5 | require 'capistrano/deploy' 6 | 7 | # Includes tasks from other gems included in your Gemfile 8 | # 9 | # For documentation on these, see for example: 10 | # 11 | # https://github.com/capistrano/rvm 12 | # https://github.com/capistrano/rbenv 13 | # https://github.com/capistrano/chruby 14 | # https://github.com/capistrano/bundler 15 | # https://github.com/capistrano/rails 16 | # 17 | # require 'capistrano/rvm' 18 | # require 'capistrano/rbenv' 19 | # require 'capistrano/chruby' 20 | # require 'capistrano/bundler' 21 | # require 'capistrano/rails/assets' 22 | # require 'capistrano/rails/migrations' 23 | 24 | require 'capistrano/bundler' 25 | require 'capistrano/rails' 26 | 27 | # Loads custom tasks from `lib/capistrano/tasks' if you have any defined. 28 | Dir.glob('lib/capistrano/tasks/*.rake').each { |r| import r } 29 | -------------------------------------------------------------------------------- /app/controllers/redemption_controller.rb: -------------------------------------------------------------------------------- 1 | class RedemptionController < ApplicationController 2 | protect_from_forgery except: :create 3 | 4 | def create 5 | tokens = params[:tokens] || [] 6 | successful = [] 7 | redemptions = tokens.inject({}) do |m, t| 8 | next m if t.blank? 9 | r = begin 10 | StatsD.measure "#{current_team.certname}.redeem_for" do 11 | rr = Redemption.redeem_for(current_team, t) 12 | successful << rr 13 | rr.uuid 14 | end 15 | rescue => e 16 | Scorebot.log "Redeem #{t} failed for #{current_team.try(:name)}: #{e.message}" 17 | "error: #{e.message}" 18 | end 19 | m[t] = r 20 | m 21 | end 22 | 23 | successful.each do |r| 24 | Event.new('redemption', r.as_event_json ).publish! rescue nil 25 | end 26 | 27 | render json: redemptions.to_json 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /config/application.rb: -------------------------------------------------------------------------------- 1 | require_relative 'boot' 2 | 3 | require 'rails/all' 4 | 5 | # Require the gems listed in Gemfile, including any gems 6 | # you've limited to :test, :development, or :production. 7 | Bundler.require(*Rails.groups) 8 | 9 | module Scorebot 10 | class Application < Rails::Application 11 | # Initialize configuration defaults for originally generated Rails version. 12 | config.load_defaults 5.1 13 | 14 | # Settings in config/environments/* take precedence over those specified here. 15 | # Application configuration should go into files in config/initializers 16 | # -- all .rb files in that directory are automatically loaded. 17 | end 18 | 19 | 20 | def self.log(*args) 21 | timestamp = Time.now.to_s 22 | body = args.map{|a| a.is_a?(String) ? a : a.inspect }.join(' ') 23 | 24 | Rails.logger.info "#{timestamp} [scorebot] #{body}" 25 | unless Rails.env.test? 26 | $stderr.puts body 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /bin/2014-day2-downtime: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | $stderr.puts "#{Time.now} Loading 2014 reprocess script" 3 | require ::File.expand_path('../../config/environment', __FILE__) 4 | 5 | puts "if you aren't running this on a copy of the db..." 6 | puts "you already fucked up, hit ctrl-c now" 7 | gets 8 | 9 | rt = Team.find_by name: 'Routards' 10 | rt_rounds = Round.where id: [118, 197] 11 | 12 | rt_avs = Availability.where(instance: rt.instances, round: rt_rounds) 13 | 14 | rt_avs.each(&:fix_availability) 15 | binding.pry 16 | bl = Team.find_by name: 'BalalaikaCr3w' 17 | bl_rounds = Round.where id: [217, 216, 159, 127, 123, 63, 62, 61, 60] 18 | 19 | bl_avs = Availability.where(instance: bl.instances, round: bl_rounds) 20 | 21 | bl_avs.each(&:fix_availability) 22 | 23 | sp = Team.find_by name: 'shellphish' 24 | sp_rounds = Round.where id: [192, 191, 165, 74, 72] 25 | 26 | sp_avs = Availability.where(instance: sp.instances, round: sp_rounds) 27 | 28 | sp_avs.each(&:fix_availability) 29 | -------------------------------------------------------------------------------- /bin/2015-day3-downtime: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | $stderr.puts "#{Time.now} Loading 2015 reprocess script" 3 | require ::File.expand_path('../../config/environment', __FILE__) 4 | 5 | puts "if you aren't running this on a copy of the db..." 6 | puts "you already fucked up, hit ctrl-c now" 7 | gets 8 | 9 | ci20_services = Service.where(name: %w{irkd ombdsu}) 10 | 11 | samurai = Team.find_by certname: 'samurai' 12 | samurai_rounds = Round.where id: (253..291) 13 | samurai_instances = samurai.instances.where(service: ci20_services) 14 | 15 | samurai_avs = Availability. 16 | where(instance: samurai_instances, round: samurai_rounds) 17 | 18 | samurai_avs.each(&:fix_availability) 19 | 20 | badlogger = Service.find_by name: 'badlogger' 21 | badlogger_rounds = Round.where 'id < 245' 22 | 23 | badlogger_avs = Availability. 24 | where(instance: badlogger.instances, round: badlogger_rounds) 25 | 26 | badlogger_avs.each(&:fix_availability) 27 | 28 | binding.pry 29 | -------------------------------------------------------------------------------- /app/controllers/admin/teams_controller.rb: -------------------------------------------------------------------------------- 1 | class Admin::TeamsController < Admin::BaseController 2 | helper_method :team 3 | def index 4 | case params[:order] 5 | when 'alpha' 6 | @teams = Team.order(name: :asc) 7 | when 'winning' 8 | @teams = Team.all.sort_by{ |t| - t.flags.count } 9 | else 10 | @teams = Team.order(id: :asc) 11 | end 12 | end 13 | 14 | def show 15 | @round_limit = params[:limit] || 24 16 | 17 | team 18 | @services = Service.order(name: :asc) 19 | @rounds = Round.order(id: :desc).limit(@round_limit) 20 | end 21 | 22 | def edit 23 | end 24 | 25 | def update 26 | team.update_attributes! team_params 27 | redirect_to admin_team_path team 28 | end 29 | 30 | private 31 | def team 32 | @team ||= Team.find params[:id] 33 | end 34 | 35 | def team_params 36 | params.require(:team).permit(:display).tap do |tp| 37 | tp[:display] = nil if tp[:display].blank? 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /lib/tasks/db.rake: -------------------------------------------------------------------------------- 1 | namespace :db do 2 | desc 'Dump out the scorebot to a pg custom dump' 3 | task :dump => 'dump:custom' 4 | 5 | namespace :dump do 6 | task :custom => :environment do 7 | out_dir = Rails.root.join 'tmp', 'dumps' 8 | FileUtils.mkdir_p out_dir 9 | output_filename = Rails.root.join('tmp', 10 | 'dumps', 11 | "#{Time.now.to_i}-#{Rails.env}.dump") 12 | 13 | dbname = Scorebot::Application.config. 14 | database_configuration[Rails.env]['database'] 15 | 16 | sh "pg_dump -Fc -f #{output_filename} -U postgres -h db #{dbname}" 17 | end 18 | 19 | task :sql => :environment do 20 | output_filename = File.join '/home/scorebot', "#{Time.now.to_i}-#{Rails.env}.sql" 21 | dbname = Scorebot::Application.config.database_configuration[Rails.env]['database'] 22 | sh "pg_dump -Fp -f #{output_filename} #{dbname}" 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /config/locales/en.yml: -------------------------------------------------------------------------------- 1 | # Files in the config/locales directory are used for internationalization 2 | # and are automatically loaded by Rails. If you want to use locales other 3 | # than English, add the necessary files in this directory. 4 | # 5 | # To use the locales, use `I18n.t`: 6 | # 7 | # I18n.t 'hello' 8 | # 9 | # In views, this is aliased to just `t`: 10 | # 11 | # <%= t('hello') %> 12 | # 13 | # To use a different locale, set it with `I18n.locale`: 14 | # 15 | # I18n.locale = :es 16 | # 17 | # This would use the information in config/locales/es.yml. 18 | # 19 | # The following keys must be escaped otherwise they will not be retrieved by 20 | # the default I18n backend: 21 | # 22 | # true, false, on, off, yes, no 23 | # 24 | # Instead, surround them with single quotes. 25 | # 26 | # en: 27 | # 'true': 'foo' 28 | # 29 | # To learn more, please read the Rails Internationalization guide 30 | # available at http://guides.rubyonrails.org/i18n.html. 31 | 32 | en: 33 | hello: "Hello world" 34 | -------------------------------------------------------------------------------- /app/controllers/admin/services/availabilities_controller.rb: -------------------------------------------------------------------------------- 1 | class Admin::Services::AvailabilitiesController < Admin::BaseController 2 | helper_method :service 3 | 4 | def index 5 | ordered_rounds = Round.order(id: :desc) 6 | if params[:rounds] == 'all' 7 | @rounds = ordered_rounds.all 8 | else 9 | @rounds = ordered_rounds.limit 25 10 | end 11 | 12 | @instances = service.instances 13 | availability_arr = Availability. 14 | where(round_id: @rounds.map(&:id), instance_id: @instances). 15 | order(round_id: :desc). 16 | joins(:instance) 17 | 18 | @teams = Team.order(id: :asc).all 19 | 20 | @availabilities_rounds = Hash.new() 21 | availability_arr.each do |av| 22 | @availabilities_rounds[av.round_id] ||= { } 23 | @availabilities_rounds[av.round_id][av.instance.team_id] = av 24 | end 25 | end 26 | 27 | private 28 | def service 29 | @service ||= Service.find params[:service_id] 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /lib/tasks/scoreboard.rake: -------------------------------------------------------------------------------- 1 | namespace :scoreboard do 2 | desc 'Export the current scoreboard to a ctftime compatible json' 3 | task :export => :environment do 4 | File.open(Rails.root.join('tmp', '2015_finals.json'), 'w') do |f| 5 | f.write Team.as_standings_json.to_json 6 | end 7 | end 8 | 9 | desc 'Upload the current scoreboard to legitbs.net' 10 | task :upload => :environment do 11 | begin 12 | s3 = Fog::Storage.new(provider: 'AWS', 13 | aws_access_key_id: ENV['AWS_ACCESS_KEY'], 14 | aws_secret_access_key: ENV['AWS_SECRET_KEY'], 15 | path_style: true 16 | ) 17 | 18 | bucket = s3.directories.get 'live.legitbs.net' 19 | bucket.files.create(key: '2015_finals.json', 20 | content_type: 'text/json', 21 | body: Team.as_standings_json.to_json 22 | ) 23 | 24 | rescue => e 25 | puts e 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /app/views/admin/services/show.html.haml: -------------------------------------------------------------------------------- 1 | %h1 Service: #{service.name} 2 | 3 | %nav 4 | %ul 5 | %li= link_to 'availabilities', admin_service_availabilities_path(service) 6 | %li= link_to 'edit', edit_admin_service_path(service) 7 | 8 | %table 9 | %tr 10 | %th name 11 | %td= service.name 12 | %tr 13 | %th id 14 | %td= service.id 15 | %tr 16 | %th enabled 17 | %td= !!service.enabled 18 | 19 | %h2 Instances 20 | %table 21 | %thead 22 | %tr 23 | %th instance id 24 | %th team (id) 25 | %th flags 26 | %th redemptions against 27 | %th activities 28 | %tbody 29 | - service.instances.joins(:team).sort_by{|i|i.team.name.downcase}.each do |i| 30 | %tr 31 | %td= i.id 32 | %td 33 | = link_to i.team.name, admin_instance_path(i) 34 | (#{i.team.id}) 35 | %td= i.flags.count 36 | %td= i.redemptions.count 37 | %td 38 | = link_to 'instance', admin_instance_path(i) 39 | = link_to 'team', admin_team_path(i.team) 40 | -------------------------------------------------------------------------------- /app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | class ApplicationController < ActionController::Base 2 | # Prevent CSRF attacks by raising an exception. 3 | # For APIs, you may want to use :null_session instead. 4 | protect_from_forgery with: :exception 5 | 6 | helper_method :current_team, :is_legitbs? 7 | 8 | before_action :require_team, :check_rmp 9 | 10 | def client_cn 11 | request.env['HTTP_SSL_CLIENT_S_DN_CN'] 12 | end 13 | 14 | def current_team 15 | if Rails.env.development? 16 | return @current_team ||= Team.legitbs 17 | end 18 | 19 | @current_team ||= Team.find_by uuid: client_cn 20 | end 21 | 22 | def is_legitbs? 23 | @is_legitbs ||= (current_team == Team.legitbs) 24 | end 25 | 26 | def require_team 27 | raise "Couldn't find cert for #{client_cn}" unless current_team 28 | end 29 | 30 | def check_rmp 31 | Rack::MiniProfiler.authorize_request if is_legitbs? 32 | end 33 | 34 | def require_legitbs 35 | raise ActionController::RoutingError.new('Not Found') unless is_legitbs? 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /app/models/dingus.rb: -------------------------------------------------------------------------------- 1 | class Dingus 2 | KEY = 'a' * 32 3 | 4 | def initialize(dingus, opts={ }) 5 | if opts[:plaintext] 6 | @dingus_pt = dingus 7 | else 8 | @dingus_ct = dingus 9 | end 10 | end 11 | 12 | def plaintext 13 | return @dingus_pt if defined? @dingus_pt 14 | 15 | c = cipher 16 | 17 | buf = c.update @dingus_ct 18 | @dingus_pt = buf + c.final 19 | end 20 | 21 | def legit? 22 | to_h[:tag] == 0xdc22 23 | end 24 | 25 | def team 26 | Team.find to_h[:team_num] 27 | end 28 | 29 | def to_h 30 | return @to_h if defined? @to_h 31 | 32 | tag, team_num, uid, clocktime, timesopened = plaintext.unpack("S>CSLL") 33 | 34 | @to_h = { 35 | tag: tag, 36 | team_num: team_num, 37 | uid: uid, 38 | clocktime: clocktime, 39 | timesopened: timesopened 40 | } 41 | end 42 | 43 | def cipher 44 | cipher = OpenSSL::Cipher.new 'AES-256-ECB' 45 | cipher.decrypt 46 | cipher.padding = 0 47 | cipher.key = KEY 48 | 49 | return cipher 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /app/models/instance.rb: -------------------------------------------------------------------------------- 1 | class Instance < ActiveRecord::Base 2 | belongs_to :team 3 | belongs_to :service 4 | 5 | has_many :tokens 6 | has_many :redemptions, through: :tokens 7 | 8 | has_many :availabilities 9 | 10 | def check_availability(round) 11 | availability = Availability.check self, round 12 | end 13 | 14 | def owned_check 15 | @owned_check ||= health_check_rounds.map do |r| 16 | redemptions.where(round: r).first 17 | end 18 | end 19 | 20 | def uptime_check 21 | @uptime_check ||= health_check_rounds.map do |r| 22 | availabilities.where(round: r, status: 0).first 23 | end 24 | end 25 | 26 | def total_redemptions 27 | tokens.sum(:redemptions_count) 28 | end 29 | 30 | def flags 31 | Flag.where(team_id: team.id, service_id: service.id) 32 | end 33 | 34 | def legitbs_instance 35 | Instance.find_by(team: Team.legitbs, service_id: service.id) 36 | end 37 | 38 | private 39 | def health_check_rounds 40 | @health_check_rounds ||= Round.limit(3).offset(1).order('created_at desc').reverse 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | db: 4 | image: postgres 5 | command: postgres -c max_connections=500 -c shared_buffers=512MB 6 | ports: 7 | - "5432:5432" 8 | dump: 9 | image: postgres 10 | command: bash 11 | volumes: 12 | - .:/scorebot 13 | depends_on: 14 | - db 15 | redis: 16 | image: redis 17 | ports: 18 | - "6379:6379" 19 | web: 20 | build: . 21 | command: bundle exec rails s -b '0.0.0.0' 22 | volumes: 23 | - .:/scorebot 24 | ports: 25 | - "3000:3000" 26 | environment: 27 | - RAILS_ENV=production 28 | depends_on: 29 | - db 30 | - redis 31 | httpd: 32 | build: ../scorebot-httpd 33 | volumes: 34 | - ../scorebot-httpd:/scorebot-httpd 35 | - .:/scorebot 36 | ports: 37 | - "443:443" 38 | depends_on: 39 | - web 40 | service: 41 | build: . 42 | command: bundle exec bash 43 | volumes: 44 | - .:/scorebot 45 | environment: 46 | - RAILS_ENV=production 47 | depends_on: 48 | - db 49 | - redis 50 | -------------------------------------------------------------------------------- /bin/2015-day3-livectf: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | $stderr.puts "#{Time.now} Loading 2015 reprocess script" 3 | require ::File.expand_path('../../config/environment', __FILE__) 4 | 5 | puts "if you aren't running this on a copy of the db..." 6 | puts "you already fucked up, hit ctrl-c now" 7 | puts "RUN ME AFTER REPROCESSING YO" 8 | gets 9 | 10 | 11 | Flag.where(team_id: 16, service_id: 10).limit(600).update_all(team_id: 5) # defkor 12 | Flag.where(team_id: 16, service_id: 10).limit(300).update_all(team_id: 15) # lcbc 13 | Flag.where(team_id: 16, service_id: 10).limit(200).update_all(team_id: 1) # ppp 14 | Flag.where(team_id: 16, service_id: 10).limit(100).update_all(team_id: 12) # 0day 15 | Flag.where(team_id: 16, service_id: 10).limit(100).update_all(team_id: 3) # samurai 16 | Flag.where(team_id: 16, service_id: 10).limit(100).update_all(team_id: 14) # shellphish 17 | Flag.where(team_id: 16, service_id: 10).limit(100).update_all(team_id: 4) # hitcon 18 | Flag.where(team_id: 16, service_id: 10).limit(100).update_all(team_id: 6) # team-9447 19 | Flag.where(team_id: 16, service_id: 11).limit(1000).update_all(team_id: 1) # ppp 20 | -------------------------------------------------------------------------------- /bin/2017-day2-downtime: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | $stderr.puts "#{Time.now} Loading 2017 day1 reprocess script" 3 | require ::File.expand_path('../../config/environment', __FILE__) 4 | 5 | puts "if you aren't running this on a copy of the db..." 6 | puts "you already fucked up, hit ctrl-c now" 7 | gets 8 | 9 | legitbbs = Service.find_by name: 'legitbbs' 10 | instances = legitbbs.instances 11 | avs = instances.map(&:availabilities).flatten 12 | 13 | avs.each(&:fix_availability) 14 | 15 | half = Service.find_by name: 'half' 16 | instances = half.instances 17 | avs = instances.map(&:availabilities).flatten 18 | 19 | half_digest = '757378e8601295ea389e97d18068c49a7b426c91d08ea0595693d3b60eb10f71' 20 | 21 | puts 22 | 23 | avs.each.with_index do |a, i| 24 | print "\r#{i}\t#{avs.count}" 25 | repl = Replacement. 26 | where('created_at < ?', a.created_at). 27 | where(team_id: a.instance.team_id, 28 | service_id: a.instance.service_id). 29 | order(created_at: :desc). 30 | first 31 | 32 | next if repl && (repl.digest != half_digest) 33 | 34 | a.fix_availability 35 | end 36 | -------------------------------------------------------------------------------- /app/assets/stylesheets/replacements.sass: -------------------------------------------------------------------------------- 1 | body.replacements 2 | .scene 3 | background-color: $dark_amber 4 | color: $pale_mustard 5 | border: 1px solid $pale_mustard 6 | padding: 8px 7 | margin: 0 -5px 8 | #filters 9 | border: solid $pale_mustard 10 | border-width: 1px 0 11 | padding: 1em 12 | margin-bottom: 1em 13 | h2 14 | @include permie 15 | margin: 0 16 | .btn 17 | @include button 18 | font-family: 'Ubuntu' 19 | nav 20 | ul 21 | margin: 0 22 | li 23 | margin: 0.35em 0 24 | &.label 25 | @include permie 26 | display: block 27 | a:link, a:visited 28 | font-size: 0.9em 29 | padding: 0.25em 30 | &[data-team-id='16'] 31 | background-color: black 32 | color: $light_purple 33 | border-color: $light_purple 34 | &.active 35 | border-width: 4px 36 | a:hover[data-team-id='16'] 37 | color: white 38 | border-color: white 39 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'pathname' 3 | require 'fileutils' 4 | include FileUtils 5 | 6 | # path to your application root. 7 | APP_ROOT = Pathname.new File.expand_path('../../', __FILE__) 8 | 9 | def system!(*args) 10 | system(*args) || abort("\n== Command #{args} failed ==") 11 | end 12 | 13 | chdir APP_ROOT do 14 | # This script is a starting point to setup your application. 15 | # Add necessary setup steps to this file. 16 | 17 | puts '== Installing dependencies ==' 18 | system! 'gem install bundler --conservative' 19 | system('bundle check') || system!('bundle install') 20 | 21 | # Install JavaScript dependencies if using Yarn 22 | # system('bin/yarn') 23 | 24 | 25 | # puts "\n== Copying sample files ==" 26 | # unless File.exist?('config/database.yml') 27 | # cp 'config/database.yml.sample', 'config/database.yml' 28 | # end 29 | 30 | puts "\n== Preparing database ==" 31 | system! 'bin/rails db:setup' 32 | 33 | puts "\n== Removing old logs and tempfiles ==" 34 | system! 'bin/rails log:clear tmp:clear' 35 | 36 | puts "\n== Restarting application server ==" 37 | system! 'bin/rails restart' 38 | end 39 | -------------------------------------------------------------------------------- /app/views/scoreboard/index.html.haml: -------------------------------------------------------------------------------- 1 | .scoreboard.scene 2 | = render partial: 'shared/timers' 3 | .scoreboard-holder 4 | %table.names 5 | %thead 6 | %tr 7 | %th.team_name team 8 | %th 9 | %tbody#scoreboard_body{data: {refresh_path: scoreboard_path(format: :json, refresh: rand(1_000_000)), refresh_interval: 15_000} } 10 | - place = 0 11 | - prev_score = Flag.count + 1 12 | - place_buf = 1 13 | - @teams.each do |t| 14 | - score = t[:score].to_i 15 | - if score < prev_score 16 | - place += place_buf 17 | - place_buf = 1 18 | - prev_score = score 19 | - else 20 | - place_buf += 1 21 | %tr 22 | %th= t[:display_name] 23 | %td.flags 24 | .points.left= 16 - place 25 | .bar.left{style: "width: #{24 - place}em"}   26 | %br.clear 27 | %script#team_row_template{type: 'text/html'} 28 | %tr 29 | %th {{display_name}} 30 | %td.flags 31 | .points.left {{score}} 32 | .bar.left{style: "width: {{width}}em"}   33 | %br.clear 34 | -------------------------------------------------------------------------------- /bin/lazertanz: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | $stderr.puts "#{Time.now} Loading lazertanz script" 3 | require ::File.expand_path('../../config/environment', __FILE__) 4 | 5 | RR = Redemption.order(id: :asc) 6 | 7 | given_duration = (1.5).hours.to_f 8 | 9 | per_redemption = given_duration / RR.count 10 | 11 | puts "going to show a redemption every #{per_redemption} seconds" 12 | 13 | puts "press enter to start sending #{RR.count} redemption events" 14 | gets 15 | 16 | def now_sec 17 | Time.now.to_f 18 | end 19 | 20 | def chillax(ending) 21 | if (ending - now_sec) > 5 22 | midpoint = now_sec + ((ending - now_sec) / 2) 23 | else 24 | # never reached 25 | midpoint = ending + 1 26 | end 27 | 28 | while now_sec < ending 29 | if (now_sec > midpoint) && (ending - now_sec > 0.1) 30 | midpoint = now_sec + ((ending - now_sec) / 2) 31 | end 32 | sleep 0.01 33 | end 34 | end 35 | 36 | RR.find_each.with_index do |r, i| 37 | next_one = Time.now.to_f + per_redemption 38 | if i % 10 == 0 39 | puts 40 | print "#{i}/#{RR.count}: " 41 | end 42 | 43 | print Event.new('redemption', r.as_event_json).publish! 44 | print ' ' 45 | 46 | chillax(next_one) 47 | end 48 | -------------------------------------------------------------------------------- /config/routes.rb: -------------------------------------------------------------------------------- 1 | Rails.application.routes.draw do 2 | get 'replacements/index' 3 | 4 | get 'replacements/show' 5 | 6 | get 'replacements/new' 7 | 8 | resources :tickets do 9 | member do 10 | post :resolve 11 | post :unresolve 12 | end 13 | end 14 | 15 | resources :rounds 16 | 17 | resources :replacements 18 | 19 | get "scoreboard", to: 'scoreboard#index', as: 'scoreboard' 20 | get "dashboard", to: 'dashboard#index', as: 'dashboard' 21 | get "howto", to: "high_voltage/pages#show", id: 'howto', as: 'howto' 22 | 23 | post "redeem", to: 'redemption#create', as: 'redemption' 24 | 25 | get 'timers', to: 'timers#index', as: 'timers' 26 | 27 | get 'livectf/:flag', to: 'livectf#capture', as: 'livectf' 28 | 29 | root to: redirect('/dashboard') 30 | 31 | namespace :admin do 32 | root to: 'root#index' 33 | resources :rounds, 34 | :tokens, 35 | :redemptions, 36 | :instances, 37 | :teams, 38 | :availabilities, 39 | :replacements 40 | 41 | resources :services do 42 | resources :availabilities, controller: 'services/availabilities' 43 | end 44 | end 45 | 46 | end 47 | -------------------------------------------------------------------------------- /app/assets/stylesheets/banner.sass: -------------------------------------------------------------------------------- 1 | body 2 | .banner 3 | width: 960px 4 | background-color: $dark_amber 5 | border: 1px solid $light_amber 6 | color: $pale_mustard 7 | padding: 4px 8 | margin: -1px auto 10px 9 | h1 10 | margin: 0 11 | margin-top: -3px 12 | margin-bottom: -1px 13 | float: left 14 | font-size: 1em 15 | @include permie 16 | p 17 | margin: 0 18 | margin-left: 1em 19 | float: left 20 | ul 21 | list-style-type: none 22 | margin: 0 23 | margin-right: 1em 24 | float: right 25 | li 26 | display: inline-block 27 | color: $dark_amber 28 | background-color: $amber 29 | margin: -4px 30 | margin-bottom: -6px 31 | padding: 4px 32 | padding-bottom: 5px 33 | margin-left: 0.5em 34 | font-weight: bold 35 | a:link, a:visited 36 | display: inline-block 37 | color: $light_amber 38 | margin: -4px 39 | margin-bottom: -6px 40 | padding: 4px 41 | padding-bottom: 5px 42 | background-color: $dark_amber 43 | text-decoration: none 44 | font-weight: 100 45 | -------------------------------------------------------------------------------- /app/assets/stylesheets/fonts.sass: -------------------------------------------------------------------------------- 1 | @font-face 2 | font-family: "Rambla" 3 | font-weight: 400 4 | font-style: normal 5 | src: local('Rambla') font-url('Rambla-Regular.otf') format('opentype') 6 | @font-face 7 | font-family: "Rambla" 8 | font-weight: 400 9 | font-style: italic 10 | src: local('Rambla') font-url('Rambla-Italic.otf') 11 | @font-face 12 | font-family: "Rambla" 13 | font-weight: 700 14 | font-style: normal 15 | src: local('Rambla') font-url('Rambla-Bold.otf') 16 | @font-face 17 | font-family: "Rambla" 18 | font-weight: 700 19 | font-style: italic 20 | src: local('Rambla') font-url('Rambla-BoldItalic.otf') 21 | @font-face 22 | font-family: "Michroma" 23 | font-weight: 400 24 | font-style: normal 25 | src: local('Michroma') font-url('Michroma.ttf') 26 | @font-face 27 | font-family: "Ubuntu Mono" 28 | font-weight: normal 29 | font-style: normal 30 | src: local('Ubuntu Mono') font-url('UbuntuMono-Regular.ttf') format('truetype') 31 | 32 | @font-face 33 | font-family: "Ubuntu" 34 | font-weight: 400 35 | font-style: normal 36 | src: font-url('Ubuntu-R.ttf') format('truetype') 37 | 38 | @font-face 39 | font-family: 'Permanent Marker' 40 | src: font-url('PermanentMarker.ttf') format('truetype') 41 | -------------------------------------------------------------------------------- /app/views/admin/availabilities/show.html.haml: -------------------------------------------------------------------------------- 1 | %h1 Availability 2 | 3 | %table 4 | %tbody 5 | %tr 6 | %th id 7 | %td= @availability.id 8 | %tr 9 | %th instance 10 | %td= describe_instance @availability.instance 11 | %tr 12 | %th when 13 | %td 14 | = @availability.created_at.to_formatted_s(:ctf) 15 | = time_ago @availability.created_at 16 | %tr 17 | %th healthy? (status) 18 | %td 19 | = @availability.healthy? 20 | (#{@availability.status}) 21 | %tr 22 | %th /dev/ctf dingus 23 | %td 24 | - if @availability.dingus.nil? 25 | %em none 26 | - else 27 | - dingus = Dingus.new(@availability.dingus, plaintext: true) 28 | %p= @availability.dingus.unpack('H*').first 29 | %pre= dingus.to_h.pretty_inspect 30 | %p 31 | legit? #{@availability.legit_dingus?} 32 | %tr 33 | %th token string 34 | %td 35 | = none_able @availability.token_string 36 | %tr 37 | %th token? 38 | %td 39 | - if @availability.token 40 | = link_to @availability.token.id, admin_token_path(@availability.token) 41 | - else 42 | = none_able nil 43 | 44 | %h2 Memo 45 | %pre= @availability.memo 46 | -------------------------------------------------------------------------------- /test/factories.rb: -------------------------------------------------------------------------------- 1 | FactoryGirl.define do 2 | factory :replacement do 3 | team nil 4 | service nil 5 | round nil 6 | digest "MyString" 7 | end 8 | factory :service do 9 | name 'atmail' 10 | end 11 | 12 | factory :team do 13 | sequence :name 14 | sequence :certname 15 | 16 | address '10.69.4.20' 17 | 18 | factory :legitbs do 19 | name 'legitbs' 20 | uuid 'deadbeef-7872-499a-a060-3143de953e28' 21 | end 22 | end 23 | 24 | factory :round do 25 | factory :current_round do 26 | created_at(Time.now - 2.minutes) 27 | ended_at nil 28 | end 29 | end 30 | 31 | factory :instance do 32 | service 33 | team 34 | factory :lbs_instance do 35 | association :team, factory: :legitbs 36 | end 37 | end 38 | 39 | factory :token do 40 | instance 41 | round 42 | end 43 | 44 | factory :availability do 45 | instance 46 | round 47 | token 48 | 49 | status 0 50 | memo 'okay!' 51 | 52 | factory :down_availability do 53 | status 1 54 | memo 'fuck!!!' 55 | end 56 | end 57 | 58 | factory :redemption do 59 | team 60 | round 61 | token 62 | end 63 | 64 | factory :capture do 65 | redemption 66 | flag 67 | round 68 | end 69 | 70 | factory :flag do 71 | team 72 | service 73 | end 74 | end 75 | -------------------------------------------------------------------------------- /app/views/tickets/index.html.haml: -------------------------------------------------------------------------------- 1 | %h1= @title 2 | 3 | %nav 4 | %ul 5 | %li= link_to 'New Ticket', new_ticket_path 6 | - if params[:scope] != 'all' 7 | %li= link_to 'Include resolved tickets', tickets_path(scope: 'all') 8 | - else 9 | %li= link_to 'Exclude resolved tickets', tickets_path 10 | - if @tickets.empty? 11 | %p no tickets 12 | - else 13 | %table 14 | %tr 15 | - if is_legitbs? 16 | %th Team 17 | %th Body 18 | %th Created at 19 | %th Resolved at 20 | %th fun activities 21 | 22 | - @tickets.each do |ticket| 23 | %tr 24 | - if is_legitbs? 25 | %td= ticket.team.name 26 | %td= ticket.body.truncate 20 27 | %td= time_ago ticket.created_at 28 | %td 29 | - if ticket.resolved_at 30 | = time_ago_in_words(ticket.resolved_at) 31 | ago 32 | - else 33 | unresolved 34 | %td 35 | %nav 36 | %ul 37 | %li= link_to 'Show', ticket 38 | %li= link_to 'Edit', edit_ticket_path(ticket) 39 | - if is_legitbs? && !ticket.resolved_at 40 | %li= link_to 'Resolve', resolve_ticket_path(ticket), method: :post 41 | - if ticket.resolved_at 42 | %li= link_to 'Unresolve', unresolve_ticket_path(ticket), method: :post 43 | -------------------------------------------------------------------------------- /app/assets/javascripts/scoreboard.js.coffee: -------------------------------------------------------------------------------- 1 | # Place all the behaviors and hooks related to the matching controller here. 2 | # All this logic will automatically be available in application.js. 3 | # You can use CoffeeScript in this file: http://coffeescript.org/ 4 | jQuery ($)-> 5 | return unless $('#team_row_template').length == 1 6 | 7 | 8 | calculateWidth = (score) -> 9 | 8 + score 10 | 11 | class Scoreboard 12 | constructor: -> 13 | @body = $('tbody#scoreboard_body') 14 | @path = @body.data 'refresh-path' 15 | @interval = @body.data 'refresh-interval' 16 | 17 | @template = $('#team_row_template').html() 18 | @startPoll() 19 | startPoll: -> 20 | window.setTimeout(@poll(), @interval) 21 | poll: -> 22 | return => 23 | $.ajax 24 | url: @path 25 | dataType: 'json' 26 | method: 'get' 27 | success: @receive() 28 | receive: -> 29 | return (data, textStatus, jqx) => 30 | 31 | rows = for row in data.standings 32 | row['width'] = calculateWidth row['score'] 33 | row['display_name'] = data.display_names[row['id']] 34 | Mustache.render @template, row 35 | @body.html rows.join() 36 | @body.attr 'data-response-id', jqx.getResponseHeader('X-Request-Id') 37 | @startPoll() 38 | 39 | Scoreboard.scoreboard = new Scoreboard 40 | -------------------------------------------------------------------------------- /app/assets/stylesheets/dashboard.sass: -------------------------------------------------------------------------------- 1 | .dashboard.scene 2 | @mixin panel 3 | background-color: $dark_amber 4 | color: white 5 | padding: 8px 6 | margin: 8px 7 | border: 1px solid $amber 8 | h1 9 | margin-top: 0 10 | font-size: 1.2em 11 | width: 960px 12 | .parcel 13 | margin: 0 14 | padding: 0 15 | > div 16 | @include panel 17 | &.left 18 | width: 540px 19 | float: left 20 | &.right 21 | width: 400px 22 | float: right 23 | #replace 24 | ul 25 | padding-left: 0 26 | li 27 | list-style-type: none 28 | margin-left: 0 29 | #redeem 30 | form 31 | input[type='text'] 32 | width: 400px 33 | border: 1px solid grey 34 | iframe 35 | width: 400px 36 | background-color: white 37 | border: 1px solid grey 38 | #timers 39 | #actual_timers 40 | >div 41 | width: 100px 42 | display: inline-block 43 | text-align: center 44 | .status 45 | table 46 | th,td 47 | text-align: center 48 | th 49 | background-color: $dark_crimson 50 | td.ok 51 | background-color: $green 52 | color: black 53 | td.down 54 | background-color: $salmon 55 | tbody th 56 | border-top: 1px black 57 | background-color: $light_amber 58 | color: black 59 | -------------------------------------------------------------------------------- /config/secrets.yml: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Your secret key is used for verifying the integrity of signed cookies. 4 | # If you change this key, all old signed cookies will become invalid! 5 | 6 | # Make sure the secret is at least 30 characters and all random, 7 | # no regular words or you'll be exposed to dictionary attacks. 8 | # You can use `rails secret` to generate a secure secret key. 9 | 10 | # Make sure the secrets in this file are kept private 11 | # if you're sharing your code publicly. 12 | 13 | # Shared secrets are available across all environments. 14 | 15 | # shared: 16 | # api_key: a1B2c3D4e5F6 17 | 18 | # Environmental secrets are only available for that specific environment. 19 | 20 | development: 21 | secret_key_base: f0868292e78cd061f09386463e327e2674b846bb86d9ec6810761f2841f98e8532e3ead0fe1bd448d94117215c00627cda50206014353edbdbe1008d08be21a0 22 | 23 | test: 24 | secret_key_base: 2014bdcc6b941450495668cc157d629d27946369ee66be75cf7295e0fef4f6916fbd9de65ac443059bb70df9ae9dcb1175f25d4a8933e935f9cde2a805523072 25 | 26 | # Do not keep production secrets in the unencrypted secrets file. 27 | # Instead, either read values from the environment. 28 | # Or, use `bin/rails secrets:setup` to configure encrypted secrets 29 | # and move the `production:` environment over there. 30 | 31 | production: 32 | secret_key_base: <%= ENV["SECRET_KEY_BASE"] %> 33 | -------------------------------------------------------------------------------- /test/models/redemption_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class RedemptionTest < ActiveSupport::TestCase 4 | should belong_to :team 5 | should belong_to :token 6 | should have_many :captures 7 | should have_many(:flags).through(:captures) 8 | 9 | should "redeem for a team" do 10 | team = FactoryGirl.create :team 11 | token = FactoryGirl.create :token 12 | 13 | r = Redemption.redeem_for team, token.to_token_string 14 | 15 | assert r.persisted? 16 | assert_empty r.errors 17 | end 18 | 19 | context 'error cases' do 20 | 21 | should "resist redeeming the token more than once per team" do 22 | redemption = FactoryGirl.create :redemption 23 | 24 | assert_uniqueness_constraint do 25 | redemption2 = Redemption.create redemption.attributes 26 | end 27 | end 28 | 29 | should "refuse to redeem after token expiration" do 30 | team = FactoryGirl.create :team 31 | token = FactoryGirl.create :token 32 | assert token.eligible? 33 | 34 | Token::EXPIRATION.times do 35 | FactoryGirl.create :round 36 | assert_nothing_raised { Redemption.redeem_for team, token.to_token_string } 37 | token.redemptions.destroy_all 38 | end 39 | 40 | FactoryGirl.create :round 41 | assert_raises(Redemption::OldTokenError) { Redemption.redeem_for team, token.to_token_string } 42 | end 43 | 44 | should 'refuse to redeem for the token-owning team' 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /test/models/round_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class RoundTest < ActiveSupport::TestCase 4 | should have_many :availabilities 5 | should have_many :tokens 6 | should have_many :redemptions 7 | 8 | should 'find expiring tokens' do 9 | @old_rounds = (Token::EXPIRATION + 2).times.map{ FactoryGirl.create :round } 10 | @round = FactoryGirl.create :round 11 | 12 | @ancient_token = FactoryGirl.create :token, round: @old_rounds.first 13 | @expiring_token = FactoryGirl.create :token, round: @old_rounds.second 14 | @new_token = FactoryGirl.create :token, round: @round 15 | 16 | refute_includes @round.expiring_tokens, @ancient_token 17 | assert_includes @round.expiring_tokens, @expiring_token 18 | refute_includes @round.expiring_tokens, @new_token 19 | 20 | assert_equal Set.new(@round.expiring_tokens), Set.new(Token.expiring) 21 | end 22 | 23 | context 'class methods' do 24 | should 'get the current round' do 25 | r1 = FactoryGirl.create :round 26 | 27 | assert_equal r1, Round.current 28 | 29 | r2 = FactoryGirl.create :round 30 | 31 | assert_equal r2, Round.current 32 | end 33 | 34 | should 'get the rounds since a given round' do 35 | r1 = FactoryGirl.create :round 36 | 37 | assert_equal 0, Round.since(r1) 38 | 39 | (1..5).each do |n| 40 | FactoryGirl.create :round 41 | assert_equal n, Round.since(r1) 42 | end 43 | end 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /app/models/round_finalizer.rb: -------------------------------------------------------------------------------- 1 | class RoundFinalizer 2 | attr_accessor :round, :service 3 | 4 | def initialize(round, service) 5 | self.round = round 6 | self.service = service 7 | end 8 | 9 | def seed 10 | "legitbs-2015-#{round.id}" 11 | end 12 | 13 | def prng 14 | Random.new seed.chars.map{|c| c.ord.to_s(16)}.join.to_i(16) 15 | end 16 | 17 | def service_instances 18 | Instance.where(service: service) 19 | end 20 | 21 | def candidates 22 | return @candidates if defined? @candidates 23 | 24 | initial = round.availabilities. 25 | where(instance_id: service_instances.map(&:id)). 26 | failed. 27 | order(id: :asc). 28 | to_a 29 | initial += round.expiring_tokens. 30 | where(instance_id: service_instances.map(&:id)). 31 | order(id: :asc). 32 | to_a 33 | generator = prng 34 | @candidates = initial.sort_by{ generator.rand }.uniq 35 | 36 | @candidates << TokenRedistributor.new 37 | end 38 | 39 | def movements 40 | return @movements if defined? @movements 41 | 42 | @movements = candidates.map do |c| 43 | c.process_movements(round) 44 | 45 | c.as_movement_json 46 | end 47 | end 48 | 49 | def as_metadata_json 50 | { 51 | service: service.name, 52 | seed: seed, 53 | sequence: candidates.map{ |c| c.as_json include_root: true, only: :id }, 54 | movements: movements 55 | } 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /app/models/replacement.rb: -------------------------------------------------------------------------------- 1 | class Replacement < ApplicationRecord 2 | belongs_to :team 3 | belongs_to :service 4 | belongs_to :round 5 | 6 | before_save :process_upload 7 | 8 | attr_accessor :file 9 | 10 | MAX_SIZE = (ENV['MAX_REPLACEMENT_SIZE'] || (2 * 1024 * 1024)).to_i # 2mb 11 | DIGEST_BLOCK = 1024 12 | 13 | SERVICE_ROOT = ENV['SERVICE_ROOT'] || Rails.root.join("tmp", "services") 14 | ARCHIVE_ROOT = ENV['ARCHIVE_ROOT'] || Rails.root.join("tmp", "archive") 15 | 16 | 17 | def service_path 18 | File.join service.name, "#{service.name}.bin" 19 | end 20 | 21 | def archive_path 22 | File.join SERVICE_ROOT, "#{team_id}-#{service_id}-#{digest}.bin" 23 | end 24 | 25 | 26 | private 27 | 28 | def process_upload 29 | raise 'attach a file' unless file 30 | raise 'too fat' if file.size > MAX_SIZE 31 | 32 | self.size = file.size 33 | 34 | hash_fn = Digest::SHA256.new 35 | file.rewind 36 | until file.eof? 37 | hash_fn.update file.read(DIGEST_BLOCK) 38 | end 39 | self.digest = hash_fn.hexdigest 40 | 41 | FileUtils.mkdir_p(File.dirname(archive_path)) 42 | # FileUtils.mkdir_p(File.dirname(service_path)) 43 | 44 | FileUtils.cp file.path, archive_path 45 | # FileUtils.cp file.path, service_path 46 | 47 | scp = Cocaine::CommandLine. 48 | new('scp', ':path :dest') 49 | 50 | dest = "root@#{team.address}:/home/#{service.name}/#{service.name}.bin" 51 | 52 | scp.run path: file.path, dest: dest 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /public/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | We're sorry, but something went wrong (500) 5 | 48 | 49 | 50 | 51 | 52 |
53 |

We're sorry, but something went wrong.

54 |
55 |

If you are the application owner check the logs for more information.

56 | 57 | 58 | -------------------------------------------------------------------------------- /public/422.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The change you wanted was rejected (422) 5 | 48 | 49 | 50 | 51 | 52 |
53 |

The change you wanted was rejected.

54 |

Maybe you tried to change something you didn't have access to.

55 |
56 |

If you are the application owner check the logs for more information.

57 | 58 | 59 | -------------------------------------------------------------------------------- /public/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The page you were looking for doesn't exist (404) 5 | 48 | 49 | 50 | 51 | 52 |
53 |

The page you were looking for doesn't exist.

54 |

You may have mistyped the address or the page may have moved.

55 |
56 |

If you are the application owner check the logs for more information.

57 | 58 | 59 | -------------------------------------------------------------------------------- /app/assets/stylesheets/color.sass: -------------------------------------------------------------------------------- 1 | // 2013 2 | $salmon: #fa8072 3 | $blue: hsl(200, 100%, 50%) 4 | $pinku: #ff0080 5 | $parchment: #ffe 6 | $green: hsl(120, 100%, 50%) 7 | $darkgreen: darken($green, 50%) 8 | 9 | // 2014 10 | $dark_purple: #1E0533 11 | $purple: #4A2966 12 | $light_purple: #DAB7FF 13 | $distant_brown: rgb(67, 39, 32) 14 | $brown: #332105 15 | $dark_red: darken(#f00, 25%) 16 | 17 | // 2015 18 | $amber: #F5B34A 19 | $bright_amber: lighten($amber, 30%) 20 | $light_amber: lighten($amber, 15%) 21 | $dark_amber: darken($amber, 55%) 22 | $darker_amber: darken($amber, 75%) 23 | $crimson: #EB242E 24 | $dark_crimson: darken($crimson, 35%) 25 | $darker_crimson: darken($crimson, 50%) 26 | 27 | // 2016 28 | $teeth: #e7d0c6 29 | $tan: #d19269 30 | $honky: #f3c6b5 31 | $shirt: #d4e1f4 32 | $lefty: #493733 33 | $bluegrey: #817d89 34 | $light_bluegrey: lighten($bluegrey, 25%) 35 | $tielight: #8b858b 36 | $light_tielight: lighten($tielight, 25%) 37 | 38 | // 2017 39 | $pale_mustard: #f1d474 40 | $mustard: #bd8619 41 | $baby_blue: #5037f0 42 | $grey_grape: #7957ba 43 | $grape: #88198D 44 | $dark_grape: darken($grape, 15%) 45 | $superdark_grape: darken($grape, 25%) 46 | $superpale_blue: lighten($baby_blue, 38%) 47 | $paper_average: #cebea8 48 | 49 | $background_color: $dark_grape 50 | 51 | // useful 52 | $deemphasized: desaturate($mustard, 45%) 53 | $emphasized: saturate($mustard, 15%) 54 | 55 | $error: $crimson 56 | $error_bright: #faa 57 | $error_super: #fcc 58 | 59 | $content_background: $dark_grape 60 | $content_text: white 61 | $content_highlight: $light_amber 62 | 63 | a 64 | &:link, &:visited 65 | color: darken($honky, 35%) 66 | -------------------------------------------------------------------------------- /app/views/admin/replacements/show.html.haml: -------------------------------------------------------------------------------- 1 | %p#notice= notice 2 | 3 | %p 4 | %b Team: 5 | = @replacement.team.name 6 | %p 7 | %b Service: 8 | = @replacement.service.name 9 | %p 10 | %b Round: 11 | = @replacement.round_id 12 | %p 13 | %b Digest: 14 | = @replacement.digest 15 | 16 | %h1 Same as 17 | %table 18 | %thead 19 | %tr 20 | %th id 21 | %th round 22 | %th team 23 | %th activities 24 | %tbody 25 | - @sames.each do |r| 26 | %tr 27 | %td= link_to r.id, admin_replacement_path(r) 28 | %td= r.round_id 29 | %td= r.team.name 30 | %td 31 | = link_to 'show', admin_replacement_path(r) 32 | 33 | %h1 This team's 34 | %table 35 | %thead 36 | %tr 37 | %th id 38 | %th round 39 | %th digest 40 | %th activities 41 | %tbody 42 | - @teams.each do |r| 43 | %tr 44 | %td= link_to r.id, admin_replacement_path(r) 45 | %td= r.round_id 46 | %td=r.digest.truncate(16) 47 | %td 48 | = link_to 'show', admin_replacement_path(r) 49 | = 'this one' if r.id == @replacement.id 50 | = 'after this one' if r.id == @next&.id 51 | 52 | %h1 Redemptions against me 53 | %table 54 | %thead 55 | %tr 56 | %th id 57 | %th round 58 | %th scorer 59 | %th activities 60 | %tbody 61 | - @redemptions.each do |red| 62 | %tr 63 | %td= red.id 64 | %td= red.round_id 65 | %td= red.team.name 66 | %td blah 67 | 68 | = link_to 'Edit', edit_replacement_path(@replacement) 69 | \| 70 | = link_to 'Back', replacements_path 71 | -------------------------------------------------------------------------------- /db/migrate/20140807162135_forbid_nulls.rb: -------------------------------------------------------------------------------- 1 | class ForbidNulls < ActiveRecord::Migration 2 | def change 3 | change_table :availabilities do |t| 4 | t.change :instance_id, :integer, null: false 5 | t.change :round_id, :integer, null: false 6 | end 7 | 8 | change_table :captures do |t| 9 | t.change :redemption_id, :integer, null: false 10 | t.change :flag_id, :integer, null: false 11 | t.change :round_id, :integer, null: false 12 | end 13 | 14 | change_table :flags do |t| 15 | t.change :team_id, :integer, null: false 16 | t.change :service_id, :integer, null: false 17 | end 18 | 19 | change_table :instances do |t| 20 | t.change :team_id, :integer, null: false 21 | t.change :service_id, :integer, null: false 22 | end 23 | 24 | change_table :penalties do |t| 25 | t.change :availability_id, :integer, null: false 26 | t.change :team_id, :integer, null: false 27 | t.change :flag_id, :integer, null: false 28 | end 29 | 30 | change_table :redemptions do |t| 31 | t.change :team_id, :integer, null: false 32 | t.change :token_id, :integer, null: false 33 | t.change :round_id, :integer, null: false 34 | t.change :uuid, :uuid, null: false, unique: true 35 | end 36 | 37 | change_table :tickets do |t| 38 | t.change :team_id, :integer, null: false 39 | end 40 | 41 | change_table :tokens do |t| 42 | t.change :instance_id, :integer, null: false 43 | t.change :round_id, :integer, null: false 44 | t.change :key, :string, unique: true, null: false 45 | end 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /app/views/admin/instances/show.html.haml: -------------------------------------------------------------------------------- 1 | %h1 Instance: #{@instance.team.name} #{@instance.service.name} 2 | 3 | %table 4 | %tbody 5 | %tr 6 | %th team 7 | %td= @instance.team.name 8 | %tr 9 | %th service 10 | %td= @instance.service.name 11 | %tr 12 | %th flags 13 | %td= @instance.flags.count 14 | 15 | %h2 Recent Tokens 16 | 17 | %table 18 | %thead 19 | %tr 20 | %th id 21 | %th round 22 | %th when 23 | %th deposit status 24 | %th redemptions 25 | %th activities 26 | %tbody 27 | - @instance.tokens.order(id: :desc).limit(50).each do |t| 28 | %tr 29 | %td= t.id 30 | %td= t.round_id 31 | %td 32 | = time_ago t.created_at 33 | %td 34 | - if t.status == 0 35 | ok 36 | - else 37 | failed 38 | = t.status 39 | %td= t.redemptions.count 40 | %td 41 | = link_to 'show', admin_token_path(t) 42 | 43 | %h2 Recent Availability Checks 44 | 45 | %table 46 | %thead 47 | %tr 48 | %th id 49 | %th round 50 | %th when 51 | %th healthy? 52 | %th penalties 53 | %th activities 54 | %tbody 55 | - @instance.availabilities.order(created_at: :desc).limit(50).each do |a| 56 | %tr 57 | %td= a.id 58 | %td= a.round_id 59 | %td{title: a.created_at} 60 | = time_ago_in_words a.created_at, include_seconds: true 61 | ago 62 | %td= a.healthy? 63 | %td= a.penalties.count 64 | %td 65 | = link_to 'show', admin_availability_path(a) 66 | -------------------------------------------------------------------------------- /config/deploy/staging.rb: -------------------------------------------------------------------------------- 1 | # Simple Role Syntax 2 | # ================== 3 | # Supports bulk-adding hosts to roles, the primary server in each group 4 | # is considered to be the first unless any hosts have the primary 5 | # property set. Don't declare `role :all`, it's a meta role. 6 | 7 | role :app, %w{deploy@example.com} 8 | role :web, %w{deploy@example.com} 9 | role :db, %w{deploy@example.com} 10 | 11 | 12 | # Extended Server Syntax 13 | # ====================== 14 | # This can be used to drop a more detailed server definition into the 15 | # server list. The second argument is a, or duck-types, Hash and is 16 | # used to set extended properties on the server. 17 | 18 | server 'example.com', user: 'deploy', roles: %w{web app}, my_property: :my_value 19 | 20 | 21 | # Custom SSH Options 22 | # ================== 23 | # You may pass any option but keep in mind that net/ssh understands a 24 | # limited set of options, consult[net/ssh documentation](http://net-ssh.github.io/net-ssh/classes/Net/SSH.html#method-c-start). 25 | # 26 | # Global options 27 | # -------------- 28 | # set :ssh_options, { 29 | # keys: %w(/home/rlisowski/.ssh/id_rsa), 30 | # forward_agent: false, 31 | # auth_methods: %w(password) 32 | # } 33 | # 34 | # And/or per server (overrides global) 35 | # ------------------------------------ 36 | # server 'example.com', 37 | # user: 'user_name', 38 | # roles: %w{web app}, 39 | # ssh_options: { 40 | # user: 'user_name', # overrides user setting above 41 | # keys: %w(/home/user_name/.ssh/id_rsa), 42 | # forward_agent: false, 43 | # auth_methods: %w(publickey password) 44 | # # password: 'please use keys' 45 | # } 46 | -------------------------------------------------------------------------------- /app/models/token_redistributor.rb: -------------------------------------------------------------------------------- 1 | class TokenRedistributor 2 | attr_reader :movement_json, :round 3 | def initialize 4 | @movement_json = { } 5 | end 6 | 7 | def process_movements(round) 8 | @round = round 9 | 10 | Service.where(enabled: true).each do |svc| 11 | process_movements_for_service svc 12 | end 13 | end 14 | 15 | def as_movement_json 16 | return { redistribution: @movement_json } 17 | end 18 | 19 | def ==(other) 20 | return false unless other.is_a? self.class 21 | return false unless other.movement_json == self.movement_json 22 | return false unless other.round == self.round 23 | 24 | true 25 | end 26 | 27 | private 28 | def process_movements_for_service(svc) 29 | instances = svc.instances 30 | recipients = instances.map do |i| 31 | i.redemptions.where(round: @round).all 32 | end.flatten.map(&:team).uniq 33 | 34 | @movement_json[svc.id] = record = { } 35 | 36 | record[:recipients] = recipients.as_json only: %i{ id certname } 37 | 38 | return if recipients.count == 0 39 | 40 | flags = Team.legitbs.flags.where(service: svc).to_a 41 | return if flags.count < recipients.count 42 | 43 | bounty = flags.count / recipients.count 44 | 45 | record[:flags] = flags.as_json only: :id 46 | record[:bounty] = bounty 47 | record[:movements] = { } 48 | 49 | recipients.each do |r| 50 | bounty.times do 51 | candidate = flags.pop 52 | candidate.update_attribute :team_id, r.id 53 | 54 | record[:movements][r.id] ||= [] 55 | record[:movements][r.id] << candidate.id 56 | end 57 | end 58 | 59 | @movement_json[svc.name] = record 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /test/controllers/redemption_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class RedemptionControllerTest < ActionController::TestCase 4 | context 'redeeming tokens' do 5 | setup do 6 | @round = FactoryGirl.create :round 7 | @current_team = FactoryGirl.create :team 8 | @request.headers['HTTP_SSL_CLIENT_S_DN_CN'] = @current_team.uuid 9 | end 10 | 11 | should 'return json on an empty token set' do 12 | @token = FactoryGirl.create :token 13 | 14 | post :create, params: { tokens: [] } 15 | 16 | assert_response :success 17 | assert_equal Hash.new, JSON.parse(response.body) 18 | end 19 | 20 | should 'return json and a valid redemption with a single token' do 21 | @token = FactoryGirl.create :token 22 | @ts = @token.to_token_string 23 | 24 | post :create, params: { tokens: [@ts] } 25 | 26 | assert_response :success 27 | assert_equal({@ts => @token.redemptions.first.uuid}, JSON.parse(response.body)) 28 | end 29 | 30 | should 'return json with error messages' do 31 | @token = FactoryGirl.create :token 32 | (Token::EXPIRATION + 1).times{ FactoryGirl.create :round } 33 | @ts = @token.to_token_string 34 | 35 | @token2 = FactoryGirl.create :token 36 | @ts2 = @token2.to_token_string 37 | @existing_redemption = Redemption.redeem_for @current_team, @ts2 38 | 39 | post :create, params: { tokens: [@ts, @ts2] } 40 | 41 | assert_response :success 42 | assert_equal({ 43 | @ts => 'error: Token too old', 44 | @ts2 => 'error: Already redeemed that token' 45 | }, JSON.parse(response.body)) 46 | end 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /config/deploy/production.rb: -------------------------------------------------------------------------------- 1 | set :deploy_to, '/home/scorebot/production' 2 | 3 | # Simple Role Syntax 4 | # ================== 5 | # Supports bulk-adding hosts to roles, the primary server in each group 6 | # is considered to be the first unless any hosts have the primary 7 | # property set. Don't declare `role :all`, it's a meta role. 8 | 9 | role :app, %w{scorebot@10.3.1.7 scorebot@10.3.1.8} 10 | role :web, %w{scorebot@10.3.1.7 scorebot@10.3.1.8} 11 | role :db, %w{scorebot@10.3.1.8} 12 | 13 | 14 | # Extended Server Syntax 15 | # ====================== 16 | # This can be used to drop a more detailed server definition into the 17 | # server list. The second argument is a, or duck-types, Hash and is 18 | # used to set extended properties on the server. 19 | 20 | #server 'example.com', user: 'deploy', roles: %w{web app}, my_property: :my_value 21 | 22 | # Custom SSH Options 23 | # ================== 24 | # You may pass any option but keep in mind that net/ssh understands a 25 | # limited set of options, consult[net/ssh documentation](http://net-ssh.github.io/net-ssh/classes/Net/SSH.html#method-c-start). 26 | # 27 | # Global options 28 | # -------------- 29 | set :ssh_options, { 30 | keys: %w(/Volumes/ctf2015/.deploy_rsa), 31 | forward_agent: false, 32 | # auth_methods: %w(password) 33 | } 34 | # 35 | # And/or per server (overrides global) 36 | # ------------------------------------ 37 | # server 'example.com', 38 | # user: 'user_name', 39 | # roles: %w{web app}, 40 | # ssh_options: { 41 | # user: 'user_name', # overrides user setting above 42 | # keys: %w(/home/user_name/.ssh/id_rsa), 43 | # forward_agent: false, 44 | # auth_methods: %w(publickey password) 45 | # # password: 'please use keys' 46 | # } 47 | -------------------------------------------------------------------------------- /app/views/replacements/index.html.haml: -------------------------------------------------------------------------------- 1 | .scene 2 | %h1 Replacements 3 | 4 | %p= link_to 'Upload new replacement', new_replacement_path 5 | 6 | #filters 7 | %h2 8 | Filters 9 | = link_to 'Reset', replacements_path, class: 'btn' 10 | %nav#teams 11 | %ul 12 | %li.label teams 13 | - @teams.each do |t| 14 | %li= link_to(t.certname, 15 | @filters.merge(team_id: t.id), 16 | title: t.name, 17 | data: {team_id: t.id}, 18 | class: ((t.id == @filters[:team_id]) ? 'active' : '')) 19 | %nav#services 20 | %ul 21 | %li.label services 22 | - @services.each do |s| 23 | %li= link_to(s.name, 24 | @filters.merge(service_id: s.id), 25 | class: ((s.id == @filters[:service_id]) ? 'active' : '')) 26 | 27 | %nav#pages 28 | %ul 29 | %li.label pages 30 | %li= link_to('prev page', 31 | @filters.merge(page: @page - 1)) 32 | %li= link_to('next page', 33 | @filters.merge(page: @page + 1)) 34 | 35 | - if @replacements.empty? 36 | %p No replacements found. 37 | - else 38 | %table 39 | %thead 40 | %tr 41 | %th id 42 | %th team 43 | %th service 44 | %th hash 45 | %th size 46 | %th actions 47 | %tbody 48 | - @replacements.each do |r| 49 | %tr 50 | %td= r.id 51 | %td{title: r.team.name}= r.team.certname 52 | %td= r.service.name 53 | %td= r.digest 54 | %td= r.size 55 | %td 56 | = link_to 'show', replacement_path(r) 57 | = link_to 'download', replacement_path(r, download: true) 58 | -------------------------------------------------------------------------------- /app/models/flag.rb: -------------------------------------------------------------------------------- 1 | class Flag < ActiveRecord::Base 2 | belongs_to :team 3 | belongs_to :service 4 | has_many :captures 5 | 6 | TOTAL_FLAGS = 10 * # service count 7 | 15 * # team count 8 | 1337 9 | 10 | def self.initial_distribution 11 | raise "Refusing to distribute with existing flags" unless Flag.count == 0 12 | 13 | transaction do 14 | teams = Team.without_legitbs.to_a 15 | services = Service.all 16 | 17 | tranche_count = teams.count * services.count 18 | tranche_size = Flag::TOTAL_FLAGS / (tranche_count) 19 | tranche_remainder = Flag::TOTAL_FLAGS % (tranche_count) 20 | 21 | unless tranche_remainder == 0 22 | puts "#{teams.count} teams * #{services.count} services = #{tranche_count} tranches" 23 | puts "#{Flag::TOTAL_FLAGS} / #{tranche_count} = #{Flag::TOTAL_FLAGS.to_f / tranche_count.to_f}" 24 | puts "add #{tranche_size - tranche_remainder} or remove #{tranche_remainder}" 25 | raise "had flags left over when planning allocation" 26 | end 27 | 28 | puts "#{services.count} services, #{teams.count} teams, #{tranche_size} flags per" 29 | 30 | teams.each do |t| 31 | print "flags for #{t.name}: " 32 | services.each do |s| 33 | tranche_size.times do 34 | Flag.create team: t, service: s 35 | end 36 | print '.' 37 | end 38 | 39 | puts 40 | end 41 | end 42 | end 43 | 44 | def self.collect_livectf_flags(livectf_services) 45 | livectf_services.each do |serv| 46 | instances = Instance.where service: serv 47 | legitbs_inst = instances.find_by team: Team.legitbs 48 | 49 | Flag.where(service: serv).update_all(team_id: Team.legitbs.id) 50 | end 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /config/database.yml: -------------------------------------------------------------------------------- 1 | # PostgreSQL. Versions 8.2 and up are supported. 2 | # 3 | # Install the pg driver: 4 | # gem install pg 5 | # On OS X with Homebrew: 6 | # gem install pg -- --with-pg-config=/usr/local/bin/pg_config 7 | # On OS X with MacPorts: 8 | # gem install pg -- --with-pg-config=/opt/local/lib/postgresql84/bin/pg_config 9 | # On Windows: 10 | # gem install pg 11 | # Choose the win32 build. 12 | # Install PostgreSQL and put its /bin directory on your path. 13 | # 14 | # Configure Using Gemfile 15 | # gem 'pg' 16 | # 17 | development: 18 | adapter: postgresql 19 | encoding: unicode 20 | database: scorebot_development 21 | pool: 5 22 | 23 | # Connect on a TCP socket. Omitted by default since the client uses a 24 | # domain socket that doesn't need configuration. Windows does not have 25 | # domain sockets, so uncomment these lines. 26 | #host: localhost 27 | 28 | # The TCP port the server listens on. Defaults to 5432. 29 | # If your server runs on a different port number, change accordingly. 30 | #port: 5432 31 | 32 | # Schema search path. The server defaults to $user,public 33 | #schema_search_path: myapp,sharedapp,public 34 | 35 | # Minimum log levels, in increasing order: 36 | # debug5, debug4, debug3, debug2, debug1, 37 | # log, notice, warning, error, fatal, and panic 38 | # Defaults to warning. 39 | #min_messages: notice 40 | 41 | # Warning: The database defined as "test" will be erased and 42 | # re-generated from your development database when you run "rake". 43 | # Do not set this db to the same as development or production. 44 | test: 45 | adapter: postgresql 46 | encoding: unicode 47 | database: scorebot_test 48 | pool: 5 49 | 50 | production: 51 | adapter: postgresql 52 | host: db 53 | username: postgres 54 | database: scorebot_production 55 | pool: 100 56 | -------------------------------------------------------------------------------- /app/views/dashboard/index.html.haml: -------------------------------------------------------------------------------- 1 | .dashboard.scene 2 | .parcel.right 3 | = render partial: 'shared/timers' 4 | .status 5 | %h1 Service Status 6 | %table 7 | %thead 8 | %th service 9 | %th port 10 | %th -15m 11 | %th -10m 12 | %th -5m 13 | %tbody 14 | - @instances.each do |s| 15 | %tr 16 | %th&= s.service.name 17 | %td&= s.service.port 18 | - s.uptime_check.each do |c| 19 | - if c 20 | %td.ok OK 21 | - else 22 | %td.down DOWN 23 | 24 | .parcel.left 25 | #replace 26 | %h1 Replace 27 | %ul 28 | %li= link_to 'Replace a service binary', new_replacement_path 29 | %li= link_to 'Get service binaries', replacements_path 30 | #redeem 31 | %h1 Redeem 32 | %p 33 | You really should automate this. 34 | = link_to 'Read the "How to Play" screen.', howto_path 35 | = form_tag redemption_path, method: :post, target: 'redeem_target', id: 'redemption_form' do 36 | - 2.times do 37 | %p 38 | = text_field_tag 'tokens[]', '', placeholder: 'token' 39 | %p 40 | = text_field_tag 'tokens[]', '', placeholder: 'token' 41 | = submit_tag 'punch it' 42 | %iframe{id: 'redeem_target', name: 'redeem_target', style: 'display: none'} 43 | #tickets 44 | %h1 Trouble Tickets 45 | = form_for current_team.tickets.new do |f| 46 | %p 47 | = f.text_area :body, placeholder: "Problems? Securely send a message to Legitimate Business Syndicate. Definitely paste any tokens or UUIDs from redemptions." 48 | %nav 49 | %ul 50 | %li= f.submit 'Submit ticket' 51 | %li= link_to 'List my tickets', tickets_path 52 | -------------------------------------------------------------------------------- /config/environments/test.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # The test environment is used exclusively to run your application's 5 | # test suite. You never need to work with it otherwise. Remember that 6 | # your test database is "scratch space" for the test suite and is wiped 7 | # and recreated between test runs. Don't rely on the data there! 8 | config.cache_classes = true 9 | 10 | # Do not eager load code on boot. This avoids loading your whole application 11 | # just for the purpose of running a single test. If you are using a tool that 12 | # preloads Rails for running tests, you may have to set it to true. 13 | config.eager_load = false 14 | 15 | # Configure public file server for tests with Cache-Control for performance. 16 | config.public_file_server.enabled = true 17 | config.public_file_server.headers = { 18 | 'Cache-Control' => "public, max-age=#{1.hour.seconds.to_i}" 19 | } 20 | 21 | # Show full error reports and disable caching. 22 | config.consider_all_requests_local = true 23 | config.action_controller.perform_caching = false 24 | 25 | # Raise exceptions instead of rendering exception templates. 26 | config.action_dispatch.show_exceptions = false 27 | 28 | # Disable request forgery protection in test environment. 29 | config.action_controller.allow_forgery_protection = false 30 | config.action_mailer.perform_caching = false 31 | 32 | # Tell Action Mailer not to deliver emails to the real world. 33 | # The :test delivery method accumulates sent emails in the 34 | # ActionMailer::Base.deliveries array. 35 | config.action_mailer.delivery_method = :test 36 | 37 | # Print deprecation notices to the stderr. 38 | config.active_support.deprecation = :stderr 39 | 40 | # Raises error for missing translations 41 | # config.action_view.raise_on_missing_translations = true 42 | end 43 | -------------------------------------------------------------------------------- /config/deploy.rb: -------------------------------------------------------------------------------- 1 | # config valid only for Capistrano 3.1 2 | lock '3.4.0' 3 | 4 | set :application, 'scorebot' 5 | set :repo_url, 'git@waitingf.org:scorebot.git' 6 | #set :repo_url, 'file:///home/scorebot/scorebot-repo.git' 7 | 8 | # Default branch is :master 9 | # ask :branch, proc { `git rev-parse --abbrev-ref HEAD`.chomp }.call 10 | 11 | # Default deploy_to directory is /var/www/my_app 12 | # set :deploy_to, '/home/scorebot/production' 13 | 14 | # Default value for :scm is :git 15 | # set :scm, :git 16 | 17 | # Default value for :format is :pretty 18 | # set :format, :pretty 19 | 20 | # Default value for :log_level is :debug 21 | # set :log_level, :debug 22 | 23 | # Default value for :pty is false 24 | # set :pty, true 25 | 26 | # Default value for :linked_files is [] 27 | # set :linked_files, %w{config/database.yml} 28 | 29 | # Default value for linked_dirs is [] 30 | # set :linked_dirs, %w{bin log tmp/pids tmp/cache tmp/sockets vendor/bundle public/system} 31 | 32 | set :linked_dirs, fetch(:linked_dirs) + %w{tmp/pids tmp/logs logs tmp/dumps} 33 | 34 | # Default value for default_env is {} 35 | # set :default_env, { path: "/opt/ruby/bin:$PATH" } 36 | 37 | # Default value for keep_releases is 5 38 | # set :keep_releases, 5 39 | 40 | namespace :deploy do 41 | 42 | desc 'Restart application' 43 | task :restart do 44 | on roles(:app), in: :sequence, wait: 5 do 45 | within current_path do 46 | %w{puma service}.each do |f| 47 | pidfile = "#{current_path}/tmp/pids/#{f}.pid" 48 | next unless test("[ -f #{pidfile} ]") 49 | execute "kill `cat #{pidfile}`" 50 | end 51 | end 52 | end 53 | end 54 | 55 | after :publishing, :restart 56 | 57 | after :restart, :clear_cache do 58 | on roles(:web), in: :groups, limit: 3, wait: 10 do 59 | # Here we can do anything such as: 60 | # within release_path do 61 | # execute :rake, 'cache:clear' 62 | # end 63 | end 64 | end 65 | 66 | end 67 | -------------------------------------------------------------------------------- /app/controllers/tickets_controller.rb: -------------------------------------------------------------------------------- 1 | class TicketsController < ApplicationController 2 | before_action :set_ticket, only: [:show, :edit, :update, :destroy, :resolve] 3 | before_action :require_legitbs, only: %i{ resolve } 4 | 5 | # GET /tickets 6 | def index 7 | @title = "All Tickets" 8 | @tickets = ticket_scope.order(created_at: :desc) 9 | if params[:scope] != 'all' 10 | @title = 'Unresolved Tickets' 11 | @tickets = @tickets.unresolved 12 | end 13 | 14 | if is_legitbs? 15 | @title += " from Everyone" 16 | end 17 | end 18 | 19 | # GET /tickets/1 20 | def show 21 | end 22 | 23 | # GET /tickets/new 24 | def new 25 | @ticket = current_team.tickets.new 26 | end 27 | 28 | # GET /tickets/1/edit 29 | def edit 30 | end 31 | 32 | # POST /tickets 33 | def create 34 | @ticket = current_team.tickets.new(ticket_params) 35 | 36 | if @ticket.save 37 | Event.new('ticket', r.as_json).publish! rescue nil 38 | redirect_to @ticket, notice: 'Ticket was successfully created.' 39 | else 40 | render :new 41 | end 42 | end 43 | 44 | # PATCH/PUT /tickets/1 45 | def update 46 | if @ticket.update(ticket_params) 47 | redirect_to @ticket, notice: 'Ticket was successfully updated.' 48 | else 49 | render :edit 50 | end 51 | end 52 | 53 | def resolve 54 | @ticket.resolve! 55 | redirect_to tickets_path 56 | end 57 | 58 | def unresolve 59 | @ticket.unresolve! 60 | redirect_to tickets_path 61 | end 62 | 63 | private 64 | # Use callbacks to share common setup or constraints between actions. 65 | def set_ticket 66 | @ticket = ticket_scope.find(params[:id]) 67 | end 68 | 69 | def ticket_scope 70 | if is_legitbs? 71 | Ticket 72 | else 73 | current_team.tickets 74 | end 75 | end 76 | 77 | # Only allow a trusted parameter "white list" through. 78 | def ticket_params 79 | params.require(:ticket).permit(:body) 80 | end 81 | end 82 | -------------------------------------------------------------------------------- /test/models/availability_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class AvailabilityTest < ActiveSupport::TestCase 4 | should belong_to :instance 5 | should belong_to :round 6 | 7 | context 'with a round, an instance, and a shell process' do 8 | setup do 9 | @instance = FactoryGirl.create :instance 10 | 11 | @shell = mock('shell') 12 | ShellProcess. 13 | expects(:new). 14 | with(Rails.root.join('scripts', @instance.service.name), 15 | 'availability', 16 | is_a(Integer)). 17 | returns(@shell) 18 | 19 | @token = FactoryGirl.create :token, instance: @instance 20 | @dingus = [rand(2**64).to_s(16)].pack('H*') 21 | 22 | @memo = <<-EOF 23 | example memo 24 | !!legitbs-validate-token-7OPuwAj #{@token.to_token_string} 25 | EOF 26 | 27 | @shell.expects(:status).returns(0) 28 | @shell.expects(:output).at_least(1).returns(@memo) 29 | end 30 | 31 | should 'create an availability from an instance' do 32 | @availability = Availability.check @instance, @round 33 | 34 | assert @availability 35 | assert_equal 0, @availability.status 36 | assert_equal @memo, @availability.memo 37 | 38 | assert_equal @token, @availability.token 39 | end 40 | end 41 | 42 | should 'distribute flags' do 43 | @instance = FactoryGirl.create :instance 44 | @lbs_instance = FactoryGirl.create :lbs_instance, service: @instance.service 45 | 46 | @flags = FactoryGirl.create_list :flag, 19, team: @instance.team, service: @instance.service 47 | @teams = FactoryGirl.create_list :team, 19 48 | 49 | @availability = FactoryGirl.create :down_availability, instance: @instance 50 | @lbs_availability = FactoryGirl.create(:availability, 51 | instance: @lbs_instance, 52 | round: @availability.round) 53 | 54 | @availability.process_movements(@availability.round) 55 | 56 | assert @flags.any?{|f| f.reload.team != @instance.team} 57 | end 58 | 59 | should 'log flag distribution' 60 | end 61 | -------------------------------------------------------------------------------- /config/environments/development.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # In the development environment your application's code is reloaded on 5 | # every request. This slows down response time but is perfect for development 6 | # since you don't have to restart the web server when you make code changes. 7 | config.cache_classes = false 8 | 9 | # Do not eager load code on boot. 10 | config.eager_load = false 11 | 12 | # Show full error reports. 13 | config.consider_all_requests_local = true 14 | 15 | # Enable/disable caching. By default caching is disabled. 16 | if Rails.root.join('tmp/caching-dev.txt').exist? 17 | config.action_controller.perform_caching = true 18 | 19 | config.cache_store = :memory_store 20 | config.public_file_server.headers = { 21 | 'Cache-Control' => "public, max-age=#{2.days.seconds.to_i}" 22 | } 23 | else 24 | config.action_controller.perform_caching = false 25 | 26 | config.cache_store = :null_store 27 | end 28 | 29 | # Don't care if the mailer can't send. 30 | config.action_mailer.raise_delivery_errors = false 31 | 32 | config.action_mailer.perform_caching = false 33 | 34 | # Print deprecation notices to the Rails logger. 35 | config.active_support.deprecation = :log 36 | 37 | # Raise an error on page load if there are pending migrations. 38 | config.active_record.migration_error = :page_load 39 | 40 | # Debug mode disables concatenation and preprocessing of assets. 41 | # This option may cause significant delays in view rendering with a large 42 | # number of complex assets. 43 | config.assets.debug = true 44 | 45 | # Suppress logger output for asset requests. 46 | config.assets.quiet = true 47 | 48 | # Raises error for missing translations 49 | # config.action_view.raise_on_missing_translations = true 50 | 51 | # Use an evented file watcher to asynchronously detect changes in source code, 52 | # routes, locales, etc. This feature depends on the listen gem. 53 | config.file_watcher = ActiveSupport::EventedFileUpdateChecker 54 | end 55 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Bundle edge Rails instead: gem 'rails', github: 'rails/rails' 4 | gem 'rails', '~> 5.1.1' 5 | 6 | gem 'pry-rails' 7 | gem 'base62' 8 | gem 'pngqr' 9 | 10 | gem 'fog-aws' 11 | 12 | # Use postgresql as the database for Active Record 13 | gem 'pg' 14 | gem 'immigrant' 15 | 16 | gem 'redis' 17 | 18 | # Use SCSS for stylesheets 19 | gem 'sass-rails' 20 | gem 'haml-rails' 21 | gem 'bourbon' 22 | gem 'rdiscount' 23 | 24 | gem 'listen' 25 | 26 | gem 'cocaine' 27 | 28 | # Use Uglifier as compressor for JavaScript assets 29 | gem 'uglifier' #, '>= 1.3.0' 30 | 31 | # Use CoffeeScript for .js.coffee assets and views 32 | gem 'coffee-rails' #, '~> 4.0' 33 | 34 | # See https://github.com/sstephenson/execjs#readme for more supported runtimes 35 | # gem 'therubyracer', platforms: :ruby 36 | 37 | # Use jquery as the JavaScript library 38 | gem 'jquery-rails' 39 | 40 | # Turbolinks makes following links in your web application faster. Read more: https://github.com/rails/turbolinks 41 | # gem 'turbolinks' 42 | 43 | gem 'high_voltage' 44 | 45 | # Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder 46 | # gem 'jbuilder', '~> 1.2' 47 | 48 | gem 'celluloid', require: false 49 | 50 | 51 | gem 'rack-mini-profiler' 52 | gem 'flamegraph' 53 | gem 'stackprof' # ruby 2.1+ only 54 | gem 'memory_profiler' 55 | gem 'statsd-instrument' 56 | 57 | group :doc do 58 | # bundle exec rake doc:rails generates the API under doc/api. 59 | gem 'sdoc', require: false 60 | end 61 | 62 | group :development do 63 | gem 'rails-erd' 64 | gem 'better_errors' 65 | gem 'binding_of_caller' 66 | end 67 | 68 | group :production do 69 | gem 'therubyracer' 70 | gem 'puma' 71 | end 72 | 73 | group :development, :test do 74 | gem 'shoulda' 75 | gem 'factory_girl_rails' 76 | gem 'mocha', require: false 77 | end 78 | 79 | # Use ActiveModel has_secure_password 80 | gem 'bcrypt-ruby', '~> 3.0', require: 'bcrypt' 81 | 82 | # Use unicorn as the app server 83 | # gem 'unicorn' 84 | 85 | # Use Capistrano for deployment 86 | gem 'capistrano-rails', group: :development 87 | 88 | # Use debugger 89 | # gem 'debugger', group: [:development, :test] 90 | -------------------------------------------------------------------------------- /app/assets/javascripts/timers.js.coffee: -------------------------------------------------------------------------------- 1 | jQuery ($) -> 2 | return unless $('#timers').length == 1 3 | 4 | pad = (n) -> 5 | return "0#{n}" if n < 10 & n >= 0 6 | n 7 | 8 | class Countdown 9 | constructor: -> 10 | @timers = $('#timers') 11 | @timerPath = @timers.data('timer-path') 12 | @interval = @timers.data('timer-interval') 13 | @template = @timers.html() 14 | @timers.html('') 15 | @startPoll() 16 | startPoll: -> 17 | window.setTimeout(@poll(), 0) 18 | requeue: -> 19 | finish = new XDate() 20 | lag = @start.diffMilliseconds(finish) 21 | window.setTimeout(@poll(), @interval - lag) 22 | poll: -> 23 | return => 24 | @start = new XDate() 25 | if !@recentTimers? || (@start > @recentTimers.next) 26 | $.ajax 27 | url: @timerPath 28 | dataType: 'json' 29 | method: 'get' 30 | success: @receive() 31 | else 32 | @lazyUpdate() 33 | receive: -> 34 | return (data, textStatus, jqx) => 35 | server = new XDate data['time'] 36 | @adjustment = @start.diffMilliseconds(server) 37 | @recentTimers = data.timers 38 | @recentTimers.next = @start.clone().addSeconds(15) 39 | @lazyUpdate() 40 | lazyUpdate: -> 41 | server = @start.addMilliseconds(@adjustment) 42 | game = @toRemnant(server, new XDate @recentTimers.game) 43 | today = @toRemnant(server, new XDate @recentTimers.today) 44 | round = @toRemnant(server, new XDate @recentTimers.round) 45 | h = Mustache.render @template, 46 | game: game 47 | today: today 48 | round: round 49 | @timers.html h 50 | 51 | @requeue() 52 | toRemnant: (now, ending) -> 53 | try 54 | diff = 55 | s: pad(Math.abs(Math.floor(now.diffSeconds(ending) % 60))) 56 | m: pad(Math.abs(Math.floor(now.diffMinutes(ending) % 60))) 57 | h: pad(Math.floor(now.diffHours(ending))) 58 | next: now.diffMilliseconds(ending) 59 | ending: ending 60 | return diff 61 | catch 62 | return {h: '00', m: '00', s: '00', next: 0, ending: now} 63 | 64 | Countdown.countdown = new Countdown 65 | -------------------------------------------------------------------------------- /app/views/admin/teams/show.html.haml: -------------------------------------------------------------------------------- 1 | %h1 Team: #{@team.name} 2 | 3 | %table 4 | %tr 5 | %th name 6 | %td= @team.name 7 | %tr 8 | %th display name 9 | %td= @team.display 10 | %tr 11 | %th certname 12 | %td= @team.certname 13 | %tr 14 | %th address 15 | %td= @team.address 16 | %tr 17 | %th uuid 18 | %td= @team.uuid 19 | 20 | %h2 flags 21 | 22 | %table 23 | - @team.instances.joins(:service).sort_by{|i|i.service.name.downcase}.each do |inst| 24 | %tr 25 | %th= inst.service.name 26 | %td= inst.flags.count 27 | 28 | %h2 failed redemptions 29 | 30 | %table 31 | %tbody 32 | %tr 33 | %th dupe 34 | %td= @team.dupe_ctr 35 | %tr 36 | %th old 37 | %td= @team.old_ctr 38 | %tr 39 | %th notfound 40 | %td= @team.notfound_ctr 41 | %tr 42 | %th self 43 | %td= @team.self_ctr 44 | %tr 45 | %th other 46 | %td= @team.other_ctr 47 | 48 | %h2 successful redemptions 49 | 50 | %table 51 | %thead 52 | %tr 53 | %th id 54 | %th instance 55 | %th round 56 | %th token 57 | %th captures 58 | %tbody 59 | - @team.redemptions.order(id: :desc).limit(@round_limit * 30).each do |r| 60 | %tr 61 | %td= link_to r.id, admin_redemption_path(r.id) 62 | %td= link_to r.token.instance.id, admin_instance_path(r.token.instance_id) 63 | %td= link_to r.round_id, admin_round_path(r.round_id) 64 | %td= link_to r.token.to_fake_string, admin_token_path(r.token_id) 65 | %td= r.captures.count 66 | 67 | 68 | %h2 service availability 69 | 70 | %p 71 | Showing #{@round_limit} rounds. 72 | = link_to "Show all?", limit: 1_000 73 | 74 | %table#availabilities 75 | %thead 76 | %tr 77 | %th round id 78 | - @services.each do |s| 79 | %th= s.name 80 | %tbody 81 | - @rounds.each do |r| 82 | %tr 83 | %th 84 | = r.id 85 | = r.created_at.to_formatted_s(:ctf) 86 | - @services.each do |s| 87 | - i = @team.instances.find_by(service: s) 88 | - av = i.availabilities.find_by(round: r) 89 | - if av.nil? 90 | %td 91 | - else 92 | %td{class: av.healthy?.inspect} 93 | = av.healthy? 94 | -------------------------------------------------------------------------------- /app/assets/stylesheets/scoreboard.sass: -------------------------------------------------------------------------------- 1 | body.scoreboard 2 | .banner 3 | display: none 4 | .profiler-left 5 | display: none 6 | .scoreboard.scene 7 | .scoreboard-holder 8 | table 9 | $alpha_adjustment: -.15 10 | float: left 11 | margin-top: 1em 12 | background-image: asset_url('crinkled-paper-1x.jpg') 13 | color: $superdark_grape 14 | tbody tr:nth-child(odd) 15 | background-color: rgba(0, 0, 0, 0.25) 16 | td, th.team_name 17 | text-align: right 18 | padding-right: .5em 19 | th 20 | text-align: left 21 | tbody th 22 | text-align: right 23 | width: 14em 24 | font-size: 1em 25 | padding-right: .5em 26 | td 27 | text-align: right 28 | &.flags 29 | width: 700px 30 | .points 31 | z-index: 1 32 | position: relative 33 | width: 4em 34 | color: $light_amber 35 | background-color: $dark_grape 36 | padding: 2px 37 | font-weight: bold 38 | &.left 39 | float: left 40 | .bar 41 | z-index: 0 42 | position: relative 43 | background-color: $dark_grape 44 | padding: 2px 45 | margin-left: -1px 46 | &.left 47 | float: left 48 | 49 | #timers 50 | position: fixed 51 | top: 2em 52 | right: 2em 53 | z-index: 10 54 | background-color: $dark_amber 55 | background-image: asset_url('crinkled-paper-1x.jpg') 56 | text-align: center 57 | border: 0px 58 | border-radius: 5px 59 | box-shadow: 0px 4px 16px 2px black 60 | h1 61 | display: none 62 | .desc 63 | @include permie 64 | color: black 65 | .timer 66 | padding: 2px 67 | @include mono 68 | 69 | @media screen and (max-width: 1281px) 70 | .scoreboard.scene 71 | font-size: 1.4em 72 | width: 1280px 73 | .scoreboard-holder table 74 | margin: 0 75 | margin-left: -2em 76 | 77 | @media screen and (min-width: 1400px) 78 | .scoreboard.scene 79 | font-size: 1.45em 80 | width: 1200px 81 | 82 | @media screen and (max-width: 840px) 83 | .scoreboard.scene 84 | width: 600px 85 | font-size: 0.82em 86 | -------------------------------------------------------------------------------- /test/models/round_finalizer_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class RoundFinalizerTest < ActiveSupport::TestCase 4 | setup do 5 | @old_rounds = Token::EXPIRATION.times.map { FactoryGirl.create :round } 6 | @token = FactoryGirl.create :token, round: @old_rounds.first 7 | 8 | @redemption = FactoryGirl.create :redemption, token: @token 9 | @round = @redemption.round 10 | @instance = @token.instance 11 | @service = @instance.service 12 | 13 | @lbs_instance = FactoryGirl.create :lbs_instance, service: @service 14 | end 15 | 16 | should 'be creatable with a round and service' do 17 | RoundFinalizer.new @round, @service 18 | end 19 | 20 | context 'with redemptions and failed availabilities' do 21 | setup do 22 | @owned_team = @instance.team 23 | @redeeming_team = @redemption.team 24 | @idle_team = FactoryGirl.create :team 25 | 26 | @lbs_availability = FactoryGirl.create :availability, instance: @lbs_instance, round: @round 27 | @failed_availability = FactoryGirl.create :down_availability, instance: @instance, round: @round 28 | 29 | @finalizer = RoundFinalizer.new @round, @service 30 | end 31 | 32 | should 'identify candidate tokens and failed availabilities' do 33 | assert_includes @finalizer.candidates, @token 34 | assert_includes @finalizer.candidates, @failed_availability 35 | end 36 | 37 | should 'scramble scoring events consistently' do 38 | assert_equal @finalizer.candidates, RoundFinalizer.new(@round, @service).candidates 39 | end 40 | 41 | should 'include the seed and sequence in the metadata' do 42 | assert_includes @finalizer.as_metadata_json, :seed 43 | assert_includes @finalizer.as_metadata_json, :sequence 44 | end 45 | 46 | should 'score events in sequence' do 47 | seq = sequence 'scoring' 48 | @finalizer.candidates.each do |c| 49 | c.expects(:process_movements).once.in_sequence(seq) 50 | c.expects(:as_movement_json).once.in_sequence(seq).returns(:ok) 51 | end 52 | 53 | @finalizer.movements 54 | end 55 | 56 | should 'have sensible movement data' do 57 | assert_kind_of Array, @finalizer.movements 58 | @finalizer.movements.each do |m| 59 | assert_kind_of Hash, m 60 | end 61 | end 62 | end 63 | end 64 | -------------------------------------------------------------------------------- /app/controllers/replacements_controller.rb: -------------------------------------------------------------------------------- 1 | class ReplacementsController < ApplicationController 2 | def index 3 | @services = Service.enabled.all 4 | @teams = Team.order(certname: :asc).all 5 | 6 | @filters = {} 7 | @filters[:team_id] = params[:team_id].to_i if params[:team_id] 8 | @filters[:service_id] = Service.enabled.find(params[:service_id]).id if params[:service_id] 9 | 10 | per_page = 50 11 | 12 | @page = (params[:page] || 1).to_i 13 | return redirect_to(replacements_path(@filters)) if @page < 1 14 | @skip = (@page - 1) * per_page 15 | 16 | @replacements = Replacement. 17 | where(@filters). 18 | limit(50). 19 | offset(@skip). 20 | order(round_id: :desc, created_at: :desc). 21 | joins(:team, :service). 22 | where(services: { enabled: true }). 23 | all 24 | end 25 | 26 | def show 27 | @replacement = Replacement.find(params[:id]) 28 | unless @replacement.service.enabled 29 | raise ActiveRecord::RecordNotFound 30 | end 31 | if params[:download] 32 | return send_file @replacement.archive_path 33 | end 34 | end 35 | 36 | def new 37 | @services = Service.enabled.all 38 | @replacement = current_team.replacements.new 39 | end 40 | 41 | def create 42 | @service = Service.enabled.find(replacement_params[:service_id]) 43 | @services = Service.enabled.all 44 | 45 | @replacement = current_team.replacements. 46 | new(file: replacement_params[:file], 47 | service: @service, 48 | round: Round.current) 49 | begin 50 | if @replacement.save 51 | return redirect_to replacement_path(@replacement.id), 52 | notice: "Replaced #{@service.name}" 53 | end 54 | rescue PG::UniqueViolation => e 55 | @replacement.errors.add :base, "already replaced this round" 56 | rescue => e 57 | @replacement.errors.add :base, "problem dog: #{e.message}" 58 | logger.info e.backtrace 59 | end 60 | 61 | render action: 'new' 62 | end 63 | 64 | private 65 | def replacement_params 66 | params. 67 | require(:replacement). 68 | permit(:service_id, :file) 69 | end 70 | end 71 | -------------------------------------------------------------------------------- /bin/state_monitor: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'redis' 4 | require 'json' 5 | require 'blink1' 6 | require 'celluloid/autostart' 7 | 8 | $redis = Redis.new(host: '10.3.1.8', 9 | port: 6379, 10 | password: 'vueshosBevounWiedMontowmuzDoHondafDeyWor' 11 | ) 12 | 13 | $blink = Blink1.new 14 | $blink.open 15 | $blink.off 16 | 17 | class StateSubscriber 18 | include Celluloid 19 | def initialize(monitor) 20 | @monitor = monitor 21 | end 22 | 23 | def subscribe 24 | msg = $redis.get 'scorebot_service_current_state_production' 25 | puts "loading initial state #{msg.length} bytes" 26 | @monitor.update_state msg 27 | 28 | $redis.subscribe('scorebot_service_state_production') do |on| 29 | on.message do |channel, msg| 30 | puts "received new state #{msg.length} bytes" 31 | @monitor.async.update_state(msg) 32 | end 33 | end 34 | end 35 | end 36 | 37 | class StateMonitor 38 | include Celluloid 39 | 40 | attr_accessor :name, :body, :published_at 41 | 42 | def initialize 43 | @lock = Mutex.new 44 | end 45 | 46 | def reschedule 47 | puts "rescheduling" 48 | @state_checker = every(2){ check_state } 49 | end 50 | 51 | def update_state(message) 52 | puts "updating state" 53 | data = JSON.parse message 54 | 55 | @lock.synchronize do 56 | self.name = data['state_name'] 57 | self.body = data['state_body'] 58 | self.published_at = Time.at data['published_at'] 59 | end 60 | puts "updated state" 61 | end 62 | 63 | def check_state 64 | puts "checking state" 65 | @lock.synchronize do 66 | delta = Time.now - self.published_at 67 | puts "name: #{name}" 68 | puts "since last publish: #{delta}" 69 | 70 | if delta < 10 71 | $blink.off 72 | return 73 | end 74 | 75 | if name == 'commencing' && delta > 30 76 | $blink.set_rgb(255, 0, 255) 77 | elsif name == 'scheduling' && delta > 100 78 | $blink.set_rgb(255, 0, 0) 79 | elsif name == 'waiting_to_end' && delta > 100 80 | $blink.set_rgb(255, 0, 0) 81 | end 82 | end 83 | puts "checked state" 84 | end 85 | end 86 | 87 | @monitor = StateMonitor.new 88 | @subscriber = StateSubscriber.new @monitor 89 | 90 | @monitor.reschedule 91 | puts "subscribing" 92 | @subscriber.async.subscribe 93 | 94 | sleep 95 | -------------------------------------------------------------------------------- /config/puma.rb: -------------------------------------------------------------------------------- 1 | # Puma can serve each request in a thread from an internal thread pool. 2 | # The `threads` method setting takes two numbers: a minimum and maximum. 3 | # Any libraries that use thread pools should be configured to match 4 | # the maximum value specified for Puma. Default is set to 5 threads for minimum 5 | # and maximum; this matches the default thread size of Active Record. 6 | # 7 | threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 } 8 | threads threads_count, threads_count 9 | 10 | # Specifies the `port` that Puma will listen on to receive requests; default is 3000. 11 | # 12 | port ENV.fetch("PORT") { 3000 } 13 | 14 | # Specifies the `environment` that Puma will run in. 15 | # 16 | environment ENV.fetch("RAILS_ENV") { "development" } 17 | 18 | # Specifies the number of `workers` to boot in clustered mode. 19 | # Workers are forked webserver processes. If using threads and workers together 20 | # the concurrency of the application would be max `threads` * `workers`. 21 | # Workers do not work on JRuby or Windows (both of which do not support 22 | # processes). 23 | # 24 | # workers ENV.fetch("WEB_CONCURRENCY") { 2 } 25 | 26 | # Use the `preload_app!` method when specifying a `workers` number. 27 | # This directive tells Puma to first boot the application and load code 28 | # before forking the application. This takes advantage of Copy On Write 29 | # process behavior so workers use less memory. If you use this option 30 | # you need to make sure to reconnect any threads in the `on_worker_boot` 31 | # block. 32 | # 33 | # preload_app! 34 | 35 | # If you are preloading your application and using Active Record, it's 36 | # recommended that you close any connections to the database before workers 37 | # are forked to prevent connection leakage. 38 | # 39 | # before_fork do 40 | # ActiveRecord::Base.connection_pool.disconnect! if defined?(ActiveRecord) 41 | # end 42 | 43 | # The code in the `on_worker_boot` will be called if you are using 44 | # clustered mode by specifying a number of `workers`. After each worker 45 | # process is booted, this block will be run. If you are using the `preload_app!` 46 | # option, you will want to use this block to reconnect to any threads 47 | # or connections that may have been created at application boot, as Ruby 48 | # cannot share connections between processes. 49 | # 50 | # on_worker_boot do 51 | # ActiveRecord::Base.establish_connection if defined?(ActiveRecord) 52 | # end 53 | # 54 | 55 | # Allow puma to be restarted by `rails restart` command. 56 | plugin :tmp_restart 57 | -------------------------------------------------------------------------------- /app/views/admin/rounds/show.html.haml: -------------------------------------------------------------------------------- 1 | %h1 Round #{@round.id} 2 | 3 | %table 4 | %tbody 5 | %tr 6 | %th id 7 | %td= @round.id 8 | %tr 9 | %th signature 10 | %td= @round.signature 11 | %tr 12 | %th started 13 | %td= time_ago @round.created_at 14 | %tr 15 | %th ended 16 | %td 17 | - if @round.ended_at 18 | = time_ago @round.ended_at 19 | - else 20 | %em not yet 21 | %tr 22 | %th round length 23 | %td 24 | - if @round.ended_at 25 | = @round.ended_at - @round.created_at 26 | - else 27 | %em not yet 28 | 29 | %h2 Tokens / Deposits 30 | 31 | %table 32 | %thead 33 | %tr 34 | %th id 35 | %th instance 36 | %th status 37 | %th redemptions 38 | %th activities 39 | %tbody 40 | - @round.tokens.order(instance_id: :asc).each do |t| 41 | %tr 42 | %td= link_to t.id, admin_token_path(t) 43 | %td= describe_instance t.instance 44 | %td 45 | - if t.status == 0 46 | ok 47 | - else 48 | failed 49 | = t.status 50 | %td= t.redemptions.count 51 | %td 52 | = link_to 'show', admin_token_path(t) 53 | 54 | %h2 Redemptions 55 | 56 | %table 57 | %thead 58 | %tr 59 | %th id 60 | %th team 61 | %th instance 62 | %th when 63 | %th activities 64 | %tbody 65 | - @round.redemptions.order(created_at: :asc).each do |r| 66 | %tr 67 | %td= link_to r.id, admin_redemption_path(r) 68 | %td= link_to r.team.certname, admin_team_path(r.team) 69 | %td= describe_instance r.token.instance 70 | %td{title: r.created_at} 71 | = time_ago r.created_at 72 | %td 73 | = link_to 'show', admin_redemption_path(r) 74 | 75 | %h2 Availability Checks 76 | 77 | %table 78 | %thead 79 | %tr 80 | %th id 81 | %th instance 82 | %th when 83 | %th healthy? 84 | %th activities 85 | %tbody 86 | - @round.availabilities.order(created_at: :asc).each do |a| 87 | %tr 88 | %td= link_to a.id, admin_availability_path(a) 89 | %td= describe_instance a.instance 90 | %td{title: a.created_at} 91 | = time_ago_in_words a.created_at, include_seconds: true 92 | ago 93 | %td= a.healthy? 94 | %td 95 | = link_to 'show', admin_availability_path(a) 96 | 97 | %h2 Distribution 98 | 99 | - if @round.distribution 100 | %pre 101 | = @round.distribution.pretty_inspect 102 | -------------------------------------------------------------------------------- /test/models/token_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class TokenTest < ActiveSupport::TestCase 4 | should belong_to :instance 5 | should belong_to :round 6 | 7 | should have_many :redemptions 8 | 9 | should validate_presence_of :instance 10 | should validate_presence_of :round 11 | 12 | context "token generation" do 13 | should "generate and validate a token string" do 14 | token = FactoryGirl.create :token 15 | token_str = token.to_token_string 16 | 17 | token2 = Token.from_token_string token_str 18 | 19 | assert_equal token, token2 20 | 21 | token.destroy 22 | end 23 | 24 | should "resist creating multiple tokens per instance-round" do 25 | token = FactoryGirl.create :token 26 | 27 | assert_uniqueness_constraint do 28 | token2 = Token.create token.attributes 29 | end 30 | end 31 | end 32 | 33 | context 'token deposition' do 34 | setup do 35 | @instance = FactoryGirl.create :instance 36 | @round = FactoryGirl.create :round 37 | end 38 | should 'run a per-service deposit script' do 39 | @shell = mock('shell', output: 'example', status: 0) 40 | 41 | ShellProcess. 42 | expects(:new). 43 | with(Rails.root.join('scripts', @instance.service.name), 44 | 'deposit', 45 | @instance.team_id, 46 | is_a(String), 47 | @round.id). 48 | returns(@shell) 49 | 50 | @token = Token.create instance: @instance, round: @round 51 | @token.deposit 52 | 53 | assert_equal @token, Token.from_token_string(@token.to_token_string) 54 | end 55 | should 'allow deposit scripts to substitute tokens' do 56 | replaced_token = "replaced-token-#{rand(36**5).to_s(36)}" 57 | @shell = mock('shell', output: "!!legitbs-replace-token-3ELrtvi #{replaced_token}", status: 0) 58 | 59 | ShellProcess. 60 | expects(:new). 61 | with(Rails.root.join('scripts', @instance.service.name), 62 | 'deposit', 63 | @instance.team_id, 64 | is_a(String), 65 | @round.id). 66 | returns(@shell) 67 | 68 | @token = Token.create instance: @instance, round: @round 69 | @token.deposit 70 | @token.save 71 | 72 | assert_equal @token, Token.from_token_string(replaced_token) 73 | end 74 | end 75 | 76 | should "not be eligible after #{Token::EXPIRATION} rounds" do 77 | token = FactoryGirl.create :token 78 | assert token.eligible? 79 | 80 | Token::EXPIRATION.times do 81 | FactoryGirl.create :round 82 | assert token.eligible? 83 | end 84 | 85 | refute_includes Token.expiring, token 86 | 87 | FactoryGirl.create :round 88 | refute token.eligible? 89 | 90 | assert_includes Token.expiring, token 91 | end 92 | end 93 | -------------------------------------------------------------------------------- /app/controllers/admin/replacements_controller.rb: -------------------------------------------------------------------------------- 1 | class Admin::ReplacementsController < Admin::BaseController 2 | before_action :set_replacement, only: [:show, :edit, :update, :destroy] 3 | 4 | # GET /replacements 5 | def index 6 | @replacements = Replacement.order(round_id: :asc, id: :asc).all 7 | end 8 | 9 | # GET /replacements/1 10 | def show 11 | @sames = Replacement. 12 | where(digest: @replacement.digest). 13 | order(created_at: :asc). 14 | all 15 | 16 | @teams = Replacement. 17 | where(team_id: @replacement.team_id, 18 | service: @replacement.service). 19 | order(created_at: :asc). 20 | all 21 | 22 | @next = Replacement. 23 | where(team_id: @replacement.team_id, 24 | service: @replacement.service). 25 | where('id > ?', @replacement.id). 26 | order(id: :asc). 27 | first 28 | 29 | @instance = Instance. 30 | where(team: @replacement.team, 31 | service: @replacement.service). 32 | first 33 | 34 | @tokens = @instance. 35 | tokens. 36 | where('created_at > ?', 37 | @replacement.created_at) 38 | 39 | if @next 40 | @tokens = @tokens. 41 | where('created_at < ?', 42 | @next.created_at) 43 | end 44 | 45 | @redemptions = @tokens.map(&:redemptions).flatten 46 | end 47 | 48 | # GET /replacements/new 49 | def new 50 | @replacement = Replacement.new(round_id: 1, team_id: 16) 51 | end 52 | 53 | # GET /replacements/1/edit 54 | def edit 55 | end 56 | 57 | # POST /replacements 58 | def create 59 | @replacement = Replacement.new(replacement_params) 60 | 61 | if @replacement.save 62 | redirect_to @replacement, notice: 'Replacement was successfully created.' 63 | else 64 | render :new 65 | end 66 | end 67 | 68 | # PATCH/PUT /replacements/1 69 | def update 70 | if @replacement.update(replacement_params) 71 | redirect_to @replacement, notice: 'Replacement was successfully updated.' 72 | else 73 | render :edit 74 | end 75 | end 76 | 77 | # DELETE /replacements/1 78 | def destroy 79 | @replacement.destroy 80 | redirect_to replacements_url, notice: 'Replacement was successfully destroyed.' 81 | end 82 | 83 | private 84 | # Use callbacks to share common setup or constraints between actions. 85 | def set_replacement 86 | @replacement = Replacement.find(params[:id]) 87 | end 88 | 89 | # Only allow a trusted parameter "white list" through. 90 | def replacement_params 91 | params.require(:replacement).permit(:team_id, :service_id, :round_id, :file) 92 | end 93 | end 94 | -------------------------------------------------------------------------------- /app/models/team.rb: -------------------------------------------------------------------------------- 1 | class Team < ActiveRecord::Base 2 | has_many :redemptions 3 | has_many :flags 4 | has_many :captures, through: :redemptions 5 | has_many :instances 6 | has_many :tickets 7 | has_many :replacements 8 | before_create :set_uuid 9 | 10 | after_rollback :flush_counters 11 | attr_accessor :dupe_ctr_defer 12 | attr_accessor :other_ctr_defer 13 | 14 | PARTICIPANT_COUNT = 15 15 | 16 | def as_ca_json 17 | { 18 | teamname: name, 19 | uuid: uuid, 20 | certname: certname 21 | } 22 | end 23 | 24 | def self.legitbs 25 | return @@legitbs if defined?(@@legitbs) && (@@legitbs.reload rescue false) 26 | @@legitbs = find_by uuid: 'deadbeef-7872-499a-a060-3143de953e28' 27 | end 28 | 29 | def self.without_legitbs 30 | where('certname != ?', 'legitbs') 31 | end 32 | 33 | def self.for_scoreboard 34 | q = <<-SQL 35 | SELECT 36 | t.name, 37 | t.id, 38 | count(f.id) as score, 39 | coalesce(t.display, t.name) as display_name 40 | FROM teams as t 41 | left JOIN flags AS f 42 | ON f.team_id = t.id 43 | WHERE t.certname != 'legitbs' 44 | GROUP BY t.name, t.id, display_name 45 | ORDER BY 46 | score desc, 47 | t.name asc 48 | SQL 49 | 50 | StatsD.measure('scoreboard_query'){ connection.select_all(q).map(&:symbolize_keys) } 51 | end 52 | 53 | def self.as_standings_json 54 | data = for_scoreboard 55 | 56 | place = 0 57 | prev_score = Flag.count + 1 58 | place_buf = 1 59 | { 60 | standings: data.map do |r| 61 | score = r[:score].to_i 62 | if score < prev_score 63 | place += place_buf 64 | place_buf = 1 65 | prev_score = score 66 | else 67 | place_buf += 1 68 | end 69 | { pos: place, team: r[:name], score: (16 - place), id: r[:id], place: place } 70 | # { pos: place, 71 | # team: r[:name], 72 | # score: score, 73 | # id: r[:id], 74 | # place: place } 75 | end, 76 | display_names: Hash[data.map do |r| 77 | [r[:id], r[:display_name]] 78 | end], 79 | generated_at: Time.now 80 | } 81 | end 82 | 83 | def scores_by_service 84 | q = <<-SQL 85 | SELECT 86 | service_id, count(service_id) 87 | FROM flags 88 | WHERE team_id=#{id} 89 | GROUP BY service_id 90 | ORDER BY service_id ASC 91 | SQL 92 | 93 | result = self.class.connection.select_all(q) 94 | Hash[result.map do |row| 95 | [row['service_id'].to_i, row['count'].to_i] 96 | end] 97 | end 98 | 99 | private 100 | def set_uuid 101 | self.uuid ||= SecureRandom.uuid 102 | end 103 | 104 | def flush_counters 105 | increment! :dupe_ctr, dupe_ctr_defer if dupe_ctr_defer 106 | increment! :other_ctr, other_ctr_defer if other_ctr_defer 107 | end 108 | end 109 | -------------------------------------------------------------------------------- /app/models/redemption.rb: -------------------------------------------------------------------------------- 1 | class Redemption < ActiveRecord::Base 2 | belongs_to :team 3 | belongs_to :token, counter_cache: true 4 | belongs_to :round 5 | has_many :captures 6 | has_many :flags, through: :captures 7 | 8 | before_create :set_uuid 9 | after_create :log_spew 10 | 11 | def self.redeem_for(team, token_str) 12 | transaction do 13 | round = Round.current 14 | 15 | token = Token.from_token_string token_str 16 | if token.blank? 17 | team.increment! :notfound_ctr 18 | raise NoTokenError.new 19 | end 20 | unless token.eligible? 21 | team.increment! :old_ctr 22 | raise OldTokenError.new 23 | end 24 | if team == token.instance.team 25 | team.increment! :self_ctr 26 | raise SelfScoringError.new 27 | end 28 | 29 | begin 30 | candidate = create team: team, token: token, round: round 31 | rescue ActiveRecord::RecordNotUnique => e 32 | team.dupe_ctr_defer ||= 0 33 | team.dupe_ctr_defer += 1 34 | raise DuplicateTokenError.new 35 | rescue => e 36 | team.other_ctr_defer ||= 0 37 | team.other_ctr_defer += 1 38 | raise OtherTokenError.new e 39 | end 40 | 41 | return candidate 42 | end 43 | end 44 | 45 | class OtherTokenError < ArgumentError 46 | def initialize(e) 47 | correlation = SecureRandom.uuid 48 | Scorebot.log( 49 | [correlation, 50 | e.class.name, 51 | e.message, 52 | e.backtrace.join(', ')]. 53 | join('; ')) 54 | super "some other error #{correlation}" 55 | end 56 | end 57 | 58 | class DuplicateTokenError < ArgumentError 59 | def initialize 60 | super "Already redeemed that token" 61 | end 62 | end 63 | 64 | class OldTokenError < ArgumentError 65 | def initialize 66 | super "Token too old" 67 | end 68 | end 69 | 70 | class NoTokenError < ArgumentError 71 | def initialize 72 | super "Token doesn't exist" 73 | end 74 | end 75 | 76 | class SelfScoringError < ArgumentError 77 | def initialize 78 | super "Can't redeem your own tokens" 79 | end 80 | end 81 | 82 | def as_event_json 83 | { 84 | redeeming_team: team.as_json, 85 | owned_team: token.instance.team.as_json, 86 | service: token.instance.service.as_json, 87 | redemption_stats: { 88 | service: token.instance.service.total_redemptions, 89 | instance: token.instance.total_redemptions, 90 | redeeming_team: team.redemptions.count, 91 | all: Redemption.count, 92 | }, 93 | redeemed_at_local: created_at.to_formatted_s(:ctf) 94 | } 95 | end 96 | 97 | private 98 | def set_uuid 99 | self.uuid = SecureRandom.uuid 100 | end 101 | 102 | def log_spew 103 | Scorebot.log "Redeemed token #{token_id} from #{token.instance.team.name} #{token.instance.service.name} for team #{team.name} as #{uuid}" 104 | end 105 | end 106 | -------------------------------------------------------------------------------- /test/models/availability_check_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class AvailabilityCheckTest < ActiveSupport::TestCase 4 | setup do 5 | @round = FactoryGirl.create :current_round 6 | 7 | @legitbs = FactoryGirl.create :legitbs 8 | Team.stubs(:legitbs).returns(@legitbs) 9 | @general_teams = FactoryGirl.create_list :team, 20 10 | 11 | @service = FactoryGirl.create :service, name: 'noop', enabled: true 12 | @shell = stub 'shell', status: 0, output: 'okay' 13 | ShellProcess.stubs(:new).returns(@shell) 14 | 15 | @lbs_instance = FactoryGirl.create :instance, team: @legitbs, service: @service 16 | @instances = @general_teams.map{|t| Instance.create team: t, service: @service } 17 | end 18 | 19 | 20 | context 'initializing' do 21 | should 'initialize for a service' do 22 | assert @check = AvailabilityCheck.for_service(@service) 23 | 24 | assert_equal @lbs_instance, @check.lbs_instance 25 | assert_equal @instances, @check.non_lbs_instances 26 | end 27 | 28 | should 'cache instances' do 29 | assert(checker = AvailabilityCheck.for_service(@service)) 30 | assert_equal checker.object_id, AvailabilityCheck.for_service(@service).object_id 31 | end 32 | end 33 | 34 | context 'timing' do 35 | setup do 36 | @check = AvailabilityCheck.for_service @service 37 | end 38 | 39 | should 'store timing history' do 40 | assert_respond_to @check, :timing_history 41 | assert_kind_of Enumerable, @check.timing_history 42 | assert_includes @check.timing_history, AvailabilityCheck::INITIAL_TIMING 43 | end 44 | 45 | should 'provide an average time' do 46 | assert_respond_to @check, :timing_average 47 | assert_kind_of Numeric, @check.timing_average 48 | @check.timing_history = [1, 2, 3] 49 | assert_equal 2.0, @check.timing_average 50 | end 51 | 52 | should 'warn if check will go longer than a round' do 53 | @check.timing_history = [ 2 * Round::ROUND_LENGTH ] 54 | assert @check.gonna_run_long? 55 | end 56 | 57 | should 'schedule checks for random times within the round' 58 | end 59 | 60 | context 'Availability checking' do 61 | should 'check all the teams' do 62 | lbs_av = stub 'legitbs availability', save: true 63 | @lbs_instance.expects(:check_availability).once.returns(lbs_av) 64 | 65 | @instances.each do |i| 66 | av = stub 'availability', status: 0 67 | i.expects(:check_availability).once.returns(av) 68 | av.expects(:save).once 69 | end 70 | 71 | @check = AvailabilityCheck.new @service 72 | 73 | @check.instance_variable_set(:@lbs_instance, @lbs_instance) 74 | @check.instance_variable_set(:@non_lbs_instances, @instances) 75 | 76 | assert_nothing_raised do 77 | @check.check_all_instances 78 | end 79 | end 80 | should 'claim flags on failures' do 81 | @check = AvailabilityCheck.new @service 82 | @check.instance_variable_set(:@lbs_check, 83 | stub('lbs availability', :healthy? => true)) 84 | 85 | failure = stub('failed', :healthy? => false) 86 | okay = stub('okay', :healthy? => true) 87 | checks = [failure] + [okay] * 19 88 | 89 | @check.instance_variable_set(:@non_lbs_checks, checks) 90 | 91 | failure.expects(:distribute!) 92 | 93 | assert_nothing_raised do 94 | @check.distribute_flags 95 | end 96 | end 97 | end 98 | end 99 | -------------------------------------------------------------------------------- /app/models/availability_check.rb: -------------------------------------------------------------------------------- 1 | class AvailabilityCheck 2 | WORKERS = 16 3 | INITIAL_TIMING = 2 * 60 4 | 5 | attr_accessor :timing_history 6 | attr_accessor :deadline 7 | 8 | def self.for_service(service) 9 | @@cache ||= Hash.new 10 | @@cache[service] ||= new service 11 | end 12 | 13 | def initialize(service) 14 | @service = service 15 | self.timing_history = [INITIAL_TIMING] 16 | @history_lock = Mutex.new 17 | end 18 | 19 | def timing_average 20 | timing_history.sum.to_f / timing_history.length 21 | end 22 | 23 | def gonna_run_long? 24 | Time.now.to_f + timing_average >= deadline.to_f 25 | end 26 | 27 | def schedule! 28 | remaining = deadline.to_f - Time.now.to_f 29 | 30 | # skew earlier 31 | start_before = remaining - (70 + timing_average) 32 | 33 | wait = rand(start_before) 34 | 35 | @thread = Thread.new do 36 | chill wait 37 | clock = Time.now.to_f 38 | check_all_instances 39 | duration = Time.now.to_f - clock 40 | 41 | @history_lock.synchronize do 42 | timing_history << duration 43 | end 44 | 45 | Round.clear_active_connections! 46 | end 47 | 48 | wait 49 | end 50 | 51 | def join 52 | return unless defined? @thread 53 | Round.clear_active_connections! 54 | @thread.join 55 | end 56 | 57 | def instances 58 | @service.instances 59 | end 60 | 61 | def lbs_instance 62 | @lbs_instance ||= instances.where(team_id: Team.legitbs.id).first 63 | end 64 | 65 | def non_lbs_instances 66 | @non_lbs_instances ||= instances.where('team_id != ?', Team.legitbs.id).includes(:service, :team) 67 | end 68 | 69 | def check_all_instances 70 | return unless @service.enabled 71 | 72 | @lbs_check = lbs_instance.check_availability Round.current 73 | @lbs_check.save 74 | 75 | @non_lbs_checks = process_instance_queue 76 | end 77 | 78 | def process_instance_queue 79 | queue = non_lbs_instances.shuffle 80 | queue_mutex = Mutex.new 81 | 82 | results = [] 83 | result_mutex = Mutex.new 84 | 85 | round = Round.current 86 | 87 | threads = 1.upto(WORKERS).map do 88 | Thread.new do 89 | loop do 90 | instance = queue_mutex.synchronize do 91 | queue.shift 92 | end 93 | 94 | break if instance.nil? 95 | 96 | l "#{ instance.service.name } #{instance.team.certname} checking" 97 | 98 | av = instance.check_availability round 99 | l "#{ instance.service.name } #{instance.team.certname} status #{av.status}" 100 | result_mutex.synchronize do 101 | results.push av 102 | end 103 | end 104 | Round.clear_active_connections! 105 | end 106 | end 107 | 108 | threads.each {|t| t.join } 109 | 110 | Availability.transaction do 111 | results.each(&:save!) 112 | end 113 | 114 | results 115 | end 116 | 117 | def distribute_flags 118 | return unless @service.enabled 119 | return unless @lbs_check.healthy? 120 | 121 | @non_lbs_checks.reject(&:healthy?).each(&:distribute!) 122 | end 123 | 124 | private 125 | def chill(duration) 126 | schedule = Time.now.to_f + duration 127 | while Time.now.to_f < schedule 128 | sleep 0.1 129 | end 130 | end 131 | 132 | def l(str) 133 | Scorebot.log str 134 | end 135 | end 136 | -------------------------------------------------------------------------------- /app/models/availability.rb: -------------------------------------------------------------------------------- 1 | class Availability < ActiveRecord::Base 2 | belongs_to :instance 3 | belongs_to :round 4 | belongs_to :token, optional: true 5 | scope :failed, -> { where.not(status: 0) } 6 | has_many :penalties 7 | 8 | def self.check(instance, round) 9 | candidate = new instance: instance, round: round 10 | candidate.check 11 | return candidate 12 | end 13 | 14 | def fix_availability 15 | return if status == 0 16 | self.status = 0 17 | self.memo = "administratively fixed by legitbs <3 vito@legitbs.net" 18 | save 19 | end 20 | 21 | def decoded_dingus 22 | return nil if dingus.nil? 23 | @decoded_dingus ||= Dingus.new self.dingus, plaintext: true 24 | end 25 | 26 | def legit_dingus? 27 | return nil if decoded_dingus.nil? 28 | return false unless decoded_dingus.legit? 29 | return false unless decoded_dingus.team == instance.team 30 | return false unless ((created_at.to_i - 30)..(created_at.to_i + 30)).cover? decoded_dingus.to_h[:clocktime] 31 | 32 | return true 33 | end 34 | 35 | def check 36 | service_name = instance.service.name 37 | team_address = instance.team.address 38 | 39 | dir = Rails.root.join 'scripts', service_name 40 | script = 'availability' 41 | 42 | shell = ShellProcess. 43 | new( 44 | dir, 45 | script, 46 | instance.team.id 47 | ) 48 | 49 | StatsD.measure "#{instance.team.certname}.#{instance.service.name}.availability" do 50 | self.status = shell.status 51 | end 52 | 53 | self.memo = shell.output.force_encoding('binary') 54 | 55 | load_dinguses shell.output 56 | 57 | if self.token_string and !self.token 58 | self.status = 420 59 | end 60 | 61 | self 62 | end 63 | 64 | def healthy? 65 | status == 0 66 | end 67 | 68 | def as_movement_json 69 | return { availability: { id: id, healthy: true } } if healthy? 70 | 71 | return { availability: as_json(only: :id, include: :penalties) } 72 | end 73 | 74 | def load_dinguses(memo) 75 | if has_token = /^!!legitbs-validate-token-7OPuwAj (.+)$/.match(memo) 76 | self.token_string = has_token[1] 77 | candidate_token = Token.from_token_string self.token_string 78 | 79 | return false if candidate_token.nil? 80 | return false if candidate_token.instance != self.instance 81 | return false if candidate_token.expired? 82 | 83 | self.token = candidate_token 84 | end 85 | end 86 | 87 | def process_movements(_round) 88 | return if instance.team == Team.legitbs 89 | return unless instance.legitbs_instance.availabilities.find_by(round: round).healthy? 90 | 91 | flags = instance.flags.limit(15) 92 | 93 | return distribute_parking(flags) if flags.count < 15 94 | return distribute_everywhere(flags) 95 | end 96 | 97 | private 98 | def distribute_everywhere(flags) 99 | teams = Team.where('id != ? and id != ?', 100 | Team.legitbs.id, 101 | instance.team.id).to_a 102 | 103 | Scorebot.log "reallocating #{flags.length} from #{instance.team.name} #{instance.service.name} flags to #{teams.map(&:certname)} teams" 104 | 105 | flags.zip(teams) do |f, t| 106 | break if t.nil? 107 | f.team = t 108 | f.save! 109 | penalties.create team_id: t.id, flag_id: f.id 110 | end 111 | end 112 | 113 | def distribute_parking(flags) 114 | flags.each do |f| 115 | f.team = Team.legitbs 116 | f.save! 117 | penalties.create team_id: Team.legitbs.id, flag_id: f.id 118 | end 119 | end 120 | end 121 | --------------------------------------------------------------------------------