first commit
This commit is contained in:
commit
e2b0cf9794
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
.bundle
|
||||||
|
.cache
|
||||||
|
.local
|
||||||
|
.ruby-lsp
|
||||||
|
.ash_history
|
48
Dockerfile
Normal file
48
Dockerfile
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
FROM ruby:alpine as base
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
RUN <<EOT
|
||||||
|
gem update --system --no-document
|
||||||
|
gem install -N bundler
|
||||||
|
apk update
|
||||||
|
apk upgrade --no-cache
|
||||||
|
EOT
|
||||||
|
|
||||||
|
|
||||||
|
FROM base AS build
|
||||||
|
|
||||||
|
RUN <<EOT
|
||||||
|
apk add gcc musl-dev ruby-dev make
|
||||||
|
EOT
|
||||||
|
|
||||||
|
COPY Gemfile* .
|
||||||
|
|
||||||
|
RUN <<EOT
|
||||||
|
bundle config set --local without development
|
||||||
|
bundle install
|
||||||
|
EOT
|
||||||
|
|
||||||
|
FROM build as dev
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
RUN <<EOT
|
||||||
|
bundle install
|
||||||
|
EOT
|
||||||
|
|
||||||
|
CMD [ "sleep", "infinity" ]
|
||||||
|
|
||||||
|
FROM base
|
||||||
|
|
||||||
|
# RUN useradd ruby --home /app --shell /bin/sh
|
||||||
|
RUN adduser ruby -h /app -D
|
||||||
|
USER ruby:ruby
|
||||||
|
|
||||||
|
COPY --from=build /usr/local/bundle /usr/local/bundle
|
||||||
|
COPY --from=build --chown=ruby:ruby /app /app
|
||||||
|
|
||||||
|
COPY --chown=ruby:ruby . .
|
||||||
|
|
||||||
|
EXPOSE 4567
|
||||||
|
CMD [ "bundle", "exec", "rackup", "--host", "0.0.0.0", "--port", "4567" ]
|
20
Gemfile
Normal file
20
Gemfile
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
source "https://rubygems.org"
|
||||||
|
|
||||||
|
gem "sinatra"
|
||||||
|
gem 'sinatra-contrib'
|
||||||
|
gem "puma"
|
||||||
|
gem "rackup"
|
||||||
|
|
||||||
|
gem "anyflake"
|
||||||
|
gem "ksuid"
|
||||||
|
gem "nanoid"
|
||||||
|
gem "ulid"
|
||||||
|
gem "uuid7"
|
||||||
|
|
||||||
|
gem 'jwt'
|
||||||
|
|
||||||
|
group :development do
|
||||||
|
gem "ruby-lsp"
|
||||||
|
gem "rubocop"
|
||||||
|
gem "rbs"
|
||||||
|
end
|
103
Gemfile.lock
Normal file
103
Gemfile.lock
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
GEM
|
||||||
|
remote: https://rubygems.org/
|
||||||
|
specs:
|
||||||
|
anyflake (0.0.1)
|
||||||
|
ast (2.4.2)
|
||||||
|
base64 (0.2.0)
|
||||||
|
json (2.7.2)
|
||||||
|
jwt (2.8.2)
|
||||||
|
base64
|
||||||
|
ksuid (1.0.0)
|
||||||
|
language_server-protocol (3.17.0.3)
|
||||||
|
logger (1.6.0)
|
||||||
|
multi_json (1.15.0)
|
||||||
|
mustermann (3.0.0)
|
||||||
|
ruby2_keywords (~> 0.0.1)
|
||||||
|
nanoid (2.0.0)
|
||||||
|
nio4r (2.7.3)
|
||||||
|
parallel (1.25.1)
|
||||||
|
parser (3.3.3.0)
|
||||||
|
ast (~> 2.4.1)
|
||||||
|
racc
|
||||||
|
prism (0.30.0)
|
||||||
|
puma (6.4.2)
|
||||||
|
nio4r (~> 2.0)
|
||||||
|
racc (1.8.0)
|
||||||
|
rack (3.1.3)
|
||||||
|
rack-protection (4.0.0)
|
||||||
|
base64 (>= 0.1.0)
|
||||||
|
rack (>= 3.0.0, < 4)
|
||||||
|
rack-session (2.0.0)
|
||||||
|
rack (>= 3.0.0)
|
||||||
|
rackup (2.1.0)
|
||||||
|
rack (>= 3)
|
||||||
|
webrick (~> 1.8)
|
||||||
|
rainbow (3.1.1)
|
||||||
|
rbs (3.5.1)
|
||||||
|
logger
|
||||||
|
regexp_parser (2.9.2)
|
||||||
|
rexml (3.3.0)
|
||||||
|
strscan
|
||||||
|
rubocop (1.64.1)
|
||||||
|
json (~> 2.3)
|
||||||
|
language_server-protocol (>= 3.17.0)
|
||||||
|
parallel (~> 1.10)
|
||||||
|
parser (>= 3.3.0.2)
|
||||||
|
rainbow (>= 2.2.2, < 4.0)
|
||||||
|
regexp_parser (>= 1.8, < 3.0)
|
||||||
|
rexml (>= 3.2.5, < 4.0)
|
||||||
|
rubocop-ast (>= 1.31.1, < 2.0)
|
||||||
|
ruby-progressbar (~> 1.7)
|
||||||
|
unicode-display_width (>= 2.4.0, < 3.0)
|
||||||
|
rubocop-ast (1.31.3)
|
||||||
|
parser (>= 3.3.1.0)
|
||||||
|
ruby-lsp (0.17.3)
|
||||||
|
language_server-protocol (~> 3.17.0)
|
||||||
|
prism (>= 0.29.0, < 0.31)
|
||||||
|
rbs (>= 3, < 4)
|
||||||
|
sorbet-runtime (>= 0.5.10782)
|
||||||
|
ruby-progressbar (1.13.0)
|
||||||
|
ruby2_keywords (0.0.5)
|
||||||
|
sinatra (4.0.0)
|
||||||
|
mustermann (~> 3.0)
|
||||||
|
rack (>= 3.0.0, < 4)
|
||||||
|
rack-protection (= 4.0.0)
|
||||||
|
rack-session (>= 2.0.0, < 3)
|
||||||
|
tilt (~> 2.0)
|
||||||
|
sinatra-contrib (4.0.0)
|
||||||
|
multi_json (>= 0.0.2)
|
||||||
|
mustermann (~> 3.0)
|
||||||
|
rack-protection (= 4.0.0)
|
||||||
|
sinatra (= 4.0.0)
|
||||||
|
tilt (~> 2.0)
|
||||||
|
sorbet-runtime (0.5.11435)
|
||||||
|
strscan (3.1.0)
|
||||||
|
tilt (2.3.0)
|
||||||
|
ulid (1.4.0)
|
||||||
|
unicode-display_width (2.5.0)
|
||||||
|
uuid7 (0.2.0)
|
||||||
|
zeitwerk (~> 2.4)
|
||||||
|
webrick (1.8.1)
|
||||||
|
zeitwerk (2.6.15)
|
||||||
|
|
||||||
|
PLATFORMS
|
||||||
|
ruby
|
||||||
|
x86_64-linux-musl
|
||||||
|
|
||||||
|
DEPENDENCIES
|
||||||
|
anyflake
|
||||||
|
jwt
|
||||||
|
ksuid
|
||||||
|
nanoid
|
||||||
|
puma
|
||||||
|
rackup
|
||||||
|
rbs
|
||||||
|
rubocop
|
||||||
|
ruby-lsp
|
||||||
|
sinatra
|
||||||
|
sinatra-contrib
|
||||||
|
ulid
|
||||||
|
uuid7
|
||||||
|
|
||||||
|
BUNDLED WITH
|
||||||
|
2.5.13
|
331
app.rb
Normal file
331
app.rb
Normal file
@ -0,0 +1,331 @@
|
|||||||
|
require "bundler/setup"
|
||||||
|
require "sinatra"
|
||||||
|
require "sinatra/json"
|
||||||
|
require "sinatra/cookies"
|
||||||
|
require "time"
|
||||||
|
require "fileutils"
|
||||||
|
require "json"
|
||||||
|
|
||||||
|
require "securerandom"
|
||||||
|
require "random/formatter"
|
||||||
|
require "ulid"
|
||||||
|
require "anyflake"
|
||||||
|
|
||||||
|
require "jwt"
|
||||||
|
|
||||||
|
SESSION_SECRET_HEX_LENGTH = 64
|
||||||
|
|
||||||
|
set :session_secret, ENV.fetch('SESSION_SECRET') { SecureRandom.hex(SESSION_SECRET_HEX_LENGTH) }
|
||||||
|
|
||||||
|
CLK_TCK = 100
|
||||||
|
PID_FILE_PATH = "/run/pid".freeze
|
||||||
|
PROC_UPTIME_PATH = "/proc/uptime".freeze
|
||||||
|
|
||||||
|
SECONDS_PER_YEAR = 31_556_952
|
||||||
|
SECONDS_PER_MINUTE = 60
|
||||||
|
SECONDS_PER_HOUR = 3_600
|
||||||
|
SECONDS_PER_DAY = 86_400
|
||||||
|
SECONDS_PER_WEEK = 604_800
|
||||||
|
SECONDS_PER_MONTH = SECONDS_PER_YEAR / 12
|
||||||
|
|
||||||
|
DURATION_PARTS = [
|
||||||
|
[SECONDS_PER_YEAR, "year", "y"],
|
||||||
|
[SECONDS_PER_MONTH, "month", "m"],
|
||||||
|
[SECONDS_PER_WEEK, "week", "w"],
|
||||||
|
[SECONDS_PER_DAY, "day", "d"],
|
||||||
|
[SECONDS_PER_HOUR, "hour", "h"],
|
||||||
|
[SECONDS_PER_MINUTE, "minute", "m"],
|
||||||
|
[1, "second", "s"],
|
||||||
|
].freeze
|
||||||
|
|
||||||
|
JWT_SECRET = SecureRandom.bytes(64).freeze
|
||||||
|
|
||||||
|
module Sinatra
|
||||||
|
module RequestHeadersHelper
|
||||||
|
def req_headers
|
||||||
|
hash = request.env.select { |k, _| k.start_with? "HTTP_" }
|
||||||
|
.collect { |k, v| [k.gsub(/^HTTP_/, "").gsub(/_/, "-").downcase, v] }
|
||||||
|
.sort
|
||||||
|
h = Rack::Headers.new
|
||||||
|
h.merge hash
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
helpers RequestHeadersHelper
|
||||||
|
end
|
||||||
|
|
||||||
|
module State
|
||||||
|
def enable
|
||||||
|
FileUtils.touch(@file)
|
||||||
|
end
|
||||||
|
|
||||||
|
def disable
|
||||||
|
File.delete @file if File.file? @file
|
||||||
|
end
|
||||||
|
|
||||||
|
def enabled?
|
||||||
|
File.file? @file
|
||||||
|
end
|
||||||
|
|
||||||
|
def toggle
|
||||||
|
return enable unless enabled?
|
||||||
|
|
||||||
|
disable
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
module UpDown
|
||||||
|
def up
|
||||||
|
enable
|
||||||
|
end
|
||||||
|
|
||||||
|
def down
|
||||||
|
disable
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_s
|
||||||
|
enabled? ? "up" : "down"
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_json(*_args)
|
||||||
|
return unless enabled?
|
||||||
|
|
||||||
|
JSON.generate({ "status" => "ok" })
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class TickTock
|
||||||
|
def initialize
|
||||||
|
@pid = master_pid
|
||||||
|
@procfs_f = format "/proc/%s/stat", @pid
|
||||||
|
puts @pid
|
||||||
|
end
|
||||||
|
|
||||||
|
def uptime
|
||||||
|
x = ""
|
||||||
|
File.open @procfs_f do |f|
|
||||||
|
f.each_line do |l|
|
||||||
|
x = l.strip.split
|
||||||
|
end
|
||||||
|
end
|
||||||
|
system_uptime - (Integer(x[21]) / CLK_TCK)
|
||||||
|
end
|
||||||
|
|
||||||
|
def started_at
|
||||||
|
Time.now - uptime
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class Health
|
||||||
|
include Singleton
|
||||||
|
include State
|
||||||
|
include UpDown
|
||||||
|
|
||||||
|
def initialize
|
||||||
|
@file = "./healthy"
|
||||||
|
end
|
||||||
|
|
||||||
|
def healthy?
|
||||||
|
enabled?
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class Ready
|
||||||
|
include Singleton
|
||||||
|
include State
|
||||||
|
include UpDown
|
||||||
|
|
||||||
|
def initialize
|
||||||
|
@file = "./ready"
|
||||||
|
end
|
||||||
|
|
||||||
|
def ready?
|
||||||
|
enabled?
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class Sleep
|
||||||
|
include Singleton
|
||||||
|
include State
|
||||||
|
|
||||||
|
def initialize
|
||||||
|
@file = "./sleep"
|
||||||
|
end
|
||||||
|
|
||||||
|
def asleep?
|
||||||
|
enabled?
|
||||||
|
end
|
||||||
|
|
||||||
|
def wake
|
||||||
|
disable
|
||||||
|
end
|
||||||
|
|
||||||
|
def sleep
|
||||||
|
enable
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def master_pid
|
||||||
|
pid_s = File.read PID_FILE_PATH
|
||||||
|
Integer pid_s.strip
|
||||||
|
end
|
||||||
|
|
||||||
|
def system_uptime
|
||||||
|
uptime_s = File.read PROC_UPTIME_PATH
|
||||||
|
Float uptime_s.strip.split[0]
|
||||||
|
end
|
||||||
|
|
||||||
|
##
|
||||||
|
# Convert to human friendly time interval
|
||||||
|
#
|
||||||
|
# @param [Integer] time in seconds
|
||||||
|
def human_time(seconds, delim = " ")
|
||||||
|
s = []
|
||||||
|
DURATION_PARTS.each do |divisor, unit, abbrev|
|
||||||
|
q = seconds / divisor
|
||||||
|
r = seconds % divisor
|
||||||
|
s.push "#{q}#{abbrev}" if q.positive?
|
||||||
|
seconds = r
|
||||||
|
end
|
||||||
|
|
||||||
|
s.join delim
|
||||||
|
end
|
||||||
|
|
||||||
|
Health.instance.up
|
||||||
|
Ready.instance.up
|
||||||
|
Sleep.instance.wake
|
||||||
|
|
||||||
|
enable :sessions
|
||||||
|
|
||||||
|
configure do
|
||||||
|
mime_type :json, "application/json"
|
||||||
|
end
|
||||||
|
|
||||||
|
before do
|
||||||
|
# content_type 'text/plain'
|
||||||
|
sleep(1) while Sleep.instance.asleep? unless request.path_info == "/livez/sleep"
|
||||||
|
end
|
||||||
|
|
||||||
|
get "/" do
|
||||||
|
"hello there!\n"
|
||||||
|
end
|
||||||
|
|
||||||
|
get "/env" do
|
||||||
|
stream do |out|
|
||||||
|
ENV.sort.each do |k, v|
|
||||||
|
out << "#{k}=#{v}\n"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
get "/headers", provides: "json" do
|
||||||
|
h = req_headers
|
||||||
|
return JSON.pretty_generate h if params.key? "pretty"
|
||||||
|
|
||||||
|
JSON.generate h
|
||||||
|
end
|
||||||
|
|
||||||
|
get "/headers" do
|
||||||
|
stream do |out|
|
||||||
|
req_headers.each do |k, v|
|
||||||
|
out << "#{k}: #{v.inspect}\n"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
get "/livez" do
|
||||||
|
error 503 unless Health.instance.healthy?
|
||||||
|
|
||||||
|
return Health.instance.to_json if request.env["HTTP_ACCEPT"] == "application/json"
|
||||||
|
|
||||||
|
Health.instance.to_s
|
||||||
|
end
|
||||||
|
|
||||||
|
get "/livez/uptime" do
|
||||||
|
tt = TickTock.new
|
||||||
|
x = { started_at: tt.started_at, seconds: tt.uptime.to_i, human: human_time(tt.uptime.to_i) }
|
||||||
|
json x
|
||||||
|
end
|
||||||
|
|
||||||
|
post "/livez/toggle" do
|
||||||
|
Health.instance.toggle
|
||||||
|
"ok\n"
|
||||||
|
end
|
||||||
|
|
||||||
|
post "/livez/sleep" do
|
||||||
|
Sleep.instance.toggle
|
||||||
|
"ok\n"
|
||||||
|
end
|
||||||
|
|
||||||
|
get "/uuid" do
|
||||||
|
n = params.fetch(:n, 1).to_i
|
||||||
|
stream do |out|
|
||||||
|
n.times do |_|
|
||||||
|
out << format("%s\n", SecureRandom.uuid)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
get "/ulid" do
|
||||||
|
n = params.fetch(:n, 1).to_i
|
||||||
|
stream do |out|
|
||||||
|
n.times do |_|
|
||||||
|
out << format("%s\n", ULID.generate)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
get "/snowflake" do
|
||||||
|
n = params.fetch(:n, 1).to_i
|
||||||
|
epoch = Time.new(2016, 7, 1, 0, 0, 0).strftime("%s%L").to_i
|
||||||
|
node_id = 1
|
||||||
|
af = AnyFlake.new(epoch, node_id)
|
||||||
|
stream do |out|
|
||||||
|
n.times do |_|
|
||||||
|
out << format("%s\n", af.next_id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
post "/quit" do
|
||||||
|
Process.kill("TERM", master_pid)
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
|
||||||
|
post "/halt" do
|
||||||
|
Process.kill("QUIT", master_pid)
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
|
||||||
|
get "/pid" do
|
||||||
|
JSON.generate({ puma: master_pid, pid: Process.pid })
|
||||||
|
end
|
||||||
|
|
||||||
|
get "/token" do
|
||||||
|
exp = Time.now.to_i + SECONDS_PER_MINUTE * 2
|
||||||
|
payload = { name: "anonymous", exp: exp, jti: Random.uuid }
|
||||||
|
expires_at = Time.at(exp).to_datetime
|
||||||
|
token = JWT.encode payload, JWT_SECRET, "HS256"
|
||||||
|
token
|
||||||
|
x = { :token => token, :expires_at => expires_at }
|
||||||
|
json x
|
||||||
|
end
|
||||||
|
|
||||||
|
get "/token/validate" do
|
||||||
|
token = req_headers["authorization"].split[1]
|
||||||
|
payload = JWT.decode token, JWT_SECRET, true, algorithm: "HS256"
|
||||||
|
json payload
|
||||||
|
end
|
||||||
|
|
||||||
|
post "/session" do
|
||||||
|
session.merge! params
|
||||||
|
json session.to_hash
|
||||||
|
end
|
||||||
|
|
||||||
|
get "/session" do
|
||||||
|
json session.to_hash
|
||||||
|
end
|
||||||
|
|
||||||
|
get "/cookies" do
|
||||||
|
json response.headers
|
||||||
|
end
|
6
config.ru
Normal file
6
config.ru
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
require 'bundler/setup'
|
||||||
|
require 'sinatra'
|
||||||
|
|
||||||
|
require './app'
|
||||||
|
|
||||||
|
run Sinatra::Application
|
3
config/puma.rb
Normal file
3
config/puma.rb
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# workers 3
|
||||||
|
pidfile '/run/pid'
|
||||||
|
preload_app!
|
15
docker-compose.yml
Normal file
15
docker-compose.yml
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
services:
|
||||||
|
web:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
target: dev
|
||||||
|
ports:
|
||||||
|
- "4567:4567"
|
||||||
|
volumes:
|
||||||
|
- .:/app
|
||||||
|
environment:
|
||||||
|
{}
|
||||||
|
# WEB_CONCURRENCY: 3
|
||||||
|
command:
|
||||||
|
- sleep
|
||||||
|
- infinity
|
63
sig/app.rbs
Normal file
63
sig/app.rbs
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
module State
|
||||||
|
def enable: () -> untyped
|
||||||
|
|
||||||
|
def disable: () -> (untyped | nil)
|
||||||
|
|
||||||
|
def enabled?: () -> bool
|
||||||
|
|
||||||
|
def toggle: () -> untyped
|
||||||
|
end
|
||||||
|
|
||||||
|
module UpDown
|
||||||
|
def up: () -> untyped
|
||||||
|
|
||||||
|
def down: () -> untyped
|
||||||
|
|
||||||
|
def to_s: () -> ("up" | "down")
|
||||||
|
|
||||||
|
def to_json: (*untyped _args) -> (nil | untyped)
|
||||||
|
end
|
||||||
|
|
||||||
|
class Health
|
||||||
|
@file: untyped
|
||||||
|
|
||||||
|
include Singleton
|
||||||
|
|
||||||
|
include State
|
||||||
|
|
||||||
|
include UpDown
|
||||||
|
|
||||||
|
def initialize: () -> void
|
||||||
|
|
||||||
|
def healthy?: () -> bool
|
||||||
|
end
|
||||||
|
|
||||||
|
class Ready
|
||||||
|
@file: untyped
|
||||||
|
|
||||||
|
include Singleton
|
||||||
|
|
||||||
|
include State
|
||||||
|
|
||||||
|
include UpDown
|
||||||
|
|
||||||
|
def initialize: () -> void
|
||||||
|
|
||||||
|
def ready?: () -> bool
|
||||||
|
end
|
||||||
|
|
||||||
|
class Sleep
|
||||||
|
@file: untyped
|
||||||
|
|
||||||
|
include Singleton
|
||||||
|
|
||||||
|
include State
|
||||||
|
|
||||||
|
def initialize: () -> void
|
||||||
|
|
||||||
|
def asleep?: () -> bool
|
||||||
|
|
||||||
|
def wake: () -> untyped
|
||||||
|
|
||||||
|
def sleep: () -> untyped
|
||||||
|
end
|
Loading…
Reference in New Issue
Block a user