mimas 0.1.0
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 +7 -0
- data/.gitignore +10 -0
- data/Gemfile +12 -0
- data/Gemfile.lock +106 -0
- data/LICENSE.txt +22 -0
- data/README.md +346 -0
- data/Rakefile +4 -0
- data/bin/mimas +5 -0
- data/lib/mimas/archiver.rb +68 -0
- data/lib/mimas/base_command.rb +22 -0
- data/lib/mimas/cli.rb +22 -0
- data/lib/mimas/commands/console.rb +21 -0
- data/lib/mimas/commands/init.rb +63 -0
- data/lib/mimas/commands/install.rb +21 -0
- data/lib/mimas/commands/provision.rb +50 -0
- data/lib/mimas/commands/push.rb +28 -0
- data/lib/mimas/commands/setup/caddy.rb +18 -0
- data/lib/mimas/commands/setup/deploy_user.rb +16 -0
- data/lib/mimas/commands/setup/ruby.rb +17 -0
- data/lib/mimas/commands/setup.rb +10 -0
- data/lib/mimas/config.rb +124 -0
- data/lib/mimas/deployment/caddy.rb +39 -0
- data/lib/mimas/deployment/files.rb +51 -0
- data/lib/mimas/deployment/paths.rb +54 -0
- data/lib/mimas/deployment/ruby_app.rb +126 -0
- data/lib/mimas/deployment/static_site.rb +28 -0
- data/lib/mimas/deployment.rb +27 -0
- data/lib/mimas/hooks.rb +25 -0
- data/lib/mimas/plugins.rb +19 -0
- data/lib/mimas/scripts/configure_ruby.sh +19 -0
- data/lib/mimas/scripts/create_deploy_user.sh +30 -0
- data/lib/mimas/scripts/harden.sh +125 -0
- data/lib/mimas/scripts/install_caddy.sh +51 -0
- data/lib/mimas/scripts/install_ruby_prerequisites.sh +26 -0
- data/lib/mimas/server/caddy.rb +17 -0
- data/lib/mimas/server/console.rb +34 -0
- data/lib/mimas/server/deploy_user.rb +11 -0
- data/lib/mimas/server/harden.rb +20 -0
- data/lib/mimas/server/prerequisites.rb +25 -0
- data/lib/mimas/server/ruby.rb +37 -0
- data/lib/mimas/server.rb +37 -0
- data/lib/mimas/site.rb +24 -0
- data/lib/mimas/ssh.rb +78 -0
- data/lib/mimas/template.rb +28 -0
- data/lib/mimas/templates/Caddyfile.ruby.erb +12 -0
- data/lib/mimas/templates/Caddyfile.static.erb +15 -0
- data/lib/mimas/templates/caddy.service.erb +19 -0
- data/lib/mimas/templates/config.rb.erb +51 -0
- data/lib/mimas/templates/falcon.rb.erb +12 -0
- data/lib/mimas/templates/mimas.env.erb +2 -0
- data/lib/mimas/templates/puma.rb +11 -0
- data/lib/mimas/templates/systemd.service.erb +20 -0
- data/lib/mimas/terminal/ansi.rb +71 -0
- data/lib/mimas/terminal/printer.rb +14 -0
- data/lib/mimas/version.rb +3 -0
- data/lib/mimas.rb +48 -0
- data/mimas.gemspec +29 -0
- data/scripts/release +8 -0
- data/scripts/test +7 -0
- data/tmp/.keep +0 -0
- metadata +181 -0
data/lib/mimas/hooks.rb
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
module Mimas
|
|
2
|
+
class Hooks
|
|
3
|
+
POINTS = [ :before_push, :after_push ]
|
|
4
|
+
|
|
5
|
+
def initialize
|
|
6
|
+
@hooks = Hash[POINTS.zip([[], [], []])].freeze
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def [](hook)
|
|
10
|
+
@hooks[hook]
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def merge(with_hooks)
|
|
14
|
+
POINTS.each do |point|
|
|
15
|
+
@hooks[point].push(*with_hooks[point])
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
POINTS.each do |point|
|
|
20
|
+
define_method(point) do |&block|
|
|
21
|
+
@hooks[point] << block
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module Mimas
|
|
2
|
+
MUTEX = Mutex.new
|
|
3
|
+
private_constant :MUTEX
|
|
4
|
+
|
|
5
|
+
PLUGINS = Set.new([])
|
|
6
|
+
private_constant :PLUGINS
|
|
7
|
+
|
|
8
|
+
module Plugins
|
|
9
|
+
extend self
|
|
10
|
+
|
|
11
|
+
def fetch
|
|
12
|
+
MUTEX.synchronize { PLUGINS }
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def register(plugin)
|
|
16
|
+
MUTEX.synchronize { PLUGINS << plugin }
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
|
|
3
|
+
set -e
|
|
4
|
+
|
|
5
|
+
cat << STRING >> ~/.profile
|
|
6
|
+
|
|
7
|
+
# Use chruby to auto-switch Ruby versions
|
|
8
|
+
source /usr/local/share/chruby/chruby.sh
|
|
9
|
+
source /usr/local/share/chruby/auto.sh
|
|
10
|
+
STRING
|
|
11
|
+
|
|
12
|
+
source /usr/local/share/chruby/chruby.sh
|
|
13
|
+
source /usr/local/share/chruby/auto.sh
|
|
14
|
+
|
|
15
|
+
echo 'gem: --no-document' >> ~/.gemrc
|
|
16
|
+
|
|
17
|
+
gem update --system
|
|
18
|
+
gem install bundler
|
|
19
|
+
bundle config set --global without 'development test'
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
|
|
3
|
+
set -euo pipefail
|
|
4
|
+
export DEBIAN_FRONTEND=noninteractive
|
|
5
|
+
|
|
6
|
+
DEPLOY_USER="${DEPLOY_USER:-deploy}"
|
|
7
|
+
|
|
8
|
+
if ! id $DEPLOY_USER >/dev/null 2>&1; then
|
|
9
|
+
printf "Creating the $DEPLOY_USER user\n\n"
|
|
10
|
+
useradd $DEPLOY_USER -s /bin/bash -m
|
|
11
|
+
printf "$DEPLOY_USER user created\n\n"
|
|
12
|
+
else
|
|
13
|
+
printf "$DEPLOY_USER user exists. Configuring the user..."
|
|
14
|
+
fi
|
|
15
|
+
|
|
16
|
+
sudo passwd -ld $DEPLOY_USER
|
|
17
|
+
sudo loginctl enable-linger $DEPLOY_USER
|
|
18
|
+
sudo rsync --archive --chown=$DEPLOY_USER:$DEPLOY_USER ~/.ssh /home/$DEPLOY_USER
|
|
19
|
+
|
|
20
|
+
sudo touch /etc/logrotate.d/$DEPLOY_USER
|
|
21
|
+
sudo cat << STRING > /etc/logrotate.d/$DEPLOY_USER
|
|
22
|
+
/home/$DEPLOY_USER/http/*/logs/*.log {
|
|
23
|
+
daily
|
|
24
|
+
rotate 30
|
|
25
|
+
compress
|
|
26
|
+
missingok
|
|
27
|
+
}
|
|
28
|
+
STRING
|
|
29
|
+
|
|
30
|
+
printf "$DEPLOY_USER user configured\n\n"
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
|
|
3
|
+
set -euo pipefail
|
|
4
|
+
export DEBIAN_FRONTEND=noninteractive
|
|
5
|
+
|
|
6
|
+
printf "Upgrading APT\n\n"
|
|
7
|
+
apt-get update && apt-get -y upgrade
|
|
8
|
+
|
|
9
|
+
printf "Enabling UFW\n\n"
|
|
10
|
+
ufw default deny incoming
|
|
11
|
+
ufw default allow outgoing
|
|
12
|
+
ufw allow OpenSSH
|
|
13
|
+
ufw allow http
|
|
14
|
+
ufw allow https
|
|
15
|
+
ufw --force enable
|
|
16
|
+
printf "UFW enabled\n\n"
|
|
17
|
+
|
|
18
|
+
printf "Creating the admin user\n\n"
|
|
19
|
+
|
|
20
|
+
if ! id admin >/dev/null 2>&1; then
|
|
21
|
+
# Add a new user, set the default shell, and create a home directory
|
|
22
|
+
useradd admin -s /bin/bash -m
|
|
23
|
+
fi
|
|
24
|
+
|
|
25
|
+
# Disable password login
|
|
26
|
+
passwd -ld admin
|
|
27
|
+
# Sync the private keys to allow SSH access
|
|
28
|
+
rsync --archive --chown=admin:admin ~/.ssh /home/admin
|
|
29
|
+
|
|
30
|
+
# Allow passwordless sudo for `admin`
|
|
31
|
+
echo "%admin $(cat /etc/hostname)=(root) NOPASSWD:ALL" > /etc/sudoers.d/admin
|
|
32
|
+
|
|
33
|
+
# Validate the modified sudoers file
|
|
34
|
+
if ! visudo -c -f /etc/sudoers.d/admin; then
|
|
35
|
+
printf "Failed to configure passwordless sudo for the admin user.\n\n"
|
|
36
|
+
exit 1
|
|
37
|
+
fi
|
|
38
|
+
|
|
39
|
+
printf "Installing fail2ban\n\n"
|
|
40
|
+
# Install fail2ban to block abusive IPs
|
|
41
|
+
apt-get install fail2ban -y
|
|
42
|
+
touch /etc/fail2ban/jail.local
|
|
43
|
+
cat << STRING >> /etc/fail2ban/jail.local
|
|
44
|
+
[DEFAULT]
|
|
45
|
+
bantime = 1d
|
|
46
|
+
findtime = 10m
|
|
47
|
+
maxretry = 5
|
|
48
|
+
|
|
49
|
+
[sshd]
|
|
50
|
+
enabled = true
|
|
51
|
+
STRING
|
|
52
|
+
systemctl enable fail2ban
|
|
53
|
+
printf "fail2ban installed and enabled\n\n"
|
|
54
|
+
|
|
55
|
+
printf "Enhanching kernel and system security...\n\n"
|
|
56
|
+
cat << STRING > /etc/sysctl.d/mimas-security.conf
|
|
57
|
+
# Disable ICMP redirects.
|
|
58
|
+
net.ipv4.conf.all.send_redirects = 0
|
|
59
|
+
net.ipv4.conf.default.send_redirects = 0
|
|
60
|
+
net.ipv4.conf.all.accept_redirects = 0
|
|
61
|
+
net.ipv4.conf.default.accept_redirects = 0
|
|
62
|
+
net.ipv6.conf.all.accept_redirects = 0
|
|
63
|
+
net.ipv6.conf.default.accept_redirects = 0
|
|
64
|
+
|
|
65
|
+
# ICMP noise hygiene.
|
|
66
|
+
net.ipv4.icmp_echo_ignore_broadcasts = 1
|
|
67
|
+
net.ipv4.icmp_ignore_bogus_error_responses = 1
|
|
68
|
+
|
|
69
|
+
# Drop spoofed/martian packets at the door.
|
|
70
|
+
net.ipv4.conf.all.rp_filter = 1
|
|
71
|
+
net.ipv4.conf.default.rp_filter = 1
|
|
72
|
+
net.ipv4.conf.all.accept_source_route = 0
|
|
73
|
+
net.ipv4.conf.default.accept_source_route = 0
|
|
74
|
+
net.ipv6.conf.all.accept_source_route = 0
|
|
75
|
+
net.ipv6.conf.default.accept_source_route = 0
|
|
76
|
+
|
|
77
|
+
# SYN-flood resilience as the server handles
|
|
78
|
+
# port 80/443 traffic from the internet.
|
|
79
|
+
net.ipv4.tcp_syncookies = 1
|
|
80
|
+
net.ipv4.tcp_synack_retries = 2
|
|
81
|
+
net.ipv4.tcp_syn_retries = 5
|
|
82
|
+
|
|
83
|
+
# Hide kernel internals from unprivileged eyes (kernel pointer leaks, dmesg,
|
|
84
|
+
# perf side channels) and disable unprivileged eBPF.
|
|
85
|
+
kernel.kptr_restrict = 2
|
|
86
|
+
kernel.dmesg_restrict = 1
|
|
87
|
+
kernel.perf_event_paranoid = 3
|
|
88
|
+
kernel.unprivileged_bpf_disabled = 1
|
|
89
|
+
net.core.bpf_jit_harden = 2
|
|
90
|
+
|
|
91
|
+
# Only processes with CAP_SYS_PTRACE may ptrace.
|
|
92
|
+
kernel.yama.ptrace_scope = 2
|
|
93
|
+
|
|
94
|
+
# Block runtime kernel replacement via kexec (one-way until reboot).
|
|
95
|
+
# CAVEAT: if you ever set up kdump crash dumps, remove this line first.
|
|
96
|
+
kernel.kexec_load_disabled = 1
|
|
97
|
+
|
|
98
|
+
# Filesystem link/dump hardening.
|
|
99
|
+
fs.protected_hardlinks = 1
|
|
100
|
+
fs.protected_symlinks = 1
|
|
101
|
+
fs.suid_dumpable = 0
|
|
102
|
+
|
|
103
|
+
# Turn off Martian logging
|
|
104
|
+
net.ipv4.conf.all.log_martians = 0
|
|
105
|
+
net.ipv4.conf.default.log_martians = 0
|
|
106
|
+
STRING
|
|
107
|
+
|
|
108
|
+
sysctl --system >/dev/null
|
|
109
|
+
|
|
110
|
+
printf "System security settings applied.\n\n"
|
|
111
|
+
|
|
112
|
+
printf "Installing molly-guard\n\n"
|
|
113
|
+
apt-get install molly-guard
|
|
114
|
+
printf "molly-guard installed.\n\n"
|
|
115
|
+
|
|
116
|
+
# Disable root login and password authentication
|
|
117
|
+
touch /etc/ssh/sshd_config.d/mimas.conf
|
|
118
|
+
cat << STRING >> /etc/ssh/sshd_config.d/mimas.conf
|
|
119
|
+
PermitRootLogin no
|
|
120
|
+
PasswordAuthentication no
|
|
121
|
+
STRING
|
|
122
|
+
systemctl restart ssh
|
|
123
|
+
passwd -ld root
|
|
124
|
+
|
|
125
|
+
printf "Password auth and root login disabled\n\n"
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
|
|
3
|
+
set -euo pipefail
|
|
4
|
+
export DEBIAN_FRONTEND=noninteractive
|
|
5
|
+
|
|
6
|
+
# Update the apt source for Caddy
|
|
7
|
+
sudo apt-get install -y debian-keyring debian-archive-keyring apt-transport-https
|
|
8
|
+
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor --no-tty --batch --yes -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
|
|
9
|
+
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
|
|
10
|
+
sudo chmod o+r /usr/share/keyrings/caddy-stable-archive-keyring.gpg
|
|
11
|
+
sudo chmod o+r /etc/apt/sources.list.d/caddy-stable.list
|
|
12
|
+
sudo apt-get update
|
|
13
|
+
|
|
14
|
+
# Download and install the Caddy binary
|
|
15
|
+
mkdir ~/tmp
|
|
16
|
+
cd ~/tmp
|
|
17
|
+
apt-get download caddy
|
|
18
|
+
mkdir -p caddy-unpack
|
|
19
|
+
dpkg -x $(find caddy*.deb) ~/tmp/caddy-unpack
|
|
20
|
+
sudo mv ~/tmp/caddy-unpack/usr/bin/caddy /usr/local/bin/caddy
|
|
21
|
+
cd ~/
|
|
22
|
+
rm -rf ~/tmp
|
|
23
|
+
|
|
24
|
+
DEPLOY_USER="${DEPLOY_USER:-deploy}"
|
|
25
|
+
|
|
26
|
+
# Give the deploy user sudo access to control the Caddy service only
|
|
27
|
+
sudo touch /etc/sudoers.d/$DEPLOY_USER
|
|
28
|
+
|
|
29
|
+
cat << STRING >> /etc/sudoers.d/$DEPLOY_USER
|
|
30
|
+
deploy $(cat /etc/hostname)=(root) NOPASSWD: /bin/systemctl reload caddy
|
|
31
|
+
deploy $(cat /etc/hostname)=(root) NOPASSWD: /bin/systemctl status caddy
|
|
32
|
+
STRING
|
|
33
|
+
|
|
34
|
+
sudo mkdir -p /home/$DEPLOY_USER/http
|
|
35
|
+
sudo touch /home/$DEPLOY_USER/http/Caddyfile
|
|
36
|
+
sudo touch /home/$DEPLOY_USER/http/caddy.global
|
|
37
|
+
|
|
38
|
+
sudo cat <<STRING >> /home/$DEPLOY_USER/http/caddy.global
|
|
39
|
+
{
|
|
40
|
+
servers {
|
|
41
|
+
protocols h1 h2
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
STRING
|
|
45
|
+
|
|
46
|
+
sudo cat <<STRING >> /home/$DEPLOY_USER/http/Caddyfile
|
|
47
|
+
import caddy.global
|
|
48
|
+
import */Caddyfile
|
|
49
|
+
STRING
|
|
50
|
+
|
|
51
|
+
sudo chown $DEPLOY_USER:$DEPLOY_USER -R /home/$DEPLOY_USER
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
|
|
3
|
+
set -euo pipefail
|
|
4
|
+
export DEBIAN_FRONTEND=noninteractive
|
|
5
|
+
|
|
6
|
+
# Install Ruby prerequisites
|
|
7
|
+
sudo apt-get install $PREREQUISITES -y
|
|
8
|
+
|
|
9
|
+
# Install Ruby Install
|
|
10
|
+
wget https://github.com/postmodern/ruby-install/releases/download/v0.10.2/ruby-install-0.10.2.tar.gz
|
|
11
|
+
tar -xzvf ruby-install-0.10.2.tar.gz
|
|
12
|
+
cd ruby-install-0.10.2/
|
|
13
|
+
sudo make install
|
|
14
|
+
cd ..
|
|
15
|
+
rm ruby-install-0.10.2.tar.gz
|
|
16
|
+
rm -rf ruby-install-0.10.2/
|
|
17
|
+
|
|
18
|
+
# Install chruby
|
|
19
|
+
wget https://github.com/postmodern/chruby/releases/download/v0.3.9/chruby-0.3.9.tar.gz
|
|
20
|
+
tar -xzvf chruby-0.3.9.tar.gz
|
|
21
|
+
cd chruby-0.3.9/
|
|
22
|
+
sudo make install
|
|
23
|
+
cd ..
|
|
24
|
+
rm chruby-0.3.9.tar.gz
|
|
25
|
+
rm -rf chruby-0.3.9/
|
|
26
|
+
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
module Mimas
|
|
2
|
+
class Server
|
|
3
|
+
module Caddy
|
|
4
|
+
def install_caddy(sudo_user:)
|
|
5
|
+
ssh(self, user: sudo_user) do |session|
|
|
6
|
+
session.script("install_caddy", sudo: true, vars: { DEPLOY_USER: self.user })
|
|
7
|
+
|
|
8
|
+
caddy_service = template("caddy.service", deploy_user: self.user)
|
|
9
|
+
session.upload StringIO.new(caddy_service), "/tmp/caddy.service"
|
|
10
|
+
session.run "sudo mv /tmp/caddy.service /etc/systemd/system/caddy.service"
|
|
11
|
+
|
|
12
|
+
session.run "sudo systemctl daemon-reload && sudo systemctl enable --now caddy"
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
module Mimas
|
|
2
|
+
class Server
|
|
3
|
+
module Console
|
|
4
|
+
def start_console(site)
|
|
5
|
+
console_command = site.console
|
|
6
|
+
|
|
7
|
+
system('stty cbreak -echo')
|
|
8
|
+
Net::SSH.start(host, user) do |session|
|
|
9
|
+
session.open_channel do |channel|
|
|
10
|
+
channel.request_pty do
|
|
11
|
+
channel.exec("cd #{current_path(site)} && /bin/bash -lc #{console_command}") do
|
|
12
|
+
channel.on_data do |_, data|
|
|
13
|
+
$stdout.print(data) if data
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
channel.on_extended_data do |_, data|
|
|
17
|
+
$stdout.print(data) if data
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
session.listen_to($stdin) do |stdin|
|
|
21
|
+
input = stdin.readpartial(1024)
|
|
22
|
+
channel.send_data(input) unless input.empty?
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
session.loop
|
|
28
|
+
end
|
|
29
|
+
ensure
|
|
30
|
+
system('stty sane')
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
module Mimas
|
|
2
|
+
class Server
|
|
3
|
+
module Harden
|
|
4
|
+
def harden
|
|
5
|
+
ssh(self, user: "root") do |session|
|
|
6
|
+
session.script("harden", vars: { DEPLOY_USER: self.user })
|
|
7
|
+
|
|
8
|
+
say "Rebooting server...".bold.blue
|
|
9
|
+
session.run "reboot.no-molly-guard"
|
|
10
|
+
rescue SSH::Session::NonZeroExitCode => e
|
|
11
|
+
say "Sorry, an error occured while hardening the server".bold.red
|
|
12
|
+
say e.to_s.bold.red
|
|
13
|
+
raise e
|
|
14
|
+
rescue Errno::ECONNRESET
|
|
15
|
+
# Triggered by reboot
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
module Mimas
|
|
2
|
+
class Server
|
|
3
|
+
module Prerequisites
|
|
4
|
+
extend self
|
|
5
|
+
|
|
6
|
+
def for_ruby
|
|
7
|
+
%w[
|
|
8
|
+
xz-utils
|
|
9
|
+
build-essential
|
|
10
|
+
zlib1g-dev
|
|
11
|
+
libyaml-dev
|
|
12
|
+
libssl-dev
|
|
13
|
+
libncurses-dev
|
|
14
|
+
libffi-dev
|
|
15
|
+
rustc
|
|
16
|
+
libjemalloc-dev
|
|
17
|
+
]
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def utilities
|
|
21
|
+
%w[curl jq]
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
module Mimas
|
|
2
|
+
class Server
|
|
3
|
+
module Ruby
|
|
4
|
+
def install_ruby_prerequisites(sudo_user:)
|
|
5
|
+
ssh(self, user: sudo_user) do |session|
|
|
6
|
+
prerequisites = Prerequisites.for_ruby + Prerequisites.utilities
|
|
7
|
+
prerequisites = prerequisites.join(" ")
|
|
8
|
+
|
|
9
|
+
session.script("install_ruby_prerequisites",
|
|
10
|
+
sudo: true,
|
|
11
|
+
vars: { PREREQUISITES: "'#{prerequisites}'" }
|
|
12
|
+
)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def install_ruby(version: Config.current.ruby_version)
|
|
17
|
+
ssh(self) do |session|
|
|
18
|
+
current_ruby = session.run "/usr/bin/env bash -lc 'which ruby'", capture: true, silent: true
|
|
19
|
+
|
|
20
|
+
if current_ruby != ""
|
|
21
|
+
ruby_grep = session.run "/usr/bin/env bash -lc 'chruby' | grep #{version}", capture: true, silent: true
|
|
22
|
+
if ruby_grep.size > 0
|
|
23
|
+
return say "Ruby #{version} is already installed!"
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
session.run "ruby-install --no-install-deps --cleanup #{version} -- --enable-yjit --with-jemalloc --disable-install-rdoc"
|
|
28
|
+
session.run "echo '#{version}' > ~/.ruby-version"
|
|
29
|
+
|
|
30
|
+
if current_ruby == ""
|
|
31
|
+
session.script("configure_ruby")
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
data/lib/mimas/server.rb
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
module Mimas
|
|
2
|
+
class Server
|
|
3
|
+
include SSH, Template, Terminal::Printer
|
|
4
|
+
|
|
5
|
+
# Server setup helpers
|
|
6
|
+
include Harden, DeployUser, Ruby, Caddy
|
|
7
|
+
|
|
8
|
+
attr_reader :name, :host, :user, :ssh_options
|
|
9
|
+
|
|
10
|
+
def initialize(name, args)
|
|
11
|
+
@name = name
|
|
12
|
+
@host = args[:host]
|
|
13
|
+
@user = args[:user]
|
|
14
|
+
@ssh_options = args.except(:host, :user)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def user_home
|
|
18
|
+
@user_home ||= Pathname.new(File.join("/", "home", @user.to_s))
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def http_root
|
|
22
|
+
@http_root ||= user_home.join("http")
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def user_systemd_path
|
|
26
|
+
@user_systemd_path ||= user_home.join(".config", "systemd", "user")
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def site_root(site)
|
|
30
|
+
http_root.join("#{site.domain}")
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def current_path(site)
|
|
34
|
+
site_root(site).join("current")
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
data/lib/mimas/site.rb
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
module Mimas
|
|
2
|
+
class Site
|
|
3
|
+
attr_reader :name
|
|
4
|
+
def initialize(name, args)
|
|
5
|
+
@name = name
|
|
6
|
+
args[:services] ||= []
|
|
7
|
+
args[:deploy] ||= "bundle install"
|
|
8
|
+
args[:env] ||= ""
|
|
9
|
+
args[:healthcheck_path] ||= "/"
|
|
10
|
+
|
|
11
|
+
args.each do |key, value|
|
|
12
|
+
define_singleton_method(key) { value }
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def static?
|
|
17
|
+
!ruby_app?
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def ruby_app?
|
|
21
|
+
services.any?
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
data/lib/mimas/ssh.rb
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
module Mimas
|
|
2
|
+
module SSH
|
|
3
|
+
def ssh(server, user: nil, &block)
|
|
4
|
+
Session.new(server, user: user).perform(&block)
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
class Session
|
|
8
|
+
class NonZeroExitCode < StandardError
|
|
9
|
+
attr_reader :exit_code, :output
|
|
10
|
+
def initialize(command:, exit_code:, output:)
|
|
11
|
+
@command = command
|
|
12
|
+
@exit_code = exit_code
|
|
13
|
+
@output = output
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def to_s
|
|
17
|
+
"'#{@command}' exited with code: #{@exit_code}"
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def initialize(server, user:)
|
|
22
|
+
@server = server
|
|
23
|
+
@user = user || server.user
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def perform(&block)
|
|
27
|
+
@ssh = Net::SSH.start(@server.host, @user, timeout: 60, **@server.ssh_options)
|
|
28
|
+
@hostname = run("cat /etc/hostname", capture: true).chomp
|
|
29
|
+
yield(self)
|
|
30
|
+
ensure
|
|
31
|
+
@ssh&.close
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def run(command, capture: false, silent: false)
|
|
35
|
+
status = {}
|
|
36
|
+
output = ""
|
|
37
|
+
@ssh.exec!(command, status: status) do |channel, stream, data|
|
|
38
|
+
if stream == :stdout && capture
|
|
39
|
+
output << data
|
|
40
|
+
elsif stream == :stderr
|
|
41
|
+
print "#{data}".bold.red unless capture
|
|
42
|
+
else
|
|
43
|
+
print "#{data}".white unless capture
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
if status[:exit_code] != 0 && !silent
|
|
48
|
+
raise NonZeroExitCode.new(command: command, exit_code: status[:exit_code], output: output)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
return output if capture
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def upload(source_file, destination_file)
|
|
55
|
+
@ssh.scp.upload! source_file, destination_file
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def script(name, sudo: false, vars: {})
|
|
59
|
+
filename = "#{name}.sh"
|
|
60
|
+
remote_location = "/tmp/#{filename}"
|
|
61
|
+
|
|
62
|
+
source_path = File.expand_path("./scripts/#{filename}", __dir__)
|
|
63
|
+
upload source_path, remote_location
|
|
64
|
+
run "chmod +x #{remote_location}"
|
|
65
|
+
|
|
66
|
+
vars = vars.to_a.map { it.join("=") }.join(" ")
|
|
67
|
+
|
|
68
|
+
exec_command = remote_location
|
|
69
|
+
exec_command = "#{vars} #{exec_command}".strip
|
|
70
|
+
exec_command = "sudo #{exec_command}" if sudo
|
|
71
|
+
|
|
72
|
+
run exec_command
|
|
73
|
+
|
|
74
|
+
run "rm #{remote_location}"
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
require "fileutils"
|
|
2
|
+
|
|
3
|
+
module Mimas
|
|
4
|
+
module Template
|
|
5
|
+
BASE_DIRECTORY = File.join(__dir__, "templates")
|
|
6
|
+
|
|
7
|
+
def template(file_name, dir: BASE_DIRECTORY, **vars)
|
|
8
|
+
template = read_template(file_name, dir: dir)
|
|
9
|
+
template.result_with_hash(vars)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def read_file(file_name, dir: BASE_DIRECTORY)
|
|
13
|
+
File.read(File.join(dir, file_name))
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def copy_file(destination, source_file_name, rename: nil)
|
|
17
|
+
source = File.join(BASE_DIRECTORY, source_file_name)
|
|
18
|
+
FileUtils.copy(source, File.join(destination, rename || source_file_name))
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def read_template(file_name, dir:)
|
|
24
|
+
file = File.read(File.join(dir, "#{file_name}.erb"))
|
|
25
|
+
ERB.new(file, trim_mode: "-")
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
[Unit]
|
|
2
|
+
Description=Caddy
|
|
3
|
+
Documentation=https://caddyserver.com/docs/
|
|
4
|
+
After=network.target network-online.target
|
|
5
|
+
Requires=network-online.target
|
|
6
|
+
|
|
7
|
+
[Service]
|
|
8
|
+
Type=notify
|
|
9
|
+
User=<%= deploy_user %>
|
|
10
|
+
ExecStart=/usr/local/bin/caddy run --environ --config /home/<%= deploy_user %>/http/Caddyfile
|
|
11
|
+
ExecReload=/usr/local/bin/caddy reload --config /home/<%= deploy_user %>/http/Caddyfile --force
|
|
12
|
+
TimeoutStopSec=5s
|
|
13
|
+
LimitNOFILE=1048576
|
|
14
|
+
PrivateTmp=true
|
|
15
|
+
ProtectSystem=full
|
|
16
|
+
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE
|
|
17
|
+
|
|
18
|
+
[Install]
|
|
19
|
+
WantedBy=multi-user.target
|