diff --git a/README.md b/README.md index 6809ef3..08f5cf4 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,26 @@ ![pypi server logo](docs/__resources__/pypiserver_logo.png) -[**pypiserver - minimal PyPI server for use with pip/easy_install**](<>) +# [**pypiserver - minimal PyPI server for use with pip/easy_install**](#pypiserver) [![pypi badge](https://img.shields.io/pypi/v/pypiserver.svg)](https://shields.io/) [![ci workflow](https://github.com/pypiserver/pypiserver/actions/workflows/ci.yml/badge.svg)](https://github.com/pypiserver/pypiserver/actions/workflows/ci.yml) [![Generic badge](https://img.shields.io/badge/python-3.6%7C3.7%7C3.8+-blue.svg)](https://pypi.org/project/pypiserver/) [![Generic badge](https://img.shields.io/badge/license-MIT%7Czlib/libpng-blue.svg)](https://raw.githubusercontent.com/pypiserver/pypiserver/master/LICENSE.txt) -| name | description | -| :---------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Version | 2.1.1 | -| Date: | 2024-04-25 | -| Source | | -| PyPI | | -| Tests | | -| Maintainers | Kostis Anagnostopoulos , Matthew Planchard , Dmitrii Orlov , **Someone new?** We are looking for new maintainers! [#397](https://github.com/pypiserver/pypiserver/issues/397) | -| License | zlib/libpng + MIT | -| Community | | +| name | description | +| :---------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Version | 2.1.1 | +| Date: | 2024-04-25 | +| Source | | +| PyPI | | +| Tests | | +| Maintainers | [`@ankostis`](https://github.com/ankostis), [`@mplanchard`](https://github.com/mplanchard), [`@dee-me-tree-or-love`](https://github.com/dee-me-tree-or-love), [`@pawamoy`](https://github.com/pawamoy), **Someone new?** *We are open for new maintainers! [#397](https://github.com/pypiserver/pypiserver/issues/397)* | +| License | zlib/libpng + MIT | +| Community | | -Chat with us on [Zulip](https://pypiserver.zulipchat.com)! +> \[!TIP\] +> Reach out in [**Discussions**](https://github.com/pypiserver/pypiserver/discussions), +> or chat with us on [**Zulip**](https://pypiserver.zulipchat.com) **pypiserver** is a minimal [PyPI](https://pypi.org/) compatible server for **pip** or **easy_install**. It is based on [bottle](http://bottlepy.org/) and serves packages from regular directories. @@ -26,14 +28,14 @@ Wheels, bdists, eggs and accompanying PGP-signatures can be uploaded either with **pip**, **setuptools**, **twine**, **pypi-uploader**, or simply copied with **scp**. -Note -The official software powering [PyPI](https://pypi.org/) is -[Warehouse](https://github.com/pypa/warehouse/). However, -[Warehouse](https://github.com/pypa/warehouse/) -is fairly specialized to be **pypi.org**'s own software, and should not -be used in other contexts. In particular, it does not officially support -being used as a custom package index by users wishing to serve their own -packages. +> \[!Note\] +> The official software powering [PyPI](https://pypi.org/) is +> [Warehouse](https://github.com/pypa/warehouse/). However, +> [Warehouse](https://github.com/pypa/warehouse/) +> is fairly specialized to be **pypi.org**'s own software, and should not +> be used in other contexts. In particular, it does not officially support +> being used as a custom package index by users wishing to serve their own +> packages. **pypiserver** implements the same interfaces as [PyPI](https://pypi.org/), allowing standard Python packaging tooling such as **pip** and **twine** to @@ -44,6 +46,7 @@ making it much easier to get a running index server. Table of Contents +- [**pypiserver - minimal PyPI server for use with pip/easy_install**](#pypiserver---minimal-pypi-server-for-use-with-pipeasy_install) - [pypiserver](#pypiserver) - [Quickstart Installation and Usage](#quickstart-installation-and-usage) - [More details about pypi server run](#more-details-about-pypi-server-run) @@ -95,33 +98,34 @@ Older Python versions may still work, but they are not tested. For legacy Python versions, use **pypiserver-1.x** series. Note that these are not officially supported, and will not receive bugfixes or new features. -Tip - -The commands below work on a unix-like operating system with a posix shell. -The **'~'** character expands to user's home directory. +> \[!TIP\] +> +> The commands below work on a unix-like operating system with a posix shell. +> The **'~'** character expands to user's home directory. If you're using Windows, you'll have to use their "Windows counterparts". The same is true for the rest of this documentation. 1. Install **pypiserver** with this command -```shell + ```shell pip install pypiserver # Or: pypiserver[passlib,cache] mkdir ~/packages # Copy packages into this directory. -``` + ``` -See also [Alternative Installation methods](<>) + > \[!TIP\] + > See also [Alternative Installation methods](#alternative-installation-methods) -2. Copy some packages into your **~/packages** folder and then +1. Copy some packages into your **~/packages** folder and then get your **pypiserver** up and running -```shell + ```shell pypi-server run -p 8080 ~/packages & # Will listen to all IPs. -``` + ``` -3. From the client computer, type this +1. From the client computer, type this -```shell + ```shell # Download and install hosted packages. pip install --extra-index-url http://localhost:8080/simple/ ... @@ -132,57 +136,60 @@ See also [Alternative Installation methods](<>) pip search --index http://localhost:8080 ... # Note that pip search does not currently work with the /simple/ endpoint. -``` + ``` -See also [Client-side configurations](#client-side-configurations) for avoiding tedious typing. + > \[!TIP\] + > See also [Client-side configurations](#client-side-configurations) for avoiding tedious typing. -4. Enter **pypi-server -h** in the cmd-line to print a detailed usage message +1. Enter **pypi-server -h** in the cmd-line to print a detailed usage message -```text -usage: pypi-server [-h] [-v] [--log-file FILE] [--log-stream STREAM] - [--log-frmt FORMAT] [--hash-algo HASH_ALGO] - [--backend {auto,simple-dir,cached-dir}] [--version] - {run,update} ... + -start PyPI compatible package server serving packages from PACKAGES_DIRECTORY. If PACKAGES_DIRECTORY is not given on the command line, it uses the default ~/packages. pypiserver scans this directory recursively for packages. It skips packages and directories starting with a dot. Multiple package directories may be specified. + ```text + usage: pypi-server [-h] [-v] [--log-file FILE] [--log-stream STREAM] + [--log-frmt FORMAT] [--hash-algo HASH_ALGO] + [--backend {auto,simple-dir,cached-dir}] [--version] + {run,update} ... -positional arguments: - {run,update} - run Run pypiserver, serving packages from - PACKAGES_DIRECTORY - update Handle updates of packages managed by pypiserver. By - default, a pip command to update the packages is - printed to stdout for introspection or pipelining. See - the `-x` option for updating packages directly. + start PyPI compatible package server serving packages from PACKAGES_DIRECTORY. If PACKAGES_DIRECTORY is not given on the command line, it uses the default ~/packages. pypiserver scans this directory recursively for packages. It skips packages and directories starting with a dot. Multiple package directories may be specified. -optional arguments: - -h, --help show this help message and exit - -v, --verbose Enable verbose logging; repeat for more verbosity. - --log-file FILE Write logging info into this FILE, as well as to - stdout or stderr, if configured. - --log-stream STREAM Log messages to the specified STREAM. Valid values are - stdout, stderr, and none - --log-frmt FORMAT The logging format-string. (see `logging.LogRecord` - class from standard python library) - --hash-algo HASH_ALGO - Any `hashlib` available algorithm to use for - generating fragments on package links. Can be disabled - with one of (0, no, off, false). - --backend {auto,simple-dir,cached-dir} - A backend implementation. Keep the default 'auto' to - automatically determine whether to activate caching or - not - --version show program's version number and exit + positional arguments: + {run,update} + run Run pypiserver, serving packages from + PACKAGES_DIRECTORY + update Handle updates of packages managed by pypiserver. By + default, a pip command to update the packages is + printed to stdout for introspection or pipelining. See + the `-x` option for updating packages directly. -Visit https://github.com/pypiserver/pypiserver for more information - + optional arguments: + -h, --help show this help message and exit + -v, --verbose Enable verbose logging; repeat for more verbosity. + --log-file FILE Write logging info into this FILE, as well as to + stdout or stderr, if configured. + --log-stream STREAM Log messages to the specified STREAM. Valid values are + stdout, stderr, and none + --log-frmt FORMAT The logging format-string. (see `logging.LogRecord` + class from standard python library) + --hash-algo HASH_ALGO + Any `hashlib` available algorithm to use for + generating fragments on package links. Can be disabled + with one of (0, no, off, false). + --backend {auto,simple-dir,cached-dir} + A backend implementation. Keep the default 'auto' to + automatically determine whether to activate caching or + not + --version show program's version number and exit -``` + Visit https://github.com/pypiserver/pypiserver for more information + ``` ### More details about pypi server run Enter **pypi-server run -h** in the cmd-line to print a detailed usage + + ```text usage: pypi-server run [-h] [-v] [--log-file FILE] [--log-stream STREAM] [--log-frmt FORMAT] [--hash-algo HASH_ALGO] @@ -335,8 +342,6 @@ optional arguments: might pose a security risk - e.g. a malicious user might publish a higher version of the private package, containing arbitrary code. - - ``` ## Client-Side Configurations @@ -362,12 +367,12 @@ or by adding the following lines to **~/.pip/pip.conf** extra-index-url = http://localhost:8080/simple/ ``` -Note - -If you have installed **pypiserver** on a remote url without *https* -you will receive an "untrusted" warning from *pip*, urging you to append -the **--trusted-host** option. You can also include this option permanently -in your configuration-files or environment variables. +> \[!NOTE\] +> +> If you have installed **pypiserver** on a remote url without *https* +> you will receive an "untrusted" warning from *pip*, urging you to append +> the **--trusted-host** option. You can also include this option permanently +> in your configuration-files or environment variables. ### Configuring easy_install @@ -385,9 +390,9 @@ Instead of copying packages directly to the server's folder (i.e. with **scp**), you may use python tools for the task, e.g. **python setup.py upload**. In that case, **pypiserver** is responsible for authenticating the upload-requests. -Note - -We strongly advise to password-protected your uploads! +> \[!NOTE\] +> +> We strongly advise to ***password-protect*** your uploads! It is possible to disable authentication for uploads (e.g. in intranets). To avoid lazy security decisions, read help for **-P** and **-a** options. @@ -399,74 +404,75 @@ To avoid lazy security decisions, read help for **-P** and **-a** options. *htpasswd* file specified by the **-P**, **--passwords** option (see next steps) -```shell - pip install passlib -``` + ```shell + pip install passlib + ``` -2. Create the Apache **htpasswd** file with at least one user/password pair +1. Create the Apache **htpasswd** file with at least one user/password pair with this command (you'll be prompted for a password) -```shell - htpasswd -sc htpasswd.txt + ```shell + htpasswd -sc htpasswd.txt + ``` -``` +> \[!TIP\] +> +> Read this [SO](http://serverfault.com/questions/152950/how-to-create-and-edit-htaccess-and-htpasswd-locally-on-my-computer-and-then-u) +> question for running `htpasswd` cmd under *Windows* +> or if you have bogus passwords that you don't care because they are for +> an internal service (which is still "bad", from a security perspective...) +> you may use this [public service](http://www.htaccesstools.com/htpasswd-generator/) -Tip + -Read this [SO](http://serverfault.com/questions/152950/how-to-create-and-edit-htaccess-and-htpasswd-locally-on-my-computer-and-then-u) question for running `htpasswd` cmd under *Windows* -or if you have bogus passwords that you don't care because they are for -an internal service (which is still "bad", from a security perspective...) -you may use this [public service](http://www.htaccesstools.com/htpasswd-generator/) +> \[!TIP\] +> +> When accessing pypiserver via the api, alternate authentication +> methods are available via the **auther** config flag. Any callable +> returning a boolean can be passed through to the pypiserver config in +> order to provide custom authentication. For example, to configure +> pypiserver to authenticate using the [python-pam](https://pypi.org/project/python-pam/) +> +> ```shell +> import pam +> pypiserver.default_config(auther=pam.authenticate) +> ``` -Tip +Please see [`Using Ad-hoc authentication providers`](#using-ad-hoc-authentication-providers) for more information. -When accessing pypiserver via the api, alternate authentication -methods are available via the **auther** config flag. Any callable -returning a boolean can be passed through to the pypiserver config in -order to provide custom authentication. For example, to configure -pypiserver to authenticate using the [python-pam](https://pypi.org/project/python-pam/) - -```shell - import pam - pypiserver.default_config(auther=pam.authenticate) - -``` - -Please see `Using Ad-hoc authentication providers`\_ for more information. - -3. You need to restart the server with the **-P** option only once +1. You need to restart the server with the **-P** option only once (but user/password pairs can later be added or updated on the fly) -```shell - ./pypi-server run -p 8080 -P htpasswd.txt ~/packages & -``` + ```shell + ./pypi-server run -p 8080 -P htpasswd.txt ~/packages & + ``` #### Upload with setuptools 1. On client-side, edit or create a **~/.pypirc** file with a similar content: -```shell - [distutils] - index-servers = - pypi - local + ```shell + [distutils] + index-servers = + pypi + local - [pypi] - username: - password: + [pypi] + username: + password: - [local] - repository: http://localhost:8080 - username: - password: -``` + [local] + repository: http://localhost:8080 + username: + password: + ``` 1. Then from within the directory of the python-project you wish to upload, issue this command: -```shell - python setup.py sdist upload -r local -``` + ```shell + python setup.py sdist upload -r local + ``` #### Upload with twine @@ -475,18 +481,18 @@ To avoid storing you passwords on disk, in clear text, you may either: - use the **register** *setuptools*'s command with the **-r** option, like that -```shell - python setup.py sdist register -r local upload -r local -``` + ```shell + python setup.py sdist register -r local upload -r local + ``` - use *twine* library, which breaks the procedure in two steps. In addition, it supports signing your files with PGP-Signatures and uploading the generated *.asc* files to **pypiserver**:: -```shell - twine upload -r local --sign -identity user_name ./foo-1.zip -``` + ```shell + twine upload -r local --sign -identity user_name ./foo-1.zip + ``` ## Using the Docker Image @@ -575,23 +581,23 @@ available packages. It scans the package directory for available packages and searches on pypi.org for updates. Without further options **pypi-server update** will just print a list of commands which must be run in order to get the latest version of each package. Output -looks like +looks like: ```shell - $ ./pypi-server update - checking 106 packages for newer version +$ ./pypi-server update +checking 106 packages for newer version - .........u.e...........e..u............. - .....e..............................e... - .......................... +.........u.e...........e..u............. +.....e..............................e... +.......................... - no releases found on pypi for PyXML, Pymacs, mercurial, setuptools +no releases found on pypi for PyXML, Pymacs, mercurial, setuptools - # update raven from 1.4.3 to 1.4.4 - pip -q install --no-deps --extra-index-url https://pypi.org/simple/ -d /home/ralf/packages/mirror raven==1.4.4 +# update raven from 1.4.3 to 1.4.4 +pip -q install --no-deps --extra-index-url https://pypi.org/simple/ -d /home/ralf/packages/mirror raven==1.4.4 - # update greenlet from 0.3.3 to 0.3.4 - pip -q install --no-deps --extra-index-url https://pypi.org/simple/ -d /home/ralf/packages/mirror greenlet==0.3.4 +# update greenlet from 0.3.3 to 0.3.4 +pip -q install --no-deps --extra-index-url https://pypi.org/simple/ -d /home/ralf/packages/mirror greenlet==0.3.4 ``` It first prints for each package a single character after checking the @@ -608,9 +614,10 @@ releases won't be considered. ### Serving Thousands of Packages -By default, **pypiserver** scans the entire packages directory each time an -incoming HTTP request occurs. This isn't a problem for a small number of -packages, but causes noticeable slow-downs when serving thousands of packages. +> \[!IMPORTANT\] +> By default, **pypiserver** scans the entire packages directory each time an +> incoming HTTP request occurs. This isn't a problem for a small number of +> packages, but causes noticeable slow-downs when serving thousands of packages. If you run into this problem, significant speedups can be gained by enabling pypiserver's directory caching functionality. The only requirement is to @@ -618,7 +625,7 @@ install the **watchdog** package, or it can be installed during **pypiserver** installation, by specifying the **cache** extras option:: ```shell - pip install pypiserver[cache] +pip install pypiserver[cache] ``` Additional speedups can be obtained by using your webserver's builtin @@ -628,26 +635,26 @@ easily enable caching. For example, to allow nginx to cache up to 10 gigabytes of data for up to 1 hour:: ```shell - proxy_cache_path /data/nginx/cache - levels=1:2 - keys_zone=pypiserver_cache:10m - max_size=10g - inactive=60m - use_temp_path=off; +proxy_cache_path /data/nginx/cache + levels=1:2 + keys_zone=pypiserver_cache:10m + max_size=10g + inactive=60m + use_temp_path=off; - server { - # ... - location / { - proxy_cache pypiserver_cache; - proxy_pass http://localhost:8080; - } +server { + # ... + location / { + proxy_cache pypiserver_cache; + proxy_pass http://localhost:8080; } - +} ``` -Using webserver caching is especially helpful if you have high request -volume. Using nginx caching, a real-world pypiserver installation was -able to easily support over 1000 package downloads/min at peak load. +> \[!TIP\] +> Using webserver caching is especially helpful if you have high request +> volume. Using nginx caching, a real-world pypiserver installation was +> able to easily support over 1000 package downloads/min at peak load. ### Managing Automated Startup @@ -663,29 +670,29 @@ it is an excellent option for managing the pypiserver process. An example config file for **systemd** can be seen below ```shell - [Unit] - Description=A minimal PyPI server for use with pip/easy_install. - After=network.target +[Unit] +Description=A minimal PyPI server for use with pip/easy_install. +After=network.target - [Service] - Type=simple - # systemd requires absolute path here too. - PIDFile=/var/run/pypiserver.pid - User=www-data - Group=www-data +[Service] +Type=simple +# systemd requires absolute path here too. +PIDFile=/var/run/pypiserver.pid +User=www-data +Group=www-data - ExecStart=/usr/local/bin/pypi-server run -p 8080 -a update,download --log-file /var/log/pypiserver.log -P /etc/nginx/.htpasswd /var/www/pypi - ExecStop=/bin/kill -TERM $MAINPID - ExecReload=/bin/kill -HUP $MAINPID - Restart=always +ExecStart=/usr/local/bin/pypi-server run -p 8080 -a update,download --log-file /var/log/pypiserver.log -P /etc/nginx/.htpasswd /var/www/pypi +ExecStop=/bin/kill -TERM $MAINPID +ExecReload=/bin/kill -HUP $MAINPID +Restart=always - WorkingDirectory=/var/www/pypi +WorkingDirectory=/var/www/pypi - TimeoutStartSec=3 - RestartSec=5 +TimeoutStartSec=3 +RestartSec=5 - [Install] - WantedBy=multi-user.target +[Install] +WantedBy=multi-user.target ``` Adjusting the paths and adding this file as **pypiserver.service** into your @@ -702,45 +709,48 @@ package and as such, it provides excellent cross-platform support for process management. An example configuration file for **supervisor** is given below ```shell - [program:pypi] - command=/home/pypi/pypi-venv/bin/pypi-server run -p 7001 -P /home/pypi/.htpasswd /home/pypi/packages - directory=/home/pypi - user=pypi - autostart=true - autorestart=true - stderr_logfile=/var/log/pypiserver.err.log - stdout_logfile=/var/log/pypiserver.out.log +[program:pypi] +command=/home/pypi/pypi-venv/bin/pypi-server run -p 7001 -P /home/pypi/.htpasswd /home/pypi/packages +directory=/home/pypi +user=pypi +autostart=true +autorestart=true +stderr_logfile=/var/log/pypiserver.err.log +stdout_logfile=/var/log/pypiserver.out.log ``` From there, the process can be managed via **supervisord** using **supervisorctl**. #### Running As a service with NSSM -For Windows download NSSM from unzip to a desired location such as Program Files. Decide whether you are going -to use win32 or win64, and add that exe to environment PATH. +For Windows download [NSSM](https://nssm.cc/) from unzip to a +desired location such as Program Files. Decide whether you are going +to use `win32` or `win64`, and add that `exe` to environment `PATH`. Create a start_pypiserver.bat ```shell - pypi-server run -p 8080 C:\Path\To\Packages & +pypi-server run -p 8080 C:\Path\To\Packages & ``` -Test the batch file by running it first before creating the service. Make sure you can access -the server remotely, and install packages. If you can, proceed, if not troubleshoot until you can. -This will ensure you know the server works, before adding NSSM into the mix. +> \[!TIP\] +> Test the batch file by running it first before creating the service. +> Make sure you can access the server remotely, and install packages. If you can, +> proceed, if not troubleshoot until you can. This will ensure you know the server +> works, before adding NSSM into the mix. From the command prompt ```shell - nssm install pypiserver +nssm install pypiserver ``` This command will launch a NSSM gui application ```shell - Path: C:\Path\To\start_pypiserver.bat - Startup directory: Auto generates when selecting path - Service name: pypiserver +Path: C:\Path\To\start_pypiserver.bat +Startup directory: Auto generates when selecting path +Service name: pypiserver ``` There are more tabs, but that is the basic setup. If the service needs to be running with a certain @@ -749,20 +759,20 @@ login credentials, make sure you enter those credentials in the logon tab. Start the service ```shell - nssm start pypiserver +nssm start pypiserver ``` -Other useful commands - -```shell - nssm --help - nssm stop - nssm restart - nssm status - -``` - -For detailed information please visit +> \[!TIP\] +> Other useful commands +> +> ```shell +> nssm --help +> nssm stop +> nssm restart +> nssm status +> ``` +> +> For detailed information please visit ### Using a Different WSGI Server @@ -772,14 +782,13 @@ For detailed information please visit - You may view all supported WSGI servers using the following interactive code -```python - >>> from pypiserver import bottle - >>> list(bottle.server_names.keys()) - ['cgi', 'gunicorn', 'cherrypy', 'eventlet', 'tornado', 'geventSocketIO', - 'rocket', 'diesel', 'twisted', 'wsgiref', 'fapws3', 'bjoern', 'gevent', - 'meinheld', 'auto', 'aiohttp', 'flup', 'gae', 'paste', 'waitress'] - -``` + ```python + >>> from pypiserver import bottle + >>> list(bottle.server_names.keys()) + ['cgi', 'gunicorn', 'cherrypy', 'eventlet', 'tornado', 'geventSocketIO', + 'rocket', 'diesel', 'twisted', 'wsgiref', 'fapws3', 'bjoern', 'gevent', + 'meinheld', 'auto', 'aiohttp', 'flup', 'gae', 'paste', 'waitress'] + ``` - If none of the above servers matches your needs, invoke just the **pypiserver:app()** method which returns the internal WSGI-app WITHOUT @@ -794,81 +803,85 @@ For detailed information please visit To use your *Apache2* with **pypiserver**, prefer to utilize **mod_wsgi** as explained in [bottle's documentation](http://bottlepy.org/docs/dev/deployment.html#apache-mod-wsgi%3E). -Note -If you choose instead to go with **mod_proxy**, mind that you may bump into problems -with the prefix-path (see [#155](https://github.com/pypiserver/pypiserver/issues/155%3E)). +> \[!NOTE\] +> If you choose instead to go with **mod_proxy**, mind that you may bump into problems +> with the prefix-path (see [#155](https://github.com/pypiserver/pypiserver/issues/155%3E)). 1. Adapt and place the following *Apache* configuration either into top-level scope, - or inside some **** (contributed by Thomas Waldmann): + or inside some **``** (contributed by Thomas Waldmann): -```shell - WSGIScriptAlias / /yoursite/wsgi/pypiserver-wsgi.py - WSGIDaemonProcess pypisrv user=pypisrv group=pypisrv umask=0007 \ - processes=1 threads=5 maximum-requests=500 \ - display-name=wsgi-pypisrv inactivity-timeout=300 - WSGIProcessGroup pypisrv - WSGIPassAuthorization On # Required for authentication (https://github.com/pypiserver/pypiserver/issues/288) + ```shell + WSGIScriptAlias / /yoursite/wsgi/pypiserver-wsgi.py + WSGIDaemonProcess pypisrv user=pypisrv group=pypisrv umask=0007 \ + processes=1 threads=5 maximum-requests=500 \ + display-name=wsgi-pypisrv inactivity-timeout=300 + WSGIProcessGroup pypisrv + WSGIPassAuthorization On # Required for authentication (https://github.com/pypiserver/pypiserver/issues/288) - - Require all granted - -``` + + Require all granted + + ``` -or if using older **Apache \< 2.4**, substitute the last part with this:: + or if using older **Apache \< 2.4**, substitute the last part with this:: -```shell - - Order deny,allow - Allow from all - -``` + ```shell + + Order deny,allow + Allow from all + + ``` -2. Then create the **/yoursite/cfg/pypiserver.wsgi** file and make sure that +1. Then create the **/yoursite/cfg/pypiserver.wsgi** file and make sure that the **user** and **group** of the **WSGIDaemonProcess** directive (**pypisrv:pypisrv** in the example) have the read permission on it -```python + ```python - import pypiserver + import pypiserver - conf = pypiserver.default_config( - root = "/yoursite/packages", - password_file = "/yoursite/htpasswd", ) - application = pypiserver.app(**conf) + conf = pypiserver.default_config( + root = "/yoursite/packages", + password_file = "/yoursite/htpasswd", ) + application = pypiserver.app(**conf) -``` + ``` -Tip -If you have installed **pypiserver** in a virtualenv, follow **mod_wsgi**'s -[instructions](http://modwsgi.readthedocs.io/en/develop/user-guides/virtual-environments.html) -and prepend the python code above with the following + > \[!TIP\] + > If you have installed **pypiserver** in a virtualenv, follow **mod_wsgi**'s + > [instructions](http://modwsgi.readthedocs.io/en/develop/user-guides/virtual-environments.html) + > and prepend the python code above with the following + > + > ```python + > import site + > + > site.addsitedir('/yoursite/venv/lib/pythonX.X/site-packages') + > ``` -```python - import site + - site.addsitedir('/yoursite/venv/lib/pythonX.X/site-packages') -``` +> \[!NOTE\] +> For security reasons, notice that the **Directory** directive grants access +> to a directory holding the **wsgi** start-up script, alone; nothing else. -Note -For security reasons, notice that the **Directory** directive grants access -to a directory holding the **wsgi** start-up script, alone; nothing else. + -Note -To enable HTTPS support on Apache, configure the directive that contains the -WSGI configuration to use SSL. +> \[!NOTE\] +> To enable HTTPS support on Apache, configure the directive that contains the +> WSGI configuration to use SSL. #### gunicorn The following command uses **gunicorn** to start **pypiserver** ```shell - gunicorn -w4 'pypiserver:app(root="/home/ralf/packages")' +gunicorn -w4 'pypiserver:app(root="/home/ralf/packages")' ``` or when using multiple roots ```shell - gunicorn -w4 'pypiserver:app(root=["/home/ralf/packages", "/home/ralf/experimental"])' +gunicorn -w4 'pypiserver:app(root=["/home/ralf/packages", "/home/ralf/experimental"])' ``` #### paste @@ -881,41 +894,40 @@ The following example **paste.ini** could be used to serve stable and unstable packages on different paths ```shell - [composite:main] - use = egg:Paste#urlmap - /unstable/ = unstable - / = stable + [composite:main] + use = egg:Paste#urlmap + /unstable/ = unstable + / = stable - [app:stable] - use = egg:pypiserver#main - root = ~/stable-packages + [app:stable] + use = egg:pypiserver#main + root = ~/stable-packages - [app:unstable] - use = egg:pypiserver#main - root = ~/stable-packages - ~/unstable-packages - - [server:main] - use = egg:gunicorn#main - host = 0.0.0.0 - port = 9000 - workers = 5 - accesslog = - + [app:unstable] + use = egg:pypiserver#main + root = ~/stable-packages + ~/unstable-packages + [server:main] + use = egg:gunicorn#main + host = 0.0.0.0 + port = 9000 + workers = 5 + accesslog = - ``` -Note -You need to install some more dependencies for this to work, like:: - -```shell - pip install paste pastedeploy gunicorn pypiserver -``` - -The server can then start with - -```shell - gunicorn_paster paste.ini -``` +> \[!NOTE\] +> You need to install some more dependencies for this to work, like:: +> +> ```shell +> pip install paste pastedeploy gunicorn pypiserver +> ``` +> +> The server can then start with +> +> ```shell +> gunicorn_paster paste.ini +> ``` ### Behind a Reverse Proxy @@ -926,21 +938,20 @@ You can run **pypiserver** behind a reverse proxy as well. Extend your nginx configuration ```shell - upstream pypi { - server pypiserver.example.com:12345 fail_timeout=0; - } +upstream pypi { + server pypiserver.example.com:12345 fail_timeout=0; +} - server { - server_name myproxy.example.com; - - location / { - proxy_set_header Host $host:$server_port; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_set_header X-Real-IP $remote_addr; - proxy_pass http://pypi; - } - } +server { + server_name myproxy.example.com; + location / { + proxy_set_header Host $host:$server_port; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Real-IP $remote_addr; + proxy_pass http://pypi; + } +} ``` As of pypiserver 1.3, you may also use the `X-Forwarded-Host` header in your @@ -948,19 +959,19 @@ reverse proxy config to enable changing the base URL. For example if you want to host pypiserver under a particular path on your server ```shell - upstream pypi { - server localhost:8000; - } +upstream pypi { + server localhost:8000; +} - server { - location /pypi/ { - proxy_set_header X-Forwarded-Host $host:$server_port/pypi; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Real-IP $remote_addr; - proxy_pass http://pypi; - } - } +server { + location /pypi/ { + proxy_set_header X-Forwarded-Host $host:$server_port/pypi; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Real-IP $remote_addr; + proxy_pass http://pypi; + } +} ``` #### Supporting HTTPS @@ -970,44 +981,47 @@ HTTPS. For example, to put pypiserver behind HTTPS on port 443, with automatic HTTP redirection, using `nginx` ```shell - upstream pypi { - server localhost:8000; - } +upstream pypi { + server localhost:8000; +} - server { - listen 80 default_server; - server_name _; - return 301 https://$host$request_uri; - } +server { + listen 80 default_server; + server_name _; + return 301 https://$host$request_uri; +} - server { - listen 443 ssl; - server_name pypiserver.example.com; +server { + listen 443 ssl; + server_name pypiserver.example.com; - ssl_certificate /etc/star.example.com.crt; - ssl_certificate_key /etc/star.example.com.key; - ssl_protocols TLSv1 TLSv1.1 TLSv1.2; - ssl_ciphers HIGH:!aNULL:!MD5; - - location / { - proxy_set_header Host $host:$server_port; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_set_header X-Real-IP $remote_addr; - proxy_pass http://pypi; - } - } + ssl_certificate /etc/star.example.com.crt; + ssl_certificate_key /etc/star.example.com.key; + ssl_protocols TLSv1 TLSv1.1 TLSv1.2; + ssl_ciphers HIGH:!aNULL:!MD5; + location / { + proxy_set_header Host $host:$server_port; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Real-IP $remote_addr; + proxy_pass http://pypi; + } +} ``` -Please see [nginx's HTTPS docs for more details](http://nginx.org/en/docs/http/configuring_https_servers.html). - -Getting and keeping your certificates up-to-date can be simplified using, -for example, using [certbot and letsencrypt](https://www.digitalocean.com/community/tutorials/how-to-secure-nginx-with-let-s-encrypt-on-ubuntu-18-04%3E). +> \[!TIP\] +> Please see [nginx's HTTPS docs for more details](http://nginx.org/en/docs/http/configuring_https_servers.html). +> +> Getting and keeping your certificates up-to-date can be simplified using, +> for example, using [certbot and letsencrypt](https://www.digitalocean.com/community/tutorials/how-to-secure-nginx-with-let-s-encrypt-on-ubuntu-18-04%3E). #### Traefik -It is also possible to use [Traefik](https://docs.traefik.io/) to put pypiserver behind HTTPS on port 443, with -automatic HTTP redirection using Docker Compose. Please see the provided [docker-compose.yml](https://github.com/pypiserver/pypiserver/blob/master/docker-compose.yml) example for more information. +It is also possible to use [Traefik](https://docs.traefik.io/) to put pypiserver +behind HTTPS on port 443, with automatic HTTP redirection using Docker Compose. +Please see the provided +[docker-compose.yml](https://github.com/pypiserver/pypiserver/blob/master/docker-compose.yml) +example for more information. ### Utilizing the API @@ -1015,8 +1029,9 @@ In order to enable ad-hoc authentication-providers or to use WSGI-servers not supported by *bottle* out-of-the-box, you needed to launch **pypiserver** via its API. -- The main entry-point for configuring **pypiserver** is the [pypiserver:app()](https://github.com/pypiserver/pypiserver/blob/master/pypiserver/__init__.py#L116) - function. This function returns the internal WSGI-app that you my then +- The main entry-point for configuring **pypiserver** is the + [pypiserver:app()](https://github.com/pypiserver/pypiserver/blob/master/pypiserver/__init__.py#L116) + function. This function returns the internal WSGI-app that you my then send to any WSGI-server you like. - To get all **pypiserver:app()** keywords and their explanations, read the @@ -1039,11 +1054,13 @@ these steps: 1. Ensure **python-pam** module is installed + ```shell pip install python-pam + ``` 1. Create a python-script along these lines -```shell + ```shell $ cat > pypiserver-start.py import pypiserver from pypiserver import bottle @@ -1052,18 +1069,18 @@ these steps: bottle.run(app=app, host='0.0.0.0', port=80, server='auto') [Ctrl+ D] -``` + ``` -3. Invoke the python-script to start-up **pypiserver** +1. Invoke the python-script to start-up **pypiserver** -```shell + ```shell python pypiserver-start.py -``` + ``` -Note -The [python-pam](https://pypi.org/project/python-pam/) module, requires *read* access to **/etc/shadow** file; -you may add the user under which **pypiserver** runs into the *shadow* -group, with a command like this: **sudo usermod -a -G shadow pypy-user**. +> \[!NOTE\] +> The [python-pam](https://pypi.org/project/python-pam/) module, requires *read* access to **/etc/shadow** file; +> you may add the user under which **pypiserver** runs into the *shadow* +> group, with a command like this: **sudo usermod -a -G shadow pypy-user**. ### Use with MicroPython @@ -1074,22 +1091,21 @@ package information. This endpoint is supported by **pypiserver**. It can be tested with the UNIX port of **micropython** ```shell - cd micropython - ports/unix/micropython -m tools.upip install -i http://my-server:8080 -p /tmp/mymodules micropython-foobar - +cd micropython +ports/unix/micropython -m tools.upip install -i http://my-server:8080 -p /tmp/mymodules micropython-foobar ``` Installing packages from the REPL of an embedded device works in this way: ```python - import network - import upip +import network +import upip - sta_if = network.WLAN(network.STA_IF) - sta_if.active(True) - sta_if.connect('', '') - upip.index_urls = ["http://my-server:8080"] - upip.install("micropython-foobar") +sta_if = network.WLAN(network.STA_IF) +sta_if.active(True) +sta_if.connect('', '') +upip.index_urls = ["http://my-server:8080"] +upip.install("micropython-foobar") ``` Further information on micropython-packaging can be found here: @@ -1109,23 +1125,17 @@ Valid examples: **/healthz**, **/health/live-1**, **/api_health**, **/action/hea Run pypiserver with **--health-endpoint** argument: ```shell - pypi-server run --health-endpoint /action/health +pypi-server run --health-endpoint /action/health ``` #### Configure a custom health endpoint by script -````python - import pypiserver - from pypiserver import bottle - app = pypiserver.app(root="./packages", health_endpoint="/action/health") - bottle.run(app=app, host=" - ```python - import pypiserver - from pypiserver import bottle - app = pypiserver.app(root="./packages", health_endpoint="/action/health") - bottle.run(app=app, host="0.0.0.0", port=8080, server="auto") -```` +import pypiserver +from pypiserver import bottle +app = pypiserver.app(root="./packages", health_endpoint="/action/health") +bottle.run(app=app, host="0.0.0.0", port=8080, server="auto") +``` Try **curl ** @@ -1134,21 +1144,22 @@ Try **curl ** To create a copy of the repository, use ```shell - git clone https://github.com/pypiserver/pypiserver.git - cd pypiserver +git clone https://github.com/pypiserver/pypiserver.git +cd pypiserver ``` To receive any later changes, in the above folder use: ```shell - git pull +git pull ``` ## Known Limitations -**pypiserver** does not implement the full API as seen on [PyPI](https://pypi.org/). It -implements just enough to make **easy_install**, **pip install**, and -**search** work. +> \[!IMPORTANT\] +> **pypiserver** does not implement the full API as seen on [PyPI](https://pypi.org/). +> It implements just enough to make **easy_install**, **pip install**, and +> **search** work. The following limitations are known: @@ -1207,6 +1218,6 @@ may want to familiarize with: # Licensing -**pypiserver** contains a copy of [bottle](http://bottlepy.org/) which is available under the -MIT license, and the remaining part is distributed under the zlib/libpng license. -See the **LICENSE.txt** file. +**pypiserver** contains a copy of [bottle](http://bottlepy.org/) which is +available under the MIT license, and the remaining part is distributed under +the zlib/libpng license. See the **LICENSE.txt** file.