Skip to content

Tutorial

Installation

It's recommended to install Mahoraga with uv:

uv tool install -U mahoraga

Note

Installing Mahoraga requires uv version 0.9.0 or later.

Server Configuration

Before starting Mahoraga, you need to initialize a directory (for example ~/.mahoraga) to hold its configuration and data:

uvx mahoraga new ~/.mahoraga
The default configuration may not be suitable for you. View and edit it with any text editor you like:
uvx pyvim ~/.mahoraga/mahoraga.toml
Inline documentations can be found inside the file.

Note

Running uvx without uv tool install will result in a full installation every time you run the tool. See uv tool documentation for details.

Server Deployment

Start the server directly to check if your configuration is correct:

cd ~/.mahoraga
uvx mahoraga run
Logs will appear in the console as well as ~/.mahoraga/log/mahoraga.log. If the server started successfully, you can configure your clients and try fetching some packages from Mahoraga. Then if everything goes well, press Ctrl+C to stop the server, and start it again in the background:
uvx mahoraga run &>/dev/null &
uvw tool run mahoraga run

For better performance, you can set up another server (for example Nginx) in front of Mahoraga. Packages cached on disk can be served within that server, so that subsequent requests won't go to Mahoraga again. You can find the following Nginx configuration files in ~/.mahoraga/nginx:

  • nginx.conf: Top-level configuration file for direct use.
  • mahoraga.conf: A snippet intended to be included in the http block of another file.

Note

For Linux and macOS, Nginx is available in conda-forge and can be installed by Pixi:

pixi global install nginx

To start the Nginx server, simply run nginx -c ~/.mahoraga/nginx/nginx.conf. When configuring the clients, make sure they don't communicate with Mahoraga directly, but through Nginx.

Client Configuration

Note

Mahoraga serves on http://127.0.0.1:3450 by default. Replace it with the actual URL exposed to your clients.

uv

Note

Mirror configuration requires uv version 0.9.10 or later.

To install or update uv on a client machine, download and run the standalone installer:

curl -LsSf http://127.0.0.1:3450/uv/uv-installer.sh |
    env UV_DOWNLOAD_URL="http://127.0.0.1:3450/uv" sh
PowerShell
$Env:UV_DOWNLOAD_URL = "http://127.0.0.1:3450/uv"
irm http://127.0.0.1:3450/uv/uv-installer.ps1 | iex

Note

uv self update is unsupported due to this issue.

uv can be configured to grab PyPI packages and Python itself from Mahoraga, via either environment variables or a config file:

export UV_PYTHON_DOWNLOADS_JSON_URL=http://127.0.0.1:3450/uv/python-downloads.json
export UV_PYTHON_INSTALL_MIRROR=http://127.0.0.1:3450/python-build-standalone
export UV_DEFAULT_INDEX=http://127.0.0.1:3450/pypi/simple
export UV_HTTP_TIMEOUT=60

Note

Cache-Control override can only be set via config file.

$Env:UV_PYTHON_DOWNLOADS_JSON_URL = "http://127.0.0.1:3450/uv/python-downloads.json"
$Env:UV_PYTHON_INSTALL_MIRROR = "http://127.0.0.1:3450/python-build-standalone"
$Env:UV_DEFAULT_INDEX = "http://127.0.0.1:3450/pypi/simple"
$Env:UV_HTTP_TIMEOUT = "60"

Note

Cache-Control override can only be set via config file.

python-downloads-json-url = "http://127.0.0.1:3450/uv/python-downloads.json"
python-install-mirror = "http://127.0.0.1:3450/python-build-standalone"

[[index]]
url = "http://127.0.0.1:3450/pypi/simple"
default = true

# Mahoraga inherits upstream response headers.
# Override them in case an upstream mirror doesn't implement cache control.
cache-control = { api = "max-age=600", files = "max-age=365000000, immutable" }

Note

Timeout can only be set via environment variable.

[tool.uv]
python-downloads-json-url = "http://127.0.0.1:3450/uv/python-downloads.json"
python-install-mirror = "http://127.0.0.1:3450/python-build-standalone"

[[tool.uv.index]]
url = "http://127.0.0.1:3450/pypi/simple"
default = true

# Mahoraga inherits upstream response headers.
# Override them in case an upstream mirror doesn't implement cache control.
cache-control = { api = "max-age=600", files = "max-age=365000000, immutable" }

Note

Timeout can only be set via environment variable.

Upgrading or downgrading uv to a specific version is not directly supported, however a small shell trick can work:

alias uv='uvx uv@0.9.10'
PowerShell
function uv {
    uvx 'uv@0.9.10' @args
}

Pixi

Note

Mirror configuration requires Pixi version 0.43.1 or later.

It's recommended to enable sharded repodata in Mahoraga configuration if you use Pixi or any other tools for the Conda ecosystem.
There is no mirror for the standalone installer of Pixi as of now. Instead, we provide a Python script which can be executed by uv:

uv run http://127.0.0.1:3450/static/get_pixi.py http://127.0.0.1:3450
By default, the script installs the latest version of Pixi to PIXI_HOME, replacing any existed version. To specify a version, pass it via CLI arguments:
-v '0.43.1'  # Exact version
-v '0.43.*'  # Latest revision of a specific minor version
-v '>=0.43.1,<1'  # Version range
The script respects environment variables PIXI_HOME and PIXI_CACHE_DIR if present.

Once Pixi is installed, run the following command to configure it:

pixi config set -g mirrors '{
    "https://conda.anaconda.org/": ["http://127.0.0.1:3450/conda/"],
    "https://pypi.org/simple/": ["http://127.0.0.1:3450/pypi/simple/"]
}'
pixi config set -g mirrors '{
    "https://conda.anaconda.org/": ["http://127.0.0.1:3450/conda/"],
    "https://pypi.org/simple/": ["http://127.0.0.1:3450/pypi/simple/"],
    "https://raw.githubusercontent.com/prefix-dev/parselmouth/main/files/": ["http://127.0.0.1:3450/parselmouth/"],
    "https://conda-mapping.prefix.dev/": ["http://127.0.0.1:3450/parselmouth/"]
}'

After that, you can install Conda packages like rattler-build with Pixi.

rattler-build

Note

Mirror configuration requires rattler-build version 0.41.0 or later.

rattler-build accepts the same config file format as Pixi. Pass the Pixi config file modified above to rattler-build, unless there are options you would not like to share between Pixi and rattler-build.

rattler-build build \
    --config-file "${PIXI_HOME:-"$HOME/.pixi"}/config.toml" \
    ...
PowerShell 7
rattler-build build `
    --config-file "$($Env:PIXI_HOME ?? "$HOME/.pixi")/config.toml" `
    ...

Pyodide

Note

Mirror configuration requires Pyodide version 0.28.0 or later.

Pyodide Python distribution, wheels and JavaScript/WebAssembly runtime are all available in Mahoraga. To enable them, add the following line to your mahoraga.toml:

mahoraga.toml
[cors]
allow-origins = [
    "*",
]
Additionally, if you are using Nginx, uncomment all blocks in your mahoraga.conf like this:
mahoraga.conf
# if ($http_origin) {
#     add_header Access-Control-Allow-Origin *;
# }
The frontend configuration depends on the library you directly use:
<!doctype html>
<html>
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <script type="text/javascript" src="http://127.0.0.1:3450/pyodide/v0.28.3/full/pyodide.js"></script>
  </head>
  <body>
    <script type="text/javascript">
      async function main() {
        let pyodide = await loadPyodide();
        await pyodide.loadPackage("micropip");
        // Prefer Pyodide-maintained wheels over PyPI ones
        // If this is not desired, remove the following lines
        pyodide.runPython(`
            import micropip
            class _Transaction(micropip.transaction.Transaction):
                def __post_init__(self):
                    super().__post_init__()
                    self.search_pyodide_lock_first = True
            micropip.package_manager.Transaction = _Transaction
        `);
        const micropip = pyodide.pyimport("micropip");
        micropip.set_index_urls("http://127.0.0.1:3450/pypi/simple/{package_name}/?micropip=1");
        await micropip.install(["your_package"]);
        pyodide.runPython(`# Your Python code here`);
      }
      main();
    </script>
  </body>
</html>
<!doctype html>
<html>
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <script type="module" src="http://127.0.0.1:3450/npm/@pyscript/core@0/dist/core.js"></script>
    <link rel="stylesheet" href="http://127.0.0.1:3450/npm/@pyscript/core@0/dist/core.css">
  </head>
  <body>
    <script type="py" config='{
      "index_urls": ["http://127.0.0.1:3450/pypi/simple/{package_name}/?micropip=1"],
      "interpreter": "http://127.0.0.1:3450/pyodide/v0.28.3/full/pyodide.mjs",
      "packages": ["your_package"]
    }'># Your Python code here</script>
  </body>
</html>
<!doctype html>
<html>
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
    <link rel="stylesheet" href="http://127.0.0.1:3450/npm/@stlite/browser@1/build/stlite.css">
  </head>
  <body>
    <div id="root"></div>
    <script type="module">
      import { mount } from "http://127.0.0.1:3450/npm/@stlite/browser@1/build/stlite.js";
      mount(
        {
          pyodideUrl: "http://127.0.0.1:3450/pyodide/v0.28.3/full/pyodide.js",
          requirements: [
            "http://127.0.0.1:3450/pypi/packages/py3/b/blinker/blinker-1.9.0-py3-none-any.whl",
            "http://127.0.0.1:3450/pypi/packages/py3/t/tenacity/tenacity-9.1.2-py3-none-any.whl",
          ],
          entrypoint: "your_app.py",
          files: {
            "your_app.py": `# Your Python code here`,
          },
        },
        document.getElementById("root"),
      );
    </script>
  </body>
</html>

pymanager

The next generation of the official Python installer for Windows, pymanager, can be downloaded from Mahoraga:

curl -O http://127.0.0.1:3450/python/pymanager/python-manager-26.0b1.msix
curl -O http://127.0.0.1:3450/python/pymanager/python-manager-26.0b1.msi

Note

Support for the command pymanager install hasn't been implemented in Mahoraga yet.