standard-procedure-anvil 0.1.2 → 0.1.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.devcontainer/devcontainer.json +19 -9
- data/Dockerfile +10 -0
- data/assets/cloudinit/dokku.ubuntu-22.yml +48 -0
- data/assets/cloudinit/memcached.ubuntu-22.yml +50 -0
- data/assets/cloudinit/mysql.ubuntu-22.yml +57 -0
- data/assets/cloudinit/opensearch.ubuntu-22.yml +90 -0
- data/assets/cloudinit/redis.ubuntu-22.yml +51 -0
- data/lib/anvil/app/env.rb +30 -0
- data/lib/anvil/app/host_installer.rb +54 -0
- data/lib/anvil/app/install.rb +16 -0
- data/lib/anvil/app.rb +68 -0
- data/lib/anvil/cli.rb +19 -45
- data/lib/anvil/cloudinit/generator.rb +40 -0
- data/lib/anvil/cloudinit.rb +34 -0
- data/lib/anvil/configuration_reader.rb +36 -0
- data/lib/anvil/ssh_executor.rb +11 -23
- data/lib/anvil/subcommand.rb +13 -0
- data/lib/anvil/version.rb +1 -1
- metadata +30 -13
- data/lib/anvil/installer.rb +0 -17
- data/lib/anvil/server_installer/configure_docker.rb +0 -10
- data/lib/anvil/server_installer/configure_dokku.rb +0 -11
- data/lib/anvil/server_installer/configure_firewall.rb +0 -10
- data/lib/anvil/server_installer/configure_ssh_server.rb +0 -12
- data/lib/anvil/server_installer/create_user.rb +0 -18
- data/lib/anvil/server_installer/install_packages.rb +0 -40
- data/lib/anvil/server_installer/install_plugins.rb +0 -14
- data/lib/anvil/server_installer/set_hostname.rb +0 -12
- data/lib/anvil/server_installer/set_timezone.rb +0 -10
- data/lib/anvil/server_installer.rb +0 -66
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3d027adf40008dfa46d97cee23b65a6a8468e935d11e56da74ee9f464b06ff68
|
4
|
+
data.tar.gz: 81aa2e0e4b54cc557338df3198a57bd69ff8da6e1cb53b26e9a437b8aafcc082
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 07fc89824892dbd33fc70dd33b3b8420fc8ac4c334a92c31e98a4b7f94a2f7071a9c5c58b02d9baf20ae3926d54a3fdf206c3cdfac29de48375b98a6847c4aee
|
7
|
+
data.tar.gz: 8b56e4d0f3c223b4225b587d218ac6694397aa528e69a5bef8e92ba3cb9148a89718cfb842e8076d0951ae60516269db55766dd653438694783801448ca394bc
|
@@ -3,20 +3,30 @@
|
|
3
3
|
{
|
4
4
|
"name": "Ruby",
|
5
5
|
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
|
6
|
-
"
|
7
|
-
|
6
|
+
"dockerFile": "../Dockerfile",
|
8
7
|
// Features to add to the dev container. More info: https://containers.dev/features.
|
9
8
|
// "features": {},
|
10
|
-
|
11
9
|
// Use 'forwardPorts' to make a list of ports inside the container available locally.
|
12
10
|
// "forwardPorts": [],
|
13
|
-
|
14
11
|
// Use 'postCreateCommand' to run commands after the container is created.
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
12
|
+
"postCreateCommand": "bundle install",
|
13
|
+
"customizations": {
|
14
|
+
"vscode": {
|
15
|
+
"extensions": [
|
16
|
+
"ms-azuretools.vscode-docker",
|
17
|
+
"donjayamanne.githistory",
|
18
|
+
"github.vscode-github-actions",
|
19
|
+
"GitHub.copilot-chat",
|
20
|
+
"GitHub.vscode-pull-request-github",
|
21
|
+
"aki77.rails-db-schema",
|
22
|
+
"rebornix.ruby",
|
23
|
+
"sibiraj-s.vscode-scss-formatter",
|
24
|
+
"testdouble.vscode-standard-ruby",
|
25
|
+
"bennycode.sort-everything",
|
26
|
+
"Thadeu.vscode-run-rspec-file",
|
27
|
+
]
|
28
|
+
}
|
29
|
+
}
|
20
30
|
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
|
21
31
|
// "remoteUser": "root"
|
22
32
|
}
|
data/Dockerfile
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
#cloud-config
|
2
|
+
users:
|
3
|
+
- name: %{USER}
|
4
|
+
groups: users, admin, docker
|
5
|
+
sudo: ALL=(ALL) NOPASSWD:ALL
|
6
|
+
shell: /bin/bash
|
7
|
+
ssh_authorized_keys:
|
8
|
+
- %{PUBLIC_KEY}
|
9
|
+
packages:
|
10
|
+
- fail2ban
|
11
|
+
- ufw
|
12
|
+
- wget
|
13
|
+
- apt-transport-https
|
14
|
+
package_update: true
|
15
|
+
package_upgrade: true
|
16
|
+
runcmd:
|
17
|
+
# General server setup
|
18
|
+
- timedatectl set-timezone UTC
|
19
|
+
# Fail2Ban setup
|
20
|
+
- printf "[sshd]\nenabled = true\nbanaction = iptables-multiport" > /etc/fail2ban/jail.local
|
21
|
+
- systemctl enable fail2ban
|
22
|
+
# UFW and SSH setup
|
23
|
+
- ufw allow 22/tcp
|
24
|
+
- ufw allow 80/tcp
|
25
|
+
- ufw allow 443/tcp
|
26
|
+
- ufw enable
|
27
|
+
# Harden SSH
|
28
|
+
- sed -i -e '/^\(#\|\)PermitRootLogin/s/^.*$/PermitRootLogin no/' /etc/ssh/sshd_config
|
29
|
+
- sed -i -e '/^\(#\|\)PasswordAuthentication/s/^.*$/PasswordAuthentication no/' /etc/ssh/sshd_config
|
30
|
+
- sed -i -e '/^\(#\|\)X11Forwarding/s/^.*$/X11Forwarding no/' /etc/ssh/sshd_config
|
31
|
+
- sed -i -e '/^\(#\|\)MaxAuthTries/s/^.*$/MaxAuthTries 2/' /etc/ssh/sshd_config
|
32
|
+
- sed -i -e '/^\(#\|\)AllowTcpForwarding/s/^.*$/AllowTcpForwarding no/' /etc/ssh/sshd_config
|
33
|
+
- sed -i -e '/^\(#\|\)AllowAgentForwarding/s/^.*$/AllowAgentForwarding no/' /etc/ssh/sshd_config
|
34
|
+
- sed -i -e '/^\(#\|\)AuthorizedKeysFile/s/^.*$/AuthorizedKeysFile .ssh\/authorized_keys/' /etc/ssh/sshd_config
|
35
|
+
- sed -i '$a AllowUsers %{USER} dokku' /etc/ssh/sshd_config
|
36
|
+
# Dokku setup
|
37
|
+
- echo "dokku dokku/vhost_enable boolean true" | sudo debconf-set-selections
|
38
|
+
- wget https://dokku.com/install/v0.30.7/bootstrap.sh && sudo DOKKU_TAG=v0.30.7 bash bootstrap.sh
|
39
|
+
- cat /home/%{USER}/.ssh/authorized_keys | dokku ssh-keys:add admin
|
40
|
+
- dokku plugin:install https://github.com/dokku/dokku-cron-restart.git cron-restart
|
41
|
+
- dokku plugin:install https://github.com/dokku/dokku-maintenance.git maintenance
|
42
|
+
- dokku plugin:install https://github.com/dokku/dokku-redis.git redis
|
43
|
+
- dokku plugin:install https://github.com/dokku/dokku-mariadb.git mariadb
|
44
|
+
- dokku plugin:install https://github.com/dokku/dokku-memcached.git memcached
|
45
|
+
- dokku plugin:install https://github.com/dokku/dokku-letsencrypt.git letsencrypt
|
46
|
+
- dokku config:set --global DOKKU_LETSENCRYPT_EMAIL=sysadmin@echodek.co
|
47
|
+
- dokku git:set --global deploy-branch main
|
48
|
+
- reboot
|
@@ -0,0 +1,50 @@
|
|
1
|
+
#cloud-config
|
2
|
+
users:
|
3
|
+
- name: %{USER}
|
4
|
+
groups: users, admin, docker
|
5
|
+
sudo: ALL=(ALL) NOPASSWD:ALL
|
6
|
+
shell: /bin/bash
|
7
|
+
ssh_authorized_keys:
|
8
|
+
- %{PUBLIC_KEY}
|
9
|
+
packages:
|
10
|
+
- fail2ban
|
11
|
+
- ufw
|
12
|
+
- wget
|
13
|
+
- memcached
|
14
|
+
- logrotate
|
15
|
+
package_update: true
|
16
|
+
package_upgrade: true
|
17
|
+
runcmd:
|
18
|
+
# General server setup
|
19
|
+
- timedatectl set-timezone UTC
|
20
|
+
# Fail2Ban setup
|
21
|
+
- printf "[sshd]\nenabled = true\nbanaction = iptables-multiport" > /etc/fail2ban/jail.local
|
22
|
+
- systemctl enable fail2ban
|
23
|
+
# UFW and SSH setup
|
24
|
+
- ufw allow 22/tcp
|
25
|
+
- ufw allow 11211/tcp
|
26
|
+
- ufw enable
|
27
|
+
# Harden SSH
|
28
|
+
- sed -i -e '/^\(#\|\)PermitRootLogin/s/^.*$/PermitRootLogin no/' /etc/ssh/sshd_config
|
29
|
+
- sed -i -e '/^\(#\|\)PasswordAuthentication/s/^.*$/PasswordAuthentication no/' /etc/ssh/sshd_config
|
30
|
+
- sed -i -e '/^\(#\|\)X11Forwarding/s/^.*$/X11Forwarding no/' /etc/ssh/sshd_config
|
31
|
+
- sed -i -e '/^\(#\|\)MaxAuthTries/s/^.*$/MaxAuthTries 2/' /etc/ssh/sshd_config
|
32
|
+
- sed -i -e '/^\(#\|\)AllowTcpForwarding/s/^.*$/AllowTcpForwarding no/' /etc/ssh/sshd_config
|
33
|
+
- sed -i -e '/^\(#\|\)AllowAgentForwarding/s/^.*$/AllowAgentForwarding no/' /etc/ssh/sshd_config
|
34
|
+
- sed -i -e '/^\(#\|\)AuthorizedKeysFile/s/^.*$/AuthorizedKeysFile .ssh\/authorized_keys/' /etc/ssh/sshd_config
|
35
|
+
- sed -i '$a AllowUsers %{USER}' /etc/ssh/sshd_config
|
36
|
+
# Set up memcached
|
37
|
+
- sed -i 's/-m 64/-m 512/g' /etc/memcached.conf
|
38
|
+
- sed -i 's/-l 127.0.0.1/-l 0.0.0.0/g' /etc/memcached.conf
|
39
|
+
- systemctl restart memcached.service
|
40
|
+
- |
|
41
|
+
cat > /etc/logrotate.d/memcached << EOF
|
42
|
+
/var/log/redis/memcached*.log {
|
43
|
+
daily
|
44
|
+
missingok
|
45
|
+
rotate 7
|
46
|
+
compress
|
47
|
+
notifempty
|
48
|
+
}
|
49
|
+
EOF
|
50
|
+
- reboot
|
@@ -0,0 +1,57 @@
|
|
1
|
+
#cloud-config
|
2
|
+
users:
|
3
|
+
- name: %{USER}
|
4
|
+
groups: users, admin
|
5
|
+
sudo: ALL=(ALL) NOPASSWD:ALL
|
6
|
+
shell: /bin/bash
|
7
|
+
ssh_authorized_keys:
|
8
|
+
- %{PUBLIC_KEY}
|
9
|
+
packages:
|
10
|
+
- fail2ban
|
11
|
+
- mysql-client
|
12
|
+
- libmysqlclient-dev
|
13
|
+
- ufw
|
14
|
+
package_update: true
|
15
|
+
package_upgrade: true
|
16
|
+
runcmd:
|
17
|
+
# General server setup
|
18
|
+
- timedatectl set-timezone UTC
|
19
|
+
# Install MySQL
|
20
|
+
- echo "mysql-server mysql-server/root_password password root" | sudo debconf-set-selections
|
21
|
+
- echo "mysql-server mysql-server/root_password_again password root" | sudo debconf-set-selections
|
22
|
+
- sudo apt-get -y install mysql-server
|
23
|
+
- |
|
24
|
+
cat >> /etc/mysql/mysql.conf.d/utf8.cnf << CONF
|
25
|
+
[client]
|
26
|
+
default-character-set=utf8mb4
|
27
|
+
|
28
|
+
[mysql]
|
29
|
+
default-character-set=utf8mb4
|
30
|
+
|
31
|
+
[mysqld]
|
32
|
+
init_connect='SET collation_connection = utf8mb4_unicode_ci'
|
33
|
+
init_connect='SET NAMES utf8mb4'
|
34
|
+
character-set-server=utf8mb4
|
35
|
+
collation-server=utf8mb4_unicode_ci
|
36
|
+
skip-character-set-client-handshake
|
37
|
+
CONF
|
38
|
+
- sed -i -e '/^\(#\|\)bind-address/s/^.*$/bind-address = 0.0.0.0/' /etc/mysql/mysql.conf.d/mysqld.cnf
|
39
|
+
# Setup fail2ban
|
40
|
+
- printf "[sshd]\nenabled = true\nbanaction = iptables-multiport" > /etc/fail2ban/jail.local
|
41
|
+
- systemctl enable fail2ban
|
42
|
+
# Start MySQL
|
43
|
+
- systemctl start mysql.service
|
44
|
+
# Setup ufw
|
45
|
+
- ufw allow 22/tcp
|
46
|
+
- ufw allow 3306/tcp
|
47
|
+
- ufw enable
|
48
|
+
# Harden SSH
|
49
|
+
- sed -i -e '/^\(#\|\)PermitRootLogin/s/^.*$/PermitRootLogin no/' /etc/ssh/sshd_config
|
50
|
+
- sed -i -e '/^\(#\|\)PasswordAuthentication/s/^.*$/PasswordAuthentication no/' /etc/ssh/sshd_config
|
51
|
+
- sed -i -e '/^\(#\|\)X11Forwarding/s/^.*$/X11Forwarding no/' /etc/ssh/sshd_config
|
52
|
+
- sed -i -e '/^\(#\|\)MaxAuthTries/s/^.*$/MaxAuthTries 2/' /etc/ssh/sshd_config
|
53
|
+
- sed -i -e '/^\(#\|\)AllowTcpForwarding/s/^.*$/AllowTcpForwarding no/' /etc/ssh/sshd_config
|
54
|
+
- sed -i -e '/^\(#\|\)AllowAgentForwarding/s/^.*$/AllowAgentForwarding no/' /etc/ssh/sshd_config
|
55
|
+
- sed -i -e '/^\(#\|\)AuthorizedKeysFile/s/^.*$/AuthorizedKeysFile .ssh\/authorized_keys/' /etc/ssh/sshd_config
|
56
|
+
- sed -i '$a AllowUsers %{USER}' /etc/ssh/sshd_config
|
57
|
+
- reboot
|
@@ -0,0 +1,90 @@
|
|
1
|
+
#cloud-config
|
2
|
+
users:
|
3
|
+
- name: %{USER}
|
4
|
+
groups: users, admin, docker
|
5
|
+
sudo: ALL=(ALL) NOPASSWD:ALL
|
6
|
+
shell: /bin/bash
|
7
|
+
ssh_authorized_keys:
|
8
|
+
- %{PUBLIC_KEY}
|
9
|
+
packages:
|
10
|
+
- fail2ban
|
11
|
+
- ufw
|
12
|
+
- wget
|
13
|
+
- docker.io
|
14
|
+
- docker-compose
|
15
|
+
- apt-transport-https
|
16
|
+
package_update: true
|
17
|
+
package_upgrade: true
|
18
|
+
runcmd:
|
19
|
+
# General server setup
|
20
|
+
- timedatectl set-timezone UTC
|
21
|
+
# Prepare for OpenSearch
|
22
|
+
- swapoff -a
|
23
|
+
- echo "vm.max_map_count=262144" > /etc/sysctl.d/98-opensearch.conf
|
24
|
+
- sysctl -p /etc/sysctl.d/98-opensearch.conf
|
25
|
+
# Fail2Ban setup
|
26
|
+
- printf "[sshd]\nenabled = true\nbanaction = iptables-multiport" > /etc/fail2ban/jail.local
|
27
|
+
- systemctl enable fail2ban
|
28
|
+
# UFW and SSH setup
|
29
|
+
- ufw allow 22/tcp
|
30
|
+
- ufw allow 9200/tcp
|
31
|
+
- ufw allow 9600/tcp
|
32
|
+
- ufw enable
|
33
|
+
# Harden SSH
|
34
|
+
- sed -i -e '/^\(#\|\)PermitRootLogin/s/^.*$/PermitRootLogin no/' /etc/ssh/sshd_config
|
35
|
+
- sed -i -e '/^\(#\|\)PasswordAuthentication/s/^.*$/PasswordAuthentication no/' /etc/ssh/sshd_config
|
36
|
+
- sed -i -e '/^\(#\|\)X11Forwarding/s/^.*$/X11Forwarding no/' /etc/ssh/sshd_config
|
37
|
+
- sed -i -e '/^\(#\|\)MaxAuthTries/s/^.*$/MaxAuthTries 2/' /etc/ssh/sshd_config
|
38
|
+
- sed -i -e '/^\(#\|\)AllowTcpForwarding/s/^.*$/AllowTcpForwarding no/' /etc/ssh/sshd_config
|
39
|
+
- sed -i -e '/^\(#\|\)AllowAgentForwarding/s/^.*$/AllowAgentForwarding no/' /etc/ssh/sshd_config
|
40
|
+
- sed -i -e '/^\(#\|\)AuthorizedKeysFile/s/^.*$/AuthorizedKeysFile .ssh\/authorized_keys/' /etc/ssh/sshd_config
|
41
|
+
- sed -i '$a AllowUsers %{USER}' /etc/ssh/sshd_config
|
42
|
+
# OpenSearch setup
|
43
|
+
- mkdir -p /etc/opensearch
|
44
|
+
- docker pull opensearchproject/opensearch:latest
|
45
|
+
- docker pull opensearchproject/opensearch-dashboards:latest
|
46
|
+
- |
|
47
|
+
cat >> /etc/opensearch/docker-compose.yml << EOF
|
48
|
+
version: '3'
|
49
|
+
services:
|
50
|
+
search_db:
|
51
|
+
image: opensearchproject/opensearch:latest
|
52
|
+
container_name: search_db
|
53
|
+
environment:
|
54
|
+
- discovery.type=single-node
|
55
|
+
- node.name=search_db
|
56
|
+
- bootstrap.memory_lock=true
|
57
|
+
- plugins.security.disabled=true
|
58
|
+
- "OPENSEARCH_JAVA_OPTS=-Xms4096m -Xmx4096m"
|
59
|
+
ulimits:
|
60
|
+
memlock:
|
61
|
+
soft: -1
|
62
|
+
hard: -1
|
63
|
+
nofile:
|
64
|
+
soft: 65536
|
65
|
+
hard: 65536
|
66
|
+
volumes:
|
67
|
+
- opensearch_data:/usr/share/opensearch/data
|
68
|
+
ports:
|
69
|
+
- 9200:9200
|
70
|
+
- 9600:9600
|
71
|
+
volumes:
|
72
|
+
opensearch_data:
|
73
|
+
EOF
|
74
|
+
- |
|
75
|
+
cat >> /etc/systemd/system/opensearch.service << EOF
|
76
|
+
Description=OpenSearch container
|
77
|
+
Requires=docker.service
|
78
|
+
After=docker.service
|
79
|
+
[Service]
|
80
|
+
WorkingDirectory=/etc/opensearch
|
81
|
+
Restart=always
|
82
|
+
ExecStart=/usr/bin/docker-compose up
|
83
|
+
ExecStop=/usr/bin/docker-compose down
|
84
|
+
[Install]
|
85
|
+
WantedBy=multi-user.target
|
86
|
+
EOF
|
87
|
+
- systemctl daemon-reload
|
88
|
+
- systemctl enable opensearch.service
|
89
|
+
- service opensearch start
|
90
|
+
- reboot
|
@@ -0,0 +1,51 @@
|
|
1
|
+
#cloud-config
|
2
|
+
users:
|
3
|
+
- name: %{USER}
|
4
|
+
groups: users, admin, docker
|
5
|
+
sudo: ALL=(ALL) NOPASSWD:ALL
|
6
|
+
shell: /bin/bash
|
7
|
+
ssh_authorized_keys:
|
8
|
+
- %{PUBLIC_KEY}
|
9
|
+
packages:
|
10
|
+
- fail2ban
|
11
|
+
- ufw
|
12
|
+
- wget
|
13
|
+
- redis-server
|
14
|
+
- logrotate
|
15
|
+
package_update: true
|
16
|
+
package_upgrade: true
|
17
|
+
runcmd:
|
18
|
+
# General server setup
|
19
|
+
- timedatectl set-timezone UTC
|
20
|
+
# Fail2Ban setup
|
21
|
+
- printf "[sshd]\nenabled = true\nbanaction = iptables-multiport" > /etc/fail2ban/jail.local
|
22
|
+
- systemctl enable fail2ban
|
23
|
+
# UFW and SSH setup
|
24
|
+
- ufw allow 22/tcp
|
25
|
+
- ufw allow 6379/tcp
|
26
|
+
- ufw enable
|
27
|
+
# Harden SSH
|
28
|
+
- sed -i -e '/^\(#\|\)PermitRootLogin/s/^.*$/PermitRootLogin no/' /etc/ssh/sshd_config
|
29
|
+
- sed -i -e '/^\(#\|\)PasswordAuthentication/s/^.*$/PasswordAuthentication no/' /etc/ssh/sshd_config
|
30
|
+
- sed -i -e '/^\(#\|\)X11Forwarding/s/^.*$/X11Forwarding no/' /etc/ssh/sshd_config
|
31
|
+
- sed -i -e '/^\(#\|\)MaxAuthTries/s/^.*$/MaxAuthTries 2/' /etc/ssh/sshd_config
|
32
|
+
- sed -i -e '/^\(#\|\)AllowTcpForwarding/s/^.*$/AllowTcpForwarding no/' /etc/ssh/sshd_config
|
33
|
+
- sed -i -e '/^\(#\|\)AllowAgentForwarding/s/^.*$/AllowAgentForwarding no/' /etc/ssh/sshd_config
|
34
|
+
- sed -i -e '/^\(#\|\)AuthorizedKeysFile/s/^.*$/AuthorizedKeysFile .ssh\/authorized_keys/' /etc/ssh/sshd_config
|
35
|
+
- sed -i '$a AllowUsers %{USER}' /etc/ssh/sshd_config
|
36
|
+
# Set up Redis
|
37
|
+
- sed -i 's/supervised no/supervised systemd/g' /etc/redis/redis.conf
|
38
|
+
- sed -i 's/bind 127.0.0.1 ::1/# bind 127.0.0.1 ::1/g' /etc/redis/redis.conf
|
39
|
+
- sed -i 's/protected-mode yes/protected-mode no/g' /etc/redis/redis.conf
|
40
|
+
- systemctl restart redis.service
|
41
|
+
- |
|
42
|
+
cat > /etc/logrotate.d/redis-server << EOF
|
43
|
+
/var/log/redis/redis-server*.log {
|
44
|
+
daily
|
45
|
+
missingok
|
46
|
+
rotate 7
|
47
|
+
compress
|
48
|
+
notifempty
|
49
|
+
}
|
50
|
+
EOF
|
51
|
+
- reboot
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Anvil
|
4
|
+
class App
|
5
|
+
require_relative "../configuration_reader"
|
6
|
+
class Env < Struct.new(:configuration, :host, :secrets)
|
7
|
+
include ConfigurationReader
|
8
|
+
|
9
|
+
def call
|
10
|
+
self.host ||= hosts.first
|
11
|
+
validate host
|
12
|
+
[env_vars_for(host), env_vars_for_app, secrets].compact.join(" ").strip
|
13
|
+
end
|
14
|
+
|
15
|
+
protected
|
16
|
+
|
17
|
+
def env_vars_for host
|
18
|
+
generate_from environment_for(host)
|
19
|
+
end
|
20
|
+
|
21
|
+
def env_vars_for_app
|
22
|
+
generate_from environment_for_app
|
23
|
+
end
|
24
|
+
|
25
|
+
def generate_from variables
|
26
|
+
variables&.join(" ")&.strip
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "standard_procedure/async"
|
4
|
+
module Anvil
|
5
|
+
require_relative "../logger"
|
6
|
+
require_relative "../ssh_executor"
|
7
|
+
require_relative "env"
|
8
|
+
class App
|
9
|
+
class HostInstaller < Struct.new(:configuration, :host, :secrets)
|
10
|
+
include StandardProcedure::Async::Actor
|
11
|
+
|
12
|
+
async :call do
|
13
|
+
Anvil::SshExecutor.new(host, user_for(host), logger).call do |ssh|
|
14
|
+
create_app ssh
|
15
|
+
set_environment ssh
|
16
|
+
set_dokku_options ssh
|
17
|
+
run_post_installation_scripts ssh
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
protected
|
22
|
+
|
23
|
+
def create_app ssh
|
24
|
+
ssh.exec! "dokku apps:create app", "create_app"
|
25
|
+
end
|
26
|
+
|
27
|
+
def set_environment ssh
|
28
|
+
ssh.exec! "dokku config:set app #{Anvil::App::Env.new(configuration, host, secrets).call}", "set_environment"
|
29
|
+
end
|
30
|
+
|
31
|
+
def set_dokku_options ssh
|
32
|
+
ssh.exec! "dokku docker-options:add app run \"--add-host=host.docker.internal:host-gateway\"", "set_dokku_options"
|
33
|
+
ssh.exec! "dokku domains:set app #{configuration_for_app["domain"]}", "set_dokku_options"
|
34
|
+
ssh.exec! "dokku proxy:ports-add app http:80:#{configuration_for_app["port"]}", "set_dokku_options"
|
35
|
+
ssh.exec! "dokku nginx:set app client-max-body-size 512m", "set_dokku_options"
|
36
|
+
ssh.exec! "dokku nginx:set app proxy-read-timeout 60s", "set_dokku_options"
|
37
|
+
ssh.exec! "dokku proxy:build-config app", "set_dokku_options"
|
38
|
+
end
|
39
|
+
|
40
|
+
def run_post_installation_scripts ssh
|
41
|
+
configuration_for_app.fetch("scripts")&.fetch("post_install")&.each do |script|
|
42
|
+
ssh.exec! script, "run_post_installation_scripts"
|
43
|
+
end
|
44
|
+
configuration_for(host).fetch("scripts")&.fetch("post_install")&.each do |script|
|
45
|
+
ssh.exec! script, "run_post_installation_scripts"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def logger
|
50
|
+
@logger ||= Anvil::Logger.new("HostInstaller - #{host}")
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Anvil
|
4
|
+
class App
|
5
|
+
require_relative "host_installer"
|
6
|
+
require_relative "../configuration_reader"
|
7
|
+
class Install < Struct.new(:configuration, :secrets)
|
8
|
+
include ConfigurationReader
|
9
|
+
def call
|
10
|
+
hosts.each do |host|
|
11
|
+
HostInstaller.new(configuration, host, secrets).call
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
data/lib/anvil/app.rb
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "subcommand"
|
4
|
+
require "yaml"
|
5
|
+
|
6
|
+
module Anvil
|
7
|
+
class App < Anvil::SubCommandBase
|
8
|
+
require_relative "app/env"
|
9
|
+
|
10
|
+
desc "env", "Generate environment variables for an app"
|
11
|
+
long_desc <<-DESC
|
12
|
+
List the environment variables for an app (on a given host)
|
13
|
+
|
14
|
+
Example:
|
15
|
+
anvil app env /path/to/config
|
16
|
+
|
17
|
+
If the /path/to/config is not supplied, it defaults to deploy.yml
|
18
|
+
|
19
|
+
Options:
|
20
|
+
|
21
|
+
--host, -h: The server that the environment variables should be generated for - only required if multiple servers are configured
|
22
|
+
|
23
|
+
--secrets, -s: The path to a file containing secrets to be injected into the environment variables
|
24
|
+
|
25
|
+
--secrets-stdin, -S: Read secrets from STDIN instead of a file
|
26
|
+
DESC
|
27
|
+
option :host, type: :string, default: nil, aliases: "-h"
|
28
|
+
option :secrets, type: :string, default: nil, aliases: "-s"
|
29
|
+
option :secrets_stdin, type: :boolean, default: false, aliases: "-S"
|
30
|
+
def env filename = "deploy.yml"
|
31
|
+
configuration = YAML.load_file(filename)
|
32
|
+
secrets = read_secrets filename: options[:secrets], stdin: options[:secrets_stdin]
|
33
|
+
puts Anvil::App::Env.new(configuration, options[:host], secrets).call
|
34
|
+
end
|
35
|
+
|
36
|
+
desc "install", "Install an app"
|
37
|
+
long_desc <<-DESC
|
38
|
+
Install an app on the hosts specified in the configuration.
|
39
|
+
|
40
|
+
This logs in to each host in turn, using the user specified in the configuration file, it initialises the app, using dokku, then sets up the environment variables and other dokku options and finally runs any post-installation scripts.
|
41
|
+
|
42
|
+
In order to SSH in to the server correctly, it expects the private key to be available via your SSH agent. To test this, make sure you can `ssh user@host` without being prompted for a password.
|
43
|
+
|
44
|
+
Example:
|
45
|
+
anvil app install /path/to/config
|
46
|
+
If the /path/to/config is not supplied, it defaults to deploy.yml
|
47
|
+
|
48
|
+
Options:
|
49
|
+
|
50
|
+
--secrets, -s: The path to a file containing secrets to be injected into the environment variables
|
51
|
+
|
52
|
+
--secrets-stdin, -S: Read secrets from STDIN instead of a file
|
53
|
+
DESC
|
54
|
+
def install filename = "deploy.yml"
|
55
|
+
configuration = YAML.load_file(filename)
|
56
|
+
secrets = read_secrets filename: options[:secrets], stdin: options[:secrets_stdin]
|
57
|
+
Anvil::App::Install.new(configuration, secrets).call
|
58
|
+
end
|
59
|
+
|
60
|
+
protected
|
61
|
+
|
62
|
+
def read_secrets(filename: nil, stdin: false)
|
63
|
+
return nil if filename.nil? && !stdin
|
64
|
+
return $stdin.read if stdin
|
65
|
+
return File.read(filename) if File.exist?(filename)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
data/lib/anvil/cli.rb
CHANGED
@@ -1,56 +1,30 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "thor"
|
4
|
-
require_relative "
|
5
|
-
|
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.
|
4
|
+
require_relative "cloudinit"
|
5
|
+
require_relative "app"
|
9
6
|
|
10
|
-
|
11
|
-
|
7
|
+
module Anvil
|
8
|
+
class Cli < Thor
|
9
|
+
desc "cloudinit", "Generate a cloudinit configuration"
|
10
|
+
subcommand "cloudinit", Anvil::Cloudinit
|
12
11
|
|
13
|
-
|
12
|
+
desc "app", "Install or deploy a dokku app"
|
13
|
+
subcommand "app", Anvil::App
|
14
14
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
option :private_key, type: :string, default: nil, aliases: "-k"
|
20
|
-
option :passphrase, type: :string, default: nil, aliases: "-p"
|
21
|
-
def install config = "anvil.yml", private_key = nil, passphrase = nil
|
22
|
-
Anvil::Installer.new(configuration_from(config), private_key, passphrase).call
|
23
|
-
end
|
24
|
-
|
25
|
-
desc "anvil deploy", "Deploy the current app"
|
26
|
-
long_desc <<-DESC
|
27
|
-
Deploy the current app to the server to the servers
|
28
|
-
|
29
|
-
Example:
|
30
|
-
`anvil deploy CONFIG`
|
31
|
-
|
32
|
-
The default CONFIG file is `anvil.yml` in the current directory.
|
33
|
-
Options:
|
34
|
-
--private_key, -k: The path to the key certificate file to use when connecting to the server.
|
35
|
-
--passphrase, -p: The passphrase to use when connecting to the server.
|
36
|
-
DESC
|
37
|
-
option :private_key, type: :string, default: nil, aliases: "-k"
|
38
|
-
option :passphrase, type: :string, default: nil, aliases: "-p"
|
39
|
-
def deploy config = "anvil.yml", private_key = nil, passphrase = nil
|
40
|
-
end
|
41
|
-
|
42
|
-
desc "anvil version", "Print the version of the anvil gem"
|
43
|
-
def version
|
44
|
-
puts Anvil::VERSION
|
45
|
-
end
|
15
|
+
desc "version", "Print the version of the anvil gem"
|
16
|
+
def version
|
17
|
+
puts Anvil::VERSION
|
18
|
+
end
|
46
19
|
|
47
|
-
|
48
|
-
|
49
|
-
|
20
|
+
def self.exit_on_failure?
|
21
|
+
true
|
22
|
+
end
|
50
23
|
|
51
|
-
|
24
|
+
protected
|
52
25
|
|
53
|
-
|
54
|
-
|
26
|
+
def configuration_from(file_name)
|
27
|
+
@configuration ||= YAML.load_file(file_name)
|
28
|
+
end
|
55
29
|
end
|
56
30
|
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Anvil
|
4
|
+
class Cloudinit
|
5
|
+
class Generator < Struct.new(:filename, :user, :public_key_path)
|
6
|
+
def call
|
7
|
+
public_key = public_key_path.to_s.gsub("~", Dir.home)
|
8
|
+
|
9
|
+
if File.exist?(public_key)
|
10
|
+
puts File.read(filename).gsub("%{USER}", user).gsub("%{PUBLIC_KEY}", File.read(public_key))
|
11
|
+
else
|
12
|
+
puts "Cannot find public key file at #{public_key}"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# Compare this snippet from assets/cloudinit/mysql.ubuntu-22.yml:
|
20
|
+
# # frozen_string_literal: true
|
21
|
+
#
|
22
|
+
# ---
|
23
|
+
# packages:
|
24
|
+
# - mysql-server
|
25
|
+
# - mysql-client
|
26
|
+
# - libmysqlclient-dev
|
27
|
+
#
|
28
|
+
# users:
|
29
|
+
# - name: <%= user %>
|
30
|
+
# groups: sudo
|
31
|
+
# shell: /bin/bash
|
32
|
+
# sudo: ALL=(ALL) NOPASSWD:ALL
|
33
|
+
# ssh_authorized_keys:
|
34
|
+
# - <%= public_key %>
|
35
|
+
#
|
36
|
+
# files:
|
37
|
+
# - path: /etc/mysql/mysql.conf.d/mysqld.cnf
|
38
|
+
# content: |
|
39
|
+
# [mysqld]
|
40
|
+
# bind-address =
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "subcommand"
|
4
|
+
|
5
|
+
module Anvil
|
6
|
+
class Cloudinit < Anvil::SubCommandBase
|
7
|
+
require_relative "cloudinit/generator"
|
8
|
+
|
9
|
+
desc "list", "List cloudinit generators"
|
10
|
+
def list
|
11
|
+
Dir[File.dirname(__FILE__) + "/../../assets/cloudinit/*.yml"].each do |filename|
|
12
|
+
puts File.basename(filename.to_s, ".yml")
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
desc "generate", "Generate a cloudinit configuration"
|
17
|
+
long_desc <<-DESC
|
18
|
+
Generate a cloudinit configuration for a server
|
19
|
+
|
20
|
+
Example:
|
21
|
+
anvil cloudinit generate mysql.ubuntu-22 --user dbuser --public_key ~/.ssh/my_key.pub
|
22
|
+
|
23
|
+
Options:
|
24
|
+
--user, -u: The user to create on the server - defaults to app
|
25
|
+
--public_key, -k: The path to the public key file that will be installed for the user - default to ~/.ssh/id_rsa.pub
|
26
|
+
DESC
|
27
|
+
option :user, type: :string, default: "app", aliases: "-u"
|
28
|
+
option :public_key, type: :string, default: "~/.ssh/id_rsa.pub", aliases: "-k"
|
29
|
+
def generate configuration
|
30
|
+
filename = File.dirname(__FILE__) + "/../../assets/cloudinit/#{configuration}.yml"
|
31
|
+
Anvil::Cloudinit::Generator.new(filename, options[:user], options[:public_key]).call
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Anvil
|
4
|
+
# A set of utility methods for reading configuration data
|
5
|
+
# It expects the implemneting class to have a `configuration` method
|
6
|
+
module ConfigurationReader
|
7
|
+
def hosts
|
8
|
+
configuration["hosts"].collect { |host_data| host_data.keys }.flatten
|
9
|
+
end
|
10
|
+
|
11
|
+
def validate hostname
|
12
|
+
raise ArgumentError.new("Host #{hostname} is not in the configuration hosts list") unless hosts.include? hostname
|
13
|
+
end
|
14
|
+
|
15
|
+
def configuration_for_app
|
16
|
+
configuration["app"]
|
17
|
+
end
|
18
|
+
|
19
|
+
def environment_for_app
|
20
|
+
configuration_for_app.fetch "environment", []
|
21
|
+
end
|
22
|
+
|
23
|
+
def configuration_for hostname
|
24
|
+
host_config = configuration["hosts"].find { |host_data| host_data.key?(hostname) ? host_data[hostname] : nil }
|
25
|
+
host_config&.fetch(hostname)
|
26
|
+
end
|
27
|
+
|
28
|
+
def environment_for hostname
|
29
|
+
configuration_for(hostname)&.fetch "environment", []
|
30
|
+
end
|
31
|
+
|
32
|
+
def user_for hostname
|
33
|
+
configuration_for(hostname)&.fetch "user", nil
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
data/lib/anvil/ssh_executor.rb
CHANGED
@@ -7,31 +7,19 @@ require "net/ssh"
|
|
7
7
|
# - without sudo it runs the scripts as supplied
|
8
8
|
# - with sudo it creates a script on the remote server, runs it via sudo, and then deletes it
|
9
9
|
# If supplied, it will also write the output of the script to a logger.
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
10
|
+
module Anvil
|
11
|
+
class SshExecutor < Struct.new(:hostname, :user, :logger)
|
12
|
+
def call &block
|
13
|
+
@connection = Net::SSH.start hostname, user, use_agent: true
|
14
|
+
block.call self
|
15
|
+
end
|
15
16
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
17
|
+
def exec! script, category = ""
|
18
|
+
@connection.exec! script do |channel, stream, data|
|
19
|
+
data.to_s.split("\n") do |line|
|
20
|
+
logger&.info line, category
|
21
|
+
end
|
21
22
|
end
|
22
23
|
end
|
23
24
|
end
|
24
|
-
|
25
|
-
protected
|
26
|
-
|
27
|
-
def exec_without_sudo script, category = "", &block
|
28
|
-
@connection.exec! script, &block
|
29
|
-
end
|
30
|
-
|
31
|
-
def exec_with_sudo script, category = "", &block
|
32
|
-
@connection.exec! "cat >> exec.sh << SCRIPT\n#{script}\nSCRIPT", &block
|
33
|
-
@connection.exec! "chmod 755 exec.sh", &block
|
34
|
-
@connection.exec! "sudo ./exec.sh", &block
|
35
|
-
@connection.exec! "rm exec.sh", &block
|
36
|
-
end
|
37
25
|
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require "thor"
|
2
|
+
|
3
|
+
module Anvil
|
4
|
+
class SubCommandBase < Thor
|
5
|
+
def self.banner(command, namespace = nil, subcommand = false)
|
6
|
+
"#{basename} #{subcommand_prefix} #{command.usage}"
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.subcommand_prefix
|
10
|
+
name.gsub(%r{.*::}, "").gsub(%r{^[A-Z]}) { |match| match[0].downcase }.gsub(%r{[A-Z]}) { |match| "-#{match[0].downcase}" }
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
data/lib/anvil/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: standard-procedure-anvil
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Rahoul Baruah
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-06-
|
11
|
+
date: 2023-06-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: thor
|
@@ -66,6 +66,20 @@ dependencies:
|
|
66
66
|
- - ">="
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: standard-procedure-async
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
69
83
|
description: Tools for managing servers and apps built using dokku
|
70
84
|
email:
|
71
85
|
- rahoulb@standardprocedure.app
|
@@ -77,25 +91,28 @@ files:
|
|
77
91
|
- ".rspec"
|
78
92
|
- ".standard.yml"
|
79
93
|
- CHANGELOG.md
|
94
|
+
- Dockerfile
|
80
95
|
- LICENSE.txt
|
81
96
|
- README.md
|
82
97
|
- Rakefile
|
98
|
+
- assets/cloudinit/dokku.ubuntu-22.yml
|
99
|
+
- assets/cloudinit/memcached.ubuntu-22.yml
|
100
|
+
- assets/cloudinit/mysql.ubuntu-22.yml
|
101
|
+
- assets/cloudinit/opensearch.ubuntu-22.yml
|
102
|
+
- assets/cloudinit/redis.ubuntu-22.yml
|
83
103
|
- exe/anvil
|
84
104
|
- lib/anvil.rb
|
105
|
+
- lib/anvil/app.rb
|
106
|
+
- lib/anvil/app/env.rb
|
107
|
+
- lib/anvil/app/host_installer.rb
|
108
|
+
- lib/anvil/app/install.rb
|
85
109
|
- lib/anvil/cli.rb
|
86
|
-
- lib/anvil/
|
110
|
+
- lib/anvil/cloudinit.rb
|
111
|
+
- lib/anvil/cloudinit/generator.rb
|
112
|
+
- lib/anvil/configuration_reader.rb
|
87
113
|
- lib/anvil/logger.rb
|
88
|
-
- lib/anvil/server_installer.rb
|
89
|
-
- lib/anvil/server_installer/configure_docker.rb
|
90
|
-
- lib/anvil/server_installer/configure_dokku.rb
|
91
|
-
- lib/anvil/server_installer/configure_firewall.rb
|
92
|
-
- lib/anvil/server_installer/configure_ssh_server.rb
|
93
|
-
- lib/anvil/server_installer/create_user.rb
|
94
|
-
- lib/anvil/server_installer/install_packages.rb
|
95
|
-
- lib/anvil/server_installer/install_plugins.rb
|
96
|
-
- lib/anvil/server_installer/set_hostname.rb
|
97
|
-
- lib/anvil/server_installer/set_timezone.rb
|
98
114
|
- lib/anvil/ssh_executor.rb
|
115
|
+
- lib/anvil/subcommand.rb
|
99
116
|
- lib/anvil/version.rb
|
100
117
|
- sig/standard/procedure/anvil.rbs
|
101
118
|
homepage: https://github.com/standard-procedure/anvil
|
data/lib/anvil/installer.rb
DELETED
@@ -1,17 +0,0 @@
|
|
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
|
@@ -1,10 +0,0 @@
|
|
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
|
@@ -1,11 +0,0 @@
|
|
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
|
@@ -1,10 +0,0 @@
|
|
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
|
@@ -1,12 +0,0 @@
|
|
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
|
@@ -1,18 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
class Anvil::ServerInstaller::CreateUser < Struct.new(:ssh_connection, :user_name)
|
4
|
-
def call
|
5
|
-
script = <<~SCRIPT
|
6
|
-
if id -u #{user_name} >/dev/null 2>&1; then
|
7
|
-
echo "#{user_name} already exists"
|
8
|
-
else
|
9
|
-
echo "Adding #{user_name}"
|
10
|
-
adduser --disabled-password --gecos "" #{user_name}
|
11
|
-
usermod -aG sudo #{user_name}
|
12
|
-
usermod -aG docker #{user_name}
|
13
|
-
echo "#{user_name} ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers
|
14
|
-
fi
|
15
|
-
SCRIPT
|
16
|
-
ssh_connection.exec! script, "CreateUser"
|
17
|
-
end
|
18
|
-
end
|
@@ -1,40 +0,0 @@
|
|
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
|
@@ -1,14 +0,0 @@
|
|
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
|
@@ -1,12 +0,0 @@
|
|
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
|
@@ -1,66 +0,0 @@
|
|
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
|
-
# You can specify a custom logger or SSH executor using the options hash.
|
17
|
-
class Anvil::ServerInstaller < Struct.new(:hostname, :configuration, :private_key, :passphrase, :options)
|
18
|
-
require_relative "server_installer/set_hostname"
|
19
|
-
require_relative "server_installer/set_timezone"
|
20
|
-
require_relative "server_installer/install_packages"
|
21
|
-
require_relative "server_installer/create_user"
|
22
|
-
require_relative "server_installer/configure_dokku"
|
23
|
-
require_relative "server_installer/configure_docker"
|
24
|
-
require_relative "server_installer/install_plugins"
|
25
|
-
require_relative "server_installer/configure_firewall"
|
26
|
-
require_relative "server_installer/configure_ssh_server"
|
27
|
-
|
28
|
-
def call
|
29
|
-
ssh_executor.call do |ssh_connection|
|
30
|
-
logger.info "SetHostname"
|
31
|
-
Anvil::ServerInstaller::SetHostname.new(ssh_connection, hostname).call
|
32
|
-
logger.info "SetTimezone"
|
33
|
-
Anvil::ServerInstaller::SetTimezone.new(ssh_connection, server_configuration["timezone"]).call
|
34
|
-
logger.info "InstallPackages"
|
35
|
-
Anvil::ServerInstaller::InstallPackages.new(ssh_connection, server_configuration["public_key"]).call
|
36
|
-
logger.info "ConfigureDokku"
|
37
|
-
Anvil::ServerInstaller::ConfigureDokku.new(ssh_connection, hostname).call
|
38
|
-
logger.info "CreateUsers"
|
39
|
-
Anvil::ServerInstaller::CreateUser.new(ssh_connection, server_configuration["app_user"]).call
|
40
|
-
logger.info "InstallPlugins"
|
41
|
-
Anvil::ServerInstaller::InstallPlugins.new(ssh_connection, server_configuration["plugins"]).call
|
42
|
-
logger.info "ConfigureDocker"
|
43
|
-
Anvil::ServerInstaller::ConfigureDocker.new(ssh_connection).call
|
44
|
-
logger.info "ConfigureFirewall"
|
45
|
-
Anvil::ServerInstaller::ConfigureFirewall.new(ssh_connection, server_configuration["ports"]).call
|
46
|
-
logger.info "ConfigureSshServer"
|
47
|
-
Anvil::ServerInstaller::ConfigureSshServer.new(ssh_connection).call
|
48
|
-
end
|
49
|
-
end
|
50
|
-
|
51
|
-
def server_configuration
|
52
|
-
configuration["server"]
|
53
|
-
end
|
54
|
-
|
55
|
-
def options
|
56
|
-
super || {}
|
57
|
-
end
|
58
|
-
|
59
|
-
def logger
|
60
|
-
options[:logger].nil? ? Anvil::Logger.new(hostname) : options[:logger]
|
61
|
-
end
|
62
|
-
|
63
|
-
def ssh_executor
|
64
|
-
options[:ssh_executor].nil? ? Anvil::SshExecutor.new(hostname, server_configuration["install_user"], server_configuration["use_sudo"], logger) : options[:ssh_executor]
|
65
|
-
end
|
66
|
-
end
|