Initial commit
This commit is contained in:
71
lib/opendax/config_loader.rb
Normal file
71
lib/opendax/config_loader.rb
Normal file
@@ -0,0 +1,71 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'yaml'
|
||||
|
||||
module Opendax
|
||||
# Loads config/app.yml and deep-merges an environment overlay when present.
|
||||
#
|
||||
# Overlay resolution (first match wins):
|
||||
# 1. OPENDAX_ENV=local|staging|production → config/app.{env}.yml
|
||||
# 2. If OPENDAX_ENV unset and config/app.local.yml exists → use local overlay
|
||||
#
|
||||
# Never commit real secrets to config/app.yml — use app.local.yml or app.production.yml.
|
||||
module ConfigLoader
|
||||
BASE_PATH = 'config/app.yml'
|
||||
|
||||
module_function
|
||||
|
||||
def load
|
||||
base = load_yaml_hash!(BASE_PATH, 'base config')
|
||||
overlay_path = resolve_overlay_path
|
||||
return base unless overlay_path && File.exist?(overlay_path)
|
||||
|
||||
overlay = load_yaml_hash!(overlay_path, 'overlay config')
|
||||
deep_merge(base, overlay)
|
||||
end
|
||||
|
||||
def load_yaml_hash!(path, label)
|
||||
data = YAML.load_file(path)
|
||||
return data if data.is_a?(Hash)
|
||||
|
||||
raise TypeError,
|
||||
"#{label} #{path} must be a YAML mapping (Hash), got #{data.class}. " \
|
||||
'Check syntax — quote values that contain colons (e.g. database passwords).'
|
||||
end
|
||||
|
||||
def resolve_overlay_path
|
||||
env = ENV['OPENDAX_ENV'].to_s.strip
|
||||
if env.empty?
|
||||
return 'config/app.local.yml' if File.exist?('config/app.local.yml')
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
"config/app.#{env}.yml"
|
||||
end
|
||||
|
||||
def active_overlay
|
||||
path = resolve_overlay_path
|
||||
path if path && File.exist?(path)
|
||||
end
|
||||
|
||||
def deep_merge(base, overlay)
|
||||
unless base.is_a?(Hash) && overlay.is_a?(Hash)
|
||||
raise TypeError,
|
||||
"deep_merge expects Hash + Hash, got #{base.class} + #{overlay.class}"
|
||||
end
|
||||
|
||||
base.merge(overlay) do |key, old_val, new_val|
|
||||
if old_val.is_a?(Hash) && new_val.is_a?(Hash)
|
||||
deep_merge(old_val, new_val)
|
||||
elsif old_val.is_a?(Hash) && !new_val.is_a?(Hash)
|
||||
raise TypeError,
|
||||
"Config type mismatch at #{key}: base has Hash but overlay has " \
|
||||
"#{new_val.class}. Quote scalar values in app.staging.yml (passwords with ':' need quotes)."
|
||||
else
|
||||
new_val
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
46
lib/opendax/payload.rb
Normal file
46
lib/opendax/payload.rb
Normal file
@@ -0,0 +1,46 @@
|
||||
require 'jwt'
|
||||
|
||||
module Opendax
|
||||
|
||||
class Error < StandardError
|
||||
end
|
||||
|
||||
class Payload
|
||||
def initialize(params)
|
||||
@secret = params[:secret]
|
||||
@expire = params[:expire] || 600
|
||||
|
||||
raise Opendax::Error.new unless @secret
|
||||
raise Opendax::Error.new unless @expire > 0
|
||||
end
|
||||
|
||||
def generate!(params)
|
||||
raise Opendax::Error.new unless params[:service]
|
||||
raise Opendax::Error.new unless params[:image]
|
||||
|
||||
token = params.merge({
|
||||
'iat': Time.now.to_i,
|
||||
'exp': (Time.now + @expire).to_i
|
||||
})
|
||||
|
||||
JWT.encode token, @secret, 'HS256'
|
||||
end
|
||||
|
||||
def decode!(token)
|
||||
JWT.decode(token, @secret, true, {
|
||||
algorithm: 'HS256'
|
||||
}).first
|
||||
end
|
||||
|
||||
def safe_decode(token)
|
||||
begin
|
||||
decode!(token)
|
||||
rescue JWT::ExpiredSignature
|
||||
rescue JWT::ImmatureSignature
|
||||
rescue JWT::VerificationError
|
||||
rescue JWT::DecodeError
|
||||
nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
79
lib/opendax/renderer.rb
Normal file
79
lib/opendax/renderer.rb
Normal file
@@ -0,0 +1,79 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'openssl'
|
||||
require 'sshkey'
|
||||
require 'pathname'
|
||||
require 'yaml'
|
||||
require 'base64'
|
||||
require 'fileutils'
|
||||
|
||||
module Opendax
|
||||
# Renderer is class for rendering Opendax templates.
|
||||
class Renderer
|
||||
TEMPLATE_PATH = Pathname.new('./templates')
|
||||
|
||||
BARONG_KEY = 'config/secrets/barong.key'
|
||||
APPLOGIC_KEY = 'config/secrets/applogic.key'
|
||||
SSH_KEY = 'config/secrets/app.key'
|
||||
|
||||
def render
|
||||
@config ||= config
|
||||
@utils ||= utils
|
||||
@name ||= @config['app']['name'].downcase
|
||||
@barong_key ||= OpenSSL::PKey::RSA.new(File.read(BARONG_KEY), '')
|
||||
@applogic_key ||= OpenSSL::PKey::RSA.new(File.read(APPLOGIC_KEY), '')
|
||||
@barong_private_key ||= Base64.urlsafe_encode64(@barong_key.to_pem)
|
||||
@barong_public_key ||= Base64.urlsafe_encode64(@barong_key.public_key.to_pem)
|
||||
@applogic_private_key ||= Base64.urlsafe_encode64(@applogic_key.to_pem)
|
||||
@applogic_public_key ||= Base64.urlsafe_encode64(@applogic_key.public_key.to_pem)
|
||||
|
||||
Dir.glob("#{TEMPLATE_PATH}/**/*.erb", File::FNM_DOTMATCH).each do |file|
|
||||
output_file = template_name(file)
|
||||
FileUtils.chmod 0o644, output_file if File.exist?(output_file)
|
||||
render_file(file, output_file)
|
||||
FileUtils.chmod 0o444, output_file if @config['render_protect']
|
||||
end
|
||||
end
|
||||
|
||||
def render_file(file, out_file)
|
||||
puts "Rendering #{out_file}"
|
||||
result = ERB.new(File.read(file), trim_mode: '-').result(binding)
|
||||
dir = File.dirname(out_file)
|
||||
FileUtils.mkdir(dir) unless Dir.exist?(dir)
|
||||
File.write(out_file, result)
|
||||
end
|
||||
|
||||
def ssl_helper(arg)
|
||||
@config['ssl']['enabled'] ? arg << 's' : arg
|
||||
end
|
||||
|
||||
def template_name(file)
|
||||
path = Pathname.new(file)
|
||||
out_path = path.relative_path_from(TEMPLATE_PATH).sub('.erb', '')
|
||||
|
||||
File.join('.', out_path)
|
||||
end
|
||||
|
||||
def render_keys
|
||||
generate_key(BARONG_KEY)
|
||||
generate_key(APPLOGIC_KEY)
|
||||
generate_key(SSH_KEY, public: true)
|
||||
end
|
||||
|
||||
def generate_key(filename, public: false)
|
||||
return if File.file?(filename)
|
||||
|
||||
key = SSHKey.generate(type: 'RSA', bits: 2048)
|
||||
File.write(filename, key.private_key)
|
||||
File.write("#{filename}.pub", key.ssh_public_key) if public
|
||||
end
|
||||
|
||||
def config
|
||||
Opendax::ConfigLoader.load
|
||||
end
|
||||
|
||||
def utils
|
||||
YAML.load_file('./config/utils.yml')
|
||||
end
|
||||
end
|
||||
end
|
||||
81
lib/opendax/vault.rb
Normal file
81
lib/opendax/vault.rb
Normal file
@@ -0,0 +1,81 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Opendax
|
||||
class Vault
|
||||
POLICIES_NAMES = ["barong", "finex_engine", "peatio_rails", "peatio_crypto", "peatio_upstream", "peatio_matching"]
|
||||
|
||||
def vault_secrets_path
|
||||
'config/vault-secrets.yml'
|
||||
end
|
||||
|
||||
def vault_exec(command)
|
||||
`docker-compose exec -T vault sh -c '#{command}'`
|
||||
end
|
||||
|
||||
def secrets(command, endpoints, options = '')
|
||||
endpoints.each { |endpoint| vault_exec("vault secrets #{command} #{options} #{endpoint}") }
|
||||
end
|
||||
|
||||
def setup
|
||||
puts '----- Checking Vault status -----'
|
||||
vault_status = YAML.safe_load(vault_exec('vault status -format yaml'))
|
||||
|
||||
return if vault_status.nil?
|
||||
|
||||
if vault_status['initialized']
|
||||
puts '----- Vault is initialized -----'
|
||||
begin
|
||||
vault_secrets = YAML.safe_load(File.read(vault_secrets_path))
|
||||
rescue SystemCallError => e
|
||||
puts 'Vault keys are missing'
|
||||
return
|
||||
end
|
||||
vault_root_token = vault_secrets['root_token']
|
||||
unseal_keys = vault_secrets['unseal_keys_b64'][0, 3]
|
||||
else
|
||||
puts '----- Initializing Vault -----'
|
||||
vault_init_output = YAML.safe_load(vault_exec('vault operator init -format yaml --recovery-shares=3 --recovery-threshold=2'))
|
||||
File.write(vault_secrets_path, YAML.dump(vault_init_output))
|
||||
vault_root_token = vault_init_output['root_token']
|
||||
unseal_keys = vault_init_output['unseal_keys_b64'][0, 3]
|
||||
end
|
||||
|
||||
if vault_status['sealed']
|
||||
puts '----- Unsealing Vault -----'
|
||||
unseal_keys.each { |key| vault_exec("vault operator unseal #{key}") }
|
||||
else
|
||||
puts '----- Vault is unsealed -----'
|
||||
end
|
||||
|
||||
return vault_root_token if vault_status['initialized']
|
||||
|
||||
puts '----- Vault login -----'
|
||||
vault_exec("vault login #{vault_root_token}")
|
||||
|
||||
puts '----- Configuring the endpoints -----'
|
||||
secrets('enable', %w[totp transit])
|
||||
secrets('disable', ['secret'])
|
||||
secrets('enable', ['kv'], '-path=secret -version=1')
|
||||
vault_root_token
|
||||
end
|
||||
|
||||
def load_policies(deployment_name, vault_root_token)
|
||||
puts '----- Vault login -----'
|
||||
vault_exec("vault login #{vault_root_token}")
|
||||
|
||||
tokens = {}
|
||||
POLICIES_NAMES.each do |policy|
|
||||
name = "#{deployment_name.downcase}_#{policy}"
|
||||
|
||||
puts "Loading #{name} policy..."
|
||||
vault_exec("vault policy write #{name} /tmp/policies/#{policy}.hcl")
|
||||
|
||||
puts "Creating the #{name} token..."
|
||||
vault_token_create_output = YAML.safe_load(vault_exec("vault token create -policy=#{name} -renewable=true -ttl=240h -period=240h -format=yaml"))
|
||||
tokens["#{policy}_token"] = vault_token_create_output["auth"]["client_token"]
|
||||
end
|
||||
|
||||
tokens
|
||||
end
|
||||
end
|
||||
end
|
||||
74
lib/opendax/webhook.rb
Normal file
74
lib/opendax/webhook.rb
Normal file
@@ -0,0 +1,74 @@
|
||||
require 'sinatra/base'
|
||||
require 'json'
|
||||
require 'yaml'
|
||||
|
||||
require_relative 'payload'
|
||||
require_relative 'renderer'
|
||||
|
||||
class Webhook < Sinatra::Base
|
||||
CONFIG_PATH = 'config/app.yml'.freeze
|
||||
|
||||
set :show_exceptions, false
|
||||
|
||||
def initialize
|
||||
super
|
||||
@services = %w[barong peatio frontend AdminTower applogic finex-engine finex-api]
|
||||
secret = ENV['WEBHOOK_JWT_SECRET']
|
||||
raise 'WEBHOOK_JWT_SECRET is not set' if secret.to_s.empty?
|
||||
@decoder = Opendax::Payload.new(secret: secret)
|
||||
end
|
||||
|
||||
def update_config(service, image)
|
||||
config = YAML.load_file(CONFIG_PATH)
|
||||
config["images"][service] = image
|
||||
File.open(CONFIG_PATH, 'w') {|f| f.write config.to_yaml }
|
||||
end
|
||||
|
||||
before do
|
||||
content_type 'application/json'
|
||||
end
|
||||
|
||||
get '/deploy/ping' do
|
||||
'pong'
|
||||
end
|
||||
|
||||
get '/deploy/:token' do |token|
|
||||
decoded = @decoder.safe_decode(token)
|
||||
return answer(400, 'invalid token') unless decoded
|
||||
|
||||
service = decoded['service']
|
||||
image = decoded['image']
|
||||
|
||||
return answer(400, 'service is not specified') unless service
|
||||
return answer(400, 'image is not specified') unless image
|
||||
return answer(404, 'unknown service') unless @services.include? service
|
||||
return answer(400, 'invalid image') if (%r(^(([-_\w\.]){,20}(\/|:))+([-\w\.]{,20})$) =~ image) == nil
|
||||
|
||||
system "docker image pull #{image}"
|
||||
|
||||
unless $?.success?
|
||||
system("docker image inspect #{image} > /dev/null")
|
||||
return answer(404, 'invalid image') unless $?.success?
|
||||
end
|
||||
|
||||
if $?.success?
|
||||
update_config(service, image)
|
||||
|
||||
renderer = Opendax::Renderer.new
|
||||
renderer.render
|
||||
|
||||
system "docker-compose up -Vd #{service}"
|
||||
end
|
||||
|
||||
return answer(500, 'could not restart container') unless $?.success?
|
||||
return answer(200, "service #{service} updated with image #{image}")
|
||||
end
|
||||
|
||||
def answer(response_status, message)
|
||||
status response_status
|
||||
|
||||
{
|
||||
message: message
|
||||
}.to_json
|
||||
end
|
||||
end
|
||||
22
lib/tasks/audit.rake
Normal file
22
lib/tasks/audit.rake
Normal file
@@ -0,0 +1,22 @@
|
||||
namespace :audit do
|
||||
desc 'Print CVE audit commands (run via Docker on this machine)'
|
||||
task :help do
|
||||
puts <<~HELP
|
||||
Fibitex dependency CVE audit — see Docs/10-cve-audit.md
|
||||
|
||||
Ruby (Dena):
|
||||
docker run --rm -v "#{Dir.pwd}/../Dena:/app" -w /app ruby:2.6.6 bash -c "gem install bundler:2.1.4 bundler-audit --no-document && bundle audit check --update"
|
||||
|
||||
Ruby (Dalan):
|
||||
docker run --rm -v "#{Dir.pwd}/../Dalan:/app" -w /app ruby:2.6.6 bash -c "gem install bundler:2.1.4 bundler-audit --no-document && bundle audit check --update"
|
||||
|
||||
OSV (summary):
|
||||
docker run --rm -v "#{Dir.pwd}/../Dena:/src" ghcr.io/google/osv-scanner:latest -L /src/Gemfile.lock
|
||||
docker run --rm -v "#{Dir.pwd}/../Dalan:/src" ghcr.io/google/osv-scanner:latest -L /src/Gemfile.lock
|
||||
docker run --rm -v "#{Dir.pwd}/../Gereh:/src" ghcr.io/google/osv-scanner:latest -L /src/yarn.lock
|
||||
|
||||
Config secrets check:
|
||||
bundle exec rake config:check
|
||||
HELP
|
||||
end
|
||||
end
|
||||
47
lib/tasks/config.rake
Normal file
47
lib/tasks/config.rake
Normal file
@@ -0,0 +1,47 @@
|
||||
namespace :config do
|
||||
desc 'Verify config/app.yml contains no real secrets (safe for git)'
|
||||
task :check do
|
||||
require_relative '../opendax/config_loader'
|
||||
|
||||
raw = YAML.load_file('config/app.yml')
|
||||
leaks = []
|
||||
|
||||
check_value = lambda do |path, value|
|
||||
return if value.nil?
|
||||
return unless value.is_a?(String)
|
||||
|
||||
str = value.strip
|
||||
return if str.empty? || str == 'changeme'
|
||||
|
||||
if str.match?(/\As\.[A-Za-z0-9]{20,}\z/)
|
||||
leaks << "#{path}: looks like a Vault token"
|
||||
elsif path.include?('password') && str != 'changeme'
|
||||
leaks << "#{path}: non-placeholder password"
|
||||
end
|
||||
end
|
||||
|
||||
walk = lambda do |node, prefix|
|
||||
case node
|
||||
when Hash
|
||||
node.each { |k, v| walk.call(v, prefix.empty? ? k.to_s : "#{prefix}.#{k}") }
|
||||
when Array
|
||||
node.each_with_index { |v, i| walk.call(v, "#{prefix}[#{i}]") }
|
||||
else
|
||||
check_value.call(prefix, node)
|
||||
end
|
||||
end
|
||||
|
||||
walk.call(raw, '')
|
||||
|
||||
if leaks.any?
|
||||
puts 'FAIL: config/app.yml may contain secrets:'
|
||||
leaks.each { |l| puts " - #{l}" }
|
||||
puts 'Move sensitive values to config/app.local.yml or config/app.production.yml'
|
||||
exit 1
|
||||
end
|
||||
|
||||
overlay = Opendax::ConfigLoader.active_overlay
|
||||
puts 'OK: config/app.yml looks safe for git'
|
||||
puts "Active overlay: #{overlay || 'none'}"
|
||||
end
|
||||
end
|
||||
32
lib/tasks/db.rake
Normal file
32
lib/tasks/db.rake
Normal file
@@ -0,0 +1,32 @@
|
||||
namespace :db do
|
||||
|
||||
def mysql_cli
|
||||
return "mysql -u root -h db -P 3306 -pchangeme"
|
||||
end
|
||||
|
||||
desc 'Create database'
|
||||
task :create do
|
||||
sh 'docker-compose run --rm peatio bundle exec rake db:create'
|
||||
sh 'docker-compose run --rm barong bundle exec rake db:create'
|
||||
end
|
||||
|
||||
desc 'Load database dump'
|
||||
task :load => :create do
|
||||
sh %Q{cat data/mysql/peatio_production.sql | docker-compose run --rm db #{mysql_cli} peatio_production}
|
||||
sh %Q{cat data/mysql/barong_production.sql | docker-compose run --rm db #{mysql_cli} barong_production}
|
||||
sh 'docker-compose run --rm peatio bundle exec rake db:migrate'
|
||||
sh 'docker-compose run --rm barong bundle exec rake db:migrate'
|
||||
end
|
||||
|
||||
desc 'Drop all databases'
|
||||
task :drop do
|
||||
sh %q(docker-compose run --rm db /bin/sh -c "mysql -u root -h db -P 3306 -pchangeme -e 'DROP DATABASE peatio_production'")
|
||||
sh %q(docker-compose run --rm db /bin/sh -c "mysql -u root -h db -P 3306 -pchangeme -e 'DROP DATABASE barong_production'")
|
||||
sh %q(docker-compose run --rm db /bin/sh -c "mysql -u root -h db -P 3306 -pchangeme -e 'DROP DATABASE superset'")
|
||||
end
|
||||
|
||||
desc 'Database Console'
|
||||
task :console do
|
||||
sh "docker-compose run --rm db #{mysql_cli}"
|
||||
end
|
||||
end
|
||||
11
lib/tasks/docker.rake
Normal file
11
lib/tasks/docker.rake
Normal file
@@ -0,0 +1,11 @@
|
||||
namespace :docker do
|
||||
desc 'Stop all runnning docker contrainers'
|
||||
task :down do
|
||||
sh 'docker-compose down'
|
||||
end
|
||||
|
||||
desc 'Clean up all docker volumes'
|
||||
task :clean do
|
||||
sh 'docker volume prune -f'
|
||||
end
|
||||
end
|
||||
18
lib/tasks/payload.rake
Normal file
18
lib/tasks/payload.rake
Normal file
@@ -0,0 +1,18 @@
|
||||
require_relative '../opendax/payload'
|
||||
require 'faraday'
|
||||
|
||||
namespace :payload do
|
||||
desc 'Generate JWT'
|
||||
task :send, [:service, :image, :url] do |_, args|
|
||||
secret = ENV['WEBHOOK_JWT_SECRET']
|
||||
abort 'WEBHOOK_JWT_SECRET not set' if secret.to_s.empty?
|
||||
coder = Opendax::Payload.new(secret: secret)
|
||||
jwt = coder.generate!(service: args.service, image: args.image)
|
||||
url = "#{args.url}/deploy/#{jwt}"
|
||||
response = Faraday::Connection.new.get(url) do |request|
|
||||
request.options.timeout = 300
|
||||
end
|
||||
pp response.body
|
||||
fail unless response.status == 200
|
||||
end
|
||||
end
|
||||
17
lib/tasks/render.rake
Normal file
17
lib/tasks/render.rake
Normal file
@@ -0,0 +1,17 @@
|
||||
|
||||
require_relative '../opendax/renderer'
|
||||
|
||||
namespace :render do
|
||||
desc 'Render configuration and compose files and keys'
|
||||
task :config do
|
||||
if (overlay = Opendax::ConfigLoader.active_overlay)
|
||||
puts "Using config overlay: #{overlay}"
|
||||
else
|
||||
puts 'Using config/app.yml only (no overlay)'
|
||||
end
|
||||
|
||||
renderer = Opendax::Renderer.new
|
||||
renderer.render_keys
|
||||
renderer.render
|
||||
end
|
||||
end
|
||||
347
lib/tasks/service.rake
Normal file
347
lib/tasks/service.rake
Normal file
@@ -0,0 +1,347 @@
|
||||
namespace :service do
|
||||
ENV['APP_DOMAIN'] = @config['app']['domain']
|
||||
|
||||
@switch = Proc.new do |args, start, stop|
|
||||
case args.command
|
||||
when 'start'
|
||||
start.call
|
||||
when 'stop'
|
||||
stop.call
|
||||
when 'restart'
|
||||
stop.call
|
||||
start.call
|
||||
else
|
||||
puts "unknown command #{args.command}"
|
||||
end
|
||||
end
|
||||
|
||||
desc 'Run Traefik (reverse-proxy)'
|
||||
task :proxy, [:command] do |task, args|
|
||||
args.with_defaults(:command => 'start')
|
||||
|
||||
def start
|
||||
puts '----- Starting the proxy -----'
|
||||
File.new('config/acme.json', File::CREAT, 0600) unless File.exist? 'config/acme.json'
|
||||
sh 'docker-compose up -d proxy'
|
||||
end
|
||||
|
||||
def stop
|
||||
puts '----- Stopping the proxy -----'
|
||||
sh 'docker-compose rm -fs proxy'
|
||||
end
|
||||
|
||||
@switch.call(args, method(:start), method(:stop))
|
||||
end
|
||||
|
||||
desc 'Run backend (vault db redis rabbitmq)'
|
||||
task :backend, [:command] do |task, args|
|
||||
args.with_defaults(:command => 'start')
|
||||
|
||||
def start
|
||||
puts '----- Starting dependencies -----'
|
||||
sh 'docker-compose up -d vault db redis rabbitmq'
|
||||
sleep 7 # time for db to start, we can get connection refused without sleeping
|
||||
end
|
||||
|
||||
def stop
|
||||
puts '----- Stopping dependencies -----'
|
||||
sh 'docker-compose rm -fs vault db redis rabbitmq'
|
||||
end
|
||||
|
||||
|
||||
@switch.call(args, method(:start), method(:stop))
|
||||
end
|
||||
|
||||
desc 'Run influxdb'
|
||||
task :influxdb, [:command] do |task, args|
|
||||
args.with_defaults(:command => 'start')
|
||||
|
||||
def start
|
||||
puts '----- Starting influxdb -----'
|
||||
sh 'docker-compose up -d influxdb'
|
||||
puts 'Wait 5 second for influx'
|
||||
sleep(5)
|
||||
sh 'docker-compose exec influxdb bash -c "cat peatio.sql | influx"'
|
||||
end
|
||||
|
||||
def stop
|
||||
puts '----- Stopping influxdb -----'
|
||||
sh 'docker-compose rm -fs influxdb'
|
||||
end
|
||||
|
||||
@switch.call(args, method(:start), method(:stop))
|
||||
end
|
||||
|
||||
desc '[Optional] Run arke-maker'
|
||||
task :arke_maker, [:command] do |task, args|
|
||||
args.with_defaults(:command => 'start')
|
||||
|
||||
def start
|
||||
puts '----- Starting arke -----'
|
||||
sh 'docker-compose up -d arke-maker'
|
||||
end
|
||||
|
||||
def stop
|
||||
puts '----- Stopping arke -----'
|
||||
sh 'docker-compose rm -fs arke-maker'
|
||||
end
|
||||
|
||||
@switch.call(args, method(:start), method(:stop))
|
||||
end
|
||||
|
||||
desc '[Optional] Run daemons (rango, peatio daemons, barong sidekiq)'
|
||||
task :daemons, [:command] do |_task, args|
|
||||
@daemons = %w[rango blockchain cron_job upstream deposit deposit_coin_address withdraw_coin influx_writer barong_sidekiq]
|
||||
|
||||
if @config['finex']['enabled']
|
||||
@daemons |= %w[finex-engine finex-api]
|
||||
else
|
||||
@daemons |= %w[matching order_processor trade_executor]
|
||||
end
|
||||
|
||||
args.with_defaults(:command => 'start')
|
||||
|
||||
def start
|
||||
puts '----- Starting daemons -----'
|
||||
sh "docker-compose up -d #{@daemons.join(' ')}"
|
||||
end
|
||||
|
||||
def stop
|
||||
puts '----- Stopping daemons -----'
|
||||
sh "docker-compose rm -fs #{@daemons.join(' ')}"
|
||||
end
|
||||
|
||||
@switch.call(args, method(:start), method(:stop))
|
||||
end
|
||||
|
||||
desc '[Optional] Run cryptonodes'
|
||||
task :cryptonodes, [:command] do |task, args|
|
||||
args.with_defaults(:command => 'start')
|
||||
|
||||
def start
|
||||
puts '----- Starting cryptonodes -----'
|
||||
sh 'docker-compose up -d parity'
|
||||
if @config['bitcoind']['enabled']
|
||||
sh 'docker-compose up -d bitcoind'
|
||||
end
|
||||
end
|
||||
|
||||
def stop
|
||||
puts '----- Stopping cryptonodes -----'
|
||||
sh 'docker-compose rm -fs parity'
|
||||
if @config['bitcoind']['enabled']
|
||||
sh 'docker-compose rm -fs bitcoind'
|
||||
end
|
||||
end
|
||||
|
||||
@switch.call(args, method(:start), method(:stop))
|
||||
end
|
||||
|
||||
desc 'Run setup hooks for peatio, barong'
|
||||
task :setup, [:command] => ['vault:setup', 'vault:load_policies'] do |task, args|
|
||||
if args.command != 'stop'
|
||||
Rake::Task["render:config"].execute
|
||||
puts '----- Running hooks -----'
|
||||
sh 'docker-compose run --rm peatio bash -c "./bin/link_config && bundle exec rake db:create db:migrate"'
|
||||
sh 'docker-compose run --rm peatio bash -c "./bin/link_config && bundle exec rake db:seed"'
|
||||
sh 'docker-compose run --rm barong bash -c "./bin/init_config && bundle exec rake db:create db:migrate"'
|
||||
sh 'docker-compose run --rm barong bash -c "./bin/link_config && bundle exec rake db:seed"'
|
||||
if ENV.fetch('FIBITEX_SEED_TEST_USERS', 'true') != 'false'
|
||||
puts '----- Seeding test users and balances -----'
|
||||
sh 'docker-compose run --rm barong bash -c "bundle exec rails runner ./lib/seed_test_users.rb"'
|
||||
sh 'docker-compose run --rm peatio bash -c "ruby ./bin/link_config && bundle exec rails runner ./lib/seed_test_balances.rb"'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
desc 'Run mikro app (barong, peatio)'
|
||||
task :app, [:command] => [:backend, :setup] do |task, args|
|
||||
args.with_defaults(:command => 'start')
|
||||
|
||||
def start
|
||||
puts '----- Starting app -----'
|
||||
sh 'docker-compose up -d peatio barong gateway'
|
||||
end
|
||||
|
||||
def stop
|
||||
puts '----- Stopping app -----'
|
||||
sh 'docker-compose rm -fs peatio barong gateway'
|
||||
end
|
||||
|
||||
@switch.call(args, method(:start), method(:stop))
|
||||
end
|
||||
|
||||
desc 'Run the frontend application'
|
||||
task :frontend, [:command] do |task, args|
|
||||
args.with_defaults(:command => 'start')
|
||||
|
||||
def start
|
||||
puts '----- Starting the frontend -----'
|
||||
sh 'docker-compose up -d frontend'
|
||||
end
|
||||
|
||||
def stop
|
||||
puts '----- Stopping the frontend -----'
|
||||
sh 'docker-compose rm -fs frontend'
|
||||
end
|
||||
|
||||
@switch.call(args, method(:start), method(:stop))
|
||||
end
|
||||
|
||||
desc 'Run the tower application'
|
||||
task :AdminTower, [:command] do |task, args|
|
||||
args.with_defaults(:command => 'start')
|
||||
|
||||
def start
|
||||
puts '----- Starting the AdminTower -----'
|
||||
sh 'docker-compose up -d AdminTower'
|
||||
end
|
||||
|
||||
def stop
|
||||
puts '----- Stopping the AdminTower -----'
|
||||
sh 'docker-compose rm -fs AdminTower'
|
||||
end
|
||||
|
||||
@switch.call(args, method(:start), method(:stop))
|
||||
end
|
||||
|
||||
desc 'Run Mailer'
|
||||
task :mailer, [:command] do |task, args|
|
||||
args.with_defaults(:command => 'start')
|
||||
|
||||
def start
|
||||
puts '----- Starting Mailer -----'
|
||||
sh 'docker-compose up -d mailer'
|
||||
end
|
||||
|
||||
def stop
|
||||
puts '----- Stopping Mailer -----'
|
||||
sh 'docker-compose rm -fs mailer'
|
||||
end
|
||||
|
||||
@switch.call(args, method(:start), method(:stop))
|
||||
end
|
||||
|
||||
desc '[Optional] Run monitoring'
|
||||
task :monitoring, [:command] do |task, args|
|
||||
args.with_defaults(:command => 'start')
|
||||
|
||||
def start
|
||||
puts '----- Starting monitoring -----'
|
||||
sh 'docker-compose up -d prometheus'
|
||||
sh 'docker-compose up -d grafana'
|
||||
sh 'docker-compose up -d alertmanager'
|
||||
sh 'docker-compose up -d loki'
|
||||
sh 'docker-compose up -d promtail'
|
||||
sh 'docker-compose up -d scope'
|
||||
sh 'docker-compose up -d node-exporter'
|
||||
sh 'docker-compose up -d cadvisor'
|
||||
end
|
||||
|
||||
def stop
|
||||
puts '----- Stopping monitoring -----'
|
||||
sh 'docker-compose rm -fs prometheus'
|
||||
sh 'docker-compose rm -fs grafana'
|
||||
sh 'docker-compose rm -fs alertmanager'
|
||||
sh 'docker-compose rm -fs loki'
|
||||
sh 'docker-compose rm -fs promtail'
|
||||
sh 'docker-compose rm -fs scope'
|
||||
sh 'docker-compose rm -fs node-exporter'
|
||||
sh 'docker-compose rm -fs cadvisor'
|
||||
end
|
||||
|
||||
@switch.call(args, method(:start), method(:stop))
|
||||
end
|
||||
|
||||
desc '[Optional] Run superset'
|
||||
task :superset, [:command] do |task, args|
|
||||
args.with_defaults(:command => 'start')
|
||||
|
||||
def start
|
||||
conf = @utils['superset']
|
||||
init_params = [
|
||||
'--app', 'superset',
|
||||
'--firstname', 'Admin',
|
||||
'--lastname', 'Superset',
|
||||
'--username', conf['username'],
|
||||
'--email', conf['email'],
|
||||
'--password', conf['password']
|
||||
].join(' ')
|
||||
|
||||
puts '----- Initializing Superset -----'
|
||||
sh [
|
||||
'docker-compose run --rm superset',
|
||||
'sh -c "',
|
||||
"fabmanager create-admin #{init_params}",
|
||||
'&& superset db upgrade',
|
||||
# '&& superset load_examples',
|
||||
'&& superset init"'
|
||||
].join(' ')
|
||||
|
||||
puts '----- Starting Superset -----'
|
||||
sh 'docker-compose up -d superset'
|
||||
end
|
||||
|
||||
def stop
|
||||
puts '----- Stopping Superset -----'
|
||||
sh 'docker-compose rm -fs superset'
|
||||
end
|
||||
|
||||
@switch.call(args, method(:start), method(:stop))
|
||||
end
|
||||
|
||||
desc '[Optional] Run Blog'
|
||||
task :blog, [:command] do |task, args|
|
||||
args.with_defaults(:command => 'start')
|
||||
|
||||
def start
|
||||
puts '----- Starting Blog_db -----'
|
||||
sh 'docker-compose up -d blog_db'
|
||||
puts '----- Starting Blog -----'
|
||||
sh 'docker-compose up -d blog'
|
||||
end
|
||||
|
||||
def stop
|
||||
puts '----- Stopping Blog -----'
|
||||
sh 'docker-compose rm -fs blog'
|
||||
puts '----- Stopping Blog_db -----'
|
||||
sh 'docker-compose rm -fs blog_db'
|
||||
end
|
||||
|
||||
@switch.call(args, method(:start), method(:stop))
|
||||
end
|
||||
|
||||
|
||||
desc 'Set up and start all services with dependencies (does not run optional ones)'
|
||||
task :all, [:command] => ['versions:update', 'render:config'] do |task, args|
|
||||
args.with_defaults(:command => 'start')
|
||||
|
||||
def start
|
||||
Rake::Task["service:proxy"].invoke('start')
|
||||
Rake::Task["service:backend"].invoke('start')
|
||||
Rake::Task["service:influxdb"].invoke('start')
|
||||
puts 'Wait 5 second for backend'
|
||||
sleep(5)
|
||||
Rake::Task["service:setup"].invoke('start')
|
||||
Rake::Task["service:app"].invoke('start')
|
||||
Rake::Task["service:frontend"].invoke('start')
|
||||
Rake::Task["service:AdminTower"].invoke('start')
|
||||
Rake::Task["service:mailer"].invoke('start')
|
||||
Rake::Task["service:daemons"].invoke('start')
|
||||
end
|
||||
|
||||
def stop
|
||||
Rake::Task["service:proxy"].invoke('stop')
|
||||
Rake::Task["service:backend"].invoke('stop')
|
||||
Rake::Task["service:influxdb"].invoke('stop')
|
||||
Rake::Task["service:setup"].invoke('stop')
|
||||
Rake::Task["service:app"].invoke('stop')
|
||||
Rake::Task["service:frontend"].invoke('stop')
|
||||
Rake::Task["service:AdminTower"].invoke('stop')
|
||||
Rake::Task["service:mailer"].invoke('stop')
|
||||
Rake::Task["service:daemons"].invoke('stop')
|
||||
end
|
||||
|
||||
@switch.call(args, method(:start), method(:stop))
|
||||
end
|
||||
end
|
||||
16
lib/tasks/terraform.rake
Normal file
16
lib/tasks/terraform.rake
Normal file
@@ -0,0 +1,16 @@
|
||||
namespace :terraform do
|
||||
desc 'Initialize the Terraform configuration'
|
||||
task :init do
|
||||
Dir.chdir('terraform') { sh 'terraform init' }
|
||||
end
|
||||
|
||||
desc 'Apply the Terraform configuration'
|
||||
task :apply => ['render:config', :init] do
|
||||
Dir.chdir('terraform') { sh 'terraform apply' }
|
||||
end
|
||||
|
||||
desc 'Destroy the Terraform infrastructure'
|
||||
task :destroy do
|
||||
Dir.chdir('terraform') { sh 'terraform destroy' }
|
||||
end
|
||||
end
|
||||
15
lib/tasks/test_seed.rake
Normal file
15
lib/tasks/test_seed.rake
Normal file
@@ -0,0 +1,15 @@
|
||||
namespace :test do
|
||||
desc 'Seed test users (Barong + Peatio balances) for local/test environments'
|
||||
task seed: :environment do
|
||||
if ENV.fetch('FIBITEX_SEED_TEST_USERS', 'true') == 'false'
|
||||
puts 'SKIP test:seed (FIBITEX_SEED_TEST_USERS=false)'
|
||||
next
|
||||
end
|
||||
|
||||
puts '----- Seeding test users (Barong) -----'
|
||||
sh 'docker-compose run --rm barong bash -c "bundle exec rails runner ./lib/seed_test_users.rb"'
|
||||
|
||||
puts '----- Seeding test balances (Peatio) -----'
|
||||
sh 'docker-compose run --rm peatio bash -c "ruby ./bin/link_config && bundle exec rails runner ./lib/seed_test_balances.rb"'
|
||||
end
|
||||
end
|
||||
12
lib/tasks/toolbox.rake
Normal file
12
lib/tasks/toolbox.rake
Normal file
@@ -0,0 +1,12 @@
|
||||
namespace :toolbox do
|
||||
desc 'Run the toolbox'
|
||||
task :run do
|
||||
run_cmd = %w[docker-compose run --rm toolbox run]
|
||||
|
||||
YAML.safe_load(File.read('config/toolbox.yaml'))
|
||||
.transform_keys { |k| '--' << k }
|
||||
.each_pair { |k, v| run_cmd << k << v.to_s }
|
||||
|
||||
sh *run_cmd
|
||||
end
|
||||
end
|
||||
25
lib/tasks/vault.rake
Normal file
25
lib/tasks/vault.rake
Normal file
@@ -0,0 +1,25 @@
|
||||
|
||||
require_relative '../opendax/vault'
|
||||
|
||||
namespace :vault do
|
||||
desc 'Initialize, unseal and set secrets for Vault'
|
||||
task :setup do
|
||||
vault = Opendax::Vault.new
|
||||
vault_root_token = vault.setup
|
||||
unless vault_root_token.nil?
|
||||
@config["vault"]["root_token"] = vault_root_token
|
||||
File.open(CONFIG_PATH, 'w') { |f| YAML.dump(@config, f) }
|
||||
end
|
||||
end
|
||||
|
||||
task :load_policies do
|
||||
vault = Opendax::Vault.new
|
||||
vault_tokens = vault.load_policies(@config["app"]["name"], @config["vault"]["root_token"])
|
||||
unless vault_tokens.empty?
|
||||
vault_tokens.each do |k, v|
|
||||
@config["vault"][k] = v
|
||||
File.open(CONFIG_PATH, 'w') { |f| YAML.dump(@config, f) }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
11
lib/tasks/vendor.rake
Normal file
11
lib/tasks/vendor.rake
Normal file
@@ -0,0 +1,11 @@
|
||||
namespace :vendor do
|
||||
desc 'Clone the frontend apps repos into vendor/'
|
||||
task :clone do
|
||||
puts '----- Clone the frontend apps repos -----'
|
||||
puts
|
||||
@config['vendor'].each do |name, repo_url|
|
||||
sh "git clone #{repo_url} vendor/#{name}"
|
||||
puts
|
||||
end
|
||||
end
|
||||
end
|
||||
33
lib/tasks/versions.rake
Normal file
33
lib/tasks/versions.rake
Normal file
@@ -0,0 +1,33 @@
|
||||
require 'faraday'
|
||||
|
||||
namespace :versions do
|
||||
desc 'Fetch global image versions and update config/app.yaml'
|
||||
task :update do
|
||||
unless @config['updateVersions']
|
||||
puts "To enable version updates, set updateVersions to true"
|
||||
next
|
||||
end
|
||||
|
||||
puts "Fetching latest global versions"
|
||||
response = Faraday.get 'https://raw.githubusercontent.com/openware/versions/master/opendax/2-6/versions.yaml'
|
||||
|
||||
if response.status >= 400 || response.status >= 500
|
||||
raise "Error fetching global versions, got #{response.body}"
|
||||
end
|
||||
|
||||
versions = YAML.load(response.body)
|
||||
|
||||
versions.each { |k, v| update_image_tag(k, v['image']['tag']) if @config['images'].key? k }
|
||||
|
||||
File.write(CONFIG_PATH, YAML.dump(@config))
|
||||
|
||||
puts 'Version update complete!'
|
||||
end
|
||||
end
|
||||
|
||||
def update_image_tag(component, tag)
|
||||
image = @config['images'][component].split(':')
|
||||
image[-1] = tag
|
||||
|
||||
@config['images'][component] = image.join(':')
|
||||
end
|
||||
20
lib/tasks/wallets.rake
Normal file
20
lib/tasks/wallets.rake
Normal file
@@ -0,0 +1,20 @@
|
||||
require 'faraday'
|
||||
require 'json'
|
||||
require 'yaml'
|
||||
|
||||
namespace :wallet do
|
||||
desc 'Generate ethereum wallet from a cryptonode'
|
||||
task :create, [:kind,:url,:secret] do |_, args|
|
||||
response = Faraday::Connection.new.post(args.url) do |request|
|
||||
request.headers["Content-Type"] = "application/json"
|
||||
request.body = "{\"jsonrpc\":\"2.0\",\"method\":\"personal_newAccount\",\"params\":[\"#{args.secret}\"],\"id\":1}"
|
||||
request.options.timeout = 300
|
||||
end
|
||||
address = JSON.parse(response.body)['result']
|
||||
puts "----- Generating a new #{args.kind} wallet -----", "Address: " + address
|
||||
|
||||
@config['wallets']['eth'].find { |w| w['kind'] == args.kind }.update('address' => address, 'secret' => args.secret)
|
||||
|
||||
File.open(CONFIG_PATH, 'w') {|f| f.write @config.to_yaml }
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user