Skip to content

Commit

Permalink
Introduce supervisor deploy (#4)
Browse files Browse the repository at this point in the history
  • Loading branch information
tschaefer authored Dec 5, 2024
1 parent 30f0f70 commit ddaa6d4
Show file tree
Hide file tree
Showing 21 changed files with 852 additions and 71 deletions.
2 changes: 2 additions & 0 deletions .yamllint
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,7 @@ extends: default
rules:
indentation:
indent-sequences: consistent
line-length:
max: 120

# vim:ft=yaml
162 changes: 159 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,13 +73,14 @@ actions.

### Configuration

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

```yaml
---
base_url: https://supervisor.example.com
api_key: 8db7fde4-6a11-462e-ba27-6897b7c9281b
api:
uri: https://supervisor.example.com
token: 8db7fde4-6a11-462e-ba27-6897b7c9281b
```
### Command Reference
Expand All @@ -96,6 +97,161 @@ supervisor is-healthy

Checks the health of the Supervisor service.

### Deployment Management

The command `deploy` installs and sets up a containerized Supervisor service
on a vanilla Linux machine by provisioning the docker service and
deploying the application proxy [Traefik](https://traefik.io/).

#### Default Traefik docker command

```bash
docker run \
--detach --restart always --name traefik \
--volume /var/run/docker.sock:/var/run/docker.sock \
--volume /var/lib/traefik:/etc/traefik \
--network supervisor \
--publish 80:80 --publish 443:443 \
traefik:v3.2.1 \
--providers.docker.exposedbydefault="false" \
--entrypoints.web.address=":80" \
--entrypoints.websecure.address=":443" \
--certificatesresolvers.letsencrypt.acme.email="[email protected]" \
--certificatesresolvers.letsencrypt.acme.storage="/etc/traefik/certs.d/acme.json" \
--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint="web"
```

#### Default Supervisor docker command

```bash
docker run \
--detach --restart always --name supervisor \
--volume /var/run/docker.sock:/var/run/docker.sock \
--volume /var/lib/supervisor:/rails/storage \
--network supervisor \
--label traefik.enable="true" \
--label traefik.http.routers.supervisor.tls="true" \
--label traefik.http.routers.supervisor.tls.certresolver="letsencrypt" \
--label traefik.http.routers.supervisor.rule="Host(\"supervisor.example.com\")" \
--label traefik.http.routers.supervisor.entrypoints="websecure" \
--env SECRET_KEY_BASE="601f72235d8ea11db69e678f9...1a" \
--env SUPERVISOR_API_KEY="8db7fde4-6a11-462e-ba27-6897b7c9281b" \
ghcr.io/tschaefer/supervisor:main
```

#### Default docker network command

```bash
docker network create \
--attachable true \
--ipv6=true \
--driver=bridge \
--opt com.docker.network.container_iface_prefix=supervisor
supervisor
```

Prerequisites are super-user privileges, a valid DNS record for the
Supervisor service and the above mentioned configuration file.

While setup the necessary certificate is requested from
[Let's Encrypt](https://letsencrypt.org/) via HTTP-challenge.


```bash
supervisor deploy --host [email protected]
```

The provisioning of docker can be skipped wit the option `--skip-docker` as
well as the installation of Traefik with the option `--skip-traefik`. For a
more informative output use `--verbose` - beware, sensible information will be
exposed.

The deployment is customizable by configuration in the root under `deploy`.

```yaml
deploy:

# Network settings
network:

# The name of the network to create, defaults to supervisor
name: supervisor
# Additional options to pass to the network create command
options:
ipv6: false
opt: com.docker.network.driver.mtu=1500

# Traefik settings
traefik:

# The Traefik image to use, defaults to traefik:v3.2.1
image: traefik:v3.2.0

# Additional arguments to pass to the Traefik container
args:
configfile: /etc/traefik/traefik.yml

# Additional environment variables to pass to the Traefik container
env:
CF_API_EMAIL: [email protected]
CF_DNS_API_TOKEN: YSsfAH-d1q57j2D7T41ptAfM

# Supervisor settings
supervisor:

# The Supervisor image to use, defaults to ghcr.io/tschaefer/supervisor:main
image: ghcr.io/tschaefer/supervisor:latest

# Additional labels to apply to the Supervisor container
labels:
traefik.http.routers.supervisor.tls.certresolver: cloudflare

# Additional environment variables to pass to the Supervisor container
env: {}
```
Custom `hooks` scripts can be run before and after certain deployment steps.

* `post-docker-setup`
* `pre-traefik-deploy`
* `post-traefik-deploy`
* `pre-supervisor-deploy`
* `post-supervisor-deploy`

**Example**:

```bash
#!/usr/bin/env sh
# pre-traefik-deploy hook script
cat <<EOF> /var/lib/traefik/traefik.yml
---
certificatesresolvers:
cloudflare:
acme:
email: [email protected]
storage: /etc/traefik/certs.d/cloudflare.json
dnschallenge:
provider: cloudflare
EOF
```

The hook filename must be the hook name without any extension. The path to the
hooks directory can be configured in the root under `hooks`.

```yaml
hooks: /path/to/hooks
```

The Supervisor service can be redeployed with the command `redeploy`.

```bash
supervisor redeploy --host machine.example.com
```

Optionally, Traefik can be redeployed with the option `--with-traefik`.

### Stack Management

The `stacks` commands provide a variety of operations for managing stacks.
Expand Down
111 changes: 100 additions & 11 deletions etc/bash/completion
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ __supervisor_stacks_completion() {
done

local cmd
cmd="supervisor ${configuration_file} list --json"
cmd="supervisor ${configuration_file} stacks list --json"

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

local options='--help --command'
local actions='start stop restart'
local actions='start stop restart redeploy'

case "$prev" in
--command)
Expand Down Expand Up @@ -223,23 +223,66 @@ _supervisor_health() {
fi
}

_supervisor() {
_supervisor_deploy() {
local cur prev
_get_comp_words_by_ref -n : cur prev

local options='--help'
local options_deploy='--host --skip-docker --skip-traefik --verbose'

options="${options} ${options_deploy}"

case "$prev" in
--help)
return
;;
--host)
_known_hosts
return
;;
esac

if [[ "$cur" == -* ]]; then
mapfile -t COMPREPLY < <(compgen -W "$options" -- "$cur")
return
fi
}

_supervisor_redeploy() {
local cur prev
_get_comp_words_by_ref -n : cur prev

local options='--help'
local options_deploy='--host --verbose --with-traefik'

options="${options} ${options_deploy}"

case "$prev" in
--help)
return
;;
--host)
_known_hosts
return
;;
esac

if [[ "$cur" == -* ]]; then
mapfile -t COMPREPLY < <(compgen -W "$options" -- "$cur")
return
fi
}

_supervisor_stacks() {
local cur prev words
_get_comp_words_by_ref -n : cur prev words

local actions="is-healthy create delete list show stats update control log"
local actions="create delete list show stats update control log"
local options='--help --man --version'
local options_config='--configuration-file'

options="${options} ${options_config}"

local word
for word in "${words[@]}"; do
case $word in
is-healthy)
_supervisor_health
return
;;
delete)
_supervisor_stack_delete
return
Expand Down Expand Up @@ -275,6 +318,52 @@ _supervisor() {
esac
done

case "$prev" in
--help|--man|--version)
return
;;
esac

if [[ "$cur" == -* ]]; then
mapfile -t COMPREPLY < <(compgen -W "${options}" -- "$cur")
return
fi

mapfile -t COMPREPLY < <(compgen -W "${actions}" -- "$cur")
}

_supervisor() {
local cur prev words
_get_comp_words_by_ref -n : cur prev words

local actions="redeploy deploy is-healthy stacks"
local options='--help --man --version'
local options_config='--configuration-file'

options="${options} ${options_config}"

local word
for word in "${words[@]}"; do
case $word in
deploy)
_supervisor_deploy
return
;;
redeploy)
_supervisor_redeploy
return
;;
is-healthy)
_supervisor_health
return
;;
stacks)
_supervisor_stacks
return
;;
esac
done

case "$prev" in
--help|--man|--version)
return
Expand Down
10 changes: 9 additions & 1 deletion lib/supervisor.rb
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
# frozen_string_literal: true

require 'active_support'
require 'active_support/core_ext'
require 'zeitwerk'

loader = Zeitwerk::Loader.for_gem
loader.inflector.inflect 'prepares_sshkit' => 'PreparesSSHKit'
Dir.glob(File.join(__dir__, '/**/*/')).each do |dir|
next unless dir.ends_with?('/concerns/')

loader.collapse(dir)
end
loader.setup

module Supervisor
Expand All @@ -17,7 +25,7 @@ def configure
end

def configured?
@client ? true : false
defined?(@client)
end

def configured!
Expand Down
20 changes: 12 additions & 8 deletions lib/supervisor/app.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,19 @@ module App
class Command < Supervisor::App::Base
option ['-c', '--configuration-file'], 'FILE', 'configuration file', attribute_name: :cfgfile

subcommand 'deploy', 'Deploy the Supervisor service with stack', Supervisor::App::Deploy
subcommand 'redeploy', 'Redeploy the Supervisor service', Supervisor::App::Redeploy
subcommand 'is-healthy', 'Check the health of the Supervisor service', Supervisor::App::Health
subcommand 'list', 'List all stacks', Supervisor::App::Stacks::List
subcommand 'show', 'Show a stack', Supervisor::App::Stacks::Show
subcommand 'stats', 'Show stats of a stack', Supervisor::App::Stacks::Stats
subcommand 'create', 'Create a stack', Supervisor::App::Stacks::Create
subcommand 'update', 'Update a stack', Supervisor::App::Stacks::Update
subcommand 'delete', 'Delete a stack', Supervisor::App::Stacks::Delete
subcommand 'control', 'Control a stack', Supervisor::App::Stacks::Control
subcommand 'log', 'Show the log of a stack', Supervisor::App::Stacks::Log
subcommand 'stacks', 'Manage stacks' do
subcommand 'list', 'List all stacks', Supervisor::App::Stacks::List
subcommand 'show', 'Show a stack', Supervisor::App::Stacks::Show
subcommand 'stats', 'Show stats of a stack', Supervisor::App::Stacks::Stats
subcommand 'create', 'Create a stack', Supervisor::App::Stacks::Create
subcommand 'update', 'Update a stack', Supervisor::App::Stacks::Update
subcommand 'delete', 'Delete a stack', Supervisor::App::Stacks::Delete
subcommand 'control', 'Control a stack', Supervisor::App::Stacks::Control
subcommand 'log', 'Show the log of a stack', Supervisor::App::Stacks::Log
end
end
end
end
Loading

0 comments on commit ddaa6d4

Please sign in to comment.