blavoshost 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,21 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ *.swp
15
+
16
+ ## PROJECT::GENERAL
17
+ coverage
18
+ rdoc
19
+ pkg
20
+
21
+ ## PROJECT::SPECIFIC
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
@@ -0,0 +1,10 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+ require 'shoulda'
4
+
5
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
6
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
7
+ require 'blavoshost'
8
+
9
+ class Test::Unit::TestCase
10
+ end
@@ -0,0 +1,7 @@
1
+ require 'helper'
2
+
3
+ class TestBlavoshost < Test::Unit::TestCase
4
+ should "probably rename this file and start testing for real" do
5
+ flunk "hey buddy, you should probably rename this file and start testing for real"
6
+ end
7
+ end
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