consist 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/README.md +57 -390
- data/lib/consist/cli.rb +28 -9
- data/lib/consist/consistfile.rb +11 -6
- data/lib/consist/recipe.rb +13 -14
- data/lib/consist/resolver.rb +16 -0
- data/lib/consist/step.rb +8 -2
- data/lib/consist/templates/Consistfile.tt +19 -0
- data/lib/consist/utils.rb +28 -0
- data/lib/consist/version.rb +1 -1
- metadata +5 -7
- data/lib/consist/recipes.rb +0 -28
- data/lib/recipes/kamal_single_server.rb +0 -8
- data/lib/steps/install_apt_packages/step.rb +0 -19
- data/lib/steps/update_apt_packages/apt_auto_upgrades +0 -3
- data/lib/steps/update_apt_packages/step.rb +0 -11
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ce1770d8755d16d374ad05d69e36645c67b819926bd7a163e497714ca6400a20
|
4
|
+
data.tar.gz: '09ce200d04369a78c1d66bd4b389df1ff402951be094189d3d13c7042a18ff83'
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b487a6385b295f593cd3477afe5be8031f1cee5de7742c6fe99892da0f28eba0a855f91fbb7cc84750538826e5bd763164a49c4ac172d448dec477c09bdc4847
|
7
|
+
data.tar.gz: 65cb3135f0ea5afc548b321282f289b7fe66e4642b80eecfa530174235f173616781d60cb976ec362d05f1496f9b8fc2ca3a025a7c96c6435129c941e375c5b8
|
data/README.md
CHANGED
@@ -23,6 +23,7 @@ server, such as firewalls, general hardening, enabling swapfile etc.
|
|
23
23
|
- Procedural declaration execution - no converging, orchestration or
|
24
24
|
event driven operation
|
25
25
|
- If you can shell script it, you can `consist` it directly
|
26
|
+
- Encouraging sharing of portable `Consistfiles` across the community
|
26
27
|
|
27
28
|
---
|
28
29
|
|
@@ -30,6 +31,13 @@ server, such as firewalls, general hardening, enabling swapfile etc.
|
|
30
31
|
- [Support](#support)
|
31
32
|
- [Rationale](#rationale)
|
32
33
|
- [Key Concepts](#key-concepts)
|
34
|
+
- [Recipes](#recipes)
|
35
|
+
- [Steps](#steps)
|
36
|
+
- [Files](#files)
|
37
|
+
- [Consistfile](#consistfile)
|
38
|
+
- [Artifacts](#artifacts)
|
39
|
+
- [.consist directory](#.consist-directory)
|
40
|
+
- [Comunity Consistfiles](#community-consistfiles)
|
33
41
|
- [Is It Good?](#is-it-good%3F)
|
34
42
|
- [License](#license)
|
35
43
|
- [Code of conduct](#code-of-conduct)
|
@@ -41,26 +49,32 @@ server, such as firewalls, general hardening, enabling swapfile etc.
|
|
41
49
|
gem install consist
|
42
50
|
```
|
43
51
|
|
44
|
-
You must be already
|
45
|
-
your SSH id to perform actions.
|
52
|
+
You must be already authenticated with the server you want to scaffold.
|
53
|
+
`consist` will use your account's SSH id to perform actions.
|
46
54
|
|
47
|
-
|
55
|
+
The main way of using `consist` is to go with a `Consistfile` in
|
56
|
+
your project root that describes the recipe and steps. Then you can say:
|
48
57
|
|
49
58
|
```sh
|
50
|
-
consist
|
59
|
+
consist up <ip_address> [--consistfile=/path/to/consistfile] [--consistdir=/path/to/.consistdir]
|
51
60
|
```
|
52
61
|
|
53
|
-
|
54
|
-
recipe, using the `root` user.
|
62
|
+
And `consist` will do it's thing with that given IP address.
|
55
63
|
|
56
|
-
|
57
|
-
your project root that describes the recipe and steps. Then you can say:
|
64
|
+
To create a blank `Consistfile` in your project, execute:
|
58
65
|
|
59
|
-
```
|
60
|
-
consist
|
66
|
+
```ruby
|
67
|
+
consist init
|
61
68
|
```
|
62
69
|
|
63
|
-
|
70
|
+
## Commands
|
71
|
+
|
72
|
+
Other commands available:
|
73
|
+
|
74
|
+
- `consist init [account/repo]` - initialize your project with a new Consist file. Optionally,
|
75
|
+
you can specify a Github `account/repo` path and that location will be used to clone down a
|
76
|
+
Consistfile, and any associated artifacts needed by the Consistfile.
|
77
|
+
- `consist ping <ip_address>` - checks you can connect and authenticate with the given IP
|
64
78
|
|
65
79
|
## Features
|
66
80
|
|
@@ -160,9 +174,10 @@ end
|
|
160
174
|
|
161
175
|
A `Consistfile` is a portable giant file of a recipe and all its
|
162
176
|
steps. Something like (this is a _full_ example, in practice you
|
163
|
-
would reference some of
|
177
|
+
would reference some of Consists' built in steps):
|
164
178
|
|
165
179
|
```ruby
|
180
|
+
# This is a shortened non-complete example.
|
166
181
|
consist do
|
167
182
|
config :hostname, "testexample.com"
|
168
183
|
config :site_fqdn, "textexample.com"
|
@@ -191,171 +206,6 @@ consist do
|
|
191
206
|
EOS
|
192
207
|
end
|
193
208
|
|
194
|
-
file :fail2ban_config do
|
195
|
-
<<~EOS
|
196
|
-
# Fail2Ban configuration file.
|
197
|
-
#
|
198
|
-
|
199
|
-
# to view current bans, run one of the following:
|
200
|
-
# fail2ban-client status ssh
|
201
|
-
# iptables --list -n | fgrep DROP
|
202
|
-
|
203
|
-
# The DEFAULT allows a global definition of the options. They can be overridden
|
204
|
-
# in each jail afterwards.
|
205
|
-
|
206
|
-
[DEFAULT]
|
207
|
-
|
208
|
-
ignoreip = 127.0.0.1
|
209
|
-
bantime = 600
|
210
|
-
maxretry = 3
|
211
|
-
backend = auto
|
212
|
-
usedns = warn
|
213
|
-
destemail = <%= admin_email %>
|
214
|
-
|
215
|
-
#
|
216
|
-
# ACTIONS
|
217
|
-
#
|
218
|
-
|
219
|
-
banaction = iptables-multiport
|
220
|
-
mta = sendmail
|
221
|
-
protocol = tcp
|
222
|
-
chain = INPUT
|
223
|
-
|
224
|
-
#
|
225
|
-
# Action shortcuts. To be used to define action parameter
|
226
|
-
|
227
|
-
# The simplest action to take: ban only
|
228
|
-
action_ = %(banaction)s[name=%(__name__)s, port="%(port)s", protocol="%(protocol)s", chain="%(chain)s"]
|
229
|
-
|
230
|
-
# ban & send an e-mail with whois report to the destemail.
|
231
|
-
action_mw = %(banaction)s[name=%(__name__)s, port="%(port)s", protocol="%(protocol)s", chain="%(chain)s"]
|
232
|
-
%(mta)s-whois[name=%(__name__)s, dest="%(destemail)s", protocol="%(protocol)s", chain="%(chain)s"]
|
233
|
-
|
234
|
-
# ban & send an e-mail with whois report and relevant log lines
|
235
|
-
# to the destemail.
|
236
|
-
action_mwl = %(banaction)s[name=%(__name__)s, port="%(port)s", protocol="%(protocol)s", chain="%(chain)s"]
|
237
|
-
%(mta)s-whois-lines[name=%(__name__)s, dest="%(destemail)s", logpath=%(logpath)s, chain="%(chain)s"]
|
238
|
-
|
239
|
-
# default action
|
240
|
-
action = %(action_mw)s
|
241
|
-
|
242
|
-
[ssh]
|
243
|
-
|
244
|
-
enabled = true
|
245
|
-
port = 987
|
246
|
-
filter = sshd
|
247
|
-
logpath = /var/log/auth.log
|
248
|
-
maxretry = 6
|
249
|
-
|
250
|
-
[ssh-ddos]
|
251
|
-
|
252
|
-
enabled = true
|
253
|
-
port = 987
|
254
|
-
filter = sshd-ddos
|
255
|
-
logpath = /var/log/auth.log
|
256
|
-
maxretry = 6
|
257
|
-
EOS
|
258
|
-
end
|
259
|
-
|
260
|
-
file :logwatch_config do
|
261
|
-
<<~EOS
|
262
|
-
Output = mail
|
263
|
-
MailTo = <%= admin_email %>
|
264
|
-
MailFrom = logwatch@host1.mydomain.org
|
265
|
-
Detail = Low
|
266
|
-
Service = All
|
267
|
-
EOS
|
268
|
-
end
|
269
|
-
|
270
|
-
file :sysctl_config do
|
271
|
-
<<~EOS
|
272
|
-
# Do not accept ICMP redirects (prevent MITM attacks)
|
273
|
-
net.ipv4.conf.all.accept_redirects = 0
|
274
|
-
net.ipv6.conf.all.accept_redirects = 0
|
275
|
-
# Do not send ICMP redirects (we are not a router)
|
276
|
-
net.ipv4.conf.all.send_redirects = 0
|
277
|
-
# Log Martian Packets
|
278
|
-
net.ipv4.conf.all.log_martians = 1
|
279
|
-
# Controls IP packet forwarding
|
280
|
-
net.ipv4.ip_forward = 0
|
281
|
-
# Controls source route verification
|
282
|
-
net.ipv4.conf.default.rp_filter = 1
|
283
|
-
# Do not accept source routing
|
284
|
-
net.ipv4.conf.default.accept_source_route = 0
|
285
|
-
# Controls the System Request debugging functionality of the kernel
|
286
|
-
kernel.sysrq = 0
|
287
|
-
# Controls whether core dumps will append the PID to the core filename
|
288
|
-
# Useful for debugging multi-threaded applications
|
289
|
-
kernel.core_uses_pid = 1
|
290
|
-
# Controls the use of TCP syncookies
|
291
|
-
net.ipv4.tcp_synack_retries = 2
|
292
|
-
######## IPv4 networking start ###########
|
293
|
-
# Send redirects, if router, but this is just server
|
294
|
-
net.ipv4.conf.all.send_redirects = 0
|
295
|
-
net.ipv4.conf.default.send_redirects = 0
|
296
|
-
# Accept packets with SRR option? No
|
297
|
-
net.ipv4.conf.all.accept_source_route = 0
|
298
|
-
# Accept Redirects? No, this is not router
|
299
|
-
net.ipv4.conf.all.accept_redirects = 0
|
300
|
-
net.ipv4.conf.all.secure_redirects = 0
|
301
|
-
# Log packets with impossible addresses to kernel log? Yes
|
302
|
-
net.ipv4.conf.all.log_martians = 1
|
303
|
-
net.ipv4.conf.default.accept_source_route = 0
|
304
|
-
net.ipv4.conf.default.accept_redirects = 0
|
305
|
-
net.ipv4.conf.default.secure_redirects = 0
|
306
|
-
# Ignore all ICMP ECHO and TIMESTAMP requests sent to it via broadcast/multicast
|
307
|
-
net.ipv4.icmp_echo_ignore_broadcasts = 1
|
308
|
-
# Prevent against the common 'syn flood attack'
|
309
|
-
net.ipv4.tcp_syncookies = 1
|
310
|
-
# Enable source validation by reversed path, as specified in RFC1812
|
311
|
-
net.ipv4.conf.all.rp_filter = 1
|
312
|
-
net.ipv4.conf.default.rp_filter = 1
|
313
|
-
######## IPv6 networking start ###########
|
314
|
-
# Number of Router Solicitations to send until assuming no routers are present.
|
315
|
-
# This is host and not router
|
316
|
-
net.ipv6.conf.default.router_solicitations = 0
|
317
|
-
# Accept Router Preference in RA?
|
318
|
-
net.ipv6.conf.default.accept_ra_rtr_pref = 0
|
319
|
-
# Learn Prefix Information in Router Advertisement
|
320
|
-
net.ipv6.conf.default.accept_ra_pinfo = 0
|
321
|
-
# Setting controls whether the system will accept Hop Limit settings from a router advertisement
|
322
|
-
net.ipv6.conf.default.accept_ra_defrtr = 0
|
323
|
-
#router advertisements can cause the system to assign a global unicast address to an interface
|
324
|
-
net.ipv6.conf.default.autoconf = 0
|
325
|
-
#how many neighbor solicitations to send out per address?
|
326
|
-
net.ipv6.conf.default.dad_transmits = 0
|
327
|
-
# How many global unicast IPv6 addresses can be assigned to each interface?
|
328
|
-
net.ipv6.conf.default.max_addresses = 1
|
329
|
-
######## IPv6 networking ends ###########
|
330
|
-
# Disabled, not used anymore
|
331
|
-
#Enable ExecShield protection |
|
332
|
-
#kernel.exec-shield = 1
|
333
|
-
#kernel.randomize_va_space = 1
|
334
|
-
# TCP and memory optimization
|
335
|
-
# increase TCP max buffer size setable using setsockopt()
|
336
|
-
#net.ipv4.tcp_rmem = 4096 87380 8388608
|
337
|
-
#net.ipv4.tcp_wmem = 4096 87380 8388608
|
338
|
-
# increase Linux auto tuning TCP buffer limits
|
339
|
-
#net.core.rmem_max = 8388608
|
340
|
-
#net.core.wmem_max = 8388608
|
341
|
-
#net.core.netdev_max_backlog = 5000
|
342
|
-
#net.ipv4.tcp_window_scaling = 1
|
343
|
-
# increase system file descriptor limit
|
344
|
-
fs.file-max = 65535
|
345
|
-
#Allow for more PIDs
|
346
|
-
kernel.pid_max = 65536
|
347
|
-
#Increase system IP port limits
|
348
|
-
net.ipv4.ip_local_port_range = 2000 65000
|
349
|
-
# Disable IPv6 autoconf
|
350
|
-
#net.ipv6.conf.all.autoconf = 0
|
351
|
-
#net.ipv6.conf.default.autoconf = 0
|
352
|
-
#net.ipv6.conf.eth0.autoconf = 0
|
353
|
-
#net.ipv6.conf.all.accept_ra = 0
|
354
|
-
#net.ipv6.conf.default.accept_ra = 0
|
355
|
-
#net.ipv6.conf.eth0.accept_ra = 0
|
356
|
-
EOS
|
357
|
-
end
|
358
|
-
|
359
209
|
recipe :kamal_single_server do
|
360
210
|
name "Kamal Single Server Scaffold"
|
361
211
|
|
@@ -395,233 +245,50 @@ consist do
|
|
395
245
|
EOS
|
396
246
|
end
|
397
247
|
end
|
248
|
+
end
|
249
|
+
end
|
250
|
+
end
|
398
251
|
|
399
|
-
|
400
|
-
|
401
|
-
required_user :root
|
402
|
-
|
403
|
-
upload_file message: "Uploading APT config...",
|
404
|
-
local_file: :apt_auto_upgrade,
|
405
|
-
remote_path: "/etc/apt/apt.conf.d/20auto-upgrades"
|
406
|
-
|
407
|
-
shell do
|
408
|
-
<<~EOS
|
409
|
-
apt-get update && apt-get upgrade -y
|
410
|
-
apt-get autoremove
|
411
|
-
apt-get autoclean
|
412
|
-
EOS
|
413
|
-
end
|
414
|
-
end
|
415
|
-
|
416
|
-
step :install_apt_packages do
|
417
|
-
name "Installing essential APT packages"
|
418
|
-
required_user :root
|
419
|
-
|
420
|
-
shell "Installing essential packages" do
|
421
|
-
<<~EOS
|
422
|
-
apt-get -y install build-essential curl git vim
|
423
|
-
EOS
|
424
|
-
end
|
425
|
-
end
|
426
|
-
|
427
|
-
step :setup_ntp do
|
428
|
-
name "Installing NTP daemon"
|
429
|
-
required_user :root
|
430
|
-
|
431
|
-
shell "Configuring NTP daemon", params: {raise_on_non_zero_exit: false} do
|
432
|
-
<<~EOS
|
433
|
-
apt-get -y remove systemd-timesyncd
|
434
|
-
timedatectl set-ntp no 2>1
|
435
|
-
apt-get -y install ntp
|
436
|
-
EOS
|
437
|
-
end
|
438
|
-
|
439
|
-
shell "Start NTP and Fail2Ban" do
|
440
|
-
<<~EOS
|
441
|
-
service ntp restart
|
442
|
-
EOS
|
443
|
-
end
|
444
|
-
end
|
445
|
-
|
446
|
-
step :install_fail2ban do
|
447
|
-
name "Installing fail2ban"
|
448
|
-
required_user :root
|
449
|
-
|
450
|
-
shell "Installing essential packages" do
|
451
|
-
<<~EOS
|
452
|
-
apt-get -y install fail2ban
|
453
|
-
EOS
|
454
|
-
end
|
455
|
-
|
456
|
-
upload_file message: "Uploading fail2ban confing", local_file: :fail2ban_config,
|
457
|
-
remote_path: "/etc/fail2ban/jail.local"
|
458
|
-
|
459
|
-
shell "Start Fail2Ban" do
|
460
|
-
<<~EOS
|
461
|
-
service fail2ban restart
|
462
|
-
systemctl enable fail2ban.service
|
463
|
-
EOS
|
464
|
-
end
|
465
|
-
end
|
466
|
-
|
467
|
-
step :setup_swap do
|
468
|
-
name "Configure and enable the swapfile"
|
469
|
-
required_user :root
|
470
|
-
|
471
|
-
check status: :nonexistant, file: "/swapfile" do
|
472
|
-
shell do
|
473
|
-
<<~EOS
|
474
|
-
fallocate -l <%= swap_size %> /swapfile
|
475
|
-
chmod 600 /swapfile
|
476
|
-
mkswap /swapfile
|
477
|
-
swapon /swapfile
|
478
|
-
echo "\n/swapfile swap swap defaults 0 0\n" >> /etc/fstab
|
479
|
-
sysctl vm.swappiness=<%=swap_swappiness%>
|
480
|
-
echo "\nvm.swappiness=<%=swap_swappiness%>\n" >> /etc/sysctl.conf
|
481
|
-
EOS
|
482
|
-
end
|
483
|
-
end
|
484
|
-
end
|
485
|
-
|
486
|
-
step :harden_ssh do
|
487
|
-
name "Harden the SSH config"
|
488
|
-
|
489
|
-
mutate_file mode: :replace, target_file: "/etc/ssh/sshd_config", match: "^#PasswordAuthentication yes$",
|
490
|
-
target_string: "PasswordAuthentication no"
|
491
|
-
mutate_file mode: :replace, target_file: "/etc/ssh/sshd_config", match: "^#PubkeyAuthentication yes$",
|
492
|
-
target_string: "PubkeyAuthentication yes"
|
493
|
-
|
494
|
-
shell do
|
495
|
-
<<~EOS
|
496
|
-
service ssh restart
|
497
|
-
EOS
|
498
|
-
end
|
499
|
-
end
|
500
|
-
|
501
|
-
step :harden_system do
|
502
|
-
name "Harden the SYSCTL settings"
|
503
|
-
|
504
|
-
upload_file message: "Uploading sysctl config...",
|
505
|
-
local_file: :sysctl_config,
|
506
|
-
remote_path: "/tmp/sysctl_config"
|
507
|
-
|
508
|
-
shell do
|
509
|
-
<<~EOS
|
510
|
-
cat /etc/sysctl.conf /tmp/sysctl_config > /etc/sysctl.conf
|
511
|
-
rm /tmp/sysctl_config
|
512
|
-
sysctl -p
|
513
|
-
EOS
|
514
|
-
end
|
515
|
-
end
|
516
|
-
|
517
|
-
step :setup_ufw do
|
518
|
-
name "Setup UFW"
|
519
|
-
|
520
|
-
shell do
|
521
|
-
<<~EOS
|
522
|
-
ufw logging on
|
523
|
-
ufw default deny incoming
|
524
|
-
ufw default allow outgoing
|
525
|
-
ufw allow 22
|
526
|
-
ufw allow 80
|
527
|
-
ufw allow 443
|
528
|
-
ufw --force enable
|
529
|
-
service ufw restart
|
530
|
-
EOS
|
531
|
-
end
|
532
|
-
end
|
533
|
-
|
534
|
-
step :setup_postfix do
|
535
|
-
name "Install Postfix for admin emails"
|
252
|
+
# vim: filetype=ruby
|
253
|
+
```
|
536
254
|
|
537
|
-
|
538
|
-
|
539
|
-
echo "postfix postfix/mailname string <%= site_fqdn %>" | debconf-set-selections
|
540
|
-
EOS
|
541
|
-
end
|
255
|
+
Given a `Consistfile` you could then say `consist up <ip_address>` and
|
256
|
+
it would just work.
|
542
257
|
|
543
|
-
|
544
|
-
<<~EOS
|
545
|
-
echo "postfix postfix/main_mailer_type string 'Internet Site'" | debconf-set-selections
|
546
|
-
EOS
|
547
|
-
end
|
258
|
+
### Artifacts
|
548
259
|
|
549
|
-
|
550
|
-
<<~EOS
|
551
|
-
DEBIAN_FRONTEND=noninteractive apt-get install --assume-yes postfix
|
552
|
-
EOS
|
553
|
-
end
|
554
|
-
end
|
260
|
+
Artifacts allow you to split out your `Consistfile` into separate files.
|
555
261
|
|
556
|
-
|
557
|
-
|
262
|
+
You can create blocks in the `Consistfile` as shown above, but you can also only
|
263
|
+
specify an `id`, and that `id` will be used to try and attempt to load a file of that
|
264
|
+
name in the `.consist/<type>/<id>` directory. For example, referencing a file:
|
558
265
|
|
559
|
-
|
560
|
-
|
561
|
-
|
562
|
-
EOS
|
563
|
-
end
|
266
|
+
```ruby
|
267
|
+
file :apt_auto_upgrade
|
268
|
+
```
|
564
269
|
|
565
|
-
|
566
|
-
|
270
|
+
Will attempt to load a file in `.consist/files/apt_auto_upgrade`. The same is
|
271
|
+
possible for any of the main types: `files`, `steps`, and `recipes`
|
567
272
|
|
568
|
-
|
569
|
-
remote_path: "/etc/logwatch/conf"
|
570
|
-
end
|
273
|
+
### `.consist` directory
|
571
274
|
|
572
|
-
|
573
|
-
|
275
|
+
The `.consist` directory is assumed to be in the root of your project, and should
|
276
|
+
contain three subdirectories for each of the types: `files`, `steps`, `recipes`.
|
574
277
|
|
575
|
-
|
576
|
-
|
577
|
-
# Add Docker's official GPG key:
|
578
|
-
apt-get update
|
579
|
-
apt-get install ca-certificates curl gnupg -y
|
580
|
-
install -m 0755 -d /etc/apt/keyrings
|
581
|
-
rm /etc/apt/keyrings/docker.gpg
|
582
|
-
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --batch --no-tty --dearmor -o /etc/apt/keyrings/docker.gpg
|
583
|
-
chmod a+r /etc/apt/keyrings/docker.gpg
|
584
|
-
|
585
|
-
# Add the repository to Apt sources:
|
586
|
-
echo \
|
587
|
-
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
|
588
|
-
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
|
589
|
-
tee /etc/apt/sources.list.d/docker.list > /dev/null
|
590
|
-
apt-get update
|
591
|
-
|
592
|
-
# Install Docker
|
593
|
-
apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin -y
|
594
|
-
|
595
|
-
# Make Docker start on boot
|
596
|
-
sudo systemctl enable docker.service
|
597
|
-
sudo systemctl enable containerd.service
|
598
|
-
EOS
|
599
|
-
end
|
278
|
+
You can specify an alternate directory location by passing the `--consistdir` switch
|
279
|
+
to the `up` command.
|
600
280
|
|
601
|
-
|
602
|
-
<<~EOS
|
603
|
-
# Create group
|
604
|
-
sudo groupadd docker
|
605
|
-
sudo usermod -aG docker $USER
|
606
|
-
EOS
|
607
|
-
end
|
281
|
+
## Community Consistfiles
|
608
282
|
|
609
|
-
|
610
|
-
|
611
|
-
|
612
|
-
docker network create private
|
613
|
-
EOS
|
614
|
-
end
|
615
|
-
end
|
616
|
-
end
|
617
|
-
end
|
618
|
-
end
|
283
|
+
If you create a Github repo, and all it contains is a `Consistfile` and any associated
|
284
|
+
artifacts under a `.consist` directory, other people will be able to use it by
|
285
|
+
executing `consist init <gh_repo_path>` in their project root.
|
619
286
|
|
620
|
-
|
621
|
-
```
|
287
|
+
If you create one, please open a PR to include it here:
|
622
288
|
|
623
|
-
|
624
|
-
|
289
|
+
| Name | Repo | Description |
|
290
|
+
| ------------------------- | ----------------------------------------------------------------------------------- | ----------------------------------------------------------- |
|
291
|
+
| Kamal Single Server Setup | [consist-sh/kamal-single-server](https://github.com/consist-sh/kamal-single-server) | Setup a single server with good defaults ready to run Kamal |
|
625
292
|
|
626
293
|
## Is it good?
|
627
294
|
|
data/lib/consist/cli.rb
CHANGED
@@ -1,9 +1,12 @@
|
|
1
|
+
require "fileutils"
|
2
|
+
|
1
3
|
require "thor"
|
2
4
|
require "sshkit"
|
3
5
|
require "sshkit/dsl"
|
4
6
|
|
7
|
+
require "consist/utils"
|
8
|
+
require "consist/resolver"
|
5
9
|
require "consist/recipe"
|
6
|
-
require "consist/recipes"
|
7
10
|
require "consist/step"
|
8
11
|
require "consist/consistfile"
|
9
12
|
require "consist/commands/includes/stream_logger"
|
@@ -16,16 +19,22 @@ require "consist/commands/check"
|
|
16
19
|
module Consist
|
17
20
|
class CLI < Thor
|
18
21
|
extend ThorExt::Start
|
22
|
+
include Thor::Actions
|
23
|
+
include SSHKit::DSL
|
19
24
|
|
20
25
|
map %w[-v --version] => "version"
|
21
26
|
|
27
|
+
def self.source_root
|
28
|
+
File.dirname(__FILE__)
|
29
|
+
end
|
30
|
+
|
22
31
|
desc "version", "Display consist version"
|
23
32
|
def version
|
24
33
|
say "consist/#{VERSION} #{RUBY_DESCRIPTION}"
|
25
34
|
end
|
26
35
|
|
27
|
-
desc "
|
28
|
-
def
|
36
|
+
desc "ping", "Attempt to connect to a server and execute an idempotent statement."
|
37
|
+
def ping(user, server)
|
29
38
|
puts "---> Attempting to connect to #{server} as #{user}"
|
30
39
|
on("#{user}@#{server}") do
|
31
40
|
as user do
|
@@ -34,18 +43,28 @@ module Consist
|
|
34
43
|
end
|
35
44
|
end
|
36
45
|
|
37
|
-
desc "scaffold", "Apply a given recipe to (a) server(s)"
|
38
|
-
def scaffold(_recipe, server_ip)
|
39
|
-
Consist::Recipes.new(server_ip)
|
40
|
-
end
|
41
|
-
|
42
46
|
option :step, type: :string
|
43
47
|
option :consistfile, type: :string
|
48
|
+
option :consistdir, type: :string
|
44
49
|
desc "up", "Run a Consistfile against a server"
|
45
50
|
def up(server_ip)
|
46
51
|
specified_step = options[:step]
|
47
52
|
consistfile = options[:consistfile]
|
48
|
-
|
53
|
+
consist_dir = options[:consistdir]
|
54
|
+
Consist::Consistfile.new(server_ip, consist_dir:, consistfile:, specified_step:)
|
55
|
+
end
|
56
|
+
|
57
|
+
desc "init", "Initialize a project with Consist, optionally specifying a GH path to a Consistfile"
|
58
|
+
def init(gh_path = nil)
|
59
|
+
if gh_path
|
60
|
+
full_url = "https://github.com/#{gh_path}"
|
61
|
+
Consist::Utils.clone_repo_contents(full_url, Dir.pwd)
|
62
|
+
else
|
63
|
+
puts "Creating new Consistfile..."
|
64
|
+
directory "templates/.consist", File.join(Dir.pwd, ".consist")
|
65
|
+
template "templates/Consistfile.tt", File.join(Dir.pwd, "Consistfile")
|
66
|
+
puts "...done"
|
67
|
+
end
|
49
68
|
end
|
50
69
|
end
|
51
70
|
end
|
data/lib/consist/consistfile.rb
CHANGED
@@ -2,18 +2,21 @@
|
|
2
2
|
|
3
3
|
module Consist
|
4
4
|
class << self
|
5
|
-
attr_accessor :files, :config
|
5
|
+
attr_accessor :files, :config, :consist_dir
|
6
6
|
end
|
7
7
|
|
8
8
|
@files = []
|
9
9
|
@config = {}
|
10
|
+
@consist_dir = ""
|
10
11
|
|
11
12
|
class Consistfile
|
12
13
|
include SSHKit::DSL
|
13
14
|
|
14
|
-
def initialize(server_ip, specified_step:, consistfile:)
|
15
|
+
def initialize(server_ip, specified_step:, consist_dir:, consistfile:)
|
15
16
|
@server_ip = server_ip
|
16
17
|
@specified_step = specified_step
|
18
|
+
Consist.consist_dir = consist_dir || ".consist"
|
19
|
+
|
17
20
|
consistfile_path = if consistfile
|
18
21
|
File.expand_path(consistfile, Dir.pwd)
|
19
22
|
else
|
@@ -30,7 +33,6 @@ module Consist
|
|
30
33
|
|
31
34
|
def recipe(id, &definition)
|
32
35
|
recipe = Consist::Recipe.new(id, &definition)
|
33
|
-
|
34
36
|
puts "Executing Recipe: #{recipe.name}"
|
35
37
|
|
36
38
|
if @specified_step.nil?
|
@@ -47,9 +49,12 @@ module Consist
|
|
47
49
|
end
|
48
50
|
|
49
51
|
def file(id, &definition)
|
50
|
-
|
51
|
-
|
52
|
-
|
52
|
+
if definition
|
53
|
+
contents = yield
|
54
|
+
else
|
55
|
+
file_contents = Consist::Resolver.new(pwd: Dir.pwd).resolve_artifact(type: :file, id:)
|
56
|
+
contents = file_contents
|
57
|
+
end
|
53
58
|
|
54
59
|
Consist.files << {id:, contents:}
|
55
60
|
end
|
data/lib/consist/recipe.rb
CHANGED
@@ -2,9 +2,16 @@
|
|
2
2
|
|
3
3
|
module Consist
|
4
4
|
class Recipe
|
5
|
-
def initialize(
|
5
|
+
def initialize(id = nil, &definition)
|
6
6
|
@steps = []
|
7
|
-
|
7
|
+
@id = id
|
8
|
+
|
9
|
+
if definition
|
10
|
+
instance_eval(&definition)
|
11
|
+
else
|
12
|
+
contents = Consist::Resolver.new(pwd: Dir.pwd).resolve_artifact(type: :recipe, id:)
|
13
|
+
instance_eval(contents)
|
14
|
+
end
|
8
15
|
end
|
9
16
|
|
10
17
|
def name(name = nil)
|
@@ -22,21 +29,13 @@ module Consist
|
|
22
29
|
@user
|
23
30
|
end
|
24
31
|
|
25
|
-
def steps(&
|
26
|
-
instance_eval(&
|
32
|
+
def steps(&definition)
|
33
|
+
instance_eval(&definition) if definition
|
27
34
|
@steps
|
28
35
|
end
|
29
36
|
|
30
|
-
def step(
|
31
|
-
|
32
|
-
@steps << Step.new(id: step_name, &block)
|
33
|
-
return
|
34
|
-
end
|
35
|
-
|
36
|
-
target_path = File.join("../../", "steps", step_name.to_s, "step.rb")
|
37
|
-
step_path = File.expand_path(target_path, __FILE__)
|
38
|
-
step_content = File.read(step_path)
|
39
|
-
@steps << Step.new(id: step_name) { instance_eval(step_content) }
|
37
|
+
def step(id, &definition)
|
38
|
+
@steps << Step.new(id:, &definition)
|
40
39
|
end
|
41
40
|
end
|
42
41
|
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Consist
|
4
|
+
class Resolver
|
5
|
+
def initialize(pwd:)
|
6
|
+
@pwd = pwd
|
7
|
+
end
|
8
|
+
|
9
|
+
def resolve_artifact(type:, id:)
|
10
|
+
file_name = %i[recipe step].include?(type) ? "#{id}.rb" : id.to_s
|
11
|
+
target_path = File.join(Consist.consist_dir, "#{type}s", file_name)
|
12
|
+
artifact_path = File.expand_path(target_path, @pwd)
|
13
|
+
File.read(artifact_path)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
data/lib/consist/step.rb
CHANGED
@@ -6,11 +6,17 @@ module Consist
|
|
6
6
|
class Step
|
7
7
|
include SSHKit::DSL
|
8
8
|
|
9
|
-
def initialize(id:, &
|
9
|
+
def initialize(id:, &definition)
|
10
10
|
@commands = []
|
11
11
|
@id = id
|
12
12
|
@required_user = :root
|
13
|
-
|
13
|
+
|
14
|
+
if definition
|
15
|
+
instance_eval(&definition)
|
16
|
+
else
|
17
|
+
contents = Consist::Resolver.new(pwd: Dir.pwd).resolve_artifact(type: :step, id:)
|
18
|
+
instance_eval(contents)
|
19
|
+
end
|
14
20
|
end
|
15
21
|
|
16
22
|
def id(id = nil)
|
@@ -0,0 +1,19 @@
|
|
1
|
+
consist do
|
2
|
+
config :example, "example"
|
3
|
+
|
4
|
+
# Define a recipe block:
|
5
|
+
#
|
6
|
+
# recipe :my_recipe do
|
7
|
+
# name "My recipe"
|
8
|
+
#
|
9
|
+
# steps do
|
10
|
+
# step do
|
11
|
+
# shell do
|
12
|
+
# <<~EOS
|
13
|
+
# echo 'hello'
|
14
|
+
# EOS
|
15
|
+
# end
|
16
|
+
# end
|
17
|
+
# end
|
18
|
+
# end
|
19
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require "fileutils"
|
2
|
+
require "tmpdir"
|
3
|
+
|
4
|
+
module Consist
|
5
|
+
module Utils
|
6
|
+
extend self
|
7
|
+
|
8
|
+
def clone_repo_contents(source_repo, target_dir)
|
9
|
+
temp_dir = Dir.mktmpdir
|
10
|
+
|
11
|
+
puts "Using #{source_repo} as template to initialize Consistfile..."
|
12
|
+
system("git clone --depth=1 #{source_repo} #{temp_dir} >/dev/null 2>&1")
|
13
|
+
|
14
|
+
Dir.foreach(temp_dir) do |item|
|
15
|
+
next if [".", "..", ".git"].include?(item)
|
16
|
+
next unless ["Consistfile", ".consist"].include?(item)
|
17
|
+
|
18
|
+
source_path = File.join(temp_dir, item)
|
19
|
+
target_path = File.join(target_dir, item)
|
20
|
+
FileUtils.cp_r(source_path, target_path)
|
21
|
+
end
|
22
|
+
|
23
|
+
FileUtils.remove_entry_secure(temp_dir)
|
24
|
+
|
25
|
+
puts "...done"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
data/lib/consist/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: consist
|
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
|
- John McDowall
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-12-04 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: net-ssh
|
@@ -73,14 +73,12 @@ files:
|
|
73
73
|
- lib/consist/commands/upload.rb
|
74
74
|
- lib/consist/consistfile.rb
|
75
75
|
- lib/consist/recipe.rb
|
76
|
-
- lib/consist/
|
76
|
+
- lib/consist/resolver.rb
|
77
77
|
- lib/consist/step.rb
|
78
|
+
- lib/consist/templates/Consistfile.tt
|
78
79
|
- lib/consist/thor_ext.rb
|
80
|
+
- lib/consist/utils.rb
|
79
81
|
- lib/consist/version.rb
|
80
|
-
- lib/recipes/kamal_single_server.rb
|
81
|
-
- lib/steps/install_apt_packages/step.rb
|
82
|
-
- lib/steps/update_apt_packages/apt_auto_upgrades
|
83
|
-
- lib/steps/update_apt_packages/step.rb
|
84
82
|
homepage: https://github.com/consist-sh/consist
|
85
83
|
licenses:
|
86
84
|
- LGPL-3.0
|
data/lib/consist/recipes.rb
DELETED
@@ -1,28 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Consist
|
4
|
-
class Recipes
|
5
|
-
include SSHKit::DSL
|
6
|
-
|
7
|
-
def initialize(server_ip)
|
8
|
-
recipe_directory = File.expand_path("../recipes", __dir__)
|
9
|
-
recipes = Dir[File.join(recipe_directory, "*.rb")]
|
10
|
-
|
11
|
-
recipes.each do |recipe_file|
|
12
|
-
recipe_content = File.read(recipe_file)
|
13
|
-
recipe = Recipe.new { instance_eval(recipe_content) }
|
14
|
-
|
15
|
-
puts "Executing Recipe: #{recipe.name}"
|
16
|
-
recipe.steps.each do |step|
|
17
|
-
puts "Executing Step: #{step.name}"
|
18
|
-
|
19
|
-
on("#{step.required_user}@#{server_ip}") do
|
20
|
-
step.perform(self)
|
21
|
-
end
|
22
|
-
end
|
23
|
-
|
24
|
-
puts "Execution of #{recipe.name} has completed."
|
25
|
-
end
|
26
|
-
end
|
27
|
-
end
|
28
|
-
end
|
@@ -1,19 +0,0 @@
|
|
1
|
-
name "Install APT packages"
|
2
|
-
required_user :root
|
3
|
-
|
4
|
-
shell "Installing essential packages" do
|
5
|
-
<<~EOS
|
6
|
-
apt-get -y remove systemd-timesyncd
|
7
|
-
timedatectl set-ntp no
|
8
|
-
apt-get -y install build-essential curl fail2ban git ntp vim
|
9
|
-
apt-get autoremove
|
10
|
-
apt-get autoclean
|
11
|
-
EOS
|
12
|
-
end
|
13
|
-
|
14
|
-
shell "Start NTP and Fail2Ban" do
|
15
|
-
<<~EOS
|
16
|
-
service ntp restart
|
17
|
-
service fail2ban restart
|
18
|
-
EOS
|
19
|
-
end
|
@@ -1,11 +0,0 @@
|
|
1
|
-
name "Update the APT packages"
|
2
|
-
required_user :root
|
3
|
-
|
4
|
-
upload_file message: "Uploading APT config...", local_file: "apt_auto_upgrades",
|
5
|
-
remote_path: "/etc/apt/apt.conf.d/20auto-upgrades"
|
6
|
-
|
7
|
-
shell do
|
8
|
-
<<~EOS
|
9
|
-
apt-get update && apt-get upgrade -y
|
10
|
-
EOS
|
11
|
-
end
|