Selkies

apps_catalog

The apps panel's catalog when proot-apps reads a local repository.

proot-apps takes PA_REPO_FOLDER as a folder of application tarballs in place of its remote registry: one directory per application, named for the image with the separators replaced (ghcr.io_linuxserver_proot-apps_firefox), and optionally a metadata/ directory holding the catalog its graphical installer shows (metadata.yml and the icons under img/). A session on such a repository installs only what the folder holds, so a panel listing the remote catalog would offer applications every install of which fails. The server therefore serves the folder's own catalog over /api/apps/metadata.yml and /api/apps/img/<name>, behind the session's own authentication, and tells the page to read it there (apps_local_repo in the settings payload).

A repository without metadata/ is still a repository: its catalog is written here from the application directories, the listing proot-apps' own shell completion offers, each named as the runner takes it on the command line (the short name for the default namespace, the image reference otherwise) and nothing more, since nothing more about them is known. The document is JSON, which is YAML to the page's parser.

attributeMETADATA_DIR
= 'metadata'
attributeMETADATA_FILE
= 'metadata.yml'
attributeIMAGE_DIR
= 'img'
attributeDEFAULT_IMAGE_PREFIX
= 'ghcr.io_linuxserver_proot-apps_'
funcmetadata_file(folder) -> str

The catalog file a local repository may carry.

Source Code
def metadata_file(folder: str) -> str:
    """The catalog file a local repository may carry."""
    return os.path.join(folder, METADATA_DIR, METADATA_FILE)
paramfolderstr

Returns

str
funcapp_name(image_folder) -> str

The command-line name of an application directory, as proot-apps resolves it.

A directory under the default namespace is the short application name; any other is the image reference, registry/namespace/image:tag.

Source Code
def app_name(image_folder: str) -> str:
    """The command-line name of an application directory, as proot-apps resolves it.

    A directory under the default namespace is the short application name;
    any other is the image reference, `registry/namespace/image:tag`.
    """
    if image_folder.startswith(DEFAULT_IMAGE_PREFIX):
        return image_folder[len(DEFAULT_IMAGE_PREFIX):]
    head, sep, tag = image_folder.rpartition("_")
    if not sep:
        return image_folder
    return f"{head.replace('_', '/')}:{tag}"
paramimage_folderstr

Returns

str
funclisted_apps(folder) -> List[str]

The applications a local repository holds, by command-line name.

Loose files and the metadata directory are not applications.

Source Code
def listed_apps(folder: str) -> List[str]:
    """The applications a local repository holds, by command-line name.

    Loose files and the metadata directory are not applications.
    """
    try:
        entries = sorted(os.listdir(folder))
    except OSError:
        return []
    return [app_name(entry) for entry in entries
            if entry != METADATA_DIR and os.path.isdir(os.path.join(folder, entry))]
paramfolderstr

Returns

typing.List[str]
funccatalog_text(folder) -> Optional[str]

The catalog document of a local repository, or None where there is no repository.

The repository's own metadata/metadata.yml verbatim when it has one, else a document naming the applications the folder holds.

Source Code
def catalog_text(folder: str) -> Optional[str]:
    """The catalog document of a local repository, or None where there is no repository.

    The repository's own `metadata/metadata.yml` verbatim when it has one, else
    a document naming the applications the folder holds.
    """
    if not folder or not os.path.isdir(folder):
        return None
    path = metadata_file(folder)
    try:
        with open(path, "r", encoding="utf-8") as fh:
            return fh.read()
    except OSError:
        pass
    include = [{"name": name, "full_name": name, "description": "", "icon": ""}
               for name in listed_apps(folder)]
    return json.dumps({"include": include}, indent=2) + "\n"
paramfolderstr

Returns

typing.Optional[str]
funcicon_path(folder, name) -> Optional[str]

The file behind one catalog icon, or None where there is none to serve.

name is a file name the catalog gave, so a value that is not one -- a path, a traversal, a NUL byte, a name resolving outside the image directory through a link -- names nothing.

Source Code
def icon_path(folder: str, name: str) -> Optional[str]:
    """The file behind one catalog icon, or None where there is none to serve.

    `name` is a file name the catalog gave, so a value that is not one -- a
    path, a traversal, a NUL byte, a name resolving outside the image
    directory through a link -- names nothing.
    """
    if not folder or not name or name in (".", "..") or "/" in name or os.sep in name or "\0" in name:
        return None
    images = os.path.realpath(os.path.join(folder, METADATA_DIR, IMAGE_DIR))
    path = os.path.realpath(os.path.join(images, name))
    if not path.startswith(images + os.sep):
        return None
    if os.path.dirname(path) != images or not os.path.isfile(path):
        return None
    return path
paramfolderstr
paramnamestr

Returns

typing.Optional[str]

On this page

No Headings
Edit on GitHub