standard-procedure-anvil 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 47f486a924f5bdee358e37587a81182f20578c61180508124ed2cef551b1fd62
4
+ data.tar.gz: 87f4d8498543db8f3aefd10ddd5e288bed1d9db89c47e84c82de2745bdd61a4a
5
+ SHA512:
6
+ metadata.gz: 92a3dbc97db730b1a55d31bdae8cae9b607f36070b2cee53b6dc386b1cfc755b13f57b90f6874c885882f4d08fd9cc6057dc5e520d9d8dcce7750339124ca518
7
+ data.tar.gz: b378044a88edce05a58234c9cca4c7878d7ed7ed25255e57b544392286d18458afffd5b709e51fe70e5e237df0a876111eb3cf4ed529ff2042ac2d8051aa6abd
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.standard.yml ADDED
@@ -0,0 +1,3 @@
1
+ # For available configuration options, see:
2
+ # https://github.com/testdouble/standard
3
+ ruby_version: 2.6
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ ## [Unreleased]
2
+
3
+ ## [0.1.0] - 2023-06-19
4
+
5
+ - Initial release
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2023 Rahoul Baruah
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,139 @@
1
+ # Standard::Procedure::Anvil
2
+
3
+ Some simple scripts for installing [Dokku](https://dokku.com) applications on Ubuntu servers.
4
+
5
+ ## Installation
6
+
7
+ ```ruby
8
+ gem install standard-procedure-anvil
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ### To build a new server
14
+
15
+ Coming soon (plan is to use [Fog](https://github.com/fog/fog) to handle building servers)
16
+
17
+ ### To install an application onto a blank server
18
+
19
+ Move to your application's root folder and create the anvil.yml file (see below).
20
+
21
+ Then run `anvil host --user=user --use-sudo --identity=~/.ssh/my_key`.
22
+
23
+ This will SSH into each server and:
24
+
25
+ - Sets the server hostname and timezone
26
+ - Installs various necessary packages, plus dokku itself
27
+ - Sets up the firewall
28
+ - Creates unix users for each app, adding them to the sudo and docker groups, and setting their authorized_keys files with the given public key
29
+ - Schedule a `docker system prune` once per week to clean up any dangling images or containers
30
+ - Configure nginx
31
+ - Install dokku plugins and run any configuration you have defined
32
+ - Sets the dokku deployment branch to `main`
33
+ - Disallows root and passwordless logins over SSH
34
+
35
+ For each app it will then:
36
+
37
+ - Create the dokku app
38
+ - Set the environment variables to those defined in your configuration and secrets files
39
+ - Set the app's domain and set up a proxy from nginx to the app's port
40
+ - Sets resource limits for the app
41
+ - Disables checks for workers
42
+
43
+ Then a git remote for each app is created on your local machine and then pushed. This performs the initial dokku deployment. Once complete:
44
+
45
+ - The app is scaled to the correct number of workers
46
+ - Plugins are configured for the app
47
+
48
+ ### Configuration Files
49
+
50
+ An Anvil configuration file specifies the configuration for multiple servers and multiple apps. Each server is configured, then each app is installed onto each server.
51
+
52
+ So you could have one app on two servers (and, we assume, a load-balancer set up in front of them). Or two apps on one server. Or even two apps on two servers (again, using a load-balancer)
53
+
54
+
55
+ ```yml
56
+ version: 0.1
57
+ servers:
58
+ hosts:
59
+ - server1.example.com
60
+ user: user
61
+ public_key: /home/local-user/.ssh/my-key.pub
62
+ timezone: Europe/London
63
+ ports:
64
+ - 22/tcp
65
+ - 80/tcp
66
+ - 443/tcp
67
+ nginx:
68
+ forward_proxy_headers: false
69
+ client_max_body_size: 512m
70
+ proxy_read_timeout: 60s
71
+ plugins:
72
+ cron-restart:
73
+ url: https://github.com/dokku/dokku-cron-restart.git
74
+ maintenance:
75
+ url: https://github.com/dokku/dokku-maintenance.git
76
+ redis:
77
+ url: https://github.com/dokku/dokku-redis.git
78
+ memcached:
79
+ url: https://github.com/dokku/dokku-memcached.git
80
+ letsencrypt:
81
+ url: https://github.com/dokku/dokku-letsencrypt.git
82
+ config:
83
+ - set --global email ssl-admin@mycompany.com
84
+ - cronjob --add
85
+ apps:
86
+ first_app:
87
+ hostname: first_app.example.com
88
+ port: 3000
89
+ environment:
90
+ - ENV_VAR=value
91
+ - ENV_VAR2=value2
92
+ - RAILS_ENV=production
93
+ secrets: secrets.yml
94
+ resource_limit: 2048m
95
+ scale: web=2 worker=1
96
+ plugins:
97
+ cron-restart:
98
+ - set first_app schedule '0 3 * * *'
99
+ redis:
100
+ - create first_app_redis_db
101
+ - link first_app_redis_db first_app
102
+ memcached:
103
+ - create first_app_memcached
104
+ - link first_app_memcached first_app
105
+ letsencrypt:
106
+ - set first_app email ssl-admin@mycompany.com
107
+ - enable first_app
108
+ second_app:
109
+ hostname: second_app.example.com
110
+ port: 3000
111
+ environment:
112
+ - ENV_VAR=value
113
+ - ENV_VAR2=value2
114
+ - RAILS_ENV=production
115
+ secrets: secrets.yml
116
+ resource_limit: 2048m
117
+ scale: web=2 worker=1
118
+ plugins:
119
+ cron-restart:
120
+ - set second_app schedule '0 3 * * *'
121
+ letsencrypt:
122
+ - set second_app email ssl-admin@mycompany.com
123
+ - enable second_app
124
+ ```
125
+ `secrets.yml` is an optional additional file containing environment variables that you do not want to check into your source code repository. It is a simple KEY=VALUE format:
126
+
127
+ ```
128
+ DB_PASSWORD=letmein
129
+ ENCRYPTION_KEY=secretstuff
130
+ ```
131
+
132
+
133
+ ## Contributing
134
+
135
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/standard-procedure-anvil.
136
+
137
+ ## License
138
+
139
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ require "standard/rake"
9
+
10
+ task default: %i[spec standard]
data/exe/anvil ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require_relative "../lib/anvil"
4
+
5
+ Anvil::Cli.start(ARGV)
data/lib/anvil/cli.rb ADDED
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "thor"
4
+ require_relative "installer"
5
+ class Anvil::Cli < Thor
6
+ desc "anvil install", "Perform an installation of dokku on a server"
7
+ long_desc <<-DESC
8
+ Perform an installation of dokku on a server in preparation for deploying apps.
9
+
10
+ Example:
11
+ `anvil install CONFIG`
12
+
13
+ The default CONFIG file is `anvil.yml` in the current directory.
14
+
15
+ Options:
16
+ --private_key, -k: The path to the key certificate file to use when connecting to the server.
17
+ --passphrase, -p: The passphrase to use when connecting to the server.
18
+ DESC
19
+ option :private_key, type: :string, default: nil, aliases: "-k"
20
+ option :passphrase, type: :string, default: nil, aliases: "-p"
21
+
22
+ def install config = "anvil.yml", private_key = nil, passphrase = nil
23
+ Anvil::Installer.new(configuration_from(config), private_key, passphrase).call
24
+ end
25
+
26
+ def self.exit_on_failure?
27
+ true
28
+ end
29
+
30
+ protected
31
+
32
+ def configuration_from(file_name)
33
+ @configuration ||= YAML.load_file(file_name)
34
+ end
35
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "yaml"
4
+ require_relative "server_installer"
5
+
6
+ # The Installer reads the configuration and runs the ServerInstaller for each host.
7
+ class Anvil::Installer < Struct.new(:configuration, :private_key, :passphrase)
8
+ def call
9
+ hosts.each do |host|
10
+ Anvil::ServerInstaller.new(host, configuration, private_key, passphrase).call
11
+ end
12
+ end
13
+
14
+ def hosts
15
+ configuration["hosts"]
16
+ end
17
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Anvil::Logger < Struct.new(:prefix)
4
+ def info message, category = nil
5
+ puts [timestamp, category, message].compact.join(" ")
6
+ end
7
+
8
+ protected
9
+
10
+ def timestamp
11
+ "#{Time.now.strftime("%H:%M:%S")} #{prefix}".rjust(40)
12
+ end
13
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Anvil::ServerInstaller::ConfigureDocker < Struct.new(:ssh_connection)
4
+ def call
5
+ script = <<~SCRIPT
6
+ echo "15 0 3 * * /usr/bin/docker system prune -f" | crontab
7
+ SCRIPT
8
+ ssh_connection.exec! script, "ConfigureDocker"
9
+ end
10
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Anvil::ServerInstaller::ConfigureDokku < Struct.new(:ssh_connection, :hostname)
4
+ def call
5
+ script = <<~SCRIPT
6
+ dokku domains:set-global #{hostname}
7
+ dokku git:set --global deploy-branch main
8
+ SCRIPT
9
+ ssh_connection.exec! script, "ConfigureDokku"
10
+ end
11
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Anvil::ServerInstaller::ConfigureFirewall < Struct.new(:ssh_connection, :ports)
4
+ def call
5
+ ports.collect do |port|
6
+ ssh_connection.exec! "ufw allow #{port}", "ConfigureFirewall"
7
+ end
8
+ ssh_connection.exec! "ufw --force enable", "ConfigureFirewall"
9
+ end
10
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Anvil::ServerInstaller::ConfigureSshServer < Struct.new(:ssh_connection)
4
+ def call
5
+ script = <<-SCRIPT
6
+ sed -i 's/PermitRootLogin yes/PermitRootLogin no/g' /etc/ssh/sshd_config
7
+ sed -i 's/#PermitRootLogin prohibit-password/PermitRootLogin no/g' /etc/ssh/sshd_config
8
+ service sshd restart
9
+ SCRIPT
10
+ ssh_connection.exec! script, "ConfigureSshServer"
11
+ end
12
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Anvil::ServerInstaller::CreateUsers < Struct.new(:ssh_connection, :names)
4
+ def call
5
+ names.collect do |name|
6
+ script = <<~SCRIPT
7
+ if id -u #{name} >/dev/null 2>&1; then
8
+ echo "#{name} already exists"
9
+ else
10
+ echo "Adding #{name}"
11
+ adduser --disabled-password --gecos "" #{name}
12
+ usermod -aG sudo #{name}
13
+ usermod -aG docker #{name}
14
+ echo "#{name} ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers
15
+ fi
16
+ SCRIPT
17
+ ssh_connection.exec! script, "CreateUsers"
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Anvil::ServerInstaller::InstallPackages < Struct.new(:ssh_connection, :public_key_file)
4
+ def call
5
+ public_key = File.read public_key_file
6
+ script = <<~SCRIPT
7
+ mkdir -p /root/.ssh
8
+ echo "#{public_key}" > /root/.ssh/id_rsa.pub
9
+ mkdir -p /etc/skel/.ssh
10
+ cp /root/.ssh/id_rsa.pub /etc/skel/.ssh/authorized_keys
11
+
12
+ echo "Installing packages"
13
+ apt-get update -qq >/dev/null
14
+ apt-get -qq -y --no-install-recommends install apt-transport-https
15
+
16
+ if command -v "$@" > /dev/null 2>&1; then
17
+ echo "Docker is already installed"
18
+ else
19
+ echo "Installing docker"
20
+ wget -nv -O - https://get.docker.com/ | sh
21
+
22
+ wget -qO- https://packagecloud.io/dokku/dokku/gpgkey | tee /etc/apt/trusted.gpg.d/dokku.asc
23
+ DISTRO="$(awk -F= '$1=="ID" { print tolower($2) ;}' /etc/os-release)"
24
+ OS_ID="$(awk -F= '$1=="VERSION_CODENAME" { print tolower($2) ;}' /etc/os-release)"
25
+ echo "deb https://packagecloud.io/dokku/dokku/${DISTRO}/ ${OS_ID} main" | tee /etc/apt/sources.list.d/dokku.list
26
+ fi
27
+
28
+ echo "Installing dokku"
29
+ apt-get update -qq >/dev/null
30
+ apt-get -qq -y install dokku
31
+
32
+ echo "Installing dependencies"
33
+ dokku plugin:install-dependencies --core
34
+
35
+ cat /root/.ssh/id_rsa.pub | dokku ssh-keys:add admin
36
+ SCRIPT
37
+
38
+ ssh_connection.exec! script, "InstallPackages"
39
+ end
40
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Anvil::ServerInstaller::InstallPlugins < Struct.new(:ssh_connection, :plugins)
4
+ def call
5
+ plugins.each do |name, config|
6
+ scripts = ["dokku plugin:install #{config["url"]} #{name}"]
7
+ plugin_config = config["config"] || []
8
+ scripts += plugin_config.collect do |cmd|
9
+ "dokku #{name}:#{cmd}"
10
+ end
11
+ ssh_connection.exec! scripts.join("\n"), "InstallPlugins"
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Anvil::ServerInstaller::SetHostname < Struct.new(:ssh_connection, :hostname)
4
+ def call
5
+ script = <<-SCRIPT
6
+ hostnamectl set-hostname #{hostname}
7
+ mkdir -p /etc/environment.d
8
+ echo "HOSTNAME=#{hostname}" > /etc/environment.d/99-hostname
9
+ SCRIPT
10
+ ssh_connection.exec! script, "SetHostname"
11
+ end
12
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Anvil::ServerInstaller::SetTimezone < Struct.new(:ssh_connection, :timezone)
4
+ def call
5
+ script = <<-SCRIPT
6
+ timedatectl set-timezone #{timezone}
7
+ SCRIPT
8
+ ssh_connection.exec! script, "SetTimezone"
9
+ end
10
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "ssh_executor"
4
+ require_relative "logger"
5
+
6
+ # The server installer uses Net::SSH to connect to the server and then run the following steps:
7
+ # - Sets the server hostname and timezone
8
+ # - Installs various necessary packages, plus dokku itself
9
+ # - Sets up the firewall
10
+ # - Creates unix users for each app, adding them to the sudo and docker groups, and setting their authorized_keys files with the given public key
11
+ # - Sets the dokku deployment branch to `main`
12
+ # - Schedule a `docker system prune` once per week to clean up any dangling images or containers
13
+ # - Configure nginx
14
+ # - Install dokku plugins and run any configuration you have defined
15
+ # - Disallows root and passwordless logins over SSH
16
+ class Anvil::ServerInstaller < Struct.new(:hostname, :configuration, :private_key, :passphrase)
17
+ require_relative "server_installer/set_hostname"
18
+ require_relative "server_installer/set_timezone"
19
+ require_relative "server_installer/install_packages"
20
+ require_relative "server_installer/create_users"
21
+ require_relative "server_installer/configure_dokku"
22
+ require_relative "server_installer/configure_docker"
23
+ require_relative "server_installer/install_plugins"
24
+ require_relative "server_installer/configure_firewall"
25
+ require_relative "server_installer/configure_ssh_server"
26
+ def call
27
+ Anvil::SshExecutor.new(hostname, server_configuration["user"], logger).call do |ssh_connection|
28
+ logger.info "SetHostname"
29
+ Anvil::ServerInstaller::SetHostname.new(ssh_connection, hostname).call
30
+ logger.info "SetTimezone"
31
+ Anvil::ServerInstaller::SetTimezone.new(ssh_connection, server_configuration["timezone"]).call
32
+ logger.info "InstallPackages"
33
+ Anvil::ServerInstaller::InstallPackages.new(ssh_connection, server_configuration["public_key"]).call
34
+ logger.info "ConfigureDokku"
35
+ Anvil::ServerInstaller::ConfigureDokku.new(ssh_connection, hostname).call
36
+ logger.info "CreateUsers"
37
+ Anvil::ServerInstaller::CreateUsers.new(ssh_connection, app_names).call
38
+ logger.info "InstallPlugins"
39
+ Anvil::ServerInstaller::InstallPlugins.new(ssh_connection, server_configuration["plugins"]).call
40
+ logger.info "ConfigureDocker"
41
+ Anvil::ServerInstaller::ConfigureDocker.new(ssh_connection).call
42
+ logger.info "ConfigureFirewall"
43
+ Anvil::ServerInstaller::ConfigureFirewall.new(ssh_connection, server_configuration["ports"]).call
44
+ logger.info "ConfigureSshServer"
45
+ Anvil::ServerInstaller::ConfigureSshServer.new(ssh_connection).call
46
+ end
47
+ end
48
+
49
+ def server_configuration
50
+ configuration["server_config"]
51
+ end
52
+
53
+ def app_names
54
+ configuration["apps"].keys
55
+ end
56
+
57
+ def logger
58
+ Anvil::Logger.new(hostname)
59
+ end
60
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "net/ssh"
4
+
5
+ # The Installer reads the configuration and runs the ServerInstaller for each host.
6
+ class Anvil::SshExecutor < Struct.new(:hostname, :user, :logger)
7
+ def call &block
8
+ @connection = Net::SSH.start hostname, user, use_agent: true
9
+ block.call self
10
+ end
11
+
12
+ def exec! script, category = ""
13
+ @connection.exec! script do |channel, stream, data|
14
+ data.to_s.split("\n") do |line|
15
+ logger.info line, category
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Anvil
4
+ VERSION = "0.1.1"
5
+ end
data/lib/anvil.rb ADDED
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "anvil/version"
4
+ require_relative "anvil/cli"
5
+
6
+ module Anvil
7
+ class Error < StandardError; end
8
+ end
@@ -0,0 +1,8 @@
1
+ module Standard
2
+ module Procedure
3
+ module Anvil
4
+ VERSION: String
5
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
6
+ end
7
+ end
8
+ end
metadata ADDED
@@ -0,0 +1,127 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: standard-procedure-anvil
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - Rahoul Baruah
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2023-06-20 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: thor
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: ed25519
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: bcrypt_pbkdf
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: net-ssh
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description: Tools for managing servers and apps built using dokku
70
+ email:
71
+ - rahoulb@standardprocedure.app
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - ".rspec"
77
+ - ".standard.yml"
78
+ - CHANGELOG.md
79
+ - LICENSE.txt
80
+ - README.md
81
+ - Rakefile
82
+ - exe/anvil
83
+ - lib/anvil.rb
84
+ - lib/anvil/cli.rb
85
+ - lib/anvil/installer.rb
86
+ - lib/anvil/logger.rb
87
+ - lib/anvil/server_installer.rb
88
+ - lib/anvil/server_installer/configure_docker.rb
89
+ - lib/anvil/server_installer/configure_dokku.rb
90
+ - lib/anvil/server_installer/configure_firewall.rb
91
+ - lib/anvil/server_installer/configure_ssh_server.rb
92
+ - lib/anvil/server_installer/create_users.rb
93
+ - lib/anvil/server_installer/install_packages.rb
94
+ - lib/anvil/server_installer/install_plugins.rb
95
+ - lib/anvil/server_installer/set_hostname.rb
96
+ - lib/anvil/server_installer/set_timezone.rb
97
+ - lib/anvil/ssh_executor.rb
98
+ - lib/anvil/version.rb
99
+ - sig/standard/procedure/anvil.rbs
100
+ homepage: https://github.com/standard-procedure/anvil
101
+ licenses:
102
+ - MIT
103
+ metadata:
104
+ allowed_push_host: https://rubygems.org
105
+ homepage_uri: https://github.com/standard-procedure/anvil
106
+ source_code_uri: https://github.com/standard-procedure/anvil
107
+ changelog_uri: https://github.com/standard-procedure/anvil
108
+ post_install_message:
109
+ rdoc_options: []
110
+ require_paths:
111
+ - lib
112
+ required_ruby_version: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ version: 2.6.0
117
+ required_rubygems_version: !ruby/object:Gem::Requirement
118
+ requirements:
119
+ - - ">="
120
+ - !ruby/object:Gem::Version
121
+ version: '0'
122
+ requirements: []
123
+ rubygems_version: 3.4.14
124
+ signing_key:
125
+ specification_version: 4
126
+ summary: Tools for managing servers and apps built using dokku
127
+ test_files: []