Skip to content

Commit ddaa6d4

Browse files
authored
Introduce supervisor deploy (#4)
1 parent 30f0f70 commit ddaa6d4

21 files changed

+852
-71
lines changed

.yamllint

+2
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,7 @@ extends: default
33
rules:
44
indentation:
55
indent-sequences: consistent
6+
line-length:
7+
max: 120
68

79
# vim:ft=yaml

README.md

+159-3
Original file line numberDiff line numberDiff line change
@@ -73,13 +73,14 @@ actions.
7373

7474
### Configuration
7575

76-
Before using the CLI, configure the Supervisor base URL and API key by
76+
Before using the CLI, configure the Supervisor base URI and API token by
7777
creating a configuration file at `~/.supervisor`:
7878

7979
```yaml
8080
---
81-
base_url: https://supervisor.example.com
82-
api_key: 8db7fde4-6a11-462e-ba27-6897b7c9281b
81+
api:
82+
uri: https://supervisor.example.com
83+
token: 8db7fde4-6a11-462e-ba27-6897b7c9281b
8384
```
8485
8586
### Command Reference
@@ -96,6 +97,161 @@ supervisor is-healthy
9697

9798
Checks the health of the Supervisor service.
9899

100+
### Deployment Management
101+
102+
The command `deploy` installs and sets up a containerized Supervisor service
103+
on a vanilla Linux machine by provisioning the docker service and
104+
deploying the application proxy [Traefik](https://traefik.io/).
105+
106+
#### Default Traefik docker command
107+
108+
```bash
109+
docker run \
110+
--detach --restart always --name traefik \
111+
--volume /var/run/docker.sock:/var/run/docker.sock \
112+
--volume /var/lib/traefik:/etc/traefik \
113+
--network supervisor \
114+
--publish 80:80 --publish 443:443 \
115+
traefik:v3.2.1 \
116+
--providers.docker.exposedbydefault="false" \
117+
--entrypoints.web.address=":80" \
118+
--entrypoints.websecure.address=":443" \
119+
--certificatesresolvers.letsencrypt.acme.email="acme@supervisor.example" \
120+
--certificatesresolvers.letsencrypt.acme.storage="/etc/traefik/certs.d/acme.json" \
121+
--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint="web"
122+
```
123+
124+
#### Default Supervisor docker command
125+
126+
```bash
127+
docker run \
128+
--detach --restart always --name supervisor \
129+
--volume /var/run/docker.sock:/var/run/docker.sock \
130+
--volume /var/lib/supervisor:/rails/storage \
131+
--network supervisor \
132+
--label traefik.enable="true" \
133+
--label traefik.http.routers.supervisor.tls="true" \
134+
--label traefik.http.routers.supervisor.tls.certresolver="letsencrypt" \
135+
--label traefik.http.routers.supervisor.rule="Host(\"supervisor.example.com\")" \
136+
--label traefik.http.routers.supervisor.entrypoints="websecure" \
137+
--env SECRET_KEY_BASE="601f72235d8ea11db69e678f9...1a" \
138+
--env SUPERVISOR_API_KEY="8db7fde4-6a11-462e-ba27-6897b7c9281b" \
139+
ghcr.io/tschaefer/supervisor:main
140+
```
141+
142+
#### Default docker network command
143+
144+
```bash
145+
docker network create \
146+
--attachable true \
147+
--ipv6=true \
148+
--driver=bridge \
149+
--opt com.docker.network.container_iface_prefix=supervisor
150+
supervisor
151+
```
152+
153+
Prerequisites are super-user privileges, a valid DNS record for the
154+
Supervisor service and the above mentioned configuration file.
155+
156+
While setup the necessary certificate is requested from
157+
[Let's Encrypt](https://letsencrypt.org/) via HTTP-challenge.
158+
159+
160+
```bash
161+
supervisor deploy --host root@machine.example.com
162+
```
163+
164+
The provisioning of docker can be skipped wit the option `--skip-docker` as
165+
well as the installation of Traefik with the option `--skip-traefik`. For a
166+
more informative output use `--verbose` - beware, sensible information will be
167+
exposed.
168+
169+
The deployment is customizable by configuration in the root under `deploy`.
170+
171+
```yaml
172+
deploy:
173+
174+
# Network settings
175+
network:
176+
177+
# The name of the network to create, defaults to supervisor
178+
name: supervisor
179+
# Additional options to pass to the network create command
180+
options:
181+
ipv6: false
182+
opt: com.docker.network.driver.mtu=1500
183+
184+
# Traefik settings
185+
traefik:
186+
187+
# The Traefik image to use, defaults to traefik:v3.2.1
188+
image: traefik:v3.2.0
189+
190+
# Additional arguments to pass to the Traefik container
191+
args:
192+
configfile: /etc/traefik/traefik.yml
193+
194+
# Additional environment variables to pass to the Traefik container
195+
env:
196+
CF_API_EMAIL: cloudflare@example.com
197+
CF_DNS_API_TOKEN: YSsfAH-d1q57j2D7T41ptAfM
198+
199+
# Supervisor settings
200+
supervisor:
201+
202+
# The Supervisor image to use, defaults to ghcr.io/tschaefer/supervisor:main
203+
image: ghcr.io/tschaefer/supervisor:latest
204+
205+
# Additional labels to apply to the Supervisor container
206+
labels:
207+
traefik.http.routers.supervisor.tls.certresolver: cloudflare
208+
209+
# Additional environment variables to pass to the Supervisor container
210+
env: {}
211+
```
212+
213+
Custom `hooks` scripts can be run before and after certain deployment steps.
214+
215+
* `post-docker-setup`
216+
* `pre-traefik-deploy`
217+
* `post-traefik-deploy`
218+
* `pre-supervisor-deploy`
219+
* `post-supervisor-deploy`
220+
221+
**Example**:
222+
223+
```bash
224+
#!/usr/bin/env sh
225+
226+
# pre-traefik-deploy hook script
227+
228+
cat <<EOF> /var/lib/traefik/traefik.yml
229+
---
230+
certificatesresolvers:
231+
cloudflare:
232+
acme:
233+
email: acme@example.com
234+
storage: /etc/traefik/certs.d/cloudflare.json
235+
dnschallenge:
236+
provider: cloudflare
237+
EOF
238+
```
239+
240+
The hook filename must be the hook name without any extension. The path to the
241+
hooks directory can be configured in the root under `hooks`.
242+
243+
```yaml
244+
hooks: /path/to/hooks
245+
```
246+
247+
The Supervisor service can be redeployed with the command `redeploy`.
248+
249+
```bash
250+
supervisor redeploy --host machine.example.com
251+
```
252+
253+
Optionally, Traefik can be redeployed with the option `--with-traefik`.
254+
99255
### Stack Management
100256

101257
The `stacks` commands provide a variety of operations for managing stacks.

etc/bash/completion

+100-11
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ __supervisor_stacks_completion() {
1919
done
2020

2121
local cmd
22-
cmd="supervisor ${configuration_file} list --json"
22+
cmd="supervisor ${configuration_file} stacks list --json"
2323

2424
local stacks
2525
stacks=$(eval ${cmd} | jq -r '.[].uuid')
@@ -165,7 +165,7 @@ _supervisor_stack_control() {
165165
_get_comp_words_by_ref -n : cur prev words
166166

167167
local options='--help --command'
168-
local actions='start stop restart'
168+
local actions='start stop restart redeploy'
169169

170170
case "$prev" in
171171
--command)
@@ -223,23 +223,66 @@ _supervisor_health() {
223223
fi
224224
}
225225

226-
_supervisor() {
226+
_supervisor_deploy() {
227+
local cur prev
228+
_get_comp_words_by_ref -n : cur prev
229+
230+
local options='--help'
231+
local options_deploy='--host --skip-docker --skip-traefik --verbose'
232+
233+
options="${options} ${options_deploy}"
234+
235+
case "$prev" in
236+
--help)
237+
return
238+
;;
239+
--host)
240+
_known_hosts
241+
return
242+
;;
243+
esac
244+
245+
if [[ "$cur" == -* ]]; then
246+
mapfile -t COMPREPLY < <(compgen -W "$options" -- "$cur")
247+
return
248+
fi
249+
}
250+
251+
_supervisor_redeploy() {
252+
local cur prev
253+
_get_comp_words_by_ref -n : cur prev
254+
255+
local options='--help'
256+
local options_deploy='--host --verbose --with-traefik'
257+
258+
options="${options} ${options_deploy}"
259+
260+
case "$prev" in
261+
--help)
262+
return
263+
;;
264+
--host)
265+
_known_hosts
266+
return
267+
;;
268+
esac
269+
270+
if [[ "$cur" == -* ]]; then
271+
mapfile -t COMPREPLY < <(compgen -W "$options" -- "$cur")
272+
return
273+
fi
274+
}
275+
276+
_supervisor_stacks() {
227277
local cur prev words
228278
_get_comp_words_by_ref -n : cur prev words
229279

230-
local actions="is-healthy create delete list show stats update control log"
280+
local actions="create delete list show stats update control log"
231281
local options='--help --man --version'
232-
local options_config='--configuration-file'
233-
234-
options="${options} ${options_config}"
235282

236283
local word
237284
for word in "${words[@]}"; do
238285
case $word in
239-
is-healthy)
240-
_supervisor_health
241-
return
242-
;;
243286
delete)
244287
_supervisor_stack_delete
245288
return
@@ -275,6 +318,52 @@ _supervisor() {
275318
esac
276319
done
277320

321+
case "$prev" in
322+
--help|--man|--version)
323+
return
324+
;;
325+
esac
326+
327+
if [[ "$cur" == -* ]]; then
328+
mapfile -t COMPREPLY < <(compgen -W "${options}" -- "$cur")
329+
return
330+
fi
331+
332+
mapfile -t COMPREPLY < <(compgen -W "${actions}" -- "$cur")
333+
}
334+
335+
_supervisor() {
336+
local cur prev words
337+
_get_comp_words_by_ref -n : cur prev words
338+
339+
local actions="redeploy deploy is-healthy stacks"
340+
local options='--help --man --version'
341+
local options_config='--configuration-file'
342+
343+
options="${options} ${options_config}"
344+
345+
local word
346+
for word in "${words[@]}"; do
347+
case $word in
348+
deploy)
349+
_supervisor_deploy
350+
return
351+
;;
352+
redeploy)
353+
_supervisor_redeploy
354+
return
355+
;;
356+
is-healthy)
357+
_supervisor_health
358+
return
359+
;;
360+
stacks)
361+
_supervisor_stacks
362+
return
363+
;;
364+
esac
365+
done
366+
278367
case "$prev" in
279368
--help|--man|--version)
280369
return

lib/supervisor.rb

+9-1
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,16 @@
11
# frozen_string_literal: true
22

3+
require 'active_support'
4+
require 'active_support/core_ext'
35
require 'zeitwerk'
46

57
loader = Zeitwerk::Loader.for_gem
8+
loader.inflector.inflect 'prepares_sshkit' => 'PreparesSSHKit'
9+
Dir.glob(File.join(__dir__, '/**/*/')).each do |dir|
10+
next unless dir.ends_with?('/concerns/')
11+
12+
loader.collapse(dir)
13+
end
614
loader.setup
715

816
module Supervisor
@@ -17,7 +25,7 @@ def configure
1725
end
1826

1927
def configured?
20-
@client ? true : false
28+
defined?(@client)
2129
end
2230

2331
def configured!

lib/supervisor/app.rb

+12-8
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,19 @@ module App
55
class Command < Supervisor::App::Base
66
option ['-c', '--configuration-file'], 'FILE', 'configuration file', attribute_name: :cfgfile
77

8+
subcommand 'deploy', 'Deploy the Supervisor service with stack', Supervisor::App::Deploy
9+
subcommand 'redeploy', 'Redeploy the Supervisor service', Supervisor::App::Redeploy
810
subcommand 'is-healthy', 'Check the health of the Supervisor service', Supervisor::App::Health
9-
subcommand 'list', 'List all stacks', Supervisor::App::Stacks::List
10-
subcommand 'show', 'Show a stack', Supervisor::App::Stacks::Show
11-
subcommand 'stats', 'Show stats of a stack', Supervisor::App::Stacks::Stats
12-
subcommand 'create', 'Create a stack', Supervisor::App::Stacks::Create
13-
subcommand 'update', 'Update a stack', Supervisor::App::Stacks::Update
14-
subcommand 'delete', 'Delete a stack', Supervisor::App::Stacks::Delete
15-
subcommand 'control', 'Control a stack', Supervisor::App::Stacks::Control
16-
subcommand 'log', 'Show the log of a stack', Supervisor::App::Stacks::Log
11+
subcommand 'stacks', 'Manage stacks' do
12+
subcommand 'list', 'List all stacks', Supervisor::App::Stacks::List
13+
subcommand 'show', 'Show a stack', Supervisor::App::Stacks::Show
14+
subcommand 'stats', 'Show stats of a stack', Supervisor::App::Stacks::Stats
15+
subcommand 'create', 'Create a stack', Supervisor::App::Stacks::Create
16+
subcommand 'update', 'Update a stack', Supervisor::App::Stacks::Update
17+
subcommand 'delete', 'Delete a stack', Supervisor::App::Stacks::Delete
18+
subcommand 'control', 'Control a stack', Supervisor::App::Stacks::Control
19+
subcommand 'log', 'Show the log of a stack', Supervisor::App::Stacks::Log
20+
end
1721
end
1822
end
1923
end

0 commit comments

Comments
 (0)