sphinx_tv 0.9.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,18 @@
1
+ .idea
2
+ *.gem
3
+ *.rbc
4
+ .bundle
5
+ .config
6
+ .yardoc
7
+ Gemfile.lock
8
+ InstalledFiles
9
+ _yardoc
10
+ coverage
11
+ doc/
12
+ lib/bundler/man
13
+ pkg
14
+ rdoc
15
+ spec/reports
16
+ test/tmp
17
+ test/version_tmp
18
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in sphinx_tv.gemspec
4
+ gemspec
@@ -0,0 +1,20 @@
1
+ Copyright 2011 David Monagle
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.
@@ -0,0 +1,54 @@
1
+ = SphinxTV
2
+
3
+ The purpose of this gem is to provide an installer/configurator to ease the setup of a Mac Mini as a media server.
4
+ MythTV is the primary focus for this installer (MythTV + Big Cat in the OS makes for the Sphinx name.) Included in the
5
+ modules is Shepherd, an Australian TV grabber which integrates well with MythTV and allows for a great free TV guide
6
+ on the MythTV side of things.
7
+
8
+ At this point SphinxTV is regarded to be in extreme Alpha. It is on Github in the interests of the people that are
9
+ alpha testing it for me. I intend to continue to develop and expand on this as time allows (this is a hobby project
10
+ for me at this point.)
11
+
12
+ The goal is to have a website up and running in the coming months that will help detail the setup of a very functional
13
+ home entertainment system using Mac Mini hardware.
14
+
15
+ == Gem Installation ==
16
+
17
+ Easy:
18
+
19
+ gem install sphinx_tv
20
+
21
+ == Using SphinxTV ==
22
+
23
+ === Setup ===
24
+
25
+ sphinx_tv setup
26
+
27
+ Turn on the modules that you wish to install and configure.
28
+
29
+ === Download ===
30
+
31
+ sphinx_tv download
32
+
33
+ Option step but will just do all the necessary downloading for the modules that have been configured in the setup stage.
34
+
35
+ === Installation ===
36
+
37
+ sphinx_tv install
38
+
39
+ This runs through the install of each module. If the download wasn't run seperately, then the downloads will be done
40
+ on-demand at ths start of the module install.
41
+
42
+ === Configuration ===
43
+
44
+ sphinx_tv configure
45
+
46
+ Post installation configuration.
47
+
48
+ == Modules ==
49
+
50
+ === MySQL ===
51
+
52
+ === MythTV ===
53
+
54
+ === Shepherd ===
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'sphinx_tv'
4
+
5
+ s = SphinxTv.new
6
+ s.run
@@ -0,0 +1,155 @@
1
+ class MySQL < SphinxModule
2
+ def check(quiet = false)
3
+ result = true
4
+ unless mysql_installed?
5
+ result = false
6
+ puts "The MySQL server doesn't appear to be installed.'".red unless quiet
7
+ end
8
+ unless File.exists? "/etc/paths.d/mysql"
9
+ result = false
10
+ puts "MySQL paths.d file is not configured.".red unless quiet
11
+ end
12
+ unless File.exists? "/usr/lib/libmysqlclient.18.dylib"
13
+ result = false
14
+ puts "MySQL does not have a symbolic link set up for the libmysqlclient dynlib.".red unless quiet
15
+ end
16
+ unless File.exists? "/var/mysql/mysql.sock"
17
+ result = false
18
+ puts "MySQL socket does not exist.".red unless quiet
19
+ end
20
+
21
+ puts "MySQL is OK.".green if result
22
+ result
23
+ end
24
+
25
+ def configure
26
+ unless mysql_installed?
27
+ puts "\nOnce you have MySQL installed, you may return to this menu to setup root access\n"
28
+ end
29
+ exit = false
30
+ until exit do
31
+ choose do |menu|
32
+ menu.header = "\nMySQL Configuration".cyan
33
+ menu.prompt = "Select an option: "
34
+ if mysql_installed?
35
+ menu.choice("Setup root access") { set_mysql_root_access }
36
+ end
37
+ menu.choice("Done") {
38
+ exit = true
39
+ }
40
+ end
41
+ end
42
+ end
43
+
44
+ def download
45
+ url = "http://cdn.mysql.com/Downloads/MySQL-5.5/mysql-5.5.25a-osx10.6-x86_64.dmg"
46
+ puts "Downloading MySQL".cyan
47
+ puts url
48
+ result = Download.url(url, SphinxTv::download_path("mysql.dmg"))
49
+ end
50
+
51
+ def install
52
+ puts "Installing MySQL".cyan
53
+ unless check(true)
54
+ download
55
+ Download.mount(SphinxTv::download_path("mysql.dmg"), "mysql*") do |volume|
56
+ self.install_mysql_server volume
57
+ self.install_mysql_prefpane volume
58
+ self.install_mysql_startup volume
59
+ end
60
+ else
61
+ puts "MySQL is installed.".green
62
+ end
63
+ end
64
+
65
+ protected
66
+
67
+ def mysql_installed?
68
+ File.exists?("/usr/local/mysql/bin/mysql")
69
+ end
70
+
71
+ def install_mysql_server volume
72
+ puts "Installing MySQL Server".cyan
73
+ puts "This will configure the MySQL server. A graphical installer should open now."
74
+ files = Dir.glob File.join(volume, "mysql*")
75
+ if (files.size == 0)
76
+ puts "Could not find installer file in volume #{volume}".red
77
+ return
78
+ end
79
+ %x[open #{files[0]}]
80
+ ask "\nPress enter when the install is complete!".magenta
81
+
82
+ # Create the paths.d file
83
+ if mysql_installed?
84
+ puts "Creating global path file /etc/paths.d/mysql".cyan
85
+ %x[sudo #{SphinxTv::SUDO_PROMPT} bash -c 'echo "/usr/local/mysql/bin" > /etc/paths.d/mysql']
86
+ end
87
+
88
+ # Create the symbolic link to the client library
89
+ unless File.exists? "/usr/lib/libmysqlclient.18.dylib"
90
+ puts "Linking dynamic libraries".cyan
91
+ %x[sudo #{SphinxTv::SUDO_PROMPT} ln -s /usr/local/mysql/lib/libmysqlclient.18.dylib /usr/lib]
92
+ end
93
+
94
+ # Create the mysql socket
95
+ unless File.exists? "/tmp/mysql.sock"
96
+ puts "Creating link to mysql socket".cyan
97
+ %x[sudo #{SphinxTv::SUDO_PROMPT} mkdir -p /var/mysql]
98
+ %x[sudo #{SphinxTv::SUDO_PROMPT} ln -s /tmp/mysql.sock /var/mysql/]
99
+ end
100
+ end
101
+
102
+ def install_mysql_prefpane volume
103
+ puts "Installing MySQL Prefpane\n".cyan
104
+ puts "Select the option to install for all users of the computer"
105
+ puts "On the Prefpane, tick the option to start the MySQL Server on Startup"
106
+ puts "Click the button to start the MySQL Server"
107
+ files = Dir.glob File.join(volume, "MySQL.prefpane")
108
+ if (files.size == 0)
109
+ puts "Could not find Prefpane file in volume #{volume}".red
110
+ return
111
+ end
112
+ %x[open #{files[0]}]
113
+ ask "\nPress enter when the install is complete!".magenta
114
+ end
115
+
116
+ def install_mysql_startup volume
117
+ puts "Installing MySQL Startup".cyan
118
+ files = Dir.glob File.join(volume, "MySQLStartup*")
119
+ if (files.size == 0)
120
+ puts "Could not find startup installer file in volume #{volume}".red
121
+ return
122
+ end
123
+ %x[open #{files[0]}]
124
+ ask "\nPress enter when the install is complete!".magenta
125
+ end
126
+
127
+ def set_mysql_root_access
128
+ puts "This will allow you to set the root password for your database.".yellow
129
+ puts "It will give you a more secure SQL server on your network but if you lose".yellow
130
+ puts "the root password at any point, it will cause you pain.".yellow
131
+ response = ask("Set up root privileges? ") { |q| q.default = "No" }
132
+ return if !/Y/i.match(response)
133
+ old_root_pass = ask("Enter the current root password: ") { |q| q.echo = "*" }
134
+ root_pass = ask("Enter the new root password: ") { |q| q.echo = "*" }
135
+ root_pass_confirm = ask("Re-enter the new root password: ") { |q| q.echo = "*" }
136
+ if root_pass != root_pass_confirm
137
+ puts "Passwords do not match!".red
138
+ return
139
+ end
140
+ hostname = %x[hostname].strip
141
+ hostname_short = /^[^\.]+/.match(hostname)
142
+ mysql_commands = Array.new.tap do |c|
143
+ c << "DROP USER ''@'localhost';" if old_root_pass.empty?
144
+ c << "SET PASSWORD FOR 'root'@'localhost' = PASSWORD('#{root_pass}');"
145
+ c << "SET PASSWORD FOR 'root'@'127.0.0.1' = PASSWORD('#{root_pass}');"
146
+ c << "SET PASSWORD FOR 'root'@'#{hostname}' = PASSWORD('#{root_pass}');"
147
+ c << "SET PASSWORD FOR 'root'@'#{hostname_short}' = PASSWORD('#{root_pass}');"
148
+ end
149
+ password_param = old_root_pass.empty? ? "" : " --password=#{old_root_pass}"
150
+ mysql_commands.each do |sql|
151
+ puts sql
152
+ %x[/usr/local/mysql/bin/mysql -u root#{password_param} -e "#{sql}"]
153
+ end
154
+ end
155
+ end
@@ -0,0 +1,198 @@
1
+ require 'nokogiri'
2
+ require 'open-uri'
3
+
4
+ class MythTv < SphinxModule
5
+ def initialize
6
+ @config = {
7
+ :version => SphinxTv::VERSION
8
+ }
9
+ load_configuration
10
+ end
11
+
12
+ def check(quiet = false)
13
+ result = true
14
+ result = false unless mythtv_backend_installed?(quiet)
15
+ result = false unless mythtv_frontend_installed?(quiet)
16
+ unless @config[:database_password]
17
+ result = false
18
+ puts "Assuming no database is set up as the MythTV database password is not set.'".red unless quiet
19
+ end
20
+ unless File.exists? "/usr/local/bin/mythfilldatabase"
21
+ result = false
22
+ puts "No symbolic link to mythfilldatabase found".red unless quiet
23
+ end
24
+
25
+ puts "MythTV is OK.".green if result
26
+ result
27
+ end
28
+
29
+ def configure
30
+ exit = false
31
+ until exit do
32
+ choose do |menu|
33
+ menu.header = "\nMythTV Configuration".cyan
34
+ menu.prompt = "Select an option: "
35
+ if mythtv_backend_installed?
36
+ menu.choice("Setup database") { setup_database }
37
+ end
38
+ menu.choice("Create Storage Directories") { create_storage_directories }
39
+ if mythtv_backend_running?
40
+ menu.choice("MythTV Backend Control " + "Loaded".green) { mythtv_backend_control(false) }
41
+ else
42
+ menu.choice("MythTV Backend Control " + "Unloaded".red) { mythtv_backend_control(true) }
43
+ end
44
+ menu.choice("Done") {
45
+ exit = true
46
+ }
47
+ end
48
+ end
49
+ end
50
+
51
+ def download
52
+ doc = Nokogiri::HTML(open('http://sourceforge.net/projects/mythtvformacosx/files/'))
53
+
54
+ doc.css('tr.file a').each do |link|
55
+ if /\.dmg\/download$/.match(link[:href])
56
+ filename = nil
57
+ if /MythBack.*10\.7/.match(link[:href])
58
+ filename = "MythBackend.dmg"
59
+ elsif /MythFront.*10\.7/.match(link[:href])
60
+ filename = "MythFrontend.dmg"
61
+ end
62
+ if filename
63
+ puts "Downloading #{filename}".cyan
64
+ puts link[:href]
65
+ Download::url(link[:href], SphinxTv::download_path(filename))
66
+ end
67
+ end
68
+ end
69
+ end
70
+
71
+ def install
72
+ puts "Installing MythTV".cyan
73
+ unless check(true)
74
+ download
75
+ install_mythtv_backend unless mythtv_backend_installed?
76
+ install_mythtv_frontend unless mythtv_frontend_installed?
77
+
78
+ unless File.exists? "/usr/local/bin/mythfilldatabase"
79
+ puts "Creating link to mythfilldatabase".cyan
80
+ %x[sudo #{SphinxTv::SUDO_PROMPT} mkdir -p /usr/local/bin]
81
+ %x[sudo #{SphinxTv::SUDO_PROMPT} ln -s /Applications/MythBackend.app/Contents/MacOS/mythfilldatabase /usr/local/bin/mythfilldatabase]
82
+ end
83
+ end
84
+ end
85
+
86
+ private
87
+
88
+ def mythtv_backend_running?
89
+ result = %x[ps ax | grep MythBackend | grep -v grep]
90
+ return false if result.strip.empty?
91
+ true
92
+ end
93
+
94
+ def mythtv_backend_control(load)
95
+ %x[sudo #{SphinxTv::SUDO_PROMPT} launchctl #{load ? "load" : "unload"} -w /Library/LaunchDaemons/MythBackend.plist]
96
+ end
97
+
98
+ def mythtv_frontend_installed?(quiet = true)
99
+ result = true
100
+ unless File.exists? "/Applications/MythFrontend.app"
101
+ result = false
102
+ puts "MythFrontend application not installed.".red unless quiet
103
+ end
104
+ result
105
+ end
106
+
107
+ def mythtv_backend_installed?(quiet = true)
108
+ result = true
109
+ unless File.exists? "/Applications/MythBackend.app"
110
+ result = false
111
+ puts "MythBackend application not installed.".red unless quiet
112
+ end
113
+ unless File.exists? "/Library/LaunchDaemons/MythBackend.plist"
114
+ result = false
115
+ puts "MythBackend LaunchDaemon does not exist.".red unless quiet
116
+ end
117
+ result
118
+ end
119
+
120
+ def install_mythtv_backend
121
+ Download.mount(SphinxTv::download_path("MythBackend.dmg"), "MythBackend*") do |volume|
122
+ puts "Installing MythTVBackend".cyan
123
+ mythtv_files = ["MythBackend.app", "MythTv-Setup.app"]
124
+ mythtv_files.each do |file|
125
+ %x[cp -Rf #{volume}/#{file} /Applications 2>&1]
126
+ end
127
+ @username = Etc.getlogin
128
+ unless File.exists? "/var/log/mythtv"
129
+ puts "Creating MythTV log directory...".cyan
130
+ %x[sudo #{SphinxTv::SUDO_PROMPT} mkdir -p /var/log/mythtv]
131
+ %x[sudo #{SphinxTv::SUDO_PROMPT} chmod a+rwx /var/log/mythtv]
132
+ end
133
+
134
+ puts "Creating MythBackend LaunchDaemon file...".cyan
135
+ SphinxTv::copy_template(SphinxTv::resources_path("MythTv/MythBackend.plist.erb"), SphinxTv::cache_path("MythBackend.plist"), binding)
136
+ %x[sudo #{SphinxTv::SUDO_PROMPT} cp #{SphinxTv::cache_path("MythBackend.plist")} /Library/LaunchDaemons]
137
+ end
138
+ end
139
+
140
+ def install_mythtv_frontend
141
+ Download.mount(SphinxTv::download_path("MythFrontend.dmg"), "MythFrontend*") do |volume|
142
+ puts "Installing MythTVFrontend".cyan
143
+ mythtv_files = ["MythFrontend.app", "MythWelcome.app", "MythAVTest.app"]
144
+ mythtv_files.each do |file|
145
+ %x[cp -Rf #{volume}/#{file} /Applications 2>&1]
146
+ end
147
+ end
148
+ end
149
+
150
+ def setup_database
151
+ puts "This will create the MythTV database if it doesn't already exist and set appropriate privileges."
152
+ root_pass = ask("Enter your mysql root password: ") { |q| q.echo = "*" }
153
+ @config[:database_password] ||= SphinxTv::get_password_with_confirmation("Enter a password for the mythtv MySQL user: ")
154
+ return if @config[:database_password].nil?
155
+ save_configuration
156
+ hostname = %x[hostname].strip
157
+ hostname_short = /^[^\.]+/.match(hostname)
158
+ mysql_commands = Array.new.tap do |c|
159
+ c << "CREATE DATABASE IF NOT EXISTS mythconverg;"
160
+ c << "GRANT ALL ON mythconverg.* TO mythtv@localhost IDENTIFIED BY '#{@config[:database_password]}';"
161
+ c << "GRANT ALL ON mythconverg.* TO mythtv@#{hostname} IDENTIFIED BY '#{@config[:database_password]}';"
162
+ c << "FLUSH PRIVILEGES;"
163
+ c << "GRANT CREATE TEMPORARY TABLES ON mythconverg.* TO mythtv@localhost IDENTIFIED BY '#{@config[:database_password]}';"
164
+ c << "GRANT CREATE TEMPORARY TABLES ON mythconverg.* TO mythtv@#{hostname} IDENTIFIED BY '#{@config[:database_password]}';"
165
+ c << "FLUSH PRIVILEGES;"
166
+ c << "ALTER DATABASE mythconverg DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;"
167
+ end
168
+ password_param = root_pass.empty? ? "" : " --password=#{root_pass}"
169
+ mysql_commands.each do |sql|
170
+ puts sql
171
+ %x[/usr/local/mysql/bin/mysql -u root#{password_param} -e "#{sql}"]
172
+ end
173
+
174
+ puts "Creating mysql.txt in /etc/mythtv".cyan
175
+ %x[sudo #{SphinxTv::SUDO_PROMPT} mkdir -p /etc/mythtv]
176
+ %x[sudo #{SphinxTv::SUDO_PROMPT} chmod a+rwx /etc/mythtv]
177
+ outfile = File.open("/etc/mythtv/mysql.txt", "w")
178
+ outfile.puts("DBHostName=#{hostname}")
179
+ outfile.puts("DBUserName=mythtv")
180
+ outfile.puts("DBPassword=#{@config[:database_password]}")
181
+ outfile.puts("DBName=mythconverg")
182
+ outfile.puts("DBType=QMYSQL3")
183
+ outfile.close
184
+ %x[chmod o-w /etc/mythtv/mysql.txt]
185
+ end
186
+
187
+ def create_storage_directories
188
+ storage_root = ask("Enter the storage directory root: ") { |q| q.default = "/Volumes/MythTV" }
189
+ directories = ['Backups', 'Banners', 'Coverart', 'Fanart', 'LiveTV', 'Recordings', 'Screenshots', 'Trailers']
190
+ directories.each do |directory|
191
+ full_dir = File.join(storage_root, directory)
192
+ puts full_dir
193
+ %x[sudo #{SphinxTv::SUDO_PROMPT} mkdir -p #{full_dir}]
194
+ end
195
+ %x[sudo #{SphinxTv::SUDO_PROMPT} chown -Rf #{Etc.getlogin} #{storage_root}]
196
+ %x[sudo #{SphinxTv::SUDO_PROMPT} chmod -Rf ugo+rwx #{storage_root}]
197
+ end
198
+ end
@@ -0,0 +1,95 @@
1
+ class Shepherd < SphinxModule
2
+ def initialize
3
+ @config = {
4
+ :version => SphinxTv::VERSION
5
+ }
6
+ load_configuration
7
+ end
8
+
9
+ def check(quiet = false)
10
+ result = true
11
+
12
+ unless File.exists? "/usr/local/share/xmltv"
13
+ result = false
14
+ puts "XMLTV is not installed.".red unless quiet
15
+ end
16
+
17
+ unless File.exists? File.join(Etc.getpwuid.dir, ".shepherd")
18
+ result = false
19
+ puts "Shepherd is not installed.".red unless quiet
20
+ end
21
+
22
+ puts "Shepherd is OK.".green if result
23
+ result
24
+ end
25
+
26
+ def download
27
+ doc = Nokogiri::HTML(open('http://sourceforge.net/projects/xmltv/files/xmltv/0.5.63/'))
28
+
29
+ url = "http://www.whuffy.com/shepherd/shepherd"
30
+ puts "Downloading Shepherd".cyan
31
+ puts url
32
+ result = Download.url(url, SphinxTv::download_path("shepherd"))
33
+
34
+ doc.css('tr.file a').each do |link|
35
+ if /\.tar\.bz2\/download$/.match(link[:href])
36
+ filename = nil
37
+ filename = "xmltv.tar.bz2" if /xmltv/.match(link[:href])
38
+ if filename
39
+ puts "Downloading #{filename}".cyan
40
+ puts link[:href]
41
+ Download::url(link[:href], SphinxTv::download_path(filename))
42
+ end
43
+ end
44
+ end
45
+ end
46
+
47
+ def install
48
+ unless check(true)
49
+ download
50
+ install_perl_prerequisites
51
+ install_xmltv unless File.exists? "/usr/local/share/xmltv"
52
+
53
+ unless File.exists? "/Library/Perl/5.12/Shepherd"
54
+ puts "Creating symlink for Shepherd/MythTV perl library...".cyan
55
+ home_dir = Etc.getpwuid.dir
56
+ %x[sudo #{SphinxTv::SUDO_PROMPT} ln -s #{home_dir}/.shepherd/references/Shepherd /Library/Perl/5.12]
57
+ end
58
+ puts "Installing Shepherd".cyan
59
+ system("perl #{SphinxTv::download_path("shepherd")} --configure")
60
+ end
61
+ end
62
+
63
+ def install_perl_prerequisites
64
+
65
+ Cpan::create_config_file
66
+
67
+ shepherd_mandatory_perl_modules = "YAML XML::Twig Algorithm::Diff Compress::Zlib Cwd Data::Dumper Date::Manip Getopt::Long \
68
+ List::Compare LWP::UserAgent POSIX Digest::SHA1"
69
+
70
+ shepherd_optional_perl_modules = "DateTime::Format::Strptime File::Basename File::Path HTML::Entities \
71
+ HTML::TokeParser HTML::TreeBuilder IO::File Storable Time::HiRes XML::DOM \
72
+ XML::DOM::NodeList XML::Simple Storable HTTP::Cookies File::Basename \
73
+ LWP::ConnCache Digest::MD5 Archive::Zip IO::String \
74
+ DateTime::Format::Strptime \
75
+ HTTP::Cache::Transparent Crypt::SSLeay DBD::mysql "
76
+
77
+ perl_modules = shepherd_mandatory_perl_modules.split(" ") + shepherd_optional_perl_modules.split(" ")
78
+ puts "Installing required perl modules...".cyan
79
+ Cpan::install(perl_modules)
80
+ end
81
+
82
+ def install_xmltv
83
+ puts "Extracting XMLTV...".cyan
84
+ %x[tar -jxf #{SphinxTv::download_path("xmltv.tar.bz2")} -C #{SphinxTv::cache_path}]
85
+ puts "Compiling XMLTV...".cyan
86
+ commands = Array.new.tap do |c|
87
+ c << "cd #{Sphinx.cache_path}/xmltv*"
88
+ c << "perl Makefile.PL"
89
+ c << "make"
90
+ c << "make test"
91
+ c << "sudo #{SphinxTv::SUDO_PROMPT} make install"
92
+ end
93
+ puts %x[#{commands.join(";")}]
94
+ end
95
+ end
@@ -0,0 +1,55 @@
1
+ class SphinxModule
2
+ def initialize
3
+ @config = {
4
+ :version => SphinxTv::VERSION
5
+ }
6
+ end
7
+
8
+ def load_configuration
9
+ config_file = File.join(SphinxTv::CONFIG_DIRECTORY, "#{self.class.to_s.downcase}.yml")
10
+ unless File.exists? config_file
11
+ return false
12
+ end
13
+ c = YAML::load_file config_file
14
+ @config.merge! c
15
+ return true
16
+ end
17
+
18
+ def save_configuration
19
+ def save_configuration
20
+ Dir.mkdir(SphinxTv::CONFIG_DIRECTORY) unless File.exists? SphinxTv::CONFIG_DIRECTORY
21
+ File.open(File.join(SphinxTv::CONFIG_DIRECTORY, "#{self.class.to_s.downcase}.yml"), "w") do |file|
22
+ file.write @config.to_yaml
23
+ end
24
+ end
25
+ end
26
+
27
+ def check(quiet = false)
28
+ return true
29
+ end
30
+
31
+ def setup
32
+ puts "#{self.class.to_s} does not have a setup menu.".red
33
+ return false
34
+ end
35
+
36
+ def configure
37
+ puts "#{self.class.to_s} does not have a configuration menu.".red
38
+ return false
39
+ end
40
+
41
+ def download
42
+ end
43
+
44
+ def install
45
+ puts "#{self.class.to_s} does not have an install function.".red
46
+ end
47
+
48
+ def uninstall
49
+ puts "#{self.class.to_s} does not have an uninstall function.".red
50
+ end
51
+
52
+ def update
53
+ puts "#{self.class.to_s} does not have an update function.".red
54
+ end
55
+ end
@@ -0,0 +1,233 @@
1
+ require "colorize"
2
+ require "etc"
3
+ require "yaml"
4
+ require 'optparse'
5
+ require "highline/import"
6
+ require "modules/sphinx_module"
7
+ require "sphinx_tv/download"
8
+ require "sphinx_tv/cpan"
9
+ require "erb"
10
+ require "version"
11
+
12
+ class SphinxTv
13
+ SUDO_PROMPT = "-p \"#{"Your administrator password is required to peform this step: ".yellow}\""
14
+ CONFIG_DIRECTORY = File.join(Etc.getpwuid.dir, ".sphinx_tv")
15
+ MODULES = ["MySQL", "MythTv", "Shepherd"]
16
+ MODULE_DEFAULTS = {
17
+ "MySQL" => {
18
+ :active => true,
19
+ :setup => false,
20
+ :configure => true,
21
+ },
22
+ "MythTv" => {
23
+ :active => true,
24
+ :setup => false,
25
+ :configure => true,
26
+ :depends => ["MySQL"],
27
+ },
28
+ "Shepherd" => {
29
+ :active => false,
30
+ :setup => false,
31
+ :configure => true,
32
+ :depends => ["MythTv"],
33
+ },
34
+ }
35
+
36
+ def initialize
37
+ @config = {
38
+ :version => VERSION,
39
+ :modules => MODULE_DEFAULTS
40
+ }
41
+ end
42
+
43
+ def self.root_directory
44
+ File.expand_path '../..', __FILE__
45
+ end
46
+
47
+ def self.resources_path(file = "")
48
+ File.join(root_directory, "resources", file)
49
+ end
50
+
51
+ def self.download_path(file = "")
52
+ download_directory = File.join(CONFIG_DIRECTORY, "downloads")
53
+ Dir.mkdir(download_directory) unless File.exists? download_directory
54
+ return File.join(download_directory, file)
55
+ end
56
+
57
+ def self.cache_path(file = "")
58
+ cache_directory = File.join(CONFIG_DIRECTORY, "cache")
59
+ Dir.mkdir(cache_directory) unless File.exists? cache_directory
60
+ return File.join(cache_directory, file)
61
+ end
62
+
63
+ def self.copy_template(source, dest, b)
64
+ t = ERB.new File.new(source).read, nil, "%"
65
+ outfile = File.open(dest, "w")
66
+ outfile.write(t.result(b || binding))
67
+ outfile.close
68
+ end
69
+
70
+ def self.get_password_with_confirmation(prompt = nil)
71
+ pass = ask(prompt || "Enter a password: ") { |q| q.echo = "*" }
72
+ pass_confirm = ask("Re-enter the password: ") { |q| q.echo = "*" }
73
+ if pass != pass_confirm
74
+ puts "Passwords do not match!".red
75
+ return nil
76
+ end
77
+ pass
78
+ end
79
+
80
+ def load_configuration
81
+ config_file = File.join(CONFIG_DIRECTORY, "sphinx.yml")
82
+ unless File.exists? config_file
83
+ puts "Configuration file does not exist. Using defaults.".yellow
84
+ return false
85
+ end
86
+ c = YAML::load_file config_file
87
+ @config.merge! c
88
+ return true
89
+ end
90
+
91
+ def save_configuration
92
+ Dir.mkdir(CONFIG_DIRECTORY) unless File.exists? CONFIG_DIRECTORY
93
+ File.open(File.join(CONFIG_DIRECTORY, "sphinx.yml"), "w") do |file|
94
+ file.write @config.to_yaml
95
+ end
96
+ end
97
+
98
+ def module_select
99
+ exit = false
100
+ until exit do
101
+ choose do |menu|
102
+ menu.header = "\nSelect Modules".cyan
103
+ menu.prompt = "Select optional modules to toggle: "
104
+ MODULES.each do |m|
105
+ options = @config[:modules][m]
106
+ menu.choice("#{m}: " + (options[:active] ? "On".green : "Off".red)) do |choice|
107
+ m = /^([^:]*)/.match(choice)[0]
108
+ toggle_module m
109
+ end
110
+ end
111
+ menu.choice(:done) {
112
+ exit = true
113
+ }
114
+ end
115
+ end
116
+ end
117
+
118
+ def toggle_module(m)
119
+ options = @config[:modules][m]
120
+
121
+ options[:active] = !options[:active]
122
+ if (options[:active])
123
+ if (options[:depends])
124
+ options[:depends].each do |d|
125
+ toggle_module d unless @config[:modules][d][:active]
126
+ end
127
+ end
128
+ end
129
+ end
130
+
131
+ def module_menu(action, title)
132
+ exit = false
133
+ until exit do
134
+ choose do |menu|
135
+ menu.header = "\n#{title}".cyan
136
+ menu.prompt = "Select a module: "
137
+ MODULES.each do |m|
138
+ options = @config[:modules][m]
139
+ if (options[action])
140
+ menu.choice(m) do |m|
141
+ require File.join("modules", m)
142
+ mod = eval("#{m}.new")
143
+ mod.send(action)
144
+ end
145
+ end
146
+ end
147
+ menu.choice(:done) {
148
+ exit = true
149
+ }
150
+ end
151
+ end
152
+ end
153
+
154
+ def run_selected_modules &block
155
+ MODULES.each do |m|
156
+ if @config[:modules][m][:active]
157
+ require File.join("modules", m)
158
+ mod = eval("#{m}.new")
159
+ yield mod
160
+ end
161
+ end
162
+ end
163
+
164
+ def setup
165
+ exit = false
166
+ until exit do
167
+ choose do |menu|
168
+ menu.header = "\nSphinx Installer".cyan
169
+ menu.prompt = "Select an option: "
170
+
171
+ menu.choice("Select Modules") { module_select }
172
+ menu.choice("Setup Modules") { module_menu(:setup, "Module Setup") }
173
+ menu.choice("Save Configuration") {
174
+ save_configuration
175
+ puts "Configuration Saved.".green
176
+ exit = true
177
+ }
178
+ menu.choice("Cancel") {
179
+ puts "Exiting without saving configuration.".red
180
+ exit = true
181
+ }
182
+ end
183
+ end
184
+ end
185
+
186
+ def run
187
+ @no_config = load_configuration
188
+
189
+ action = nil
190
+
191
+ optparse = OptionParser.new do |opts|
192
+ # Set a banner, displayed at the top
193
+ # of the help screen.
194
+ opts.banner = "Usage: sphinx [options] action"
195
+
196
+ # This displays the help screen, all programs are
197
+ # assumed to have this option.
198
+ opts.on('-h', '--help', 'Display this screen') do
199
+ puts opts
200
+ exit
201
+ end
202
+ end
203
+
204
+ # Parse the command-line. Remember there are two forms
205
+ # of the parse method. The 'parse' method simply parses
206
+ # ARGV, while the 'parse!' method parses ARGV and removes
207
+ # any options found there, as well as any parameters for
208
+ # the options. What's left is the list of files to resize.
209
+ optparse.parse!
210
+ # Set the selected modules unless it was overridden
211
+
212
+ action = ARGV[0]
213
+ case action
214
+ when "setup"
215
+ setup
216
+ when "configure"
217
+ module_menu(:configure, "Configure Modules")
218
+ when "check"
219
+ run_selected_modules { |m| m.check }
220
+ when "install"
221
+ run_selected_modules { |m| m.install }
222
+ when "uninstall"
223
+ run_selected_modules { |m| m.uninstall }
224
+ when "download"
225
+ run_selected_modules { |m| m.download }
226
+ when "update"
227
+ run_selected_modules { |m| m.update }
228
+ else
229
+ puts "Use the --help switch if you need a list of valid actions"
230
+ end
231
+ end
232
+
233
+ end
@@ -0,0 +1,26 @@
1
+ require 'expect'
2
+ require 'pty'
3
+
4
+ class Cpan
5
+ def self.install modules
6
+ modules.each do |m|
7
+ result = %x[perl -M#{m} -e 1 2>&1].strip
8
+ if result.empty?
9
+ puts "Module '#{m}' is installed".green
10
+ else
11
+ puts "Installing perl module: #{m}".cyan
12
+ system("sudo #{Sphinx::SUDO_PROMPT} cpan -j #{Sphinx::cache_path("MyConfig.pm")} -f -i #{m}")
13
+ end
14
+ end
15
+ end
16
+
17
+ def self.create_config_file
18
+ unless File.exists? Sphinx::cache_path("MyConfig.pm")
19
+ puts "Creating cpan config...".cyan
20
+
21
+ @cache_dir = Sphinx::cache_path
22
+ Sphinx::copy_template(Sphinx::resources_path("MyConfig.pm.erb"), Sphinx::cache_path("MyConfig.pm"), binding)
23
+ end
24
+ end
25
+
26
+ end
@@ -0,0 +1,84 @@
1
+ module Download
2
+ def self.url file_url, filename
3
+ Dir::mkdir("cache") unless FileTest::directory?("cache")
4
+
5
+ z_option = (File.exists?(filename) ? " -z #{filename}" : "")
6
+ result = %x[curl -L -w "%{http_code} %{url_effective}"#{z_option} -o #{filename}.download #{file_url}]
7
+ if /304/.match(result)
8
+ puts "Up to date: ".green + filename
9
+ return false
10
+ else
11
+ if /200/.match(result)
12
+ begin
13
+ File.rename "#{filename}.download", filename
14
+ rescue
15
+ end
16
+ puts "Updated: ".yellow + filename
17
+ return true
18
+ else
19
+ File.delete "#{filename}.download" if File.exists? "#{filename}.download"
20
+ puts "File not found: ".red + file_url
21
+ return false
22
+ end
23
+ end
24
+ end
25
+
26
+ def self.open_volume(filename, volume_search, &block)
27
+ if File.exists?(filename)
28
+ result = %x[open #{filename}]
29
+ volume = find_mounted_volume volume_search
30
+ if volume.size == 0
31
+ puts "Could not find #{filename} volume".red
32
+ else
33
+ yield volume
34
+ end
35
+ else
36
+ puts "File hasn't been downloaded #{filename}".red
37
+ end
38
+ end
39
+
40
+ def self.exists?
41
+ if File.exists?(filename)
42
+ return true
43
+ else
44
+ puts "File hasn't been downloaded #{filename}".red
45
+ return false
46
+ end
47
+ end
48
+
49
+ def self.mount(filename, volume_search, &block)
50
+ if File.exists?(filename)
51
+ result = %x[open #{filename}]
52
+ volume = self.find_mounted_volume volume_search
53
+ if volume.size == 0
54
+ puts "Could not find #{filename} volume".red
55
+ else
56
+ yield volume
57
+ end
58
+ else
59
+ yield filename
60
+ end
61
+ end
62
+
63
+ def self.find_mounted_volume search
64
+ not_found = true
65
+ retries = 0
66
+ while not_found && retries < 30
67
+ volumes = Dir.glob "/Volumes/#{search}"
68
+ if volumes.size == 0
69
+ retries += 1
70
+ sleep 2
71
+ elsif volumes.size > 1
72
+ puts "Too many similar disk images mounted, please unmount all but latest version".red
73
+ puts "Here are the mounted matching images:"
74
+ volumes.each do |volume|
75
+ puts volume
76
+ end
77
+ return ""
78
+ else
79
+ return volumes[0]
80
+ end
81
+ end
82
+ return ""
83
+ end
84
+ end
@@ -0,0 +1,3 @@
1
+ module SphinxTv
2
+ VERSION = "0.9.1"
3
+ end
@@ -0,0 +1,49 @@
1
+ $CPAN::Config = {
2
+ 'auto_commit' => q[0],
3
+ 'build_cache' => q[100],
4
+ 'build_dir' => q[<%= @cache_dir %>/.cpan/build],
5
+ 'build_dir_reuse' => q[0],
6
+ 'build_requires_install_policy' => q[yes],
7
+ 'cache_metadata' => q[1],
8
+ 'check_sigs' => q[0],
9
+ 'commandnumber_in_prompt' => q[1],
10
+ 'connect_to_internet_ok' => q[1],
11
+ 'cpan_home' => q[<%= @cache_dir %>/.cpan],
12
+ 'ftp_passive' => q[1],
13
+ 'ftp_proxy' => q[],
14
+ 'getcwd' => q[cwd],
15
+ 'halt_on_failure' => q[0],
16
+ 'http_proxy' => q[],
17
+ 'inactivity_timeout' => q[0],
18
+ 'index_expire' => q[1],
19
+ 'inhibit_startup_message' => q[0],
20
+ 'keep_source_where' => q[<%= @cache_dir %>/.cpan/sources],
21
+ 'load_module_verbosity' => q[none],
22
+ 'make_arg' => q[],
23
+ 'make_install_arg' => q[],
24
+ 'make_install_make_command' => q[],
25
+ 'makepl_arg' => q[],
26
+ 'mbuild_arg' => q[],
27
+ 'mbuild_install_arg' => q[],
28
+ 'mbuild_install_build_command' => q[./Build],
29
+ 'mbuildpl_arg' => q[],
30
+ 'no_proxy' => q[],
31
+ 'pager' => q[/usr/bin/less],
32
+ 'perl5lib_verbosity' => q[none],
33
+ 'prefer_installer' => q[MB],
34
+ 'prefs_dir' => q[<%= @cache_dir %>/.cpan/prefs],
35
+ 'prerequisites_policy' => q[follow],
36
+ 'scan_cache' => q[atstart],
37
+ 'shell' => q[/bin/bash],
38
+ 'show_upload_date' => q[0],
39
+ 'tar_verbosity' => q[none],
40
+ 'term_is_latin' => q[1],
41
+ 'term_ornaments' => q[1],
42
+ 'trust_test_report_history' => q[0],
43
+ 'urllist' => [],
44
+ 'use_sqlite' => q[0],
45
+ 'version_timeout' => q[15],
46
+ 'yaml_load_code' => q[0],
47
+ };
48
+ 1;
49
+ __END__
@@ -0,0 +1,19 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
+ <plist version="1.0">
4
+ <dict>
5
+ <key>KeepAlive</key>
6
+ <true/>
7
+ <key>Label</key>
8
+ <string>MythBackend</string>
9
+ <key>ProgramArguments</key>
10
+ <array>
11
+ <string>/Applications/MythBackend.app/Contents/MacOS/MythBackend</string>
12
+ <string>--logpath</string>
13
+ <string>/var/log/mythtv</string>
14
+ </array>
15
+ <key>UserName</key>
16
+ <string><%= @username %></string>
17
+ </dict>
18
+ </plist>
19
+
@@ -0,0 +1,23 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/sphinx_tv/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["David Monagle"]
6
+ gem.email = ["david.monagle@intrica.com.au"]
7
+ gem.date = '2012-07-15'
8
+ gem.summary = "SphinxTV is an installer/configurator for MythTV (and others) for OSX"
9
+ gem.description = "SphinxTV is an installer/configurator for MythTV (and others) for OSX"
10
+ gem.homepage = ""
11
+
12
+ gem.files = `git ls-files`.split($\)
13
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
14
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
15
+ gem.name = "sphinx_tv"
16
+ gem.require_paths = ["lib"]
17
+ gem.version = SphinxTv::VERSION
18
+ gem.homepage =
19
+ 'http://sphinxtv.intrica.com.au'
20
+ gem.add_dependency 'colorize'
21
+ gem.add_dependency 'highline'
22
+ gem.add_dependency 'nokogiri'
23
+ end
metadata ADDED
@@ -0,0 +1,111 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sphinx_tv
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.9.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - David Monagle
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-07-15 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: colorize
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: highline
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: nokogiri
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ description: SphinxTV is an installer/configurator for MythTV (and others) for OSX
63
+ email:
64
+ - david.monagle@intrica.com.au
65
+ executables:
66
+ - sphinx_tv
67
+ extensions: []
68
+ extra_rdoc_files: []
69
+ files:
70
+ - .gitignore
71
+ - Gemfile
72
+ - MIT-LICENSE
73
+ - README.rdoc
74
+ - Rakefile
75
+ - bin/sphinx_tv
76
+ - lib/modules/mysql.rb
77
+ - lib/modules/mythtv.rb
78
+ - lib/modules/shepherd.rb
79
+ - lib/modules/sphinx_module.rb
80
+ - lib/sphinx_tv.rb
81
+ - lib/sphinx_tv/cpan.rb
82
+ - lib/sphinx_tv/download.rb
83
+ - lib/sphinx_tv/version.rb
84
+ - resources/MyConfig.pm.erb
85
+ - resources/MythTv/MythBackend.plist.erb
86
+ - sphinx_tv.gemspec
87
+ homepage: http://sphinxtv.intrica.com.au
88
+ licenses: []
89
+ post_install_message:
90
+ rdoc_options: []
91
+ require_paths:
92
+ - lib
93
+ required_ruby_version: !ruby/object:Gem::Requirement
94
+ none: false
95
+ requirements:
96
+ - - ! '>='
97
+ - !ruby/object:Gem::Version
98
+ version: '0'
99
+ required_rubygems_version: !ruby/object:Gem::Requirement
100
+ none: false
101
+ requirements:
102
+ - - ! '>='
103
+ - !ruby/object:Gem::Version
104
+ version: '0'
105
+ requirements: []
106
+ rubyforge_project:
107
+ rubygems_version: 1.8.24
108
+ signing_key:
109
+ specification_version: 3
110
+ summary: SphinxTV is an installer/configurator for MythTV (and others) for OSX
111
+ test_files: []