diff --git a/Puppetfile b/Puppetfile index ddb6e0a..da7b53a 100644 --- a/Puppetfile +++ b/Puppetfile @@ -42,6 +42,7 @@ mod 'puppet-filemapper', '4.0.0' mod 'puppet-letsencrypt', '11.0.0' mod 'puppet-rundeck', '9.1.0' mod 'puppet-redis', '11.0.0' +mod 'puppet-nodejs', '11.0.0' # other mod 'ghoneycutt-puppet', '3.3.0' diff --git a/hieradata/roles/apps/jupyter/hub.yaml b/hieradata/roles/apps/jupyter/hub.yaml new file mode 100644 index 0000000..24c171e --- /dev/null +++ b/hieradata/roles/apps/jupyter/hub.yaml @@ -0,0 +1,45 @@ +--- +profiles::packages::include: + python3.12: {} + python3.12-pip: {} + +hiera_include: + - profiles::nginx::simpleproxy + +# manage a simple nginx reverse proxy +profiles::nginx::simpleproxy::nginx_vhost: 'jupyterhub.query.consul' +profiles::nginx::simpleproxy::nginx_aliases: + - jupyterhub.service.consul + - jupyterhub.query.consul + - "jupyterhub.service.%{facts.country}-%{facts.region}.consul" + +profiles::nginx::simpleproxy::proxy_port: 8000 +profiles::nginx::simpleproxy::proxy_path: '/' +nginx::client_max_body_size: 20M + +# additional altnames +profiles::pki::vault::alt_names: + - jupyterhub.service.consul + - jupyterhub.query.consul + - "jupyterhub.service.%{facts.country}-%{facts.region}.consul" + +# configure consul service +consul::services: + jupyterhub: + service_name: 'jupyterhub' + tags: + - 'jupyterhub' + address: "%{facts.networking.ip}" + port: 443 + checks: + - id: 'jupyterhub_http_check' + name: 'jupyterhub HTTP Check' + http: "https://%{facts.networking.fqdn}" + method: 'GET' + tls_skip_verify: true + interval: '10s' + timeout: '1s' +profiles::consul::client::node_rules: + - resource: service + segment: jupyterhub + disposition: write diff --git a/hieradata/roles/infra/auth/glauth.yaml b/hieradata/roles/infra/auth/glauth.yaml index 7330ac9..3348ffb 100644 --- a/hieradata/roles/infra/auth/glauth.yaml +++ b/hieradata/roles/infra/auth/glauth.yaml @@ -63,6 +63,7 @@ glauth::users: - 20018 - 20023 - 20024 + - 20025 loginshell: '/bin/bash' homedir: '/home/benvin' passsha256: 'd2434f6b4764ef75d5b7b96a876a32deedbd6aa726a109c3f32e823ca66f604a' @@ -294,3 +295,9 @@ glauth::groups: vault_admin: group_name: 'vault_admin' gidnumber: 20024 + jupyterhub_admin: + group_name: 'jupyterhub_admin' + gidnumber: 20025 + jupyterhub_user: + group_name: 'jupyterhub_user' + gidnumber: 20026 diff --git a/site/profiles/manifests/jupyter/jupyterhub.pp b/site/profiles/manifests/jupyter/jupyterhub.pp new file mode 100644 index 0000000..af3bb7f --- /dev/null +++ b/site/profiles/manifests/jupyter/jupyterhub.pp @@ -0,0 +1,115 @@ +# profiles::jupyter::jupyterhub +class profiles::jupyter::jupyterhub ( + Stdlib::AbsolutePath $base_path = '/opt/jupyterhub', + Stdlib::AbsolutePath $venv_path = "${base_path}/venv", + Stdlib::AbsolutePath $config_path = "${base_path}/config.py", + Hash $vault_config = {}, + String $owner = 'jupyterhub', + String $group = 'jupyterhub', + Boolean $systempkgs = false, + String $version = '3.12', + Array[String[1]] $packages = [ + 'jupyterhub', + 'dockerspawner', + 'jupyterhub-ldapauthenticator', + ], + String $ldap_server_address = 'ldap://ldap.service.consul', + String $ldap_bind_dn_template = 'cn={username},ou=people,ou=users,dc=main,dc=unkin,dc=net', + Boolean $ldap_use_ssl = false, + Array $ldap_allowed_groups = ['ou=jupyterhub_user,ou=groups,dc=main,dc=unkin,dc=net'], + Array $ldap_admin_groups = ['ou=jupyterhub_admin,ou=groups,dc=main,dc=unkin,dc=net'], +){ + + # ensure nodejs:20 is installed + package { 'nodejs_module': + ensure => '20', + name => 'nodejs', + provider => 'dnfmodule', + enable_only => true, + } + -> package { 'nodejs': + ensure => 'installed', + provider => 'dnf', + } + -> package { 'npm': + ensure => 'installed', + provider => 'dnf', + } + -> package { 'configurable-http-proxy': + ensure => installed, + provider => 'npm', + } + + # ensure python3.12 is installed + if $::facts['python3_version'] { + + $python_version = $version ? { + 'system' => $::facts['python3_version'], + default => $version, + } + + # ensure the base_path exists + file { $base_path: + ensure => directory, + mode => '0755', + owner => $owner, + group => $group, + require => Profiles::Base::Account['jupyterhub'], + } + + # create a venv + python::pyvenv { $venv_path : + ensure => present, + version => $python_version, + systempkgs => $systempkgs, + venv_dir => $venv_path, + owner => $owner, + group => $group, + require => File[$base_path], + } + + # install the required pip packages + $packages.each |String $package| { + python::pip { "${venv_path}_${package}": + ensure => present, + pkgname => $package, + virtualenv => $venv_path, + } + } + + # create the config from a template + file { $config_path: + ensure => file, + mode => '0660', + owner => $owner, + group => $group, + content => Sensitive(template('profiles/jupyterhub/config.py.erb')), + require => Python::Pyvenv[$venv_path], + } + + profiles::base::account {$owner: + username => $owner, + uid => 1101, + gid => 1101, + groups => ['systemd-journal'], + system => true, + } + + systemd::unit_file { 'jupyterhub.service': + content => template('profiles/jupyterhub/jupyterhub.service.erb'), + enable => true, + active => true, + subscribe => File[$config_path], + require => [ + File[$config_path], + ], + } + + ## create symbolic link in $PATH + #file { "/usr/local/bin/${script_name}": + # ensure => 'link', + # target => "${base_path}/${script_name}", + # require => File["${base_path}/${script_name}"], + #} + } +} diff --git a/site/profiles/templates/jupyterhub/config.py.erb b/site/profiles/templates/jupyterhub/config.py.erb new file mode 100644 index 0000000..d4f964b --- /dev/null +++ b/site/profiles/templates/jupyterhub/config.py.erb @@ -0,0 +1,43 @@ +# jupyterhub_config.py + +from dockerspawner import DockerSpawner +import os + +c = get_config() + +# Basic JupyterHub settings +c.JupyterHub.bind_url = 'http://:8000' +c.JupyterHub.hub_ip = '0.0.0.0' +c.JupyterHub.hub_port = 8081 + +# Configure the DockerSpawner +c.JupyterHub.spawner_class = DockerSpawner +c.DockerSpawner.image = '<%= @docker_image %>' +c.DockerSpawner.network_name = '<%= @docker_network %>' + +# Notebook directory and mount location +notebook_dir = '/home/jupyter/work' +c.DockerSpawner.notebook_dir = notebook_dir + +# Optional: Volume mapping for user data persistence +c.DockerSpawner.volumes = { + 'jupyterhub-user-{username}': notebook_dir +} + +# DockerSpawner options +c.DockerSpawner.remove = True +c.DockerSpawner.debug = True + +# LDAP Authentication +c.JupyterHub.authenticator_class = 'ldapauthenticator.LDAPAuthenticator' + +# LDAP Server settings +c.LDAPAuthenticator.server_address = '<%= @ldap_server_address %>' +c.LDAPAuthenticator.bind_dn_template = '<%= @ldap_bind_dn_template %>' +c.LDAPAuthenticator.use_ssl = <%= @ldap_use_ssl ? 'True' : 'False' %> + +# Restrict access to a specific LDAP group +c.LDAPAuthenticator.allowed_groups = <%= @ldap_allowed_groups.to_s %> + +# Set an LDAP group as admins +c.LDAPAuthenticator.admin_groups = <%= @ldap_admin_groups.to_s %> diff --git a/site/profiles/templates/jupyterhub/jupyterhub.service.erb b/site/profiles/templates/jupyterhub/jupyterhub.service.erb new file mode 100644 index 0000000..9ee62a7 --- /dev/null +++ b/site/profiles/templates/jupyterhub/jupyterhub.service.erb @@ -0,0 +1,16 @@ +[Unit] +Description=JupyterHub +After=network.target + +[Service] +Type=simple +ExecStart=/opt/jupyterhub/venv/bin/jupyterhub -f /opt/jupyterhub/config.py +WorkingDirectory=/opt/jupyterhub +User=<%= @owner %> +Group=<%= @group %> + +Environment="PATH=/opt/jupyterhub/venv/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" +Restart=always + +[Install] +WantedBy=multi-user.target diff --git a/site/roles/manifests/apps/jupyter/hub.pp b/site/roles/manifests/apps/jupyter/hub.pp new file mode 100644 index 0000000..235e1ed --- /dev/null +++ b/site/roles/manifests/apps/jupyter/hub.pp @@ -0,0 +1,12 @@ +# jupyterhub server profile +class roles::apps::jupyter::hub { + if $facts['firstrun'] { + include profiles::defaults + include profiles::firstrun::init + }else{ + include profiles::defaults + include profiles::base + include profiles::base::datavol + include profiles::jupyter::jupyterhub + } +}