superhosting 0.0.1
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.
- checksums.yaml +15 -0
- data/.gitignore +10 -0
- data/.rspec +2 -0
- data/.travis.yml +11 -0
- data/Gemfile +5 -0
- data/Gemfile.lock +117 -0
- data/LICENSE.txt +22 -0
- data/README.md +23 -0
- data/Rakefile +6 -0
- data/Vagrantfile +10 -0
- data/bin/sx +10 -0
- data/bootstrap.sh +31 -0
- data/lib/superhosting.rb +40 -0
- data/lib/superhosting/base.rb +37 -0
- data/lib/superhosting/cli/base.rb +227 -0
- data/lib/superhosting/cli/cmd/admin_add.rb +11 -0
- data/lib/superhosting/cli/cmd/admin_container_add.rb +16 -0
- data/lib/superhosting/cli/cmd/admin_container_delete.rb +16 -0
- data/lib/superhosting/cli/cmd/admin_container_list.rb +12 -0
- data/lib/superhosting/cli/cmd/admin_delete.rb +11 -0
- data/lib/superhosting/cli/cmd/admin_passwd.rb +16 -0
- data/lib/superhosting/cli/cmd/container_add.rb +21 -0
- data/lib/superhosting/cli/cmd/container_admin_add.rb +16 -0
- data/lib/superhosting/cli/cmd/container_admin_delete.rb +16 -0
- data/lib/superhosting/cli/cmd/container_admin_list.rb +12 -0
- data/lib/superhosting/cli/cmd/container_change.rb +21 -0
- data/lib/superhosting/cli/cmd/container_delete.rb +11 -0
- data/lib/superhosting/cli/cmd/container_list.rb +9 -0
- data/lib/superhosting/cli/cmd/container_reconfig.rb +11 -0
- data/lib/superhosting/cli/cmd/container_restore.rb +24 -0
- data/lib/superhosting/cli/cmd/container_save.rb +14 -0
- data/lib/superhosting/cli/cmd/container_update.rb +11 -0
- data/lib/superhosting/cli/cmd/mysql_db_add.rb +9 -0
- data/lib/superhosting/cli/cmd/mysql_db_delete.rb +9 -0
- data/lib/superhosting/cli/cmd/mysql_db_dump.rb +9 -0
- data/lib/superhosting/cli/cmd/mysql_db_sql.rb +9 -0
- data/lib/superhosting/cli/cmd/mysql_grant.rb +9 -0
- data/lib/superhosting/cli/cmd/mysql_user_add.rb +12 -0
- data/lib/superhosting/cli/cmd/mysql_user_delete.rb +9 -0
- data/lib/superhosting/cli/cmd/site_add.rb +16 -0
- data/lib/superhosting/cli/cmd/site_alias_add.rb +16 -0
- data/lib/superhosting/cli/cmd/site_alias_delete.rb +16 -0
- data/lib/superhosting/cli/cmd/site_delete.rb +11 -0
- data/lib/superhosting/cli/cmd/site_rename.rb +16 -0
- data/lib/superhosting/cli/cmd/user_add.rb +24 -0
- data/lib/superhosting/cli/cmd/user_change.rb +24 -0
- data/lib/superhosting/cli/cmd/user_delete.rb +16 -0
- data/lib/superhosting/cli/cmd/user_list.rb +9 -0
- data/lib/superhosting/cli/cmd/user_passwd.rb +16 -0
- data/lib/superhosting/cli/error/ambiguous_command.rb +11 -0
- data/lib/superhosting/cli/error/base.rb +8 -0
- data/lib/superhosting/cli/error/controller.rb +8 -0
- data/lib/superhosting/controller/admin.rb +21 -0
- data/lib/superhosting/controller/admin/container.rb +24 -0
- data/lib/superhosting/controller/container.rb +174 -0
- data/lib/superhosting/controller/container/admin.rb +24 -0
- data/lib/superhosting/controller/mysql.rb +17 -0
- data/lib/superhosting/controller/mysql/db.rb +23 -0
- data/lib/superhosting/controller/mysql/user.rb +15 -0
- data/lib/superhosting/controller/site.rb +106 -0
- data/lib/superhosting/controller/site/alias.rb +21 -0
- data/lib/superhosting/controller/user.rb +25 -0
- data/lib/superhosting/docker_api.rb +31 -0
- data/lib/superhosting/helpers.rb +30 -0
- data/lib/superhosting/script_executor/base.rb +26 -0
- data/lib/superhosting/script_executor/container.rb +36 -0
- data/lib/superhosting/script_executor/site.rb +13 -0
- data/lib/superhosting/version.rb +3 -0
- data/superhosting.gemspec +32 -0
- metadata +341 -0
@@ -0,0 +1,24 @@
|
|
1
|
+
module Superhosting
|
2
|
+
module Cli
|
3
|
+
module Cmd
|
4
|
+
class UserAdd < Base
|
5
|
+
option :no_ssh,
|
6
|
+
:long => '--no-ssh',
|
7
|
+
:boolean => true
|
8
|
+
|
9
|
+
option :no_ftp,
|
10
|
+
:long => '--no-ftp',
|
11
|
+
:boolean => true
|
12
|
+
|
13
|
+
option :container_name,
|
14
|
+
:short => '-c NAME',
|
15
|
+
:long => '--container NAME',
|
16
|
+
:required => true
|
17
|
+
|
18
|
+
def self.has_required_param?
|
19
|
+
true
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Superhosting
|
2
|
+
module Cli
|
3
|
+
module Cmd
|
4
|
+
class UserChange < Base
|
5
|
+
option :no_ssh,
|
6
|
+
:long => '--no-ssh',
|
7
|
+
:boolean => true
|
8
|
+
|
9
|
+
option :no_ftp,
|
10
|
+
:long => '--no-ftp',
|
11
|
+
:boolean => true
|
12
|
+
|
13
|
+
option :container_name,
|
14
|
+
:short => '-c NAME',
|
15
|
+
:long => '--container NAME',
|
16
|
+
:required => true
|
17
|
+
|
18
|
+
def self.has_required_param?
|
19
|
+
true
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Superhosting
|
2
|
+
module Controller
|
3
|
+
class Admin < Base
|
4
|
+
def add(name:)
|
5
|
+
|
6
|
+
end
|
7
|
+
|
8
|
+
def delete(name:)
|
9
|
+
|
10
|
+
end
|
11
|
+
|
12
|
+
def passwd(name:, generate: nil)
|
13
|
+
|
14
|
+
end
|
15
|
+
|
16
|
+
def container(name:, logger: @logger)
|
17
|
+
Container.new(name: name, logger: logger)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Superhosting
|
2
|
+
module Controller
|
3
|
+
class Admin
|
4
|
+
class Container < Base
|
5
|
+
def initialize(name:, **kvargs)
|
6
|
+
@admin_name = name
|
7
|
+
super(kvargs)
|
8
|
+
end
|
9
|
+
|
10
|
+
def list
|
11
|
+
|
12
|
+
end
|
13
|
+
|
14
|
+
def add(name:)
|
15
|
+
|
16
|
+
end
|
17
|
+
|
18
|
+
def delete(name:)
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,174 @@
|
|
1
|
+
module Superhosting
|
2
|
+
module Controller
|
3
|
+
class Container < Base
|
4
|
+
CONTAINER_NAME_FORMAT = /[a-zA-Z0-9][a-zA-Z0-9_.-]+/
|
5
|
+
|
6
|
+
def list
|
7
|
+
docker = @docker_api.container_list.map {|c| c['Names'].first.slice(1..-1) }.to_set
|
8
|
+
sx = @config.containers._grep_dirs.map {|n| n._name }.to_set
|
9
|
+
containers = (docker & sx)
|
10
|
+
|
11
|
+
{ data: containers.to_a }
|
12
|
+
end
|
13
|
+
|
14
|
+
def add(name:, mail: 'model', admin_mail: nil, model: nil)
|
15
|
+
return { error: :input_error, message: "Invalid container name '#{name}' - only '#{CONTAINER_NAME_FORMAT}' are allowed" } if name !~ CONTAINER_NAME_FORMAT
|
16
|
+
return { error: :logical_error, message: 'Container already running.' } if @docker_api.container_info(name)
|
17
|
+
return { error: :input_error, message: 'No model given.' } unless (model_ = model || @config.default_model(default: nil))
|
18
|
+
|
19
|
+
# config
|
20
|
+
config_path_ = "#{@config_path}/containers/#{name}"
|
21
|
+
config_path_mapper = PathMapper.new(config_path_)
|
22
|
+
FileUtils.mkdir_p config_path_
|
23
|
+
|
24
|
+
# model
|
25
|
+
create_conf("#{config_path_}/model", model) unless model.nil?
|
26
|
+
model_mapper = @config.models.f(:"#{model_}")
|
27
|
+
|
28
|
+
# image
|
29
|
+
return { error: :input_error, message: "No docker_image specified in model '#{model_}.'" } unless (image = model_mapper.docker_image(default: nil))
|
30
|
+
|
31
|
+
# mail
|
32
|
+
unless mail != 'no'
|
33
|
+
if mail == 'yes'
|
34
|
+
create_conf("#{config_path_}/mail", mail)
|
35
|
+
admin_mail_ = admin_mail
|
36
|
+
create_conf("#{config_path_}/admin_mail", admin_mail_) unless admin_mail_.nil?
|
37
|
+
return { error: :input_error, message: 'Admin mail required.' } if admin_mail_.nil?
|
38
|
+
elsif mail == 'model'
|
39
|
+
if model_mapper.default_mail == 'yes'
|
40
|
+
admin_mail_ = admin_mail || model_mapper.default_admin_mail(default: nil)
|
41
|
+
return { error: :input_error, message: 'Admin mail required.' } if admin_mail_.nil?
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# lib
|
47
|
+
lib_path_ = "#{@lib_path}/containers/#{name}"
|
48
|
+
lib_path_mapper = PathMapper.new(lib_path_)
|
49
|
+
FileUtils.rm_rf "#{lib_path_}/configs"
|
50
|
+
FileUtils.mkdir_p "#{lib_path_}/configs"
|
51
|
+
FileUtils.mkdir_p "#{lib_path_}/web"
|
52
|
+
|
53
|
+
# user/group
|
54
|
+
self.command("groupadd #{name}")
|
55
|
+
self.command("useradd #{name} -g #{name} -d #{lib_path_}/web/")
|
56
|
+
|
57
|
+
user = Etc.getpwnam(name)
|
58
|
+
|
59
|
+
write_if_not_exist("#{lib_path_}/configs/etc-group", "#{name}:x:#{user.gid}:")
|
60
|
+
write_if_not_exist("#{lib_path_}/configs/etc-passwd", "#{name}:x:#{user.uid}:#{user.gid}::/web/:/usr/sbin/nologin")
|
61
|
+
|
62
|
+
# system users
|
63
|
+
users = [config_path_mapper.system_users, model_mapper.system_users].find {|f| f.is_a? PathMapper::FileNode }
|
64
|
+
users.lines.each {|u| self.command("useradd #{name}_#{u.strip} -g #{name} -d #{lib_path_}/web/") } unless users.nil?
|
65
|
+
|
66
|
+
# services
|
67
|
+
cserv = @config.containers.f(name).services._grep(/.*\.erb/).map {|n| [n._name, n]}.to_h
|
68
|
+
mserv = model_mapper.services._grep(/.*\.erb/).map {|n| [n._name, n]}.to_h
|
69
|
+
services = mserv.merge!(cserv)
|
70
|
+
|
71
|
+
supervisor_path = "#{lib_path_}/supervisor"
|
72
|
+
FileUtils.mkdir_p supervisor_path
|
73
|
+
services.each do |_name, node|
|
74
|
+
text = erb(node, model: model_, container: config_path_mapper)
|
75
|
+
create_conf("#{supervisor_path}/#{_name[/.*[^\.erb]/]}", text)
|
76
|
+
end
|
77
|
+
|
78
|
+
# container
|
79
|
+
unless model_mapper.f('container.rb').nil?
|
80
|
+
registry_path = lib_path_mapper.registry.f('container')._path
|
81
|
+
ex = ScriptExecutor::Container.new(
|
82
|
+
container: config_path_mapper, container_name: name, container_lib: lib_path_mapper, registry_path: registry_path,
|
83
|
+
model: model_mapper, config: @config, lib: @lib
|
84
|
+
)
|
85
|
+
ex.execute(model_mapper.f('container.rb'))
|
86
|
+
ex.commands.each {|c| self.command c }
|
87
|
+
end
|
88
|
+
|
89
|
+
# docker
|
90
|
+
write_if_not_exist('/etc/security/docker.conf', "@#{name} #{name}")
|
91
|
+
|
92
|
+
# run container
|
93
|
+
self.command "docker run --detach --name #{name} -v #{lib_path_}/configs/:/.configs:ro
|
94
|
+
-v #{lib_path_}/web:/web #{image} /usr/bin/supervisord -n -c /etc/supervisor/supervisord.conf".split
|
95
|
+
|
96
|
+
{ error: :error, message: 'Unable to run docker container.' } unless @docker_api.container_info(name)
|
97
|
+
end
|
98
|
+
|
99
|
+
def delete(name:)
|
100
|
+
config_path_mapper = @config.containers.f(name)
|
101
|
+
lib_path_mapper = PathMapper.new("#{@lib_path}/containers/#{name}")
|
102
|
+
model = config_path_mapper.model(default: @config.default_model)
|
103
|
+
model_mapper = @config.models.f(:"#{model}")
|
104
|
+
|
105
|
+
sites = config_path_mapper.sites._grep_dirs.map { |n| n._name }
|
106
|
+
sites.each do |s|
|
107
|
+
begin
|
108
|
+
Site.new(instance_variables_to_hash(self)).delete(s)
|
109
|
+
rescue NetStatus::Exception => e
|
110
|
+
raise Error::Controller, e.net_status
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
unless (container = lib_path_mapper.registry.f('container')).nil?
|
115
|
+
FileUtils.rm container.lines
|
116
|
+
FileUtils.rm container._path
|
117
|
+
end
|
118
|
+
|
119
|
+
unless model_mapper.f('container.rb').nil?
|
120
|
+
registry_path = lib_path_mapper.registry.f('container')._path
|
121
|
+
ex = ScriptExecutor::Container.new(
|
122
|
+
container: config_path_mapper, container_name: name, container_lib: lib_path_mapper,
|
123
|
+
registry_path: registry_path, on_reconfig_only: true,
|
124
|
+
model: model_mapper, config: @config, lib: @lib
|
125
|
+
)
|
126
|
+
ex.execute(model_mapper.f('container.rb'))
|
127
|
+
ex.commands.each {|c| self.command c }
|
128
|
+
end
|
129
|
+
|
130
|
+
@docker_api.container_kill(name)
|
131
|
+
@docker_api.container_rm(name)
|
132
|
+
remove_line_from_file('/etc/security/docker.conf', "@#{name} #{name}")
|
133
|
+
|
134
|
+
begin
|
135
|
+
gid = Etc.getgrnam(name).gid
|
136
|
+
Etc.passwd do |user|
|
137
|
+
self.command("userdel #{user.name}") if user.gid == gid
|
138
|
+
end
|
139
|
+
self.command("groupdel #{name}")
|
140
|
+
rescue ArgumentError => e
|
141
|
+
# repeated command execution: group name already does not exist
|
142
|
+
end
|
143
|
+
|
144
|
+
FileUtils.rm_rf "#{@lib_path}/containers/#{name}/configs"
|
145
|
+
|
146
|
+
{}
|
147
|
+
end
|
148
|
+
|
149
|
+
def change(name:, mail: 'model', admin_mail: nil, model: nil)
|
150
|
+
|
151
|
+
end
|
152
|
+
|
153
|
+
def update(name:)
|
154
|
+
|
155
|
+
end
|
156
|
+
|
157
|
+
def reconfig(name:)
|
158
|
+
|
159
|
+
end
|
160
|
+
|
161
|
+
def save(name:, to:)
|
162
|
+
|
163
|
+
end
|
164
|
+
|
165
|
+
def restore(name:, from:, mail: 'model', admin_mail: nil, model: nil)
|
166
|
+
|
167
|
+
end
|
168
|
+
|
169
|
+
def admin(name:, logger: @logger)
|
170
|
+
Admin.new(name: name, logger: logger)
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Superhosting
|
2
|
+
module Controller
|
3
|
+
class Container
|
4
|
+
class Admin < Base
|
5
|
+
def initialize(name:, **kvargs)
|
6
|
+
@container_name = name
|
7
|
+
super(kvargs)
|
8
|
+
end
|
9
|
+
|
10
|
+
def list
|
11
|
+
|
12
|
+
end
|
13
|
+
|
14
|
+
def add(name:)
|
15
|
+
|
16
|
+
end
|
17
|
+
|
18
|
+
def delete(name:)
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
module Superhosting
|
2
|
+
module Controller
|
3
|
+
class Site < Base
|
4
|
+
attr_reader :site_index
|
5
|
+
|
6
|
+
DOMAIN_NAME_FORMAT = /^((?!-)[A-Za-z0-9-]{1,63}(?<!-)\.)+[A-Za-z]{2,6}$/
|
7
|
+
|
8
|
+
def site_index
|
9
|
+
def generate
|
10
|
+
@site_index = {}
|
11
|
+
@config.containers._grep_dirs.each do |c|
|
12
|
+
c.sites._grep_dirs.each do |s|
|
13
|
+
names = []
|
14
|
+
names << s._name
|
15
|
+
s.aliases.lines {|n| names << n.strip } unless s.aliases.nil?
|
16
|
+
raise NetStatus::Exception.new(error: :error, message: "Conflict between container_sites: '#{@site_index[s._name][:site]._path}' and '#{s._path}'") if @site_index.key? s._name
|
17
|
+
names.each {|n| @site_index[n] = { container: c, site: s } }
|
18
|
+
end
|
19
|
+
end
|
20
|
+
@site_index
|
21
|
+
end
|
22
|
+
|
23
|
+
@site_index ||= self.generate
|
24
|
+
end
|
25
|
+
|
26
|
+
def add(name:, container_name:)
|
27
|
+
return { error: :input_error, message: "Invalid site name '#{name}' only '#{DOMAIN_NAME_FORMAT}'" } if name !~ DOMAIN_NAME_FORMAT
|
28
|
+
return { error: :logical_error, message: 'Site already exists.' } if self.site_index.include? name
|
29
|
+
return { error: :logical_error, message: "Container '#{container_name}' doesn\'t exists." } if (config_path_mapper = @config.containers.f(container_name)).nil?
|
30
|
+
|
31
|
+
site_mapper = config_path_mapper.sites.f(name)
|
32
|
+
FileUtils.mkdir_p site_mapper._path
|
33
|
+
|
34
|
+
lib_path_mapper = PathMapper.new("#{@lib_path}/containers/#{container_name}")
|
35
|
+
model = config_path_mapper.model(default: @config.default_model)
|
36
|
+
model_mapper = @config.models.f(:"#{model}")
|
37
|
+
|
38
|
+
lib_sites_path = lib_path_mapper.registry.sites
|
39
|
+
unless model_mapper.f('site.rb').nil?
|
40
|
+
registry_path = lib_sites_path.f(name)._path
|
41
|
+
FileUtils.mkdir_p lib_sites_path._path
|
42
|
+
ex = ScriptExecutor::Site.new(
|
43
|
+
site: site_mapper, site_name: name,
|
44
|
+
container: config_path_mapper, container_name: name, container_lib: lib_path_mapper, registry_path: registry_path,
|
45
|
+
model: model_mapper, config: @config, lib: @lib
|
46
|
+
)
|
47
|
+
ex.execute(model_mapper.f('site.rb'))
|
48
|
+
ex.commands.each {|c| self.command c }
|
49
|
+
end
|
50
|
+
|
51
|
+
{}
|
52
|
+
end
|
53
|
+
|
54
|
+
def delete(name:)
|
55
|
+
if site = self.site_index[name]
|
56
|
+
FileUtils.rm_rf site[:site]._path
|
57
|
+
container_sites = site[:container].sites
|
58
|
+
FileUtils.rm_rf container_sites._path if container_sites.nil?
|
59
|
+
|
60
|
+
config_path_mapper = site[:container]
|
61
|
+
lib_path_mapper = PathMapper.new("#{@lib_path}/containers/#{config_path_mapper._name}")
|
62
|
+
model = config_path_mapper.model(default: @config.default_model)
|
63
|
+
model_mapper = @config.models.f(:"#{model}")
|
64
|
+
|
65
|
+
lib_sites_path = lib_path_mapper.registry.sites
|
66
|
+
|
67
|
+
unless (registry_site = lib_sites_path.f(name)).nil?
|
68
|
+
FileUtils.rm registry_site.lines
|
69
|
+
FileUtils.rm registry_site._path
|
70
|
+
end
|
71
|
+
|
72
|
+
unless model_mapper.f('site.rb').nil?
|
73
|
+
registry_path = lib_sites_path.f(name)._path
|
74
|
+
FileUtils.mkdir_p lib_sites_path._path
|
75
|
+
ex = ScriptExecutor::Site.new(
|
76
|
+
site: site[:site], site_name: name,
|
77
|
+
container: config_path_mapper, container_name: name, container_lib: lib_path_mapper,
|
78
|
+
registry_path: registry_path, on_reconfig_only: true,
|
79
|
+
model: model_mapper, config: @config, lib: @lib
|
80
|
+
)
|
81
|
+
ex.execute(model_mapper.f('site.rb'))
|
82
|
+
ex.commands.each {|c| self.command c }
|
83
|
+
end
|
84
|
+
|
85
|
+
{}
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def rename(name:, new_name:)
|
90
|
+
return { error: :logical_error, message: "Site '#{name}' doesn't exists." } unless site = self.site_index[name]
|
91
|
+
return { error: :logical_error, message: "Site '#{new_name}' already exists." } if self.site_index[new_name]
|
92
|
+
|
93
|
+
FileUtils.mv site[:container].sites.f(name)._path, site[:container].sites.f(new_name)._path
|
94
|
+
|
95
|
+
lib_path_mapper = PathMapper.new("#{@lib_path}/containers/#{site[:container]._name}")
|
96
|
+
unless (lib_sites_path = lib_path_mapper.registry.sites).nil?
|
97
|
+
FileUtils.mv lib_sites_path.f(name), lib_sites_path.f(new_name)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def alias(name:, logger: @logger)
|
102
|
+
Alias.new(name: name, logger: logger)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|