{"id":13609323,"url":"https://github.com/jupyterhub/systemdspawner","last_synced_at":"2025-05-16T09:03:59.555Z","repository":{"id":10918097,"uuid":"67483684","full_name":"jupyterhub/systemdspawner","owner":"jupyterhub","description":"Spawn JupyterHub single-user notebook servers with systemd","archived":false,"fork":false,"pushed_at":"2024-12-01T05:32:30.000Z","size":192,"stargazers_count":94,"open_issues_count":26,"forks_count":45,"subscribers_count":7,"default_branch":"main","last_synced_at":"2025-05-09T21:13:15.588Z","etag":null,"topics":["jupyter","jupyterhub","systemd","systemd-spawner"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jupyterhub.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2016-09-06T07:20:44.000Z","updated_at":"2025-05-02T05:19:15.000Z","dependencies_parsed_at":"2023-01-13T16:13:49.339Z","dependency_job_id":"2c867055-137f-44c6-a86d-5e6f08ae472c","html_url":"https://github.com/jupyterhub/systemdspawner","commit_stats":{"total_commits":185,"total_committers":21,"mean_commits":8.80952380952381,"dds":0.4972972972972973,"last_synced_commit":"45b06fefaf7751d2560da2bf60b9a34fbfe42f5a"},"previous_names":[],"tags_count":15,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jupyterhub%2Fsystemdspawner","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jupyterhub%2Fsystemdspawner/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jupyterhub%2Fsystemdspawner/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jupyterhub%2Fsystemdspawner/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jupyterhub","download_url":"https://codeload.github.com/jupyterhub/systemdspawner/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254501556,"owners_count":22081528,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["jupyter","jupyterhub","systemd","systemd-spawner"],"created_at":"2024-08-01T19:01:34.095Z","updated_at":"2025-05-16T09:03:59.531Z","avatar_url":"https://github.com/jupyterhub.png","language":"Python","funding_links":[],"categories":["JupyterHub容器等","JupyterHub Spawners"],"sub_categories":[],"readme":"**[Features](#features)** |\n**[Requirements](#requirements)** |\n**[Installation](#installation)** |\n**[Configuration](#configuration)** |\n**[Getting help](#getting-help)** |\n**[License](#license)**\n\n# systemdspawner\n\n[![Latest PyPI version](https://img.shields.io/pypi/v/jupyterhub-systemdspawner?logo=pypi)](https://pypi.python.org/pypi/jupyterhub-systemdspawner)\n[![Latest conda-forge version](https://img.shields.io/conda/vn/conda-forge/jupyterhub-systemdspawner?logo=conda-forge)](https://anaconda.org/conda-forge/jupyterhub-systemdspawner)\n[![GitHub Workflow Status - Test](https://img.shields.io/github/actions/workflow/status/jupyterhub/systemdspawner/test.yaml?logo=github\u0026label=tests)](https://github.com/jupyterhub/systemdspawner/actions)\n[![Test coverage of code](https://codecov.io/gh/jupyterhub/systemdspawner/branch/main/graph/badge.svg)](https://codecov.io/gh/jupyterhub/systemdspawner)\n[![GitHub](https://img.shields.io/badge/issue_tracking-github-blue?logo=github)](https://github.com/jupyterhub/systemdspawner/issues)\n[![Discourse](https://img.shields.io/badge/help_forum-discourse-blue?logo=discourse)](https://discourse.jupyter.org/c/jupyterhub)\n\nThe **systemdspawner** enables JupyterHub to spawn single-user\nnotebook servers using [systemd](https://www.freedesktop.org/wiki/Software/systemd/).\n\n## Features\n\nIf you want to use Linux Containers (Docker, rkt, etc) for isolation and\nsecurity benefits, but don't want the headache and complexity of\ncontainer image management, then you should use the SystemdSpawner.\n\nWith the **systemdspawner**, you get to use the familiar, traditional system\nadministration tools, whether you love or meh them, without having to learn an\nextra layer of container related tooling.\n\nThe following features are currently available:\n\n1. Limit maximum memory permitted to each user.\n\n   If they request more memory than this, it will not be granted (`malloc`\n   will fail, which will manifest in different ways depending on the\n   programming language you are using).\n\n2. Limit maximum CPU available to each user.\n\n3. Provide fair scheduling to users independent of the number of processes they\n   are running.\n\n   For example, if User A is running 100 CPU hogging processes, it will usually\n   mean User B's 2 CPU hogging processes will never get enough CPU time as scheduling\n   is traditionally per-process. With Systemd Spawner, both these users' processes\n   will as a whole get the same amount of CPU time, regardless of number of processes\n   being run. Good news if you are User B.\n\n4. Accurate accounting of memory and CPU usage (via cgroups, which systemd uses internally).\n\n   You can check this out with `systemd-cgtop`.\n\n5. `/tmp` isolation.\n\n   Each user gets their own `/tmp`, to prevent accidental information\n   leakage.\n\n6. Spawn notebook servers as specific local users on the system.\n\n   This can replace the need for using SudoSpawner.\n\n7. Restrict users from being able to sudo to root (or as other users) from within the\n   notebook.\n\n   This is an additional security measure to make sure that a compromise of\n   a jupyterhub notebook instance doesn't allow root access.\n\n8. Restrict what paths users can write to.\n\n   This allows making `/` read only and only granting write privileges to\n   specific paths, for additional security.\n\n9. Automatically collect logs from each individual user notebook into\n   `journald`, which also handles log rotation.\n\n10. Dynamically allocate users with Systemd's [dynamic users](http://0pointer.net/blog/dynamic-users-with-systemd.html)\n    facility. Very useful in conjunction with [tmpauthenticator](https://github.com/jupyterhub/tmpauthenticator).\n\n## Requirements\n\n### Systemd and Linux distributions\n\nSystemdSpawner 1 is recommended to be used with systemd version 245 or higher,\nbut _may_ work with systemd version 243-244 as well. Below are examples of Linux\ndistributions that use systemd and has a recommended version.\n\n- Ubuntu 20.04+\n- Debian 11+\n- Rocky 9+ / CentOS 9+\n\nThe command `systemctl --version` can be used to verify that systemd is used,\nand what version is used.\n\n### Kernel Configuration\n\nCertain kernel options need to be enabled for the CPU / Memory limiting features\nto work. If these are not enabled, CPU / Memory limiting will just fail\nsilently. You can check if your kernel supports these features by running\nthe [`check-kernel.bash`](check-kernel.bash) script.\n\n### Root access\n\nCurrently, JupyterHub must be run as root to use Systemd Spawner. `systemd-run`\nneeds to be run as root to be able to set memory \u0026 cpu limits. Simple sudo rules\ndo not help, since unrestricted access to `systemd-run` is equivalent to root. We\nwill explore hardening approaches soon.\n\n### Local Users\n\nIf running with `c.SystemdSpawner.dynamic_users = False` (the default), each user's\nserver is spawned to run as a local unix user account. Hence this spawner\nrequires that all users who authenticate have a local account already present on the\nmachine.\n\nIf running with `c.SystemdSpawner.dynamic_users = True`, no local user accounts\nare required. Systemd will automatically create dynamic users as required.\nSee [this blog post](http://0pointer.net/blog/dynamic-users-with-systemd.html) for\ndetails.\n\n## Installation\n\nYou can install it from PyPI with:\n\n```bash\npip install jupyterhub-systemdspawner\n```\n\nYou can enable it for your jupyterhub with the following lines in your\n`jupyterhub_config.py` file\n\n```python\nc.JupyterHub.spawner_class = \"systemd\"\n```\n\nNote that to confirm systemdspawner has been installed in the correct jupyterhub\nenvironment, a newly generated config file should list `systemdspawner` as one of the\navailable spawner classes in the comments above the configuration line.\n\n## Configuration\n\nLots of configuration options for you to choose! You should put all of these\nin your `jupyterhub_config.py` file:\n\n- **[`mem_limit`](#mem_limit)**\n- **[`cpu_limit`](#cpu_limit)**\n- **[`user_workingdir`](#user_workingdir)**\n- **[`username_template`](#username_template)**\n- **[`default_shell`](#default_shell)**\n- **[`extra_paths`](#extra_paths)**\n- **[`unit_name_template`](#unit_name_template)**\n- **[`unit_extra_properties`](#unit_extra_properties)**\n- **[`isolate_tmp`](#isolate_tmp)**\n- **[`isolate_devices`](#isolate_devices)**\n- **[`disable_user_sudo`](#disable_user_sudo)**\n- **[`readonly_paths`](#readonly_paths)**\n- **[`readwrite_paths`](#readwrite_paths)**\n- **[`dynamic_users`](#dynamic_users)**\n\n### `mem_limit`\n\nSpecifies the maximum memory that can be used by each individual user. It can be\nspecified as an absolute byte value. You can use the suffixes `K`, `M`, `G` or `T` to\nmean Kilobyte, Megabyte, Gigabyte or Terabyte respectively. Setting it to `None` disables\nmemory limits.\n\nEven if you want individual users to use as much memory as possible, it is still good\npractice to set a memory limit of 80-90% of total physical memory. This prevents one\nuser from being able to single handedly take down the machine accidentally by OOMing it.\n\n```python\nc.SystemdSpawner.mem_limit = '4G'\n```\n\nDefaults to `None`, which provides no memory limits.\n\nThis info is exposed to the single-user server as the environment variable\n`MEM_LIMIT` as integer bytes.\n\n### `cpu_limit`\n\nA float representing the total CPU-cores each user can use. `1` represents one\nfull CPU, `4` represents 4 full CPUs, `0.5` represents half of one CPU, etc.\nThis value is ultimately converted to a percentage and rounded down to the\nnearest integer percentage, i.e. `1.5` is converted to 150%, `0.125` is\nconverted to 12%, etc.\n\n```python\nc.SystemdSpawner.cpu_limit = 4.0\n```\n\nDefaults to `None`, which provides no CPU limits.\n\nThis info is exposed to the single-user server as the environment variable\n`CPU_LIMIT` as a float.\n\nNote: there is [a bug](https://github.com/systemd/systemd/issues/3851) in\nsystemd v231 which prevents the CPU limit from being set to a value greater\nthan 100%.\n\n#### CPU fairness\n\nCompletely unrelated to `cpu_limit` is the concept of CPU fairness - that each\nuser should have equal access to all the CPUs in the absense of limits. This\ndoes not entirely work in the normal case for Jupyter Notebooks, since CPU\nscheduling happens on a per-process level, rather than per-user. This means\na user running 100 processes has 100x more access to the CPU than a user running\none. This is far from an ideal situation.\n\nSince each user's notebook server runs in its own Systemd Service, this problem\nis mitigated - all the processes spawned from a user's notebook server are run\nin one cgroup, and cgroups are treated equally for CPU scheduling. So independent\nof how many processes each user is running, they all get equal access to the CPU.\nThis works out perfect for most cases, since this allows users to burst up and\nuse all CPU when nobody else is using CPU \u0026 forces them to automatically yield\nwhen other users want to use the CPU.\n\n### `user_workingdir`\n\nThe directory to spawn each user's notebook server in. This directory is what users\nsee when they open their notebooks servers. Usually this is the user's home directory.\n\n`{USERNAME}` and `{USERID}` in this configuration value will be expanded to the\nappropriate values for the user being spawned.\n\n```python\nc.SystemdSpawner.user_workingdir = '/home/{USERNAME}'\n```\n\nDefaults to the home directory of the user. Not respected if `dynamic_users` is true.\n\n### `username_template`\n\nTemplate for unix username each user should be spawned as.\n\n`{USERNAME}` and `{USERID}` in this configuration value will be expanded to the\nappropriate values for the user being spawned.\n\nThis user should already exist in the system.\n\n```python\nc.SystemdSpawner.username_template = 'jupyter-{USERNAME}'\n```\n\nNot respected if `dynamic_users` is set to True\n\n### `default_shell`\n\nThe default shell to use for the terminal in the notebook. Sets the `SHELL` environment\nvariable to this.\n\n```python\nc.SystemdSpawner.default_shell = '/bin/bash'\n```\n\nDefaults to whatever the value of the `SHELL` environment variable is in the JupyterHub\nprocess, or `/bin/bash` if `SHELL` isn't set.\n\n### `extra_paths`\n\nList of paths that should be prepended to the `PATH` environment variable for the spawned\nnotebook server. This is easier than setting the `env` property, since you want to\nadd to PATH, not completely replace it. Very useful when you want to add a virtualenv\nor conda install onto the user's `PATH` by default.\n\n```python\nc.SystemdSpawner.extra_paths = ['/home/{USERNAME}/conda/bin']\n```\n\n`{USERNAME}` and `{USERID}` in this configuration value will be expanded to the\nappropriate values for the user being spawned.\n\nDefaults to `[]` which doesn't add any extra paths to `PATH`\n\n### `unit_name_template`\n\nTemplate to form the Systemd Service unit name for each user notebook server. This\nallows differentiating between multiple jupyterhubs with Systemd Spawner on the same\nmachine. Should contain only [a-zA-Z0-9_-].\n\n```python\nc.SystemdSpawner.unit_name_template = 'jupyter-{USERNAME}-singleuser'\n```\n\n`{USERNAME}` and `{USERID}` in this configuration value will be expanded to the\nappropriate values for the user being spawned.\n\nDefaults to `jupyter-{USERNAME}-singleuser`\n\n### `unit_extra_properties`\n\nDict of key-value pairs used to add arbitrary properties to the spawned Jupyerhub units.\n\n```python\nc.SystemdSpawner.unit_extra_properties = {'LimitNOFILE': '16384'}\n```\n\nRead `man systemd-run` for details on per-unit properties available in transient units.\n\n`{USERNAME}` and `{USERID}` in each parameter value will be expanded to the\nappropriate values for the user being spawned.\n\nDefaults to `{}` which doesn't add any extra properties to the transient scope.\n\n### `isolate_tmp`\n\nSetting this to true provides a separate, private `/tmp` for each user. This is very\nuseful to protect against accidental leakage of otherwise private information - it is\npossible that libraries / tools you are using create /tmp files without you knowing and\nthis is leaking info.\n\n```python\nc.SystemdSpawner.isolate_tmp = True\n```\n\nDefaults to false.\n\n### `isolate_devices`\n\nSetting this to true provides a separate, private `/dev` for each user. This prevents the\nuser from directly accessing hardware devices, which could be a potential source of\nsecurity issues. `/dev/null`, `/dev/zero`, `/dev/random` and the ttyp pseudo-devices will\nbe mounted already, so most users should see no change when this is enabled.\n\n```python\nc.SystemdSpawner.isolate_devices = True\n```\n\nDefaults to false.\n\n### `disable_user_sudo`\n\nSet to true, this prevents users from being able to use `sudo` (or any other means) to\nbecome other users (including root). This helps contain damage from a compromise of a user's\ncredentials if they also have sudo rights on the machine - a web based exploit will now only\nbe able to damage the user's own stuff, rather than have complete root access.\n\n```python\nc.SystemdSpawner.disable_user_sudo = True\n```\n\nDefaults to True.\n\n### `readonly_paths`\n\nList of filesystem paths that should be mounted readonly for the users' notebook server. This\nwill override any filesystem permissions that might exist. Subpaths of paths that are mounted\nreadonly can be marked readwrite with `readwrite_paths`. This is useful for marking `/` as\nreadonly \u0026 only whitelisting the paths where notebook users can write. If paths listed here\ndo not exist, you will get an error.\n\n```python\nc.SystemdSpawner.readonly_paths = ['/']\n```\n\n`{USERNAME}` and `{USERID}` in this configuration value will be expanded to the\nappropriate values for the user being spawned.\n\nDefaults to `None` which disables this feature.\n\n### `readwrite_paths`\n\nList of filesystem paths that should be mounted readwrite for the users' notebook server. This\nonly makes sense if `readonly_paths` is used to make some paths readonly - this can then be\nused to make specific paths readwrite. This does _not_ override filesystem permissions - the\nuser needs to have appropriate rights to write to these paths.\n\n```python\nc.SystemdSpawner.readwrite_paths = ['/home/{USERNAME}']\n```\n\n`{USERNAME}` and `{USERID}` in this configuration value will be expanded to the\nappropriate values for the user being spawned.\n\nDefaults to `None` which disables this feature.\n\n### `dynamic_users`\n\nAllocate system users dynamically for each user.\n\nUses the DynamicUser= feature of Systemd to make a new system user\nfor each hub user dynamically. Their home directories are set up\nunder /var/lib/{USERNAME}, and persist over time. The system user\nis deallocated whenever the user's server is not running.\n\nSee http://0pointer.net/blog/dynamic-users-with-systemd.html for more\ninformation.\n\n### `slice`\n\nRun the spawned notebook in a given systemd slice. This allows aggregate configuration that\nwill apply to all the units that are launched. This can be used (for example) to control\nthe total amount of memory that all of the notebook users can use.\n\nSee https://samthursfield.wordpress.com/2015/05/07/running-firefox-in-a-cgroup-using-systemd/ for\nan example of how this could look.\n\nFor detailed configuration see the [manpage](http://man7.org/linux/man-pages/man5/systemd.slice.5.html)\n\n## Getting help\n\nWe encourage you to ask questions in the [Jupyter Discourse forum](https://discourse.jupyter.org/c/jupyterhub).\n\n## License\n\nWe use a shared copyright model that enables all contributors to maintain the\ncopyright on their contributions.\n\nAll code is licensed under the terms of the revised BSD license.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjupyterhub%2Fsystemdspawner","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjupyterhub%2Fsystemdspawner","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjupyterhub%2Fsystemdspawner/lists"}