Tutorial
Installation
It's recommended to install Mahoraga with uv >=0.9.0:
uv tool install -U mahoraga
Server Configuration
Before starting Mahoraga, you need to initialize a directory (for example
~/.mahoraga) to hold its configuration and data:
uvx mahoraga new ~/.mahoraga
uvx pyvim ~/.mahoraga/mahoraga.toml
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
~/.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 a non-Python server in front of Mahoraga. Packages cached on disk can be served within that server, so that subsequent requests won't go to Mahoraga again. We provide configuration files for Nginx and Caddy out-of-the-box.
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 thehttpblock 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, run nginx -c ~/.mahoraga/nginx/nginx.conf.
You can find the Caddy configuration file at ~/.mahoraga/Caddyfile. As the
name suggests, it can be used as the top-level configuration file directly.
In addition, it's also a valid snippet when imported to the root of another
Caddyfile, since it doesn't contain a global options block.
To start the Caddy server, run cd ~/.mahoraga && caddy run.
When configuring the clients, make sure they don't communicate with Mahoraga directly, but through Nginx or Caddy.
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
To get started on your client machine, install uv >=0.9.10 if you haven't got it elsewhere:
curl -LsSf http://127.0.0.1:3450/uv/uv-installer.sh |
env UV_DOWNLOAD_URL="http://127.0.0.1:3450/uv" sh
$Env:UV_DOWNLOAD_URL = "http://127.0.0.1:3450/uv"
irm http://127.0.0.1:3450/uv/uv-installer.ps1 | iex
When upgrading uv installed in this way, run the same command again. We don't
support uv self update at this moment.
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'
function uv {
uvx 'uv@0.9.10' @args
}
Pixi
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
PIXI_HOME
, replacing any existed version, and prepend
$PIXI_HOME/bin to your PATH. 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
Note
Mirror configuration requires Pixi version 0.43.1 or later.
The script respects environment variables PIXI_HOME, PIXI_NO_PATH_UPDATE
and PIXI_CACHE_DIR
if present.
For convenience, the script modifies Pixi global configuration automatically,
using Mahoraga as the sole mirror of anaconda.org, PyPI and
conda-mapping.prefix.dev. You can also configure the mirrors as you wish:
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/compressed-v0/"],
"https://conda-mapping.prefix.dev/": ["http://127.0.0.1:3450/parselmouth/"]
}'
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" \
...
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:
[cors]
allow-origins = [
"*",
]
mahoraga.conf or Caddyfile like this:
# if ($http_origin) {
# add_header Access-Control-Allow-Origin *;
# }
# header @mahoraga-has-origin 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.29.4/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.29.4/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.29.4/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/i/itsdangerous/itsdangerous-2.2.0-py3-none-any.whl",
"http://127.0.0.1:3450/pypi/packages/py3/p/python-multipart/python_multipart-0.0.32-py3-none-any.whl",
"http://127.0.0.1:3450/pypi/packages/py3/t/tenacity/tenacity-9.1.4-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.3.msix
curl -O http://127.0.0.1:3450/python/pymanager/python-manager-26.3.msi
Note
Support for the command pymanager install hasn't been implemented in
Mahoraga yet.