sbcp 0.1.4

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.
@@ -0,0 +1,9 @@
1
+ ---
2
+ starbound_directory: ~
3
+ backup_directory: ~
4
+ backup_history: 90
5
+ backup_schedule: 1
6
+ log_directory: ~
7
+ log_history: 90
8
+ log_style: daily
9
+ restart_schedule: 4
@@ -0,0 +1,38 @@
1
+ require 'sinatra/base'
2
+ require 'sinatra/contrib'
3
+ require 'sinatra/flash'
4
+ require 'securerandom'
5
+ require 'fileutils'
6
+ require 'logger'
7
+ require 'yaml'
8
+
9
+ require 'sbcp/daemon'
10
+
11
+ module SBCP
12
+ class Panel < Sinatra::Base
13
+ register Sinatra::Contrib
14
+ register Sinatra::Flash
15
+ config = YAML.load_file(File.expand_path('../../config.yml', __FILE__))
16
+ configure do
17
+ set :environment, :development
18
+ set :server, 'thin'
19
+ set :threaded, true
20
+ set :bind, '0.0.0.0'
21
+ use Rack::Session::Cookie,
22
+ :key => 'rack.session',
23
+ :path => '/',
24
+ :expire_after => 3600 # In seconds
25
+ end
26
+ Process.daemon unless settings.environment == :development
27
+ # First thing's first, we create a file containing the current process pid.
28
+ # This is used later when we need to grace or force quit SBCP.
29
+ # Note to self: Don't forget to close and unlink
30
+ pid_file = Tempfile.new('sbcp_panel-pid')
31
+ pid_file.write(Process.pid.to_s)
32
+ # Require any present plugins
33
+ plugins_directory = "#{config['starbound_directory']}/sbcp/plugins"
34
+ $LOAD_PATH.unshift(plugins_directory)
35
+ Dir[File.join(plugins_directory, '*.rb')].each {|file| require File.basename(file) }
36
+ run! if app_file == $0
37
+ end
38
+ end
@@ -0,0 +1,59 @@
1
+ require 'securerandom'
2
+ require 'fileutils'
3
+ require 'rsync'
4
+ require 'yaml'
5
+ module SBCP
6
+ class Backup
7
+
8
+ # This methods backs up various files.
9
+ # It supports 3 types of backups.
10
+ # Starbound: backs up world and related log files.
11
+ # SBCP: backs up the SBCP database and related log files.
12
+ # Full: backs up both Starbound and SBCP data.
13
+ # Defaults to Starbound.
14
+
15
+ def self.create_backup(kind=:starbound)
16
+ config = YAML.load_file(File.expand_path('../../../config.yml', __FILE__))
17
+ return('Backups disabled.') if config['backup_history'] == 'none'
18
+ case kind
19
+ when :starbound
20
+ root = config['starbound_directory']
21
+ world_files = "#{root}/giraffe_storage/universe/*.world"
22
+ latest_files_directory = File.expand_path('../../../backup', __FILE__)
23
+ backup_directory = config['backup_directory']
24
+ backup_name = "#{Time.now.strftime("%m-%d-%Y-%H-%M-%S")}-starbound_backup.tar.bz2"
25
+ changed_files = Array.new
26
+ Rsync.run(world_files, latest_files_directory, ['-a']) do |result|
27
+ if result.success?
28
+ unless result.changes.length == 0
29
+ result.changes.each do |change|
30
+ changed_files.push("#{root}/giraffe_storage/universe/#{change.filename}")
31
+ end
32
+ FileUtils.cd('/tmp') do
33
+ random_name = SecureRandom.urlsafe_base64
34
+ FileUtils.mkdir random_name
35
+ FileUtils.cp changed_files, random_name
36
+ system("tar cjpf #{backup_name} #{random_name}")
37
+ FileUtils.mv backup_name, backup_directory # Move the created backup to the backup directory
38
+ FileUtils.rm_r random_name # Remove the folder after we're done with it
39
+ end
40
+ end
41
+ end
42
+ end
43
+ when :sbcp
44
+ abort("Unimplemented.")
45
+ when :full
46
+ # This should take a complete backup of Starbound and SBCP.
47
+ # Currently only supports Starbound.
48
+ root = config['starbound_directory']
49
+ giraffe_directory = "#{root}/giraffe_storage"
50
+ backup_directory = config['backup_directory']
51
+ backup_name = "#{Time.now.strftime("%m-%d-%Y-%H-%M-%S")}-full_backup.tar.bz2"
52
+ FileUtils.cd('/tmp') do
53
+ system("tar cjpf #{backup_name} #{giraffe_directory} > /dev/null 2>&1")
54
+ FileUtils.mv backup_name, backup_directory # Move the created backup to the backup directory
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,115 @@
1
+ require 'yaml'
2
+ require 'logger'
3
+ require 'tempfile'
4
+ require 'rufus-scheduler'
5
+ require 'celluloid/current'
6
+ require_relative 'starbound'
7
+ require_relative 'backup'
8
+ require_relative 'parser'
9
+ require_relative 'logs'
10
+ module SBCP
11
+ class Daemon
12
+ def initialize
13
+ # Loads the config into an instance variable for use in GUI mode
14
+ @config = YAML.load_file(File.expand_path('../../../config.yml', __FILE__))
15
+ end
16
+
17
+ def self.run
18
+ # This method is used when starting SBCP from the sbcp executable.
19
+ # It's primary purpose is to enable running SBCP in CLI mode.
20
+ # The Sinatra web server is completely bypassed in this case.
21
+
22
+ # Check to see if the daemon is already running.
23
+ # This can happen after a server shut down and it's taking a backup or handling log files.
24
+ abort("SBCP is currently running. If you recently shut down Starbound, please allow a few minutes.\nSBCP is likely handling backup or log file operations. Interruption could lead to data loss.") if not Dir.glob('/tmp/sbcp_daemon-pid*').empty?
25
+
26
+ # Create a file containing the pid of this process so it can be aborted if neccesary.
27
+ pid_file = Tempfile.new('sbcp_daemon-pid')
28
+ pid = Process.pid.to_s
29
+ pid_file.write(pid)
30
+
31
+ # Next, we perform a check to ensure that the server isn't already running.
32
+ abort('Starbound is already running.') if not `pidof starbound_server`.empty?
33
+
34
+ # We should load the config values into a local variable.
35
+ # Since CLI mode does not create an instance of the daemon method,
36
+ # we have to set things up separately so they can be used.
37
+ config = YAML.load_file(File.expand_path('../../../config.yml', __FILE__))
38
+
39
+ # Quick check for invalid config values.
40
+ abort('Please run sbcp --setup first.') if config['starbound_directory'].nil?
41
+ abort('Error - Invalid starbound directory') if not Dir.exist?(config['starbound_directory']) && Dir.exist?(config['starbound_directory'] + '/giraffe_storage')
42
+ abort('Error - Invalid backup directory') if not Dir.exist?(config['backup_directory'])
43
+ abort('Error - Invalid backup schedule') if not ['hourly', 2, 3, 4, 6, 8, 12, 'daily', 'restart'].include? config['backup_schedule']
44
+ abort('Error - Invalid backup history') if not config['backup_history'] >= 1 || config['backup_history'] == 'none'
45
+ abort('Error - Invalid log directory') if not Dir.exist?(config['log_directory'])
46
+ abort('Error - Invalid log history') if not config['log_history'].is_a?(Integer) && config['log_history'] >= 1
47
+ abort('Error - Invalid log style') if not ['daily', 'restart'].include? config['log_style']
48
+ abort('Error - Invalid restart schedule') if not ['none', 'hourly', 2, 3, 4, 6, 8, 12, 'daily'].include? config['restart_schedule']
49
+
50
+ # Require any present plugins
51
+ plugins_directory = "#{config['starbound_directory']}/sbcp/plugins"
52
+ $LOAD_PATH.unshift(plugins_directory)
53
+ Dir[File.join(plugins_directory, '*.rb')].each {|file| require File.basename(file) }
54
+
55
+ # We detach and daemonize this process to prevent a block in the calling executable.
56
+ Process.daemon(nochdir=true)
57
+
58
+ # We create an infinite loop so we can easily restart the server.
59
+ loop do
60
+ # Next we invoke the Starbound class to create an instance of the server.
61
+ # This class will spawn a sub-process containing the server.
62
+ server = SBCP::Starbound.new
63
+ server.start
64
+
65
+ # We wait for the server process to conclude before moving on.
66
+ # This normally occurs after a shutdown, crash, or restart.
67
+ # The daemon process will do nothing until the server closes.
68
+
69
+ # Once the server has finished running, we'll want to age our logfiles.
70
+ # We'll also take backups here if they've been set to behave that way.
71
+ # There's probably a better way than sending config values as arguements, but...
72
+ SBCP::Logs.age(config['log_directory'], config['log_history']) if config['log_style'] == 'restart'
73
+ SBCP::Backup.create_backup if config['backup_schedule'] == 'restart'
74
+
75
+ # Now we must determine if the server was closed intentionally.
76
+ # If the server was shut down on purpose, we don't want to automatically restart it.
77
+ # If the shutdown file exists, it was an intentional shutdown.
78
+ # We break the loop which ends the method and closes the Daemon process.
79
+ break if not Dir.glob('/tmp/sb-shutdown*').empty?
80
+
81
+ # This delay is needed or some commands don't report back correctly
82
+ sleep 5
83
+ end
84
+ ensure
85
+ unless pid_file.nil?
86
+ pid_file.close
87
+ pid_file.unlink
88
+ end
89
+ end
90
+
91
+ private
92
+
93
+ # These methods are used only in GUI mode for managing the server.
94
+ # In CLI mode, server management is handled via CLI options.
95
+
96
+ def start_server
97
+ # TODO: Create code that spawns a sub-process containing the server
98
+ end
99
+
100
+ def restart_server
101
+ end
102
+
103
+ def force_restart_server
104
+ end
105
+
106
+ def stop_server
107
+ end
108
+
109
+ def force_stop_server
110
+ end
111
+
112
+ def server_status
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,11 @@
1
+ module SBCP
2
+ class Logs
3
+ def self.age(directory, days)
4
+ Dir.glob("#{directory}/*.log").each do |log|
5
+ if ((Time.now - File.stat(log).mtime).to_i / 86400.0) >= days
6
+ File.delete(log)
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,10 @@
1
+ module SBCP
2
+ class Parser
3
+ include Celluloid
4
+
5
+ def parse(line)
6
+ # This method will eventually be responsible for parsing server output and running other methods based on that output.
7
+ # One example would be to emulate in-game server commands.
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,71 @@
1
+ module SBCP
2
+ class Starbound
3
+ def initialize
4
+ @config = YAML.load_file(File.expand_path('../../../config.yml', __FILE__))
5
+ backup_schedule = @config['backup_schedule']
6
+ restart_schedule = @config['restart_schedule']
7
+ scheduler = Rufus::Scheduler.new
8
+ unless ['restart', 'none'].include? backup_schedule # Only run backups if they're not set to run at restart or aren't disabled
9
+ if backup_schedule == 'hourly'
10
+ scheduler.cron "0 */1 * * *" do
11
+ SBCP::Backup.create_backup
12
+ end
13
+ elsif backup_schedule == 'daily'
14
+ scheduler.cron "0 0 * * *" do
15
+ SBCP::Backup.create_backup
16
+ end
17
+ else
18
+ scheduler.cron "0 */#{backup_schedule} * * *" do
19
+ SBCP::Backup.create_backup
20
+ end
21
+ end
22
+ end
23
+ unless restart_schedule == 'none' # Only schedule restarts if enabled
24
+ if restart_schedule == 'hourly'
25
+ scheduler.cron "0 */1 * * *" do
26
+ pid = `pidof starbound_server`
27
+ system("kill -15 #{pid.to_i}") if not pid.empty?
28
+ end
29
+ elsif restart_schedule == 'daily'
30
+ scheduler.cron "0 0 * * *" do
31
+ pid = `pidof starbound_server`
32
+ system("kill -15 #{pid.to_i}") if not pid.empty?
33
+ end
34
+ else
35
+ scheduler.cron "0 */#{restart_schedule} * * *" do
36
+ pid = `pidof starbound_server`
37
+ system("kill -15 #{pid.to_i}") if not pid.empty?
38
+ end
39
+ end
40
+ end
41
+ end
42
+
43
+ def start
44
+ if @config['log_style'] == 'daily' then
45
+ log = Logger.new("#{@config['log_directory']}/starbound.log", 'daily', @config['log_history'])
46
+ elsif @config['log_style'] == 'restart' then
47
+ stamp = "#{Time.now.strftime("%m-%d-%Y-%H-%M-%S")}-starbound"
48
+ log = Logger.new("#{@config['log_directory']}/#{stamp}.log")
49
+ else
50
+ abort("Error: Invalid log style")
51
+ end
52
+ log.formatter = proc { |severity, datetime, progname, msg| date_format = datetime.strftime("%H:%M:%S.%N")[0...-6]; "[#{date_format}] #{msg}" }
53
+ log.level = Logger::INFO
54
+ log.info("---------- SBCP is starting a new Starbound instance ----------\n")
55
+ #parser = SBCP::Parser.new
56
+
57
+ IO.popen("#{@config['starbound_directory']}/linux64/starbound_server", :chdir=>"#{@config['starbound_directory']}/linux64", :err=>[:child, :out]) do |output|
58
+ while line = output.gets
59
+ log.info(line)
60
+ #parser.async.parse(line)
61
+ end
62
+ end
63
+
64
+ ensure
65
+ log.info("---------- Starbound has successfully shut down ----------\n")
66
+ log.info("\n") # Adds a newline space at the end of the log. Helpful for separating restarts in daily logs.
67
+ log.close
68
+ #parser.terminate
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,3 @@
1
+ module SBCP
2
+ VERSION = "0.1.4"
3
+ end
@@ -0,0 +1,41 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'sbcp/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'sbcp'
8
+ spec.version = SBCP::VERSION
9
+ spec.authors = ['Kazyyk']
10
+ spec.email = ['contact@kazyyk.com']
11
+
12
+ spec.summary = %q{SBCP is a Starbound server management solution for Linux.}
13
+ spec.homepage = 'https://www.kazyyk.com/sbcp'
14
+ spec.license = 'AGPLv3'
15
+
16
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
17
+ spec.bindir = 'bin'
18
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
19
+ spec.require_paths = ['lib']
20
+ spec.required_ruby_version = '>= 2.3.0'
21
+
22
+ #spec.add_runtime_dependency 'sinatra', '~> 1.4', '>= 1.4.7'
23
+ #spec.add_runtime_dependency 'sinatra-flash', '~> 0.3.0'
24
+ #spec.add_runtime_dependency 'sinatra-contrib', '~> 1.4', '>= 1.4.7'
25
+ #spec.add_runtime_dependency 'thin', '~> 1.6', '>= 1.6.4'
26
+ #spec.add_runtime_dependency 'sqlite3', '~> 1.3', '>= 1.3.11'
27
+ #spec.add_runtime_dependency 'data_mapper', '~> 1.2'
28
+ #spec.add_runtime_dependency 'dm-sqlite-adapter', '~> 1.2'
29
+ spec.add_runtime_dependency 'celluloid', '~> 0.17.3'
30
+ spec.add_runtime_dependency 'rufus-scheduler', '~> 3.2'
31
+ #spec.add_runtime_dependency 'steam-condenser', '~> 1.3', '>= 1.3.11'
32
+ spec.add_runtime_dependency 'rsync', '~> 1.0', '>= 1.0.9'
33
+ spec.add_runtime_dependency 'rbzip2', '~> 0.2.0' # seven_zip_ruby would not compile, using this instead
34
+ spec.add_runtime_dependency 'highline', '~> 1.7', '>= 1.7.8'
35
+
36
+
37
+ spec.add_development_dependency 'bundler', '~> 1.11'
38
+ spec.add_development_dependency 'rake', '~> 10.0'
39
+ spec.add_development_dependency 'minitest', '~> 5.0'
40
+ spec.add_development_dependency 'rack-test', '~> 0.6.3'
41
+ end
metadata ADDED
@@ -0,0 +1,201 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sbcp
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.4
5
+ platform: ruby
6
+ authors:
7
+ - Kazyyk
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-04-28 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: celluloid
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 0.17.3
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 0.17.3
27
+ - !ruby/object:Gem::Dependency
28
+ name: rufus-scheduler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '3.2'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '3.2'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rsync
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.0'
48
+ - - ">="
49
+ - !ruby/object:Gem::Version
50
+ version: 1.0.9
51
+ type: :runtime
52
+ prerelease: false
53
+ version_requirements: !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - "~>"
56
+ - !ruby/object:Gem::Version
57
+ version: '1.0'
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: 1.0.9
61
+ - !ruby/object:Gem::Dependency
62
+ name: rbzip2
63
+ requirement: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: 0.2.0
68
+ type: :runtime
69
+ prerelease: false
70
+ version_requirements: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: 0.2.0
75
+ - !ruby/object:Gem::Dependency
76
+ name: highline
77
+ requirement: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - "~>"
80
+ - !ruby/object:Gem::Version
81
+ version: '1.7'
82
+ - - ">="
83
+ - !ruby/object:Gem::Version
84
+ version: 1.7.8
85
+ type: :runtime
86
+ prerelease: false
87
+ version_requirements: !ruby/object:Gem::Requirement
88
+ requirements:
89
+ - - "~>"
90
+ - !ruby/object:Gem::Version
91
+ version: '1.7'
92
+ - - ">="
93
+ - !ruby/object:Gem::Version
94
+ version: 1.7.8
95
+ - !ruby/object:Gem::Dependency
96
+ name: bundler
97
+ requirement: !ruby/object:Gem::Requirement
98
+ requirements:
99
+ - - "~>"
100
+ - !ruby/object:Gem::Version
101
+ version: '1.11'
102
+ type: :development
103
+ prerelease: false
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ requirements:
106
+ - - "~>"
107
+ - !ruby/object:Gem::Version
108
+ version: '1.11'
109
+ - !ruby/object:Gem::Dependency
110
+ name: rake
111
+ requirement: !ruby/object:Gem::Requirement
112
+ requirements:
113
+ - - "~>"
114
+ - !ruby/object:Gem::Version
115
+ version: '10.0'
116
+ type: :development
117
+ prerelease: false
118
+ version_requirements: !ruby/object:Gem::Requirement
119
+ requirements:
120
+ - - "~>"
121
+ - !ruby/object:Gem::Version
122
+ version: '10.0'
123
+ - !ruby/object:Gem::Dependency
124
+ name: minitest
125
+ requirement: !ruby/object:Gem::Requirement
126
+ requirements:
127
+ - - "~>"
128
+ - !ruby/object:Gem::Version
129
+ version: '5.0'
130
+ type: :development
131
+ prerelease: false
132
+ version_requirements: !ruby/object:Gem::Requirement
133
+ requirements:
134
+ - - "~>"
135
+ - !ruby/object:Gem::Version
136
+ version: '5.0'
137
+ - !ruby/object:Gem::Dependency
138
+ name: rack-test
139
+ requirement: !ruby/object:Gem::Requirement
140
+ requirements:
141
+ - - "~>"
142
+ - !ruby/object:Gem::Version
143
+ version: 0.6.3
144
+ type: :development
145
+ prerelease: false
146
+ version_requirements: !ruby/object:Gem::Requirement
147
+ requirements:
148
+ - - "~>"
149
+ - !ruby/object:Gem::Version
150
+ version: 0.6.3
151
+ description:
152
+ email:
153
+ - contact@kazyyk.com
154
+ executables:
155
+ - sbcp
156
+ extensions: []
157
+ extra_rdoc_files: []
158
+ files:
159
+ - ".gitattributes"
160
+ - ".gitignore"
161
+ - ".travis.yml"
162
+ - Gemfile
163
+ - LICENSE.txt
164
+ - README.md
165
+ - Rakefile
166
+ - backup/README.md
167
+ - bin/sbcp
168
+ - config.yml
169
+ - lib/sbcp.rb
170
+ - lib/sbcp/backup.rb
171
+ - lib/sbcp/daemon.rb
172
+ - lib/sbcp/logs.rb
173
+ - lib/sbcp/parser.rb
174
+ - lib/sbcp/starbound.rb
175
+ - lib/sbcp/version.rb
176
+ - sbcp.gemspec
177
+ homepage: https://www.kazyyk.com/sbcp
178
+ licenses:
179
+ - AGPLv3
180
+ metadata: {}
181
+ post_install_message:
182
+ rdoc_options: []
183
+ require_paths:
184
+ - lib
185
+ required_ruby_version: !ruby/object:Gem::Requirement
186
+ requirements:
187
+ - - ">="
188
+ - !ruby/object:Gem::Version
189
+ version: 2.3.0
190
+ required_rubygems_version: !ruby/object:Gem::Requirement
191
+ requirements:
192
+ - - ">="
193
+ - !ruby/object:Gem::Version
194
+ version: '0'
195
+ requirements: []
196
+ rubyforge_project:
197
+ rubygems_version: 2.5.1
198
+ signing_key:
199
+ specification_version: 4
200
+ summary: SBCP is a Starbound server management solution for Linux.
201
+ test_files: []