blavoshost 0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.document +5 -0
- data/.gitignore +21 -0
- data/LICENSE +20 -0
- data/README.rdoc +17 -0
- data/Rakefile +20 -0
- data/VERSION +1 -0
- data/bin/blavoshost +818 -0
- data/test/helper.rb +10 -0
- data/test/test_blavoshost.rb +7 -0
- metadata +77 -0
data/.document
ADDED
data/.gitignore
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 Jerrod Blavos
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
= blavoshost
|
2
|
+
|
3
|
+
Description goes here.
|
4
|
+
|
5
|
+
== Note on Patches/Pull Requests
|
6
|
+
|
7
|
+
* Fork the project.
|
8
|
+
* Make your feature addition or bug fix.
|
9
|
+
* Add tests for it. This is important so I don't break it in a
|
10
|
+
future version unintentionally.
|
11
|
+
* Commit, do not mess with rakefile, version, or history.
|
12
|
+
(if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
|
13
|
+
* Send me a pull request. Bonus points for topic branches.
|
14
|
+
|
15
|
+
== Copyright
|
16
|
+
|
17
|
+
Copyright (c) 2010 Jerrod Blavos. See LICENSE for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
load File.expand_path('../bin/blavoshost', __FILE__)
|
5
|
+
begin
|
6
|
+
require 'jeweler'
|
7
|
+
Jeweler::Tasks.new do |gem|
|
8
|
+
gem.name = "blavoshost"
|
9
|
+
gem.summary = %Q{Add local vhosts}
|
10
|
+
gem.description = %Q{A tool to add local vhosts ot apache/OSX}
|
11
|
+
gem.email = "jerrod@indierockmedia.com"
|
12
|
+
gem.homepage = "http://github.com/jerrod/blavoshost"
|
13
|
+
gem.authors = ["Jerrod Blavos"]
|
14
|
+
gem.executables = ['blavoshost']
|
15
|
+
gem.require_paths = ["bin"] # annoying requirement
|
16
|
+
end
|
17
|
+
Jeweler::GemcutterTasks.new
|
18
|
+
rescue LoadError
|
19
|
+
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
20
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.0.0
|
data/bin/blavoshost
ADDED
@@ -0,0 +1,818 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
$:.unshift(File.dirname(__FILE__)) unless
|
3
|
+
$:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
|
4
|
+
|
5
|
+
require 'rubygems'
|
6
|
+
require 'json'
|
7
|
+
require 'open3'
|
8
|
+
require 'ostruct'
|
9
|
+
require 'optparse'
|
10
|
+
require 'fileutils'
|
11
|
+
require 'ruby-debug'
|
12
|
+
|
13
|
+
module Hipe
|
14
|
+
module Vhost
|
15
|
+
HardCoded = OpenStruct.new(
|
16
|
+
:execs => ['apachectl','apache2ctl'],
|
17
|
+
:httpd_conf => ['/etc/apache2/apache2.conf',
|
18
|
+
'/etc/apache2/httpd.conf'],
|
19
|
+
:docroots => '~/Sites',
|
20
|
+
:vhost_confs => '/etc/apache2/sites-enabled/',
|
21
|
+
:etc_hosts => '/etc/hosts'
|
22
|
+
)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
|
27
|
+
module Hipe
|
28
|
+
|
29
|
+
# Every exception thrown directly by this library will be a kind of
|
30
|
+
# Hipe::Fail. Subclasses are things like UserFail, EnvFail and
|
31
|
+
# PermissionFail, which are exceptions resulting from something
|
32
|
+
# the user did wrong, something in the environment that won't permit us
|
33
|
+
# to carry out a request, or a permissions issue with the filesystem
|
34
|
+
# or process space; respectively. The implementing client application will
|
35
|
+
# usually want to catch these and recover gracefully from them.
|
36
|
+
|
37
|
+
# There are corresponding modules for each of these exception classes
|
38
|
+
# (a UserFail is a kind of UserFailey (the adjectivial form) for example),
|
39
|
+
# so we can enhance exceptions thrown from other libraries as well,
|
40
|
+
# and re-throw them as that original exception object with its stack intact.
|
41
|
+
# All exception classes in this library end in 'Fail', and their
|
42
|
+
# corresponding modules end in 'Failey' (the adjective form.)
|
43
|
+
# So note that an exception caught from another library and 'enhanced'
|
44
|
+
# by this one will not be a kind of Hipe::Fail, only a kind of Hipe::Failey.
|
45
|
+
|
46
|
+
# So to have your bases covered, rescue Hipe::Failey or the subset of
|
47
|
+
# Hipe::Failies that you care about rescuing.
|
48
|
+
|
49
|
+
module Failey; end
|
50
|
+
module EnvFailey; include Failey end
|
51
|
+
module UserFailey; include Failey end
|
52
|
+
module PermissionFailey; include UserFailey end
|
53
|
+
|
54
|
+
class Fail < RuntimeError; include Failey end
|
55
|
+
class UserFail < Fail; include UserFailey end
|
56
|
+
class EnvFail < Fail; include EnvFailey end
|
57
|
+
class PermissionFail < UserFail; include PermissionFailey end
|
58
|
+
|
59
|
+
# AsciiFormattey last seen in f2fa2
|
60
|
+
|
61
|
+
# this guy was moved into ack-lite-parse and modified. if you
|
62
|
+
# want any more features, start with that one and note it there
|
63
|
+
module CliLitey
|
64
|
+
# if this thing exceeds 30 lines of code use hipe-core/interfacey
|
65
|
+
def cli_run(argv)
|
66
|
+
return help_text unless argv[0] && @actions[argv[0]]
|
67
|
+
action = argv.shift
|
68
|
+
action = help if (['-h','--help'].include?(action))
|
69
|
+
meth = action
|
70
|
+
send(meth, argv)
|
71
|
+
end
|
72
|
+
|
73
|
+
def program_name
|
74
|
+
File.basename $PROGRAM_NAME
|
75
|
+
end
|
76
|
+
|
77
|
+
def help(args)
|
78
|
+
help_text+"\n\n"
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
module FileyCoyote
|
83
|
+
def self.writable!(path)
|
84
|
+
if ! File.writable?(path)
|
85
|
+
raise PermissionFail.new("Can't write to #{path}")
|
86
|
+
end
|
87
|
+
@path = path
|
88
|
+
nil
|
89
|
+
end
|
90
|
+
def writable!
|
91
|
+
FileyCoyote.writable!(@path)
|
92
|
+
self
|
93
|
+
end
|
94
|
+
def fileize str
|
95
|
+
FileyCoyote.fileize str
|
96
|
+
end
|
97
|
+
def self.fileize str
|
98
|
+
str.gsub(/[^-\.a-zA-Z0-9]/,'')
|
99
|
+
end
|
100
|
+
def back_that_asset_up! orig_path
|
101
|
+
dry = respond_to?(:dry_run?) ? dry_run? : false
|
102
|
+
append = Time.now.strftime('%Y-%m-%d_%H:%M:%S')
|
103
|
+
new_name = %{#{orig_path}.bak.#{append}}
|
104
|
+
i = 1
|
105
|
+
while File.exist? new_name # very unlikely
|
106
|
+
new_name = %{#{orig_path}.bak.#{append}.#{i}}
|
107
|
+
i += 1
|
108
|
+
end
|
109
|
+
begin
|
110
|
+
FileUtils.copy_file orig_path, new_name unless dry
|
111
|
+
rescue Errno::EACCES => e
|
112
|
+
e.extend OpenStructey
|
113
|
+
e.extend PermissionFailey
|
114
|
+
e.filename = new_name
|
115
|
+
raise e
|
116
|
+
end
|
117
|
+
new_name
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
module DryRunney
|
122
|
+
def dry_run= val
|
123
|
+
@dry_run = val
|
124
|
+
end
|
125
|
+
def dry_run?
|
126
|
+
@dry_run
|
127
|
+
end
|
128
|
+
alias_method :noop, :dry_run?
|
129
|
+
def dry_run!
|
130
|
+
@dry_run = true
|
131
|
+
end
|
132
|
+
def report
|
133
|
+
wtf = @report ||= []
|
134
|
+
wtf
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
module OpenStructey
|
139
|
+
def self.extend_object(base)
|
140
|
+
base.instance_eval do
|
141
|
+
@__info = {}
|
142
|
+
end
|
143
|
+
super
|
144
|
+
end
|
145
|
+
def method_missing name, *args, &blah
|
146
|
+
if /^(.+)=$/ =~ name.to_s
|
147
|
+
@__info[$1.to_sym] = args[0]
|
148
|
+
else
|
149
|
+
@__info[name.to_sym]
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
module Systematic
|
155
|
+
def sistum command
|
156
|
+
stdin, stdout, stderr = Open3.popen3 command
|
157
|
+
err = stderr.read; out = stdout.read
|
158
|
+
raise Fail.new(err) if "" != err
|
159
|
+
out.chop
|
160
|
+
end
|
161
|
+
|
162
|
+
def sistum2 command
|
163
|
+
stdin, stdout, stderr = Open3.popen3 command
|
164
|
+
err = stderr.read; out = stdout.read
|
165
|
+
[out.chop,err.chop]
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
module Templatesimal
|
170
|
+
# the last thing the world needs is...
|
171
|
+
|
172
|
+
NamePattern = /\{\{([^\}]+)\}\}/ # e.g. "{{title}}"
|
173
|
+
|
174
|
+
def self.[] str
|
175
|
+
str.extend self
|
176
|
+
str.init_templatesimal
|
177
|
+
str
|
178
|
+
end
|
179
|
+
|
180
|
+
def init_templatesimal
|
181
|
+
names = scan(NamePattern).map{|x| x[0]}.uniq
|
182
|
+
meta = class << self; end
|
183
|
+
names.each do |name|
|
184
|
+
unless respond_to?(name)
|
185
|
+
meta = class << self; self; end
|
186
|
+
meta.send(:define_method, :"#{name}=") do |x|
|
187
|
+
template_replace(name, x)
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
def template_replace name, value
|
194
|
+
gsub! "{{#{name}}}", value
|
195
|
+
end
|
196
|
+
|
197
|
+
def substitute! hash
|
198
|
+
hash.each{|pair| template_replace(pair[0],pair[1]) }
|
199
|
+
self
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
class AsciiTableLite
|
204
|
+
#
|
205
|
+
# For now this is the official home of this here in vhost.
|
206
|
+
# This is a simplified version of hipe-core/struct/table
|
207
|
+
# Note any other projects that copy paste this here:
|
208
|
+
# [none]
|
209
|
+
#
|
210
|
+
|
211
|
+
def initialize(*cols)
|
212
|
+
@cols = cols
|
213
|
+
@pipe = '|'
|
214
|
+
@cona = '+'
|
215
|
+
@rule = '-'
|
216
|
+
@rulesep = @rule * 3
|
217
|
+
@sep = " #{@pipe} "
|
218
|
+
end
|
219
|
+
|
220
|
+
def render rows
|
221
|
+
@rows = rows
|
222
|
+
@maxes = Hash[*@cols.map{|x|[x, x.to_s.length]}.flatten]
|
223
|
+
prerendereds = []
|
224
|
+
rows.each do |info|
|
225
|
+
preprender = OpenStruct.new
|
226
|
+
@cols.each do |col|
|
227
|
+
cel = info.send(col).to_s
|
228
|
+
preprender.send("#{col}=",cel)
|
229
|
+
if (@maxes[col].nil? || cel.length > @maxes[col])
|
230
|
+
@maxes[col] = cel.length
|
231
|
+
end
|
232
|
+
end
|
233
|
+
prerendereds.push preprender
|
234
|
+
end
|
235
|
+
@lines = [go_sep]
|
236
|
+
@lines.push go_headers
|
237
|
+
@lines.push go_sep
|
238
|
+
prerendereds.each do |info|
|
239
|
+
@lines.push go_row info
|
240
|
+
end
|
241
|
+
@lines.push go_sep
|
242
|
+
@lines * "\n"
|
243
|
+
end
|
244
|
+
|
245
|
+
## private
|
246
|
+
|
247
|
+
def go_sep
|
248
|
+
"#{@cona}#{@rule}"<<
|
249
|
+
"#{@cols.map{|x| @rule * @maxes[x]} * @rulesep }"<<
|
250
|
+
"#{@rule}#{@cona}"
|
251
|
+
end
|
252
|
+
|
253
|
+
def go_headers
|
254
|
+
"#{@pipe} "<<
|
255
|
+
(@cols.map{|x|x.to_s.ljust(@maxes[x])}* @sep)<<
|
256
|
+
" #{@pipe}"
|
257
|
+
end
|
258
|
+
|
259
|
+
def go_row row
|
260
|
+
"#{@pipe} "<<
|
261
|
+
(@cols.map{|x|row.send(x).to_s.ljust(@maxes[x])}*@sep)<<
|
262
|
+
" #{@pipe}"
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
class UnitOfWork
|
267
|
+
include DryRunney
|
268
|
+
attr_reader :describe
|
269
|
+
def initialize(desc, &block)
|
270
|
+
@describe = desc
|
271
|
+
@block = block
|
272
|
+
end
|
273
|
+
def empty?
|
274
|
+
@block.nil?
|
275
|
+
end
|
276
|
+
def commit
|
277
|
+
@block.call(self) if @block
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
281
|
+
module UnitsOfWork
|
282
|
+
def add_unit_of_work(*args, &block)
|
283
|
+
units_of_work.push UnitOfWork.new(*args,&block)
|
284
|
+
end
|
285
|
+
def units_of_work
|
286
|
+
@units_of_work ||= []
|
287
|
+
end
|
288
|
+
def run_units_of_work dry_run=false
|
289
|
+
num_done = 0
|
290
|
+
units_of_work.each do |unit|
|
291
|
+
if unit.empty?
|
292
|
+
puts unit.describe
|
293
|
+
else
|
294
|
+
unit.dry_run = dry_run
|
295
|
+
unit.commit
|
296
|
+
puts unit.describe
|
297
|
+
unit.report.each do |line|
|
298
|
+
puts " #{line}"
|
299
|
+
end
|
300
|
+
num_done += 1
|
301
|
+
end
|
302
|
+
end
|
303
|
+
num_done
|
304
|
+
end
|
305
|
+
end
|
306
|
+
|
307
|
+
module Vhost
|
308
|
+
|
309
|
+
class Fail < RuntimeError; end
|
310
|
+
|
311
|
+
class Cli
|
312
|
+
include Hipe::CliLitey, Hipe::Systematic, Hipe::UnitsOfWork
|
313
|
+
Table = Hipe::AsciiTableLite
|
314
|
+
|
315
|
+
def initialize
|
316
|
+
@actions = {'help'=>1,'explain'=>1,'hosts'=>1,'confs'=>1,
|
317
|
+
'dump' =>1, 'list'=>1, 'add'=>1
|
318
|
+
}
|
319
|
+
@path = {}
|
320
|
+
%w(docroots vhost_confs etc_hosts).each do |guy|
|
321
|
+
@path[guy.to_sym] = File.expand_path HardCoded.send(guy)
|
322
|
+
end
|
323
|
+
these = HardCoded.httpd_conf
|
324
|
+
one = these.detect{ |x| File.exist?(x) }
|
325
|
+
one or fail("not found: #{these.join(', ')}")
|
326
|
+
@path[:httpd_conf] = one
|
327
|
+
get_orientated_with_your_environment
|
328
|
+
@path = OpenStruct.new(@path)
|
329
|
+
end
|
330
|
+
|
331
|
+
def run argv
|
332
|
+
begin
|
333
|
+
cli_run argv
|
334
|
+
rescue PermissionFailey => e
|
335
|
+
return "#{e.message} - do you need to run this with sudo?"
|
336
|
+
rescue EnvFailey, UserFailey => e
|
337
|
+
return "#{e.message}"
|
338
|
+
end
|
339
|
+
end
|
340
|
+
|
341
|
+
def explain args; HardCoded.explain end
|
342
|
+
|
343
|
+
def help_text
|
344
|
+
HardCoded.help.clone.substitute!(:program_name => program_name)
|
345
|
+
end
|
346
|
+
|
347
|
+
###### command implementations
|
348
|
+
|
349
|
+
#def explain argv; HardCoded.explain end
|
350
|
+
|
351
|
+
def hosts argv
|
352
|
+
rows = parse_hosts.sort!{|x,y| x.name <=> y.name }
|
353
|
+
Table.new(:ip, :name).render(rows)
|
354
|
+
end
|
355
|
+
|
356
|
+
def confs argv
|
357
|
+
rows = parse_confs.sort!{|x,y| x.server_name <=> y.server_name }
|
358
|
+
Table.new(:server_name,:document_root).render(rows)
|
359
|
+
end
|
360
|
+
|
361
|
+
def dump argv
|
362
|
+
parse = parse_dump_vhosts
|
363
|
+
puts Table.new(:server_name, :port, :conf_path).
|
364
|
+
render(parse.entries)
|
365
|
+
puts "\n"
|
366
|
+
puts Table.new(:missing_docroot).
|
367
|
+
render(parse.missing_docroots)
|
368
|
+
''
|
369
|
+
end
|
370
|
+
|
371
|
+
def list argv
|
372
|
+
infos = master.values.sort!{|x,y| x.host <=> y.host}
|
373
|
+
Table.new(
|
374
|
+
:host, :host_ok, :ip, :port, :docroot, :docroot_ok, :conf
|
375
|
+
).render(infos)
|
376
|
+
end
|
377
|
+
|
378
|
+
def add argv
|
379
|
+
opts = OpenStruct.new(
|
380
|
+
:dry_run => false,
|
381
|
+
:docroots => @path.docroots
|
382
|
+
)
|
383
|
+
parser = OptionParser.new do |o|
|
384
|
+
o.on('-n','--dry-run'){ opts.dry_run = true; }
|
385
|
+
o.on('-d','--docroots PATH') do |v|
|
386
|
+
@path.docroots = File.expand_path(v)
|
387
|
+
end
|
388
|
+
o.on('-h','--help') do
|
389
|
+
return "Usage: #{program_name} #{Cli.add_syntax}"
|
390
|
+
end
|
391
|
+
end
|
392
|
+
begin
|
393
|
+
parser.parse! argv
|
394
|
+
rescue OptionParser::ParseError => e
|
395
|
+
puts e.message
|
396
|
+
puts "Usage: #{program_name} #{Cli.add_syntax}"
|
397
|
+
return ''
|
398
|
+
end
|
399
|
+
errs = [];
|
400
|
+
if (argv.length == 0)
|
401
|
+
errs << "too few argments. expecting one."
|
402
|
+
elsif (argv.length > 1)
|
403
|
+
errs << "too many arguments. expecting one."
|
404
|
+
elsif (! (argv[0].to_s =~ EtcHostsProxy::OkNameRe))
|
405
|
+
errs << "invalid name for a domain(?): #{argv[0].inspect}"
|
406
|
+
end
|
407
|
+
if errs.length > 0
|
408
|
+
return ((errs * "\n") << "\nUsage: #{Cli.add_syntax}")
|
409
|
+
end
|
410
|
+
new_hostname = argv[0]
|
411
|
+
_add new_hostname, opts
|
412
|
+
end
|
413
|
+
|
414
|
+
###### top secret implementationz
|
415
|
+
|
416
|
+
def self.add_syntax
|
417
|
+
"add [-n|--dry-run] [-d|--docroots <path>] <host>"
|
418
|
+
end
|
419
|
+
|
420
|
+
def _add host, opts
|
421
|
+
item = master[host] # might be empty object
|
422
|
+
units_of_work.clear
|
423
|
+
go_etc_hosts item, host
|
424
|
+
go_config item, host
|
425
|
+
go_html item, host
|
426
|
+
num = run_units_of_work opts.dry_run
|
427
|
+
if (num==0)
|
428
|
+
puts "nothing to be done for #{host}."
|
429
|
+
else
|
430
|
+
puts "if this wasn't a dry run, you could restart apache with:"
|
431
|
+
puts " #{@path.apachectl} graceful"
|
432
|
+
puts "and go to:"
|
433
|
+
puts " http://#{host}/index.html"
|
434
|
+
end
|
435
|
+
'done.'
|
436
|
+
end
|
437
|
+
|
438
|
+
def go_etc_hosts item, host
|
439
|
+
if 'ok'==item.host_ok
|
440
|
+
add_unit_of_work("#{@path.etc_hosts} ok for #{host}")
|
441
|
+
else
|
442
|
+
hosts = EtcHostsProxy.new(@path.etc_hosts).writable!
|
443
|
+
add_unit_of_work("add line to #{@path.etc_hosts}:") do |unit|
|
444
|
+
hosts.dry_run = unit.dry_run?
|
445
|
+
hosts.add! host
|
446
|
+
unit.report.concat hosts.report
|
447
|
+
end
|
448
|
+
end
|
449
|
+
end
|
450
|
+
|
451
|
+
def go_config item, host
|
452
|
+
if item.conf
|
453
|
+
add_unit_of_work("config ok for #{host}")
|
454
|
+
else
|
455
|
+
doc_root = File.join(@path.docroots, FileyCoyote.fileize(host))
|
456
|
+
confs = VhostFileMakerPro.new(apache_conf_proxy.sites_enabled_path)
|
457
|
+
add_unit_of_work("add config file for #{host}:") do |unit|
|
458
|
+
confs.dry_run = unit.dry_run?
|
459
|
+
confs.add! host, doc_root
|
460
|
+
unit.report.concat confs.report
|
461
|
+
end
|
462
|
+
end
|
463
|
+
end
|
464
|
+
|
465
|
+
def go_html item, host
|
466
|
+
if 'ok'==item.docroot_ok
|
467
|
+
add_unit_of_work("#{@path.docroots} ok for #{host}")
|
468
|
+
else
|
469
|
+
docroot = DocRootProxy.new(@path.docroots).writable!
|
470
|
+
add_unit_of_work("add stub website to #{@path.docroots}:")do |unit|
|
471
|
+
docroot.dry_run = unit.dry_run?
|
472
|
+
docroot.make_stub_site(host)
|
473
|
+
unit.report.concat docroot.report
|
474
|
+
end
|
475
|
+
end
|
476
|
+
end
|
477
|
+
|
478
|
+
def parse_hosts
|
479
|
+
EtcHostsProxy.new(@path.etc_hosts).entries
|
480
|
+
end
|
481
|
+
|
482
|
+
def parse_confs
|
483
|
+
conf = apache_conf_proxy
|
484
|
+
confs = conf.sites_enabled_confs
|
485
|
+
confs.delete_if{|x| x.document_root == '' }
|
486
|
+
confs.sort!{|x,y| x.server_name <=> y.server_name}
|
487
|
+
end
|
488
|
+
|
489
|
+
def apache_conf_proxy
|
490
|
+
@apache_conf_proxy ||= ApacheConfProxy.new @path.httpd_conf
|
491
|
+
end
|
492
|
+
|
493
|
+
def parse_dump_vhosts
|
494
|
+
cmd = "#{@path.apachectl} -t -D DUMP_VHOSTS"
|
495
|
+
out,err = sistum2(cmd)
|
496
|
+
lines = out.split("\n");
|
497
|
+
lines.concat err.split("\n");
|
498
|
+
# on ubuntu, we get 'Syntax OK' in out, and details in err
|
499
|
+
# on osx, we get empty out and everything in err
|
500
|
+
p = DumpParse.new lines.join("\n") # @todo plan on having this crap out a lot
|
501
|
+
p
|
502
|
+
end
|
503
|
+
|
504
|
+
def get_orientated_with_your_environment
|
505
|
+
list = HardCoded.execs
|
506
|
+
infos = list.map do |executable|
|
507
|
+
OpenStruct.new(
|
508
|
+
'which' => sistum("which #{executable}")
|
509
|
+
)
|
510
|
+
end
|
511
|
+
infos.delete_if{|x| ""==x.which}
|
512
|
+
case infos.length
|
513
|
+
when 0:
|
514
|
+
raise Fail.new("#{sistum('whoami')} has neither "<<
|
515
|
+
(list * ' nor ') << "within his or her executable path."<<
|
516
|
+
" I can be of no help to you."
|
517
|
+
)
|
518
|
+
when 1: # fallthru
|
519
|
+
else
|
520
|
+
raise Fail.new("#{sistum('whoami')} has all of these in"<<
|
521
|
+
" his or her path: "<< (infos.map(&:which) * ' and ')<<'.'<<
|
522
|
+
" As this is a zero-configuration jobbie, I have no idea"<<
|
523
|
+
" what to do."
|
524
|
+
)
|
525
|
+
end
|
526
|
+
@path[:apachectl] = infos[0].which
|
527
|
+
end
|
528
|
+
|
529
|
+
def master
|
530
|
+
hosts = parse_hosts
|
531
|
+
confs = parse_confs
|
532
|
+
dumps = parse_dump_vhosts # entries, missing_doctroots
|
533
|
+
missings = Hash[*
|
534
|
+
dumps.missing_docroots.each.map{|x| [x.missing_docroot, 1]}.flatten
|
535
|
+
]
|
536
|
+
master = Hash.new(){|h,k| h[k] = OpenStruct.new(:host=>k) }
|
537
|
+
hosts.each do |host|
|
538
|
+
item = master[host.name]
|
539
|
+
item.ip = host.ip
|
540
|
+
item.host_ok = 'ok'
|
541
|
+
end
|
542
|
+
confs.each do |conf|
|
543
|
+
item = master[conf.server_name]
|
544
|
+
item.docroot = conf.document_root
|
545
|
+
item.docroot_ok = (missings[conf.document_root]) ? 'missing' : 'ok'
|
546
|
+
end
|
547
|
+
dumps.entries.each do |dump|
|
548
|
+
item = master[dump.server_name]
|
549
|
+
item.conf = dump.conf_path
|
550
|
+
item.port = dump.port
|
551
|
+
end
|
552
|
+
master
|
553
|
+
end
|
554
|
+
end # Cli
|
555
|
+
|
556
|
+
class DumpParse
|
557
|
+
attr_reader :missing_docroots, :entries
|
558
|
+
def initialize str
|
559
|
+
lines = str.split("\n")
|
560
|
+
@missing_docroots = []
|
561
|
+
infos = []
|
562
|
+
lines.each do |line|
|
563
|
+
case line
|
564
|
+
when /^Warning: DocumentRoot \[(.+)\] does not exist$/:
|
565
|
+
@missing_docroots.push OpenStruct.new(:missing_docroot => $1)
|
566
|
+
when /^.*is a NameVirtualHost$/: #skip
|
567
|
+
when /^wildcard NameVirtualHosts and _default_ servers: *$/: #skip
|
568
|
+
when "VirtualHost configuration:": # skip
|
569
|
+
when /^ {9}default server (.+) \((.+)\:\d+\)$/:
|
570
|
+
infos.push OpenStruct.new(
|
571
|
+
:server_name => $1, :conf_path => $2, :port => nil
|
572
|
+
)
|
573
|
+
when /^ {9}port ([^ ]+) namevhost (.+) \((.+)\:\d+\)$/:
|
574
|
+
infos.push OpenStruct.new(
|
575
|
+
:server_name => $2, :conf_path => $3, :port => $1
|
576
|
+
)
|
577
|
+
when /^Syntax OK$/: #skip for now end
|
578
|
+
when /^httpd: Could not reliably determine/: #skip
|
579
|
+
when /^\*:(\d{4}) +([^ ]+) +\((.+)\:\d+\)$/
|
580
|
+
infos.push OpenStruct.new(
|
581
|
+
:server_name => $2, :conf_path => $3, :port => $1
|
582
|
+
)
|
583
|
+
else
|
584
|
+
raise Fail.new("we suck: parse fail:\n#{line.inspect}")
|
585
|
+
end
|
586
|
+
end
|
587
|
+
@entries = infos
|
588
|
+
end
|
589
|
+
end
|
590
|
+
|
591
|
+
class ApacheConfProxy
|
592
|
+
include DryRunney
|
593
|
+
SitesEnabledRe = %r|^ *Include +([^\n]*sites-enabled/?) *$|m
|
594
|
+
# code for pasring the file itself left behind in bf91c
|
595
|
+
def initialize path
|
596
|
+
@path = path
|
597
|
+
if !File.readable?(@path)
|
598
|
+
raise UserFail.new("no such apache conf file or not readable: #{@path}")
|
599
|
+
end
|
600
|
+
@contents = File.read path
|
601
|
+
end
|
602
|
+
|
603
|
+
# this project it is hard coded to expect that there is
|
604
|
+
# only one such entry in the file
|
605
|
+
def sites_enabled_path
|
606
|
+
arr = @contents.scan(SitesEnabledRe).map{|x| x[0]}
|
607
|
+
case arr.length
|
608
|
+
when 0:
|
609
|
+
`sudo bash -c 'sudo echo "Include /private/etc/apache2/sites-enabled/*.conf" >> /etc/apache2/httpd.conf'`
|
610
|
+
`sudo bash -c 'sudo mkdir /private/etc/apache2/sites-enabled '`
|
611
|
+
return '/private/etc/apache2/sites-enabled/'
|
612
|
+
when 1: arr[0]
|
613
|
+
else arr
|
614
|
+
end
|
615
|
+
end
|
616
|
+
|
617
|
+
def sites_enabled_confs
|
618
|
+
path1 = sites_enabled_path
|
619
|
+
paths = Dir[File.join(path1,'*.conf')]
|
620
|
+
paths.map do |path|
|
621
|
+
VhostConfProxy.new(path)
|
622
|
+
end
|
623
|
+
end
|
624
|
+
end
|
625
|
+
|
626
|
+
class VhostFileMakerPro
|
627
|
+
include DryRunney
|
628
|
+
def initialize path
|
629
|
+
unless File.directory? path
|
630
|
+
raise EnvFail.new "it's not exist: #{path}"
|
631
|
+
end
|
632
|
+
unless File.writable? path
|
633
|
+
raise PermissionFail.new "not writable: #{path}"
|
634
|
+
end
|
635
|
+
@path = path
|
636
|
+
end
|
637
|
+
def add! host, doc_root
|
638
|
+
path = File.join(@path, "#{host}.conf")
|
639
|
+
if File.exist? path
|
640
|
+
report << "File already existed: #{path}"
|
641
|
+
else
|
642
|
+
contents = HardCoded.vhost_stuff.clone
|
643
|
+
contents.doc_root = doc_root
|
644
|
+
contents.host = host
|
645
|
+
unless dry_run?
|
646
|
+
File.open(path,'a+'){|fh|
|
647
|
+
fh.write contents
|
648
|
+
}
|
649
|
+
end
|
650
|
+
report << "write #{path}"
|
651
|
+
end
|
652
|
+
nil
|
653
|
+
end
|
654
|
+
end
|
655
|
+
|
656
|
+
class VhostConfProxy < ApacheConfProxy
|
657
|
+
def initialize *args
|
658
|
+
super(*args)
|
659
|
+
@cache = {}
|
660
|
+
end
|
661
|
+
def document_root
|
662
|
+
_get('DocumentRoot')
|
663
|
+
end
|
664
|
+
def server_name
|
665
|
+
_get('ServerName')
|
666
|
+
end
|
667
|
+
def _get thing
|
668
|
+
@cache[thing] ||= begin
|
669
|
+
re = Regexp.new('^ *'<<Regexp.escape(thing)<<
|
670
|
+
' *"?([^"]+)"? *$')
|
671
|
+
matches = @contents.scan(re)
|
672
|
+
case matches.length
|
673
|
+
when 0: ""
|
674
|
+
when 1: matches[0][0]
|
675
|
+
else matches.map{|x| x[0]}.join(',')
|
676
|
+
end
|
677
|
+
end
|
678
|
+
end
|
679
|
+
end
|
680
|
+
|
681
|
+
class DocRootProxy
|
682
|
+
include FileyCoyote, DryRunney
|
683
|
+
def initialize path
|
684
|
+
@path = path
|
685
|
+
end
|
686
|
+
def writable!
|
687
|
+
unless File.directory? @path
|
688
|
+
raise EnvFail.new(
|
689
|
+
"docroots directory (the directory to hold your docroot "<<
|
690
|
+
"directory) must exist and be directory: #{@path}")
|
691
|
+
end
|
692
|
+
FileyCoyote.writable! @path
|
693
|
+
self
|
694
|
+
end
|
695
|
+
def make_stub_site host
|
696
|
+
@host = host
|
697
|
+
if (doc_root_exists?)
|
698
|
+
raise Fail.new("already exists: #{doc_root}")
|
699
|
+
end
|
700
|
+
report << "mkdir -m 755 #{doc_root}"
|
701
|
+
FileUtils.mkdir(doc_root,:mode=>0755, :verbose=>false,:noop=>dry_run?)
|
702
|
+
index_html = File.join(doc_root,"index.html")
|
703
|
+
my_new_homepage = HardCoded.web_page.clone
|
704
|
+
my_new_homepage.title = host.gsub(/_|-/,' ')
|
705
|
+
unless dry_run?
|
706
|
+
File.open(index_html,'a+'){|fh| fh.write my_new_homepage }
|
707
|
+
end
|
708
|
+
report << "write #{index_html}"
|
709
|
+
nil
|
710
|
+
end
|
711
|
+
protected
|
712
|
+
def doc_root
|
713
|
+
doc_root = File.join(@path, fileize(@host))
|
714
|
+
end
|
715
|
+
def doc_root_exists?
|
716
|
+
File.exist?(doc_root)
|
717
|
+
end
|
718
|
+
end
|
719
|
+
|
720
|
+
class EtcHostsProxy
|
721
|
+
include DryRunney
|
722
|
+
OkNameRe = /^[-a-z_0-9\.]{1,}$/
|
723
|
+
include FileyCoyote, DryRunney
|
724
|
+
def initialize path
|
725
|
+
raise Fail.new("no: #{path}") unless File.exist?(path)
|
726
|
+
@path = path
|
727
|
+
end
|
728
|
+
def entries
|
729
|
+
File.read(@path).scan(
|
730
|
+
%r{(127\.0\.0\.1)\s+([[:print:]]+)[\s\t]*$}
|
731
|
+
).map do |match|
|
732
|
+
OpenStruct.new(:ip => match[0], :name => match[1])
|
733
|
+
end
|
734
|
+
end
|
735
|
+
def add! name
|
736
|
+
writable!
|
737
|
+
raise UserFail.new("i don't like your name: #{name.inspect}") unless
|
738
|
+
(name =~ OkNameRe)
|
739
|
+
info = OpenStruct.new
|
740
|
+
info.backed_up = back_that_asset_up! @path
|
741
|
+
report.push "make backup: #{info.backed_up}"
|
742
|
+
File.open(@path,'a+') do |fh|
|
743
|
+
fh.seek(0,IO::SEEK_END)
|
744
|
+
fh.puts %{127.0.0.1 #{name}}
|
745
|
+
info.hostname = name
|
746
|
+
info.ip = '127.0.0.1'
|
747
|
+
end unless dry_run?
|
748
|
+
report.push %{add "#{name}" to #{@path}}
|
749
|
+
info
|
750
|
+
end
|
751
|
+
end
|
752
|
+
|
753
|
+
|
754
|
+
HardCoded.help = Templatesimal[<<-END.gsub(/^ {6}/,'')
|
755
|
+
do some stuff with your apache server virtualhosts.
|
756
|
+
|
757
|
+
Usage: {{program_name}} COMMAND [ARGUMENTS]
|
758
|
+
|
759
|
+
Commands:
|
760
|
+
help show this screen
|
761
|
+
explain explains the steps involved in adding a virtualhost
|
762
|
+
-- you should understand this before using this script
|
763
|
+
hosts just shows the parsed version of the /etc/hosts file
|
764
|
+
confs just list the known vhosts per the sites-enabled dir.
|
765
|
+
dump a tableized version of `apachectl -t -D DUMP_VHOSTS`
|
766
|
+
list report everything you know about anything
|
767
|
+
#{Cli.add_syntax}
|
768
|
+
add stuff to all 3 places if necessary
|
769
|
+
END
|
770
|
+
]
|
771
|
+
|
772
|
+
HardCoded.explain = Templatesimal[<<-END.gsub(/^ {6}/,'')
|
773
|
+
HOW TO ADD A VIRTUALHOST (the really short version):
|
774
|
+
1) `locate httpd.conf` # then jump to the relevant lines at the end
|
775
|
+
2) copy paste a new virtualhost section into the file -or-
|
776
|
+
add such vhost config file into a folder that gets Include'd
|
777
|
+
3) `locate /etc/hosts` # find the right file, it's usually this one
|
778
|
+
4) copy paste your line
|
779
|
+
5) apache(2?)ctl graceful
|
780
|
+
END
|
781
|
+
]
|
782
|
+
|
783
|
+
HardCoded.web_page = Templatesimal[<<-END.gsub(/^ {6}/,'')
|
784
|
+
<!DOCTYPE html><html><head><title>{{title}}</title>
|
785
|
+
<style type="text/css">
|
786
|
+
body {font-family: sans-serif; background-color: #6cc5c3; }
|
787
|
+
h1,h2,h3,h4,h5 {
|
788
|
+
text-align: center; background-color: #f05921; color: #ffffff;
|
789
|
+
-webkit-border-radius: 8px; /* for Safari */
|
790
|
+
-moz-border-radius: 8px; /* for Firefox */
|
791
|
+
padding: .23em;
|
792
|
+
text-shadow:0 0 10px #000000;
|
793
|
+
}
|
794
|
+
</style>
|
795
|
+
</head><body>
|
796
|
+
<h1>welcome to {{title}}</h1>
|
797
|
+
</body></html>
|
798
|
+
END
|
799
|
+
]
|
800
|
+
|
801
|
+
HardCoded.vhost_stuff = Templatesimal[<<-END.gsub(/^ {6}/,'')
|
802
|
+
<VirtualHost *:80>
|
803
|
+
DocumentRoot "{{doc_root}}"
|
804
|
+
ServerName "{{host}}"
|
805
|
+
<Directory "{{doc_root}}">
|
806
|
+
AllowOverride All
|
807
|
+
Allow from All
|
808
|
+
</Directory>
|
809
|
+
</VirtualHost>
|
810
|
+
END
|
811
|
+
]
|
812
|
+
|
813
|
+
end # Vhost
|
814
|
+
end # Hipe
|
815
|
+
|
816
|
+
if File.basename($PROGRAM_NAME) == File.basename(__FILE__)
|
817
|
+
puts Hipe::Vhost::Cli.new.run(ARGV)
|
818
|
+
end
|
data/test/helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: blavoshost
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 31
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 0
|
10
|
+
version: 0.0.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Jerrod Blavos
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2010-10-25 00:00:00 -04:00
|
19
|
+
default_executable: blavoshost
|
20
|
+
dependencies: []
|
21
|
+
|
22
|
+
description: A tool to add local vhosts ot apache/OSX
|
23
|
+
email: jerrod@indierockmedia.com
|
24
|
+
executables:
|
25
|
+
- blavoshost
|
26
|
+
extensions: []
|
27
|
+
|
28
|
+
extra_rdoc_files:
|
29
|
+
- LICENSE
|
30
|
+
- README.rdoc
|
31
|
+
files:
|
32
|
+
- .document
|
33
|
+
- .gitignore
|
34
|
+
- LICENSE
|
35
|
+
- README.rdoc
|
36
|
+
- Rakefile
|
37
|
+
- VERSION
|
38
|
+
- bin/blavoshost
|
39
|
+
- test/helper.rb
|
40
|
+
- test/test_blavoshost.rb
|
41
|
+
has_rdoc: true
|
42
|
+
homepage: http://github.com/jerrod/blavoshost
|
43
|
+
licenses: []
|
44
|
+
|
45
|
+
post_install_message:
|
46
|
+
rdoc_options:
|
47
|
+
- --charset=UTF-8
|
48
|
+
require_paths:
|
49
|
+
- bin
|
50
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
51
|
+
none: false
|
52
|
+
requirements:
|
53
|
+
- - ">="
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
hash: 3
|
56
|
+
segments:
|
57
|
+
- 0
|
58
|
+
version: "0"
|
59
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
60
|
+
none: false
|
61
|
+
requirements:
|
62
|
+
- - ">="
|
63
|
+
- !ruby/object:Gem::Version
|
64
|
+
hash: 3
|
65
|
+
segments:
|
66
|
+
- 0
|
67
|
+
version: "0"
|
68
|
+
requirements: []
|
69
|
+
|
70
|
+
rubyforge_project:
|
71
|
+
rubygems_version: 1.3.7
|
72
|
+
signing_key:
|
73
|
+
specification_version: 3
|
74
|
+
summary: Add local vhosts
|
75
|
+
test_files:
|
76
|
+
- test/helper.rb
|
77
|
+
- test/test_blavoshost.rb
|