Skip to content

Instantly share code, notes, and snippets.

@jbgutierrez jbgutierrez/.htdigest
Last active Oct 29, 2015

Embed
What would you like to do?
Invoke shell scripts via web
user:realm:fb6cb9e166c6c764ff2bdea12175a8aa
production:
sessions:
default:
hosts:
- localhost:27017
database: sinatra-shell
# A sample Gemfile
source "https://rubygems.org"
gem "thin", '<2.0.0'
gem "sinatra"
gem "sinatra-contrib"
gem "sinatra-websocket"
gem "haml"
gem "activesupport"
gem "mongoid"
GEM
remote: https://rubygems.org/
specs:
activemodel (4.2.4)
activesupport (= 4.2.4)
builder (~> 3.1)
activesupport (4.2.4)
i18n (~> 0.7)
json (~> 1.7, >= 1.7.7)
minitest (~> 5.1)
thread_safe (~> 0.3, >= 0.3.4)
tzinfo (~> 1.1)
addressable (2.3.8)
backports (3.6.4)
bson (3.1.2)
builder (3.2.2)
connection_pool (2.2.0)
daemons (1.2.3)
em-websocket (0.3.8)
addressable (>= 2.1.1)
eventmachine (>= 0.12.9)
eventmachine (1.0.8)
haml (4.0.6)
tilt
i18n (0.7.0)
json (1.8.3)
minitest (5.8.0)
mongoid (4.0.2)
activemodel (~> 4.0)
moped (~> 2.0.0)
origin (~> 2.1)
tzinfo (>= 0.3.37)
moped (2.0.6)
bson (~> 3.0)
connection_pool (~> 2.0)
optionable (~> 0.2.0)
multi_json (1.11.2)
optionable (0.2.0)
origin (2.1.1)
rack (1.6.4)
rack-protection (1.5.3)
rack
rack-test (0.6.3)
rack (>= 1.0)
sinatra (1.4.6)
rack (~> 1.4)
rack-protection (~> 1.4)
tilt (>= 1.3, < 3)
sinatra-contrib (1.4.2)
backports (>= 2.0)
multi_json
rack-protection
rack-test
sinatra (~> 1.4.0)
tilt (~> 1.3)
sinatra-websocket (0.3.1)
em-websocket (~> 0.3.6)
eventmachine
thin (>= 1.3.1, < 2.0.0)
thin (1.6.3)
daemons (~> 1.0, >= 1.0.9)
eventmachine (~> 1.0)
rack (~> 1.0)
thread_safe (0.3.5)
tilt (1.4.1)
tzinfo (1.2.2)
thread_safe (~> 0.1)
PLATFORMS
ruby
DEPENDENCIES
activesupport
haml
mongoid
sinatra
sinatra-contrib
sinatra-websocket
thin (< 2.0.0)
BUNDLED WITH
1.10.0.rc
!!!
%html.no-js{:lang => "es"}
%head
%meta{:charset => "utf-8"}/
%meta{:content => "IE=edge", "http-equiv" => "X-UA-Compatible"}/
%title sinatra-shell
%meta{:content => "width=device-width, initial-scale=1", :name => "viewport"}/
%link{:href => "https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0-alpha/css/bootstrap.css", :rel => "stylesheet"}/
:css
#content {
margin: 0 auto;
float: none;
}
.commands {
padding: 40px;
}
th {
text-align: center;
}
tr {
cursor: pointer;
}
%body
.container.text-center
#content.row.col-xs-9
%form.form-inline.commands{method: :post}
- cmds.each do |cmd, _|
%input.btn.btn-default.btn-lg{name: "cmd", type: "submit", value: cmd}
%table.table.table-hover
%tr
%th
%th cmd
%th status
%th duration
%th date
%th user
- if jobs.length > 0
- jobs.each_with_index do |job, idx|
%tr.job{:id => "job-#{job.id}", :"data-job" => job.to_json }
%td= jobs.length - idx
%td= job.cmd
%td.status= job.status
%td.total= job.total
%td= job.created_at
%td= job.user
- else
%tr
%td{colspan: 6, style: 'font-style: italic;'} None
.modal.fade
.modal-dialog
.modal-content
.modal-header
%button.close{"aria-label" => "Close", "data-dismiss" => "modal", :type => "button"}
%span{"aria-hidden" => "true"} ×
%h4.modal-title Output
.modal-body
%pre
.modal-footer
%button.btn.btn-default{"data-dismiss" => "modal", :type => "button"} Close
%script{:src => "//ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js"}
%script{:src => "https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.5/js/bootstrap.js"}
:javascript
var classes = {
running: "text-warning",
scheduled: "text-active",
0: "text-success",
error: "text-danger"
};
function setClass(){
var $job = $(this);
for (var name in classes) {
$job.removeClass(classes[name]);
}
var job = $job.data('job');
if (job.status != "") {
$job.addClass(classes[String(job.status)] || classes.error);
}
}
$('.job').each(setClass);
$('.job').on('click', function() {
var $modal = $('.modal'),
$el = $(this);
$modal.find('.modal-body pre').html($el.data('output'));
$modal.modal();
});
var ws = new WebSocket('ws://' + window.location.host);
ws.onmessage = function(message) {
var job = JSON.parse(message.data);
var $job = $("#job-" + job._id.$oid);
$job.data('job', job);
$job.find('.status').html(job.status);
$job.find('.total').html(job.total);
setClass.apply($job);
};
#!/usr/bin/env ruby
# coding: UTF-8
require 'bundler/setup'
require "sinatra/base"
require 'sinatra/contrib'
require 'sinatra-websocket'
require 'thread'
require "haml"
require "json"
require 'mongoid'
require "benchmark"
require "active_support"
require "csv"
Mongoid.load! 'database.yml', :production
class Job
include Mongoid::Document
include Mongoid::Attributes::Dynamic
include Mongoid::Timestamps
def run
this = self
times = Benchmark.measure { this.output = `#{this.cmd}` }
self.status = $?.exitstatus.to_s
self.total = times.real.to_s
save!
end
end
$sockets = []
$jobs = []
Thread.new do
while true do
sleep 2
next unless job = $jobs.pop
job.status = "running"
job.save
$sockets.each{|s| s.send job.to_json }
job.run
$sockets.each{|s| s.send job.to_json }
end
end
CMDS = {
long: 'sleep 3',
error: 'sync-files static/common',
tree: 'tree'
}
PWD = File.dirname(__FILE__)
class Server < Sinatra::Application
register Sinatra::Contrib::All
set :public_folder, PWD
set :views, PWD
set :haml, format: :html5
set :port, ENV['PORT'] || 4567
set :server, 'thin'
def self.new(*)
users = {}
CSV.foreach('.htdigest', col_sep: ':') { |row| users[row[0]] = row[2] }
options = {
realm: 'realm',
opaque: '',
passwords_hashed: true
}
Rack::Auth::Digest::MD5.new(super, options) { |username| users[username] }
end
get '/' do
if !request.websocket?
haml :index, locals: {
jobs: Job.where(:created_at.gt => 1.week.ago).order_by(created_at: 'desc'),
cmds: CMDS.keys
}
else
request.websocket do |ws|
ws.onopen { $sockets << ws }
ws.onclose { $sockets.delete ws }
end
end
end
post '/' do
if cmd = CMDS[params[:cmd].to_sym]
job = Job.create cmd: cmd, output: 'no output', total: 0, status: 'scheduled', user: request.env['REMOTE_USER']
$jobs << job
redirect '/'
else
status 403
"Comando no permitido"
end
end
run!
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.