NAME

CPAN::InGit::Server - A Mojolicious::Controller that serves the ArchiveTrees from a git repo

DESCRIPTION

This controller serves paths that can be used directly as a CPAN Mirror by tools like 'cpanm'. By default, it serves all branches, but you can also configure it to serve only a single branch. It serves content directly from the Git repo and does not need a working directory checked out.

ATTRIBUTES

cpan_repo

Reference to the CPAN::InGit object from which files will be served

archive_tree

Reference to an ArchvieTree object. If set, the controller will serve only the tree of this branch from whatever path it was rooted at. If not set, this attribute will be lazy-built from attribute tree_name and cached in tree_cache. The lazy-building of this attribute may return undef, and should be checked before dispatching to an action.

tree_name

Explicit selector containing two path components: ~branch/feature~demo, ~tag/v1.0, or ~commit/OBJECT_ID. Slashes in ref names are encoded as ~.

tree_cache

A hashref storing a cache of ArchiveTree objects. This needs to be supplied in the stash if you are loading branches by name at runtime, or else it will be creating an ArchiveTree object on every request.

branch_head_only

If true, this prevents serving files from a Git::Raw tree which is not the HEAD of a branch. By default, any Git sha-1, tag, or branch name may be used so long as it references a tree that meets the requirements for an ArchiveTree. Restricting these to branches-only can help ensure that a malicious user can't pollute the cache and create excessive server load by touching every tree that ever existed.

index_branches

If true, list the available local CPAN archive branches on the repository index. Disabled by default; only applies when serving multiple trees.

module_index_max_rows

Set this to the maximum number of rows to return from the JSON endpoint that searches modules, used by the per-tree index page.

METHODS

serve_repo_index

Serve the repository landing page. Local branches containing CPAN configuration and a package index are listed only when index_branches is enabled in the stash.

serve_index

Serve an HTML index for a CPAN tree, including its commit date, package index date, and the first 5000 matching modules.

serve_module_list

Serve modules/ as JSON. filter is a case-insensitive literal substring matched against the entire original 02packages row (module, version, and distribution path). after and before are exclusive module-name boundaries; supply at most one. Rows are sorted by module name, with at most 500 returned per request.

The response contains modules (objects with name, version, path, and text), the applied filter, the Git tree ID, and next_after and previous_before cursors (null at either end). complete is true only when this response includes every row matching the filter, including rows outside the requested boundary. A client may then narrow that filter locally.

check_tree_exists

This checks that the "tree_name" could be successfully resolved to a "archive_tree". It returns true, or sets a 404 error and returns undef, and can be used as ->under(\&check_tree_exists).

serve_package_details

Serve modules/02packages.details.txt[.gz].

serve_author_file

Serve all files under authors/id/.... (All files in that path of the git branch are considered public) This performs automatic creation of .tar.gz files if a directory and matching .meta file exist, allowing distributions to be unpacked within the git repo, but served as .tar.gz files.

render_gzipped

Serve content after gzipping it and setting the content type to application/gzip.

ROUTES

Cpan Mirror Paths

A functioning CPAN mirror requires a file /modules/02packages.details.txt.gz which lists the full contents of the latest version of every module. That file also lists the author paths for each module, which are relative to /authors/id/

In CPAN::InGit, /modules/02packages.details.txt is stored uncompressed, and gzipped as it is served. Each author dist is stored as a .tar.gz file and a metadata file with extension .meta and may also optionally be unpacked at a directory of the same name minus the .tar.gz extension. A special author "LOCAL" is used for custom overrides of upstream packages.

Summary:

/                                                 # browsable tree index
/modules/?filter=TEXT&after=MODULE                # filtered module list
/modules/02packages.details.txt                   # index of the mirror
/authors/id/D/DP/DPARIS/Crypt-DES-2.07.tar.gz     # copy of upstream
/authors/id/D/DP/DPARIS/Crypt-DES-2.07.meta       # extracted metadata
/authors/id/L/LO/LOCAL/Crypt-DES-2.07_01/...      # untarred customized dist
/authors/id/L/LO/LOCAL/Crypt-DES-2.07_01.meta     # untarred customized dist's metadata

mount

my $route= app->routes->any('/pan');
my $repo= CPAN::InGit->new(git_repo => '/path/to/.git');
CPAN::InGit::Server->mount($route, $repo, app => app, %options);
# %options:
#   archive_tree => $ArchiveTree_obj,
#   tree_cache => $branch_cache,
#   index_branches => 1, # optionally list local CPAN archive branches
#   branch_head_only => 1, # only serve actual branch HEADs

This class method sets up the routes for serving the mirror under a provided route. $route is the route under which you want to serve the 'PAN. The app option supplies the owning Mojolicious application and is required for multi-tree mounts, to install the before_routes path-rewriting hook.

If you specify 'archive_tree', that `PAN will be directly mounted at the route. Otherwise, all trees (or just branches if you use option branch_head_only) will be mounted as:

/                                                 # repository index
/~static/...                                      # static files served publicly
/~branch/:branch_name/*tree                       # branch, with '/' replaced by '~'
/~tag/:tag_name/*tree                             # tag, with '/' replaced by '~'
/~commit/:sha1/*tree                              # SHA-1 Hex commit ID
/*name/*tree                                      # branch, else tag, else commit

The route's root serves a repository index; index_branches enables branch links on that page and defaults to false.

Files in share/public are served verbatim under ~static/ within the mount, using Mojo's static file handler.

The ~branch, ~tag, and ~commit prefixes are followed by a single path component that unambiguously represents a tree; branch and tag names containing '/' have that character replaced by '~'. In the last form, path components are consumed from the URL in sequence until it matches a branch or tag or SHA1, which is convenient but can be ambiguous. For example, if a tag named issue exists and a branch named issue/123 exists, a url for issue/123/modules resolves to the path 123/modules within tag "issue", and does not find the branch. Most users won't have overlapping branches and tags, though, so these URLs are more convenient.

VERSION

version 0.006

AUTHOR

Michael Conrad <mike@nrdvana.net>

COPYRIGHT AND LICENSE

This software is copyright (c) 2026 by Michael Conrad, and IntelliTree Solutions.

This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.