vps 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +8 -0
- data/CHANGELOG.md +15 -0
- data/Gemfile +3 -0
- data/MIT-LICENSE +20 -0
- data/README.md +107 -0
- data/Rakefile +10 -0
- data/VERSION +1 -0
- data/bin/vps +10 -0
- data/config/services.yml +63 -0
- data/lib/vps.rb +88 -0
- data/lib/vps/cli.rb +62 -0
- data/lib/vps/cli/domain.rb +59 -0
- data/lib/vps/cli/playbook.rb +110 -0
- data/lib/vps/cli/playbook/state.rb +170 -0
- data/lib/vps/cli/playbook/tasks.rb +262 -0
- data/lib/vps/cli/service.rb +96 -0
- data/lib/vps/cli/upstream.rb +95 -0
- data/lib/vps/core_ext/ostruct.rb +17 -0
- data/lib/vps/core_ext/string.rb +33 -0
- data/lib/vps/version.rb +7 -0
- data/playbooks/deploy.yml +43 -0
- data/playbooks/deploy/docker.yml +96 -0
- data/playbooks/init.yml +12 -0
- data/playbooks/init/ubuntu-18.04.yml +110 -0
- data/playbooks/install.yml +18 -0
- data/playbooks/install/docker/ubuntu-18.04.yml +35 -0
- data/script/console +7 -0
- data/templates/docker/data/nginx/app.conf.erb +69 -0
- data/templates/docker/docker-compose.yml.erb +58 -0
- data/templates/docker/upstream/Dockerfile.phoenix.erb +13 -0
- data/templates/docker/upstream/Dockerfile.plug.erb +13 -0
- data/templates/docker/upstream/Dockerfile.rack.erb +16 -0
- data/templates/docker/upstream/Dockerfile.rails.erb +18 -0
- data/templates/docker/upstream/init-letsencrypt.sh.erb +76 -0
- data/test/test_helper.rb +12 -0
- data/test/test_helper/coverage.rb +8 -0
- data/test/unit/test_version.rb +15 -0
- data/vps.gemspec +29 -0
- metadata +214 -0
@@ -0,0 +1,96 @@
|
|
1
|
+
module VPS
|
2
|
+
class CLI < Thor
|
3
|
+
class Service < Thor
|
4
|
+
|
5
|
+
SERVICES = "#{VPS::ROOT}/config/services.yml"
|
6
|
+
|
7
|
+
desc "add HOST [SERVICE]", "Add service to host configuration"
|
8
|
+
def add(host, service = nil)
|
9
|
+
config = VPS.read_config(host)
|
10
|
+
|
11
|
+
unless service
|
12
|
+
list = services.keys.sort - config[:services].keys
|
13
|
+
service = list[Ask.list("Which service to you want to add?", list)]
|
14
|
+
end
|
15
|
+
|
16
|
+
if !config[:services].include?(service) && (yml = services[service])
|
17
|
+
list = tags(yml[:image] || "library/#{service}")
|
18
|
+
tag = list[Ask.list("Choose which tag to use", list)]
|
19
|
+
image = "#{yml[:image] || service}:#{tag}"
|
20
|
+
|
21
|
+
yml, volumes = finalize_config(yml)
|
22
|
+
config[:services][service] = {:image => image}.merge(yml)
|
23
|
+
config[:volumes].concat(volumes).uniq!
|
24
|
+
|
25
|
+
VPS.write_config(host, config)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
desc "remove HOST [SERVICE]", "Remove service from host configuration"
|
30
|
+
def remove(host, service = nil)
|
31
|
+
config = VPS.read_config(host)
|
32
|
+
|
33
|
+
unless service
|
34
|
+
list = config[:services].keys.sort
|
35
|
+
service = list[Ask.list("Which service do you want to remove?", list)]
|
36
|
+
end
|
37
|
+
|
38
|
+
if config[:services].delete(service)
|
39
|
+
VPS.write_config(host, config)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
desc "list HOST", "List services of host configuration"
|
44
|
+
def list(host)
|
45
|
+
config = VPS.read_config(host)
|
46
|
+
|
47
|
+
services = config[:services].collect do |service, yml|
|
48
|
+
"* #{yml[:image]}"
|
49
|
+
end.sort
|
50
|
+
|
51
|
+
puts services
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def services
|
57
|
+
@services ||= with_indifferent_access(YAML.load_file(SERVICES))
|
58
|
+
end
|
59
|
+
|
60
|
+
def tags(image)
|
61
|
+
uri = URI.parse("https://registry.hub.docker.com/v2/repositories/#{image}/tags/")
|
62
|
+
tags = JSON.parse(Net::HTTP.get(uri))["results"]
|
63
|
+
tags.collect{|tag| tag["name"]}.sort{|a, b| (a == "latest") ? -1 : (b <=> a)}
|
64
|
+
end
|
65
|
+
|
66
|
+
def with_indifferent_access(object)
|
67
|
+
case object
|
68
|
+
when Hash
|
69
|
+
object.inject({}.with_indifferent_access) do |hash, (key, value)|
|
70
|
+
hash[key] = with_indifferent_access(value)
|
71
|
+
hash
|
72
|
+
end
|
73
|
+
when Array
|
74
|
+
object.collect{|item| with_indifferent_access(item)}
|
75
|
+
else
|
76
|
+
object
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def finalize_config(config)
|
81
|
+
config.delete(:image)
|
82
|
+
if config[:environment]
|
83
|
+
config[:environment] = config[:environment].inject({}) do |env, variable|
|
84
|
+
env[variable] = Ask.input(variable)
|
85
|
+
env
|
86
|
+
end
|
87
|
+
end
|
88
|
+
volumes = (config[:volumes] || []).collect do |volume|
|
89
|
+
volume.split(":")[0]
|
90
|
+
end
|
91
|
+
[config, volumes]
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
module VPS
|
2
|
+
class CLI < Thor
|
3
|
+
class Upstream < Thor
|
4
|
+
|
5
|
+
desc "add HOST[:UPSTREAM] PATH", "Add upstream to host configuration"
|
6
|
+
def add(host_and_optional_upstream, path)
|
7
|
+
host, name = host_and_optional_upstream.split(":")
|
8
|
+
config = VPS.read_config(host)
|
9
|
+
path = File.expand_path(path)
|
10
|
+
|
11
|
+
unless config[:upstreams].any?{|upstream| upstream[:name] == name}
|
12
|
+
spec = derive_upstream(path)
|
13
|
+
spec[:nginx] ||= {
|
14
|
+
:http => nil,
|
15
|
+
:https => nil
|
16
|
+
}
|
17
|
+
config[:upstreams].push(spec.merge({
|
18
|
+
:name => name || File.basename(path),
|
19
|
+
:path => path,
|
20
|
+
:domains => [],
|
21
|
+
:email => nil,
|
22
|
+
:compose => nil
|
23
|
+
}))
|
24
|
+
VPS.write_config(host, config)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
desc "remove HOST[:UPSTREAM]", "Remove upstream from host configuration"
|
29
|
+
def remove(host_and_optional_upstream)
|
30
|
+
host, name = host_and_optional_upstream.split(":")
|
31
|
+
config = VPS.read_config(host)
|
32
|
+
|
33
|
+
unless name
|
34
|
+
list = config[:upstreams].collect{|upstream| upstream[:name]}.sort
|
35
|
+
name = list[Ask.list("Which upstream do you want to remove?", list)]
|
36
|
+
end
|
37
|
+
|
38
|
+
if config[:upstreams].reject!{|upstream| upstream[:name] == name}
|
39
|
+
VPS.write_config(host, config)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
desc "list HOST", "List upstreams of host configuration"
|
44
|
+
def list(host)
|
45
|
+
config = VPS.read_config(host)
|
46
|
+
|
47
|
+
upstreams = config[:upstreams].collect do |upstream|
|
48
|
+
"* #{upstream[:name]} (#{upstream[:path].gsub(Dir.home, "~")})"
|
49
|
+
end.sort
|
50
|
+
|
51
|
+
puts upstreams
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def derive_upstream(path)
|
57
|
+
if Dir["#{path}/mix.exs"].any?
|
58
|
+
elixir = <<-ELIXIR
|
59
|
+
Application.started_applications()
|
60
|
+
|> Enum.reduce([], fn {name, _desc, _version}, acc ->
|
61
|
+
if(name in [:phoenix, :plug], do: [name | acc], else: acc)
|
62
|
+
end)
|
63
|
+
|> Enum.sort()
|
64
|
+
|> Enum.at(0)
|
65
|
+
|> IO.puts()
|
66
|
+
ELIXIR
|
67
|
+
type = `cd #{path} && mix run -e "#{elixir.strip.gsub(/\n\s+/, " ")}" | tail -n 1`.strip
|
68
|
+
{
|
69
|
+
type: type,
|
70
|
+
elixir_version: `cd #{path} && mix run -e "System.version() |> IO.puts()" | tail -n 1`.strip,
|
71
|
+
port: (type == "phoenix") ? 4000 : `cd #{path} && mix run -e ":ranch.info |> hd() |> elem(0) |> :ranch.get_port() |> IO.puts()" | tail -n 1`.strip.to_i
|
72
|
+
}
|
73
|
+
elsif Dir["#{path}/Gemfile"].any?
|
74
|
+
lines = `cd #{path} && BUNDLE_GEMFILE=#{path}/Gemfile bundle list`.split("\n")
|
75
|
+
type = %w(rails rack).detect{|gem| lines.any?{|line| line.include?("* #{gem} (")}}
|
76
|
+
{
|
77
|
+
type: type,
|
78
|
+
ruby_version: `$SHELL -l -c 'cd #{path} && ruby -e "puts RUBY_VERSION"'`.strip,
|
79
|
+
bundler_version: `$SHELL -l -c 'cd #{path} && bundle -v'`.split.last,
|
80
|
+
port: (type == "rails" ? 3000 : 9292) # :'(
|
81
|
+
}.tap do |spec|
|
82
|
+
if type == "rails"
|
83
|
+
spec[:nginx] = {
|
84
|
+
root: "/opt/app/public",
|
85
|
+
try_files: true,
|
86
|
+
proxy_redirect: "off"
|
87
|
+
}
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class OpenStruct
|
2
|
+
def self.to_hash(object, hash = {})
|
3
|
+
case object
|
4
|
+
when OpenStruct then
|
5
|
+
object.each_pair do |key, value|
|
6
|
+
hash[key.to_s] = to_hash(value)
|
7
|
+
end
|
8
|
+
hash
|
9
|
+
when Array then
|
10
|
+
object.collect do |value|
|
11
|
+
to_hash(value)
|
12
|
+
end
|
13
|
+
else
|
14
|
+
object
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
class String
|
2
|
+
|
3
|
+
COLORS = {
|
4
|
+
:red => 31,
|
5
|
+
:green => 32,
|
6
|
+
:yellow => 33,
|
7
|
+
:blue => 34,
|
8
|
+
:magenta => 35,
|
9
|
+
:cyan => 36,
|
10
|
+
:white => 39,
|
11
|
+
:gray => 90
|
12
|
+
}
|
13
|
+
|
14
|
+
COLORS.keys.each do |color|
|
15
|
+
define_method color do
|
16
|
+
colorize color
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def indent(n, string = " ")
|
21
|
+
split("\n").collect do |line|
|
22
|
+
line.empty? ? line : "#{string * n}#{line}"
|
23
|
+
end.join("\n")
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def colorize(color)
|
29
|
+
color = COLORS[color]
|
30
|
+
"\033[0;#{color}m#{self}\033[0m"
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
data/lib/vps/version.rb
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
---
|
2
|
+
description:
|
3
|
+
Deploy web application to the server
|
4
|
+
usage:
|
5
|
+
deploy HOST [TOOL]
|
6
|
+
arguments:
|
7
|
+
- host
|
8
|
+
- tool
|
9
|
+
tasks:
|
10
|
+
- task: ensure
|
11
|
+
argument: user
|
12
|
+
fallbacks:
|
13
|
+
- task: read_config
|
14
|
+
key: user
|
15
|
+
- description: Obtaining remote user
|
16
|
+
task: remote_execute
|
17
|
+
command: whoami
|
18
|
+
- task: ensure
|
19
|
+
argument: tool
|
20
|
+
fallbacks:
|
21
|
+
- task: read_config
|
22
|
+
key: tool
|
23
|
+
- description: Choose which deploy tool to use
|
24
|
+
task: select
|
25
|
+
question: Which deployment tool do you want to use?
|
26
|
+
options: << playbooks >>
|
27
|
+
- task: ensure
|
28
|
+
argument: release_path
|
29
|
+
fallbacks:
|
30
|
+
- task: read_config
|
31
|
+
key: release_path
|
32
|
+
- description: Specify which directory to deploy to
|
33
|
+
task: input
|
34
|
+
question: Enter release path on the server
|
35
|
+
default: ~/app
|
36
|
+
- task: write_config
|
37
|
+
config:
|
38
|
+
user: << user >>
|
39
|
+
tool: << tool >>
|
40
|
+
release_path: << release_path >>
|
41
|
+
- task: obtain_config
|
42
|
+
- task: playbook
|
43
|
+
playbook: deploy/{{ tool }}
|
@@ -0,0 +1,96 @@
|
|
1
|
+
---
|
2
|
+
source:
|
3
|
+
- http://dimafeng.com/2015/10/17/docker-distribution/
|
4
|
+
constants:
|
5
|
+
config_path: ~/.vps/{{ host }}
|
6
|
+
docker_compose_file: "{{ config_path }}/docker-compose.yml"
|
7
|
+
nginx_conf_file: "{{ config_path }}/data/nginx/app.conf"
|
8
|
+
tasks:
|
9
|
+
- task: loop
|
10
|
+
through: << upstreams >>
|
11
|
+
as: upstream
|
12
|
+
run:
|
13
|
+
- task: generate_file
|
14
|
+
template: docker/upstream/Dockerfile.{{ upstream.type }}.erb
|
15
|
+
target: "{{ config_path }}/{{ upstream.name }}/Dockerfile"
|
16
|
+
- description: Building '{{ upstream.name }}' image
|
17
|
+
task: execute
|
18
|
+
command: docker build --no-cache -f {{ config_path }}/{{ upstream.name }}/Dockerfile -t {{ upstream.name }} {{ upstream.path }}
|
19
|
+
- description: Saving docker images as .gzip file
|
20
|
+
task: loop
|
21
|
+
through: << upstreams >>
|
22
|
+
as: upstream
|
23
|
+
run:
|
24
|
+
- task: execute
|
25
|
+
command: docker save {{ upstream.name }} | gzip > /tmp/{{ upstream.name }}.gzip
|
26
|
+
- description: Uploading images
|
27
|
+
task: loop
|
28
|
+
through: << upstreams >>
|
29
|
+
as: upstream
|
30
|
+
run:
|
31
|
+
- task: upload
|
32
|
+
file: /tmp/{{ upstream.name }}.gzip
|
33
|
+
- description: Stopping current containers
|
34
|
+
task: remote_execute
|
35
|
+
command: cd {{ release_path }} && docker-compose stop
|
36
|
+
- description: Removing current containers
|
37
|
+
task: loop
|
38
|
+
through: << upstreams >>
|
39
|
+
as: upstream
|
40
|
+
run:
|
41
|
+
- task: remote_execute
|
42
|
+
command: docker container rm $(docker container ls -f ancestor={{ upstream.name }} -aq)
|
43
|
+
- description: Removing current images
|
44
|
+
task: loop
|
45
|
+
through: << upstreams >>
|
46
|
+
as: upstream
|
47
|
+
run:
|
48
|
+
- task: remote_execute
|
49
|
+
command: docker image rm {{ upstream.name }}
|
50
|
+
- task: run_tasks
|
51
|
+
tasks: << preload >>
|
52
|
+
- description: Unzipping and loading docker images
|
53
|
+
task: loop
|
54
|
+
through: << upstreams >>
|
55
|
+
as: upstream
|
56
|
+
run:
|
57
|
+
- task: remote_execute
|
58
|
+
command: gunzip < /tmp/{{ upstream.name }}.gzip | docker load
|
59
|
+
- task: generate_file
|
60
|
+
template: docker/docker-compose.yml.erb
|
61
|
+
target: "{{ docker_compose_file }}"
|
62
|
+
- description: Uploading docker-compose.yml
|
63
|
+
task: upload
|
64
|
+
file: "{{ docker_compose_file }}"
|
65
|
+
remote_path: "{{ release_path }}/docker-compose.yml"
|
66
|
+
- task: generate_file
|
67
|
+
template: docker/data/nginx/app.conf.erb
|
68
|
+
target: "{{ nginx_conf_file }}"
|
69
|
+
- description: Uploading Nginx config
|
70
|
+
task: upload
|
71
|
+
file: "{{ nginx_conf_file }}"
|
72
|
+
remote_path: "{{ release_path }}/data/nginx/app.conf"
|
73
|
+
- task: loop
|
74
|
+
through: << upstreams >>
|
75
|
+
as: upstream
|
76
|
+
run:
|
77
|
+
- task: generate_file
|
78
|
+
template: docker/upstream/init-letsencrypt.sh.erb
|
79
|
+
target: "{{ config_path }}/{{ upstream.name }}/init-letsencrypt.sh"
|
80
|
+
- description: Uploading Let’s Encrypt script
|
81
|
+
task: upload
|
82
|
+
file: "{{ config_path }}/{{ upstream.name }}/init-letsencrypt.sh"
|
83
|
+
remote_path: "{{ release_path }}/init-letsencrypt/{{ upstream.name }}.sh"
|
84
|
+
- description: Install SSL certificates (if required)
|
85
|
+
task: remote_execute
|
86
|
+
command:
|
87
|
+
- chmod +x {{ release_path }}/init-letsencrypt/{{ upstream.name }}.sh
|
88
|
+
- cd {{ release_path }} && if [ ! -d "data/certbot/conf/live/{{ domain:upstream }}" ]; then yes Y | sudo ./init-letsencrypt/{{ upstream.name }}.sh; fi
|
89
|
+
- description: Starting containers
|
90
|
+
task: remote_execute
|
91
|
+
command: cd {{ release_path }} && docker-compose up {{ up }} -d
|
92
|
+
- description: Checking running docker images
|
93
|
+
task: remote_execute
|
94
|
+
command: docker ps
|
95
|
+
- task: run_tasks
|
96
|
+
tasks: << postload >>
|
data/playbooks/init.yml
ADDED
@@ -0,0 +1,110 @@
|
|
1
|
+
---
|
2
|
+
source:
|
3
|
+
- https://www.digitalocean.com/community/tutorials/initial-server-setup-with-ubuntu-18-04
|
4
|
+
- https://github.com/jasonheecs/ubuntu-server-setup/blob/master/setupLibrary.sh
|
5
|
+
- https://www.linode.com/stackscripts/view/1
|
6
|
+
- https://gist.github.com/parente/0227cfbbd8de1ce8ad05
|
7
|
+
tasks:
|
8
|
+
- description: Updating server with latest packages
|
9
|
+
task: remote_execute
|
10
|
+
command: apt-get update && apt-get upgrade -y
|
11
|
+
- description: Choose which tasks to execute
|
12
|
+
task: multiselect
|
13
|
+
question: Which tasks do you want to execute?
|
14
|
+
options:
|
15
|
+
set_server_hostname: Set server hostname
|
16
|
+
add_sudo_user: Add new user with sudo access
|
17
|
+
disable_password_authentication: Disable password authentication to the server
|
18
|
+
setup_uncomplicated_firewall: Setup Uncomplicated FireWall
|
19
|
+
setup_timezone: Setup server timezone
|
20
|
+
install_network_time_protocol: Install Network Time Protocol
|
21
|
+
deny_root_login: Deny root login to the server
|
22
|
+
- description: Setting the server hostname
|
23
|
+
task: when
|
24
|
+
boolean: set_server_hostname
|
25
|
+
run:
|
26
|
+
- task: input
|
27
|
+
question: Enter hostname for the server
|
28
|
+
as: hostname
|
29
|
+
- task: remote_execute
|
30
|
+
command:
|
31
|
+
- echo '{{ hostname }}' > /etc/hostname
|
32
|
+
- hostname -F /etc/hostname
|
33
|
+
- description: Creating new sudo user
|
34
|
+
task: when
|
35
|
+
boolean: add_sudo_user
|
36
|
+
run:
|
37
|
+
- task: input
|
38
|
+
question: Enter username for the new sudo user
|
39
|
+
as: username
|
40
|
+
- task: input
|
41
|
+
question: Enter location of your public SSH key
|
42
|
+
default: ~/.ssh/id_rsa.pub
|
43
|
+
as: public_key
|
44
|
+
- task: remote_execute
|
45
|
+
command: adduser --disabled-password --gecos '' {{ username }}
|
46
|
+
- description: Granting administrative privileges
|
47
|
+
task: remote_execute
|
48
|
+
command: usermod -aG sudo {{ username }}
|
49
|
+
- description: Copying public SSH key
|
50
|
+
task: execute
|
51
|
+
command: cat {{ public_key }}
|
52
|
+
as: public_key
|
53
|
+
- description: Installing public SSH key on server
|
54
|
+
task: remote_execute
|
55
|
+
user: "{{ username }}"
|
56
|
+
command:
|
57
|
+
- mkdir -p ~/.ssh
|
58
|
+
- touch ~/.ssh/authorized_keys
|
59
|
+
- echo {{{ public_key }}} >> ~/.ssh/authorized_keys
|
60
|
+
- chmod 700 ~/.ssh
|
61
|
+
- chmod 600 ~/.ssh/authorized_keys
|
62
|
+
- description: Disabling sudo password for new user
|
63
|
+
task: remote_execute
|
64
|
+
command:
|
65
|
+
- cp /etc/sudoers /etc/sudoers.bak
|
66
|
+
- sh -c 'echo "{{ username }} ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers'
|
67
|
+
- description: Disabling password authentication
|
68
|
+
task: when
|
69
|
+
boolean: disable_password_authentication
|
70
|
+
run:
|
71
|
+
- task: remote_execute
|
72
|
+
command:
|
73
|
+
- sed -i 's/#*ChallengeResponseAuthentication.*/ChallengeResponseAuthentication no/' /etc/ssh/sshd_config
|
74
|
+
- sed -i 's/#*PasswordAuthentication.*/PasswordAuthentication no/' /etc/ssh/sshd_config
|
75
|
+
- service ssh restart
|
76
|
+
- description: Denying root login to the server
|
77
|
+
task: when
|
78
|
+
boolean: deny_root_login
|
79
|
+
run:
|
80
|
+
- task: remote_execute
|
81
|
+
command:
|
82
|
+
- sed -i 's/#*PermitRootLogin.*/PermitRootLogin no/' /etc/ssh/sshd_config
|
83
|
+
- service ssh restart
|
84
|
+
- description: Setting up Uncomplicated FireWall
|
85
|
+
task: when
|
86
|
+
boolean: setup_uncomplicated_firewall
|
87
|
+
run:
|
88
|
+
- task: remote_execute
|
89
|
+
command:
|
90
|
+
- ufw allow OpenSSH
|
91
|
+
- ufw allow http
|
92
|
+
- ufw allow https
|
93
|
+
- yes y | ufw enable
|
94
|
+
- description: Setting up the server timezone
|
95
|
+
task: when
|
96
|
+
boolean: setup_timezone
|
97
|
+
run:
|
98
|
+
- task: remote_execute
|
99
|
+
command:
|
100
|
+
export timezone=`wget -qO - http://geoip.ubuntu.com/lookup | sed -n -e 's/.*<TimeZone>\(.*\)<\/TimeZone>.*/\1/p'` &&
|
101
|
+
timedatectl set-timezone $timezone &&
|
102
|
+
echo $timezone
|
103
|
+
- description: Installing Network Time Protocol
|
104
|
+
task: when
|
105
|
+
boolean: install_network_time_protocol
|
106
|
+
run:
|
107
|
+
- task: remote_execute
|
108
|
+
command:
|
109
|
+
- apt-get update
|
110
|
+
- apt-get --assume-yes install ntp
|