instance-runner.cfg 19 KB
Newer Older
1 2
[buildout]
parts =
3 4 5 6
  nginx_conf
  nginx-launcher
  certificate-authority
  ca-nginx
7
  ca-shellinabox
8 9
  gunicorn-launcher
  gunicorn-graceful
10
  test-runner
11
  sshkeys-dropbear-runner
12
  dropbear-server-add-authorized-key
13
  sshkeys-authority
14
  publish-connection-informations
15 16 17
  slaprunner-promise
  slaprunner-frontend-promise
  dropbear-promise
18
  private
19
  shellinabox-promise
20
  symlinks
21
  shellinabox
22
  slapos-cfg
23
  slapos-repo-config
24
  cron-entry-prepare-software
25
  deploy-instance-parameters
26 27 28
{% if slapparameter_dict.get('custom-frontend-backend-url') %}
  custom-frontend-promise
{% endif %}
29 30 31 32 33 34
###Parts to add for monitoring
  slap-parameters
  certificate-authority
  cron
  cron-entry-monitor
  cron-entry-rss
35
  deploy-index
36
  deploy-index-template
37 38
  deploy-monitor-script
  deploy-rss-script
39 40
  deploy-settings-cgi
  deploy-status-cgi
41
  make-rss
42
  monitor-promise
43
  setup-static-files
44 45 46 47 48 49
  certificate-authority
  public
  zero-parameters
  cgi-httpd-wrapper

extends = ${monitor-template:output}
50 51 52 53 54

eggs-directory = ${buildout:eggs-directory}
develop-eggs-directory = ${buildout:develop-eggs-directory}
offline = true

55 56 57 58 59 60 61 62 63 64 65 66 67
{% if slapparameter_dict.get('custom-frontend-backend-url') -%}
# Requests, if defined, a frontend to allow access to a server
# located inside of the virtual machine listening to port X
# to LAN IPv4.
# Internaly, the frontend will be asked to listen on the IPv6
# with port X + 10000, to match NAT rules of Qemu.
[request-custom-frontend]
recipe = slapos.cookbook:request
software-url = {{ slapparameter_dict.get('custom-frontend-software-url', 'http://git.erp5.org/gitweb/slapos.git/blob_plain/HEAD:/software/apache-frontend/software.cfg') }}
software-type = {{ slapparameter_dict.get('custom-frontend-software-type', 'RootSoftwareInstance') }}
slave = true
name = Custom Web Frontend

68 69 70 71 72
server-url = $${slap-connection:server-url}
key-file = $${slap-connection:key-file}
cert-file = $${slap-connection:cert-file}
computer-id = $${slap-connection:computer-id}
partition-id = $${slap-connection:partition-id}
73 74 75

{% if slapparameter_dict.get('custom-frontend-instance-guid') -%}
sla = instance_guid
76
sla-instance_guid = $${slap-parameter:frontend-instance-guid}
77 78
{% endif -%}

79
config = url
80
config-url = {{ slapparameter_dict.get('custom-frontend-backend-url') }}
81
return = site_url domain
82 83 84

[custom-frontend-promise]
recipe = slapos.cookbook:check_url_available
85 86 87 88 89
path = $${directory:promises}/custom_frontend_promise
url = https://$${request-custom-frontend:connection-domain}
{% if slapparameter_dict.get('custom-frontend-basic-auth') -%}
check-secure = 1
{% endif -%}
90 91 92 93
dash_path = {{ dash_executable_location }}
curl_path = {{ curl_executable_location }}

[publish-connection-informations]
94
custom-frontend-url = https://$${request-custom-frontend:connection-domain}
95 96
{% endif -%}

97
# Create all needed directories
98
[directory]
99 100 101 102 103
recipe = slapos.cookbook:mkdirectory
etc = $${buildout:directory}/etc/
var = $${buildout:directory}/var/
srv = $${buildout:directory}/srv/
bin = $${buildout:directory}/bin/
104
tmp = $${buildout:directory}/tmp/
105

106 107 108 109 110 111 112 113 114
sshkeys = $${:srv}/sshkeys
services = $${:etc}/service/
scripts = $${:etc}/run/
ssh = $${:etc}/ssh/
log = $${:var}/log/
run = $${:var}/run/
backup = $${:srv}/backup/
promises = $${:etc}/promise/
test = $${:etc}/test/
115
nginx-data = $${directory:srv}/nginx
116
ca-dir = $${:srv}/ssl
117
project = $${:srv}/runner/project
118 119 120

[runnerdirectory]
recipe = slapos.cookbook:mkdirectory
121 122
home = $${directory:srv}/runner/
test = $${directory:srv}/test/
123 124 125
project = $${:home}/project
software-root = $${:home}/software
instance-root = $${:home}/instance
126 127 128
project-test = $${:test}/project
software-test = $${:test}/software
instance-test = $${:test}/instance
129
sessions = $${buildout:directory}/.sessions
130

131 132
#Create password recovery code for slaprunner
[recovery-code]
133
recipe = slapos.cookbook:generate.password
134
storage-path = $${directory:etc}/.rcode
135
bytes = 8
136

137
[slaprunner]
138 139 140 141 142
slaprunner = ${buildout:directory}/bin/slaprunner
slapgrid_sr = ${buildout:directory}/bin/slapgrid-sr
slapgrid_cp = ${buildout:directory}/bin/slapgrid-cp
slapproxy = ${buildout:directory}/bin/slapproxy
supervisor = ${buildout:directory}/bin/slapgrid-supervisorctl
143
git-binary = ${git:location}/bin/git
144
slapos.cfg = $${directory:etc}/slapos.cfg
145 146 147 148
working-directory = $${runnerdirectory:home}
project-directory = $${runnerdirectory:project}
software-directory = $${runnerdirectory:software-root}
instance-directory = $${runnerdirectory:instance-root}
149 150 151
etc_dir = $${directory:etc}
log_dir =  $${directory:log}
run_dir = $${directory:run}
152 153 154
ssh_client = $${sshkeys-dropbear-runner:wrapper}
public_key = $${sshkeys-dropbear-runner:public-key}
private_key = $${sshkeys-dropbear-runner:private-key}
155 156
ipv4 = $${slap-network-information:local-ipv4}
ipv6 = $${slap-network-information:global-ipv6}
Jean-Baptiste Petre's avatar
Jean-Baptiste Petre committed
157
proxy_port = 50000
158
runner_port = 50005
159
partition-amount = $${slap-parameter:instance-amount}
160
wrapper = $${directory:services}/slaprunner
161
debug = $${slap-parameter:debug}
162
access-url = https://[$${:ipv6}]:$${:runner_port} 
Nicolas Wavrant's avatar
Nicolas Wavrant committed
163 164
supd-temp-var = $${directory:etc}/supervisord.conf
prox-db-var = $${slaprunner:working-directory}/proxy.db
165 166 167
console = False
verbose = False
debug = False
168

169 170 171 172
[test-runner]
<= slaprunner
recipe = slapos.cookbook:slaprunner.test
slaprunnertest = ${buildout:directory}/bin/slaprunnertest
173
slapos.cfg = $${directory:etc}/slapos-test.cfg
174 175 176 177
working-directory = $${runnerdirectory:test}
project-directory = $${runnerdirectory:project-test}
software-directory = $${runnerdirectory:software-test}
instance-directory = $${runnerdirectory:instance-test}
178
proxy_port = 8602
179 180 181 182 183 184 185 186 187
wrapper = $${directory:bin}/runTestSuite
etc_dir = $${directory:test}


# Deploy dropbear (minimalist SSH server)
[sshkeys-directory]
recipe = slapos.cookbook:mkdirectory
requests = $${directory:sshkeys}/requests/
keys = $${directory:sshkeys}/keys/
188

189 190 191 192
[sshkeys-authority]
recipe = slapos.cookbook:sshkeys_authority
request-directory = $${sshkeys-directory:requests}
keys-directory = $${sshkeys-directory:keys}
193
wrapper = $${directory:services}/sshkeys_authority
194 195
keygen-binary = ${dropbear:location}/bin/dropbearkey

196
[dropbear-runner-server]
197 198
recipe = slapos.cookbook:dropbear
host = $${slap-network-information:global-ipv6}
199
port = 22222
200
home = $${directory:ssh}
201 202
wrapper = $${directory:bin}/runner_sshd
shell = ${bash:location}/bin/bash
203 204 205
rsa-keyfile = $${directory:ssh}/server_key.rsa
dropbear-binary = ${dropbear:location}/sbin/dropbear

206
[sshkeys-dropbear-runner]
207 208 209 210
<= sshkeys-authority
recipe = slapos.cookbook:sshkeys_authority.request
name = dropbear
type = rsa
211 212 213 214
executable = $${dropbear-runner-server:wrapper}
public-key = $${dropbear-runner-server:rsa-keyfile}.pub
private-key = $${dropbear-runner-server:rsa-keyfile}
wrapper = $${directory:services}/runner_sshd
215 216

[dropbear-server-add-authorized-key]
217
<= dropbear-runner-server
218 219 220
recipe = slapos.cookbook:dropbear.add_authorized_key
key = $${slap-parameter:authorized-key}

221
#---------------------------
222
#--
223 224
#-- Set nginx frontend

225 226 227 228 229 230 231 232 233 234 235 236
[tempdirectory]
recipe = slapos.cookbook:mkdirectory
client_body_temp_path = $${directory:tmp}/client_body_temp_path
proxy_temp_path = $${directory:tmp}/proxy_temp_path
fastcgi_temp_path = $${directory:tmp}/fastcgi_temp_path
uwsgi_temp_path = $${directory:tmp}/uwsgi_temp_path
scgi_temp_path = $${directory:tmp}/scgi_temp_path

[nginx-frontend]
# Options
nb_workers = 2
# Network
237
local-ip = $${slap-network-information:local-ipv4}
238
port = 30001
239 240
global-ip = $${slap-network-information:global-ipv6}
global-port = $${slaprunner:runner_port}
241
# Backend
242 243
runner-ip = $${slaprunner:ipv4}
runner-port = $${slaprunner:runner_port}
244 245 246 247 248 249 250 251
# SSL
ssl-certificate = $${ca-nginx:cert-file}
ssl-key = $${ca-nginx:key-file}
# Log
path_pid = $${directory:run}/nginx.pid
path_log = $${directory:log}/nginx.log
path_access_log = $${directory:log}/nginx.access.log
path_error_log = $${directory:log}/nginx.error.log
252
path_tmp = $${directory:tmp}/
253 254 255
# Config files
path_nginx_conf = $${directory:etc}/nginx.conf
# Executables
256
bin_nginx = ${nginx-webdav:location}/sbin/nginx
257
bin_launcher = $${directory:bin}/launcher
258 259
# Utils
path_shell = ${dash:location}/bin/dash
260 261
# Misc.
etc_dir = $${directory:etc}
262
work_dir = $${slaprunner:working-directory}
263 264 265 266 267 268

[nginx_conf]
recipe = slapos.recipe.template:jinja2
template = ${template_nginx_conf:location}/${template_nginx_conf:filename}
rendered = $${nginx-frontend:path_nginx_conf}
context =
269
    key shellinabox_port shellinabox:port
270
    key socket gunicorn:socket
271 272 273 274 275 276 277 278 279 280 281
    section param_nginx_frontend nginx-frontend
    section param_tempdir tempdirectory

[nginx-launcher]
recipe = slapos.recipe.template:jinja2
template = ${template_launcher:location}/${template_launcher:filename}
rendered = $${nginx-frontend:bin_launcher}
mode = 700
context =
    section param_nginx_frontend nginx-frontend

282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312
[httpd-parameters]
path_pid = $${directory:run}/httpd.pid
path_error_log = $${directory:log}/httpd-error.log
path_access_log = $${directory:log}/httpd-access.log
key_file = $${ca-httpd:key-file}
cert_file = $${ca-httpd:cert-file}
global_ip = $${slap-network-information:global-ipv6}
global_port = $${slaprunner:runner_port}
monitor_port = $${monitor-parameters:port}
monitor_index = $${deploy-index:rendered}
working_directory = $${slaprunner:working-directory}
dav_lock = $${directory:var}/DavLock
etc_dir = $${directory:etc}
document_root = $${directory:www}
project_folder = $${directory:project}
git_http_backend = ${git:location}/libexec/git-core/git-http-backend
cgi_httpd_conf = $${cgi-httpd-configuration-file:output}

[httpd-conf]
recipe = slapos.recipe.template:jinja2
template = ${template_httpd_conf:location}/${template_httpd_conf:filename}
rendered = $${directory:etc}/httpd.conf
context = 
    section parameters httpd-parameters

[cgi-httpd-wrapper]
recipe = slapos.cookbook:wrapper                                                    
apache-executable = ${apache:location}/bin/httpd                                    
wrapper-path = $${ca-httpd:executable}  
command-line = $${:apache-executable} -f $${httpd-conf:rendered} -DFOREGROUND

313 314 315 316
#--------------------
#--
#-- WSGI

317
[gunicorn]
318 319 320 321 322 323 324 325
bin_gunicorn = $${directory:bin}/gunicorn
bin_launcher = $${directory:services}/gunicorn
path_shell = ${dash:location}/bin/dash
socket = $${directory:tmp}/flaskserver.sock
path_pid = $${directory:run}/gunicorn.pid

[gunicorn-launcher]
recipe = slapos.cookbook:wrapper
326 327
command-line = $${gunicorn:bin_gunicorn} slapos.runner:app -p $${gunicorn:path_pid} -b unix:$${gunicorn:socket} -e RUNNER_CONFIG=$${slaprunner:slapos.cfg} --preload
wrapper-path = $${gunicorn:bin_launcher}
328 329 330 331 332
environment = PATH=$${environ:PATH}
  RUNNER_CONFIG=$${slaprunner:slapos.cfg}

[gunicorn-graceful]
recipe = slapos.cookbook:wrapper
333
command-line = $${directory:bin}/killpidfromfile $${gunicorn:path_pid} SIGHUP
334 335
wrapper-path = $${directory:scripts}/gunicorn-graceful

336
#--------------------
337
#--
338
#-- ssl certificates
339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363

[certificate-authority]
recipe = slapos.cookbook:certificate_authority
openssl-binary = ${openssl:location}/bin/openssl
ca-dir = $${directory:ca-dir}
requests-directory = $${cadirectory:requests}
wrapper = $${directory:services}/certificate_authority
ca-private = $${cadirectory:private}
ca-certs = $${cadirectory:certs}
ca-newcerts = $${cadirectory:newcerts}
ca-crl = $${cadirectory:crl}

[cadirectory]
recipe = slapos.cookbook:mkdirectory
requests = $${directory:ca-dir}/requests/
private = $${directory:ca-dir}/private/
certs = $${directory:ca-dir}/certs/
newcerts = $${directory:ca-dir}/newcerts/
crl = $${directory:ca-dir}/crl/

[ca-nginx]
<= certificate-authority
recipe = slapos.cookbook:certificate_authority.request
key-file = $${cadirectory:certs}/nginx_frontend.key
cert-file = $${cadirectory:certs}/nginx_frontend.crt
364 365
executable = $${nginx-launcher:rendered}
wrapper = $${directory:services}/nginx-frontend
366 367 368
# Put domain name
name = example.com

369 370 371 372 373
[ca-shellinabox]
<= certificate-authority
recipe = slapos.cookbook:certificate_authority.request
executable = $${shellinabox:wrapper}
wrapper = $${directory:services}/shellinaboxd
374 375
key-file = $${cadirectory:certs}/shellinabox.key
cert-file = $${cadirectory:certs}/shellinabox.crt
376
#--------------------
377 378
#--
#-- Request frontend
379

380 381
[request-frontend]
<= slap-connection
382
recipe = slapos.cookbook:requestoptional
383
name = SlapRunner Frontend
384 385 386
# XXX We have hardcoded SR URL here.
software-url = http://git.erp5.org/gitweb/slapos.git/blob_plain/HEAD:/software/apache-frontend/software.cfg
slave = true
387
config = url domain
388
config-url = $${slaprunner:access-url}
389
config-domain = $${slap-parameter:frontend-domain}
390
return = site_url domain
391

392 393 394 395 396 397 398 399 400 401 402 403
[monitor-frontend]
<= slap-connection
recipe = slapos.cookbook:requestoptional
name = Monitor Frontend
# XXX We have hardcoded SR URL here.
software-url = http://git.erp5.org/gitweb/slapos.git/blob_plain/HEAD:/software/apache-frontend/software.cfg
slave = true
config = url domain
config-url = https://[$${cgi-httpd-configuration-file:listening-ip}]:$${monitor-parameters:port}
config-domain = $${slap-parameter:frontend-domain}
return = site_url domain

404
#--------------------------------------
405 406
#--
#-- Send informations to SlapOS Master
407

408 409
[publish-connection-informations]
recipe = slapos.cookbook:publish
410 411
1_info = On your first run, Use "access_url" to setup you account.Then you can use both "url" or "access_url". Or "backend_url" if you want to use ipv6. Set your account in the webrunner in order to use webdav, and being able to access to clone your git repositories from the runner.
2_info = In order to set up your account, get the recovery-code from the monitoring interface. Before read the notification on monitor_info.
412
backend_url = $${slaprunner:access-url}
413
access_url = $${:url}/login
414
url =  https://$${request-frontend:connection-domain}
415
ssh_command = ssh $${dropbear-runner-server:host} -p $${dropbear-runner-server:port}
416
webdav_url = https://$${request-frontend:connection-domain}/share
417
monitor_url = https://$${monitor-frontend:connection-domain}/index.cgi
418

419 420 421
#---------------------------
#--
#-- Deploy promises scripts
Jean-Baptiste Petre's avatar
Jean-Baptiste Petre committed
422

423 424
[slaprunner-promise]
recipe = slapos.cookbook:check_port_listening
425
path = $${directory:promises}/slaprunner
426 427
hostname = $${slaprunner:ipv6}
port = $${slaprunner:runner_port}
Jean-Baptiste Petre's avatar
Jean-Baptiste Petre committed
428

429
[slaprunner-frontend-promise]
Jean-Baptiste Petre's avatar
Jean-Baptiste Petre committed
430
recipe = slapos.cookbook:check_url_available
431
path = $${directory:promises}/slaprunner_frontend
432
url = https://$${request-frontend:connection-domain}/login
Jean-Baptiste Petre's avatar
Jean-Baptiste Petre committed
433 434 435
dash_path = ${dash:location}/bin/dash
curl_path = ${curl:location}/bin/curl

436 437
[dropbear-promise]
recipe = slapos.cookbook:check_port_listening
438
path = $${directory:promises}/dropbear
439 440
hostname = $${dropbear-runner-server:host}
port = $${dropbear-runner-server:port}
441

442 443 444 445 446 447
[shellinabox-promise]
recipe = slapos.cookbook:check_port_listening
path = $${directory:promises}/shellinabox
hostname = $${shellinabox:ipv6}
port = $${shellinabox:port}

448 449
[symlinks]
recipe = cns.recipe.symlink
450
symlink_target = $${directory:bin}
451
symlink_base = ${buildout:directory}/bin
452 453 454 455

[slap-parameter]
# Default value if no ssh key is specified
authorized-key =
456
# Default value of instances number in slaprunner
457
instance-amount = 10
458 459
debug = false
frontend-domain =
460 461 462 463
slapos-repository = http://git.erp5.org/repos/slapos.git
slapos-software =
slapos-reference = master
auto-deploy = true
464
auto-deploy-instance = true
465
autorun = false
466 467 468 469
monitor-port = 9685

[monitor-parameters]
port = $${slap-parameter:monitor-port}
470

471 472 473 474 475 476 477 478
[slapos-cfg]
recipe = slapos.recipe.template:jinja2
template = ${slapos-cfg-template:location}/${slapos-cfg-template:filename}
rendered = $${slaprunner:slapos.cfg}
mode = 700
context =
  key software_root runnerdirectory:software-root
  key instance_root runnerdirectory:instance-root
479 480 481
  key auto_deploy slap-parameter:auto-deploy
  key auto_deploy_instance slap-parameter:auto-deploy-instance
  key autorun slap-parameter:autorun
482 483 484 485
  key console slaprunner:console
  key debug slaprunner:debug
  key etc_dir slaprunner:etc_dir
  key git slaprunner:git-binary
486
  key ipv4 slaprunner:ipv4
487 488
  key ipv6 slaprunner:ipv6
  key log_dir slaprunner:log_dir
489
  key partition_amount slaprunner:partition-amount
490 491 492 493 494 495 496 497
  key private_key slaprunner:private_key
  key proxy_database slaprunner:prox-db-var
  key proxy_host slaprunner:ipv4
  key proxy_port slaprunner:proxy_port
  key public_key slaprunner:public_key
  key run_dir slaprunner:run_dir
  key runner_port slaprunner:runner_port
  key runner_workdir slaprunner:working-directory
498
  key slapgrid_cp slaprunner:slapgrid_cp
499
  key slapgrid_sr slaprunner:slapgrid_sr 
500
  key slapproxy slaprunner:slapproxy 
501
  key ssh_client slaprunner:ssh_client
502
  key supervisor slaprunner:supervisor
Nicolas Wavrant's avatar
Nicolas Wavrant committed
503
  key supervisord_config slaprunner:supd-temp-var
504
  key verbose slaprunner:verbose
505

506 507
[shellinabox]
recipe = slapos.cookbook:shellinabox
508
ipv6 = $${slap-network-information:global-ipv6}
509
port = 8080
510
shell = $${shell:wrapper}
511
wrapper = $${directory:bin}/shellinaboxd
512
shellinabox-binary = ${shellinabox:location}/bin/shellinaboxd 
513
password = $${zero-parameters:shell-password}
514
directory = $${runnerdirectory:home}
515
login-shell = $${directory:bin}/login
516 517 518
certificate-directory = $${cadirectory:certs}
cert-file = $${ca-shellinabox:cert-file}
key-file = $${ca-shellinabox:key-file}
519

520 521 522
[shellinabox-code]
recipe = slapos.cookbook:generate.password
storage-path = $${directory:etc}/.scode
523
bytes = 8
524 525 526 527 528 529

[shell]
recipe = slapos.cookbook:shell
wrapper = $${directory:bin}/sh
shell = ${bash:location}/bin/bash
home = $${runnerdirectory:home}
530
path = $${environ:PATH}:${nano:location}/bin:${vim:location}/bin:${screen:location}/bin:${git:location}/bin
531 532
ps1 = "\\w> "

533 534 535
[environ]
recipe = collective.recipe.environment

536 537 538 539 540 541 542 543 544 545
[slapos-repo]
recipe = slapos.recipe.build:gitclone
repository = $${slap-parameter:slapos-repository}
git-executable = ${git:location}/bin/git
develop = true
location = $${directory:project}/slapos

[slapos-repo-config]
recipe = plone.recipe.command
stop-on-error = true
546 547
command = cd $${slapos-repo:location} && ${git:location}/bin/git checkout $${slap-parameter:slapos-reference} && SR=$${slap-parameter:slapos-software} && if [ -n "$SR" ] && [ ! -f "$${directory:etc}/.project" ]; then echo workspace/slapos/$${slap-parameter:slapos-software}/ > $${directory:etc}/.project; fi
update-command = true
548 549 550

[prepare-software]
recipe = slapos.cookbook:wrapper
551
command-line = ${curl:location}/bin/curl -g https://[$${slaprunner:ipv6}]:$${slaprunner:runner_port}/isSRReady --max-time 1 --insecure
552 553
wrapper-path = $${directory:scripts}/prepareSoftware

554 555 556 557 558 559 560
[cron-entry-prepare-software]
<= cron
recipe = slapos.cookbook:cron.d
name = prepare-software
frequency = */2 * * * *
command = $${prepare-software:wrapper-path}

561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583
[instance-parameters]
recipe = slapos.recipe.template:jinja2
extensions = jinja2.ext.do
template = ${parameters-template:location}/${parameters-template:filename}
rendered = $${directory:etc}/.parameter.xml.default
mode = 0644
context = 
  key slapparameter_dict slap-configuration:configuration

[deploy-instance-parameters]
recipe = plone.recipe.command
stop-on-error = true
parameter-xml = $${directory:etc}/.parameter.xml
command = if [ ! -f $${:parameter-xml} ]; then cp $${instance-parameters:rendered} $${:parameter-xml}; fi

[slap-configuration]
recipe = slapos.cookbook:slapconfiguration.serialised
computer = $${slap-connection:computer-id}
partition = $${slap-connection:partition-id}
url = $${slap-connection:server-url}
key = $${slap-connection:key-file}
cert = $${slap-connection:cert-file}

584 585 586
[public]
shell-password = $${shellinabox-code:passwd}

587 588 589 590 591
[private]
recipe = slapos.cookbook:zeroknown.write
filename = knowledge0.cfg
recovery-code = $${recovery-code:passwd}

592
[zero-parameters]