From 72aef9171bd9168afef935242170afb516581f62 Mon Sep 17 00:00:00 2001 From: secondskoll Date: Mon, 8 Dec 2025 15:28:41 +1000 Subject: [PATCH 1/7] feat: update tooling, use new terminal extension, add metadata, add new homepage sections. --- .github/workflows/automatic-doc-checks.yml | 2 + .github/workflows/check-removed-urls.yml | 14 +- .github/workflows/markdown-style-checks.yml | 15 ++- docs/.custom_wordlist.txt | 9 ++ docs/.sphinx/.markdownlint.json | 27 ---- docs/.sphinx/.pymarkdown.json | 43 ++++++ docs/.sphinx/get_vale_conf.py | 16 +-- docs/.sphinx/metrics/build_metrics.py | 94 +++++++++++++ docs/.sphinx/metrics/build_metrics.sh | 15 --- docs/.sphinx/update_sp.py | 10 +- docs/.sphinx/version | 2 +- docs/Makefile | 36 ++--- docs/conf.py | 15 +++ docs/{how-to => }/explanation.md | 1 + docs/how-to.md | 3 +- docs/how-to/airgap-charmhub.md | 45 +++++-- docs/how-to/airgap.md | 13 +- docs/how-to/build-images.md | 14 +- docs/how-to/charmhub-proxy.md | 11 +- docs/how-to/devices.md | 9 +- docs/how-to/high-availability.md | 42 +++++- docs/how-to/install.md | 1 + .../integrate-a-dedicated-snap-store.md | 6 + docs/how-to/migrate.md | 16 ++- docs/how-to/on-prem-model-service.md | 1 + docs/how-to/overrides.md | 2 +- docs/how-to/publish-snaps.md | 43 +++--- docs/how-to/register.md | 3 +- docs/how-to/security.md | 44 ++++--- .../synchronise-with-a-dedicated-store.md | 15 ++- docs/how-to/trouble.md | 2 +- docs/index.rst | 46 +++++-- docs/reference.md | 3 +- docs/reference/api-authentication.md | 2 + docs/reference/api-overrides.md | 3 +- docs/reference/configuration.rst | 3 + docs/reference/cryptography.md | 6 + docs/reference/feature-list.md | 1 + docs/requirements.txt | 25 +++- docs/tutorial.md | 5 + docs/tutorial/air-gapped-deployment.md | 124 +++++++++++------- docs/tutorial/get-started.md | 118 +++++++++++------ 42 files changed, 637 insertions(+), 268 deletions(-) delete mode 100644 docs/.sphinx/.markdownlint.json create mode 100644 docs/.sphinx/.pymarkdown.json create mode 100644 docs/.sphinx/metrics/build_metrics.py delete mode 100644 docs/.sphinx/metrics/build_metrics.sh mode change 100644 => 100755 docs/.sphinx/update_sp.py rename docs/{how-to => }/explanation.md (91%) diff --git a/.github/workflows/automatic-doc-checks.yml b/.github/workflows/automatic-doc-checks.yml index 9f8ab04..479ebcb 100644 --- a/.github/workflows/automatic-doc-checks.yml +++ b/.github/workflows/automatic-doc-checks.yml @@ -5,6 +5,8 @@ on: push: branches: [ main ] pull_request: + paths: + - 'docs/**' # Only run on changes to the docs directory workflow_dispatch: # Manual trigger diff --git a/.github/workflows/check-removed-urls.yml b/.github/workflows/check-removed-urls.yml index 45d158b..a232637 100644 --- a/.github/workflows/check-removed-urls.yml +++ b/.github/workflows/check-removed-urls.yml @@ -9,20 +9,22 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout PR branch - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: - repository: ${{ github.event.pull_request.head.repo.full_name }} - ref: ${{ github.event.pull_request.head.ref }} + # This implicitly gets the PR branch. Making it explicit causes problems + # with private forks, but it is equivalent to the following: + # repository: ${{ github.event.pull_request.head.repo.full_name }} + # ref: ${{ github.event.pull_request.head.ref }} fetch-depth: 0 path: compare - name: Checkout base branch - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: ref: ${{ github.event.pull_request.base.ref }} repository: ${{ github.event.pull_request.base.repo.full_name }} fetch-depth: 0 path: base - - uses: actions/setup-python@v5 + - uses: actions/setup-python@v6 - name: Build docs run: | for dir in compare base; do @@ -51,4 +53,4 @@ jobs: echo "$removed" echo "Please ensure removed pages are redirected" exit 1 - fi \ No newline at end of file + fi diff --git a/.github/workflows/markdown-style-checks.yml b/.github/workflows/markdown-style-checks.yml index 9369eb6..a209126 100644 --- a/.github/workflows/markdown-style-checks.yml +++ b/.github/workflows/markdown-style-checks.yml @@ -4,17 +4,24 @@ on: push: branches: - main + paths: + - 'docs/**' # Only run on changes to the docs directory pull_request: branches: - '*' + paths: + - 'docs/**' # Only run on changes to the docs directory jobs: markdown-lint: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: fetch-depth: 0 - - uses: DavidAnson/markdownlint-cli2-action@v16 - with: - config: "docs/.sphinx/.markdownlint.json" + - name: Create venv + working-directory: "docs" + run: make install + - name: Lint markdown + working-directory: "docs" + run: make lint-md \ No newline at end of file diff --git a/docs/.custom_wordlist.txt b/docs/.custom_wordlist.txt index ddebd19..ec7513f 100644 --- a/docs/.custom_wordlist.txt +++ b/docs/.custom_wordlist.txt @@ -66,3 +66,12 @@ microk memcached sideload snapstorage +nonces? +pymemcache +unscanned +Canonical's +ctrl +postgres +jq +sideloaded +iptable diff --git a/docs/.sphinx/.markdownlint.json b/docs/.sphinx/.markdownlint.json deleted file mode 100644 index f42753f..0000000 --- a/docs/.sphinx/.markdownlint.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "default": false, - "MD003": { - "style": "atx" - }, - "MD013": { - "code_blocks": false, - "tables": false, - "stern": true, - "line_length": 150 - }, - "MD014": true, - "MD018": true, - "MD022": true, - "MD023": true, - "MD026": { - "punctuation": ".,;。,;" - }, - "MD031": { - "list_items": false - }, - "MD032": true, - "MD035": true, - "MD042": true, - "MD045": true, - "MD052": true -} \ No newline at end of file diff --git a/docs/.sphinx/.pymarkdown.json b/docs/.sphinx/.pymarkdown.json new file mode 100644 index 0000000..8d51318 --- /dev/null +++ b/docs/.sphinx/.pymarkdown.json @@ -0,0 +1,43 @@ +{ + "plugins": { + "selectively_enable_rules": true, + "heading-style": { + "enabled": true, + "style": "atx" + }, + "commands-show-output": { + "enabled": true + }, + "no-missing-space-atx": { + "enabled": true + }, + "heading-start-left": { + "enabled": true + }, + "no-trailing-punctuation": { + "enabled": true, + "punctuation": ".,;。,;" + }, + "blanks-around-fences": { + "enabled": true, + "list_items": false + }, + "blanks-around-lists": { + "enabled": true + }, + "hr-style": { + "enabled": true + }, + "no-empty-links": { + "enabled": true + }, + "no-alt-text": { + "enabled": true + } + }, + "extensions": { + "front-matter" : { + "enabled" : true + } + } +} \ No newline at end of file diff --git a/docs/.sphinx/get_vale_conf.py b/docs/.sphinx/get_vale_conf.py index e2a81c0..13e7966 100644 --- a/docs/.sphinx/get_vale_conf.py +++ b/docs/.sphinx/get_vale_conf.py @@ -31,12 +31,12 @@ def clone_repo_and_copy_paths(file_source_dest, overwrite=False): """ Clone the repository to a temporary directory and copy required files - + Args: file_source_dest: dictionary of file paths to copy from the repository, and their destination paths overwrite: boolean flag to overwrite existing files in the destination - + Returns: bool: True if all files were copied successfully, False otherwise """ @@ -52,8 +52,8 @@ def clone_repo_and_copy_paths(file_source_dest, overwrite=False): try: result = subprocess.run( - clone_cmd, - capture_output=True, + clone_cmd, + capture_output=True, text=True, check=True ) @@ -73,7 +73,7 @@ def clone_repo_and_copy_paths(file_source_dest, overwrite=False): continue if not copy_files_to_path(source_path, dest, overwrite): - is_copy_success = False + is_copy_success = False logging.error("Failed to copy %s to %s", source_path, dest) # Clean up temporary directory @@ -85,12 +85,12 @@ def clone_repo_and_copy_paths(file_source_dest, overwrite=False): def copy_files_to_path(source_path, dest_path, overwrite=False): """ Copy a file or directory from source to destination - + Args: source_path: Path to the source file or directory dest_path: Path to the destination overwrite: Boolean flag to overwrite existing files in the destination - + Returns: bool: True if copy was successful, False otherwise """ @@ -138,7 +138,7 @@ def main(): # Parse command line arguments, default to overwrite_enabled = True overwrite_enabled = not parse_arguments().no_overwrite - # Download into /tmp through git clone + # Download into /tmp through git clone if not clone_repo_and_copy_paths(vale_files_dict, overwrite=overwrite_enabled): logging.error("Failed to download files from repository") return 1 diff --git a/docs/.sphinx/metrics/build_metrics.py b/docs/.sphinx/metrics/build_metrics.py new file mode 100644 index 0000000..529fb85 --- /dev/null +++ b/docs/.sphinx/metrics/build_metrics.py @@ -0,0 +1,94 @@ +#!/usr/bin/python3 + +import sys +import argparse +from pathlib import Path +from html.parser import HTMLParser +from urllib.parse import urlsplit + + +class MetricsParser(HTMLParser): + def __init__(self): + super().__init__() + self.int_link_count = 0 + self.ext_link_count = 0 + self.fragment_count = 0 + self.image_count = 0 + self.in_object = 0 + + @property + def link_count(self): + return self.fragment_count + self.int_link_count + self.ext_link_count + + def read(self, file): + """ + Read *file* (a file-like object with a ``read`` method returning + strings) a chunk at a time, feeding each chunk to the parser. + """ + # Ensure the parser state is reset before each file (just in case + # there's an erroneous dangling ) + self.reset() + self.in_object = 0 + buf = '' + while True: + # Parse 1MB chunks at a time + buf = file.read(1024**2) + if not buf: + break + self.feed(buf) + + def handle_starttag(self, tag, attrs): + """ + Count , , and tags to determine the number of internal + and external links, and the number of images. + """ + attrs = dict(attrs) + if tag == 'a' and 'href' in attrs: + # If there's no href, it's an anchor; if there's no hostname + # (netloc) or path, it's just a fragment link within the page + url = urlsplit(attrs['href']) + if url.netloc: + self.ext_link_count += 1 + elif url.path: + self.int_link_count += 1 + else: + self.fragment_count += 1 + elif tag == 'object': + # tags are a bit complex as they nest to offer fallbacks + # and may contain an fallback. We only want to count the + # outer-most in this case + if self.in_object == 0: + self.image_count += 1 + self.in_object += 1 + elif tag == 'img' and self.in_object == 0: + self.image_count += 1 + + def handle_endtag(self, tag): + if tag == 'object': + # Never let in_object be negative + self.in_object = max(0, self.in_object - 1) + + +def main(args=None): + parser = argparse.ArgumentParser() + parser.add_argument( + 'build_dir', metavar='build-dir', nargs='?', default='.', + help="The directory to scan for HTML files") + config = parser.parse_args(args) + + parser = MetricsParser() + for path in Path(config.build_dir).rglob('*.html'): + with path.open('r', encoding='utf-8', errors='replace') as f: + parser.read(f) + + print('Summarising metrics for build files (.html)...') + print(f'\tlinks: {parser.link_count} (' + f'{parser.fragment_count} #frag…, ' + f'{parser.int_link_count} /int…, ' + f'{parser.ext_link_count} https://ext…' + ')') + print(f'\timages: {parser.image_count}') + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/docs/.sphinx/metrics/build_metrics.sh b/docs/.sphinx/metrics/build_metrics.sh deleted file mode 100644 index bd1ff1c..0000000 --- a/docs/.sphinx/metrics/build_metrics.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/bash -# shellcheck disable=all - -links=0 -images=0 - -# count number of links -links=$(find . -type d -path './.sphinx' -prune -o -name '*.html' -exec cat {} + | grep -o " $(SPHINXDIR)/styles/woke.filter @echo '.Level=="error" and .Name!="Canonical.500-Repeated-words" and .Name!="Canonical.000-US-spellcheck"' > $(SPHINXDIR)/styles/error.filter @echo '.Name=="Canonical.000-US-spellcheck"' > $(SPHINXDIR)/styles/spelling.filter - @. $(VENV); find $(SPHINXDIR)/venv/lib/python*/site-packages/vale/vale_bin -size 195c -exec vale --version \; + @. $(VENV); find $(VALEDIR)/vale_bin -size 195c -exec vale --version \; woke: vale-install - @cat $(SPHINXDIR)/styles/config/vocabularies/Canonical/accept.txt > $(SPHINXDIR)/styles/config/vocabularies/Canonical/accept_backup.txt - @cat $(SOURCEDIR)/.custom_wordlist.txt >> $(SPHINXDIR)/styles/config/vocabularies/Canonical/accept.txt + @cat $(VOCAB_CANONICAL)/accept.txt > $(VOCAB_CANONICAL)/accept_backup.txt + @cat $(SOURCEDIR)/.custom_wordlist.txt >> $(VOCAB_CANONICAL)/accept.txt @echo "Running Vale acceptable term check against $(TARGET). To change target set TARGET= with make command" @. $(VENV); vale --config="$(VALE_CONFIG)" --filter='$(SPHINXDIR)/styles/woke.filter' --glob='*.{md,rst}' $(TARGET) - @cat $(SPHINXDIR)/styles/config/vocabularies/Canonical/accept_backup.txt > $(SPHINXDIR)/styles/config/vocabularies/Canonical/accept.txt && rm $(SPHINXDIR)/styles/config/vocabularies/Canonical/accept_backup.txt + @cat $(VOCAB_CANONICAL)/accept_backup.txt > $(VOCAB_CANONICAL)/accept.txt && rm $(VOCAB_CANONICAL)/accept_backup.txt vale: vale-install - @cat $(SPHINXDIR)/styles/config/vocabularies/Canonical/accept.txt > $(SPHINXDIR)/styles/config/vocabularies/Canonical/accept_backup.txt - @cat $(SOURCEDIR)/.custom_wordlist.txt >> $(SPHINXDIR)/styles/config/vocabularies/Canonical/accept.txt + @cat $(VOCAB_CANONICAL)/accept.txt > $(VOCAB_CANONICAL)/accept_backup.txt + @cat $(SOURCEDIR)/.custom_wordlist.txt >> $(VOCAB_CANONICAL)/accept.txt @echo "Running Vale against $(TARGET). To change target set TARGET= with make command" @. $(VENV); vale --config="$(VALE_CONFIG)" --filter='$(SPHINXDIR)/styles/error.filter' --glob='*.{md,rst}' $(TARGET) - @cat $(SPHINXDIR)/styles/config/vocabularies/Canonical/accept_backup.txt > $(SPHINXDIR)/styles/config/vocabularies/Canonical/accept.txt && rm $(SPHINXDIR)/styles/config/vocabularies/Canonical/accept_backup.txt + @cat $(VOCAB_CANONICAL)/accept_backup.txt > $(VOCAB_CANONICAL)/accept.txt && rm $(VOCAB_CANONICAL)/accept_backup.txt spelling: vale-install - @cat $(SPHINXDIR)/styles/config/vocabularies/Canonical/accept.txt > $(SPHINXDIR)/styles/config/vocabularies/Canonical/accept_backup.txt - @cat $(SOURCEDIR)/.custom_wordlist.txt >> $(SPHINXDIR)/styles/config/vocabularies/Canonical/accept.txt + @cat $(VOCAB_CANONICAL)/accept.txt > $(VOCAB_CANONICAL)/accept_backup.txt + @cat $(SOURCEDIR)/.custom_wordlist.txt >> $(VOCAB_CANONICAL)/accept.txt @echo "Running Vale against $(TARGET). To change target set TARGET= with make command" @. $(VENV); vale --config="$(VALE_CONFIG)" --filter='$(SPHINXDIR)/styles/spelling.filter' --glob='*.{md,rst}' $(TARGET) - @cat $(SPHINXDIR)/styles/config/vocabularies/Canonical/accept_backup.txt > $(SPHINXDIR)/styles/config/vocabularies/Canonical/accept.txt && rm $(SPHINXDIR)/styles/config/vocabularies/Canonical/accept_backup.txt + @cat $(VOCAB_CANONICAL)/accept_backup.txt > $(VOCAB_CANONICAL)/accept.txt && rm $(VOCAB_CANONICAL)/accept_backup.txt spellcheck: spelling @echo "Please note that the \`make spellcheck\` command is being deprecated in favor of \`make spelling\`" @@ -169,11 +169,11 @@ allmetrics: html @echo "Recording documentation metrics..." @echo "Checking for existence of vale..." . $(VENV) - @. $(VENV); test -d $(SPHINXDIR)/venv/lib/python*/site-packages/vale || pip install vale + @. $(VENV); test -d $(VALEDIR) || pip install vale @. $(VENV); test -f $(VALE_CONFIG) || python3 $(SPHINXDIR)/get_vale_conf.py - @. $(VENV); find $(SPHINXDIR)/venv/lib/python*/site-packages/vale/vale_bin -size 195c -exec vale --config "$(VALE_CONFIG)" $(TARGET) > /dev/null \; + @. $(VENV); find $(VALEDIR)/vale_bin -size 195c -exec vale --config "$(VALE_CONFIG)" $(TARGET) > /dev/null \; @eval '$(METRICSDIR)/source_metrics.sh $(PWD)' - @$(METRICSDIR)/build_metrics.py $(BUILDDIR) + @. $(VENV); python3 $(METRICSDIR)/build_metrics.py $(BUILDDIR) update: install @. $(VENV); .sphinx/update_sp.py @@ -181,5 +181,5 @@ update: install # Catch-all target: route all unknown targets to Sphinx using the new # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). %: - $(MAKE) —no-print-directory install + $(MAKE) --no-print-directory install . $(VENV); $(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) \ No newline at end of file diff --git a/docs/conf.py b/docs/conf.py index d50941f..38f32ff 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -265,8 +265,23 @@ extensions = [ "canonical_sphinx", + "notfound.extension", + "sphinx_design", + "sphinx_reredirects", + "sphinx_tabs.tabs", + "sphinxcontrib.jquery", + "sphinxext.opengraph", + "sphinx_config_options", + "sphinx_contributor_listing", + "sphinx_filtered_toctree", + "sphinx_related_links", + "sphinx_roles", + "sphinx_terminal", + "sphinx_ubuntu_images", + "sphinx_youtube_links", "sphinxcontrib.cairosvgconverter", "sphinx_last_updated_by_git", + "sphinx.ext.intersphinx", "sphinx_sitemap", ] diff --git a/docs/how-to/explanation.md b/docs/explanation.md similarity index 91% rename from docs/how-to/explanation.md rename to docs/explanation.md index acf5273..cd3e1e4 100644 --- a/docs/how-to/explanation.md +++ b/docs/explanation.md @@ -1,6 +1,7 @@ --- title: Enterprise Store Explanation guides orphan: true +description: Conceptual guides explaining how the Enterprise Store works and how it can be configured and used. --- # Enterprise Store Explanation guides diff --git a/docs/how-to.md b/docs/how-to.md index 63bfad6..b3baafb 100644 --- a/docs/how-to.md +++ b/docs/how-to.md @@ -1,5 +1,6 @@ --- -title: Enterprise Store How-to guides +title: How-to guides +description: Step-by-step guides for installing, configuring, and operating the Enterprise Store in various deployment scenarios. --- # Enterprise Store How-to guides diff --git a/docs/how-to/airgap-charmhub.md b/docs/how-to/airgap-charmhub.md index e2b58f7..175e0f7 100644 --- a/docs/how-to/airgap-charmhub.md +++ b/docs/how-to/airgap-charmhub.md @@ -1,6 +1,7 @@ --- title: Offline Charmhub (air-gapped mode) table_of_contents: true +description: Distribute charms and charm bundles in air-gapped environments using the Enterprise Store as a local Charmhub. --- # Offline Charmhub (air-gapped mode) @@ -24,10 +25,13 @@ To configure your local OCI registry, specify a domain name or an IP. This setti In this setup, the local Charmhub does not directly access the registry; rather, it provides the image path and credentials to Juju. Juju then uses this information to instruct the container runtime to fetch the images. The domain name and credentials are configured to override the default upstream domain and credentials, ensuring that charm OCI image paths are correctly served from your local setup. + ```bash sudo enterprise-store config proxy.oci-registry.domain= ``` + If required, set the credentials for registry access. + ```bash sudo enterprise-store config proxy.oci-registry.username=some-username proxy.oci-registry.password=some-password ``` @@ -40,8 +44,10 @@ On an internet-connected machine, export the required charms and resources as il If the offline deployment target is a [Charmhub bundle](https://charmhub.io/?type=bundle), then the bundle and its component charms can be exported like so: -```bash -$ store-admin export bundle cos-lite --channel=latest/stable --series=kubernetes --arch=amd64 +```{terminal} + +store-admin export bundle cos-lite --channel=latest/stable --series=kubernetes --arch=amd64 + Downloading cos-lite revision 11 (stable) [####################################] 100% Downloading traefik-k8s revision 176 (stable) @@ -74,8 +80,10 @@ The `charm` key is required, while the other fields will use default values if o Pass the `charms.yaml` to the `export charms` command like so: -```bash -$ store-admin export charms ./charms.yaml +```{terminal} + +store-admin export charms ./charms.yaml + Overriding postgresql-image with local registry subpath. Downloading postgresql-k8s revision 20 (latest/stable) [####################################] 100% @@ -124,8 +132,10 @@ When installing a snap by revision, the Snap Store requires that the revision ex The export `.yaml` can be supplied to the `export snaps` command like so: -```bash -$ store-admin export snaps --from-yaml snaps.yaml +```{terminal} + +store-admin export snaps --from-yaml snaps.yaml + Downloading charmed-postgresql revision 96 (chp_14/edge amd64) [####################################] 100% Downloading charmed-mysql revision 97 (8.0/edge amd64) @@ -151,14 +161,16 @@ The image itself needs to be exported using a separate tool such as `skopeo`, wh For example, to save the above image to a local directory: -```bash -$ skopeo copy docker://registry.jujucharms.com/charm/kotcfrohea62xreenq1q75n1lyspke0qkurhk/postgresql-image@sha256:8a72e1152d4a0... --src-creds=docker-registry:MDAxOGxvY2F0aW9... dir:/home/ubuntu/ +```{terminal} + +skopeo copy docker://registry.jujucharms.com/charm/kotcfrohea62xreenq1q75n1lyspke0qkurhk/ postgresql-image@sha256:8a72e1152d4a0... --src-creds=docker-registry:MDAxOGxvY2F0aW9... dir:/home/ubuntu/ ``` The directory can then be manually copied to the air-gapped registry host, then pushed to the registry like so: -```bash -$ skopeo copy dir:/home/ubuntu/ docker:///charm/kotcfrohea62xreenq1q75n1lyspke0qkurhk/postgresql-image@sha256:8a72e1152d4a0... --dest-creds=: +```{terminal} + +skopeo copy dir:/home/ubuntu/ docker:///charm/kotcfrohea62xreenq1q75n1lyspke0qkurhk/postgresql-image@sha256:8a72e1152d4a0... --dest-creds=: ``` By default, if no override is supplied via the `resources` key in the `.yaml` supplied for charm export, Charmhub Proxy will assume an identical local registry image path (excluding the domain but including `charm/` and including the sha256 tag). When a deployment is requested, CHP will supply a regenerated blob using the local domain URL and credentials configured. @@ -166,6 +178,7 @@ By default, if no override is supplied via the `resources` key in the `.yaml` su The `skopeo` commands above pushes the image to the same path in the local registry and saves the effort of manually remapping resources. If required, the image can be pushed to a custom path, but a mapping must be defined for the resource as in the example `charms.yaml` in [Export charms](#export-charms). ## Import Packages + Once the exported charm tar file is on the on-prem store host, they should be moved to the `/var/snap/enterprise-store/common/charms-to-push/` directory, from where they can be imported. @@ -190,7 +203,6 @@ After importing, the charms/bundles are then available to be managed with Juju c - When importing machine charms that depend on a snap for functionality, you must first manually [import the required snap](airgap.md#side-loading-snaps). - When importing Kubernetes charms, ensure that the corresponding OCI image is copied to the local registry, maintaining its original path. - ## Configure Juju Ensure you have Juju configured along with the necessary cloud environment. @@ -206,18 +218,21 @@ First, you need to prepare the Juju configuration file. In this file, override t ### Self-signed certificate - You can create a self signed certificate for the Enterprise Store with the following command: + ```bash sudo enterprise-store import-certificate --selfsigned ``` After it's created, you can retrieve the public key from the configuration: + ```bash enterprise-store config proxy.tls.cert | cat > tls-cert.crt ``` + When using a self-signed certificate, it’s crucial to ensure that the underlying operating system where the Juju client is running trusts the certificate. You can achieve this by adding the certificate to the system's trusted store. You can achieve that with the following commands: + ```bash sudo cp your_certificate.crt /usr/local/share/ca-certificates/ sudo update-ca-certificates @@ -226,6 +241,7 @@ sudo update-ca-certificates ### Configuration file Example of a Juju configuration `.yaml`. Note that ca-certs list is necessary only when using self-signed certificate for the local Charmhub. + ```yaml cloudinit-userdata: | ca-certs: @@ -239,6 +255,7 @@ cloudinit-userdata: | charmhub-url: https://local-charmhub.internal snap-store-proxy-url: https://local-charmhub.internal ``` + Store this file in a Juju accessible path e.g. `/var/snap/juju/common/juju-config.yaml`. ### Controller and Model setup @@ -251,16 +268,19 @@ juju bootstrap lxd machine-controller --config=/var/snap/juju/common/juju-config The example below assumes that an LXD cloud is already set up and utilises it to create a Juju controller: + ```bash juju add-k8s k8s-cloud --controller=machine-controller ``` We can then create a model using the following example: + ```bash juju add-model test-model k8s-cloud --config /var/snap/juju/common/juju-config.yaml ``` In case we want to deploy to a non k8s cloud, we can skip the cloud parameter: + ```bash juju add-model test-model --config /var/snap/juju/common/juju-config.yaml ``` @@ -282,6 +302,7 @@ Some Juju commands are not tied to a controller and instead require the setup of ```bash CHARMHUB_URL="https://local-charmhub.internal" juju info cos-lite ``` + To make this change more permanent, you can add the variable to the `.bashrc` file in the user's home folder. This ensures that the custom URL is consistently used whenever [Juju commands](https://juju.is/docs/juju/manage-charms-or-bundles) are run from the terminal. diff --git a/docs/how-to/airgap.md b/docs/how-to/airgap.md index 384a2f6..2da1930 100644 --- a/docs/how-to/airgap.md +++ b/docs/how-to/airgap.md @@ -1,6 +1,7 @@ --- title: Operate offline table_of_contents: true +description: Deploy and operate the Enterprise Store in air-gapped mode. --- # Offline store (air-gapped mode) @@ -202,7 +203,6 @@ The above will export the following data: (`store-admin export snaps` can be used to export snaps in a more granular fashion). - ### Brand store import The exported `store-export-*.tar.gz` file can be imported on the target on-prem host using the `enterprise-store push-store` command. Example: @@ -283,29 +283,29 @@ limited to: - `core24` - `snapd` - ## Status +\ +List the imported stores and account keys: + ``` enterprise-store status ``` -lists the imported stores and account keys and + +List all imported snaps: ``` enterprise-store list-pushed-snaps ``` -lists all imported snaps. Running `snap info ` from a device connected to the on-prem store can be used to view more details about the snap, like its current channel map. - ## Client Device Configuration [Configuring client devices](devices.md) follows the same process as with an online Enterprise Store. - ## Offline Upgrades To upgrade enterprise-store on an offline machine, first download the snap and @@ -327,7 +327,6 @@ sudo snap install enterprise-store_.snap And use analogous process to upgrade the base and `snapd` snaps. - ## Configuration backup Make sure to securely backup the enterprise-store configuration (including the diff --git a/docs/how-to/build-images.md b/docs/how-to/build-images.md index 4d1eefc..0cdb2d3 100644 --- a/docs/how-to/build-images.md +++ b/docs/how-to/build-images.md @@ -1,3 +1,9 @@ +--- +title: Build Ubuntu Core images from your store +table_of_contents: true +description: Build Ubuntu Core images from an air-gapped Enterprise Store using ubuntu-image with Dedicated Snap Store credentials. +--- + # Build Ubuntu Core images from your store ```{warning} @@ -14,11 +20,11 @@ extra options in the form of environment variables to `ubuntu-image`, including: ```{terminal} :user: user :host: admin-box -:input: export UBUNTU_STORE_URL="https://snaps.acme.internal" -:input: export UBUNTU_STORE_AUTH="$(cat acme-onprem-credentials)" -:input: export UBUNTU_STORE_ID="StoreIdXYZ" -:input: ubuntu-image classic acme-core20-amd64.model +export UBUNTU_STORE_URL="https://snaps.acme.internal" +export UBUNTU_STORE_AUTH="$(cat acme-onprem-credentials)" +export UBUNTU_STORE_ID="StoreIdXYZ" +ubuntu-image classic acme-core20-amd64.model ``` ```{note} diff --git a/docs/how-to/charmhub-proxy.md b/docs/how-to/charmhub-proxy.md index 6d477d9..4fc50ee 100644 --- a/docs/how-to/charmhub-proxy.md +++ b/docs/how-to/charmhub-proxy.md @@ -1,6 +1,7 @@ --- title: Charmhub Proxy table_of_contents: true +description: Serve charms and charm bundles in air-gapped Juju deployments using the Enterprise Store's Charmhub Proxy functionality. --- # Charmhub Proxy @@ -130,14 +131,16 @@ The image itself needs to be exported using a separate tool such as `skopeo`, wh For example, to save the above image to a local directory: -```bash -$ skopeo copy docker://registry.jujucharms.com/charm/kotcfrohea62xreenq1q75n1lyspke0qkurhk/postgresql-image@sha256:8a72e1152d4a0... --src-creds=docker-registry:MDAxOGxvY2F0aW9... dir:/home/ubuntu/ +```{terminal} + +skopeo copy docker://registry.jujucharms.com/charm/kotcfrohea62xreenq1q75n1lyspke0qkurhk/postgresql-image@sha256:8a72e1152d4a0... --src-creds=docker-registry:MDAxOGxvY2F0aW9... dir:/home/ubuntu/ ``` The directory can then be manually copied to the air-gapped registry host, then pushed to the registry like so: -```bash -$ skopeo copy dir:/home/ubuntu/ docker:///charm/kotcfrohea62xreenq1q75n1lyspke0qkurhk/postgresql-image@sha256:8a72e1152d4a0... --dest-creds=: +```{terminal} + +skopeo copy dir:/home/ubuntu/ docker:///charm/kotcfrohea62xreenq1q75n1lyspke0qkurhk/postgresql-image@sha256:8a72e1152d4a0... --dest-creds=: ``` By default, if no override is supplied via the `resources` key in the `.yaml` supplied for charm export, Charmhub Proxy will assume an identical local registry image path (excluding the domain but including `charm/` and including the sha256 tag). When a deployment is requested, CHP will supply a regenerated blob using the local domain URL and credentials configured. diff --git a/docs/how-to/devices.md b/docs/how-to/devices.md index 84146db..e66d55a 100644 --- a/docs/how-to/devices.md +++ b/docs/how-to/devices.md @@ -1,13 +1,13 @@ --- -title: Configuring snap devices +title: Configure a device to use the Enterprise Store table_of_contents: true +description: Configure snap devices to connect to your Enterprise Store instead of the public Snap Store. --- -# Snap devices +# Configure a device to use the Enterprise Store -## Configuring devices +Ensure you have: -**Prerequisites** * `snapd` ≥ 2.30 on the client device * Access to a [registered Enterprise Store](register.md) @@ -83,7 +83,6 @@ the list of allowed device services (Serial Vaults), e.g.: sudo enterprise-store config \ proxy.device-auth.allowed-device-service-urls='["https://sv1.internal", "https://sv2.internal"]' - ## Next step With devices connected to the proxy, you can [create diff --git a/docs/how-to/high-availability.md b/docs/how-to/high-availability.md index 0db48d7..764c412 100644 --- a/docs/how-to/high-availability.md +++ b/docs/how-to/high-availability.md @@ -1,4 +1,11 @@ +--- +title: Enable High-Availability (HA) +table_of_contents: true +description: Configure High-Availability for the Enterprise Store using multiple units with reverse proxy load balancing. +--- + # Enable High-Availability (HA) + By default, the Enterprise Store does not use a High Availability configuration; if the machine with the Enterprise Store snap goes down, then any requests made to it will fail. @@ -9,6 +16,7 @@ to be used for serving client requests. In this scenario, if one unit goes down, then requests can be routed to another live unit. ## Overview + Below is a diagram of an example HA network topology: ![image info](../media/ha-overview.png) @@ -27,6 +35,7 @@ and files from the initial unit 6. Repeat steps 3-5 for the amount of units desired ## Configure the initial unit + Follow the documentation for [installation](install.md), [registration](register.md) and/or [setting up an offline store](airgap.md) depending on your use-case. These already assume the @@ -38,6 +47,7 @@ with a single unit, refer to the [Existing Enterprise Store](#existing-enterprise-store) section. ### TLS Termination + In HA setups, it is common to terminate the TLS connection at the reverse proxy, with traffic to the backend units using unencrypted HTTP, and devices/clients still communicating on a HTTPS connection. If @@ -72,6 +82,7 @@ in the same HA cluster. ``` ### Connect to PostgreSQL + See the [installation](install.md) guide for setting up and connecting to a PostgreSQL server. To the connect to database, set the connecting string: @@ -79,12 +90,14 @@ string: sudo enterprise-store config proxy.db.connection="postgresql://snapproxy-user@pghost.test:5432/snapproxy-db" ### Use a HA memcached or replace usage with PostgreSQL + By default, a single-unit Enterprise Store makes use of a local memcached instance for storing time-bound data like nonces. For HA, we need to either use PostgreSQL as the data store for the time-bound data, or point the units to a separate, dedicated memcached cluster. #### Use PostgreSQL + To use PostgreSQL instead of memcached, run: sudo enterprise-store config proxy.use-postgres-over-memcached="true" @@ -95,6 +108,7 @@ The above option currently does not currently support the ``` #### Use Memcached + To use memcached instance(s), set the connection strings with: # For a single instance @@ -108,6 +122,7 @@ documentation](https://pymemcache.readthedocs.io/en/latest/getting_started.html# for details on how the distribution works. ### Connect to S3 (offline store) + For an offline Enterprise Store, the unit must be configured to use S3 as a blob storage backend. The configuration options for S3 start with `proxy.storage.s3`. @@ -164,6 +179,7 @@ There should be no failing services, especially have been automatically created in the S3 server. ### Make the unit reverse proxy aware + Enterprise Store operators are expected to properly configure the reverse proxy(s) to set the `X-Forwarded-Proto` header (see the next section below for more info). @@ -179,6 +195,7 @@ the reverse proxy(s), and the header should be set appropriately. ``` ## Configure the reverse proxy(s) + Reverse proxies like HAProxy or NGINX are situated between clients and the backend Enterprise Store units. They need to be configured correctly to interact with the backend units. @@ -196,6 +213,7 @@ Reverse proxies should also specify the relevant backend units in the configuration. An example portion of a HAProxy configuration might look like: + ``` frontend my_frontend mode http @@ -219,6 +237,7 @@ backend web_servers Don't forget to restart the reverse proxy to pick up the config. ## Point devices to the reverse proxy + See [how to configure devices](devices.md). You may also have to trust the certificate served by the @@ -232,7 +251,9 @@ At this point, verify that functionality is working as expected for client devices. ## Add another unit + ### Install the snap + To add another unit to our topology, we need to provision a new machine and install the `enterprise-store` snap, using the same revision as the other unit. This could mean running the same `install.sh` @@ -254,6 +275,7 @@ After installing, remember to pin the snap: sudo snap refresh --hold enterprise-store ### Export and import the config + Next, export the config from the existing enterprise-store unit: sudo enterprise-store config --export-yaml | cat > store-config.yaml @@ -302,6 +324,7 @@ Don't forget to restart the reverse proxy to pick up the configuration changes. ## Existing Enterprise Store + Migrating to HA from an existing, non-HA Enterprise Store is generally similar to the steps above, but with some key differences, like updating TLS termination to occur at the reverse proxy and migrating @@ -327,6 +350,7 @@ to S3 storage, before switching the Enterprise Store unit to use S3 as the storage backend. ### Backup + Make a backup and store the file securely: sudo enterprise-store config --export-yaml | cat > store-config.yaml @@ -337,12 +361,13 @@ configuration with: cat store-config.yaml | sudo enterprise-store config --import-yaml ### Reverse Proxy + We want to set up a reverse proxy in front of the existing Enterprise Store snap unit, with devices directing traffic towards the reverse proxy instead of the Enterprise Store snap unit. This is relatively simple if using HTTP. However, if using HTTPS, it's a bit more involved. Please modify the steps as necessary for a HTTP Enterprise -Store. This section assumes the goal of TLS termination occuring at +Store. This section assumes the goal of TLS termination occurring at the reverse proxy. Copy the certificate and private key from the Enterprise Store unit @@ -443,6 +468,7 @@ Restart the reverse proxy, and verify that traffic to the reverse proxy is working as expected. ### S3 (Offline Store) + First, [set the relevant S3 options](#connect-to-s3-offline-store), but **do not switch over to using S3 as the storage backend** yet. @@ -454,11 +480,14 @@ take the Enterprise Store down for maintenance, either by modifying the reverse proxy or disabling the snap on the Enterprise Store unit. #### Migrate scanned package blobs + Scanned blobs can be found on the Enterprise Store unit using: ```{terminal} -:input: ls -1 /var/snap/enterprise-store/common/snapstorage-local/scanned :copy: + +ls -1 /var/snap/enterprise-store/common/snapstorage-local/scanned + LpV8761EjlAPqeXxfYhQvpSWgpxvEWpN_414.snap ``` @@ -524,13 +553,17 @@ local Enterprise Store unit. The blob count on the unit can be found with: ls -1 /var/snap/enterprise-store/common/snapstorage-local/scanned | wc -l #### Migrate unscanned blobs + Unscanned blobs can be found on the Enterprise Store unit using: ```{terminal} -:input: find /var/snap/enterprise-store/common/snapstorage-local/unscanned/ -type f :copy: + +find /var/snap/enterprise-store/common/snapstorage-local/unscanned/ -type f + /var/snap/enterprise-store/common/snapstorage-local/unscanned/e33d585a-cdf3-420e-9b6e-125d069542a5/hello-world_29.snap ``` + In the example output above, `e33d585a-cdf3-420e-9b6e-125d069542a5/hello-world_29.snap` needs to be migrated to the appropriate S3 bucket. Note that the UUID is @@ -572,6 +605,7 @@ local Enterprise Store unit. The blob count on the unit can be found with: ls -1 /var/snap/enterprise-store/common/snapstorage-local/unscanned | wc -l #### Switch to using S3 + Make a backup of the `snapstorage.package_store` table in the PostgreSQL database, to use in case the migration goes wrong. The next steps will cause some downtime. @@ -619,6 +653,7 @@ At this point the initial unit has been configured for HA, so [additional units can be added](#add-another-unit). ## Keep backups + It is advisable to maintain frequent backups of various components of the Enterprise Store. These backups include: @@ -629,6 +664,7 @@ of the Enterprise Store. These backups include: of the network topology (such as reverse proxies, memcached, etc.) ## Keep unit configuration consistent + It is important for the Enterprise Store configuration to be the same across units within the same cluster. Divergent configurations will likely lead to divergent behaviour when handling requests. diff --git a/docs/how-to/install.md b/docs/how-to/install.md index 583ec2a..fa58386 100644 --- a/docs/how-to/install.md +++ b/docs/how-to/install.md @@ -1,6 +1,7 @@ --- title: Install table_of_contents: true +description: Install and configure the Enterprise Store on an Ubuntu LTS system with PostgreSQL database support. --- # Installation diff --git a/docs/how-to/integrate-a-dedicated-snap-store.md b/docs/how-to/integrate-a-dedicated-snap-store.md index 0e56c40..f7bd5d6 100644 --- a/docs/how-to/integrate-a-dedicated-snap-store.md +++ b/docs/how-to/integrate-a-dedicated-snap-store.md @@ -1,3 +1,9 @@ +--- +title: Integrate a Dedicated Snap Store +table_of_contents: true +description: Integrate the Enterprise Store with a Dedicated Snap Store to synchronise credentials and snaps or publish directly to the Enterprise Store. +--- + # Integrate a Dedicated Snap Store Enterprise Stores are designed to work with Dedicated Snap Stores, with some limitations. diff --git a/docs/how-to/migrate.md b/docs/how-to/migrate.md index d174ed3..925a132 100644 --- a/docs/how-to/migrate.md +++ b/docs/how-to/migrate.md @@ -1,3 +1,9 @@ +--- +title: Migrate from Snap Store Proxy to Enterprise Store +table_of_contents: true +description: Upgrade from the legacy Snap Store Proxy snap to the Enterprise Store snap while preserving your configuration. +--- + # Migrate from Snap Store Proxy to Enterprise Store [Snap Store Proxy](https://snapcraft.io/snap-store-proxy) has been renamed to [Enterprise Store](https://snapcraft.io/enterprise-store). The Snap Store Proxy snap will be maintained for the time being, but it is recommended to upgrade to the Enterprise Store snap. @@ -14,6 +20,7 @@ snap install enterprise-store In an offline context, first download the snap and its assertions on a machine with internet access, e.g.: + ```bash snap download enterprise-store --channel=latest/stable ``` @@ -67,9 +74,10 @@ Store service ports conflict with the existing Snap Store Proxy service ports. This is expected. An example output of this is: ```{terminal} -:input: cat store-config.yaml | sudo enterprise-store config --import-yaml :copy: +cat store-config.yaml | sudo enterprise-store config --import-yaml + Configured database for packagereview role. Configured database for packagereview-celery role. Configured database for snaprevs role. @@ -101,9 +109,10 @@ sudo enterprise-store config internal.snapstorage.local-origin-secret="$(sudo sn **This command will fail** with an expected output similar to: ```{terminal} -:input: sudo enterprise-store config internal.snapstorage.local-origin-secret="$(sudo snap get snap-store-proxy internal.snapstorage.local-origin-secret)" :copy: +sudo enterprise-store config internal.snapstorage.local-origin-secret="$(sudo snap get snap-store-proxy internal.snapstorage.local-origin-secret)" + error: Command '['snapctl', 'restart', '--reload', 'enterprise-store.nginx', 'enterprise-store.snapmodels', 'enterprise-store.memcached', 'enterprise-store.storeadmingw', 'enterprise-store.packagereview', 'enterprise-store.snapassert', 'enterprise-store.snapauth', 'enterprise-store.snapproxy', 'enterprise-store.snapstorage', 'enterprise-store.packagereview-worker', 'enterprise-store.snapident', 'enterprise-store.snaprevs', 'enterprise-store.snapdevicegw', 'enterprise-store.publishergw']' returned non-zero exit status 1. ``` @@ -127,9 +136,10 @@ sudo enterprise-store config internal.airgap.gateway-hash="$(sudo snap get snap- **This command will fail** with an expected output similar to: ```{terminal} -:input: sudo enterprise-store config internal.airgap.gateway-hash="$(sudo snap get snap-store-proxy internal.airgap.gateway-hash)" :copy: +sudo enterprise-store config internal.airgap.gateway-hash="$(sudo snap get snap-store-proxy internal.airgap.gateway-hash)" + error: Command '['snapctl', 'restart', '--reload', 'enterprise-store.nginx', 'enterprise-store.snapmodels', 'enterprise-store.memcached', 'enterprise-store.storeadmingw', 'enterprise-store.packagereview', 'enterprise-store.snapassert', 'enterprise-store.snapauth', 'enterprise-store.snapproxy', 'enterprise-store.snapstorage', 'enterprise-store.packagereview-worker', 'enterprise-store.snapident', 'enterprise-store.snaprevs', 'enterprise-store.snapdevicegw', 'enterprise-store.publishergw']' returned non-zero exit status 1. ``` diff --git a/docs/how-to/on-prem-model-service.md b/docs/how-to/on-prem-model-service.md index 3b38335..0a6774c 100644 --- a/docs/how-to/on-prem-model-service.md +++ b/docs/how-to/on-prem-model-service.md @@ -1,6 +1,7 @@ --- title: Enterprise Store Model Service table_of_contents: true +description: Configure the Model Service for device serial provisioning in air-gapped Enterprise Store deployments with HSM support. --- # Air-gapped Model Service diff --git a/docs/how-to/overrides.md b/docs/how-to/overrides.md index a3a61c3..641749d 100644 --- a/docs/how-to/overrides.md +++ b/docs/how-to/overrides.md @@ -1,6 +1,7 @@ --- title: Override snap revisions table_of_contents: true +description: Control and pin specific snap revisions on channels to manage updates for devices connected to your Enterprise Store. --- # Snap revision overrides @@ -75,7 +76,6 @@ core18 stable armhf is tracking upstream (revision 1706) Alternatively, you can also manage overrides via a [REST API](../reference/api-overrides.md) - ## Command line tool There is a [CLI tool](https://snapcraft.io/snap-store-proxy-client) to diff --git a/docs/how-to/publish-snaps.md b/docs/how-to/publish-snaps.md index 5bc4f07..78509e8 100644 --- a/docs/how-to/publish-snaps.md +++ b/docs/how-to/publish-snaps.md @@ -1,3 +1,9 @@ +--- +title: Publish snaps directly to the Enterprise Store +table_of_contents: true +description: Publish snaps directly to an Enterprise Store configured with a Dedicated Snap Store Brand account and signing keys. +--- + # Publish snaps directly to the Enterprise Store ```{warning} @@ -31,7 +37,8 @@ sha3-384 fingerprints of the respective registered keys: :user: user :host: admin-host :copy: -:input: store-admin export store --arch=amd64 --arch=arm64 --channel=stable --channel=edge --key=keyId1 --key=keyId2 --key=keyId3 myDeviceViewStoreID + +store-admin export store --arch=amd64 --arch=arm64 --channel=stable --channel=edge --key=keyId1 --key=keyId2 --key=keyId3 myDeviceViewStoreID Logging in as store admin... Opening an authorization web page in your browser. @@ -74,7 +81,8 @@ Move the exported store bundle to the Enterprise Store machine and run the impor :user: user :host: enterprise-store-host :copy: -:input: sudo snap-proxy push-store --revision-authority-key-id keyid1 --revision-authority-key /var/snap/enterprise-store/common/snaps-to-push/keyid1.private.key /var/snap/enterprise-store/common/snaps-to-push/store-export-myDeviceViewStoreID.tar.gz + +sudo snap-proxy push-store --revision-authority-key-id keyid1 --revision-authority-key /var/snap/enterprise-store/common/snaps-to-push/keyid1.private.key /var/snap/enterprise-store/common/snaps-to-push/store-export-myDeviceViewStoreID.tar.gz Uploaded snap and assertions for core revision 13250 @@ -96,7 +104,8 @@ The key file specified with `--revision-authority-key` contains the **private ke :user: user :host: admin-host :copy: -:input: gpg --homedir ~/.snap/gnupg --export-secret-keys --armor ` + +gpg --homedir ~/.snap/gnupg --export-secret-keys --armor ` ``` Where `` is the name as shown in the `snapcraft list-keys` output. A matching `--revision-authority-key-id` has to be specified as well (also available in the `snapcraft list-keys` output). @@ -112,19 +121,21 @@ Configure the snap revision provenance for this Enterprise Store (the value for :user: user :host: enterprise-store-host :copy: -:input: sudo enterprise-store config internal.airgap.store.provenance-allowlist=acme-site-7 + +sudo enterprise-store config internal.airgap.store.provenance-allowlist=acme-site-7 ``` ## Build and publish with Snapcraft -Snapcraft is used to build revision authority delegated snaps, and to publish them to the Enteprise Store. +Snapcraft is used to build revision authority delegated snaps, and to publish them to the Enterprise Store. ```{terminal} :user: user :host: admin-host :copy: -:input: sudo snap install snapcraft --classic + +sudo snap install snapcraft --classic ``` Configure Snapcraft for your Enterprise Store using the data exported from the data provided with `store-admin export store`: @@ -132,20 +143,21 @@ Configure Snapcraft for your Enterprise Store using the data exported from the d ```{terminal} :user: user :host: admin-host -:input: export SNAPCRAFT_ADMIN_MACAROON=$(cat /home//snap/store-admin/common/export/storeID.macaroon) -:input: export SNAPCRAFT_STORE_AUTH=onprem -:input: export STORE_DASHBOARD_URL="https://example.store/publishergw" -:input: export STORE_UPLOAD_URL="https://example.store" +export SNAPCRAFT_ADMIN_MACAROON=$(cat /home//snap/store-admin/common/export/storeID.macaroon) +export SNAPCRAFT_STORE_AUTH=onprem +export STORE_DASHBOARD_URL="https://example.store/publishergw" +export STORE_UPLOAD_URL="https://example.store" ``` -Next, login to the Enteprise Store store as the publisher and export the credentials to a file: +Next, login to the Enterprise Store store as the publisher and export the credentials to a file: ```{terminal} :user: user :host: admin-host -:input: snapcraft export-login + +snapcraft export-login ``` Set the credential produced by `export-login` as `SNAPCRAFT_STORE_CREDENTIALS` environment variable: @@ -153,13 +165,14 @@ Set the credential produced by `export-login` as `SNAPCRAFT_STORE_CREDENTIALS` e ```{terminal} :user: user :host: admin-host -:input: export SNAPCRAFT_STORE_CREDENTIALS="$(cat )" + +export SNAPCRAFT_STORE_CREDENTIALS="$(cat )" ``` -You can now `snapcraft upload` and `snapcraft release` to the Enteprise Store. +You can now `snapcraft upload` and `snapcraft release` to the Enterprise Store. ```{note} -Commands supported by Enteprise Stores set up in this way are: +Commands supported by Enterprise Stores set up in this way are: * `snapcraft status ` * `snapcraft list-revisions ` diff --git a/docs/how-to/register.md b/docs/how-to/register.md index eb67db4..467ce32 100644 --- a/docs/how-to/register.md +++ b/docs/how-to/register.md @@ -1,6 +1,7 @@ --- title: Register table_of_contents: true +description: Register your Enterprise Store with Ubuntu SSO and configure it for online or offline operation. --- # Registration @@ -29,6 +30,7 @@ This will show the registration status of your proxy, as well as local status information of this store's host. Example: + ```zsh $ enterprise-store status @@ -64,7 +66,6 @@ pair can be viewed using: sudo enterprise-store config proxy.key.public proxy.key.private - ## Next step Configure the proxy to [serve HTTPS](security.md) traffic if `--https` diff --git a/docs/how-to/security.md b/docs/how-to/security.md index fa23d54..4cf34a5 100644 --- a/docs/how-to/security.md +++ b/docs/how-to/security.md @@ -1,6 +1,13 @@ +--- +title: Enhance Enterprise Store's security +table_of_contents: true +description: Configure HTTPS/TLS termination and implement security enhancements for your Enterprise Store deployment. +--- + # Enhance Enterprise Store's security ## Enable HTTPS + TLS termination is not enabled by default. This means that the Enterprise Store listens only on port 80 for plain text HTTP traffic after installation. If the Enterprise Store was [registered](register.md) with an `--https` option, the @@ -84,7 +91,6 @@ root is to configure the certificate in question using `snapd` itself: The above method works both on classic systems as well as Ubuntu Core. - ### Next step Once you've confirmed that your Enterprise Store is running and accepting HTTPS @@ -97,7 +103,6 @@ At any time, you can use: to check the status of your Enterprise Store. - ## Restrict network access This section lists necessary network traffic requirements for the Enterprise @@ -110,18 +115,20 @@ configured to use HTTPS and port 80 when not. To restrict access to the HTTPS port on the host machine itself on Ubuntu: -``` -$ sudo ufw default deny incoming -$ sudo ufw allow in https -$ sudo ufw allow in ssh -$ sudo ufw enable +```{terminal} +:copy: + +sudo ufw default deny incoming +sudo ufw allow in https +sudo ufw allow in ssh +sudo ufw enable ``` You might want to restrict ingress traffic to particular networks if needed. Example: -``` -$ sudo ufw allow in from 10.126.46.0/24 to any port https +```{terminal} +sudo ufw allow in from 10.126.46.0/24 to any port https ``` The above allows also incoming traffic to port 22 (ssh) for general host @@ -133,24 +140,25 @@ setup of choice are sufficient for the Enterprise Store itself. The Enterprise Store requires network access to the PostgreSQL database. To find out the address of currently configured database: -``` -$ enterprise-store config | grep db\.connection +```{terminal} +enterprise-store config | grep db\.connection + proxy.db.connection: postgresql://snapproxy:@10.126.46.135:5432/snapproxy ``` The result should contain the location of the database. In the example above it's 10.126.46.135. We can add a firewall rule to allow outgoing traffic to the database: -``` -$ sudo ufw allow out from any to 10.126.46.135 port 5432 +```{terminal} +sudo ufw allow out from any to 10.126.46.135 port 5432 ``` Note that by default, outgoing traffic is allowed by ``ufw``. We can deny any other outgoing traffic from this host: -``` -$ sudo ufw default deny outgoing +```{terminal} +sudo ufw default deny outgoing ``` But this will impact its ability to: @@ -166,9 +174,9 @@ Note that it's possible to configure the Enterprise Store to use an HTTP forwarding proxy (like Squid) to proxy any outgoing HTTPS traffic. Example: -``` -$ sudo enterprise-store config proxy.https.proxy=http://squid.internal:3128 -$ sudo ufw allow out from any to 3128 +```{terminal} +sudo enterprise-store config proxy.https.proxy=http://squid.internal:3128 +sudo ufw allow out from any to 3128 ``` By configuring Squid to only allow traffic to the diff --git a/docs/how-to/synchronise-with-a-dedicated-store.md b/docs/how-to/synchronise-with-a-dedicated-store.md index 77cc5ab..d641859 100644 --- a/docs/how-to/synchronise-with-a-dedicated-store.md +++ b/docs/how-to/synchronise-with-a-dedicated-store.md @@ -1,3 +1,9 @@ +--- +title: Synchronise with a Dedicated Snap Store +table_of_contents: true +description: Export and import Dedicated Snap Store credentials and snaps to your Enterprise Store using the store-admin snap. +--- + # Synchronise with a Dedicated Snap Store ```{warning} @@ -9,7 +15,8 @@ Dedicated Snap Store credentials and snaps can be exported using the `store-admi ```{terminal} :user: user :host: admin-box -:input: store-admin export store --help + +store-admin export store --help Usage: store-admin export store [OPTIONS] STORE_ID @@ -41,7 +48,8 @@ store: ```{terminal} :user: user :host: admin-box -:input: store-admin export store --arch=amd64 --arch=arm64 --channel=stable --channel=edge StoreID + +store-admin export store --arch=amd64 --arch=arm64 --channel=stable --channel=edge StoreID ``` This will export any snaps available in the store, with their channel maps, metadata, @@ -52,7 +60,8 @@ Once you move the exported store bundle to your store, you can run the import co ```{terminal} :user: user :host: enterprise-store-host -:input: sudo enterprise-store push-store /var/snap/enterprise-store/common/snaps-to-push/store-export-StoreID.tar.gz + +sudo enterprise-store push-store /var/snap/enterprise-store/common/snaps-to-push/store-export-StoreID.tar.gz Uploaded snap and assertions for core revision 13250 Uploaded snap and assertions for core revision 13253 diff --git a/docs/how-to/trouble.md b/docs/how-to/trouble.md index e726952..91b5bd9 100644 --- a/docs/how-to/trouble.md +++ b/docs/how-to/trouble.md @@ -1,6 +1,7 @@ --- title: Troubleshooting the Enterprise Store table_of_contents: true +description: Diagnose and resolve common Enterprise Store issues by checking connections, reviewing logs, and verifying configuration. --- # Troubleshooting @@ -57,7 +58,6 @@ This documentation is shipped with the snap, and available at: Please file bugs against [this project on Launchpad](https://bugs.launchpad.net/snapstore) - ### Known issues 1. The `snap download` command doesn't do the download of the snap through diff --git a/docs/index.rst b/docs/index.rst index c11cb16..f7d076d 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,3 +1,6 @@ +.. meta:: + :description: Documentation for Canonical's Enterprise Store, which provides an on-premise edge proxy to the Snap Store or a feature limited fully offline Snap Store. + Enterprise Store documentation ============================== @@ -19,21 +22,36 @@ With the Enterprise Store, snaps are as easy-to-use as ever, and administrators have much greater control over exactly what revisions are installed on each connected system. -.. grid:: 1 - - .. grid-item-card:: :doc:`Getting started ` - - A tutorial walking through setup and usage of the store. - - .. grid-item-card:: :doc:`How-to guides ` - - Step-by-step guides covering key operations and common tasks. - - .. grid-item-card:: :doc:`Reference ` - - Technical information - specifications, APIs, architecture. +In this documentation +--------------------- -For **security** information, see how to :doc:`how-to/security`. +.. list-table:: + :widths: 25 75 + :header-rows: 0 + + * - **Getting started** + - :doc:`tutorial/get-started` • :doc:`how-to/devices` • :doc:`how-to/overrides` • :doc:`reference/configuration` + * - **Air-gapped deployments** + - :doc:`tutorial/air-gapped-deployment` • :doc:`how-to/airgap` + * - **Charm support** + - :doc:`how-to/charmhub-proxy` • :doc:`how-to/airgap-charmhub` + * - **Dedicated Snap Store support** + - :doc:`how-to/integrate-a-dedicated-snap-store` • :doc:`how-to/integrate-a-dedicated-snap-store` • :doc:`how-to/publish-snaps` • :doc:`how-to/build-images` + * - **API documentation** + - :doc:`reference/api-authentication` • :doc:`reference/api-overrides` + * - **Security** + - :doc:`how-to/security` • :doc:`reference/cryptography` + +How this documentation is organised +----------------------------------- + +This documentation uses the `Diátaxis documentation structure `_. + +* :doc:`tutorial` takes you step-by-step through the setup and operation of the store in both supported modes. +* :doc:`how-to` guides assume you have basic familiarity with Product. They provide focused instructions for specific tasks. +* :doc:`reference` provides detailed information on APIs, configuration, and cryptographic protocols. + +.. * Explanation includes topic overviews, background and context and detailed discussion. Project and community --------------------- diff --git a/docs/reference.md b/docs/reference.md index f0efc46..d102fe8 100644 --- a/docs/reference.md +++ b/docs/reference.md @@ -1,5 +1,6 @@ --- -title: Enterprise Store Reference +title: Reference documentation +description: Technical reference for Enterprise Store configuration, APIs, features, and cryptographic implementations. --- # Enterprise Store Reference diff --git a/docs/reference/api-authentication.md b/docs/reference/api-authentication.md index 40b79b6..58acc32 100644 --- a/docs/reference/api-authentication.md +++ b/docs/reference/api-authentication.md @@ -1,6 +1,7 @@ --- title: API authentication table_of_contents: true +description: A summary of the Enterprise Store API authentication mechanism using Ubuntu SSO and macaroons. --- # API authentication @@ -154,6 +155,7 @@ SSO periodically. To do so, simply: Request: + ```http POST /api/v2/tokens/refresh HTTP/1.1 Host: login.ubuntu.com diff --git a/docs/reference/api-overrides.md b/docs/reference/api-overrides.md index cd09f9b..6c6818c 100644 --- a/docs/reference/api-overrides.md +++ b/docs/reference/api-overrides.md @@ -1,6 +1,7 @@ --- title: Overrides API table_of_contents: true +description: API specifications for listing and setting snap revision overrides in the Enterprise Store. --- # Overrides API @@ -22,8 +23,8 @@ GET /v2/metadata/overrides/snap_a HTTP/1.1 Host: Accept: application/json X-Ubuntu-Series: 16 - ``` + Note the `X-Ubuntu-Series` header. Response: diff --git a/docs/reference/configuration.rst b/docs/reference/configuration.rst index c4a0b10..d12edce 100644 --- a/docs/reference/configuration.rst +++ b/docs/reference/configuration.rst @@ -1,3 +1,6 @@ +.. meta:: + :description: A complete reference of Enterprise Store configuration keys and their default values. + Store configuration ******************* diff --git a/docs/reference/cryptography.md b/docs/reference/cryptography.md index a91221a..221410e 100644 --- a/docs/reference/cryptography.md +++ b/docs/reference/cryptography.md @@ -1,3 +1,9 @@ +--- +title: Cryptography +table_of_contents: true +description: Overview of cryptographic technologies used in the Enterprise Store for signing, hashing, and authentication. +--- + # Cryptography Various Cryptographic technologies are used to enable secure Enterprise Store operation. diff --git a/docs/reference/feature-list.md b/docs/reference/feature-list.md index a8a8573..83f4c89 100644 --- a/docs/reference/feature-list.md +++ b/docs/reference/feature-list.md @@ -1,5 +1,6 @@ --- title: Feature list +description: A list of Enterprise Store features including network control, caching, overrides, and management options. --- # Feature list diff --git a/docs/requirements.txt b/docs/requirements.txt index 77ec006..fb426c4 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,5 +1,28 @@ -canonical-sphinx[full] packaging sphinxcontrib-svg2pdfconverter[CairoSVG] sphinx-last-updated-by-git sphinx-sitemap +sphinx-autobuild +# Vale dependencies +sphinx-ubuntu-images>=0.1.0 +canonical-sphinx>=0.5.1 +sphinx-terminal>=1.0.2 +sphinx-related-links>=0.1.1 +# Extensions previously auto-loaded by canonical-sphinx +sphinxcontrib-jquery +# Canonical theme (still needed for Furo theme and custom templates) +sphinx-filtered-toctree>=0.1.0 +# Other dependencies +sphinx-reredirects +rst2html +vale +# Extra extensions, previously bundled as canonical-sphinx-extensions +sphinx-roles>=0.1.0 +myst-parser +sphinx-contributor-listing>=0.1.0 +sphinx-design +sphinxext-opengraph +sphinx-config-options>=0.1.0 +sphinx-notfound-page +sphinx-youtube-links>=0.1.0 +sphinx-tabs \ No newline at end of file diff --git a/docs/tutorial.md b/docs/tutorial.md index dc7f5b9..e5ca648 100644 --- a/docs/tutorial.md +++ b/docs/tutorial.md @@ -1,3 +1,8 @@ +--- +title: Tutorials +description: Hands-on tutorials for setting up and using the Enterprise Store in online and air-gapped environments. +--- + # Enterprise Store Tutorials The Enterprise Store tutorials are your first port of call for learning how to diff --git a/docs/tutorial/air-gapped-deployment.md b/docs/tutorial/air-gapped-deployment.md index 7cdc601..185e054 100644 --- a/docs/tutorial/air-gapped-deployment.md +++ b/docs/tutorial/air-gapped-deployment.md @@ -1,3 +1,9 @@ +--- +title: Getting started with an air-gapped store +table_of_contents: true +description: Step-by-step tutorial for setting up and testing the Enterprise Store in an offline, air-gapped environment. +--- + # Getting started with an air-gapped store In this tutorial, we will set up and test the Enterprise Store in a functionally @@ -39,7 +45,8 @@ Ensure LXD is installed on your **host machine**: :user: user :host: host :copy: -:input: sudo snap install lxd + +sudo snap install lxd ``` Ensure LXD is set up properly: @@ -48,7 +55,8 @@ Ensure LXD is set up properly: :user: user :host: host :copy: -:input: sudo lxd init --minimal + +sudo lxd init --minimal ``` Launch two containers, **test-offline-store**, and **test-offline-device**: @@ -57,9 +65,9 @@ Launch two containers, **test-offline-store**, and **test-offline-device**: :user: user :host: host :copy: -:input: sudo lxc launch ubuntu:22.04 test-offline-store -:input: sudo lxc launch ubuntu:22.04 test-offline-device +sudo lxc launch ubuntu:22.04 test-offline-store +sudo lxc launch ubuntu:22.04 test-offline-device ``` @@ -75,17 +83,11 @@ We can open a container by running bash. For example, to open the CLI of the **t :user: user :host: host :copy: -:input: sudo lxc exec test-offline-store -- bash -``` - -This will simulate SSH access to the container, and show you as root within the container: -```{terminal} -:user: root -:host: test-offline-store -:copy: -:input: +sudo lxc exec test-offline-store -- bash ``` + +This will simulate SSH access to the container, and show you as root within the container. ```` ## Download required software @@ -104,14 +106,23 @@ On the **test-offline-store**, download PostgreSQL: :user: root :host: test-offline-store :copy: -:input: snap download postgresql + +snap download postgresql + Fetching snap "postgresql" Fetching assertions for "postgresql" Install the snap with: snap ack postgresql_62.assert snap install postgresql_62.snap +``` + +```{terminal} +:user: root +:host: test-offline-store +:copy: + +snap download core24 -:input: snap download core24 Fetching snap "core24" Fetching assertions for "core24" Install the snap with: @@ -127,7 +138,8 @@ On the **test-offline-store**, install the `store-admin` snap: :user: root :host: test-offline-store :copy: -:input: sudo snap install store-admin + +sudo snap install store-admin ``` Then, register an offline store with the domain to be used offline: @@ -136,7 +148,8 @@ Then, register an offline store with the domain to be used offline: :user: root :host: test-offline-store :copy: -:input: store-admin register --offline http://test-offline-store + +store-admin register --offline http://test-offline-store ``` ```{note} @@ -152,7 +165,8 @@ On your **test-offline-store**, use `store-admin` to export the `helix` and `hto :host: host :dir: enterprise-store-tutorial :copy: -:input: store-admin export snaps helix htop --channel=stable --arch=amd64 --arch=arm64 --export-dir . + +store-admin export snaps helix htop --channel=stable --arch=amd64 --arch=arm64 --export-dir . ``` ```{note} @@ -175,9 +189,9 @@ From your **host machine**, use `iptables` to isolate our LXC containers: :host: host :dir: enterprise-store-tutorial :copy: -:input: sudo iptables -I FORWARD -i lxdbr0 -j REJECT -:input: sudo iptables -I FORWARD -i lxdbr0 -o lxdbr0 -j ACCEPT +sudo iptables -I FORWARD -i lxdbr0 -j REJECT +sudo iptables -I FORWARD -i lxdbr0 -o lxdbr0 -j ACCEPT ``` ```{note} @@ -196,7 +210,8 @@ Test access to `api.snapcraft.io` on your host machine: :host: host :dir: enterprise-store-tutorial :copy: -:input: curl api.snapcraft.io + +curl api.snapcraft.io snapcraft.io store API service - Copyright 2018-2022 Canonical. ``` @@ -207,7 +222,8 @@ Compare it to the same command used in **test-offline-store**: :user: root :host: test-offline-store :copy: -:input: curl api.snapcraft.io + +curl api.snapcraft.io curl: (7) Failed to connect to api.snapcraft.io port 80 after 4103 ms: Network is unreachable ``` @@ -220,9 +236,9 @@ In **test-offine-store**, install PostgreSQL: :user: root :host: test-offline-store :copy: -:input: snap ack /root/postgresql_62.assert -:input: snap install /root/postgresql_62.snap +snap ack /root/postgresql_62.assert +snap install /root/postgresql_62.snap ``` In **test-offline-store**, unzip the store: @@ -231,9 +247,9 @@ In **test-offline-store**, unzip the store: :user: root :host: test-offline-store :copy: -:input: tar -xvzf offline-snap-store.tar.gz -:input: cd offline-snap-store +tar -xvzf offline-snap-store.tar.gz +cd offline-snap-store ``` Then use the install script, and verify the installation: @@ -243,9 +259,9 @@ Then use the install script, and verify the installation: :host: test-offline-store :dir: /offline-snap-store :copy: -:input: ./install.sh -:input: enterprise-store status +./install.sh +enterprise-store status Store URL: http://test-offline-store Store DB: not configured (check the installation guide at http://localhost/docs/ or visit https://docs.ubuntu.com/enterprise-store/en/install#database) @@ -257,7 +273,8 @@ Next, configure PostgreSQL for use with the Enterprise Store: :user: root :host: test-offline-store :copy: -:input: nano ~/proxydb.sql + +nano ~/proxydb.sql ``` Copy the following into `~/proxydb.sql`, and save the file: @@ -273,10 +290,10 @@ CREATE EXTENSION "btree_gist"; :user: root :host: test-offline-store :copy: -:input: cp ~/proxydb.sql /var/snap/postgresql/common/ -:input: snap run postgresql.psql -U postgres -f /var/snap/postgresql/common/proxydb.sql -:input: enterprise-store config proxy.db.connection="postgresql://snapproxy-user@localhost:5432/snapproxy-db?sslmode=disable" +cp ~/proxydb.sql /var/snap/postgresql/common/ +snap run postgresql.psql -U postgres -f /var/snap/postgresql/common/proxydb.sql +enterprise-store config proxy.db.connection="postgresql://snapproxy-user@localhost:5432/snapproxy-db?sslmode=disable" ``` When prompted, enter the password set in `~/proxydb.sql`, `snapproxy-password`. @@ -287,7 +304,8 @@ Check the status of the store again: :user: root :host: test-offline-store :copy: -:input: enterprise-store status + +enterprise-store status Store URL: http://test-offline-store Store DB: ok @@ -307,7 +325,8 @@ On the **test-offline-store**, push the helix snap to the Enterprise Store: :user: root :host: test-offline-device :copy: -:input: enterprise-store push-snap /root/helix-*.tar.gz + +enterprise-store push-snap /root/helix-*.tar.gz [snap helix] Uploaded snap blob and assertions for helix revision 120 [snap helix] Uploaded snap blob and assertions for helix revision 121 @@ -320,7 +339,8 @@ Check that the snaps have been successfully pushed to the store: :user: root :host: test-offline-device :copy: -:input: enterprise-store list-pushed-snaps + +enterprise-store list-pushed-snaps Name Stores helix ubuntu @@ -338,7 +358,8 @@ Ensure **test-offline-device** cannot access any online services: :user: root :host: test-offline-device :copy: -:input: curl api.snapcraft.io + +curl api.snapcraft.io curl: (7) Failed to connect to api.snapcraft.io ... ``` @@ -349,9 +370,10 @@ Configure **test-offline-device** to use the air-gapped Enterprise Store: :user: root :host: test-offline-device :copy: -:input: curl -sL http://test-offline-store/v2/auth/store/assertions | snap ack /dev/stdin -:input: snap known store +curl -sL http://test-offline-store/v2/auth/store/assertions | snap ack /dev/stdin +snap known store + type: store authority-id: canonical store: ndOhjHBJfSf386KSGV0AH6oApdTuwGTy @@ -359,9 +381,14 @@ operator-id: 2SZDATTNFeUoIOOrDiKgDpjMFv3YAR1M timestamp: 2025-09-05T01:52:05.689607Z url: http://test-offline-store -... +``` + +```{terminal} +:user: root +:host: test-offline-device +:copy: -:input: snap set core proxy.store=$(snap known store | awk '/store:/{print $2}') +snap set core proxy.store=$(snap known store | awk '/store:/{print $2}') ``` Now our device is configured to use the air-gapped Enterprise Store. @@ -376,7 +403,8 @@ Use **test-offline-device** to query the snaps, starting with `helix`: :user: root :host: test-offline-device :copy: -:input: snap info helix + +snap info helix name: helix summary: Helix is a modal text editor inspired by Vim and Kakoune @@ -399,7 +427,8 @@ Check if `htop` is available: :user: root :host: test-offline-device :copy: -:input: snap info htop + +snap info htop error: no snap found for "htop" ``` @@ -414,7 +443,8 @@ Finally, install the `helix` snap to verify functionality: :user: root :host: test-offline-device :copy: -:input: snap install helix --classic + +snap install helix --classic helix 25.07.1 from Lauren Brock installed ``` @@ -431,9 +461,9 @@ On your **host machine**, delete the firewall rules we created for offline testi :host: host :dir: enterprise-store-tutorial :copy: -:input: sudo iptables -D FORWARD -i lxdbr0 -j REJECT -:input: sudo iptables -D FORWARD -i lxdbr0 -o lxdbr0 -j ACCEPT +sudo iptables -D FORWARD -i lxdbr0 -j REJECT +sudo iptables -D FORWARD -i lxdbr0 -o lxdbr0 -j ACCEPT ``` And then delete the containers we created for this tutorial: @@ -442,9 +472,9 @@ And then delete the containers we created for this tutorial: :user: user :host: host :copy: -:input: sudo lxc delete test-offline-device --force -:input: sudo lxc delete test-offline-store --force +sudo lxc delete test-offline-device --force +sudo lxc delete test-offline-store --force ``` This should return your system to the state it was in before this tutorial. diff --git a/docs/tutorial/get-started.md b/docs/tutorial/get-started.md index 60e3e92..40dfd38 100644 --- a/docs/tutorial/get-started.md +++ b/docs/tutorial/get-started.md @@ -1,3 +1,9 @@ +--- +title: Getting started with the store +table_of_contents: true +description: A step-by-step tutorial for setting up and testing the Enterprise Store in an online environment with revision control. +--- + # Getting started with the store In this tutorial, we will set up and test the Enterprise Store in an online @@ -38,7 +44,8 @@ Ensure LXD is installed on your **host machine**: :user: user :host: host :copy: -:input: sudo snap install lxd + +sudo snap install lxd ``` Ensure LXD is set up properly: @@ -47,7 +54,8 @@ Ensure LXD is set up properly: :user: user :host: host :copy: -:input: sudo lxd init --minimal + +sudo lxd init --minimal ``` Launch two containers, **test-store**, and **test-device**: @@ -56,9 +64,9 @@ Launch two containers, **test-store**, and **test-device**: :user: user :host: host :copy: -:input: sudo lxc launch ubuntu:22.04 test-store -:input: sudo lxc launch ubuntu:22.04 test-device +sudo lxc launch ubuntu:22.04 test-store +sudo lxc launch ubuntu:22.04 test-device ``` @@ -74,17 +82,11 @@ We can open a container by running bash. For example, to open the CLI of the **t :user: user :host: host :copy: -:input: sudo lxc exec test-store -- bash -``` - -This will simulate SSH access to the container, and show you as root within the container: -```{terminal} -:user: root -:host: test-store -:copy: -:input: +sudo lxc exec test-store -- bash ``` + +This will simulate SSH access to the container, and show you as root within the container. ```` ## Enterprise Store installation @@ -95,9 +97,9 @@ Within the **test-store** container, install the Enterprise Store snap and verif :user: root :host: test-store :copy: -:input: snap install enterprise-store -:input: snap list enterprise-store +snap install enterprise-store +snap list enterprise-store ``` Next, configure the domain for Enterprise Store: @@ -106,11 +108,20 @@ Next, configure the domain for Enterprise Store: :user: root :host: test-store :copy: -:input: hostname + +hostname test-store +``` + + +```{terminal} +:user: root +:host: test-store +:copy: + +enterprise-store config proxy.domain="test-store" -:input: enterprise-store config proxy.domain="test-store" proxy.domain: test-store ``` @@ -126,7 +137,8 @@ Install PostgreSQL: :user: root :host: test-store :copy: -:input: snap install postgresql + +snap install postgresql ``` Configure database for use with the Enterprise Store: @@ -135,7 +147,8 @@ Configure database for use with the Enterprise Store: :user: root :host: test-store :copy: -:input: nano proxydb.sql + +nano proxydb.sql ``` Paste the contents of the following into proxydb.sql. @@ -156,9 +169,9 @@ Run configure and start the postgres instance: :host: test-store :copy: :scroll: -:input: cp ~/proxydb.sql /var/snap/postgresql/common/ -:input: snap run postgresql.psql -U postgres -f /var/snap/postgresql/common/proxydb.sql +cp ~/proxydb.sql /var/snap/postgresql/common/ +snap run postgresql.psql -U postgres -f /var/snap/postgresql/common/proxydb.sql ``` Configure the Enterprise Store to use the database. @@ -168,7 +181,8 @@ Configure the Enterprise Store to use the database. :host: test-store :copy: :scroll: -:input: enterprise-store config proxy.db.connection="postgresql://snapproxy-user@localhost:5432/snapproxy-db?sslmode=disable" + +enterprise-store config proxy.db.connection="postgresql://snapproxy-user@localhost:5432/snapproxy-db?sslmode=disable" Authentication error with user snapproxy-user. @@ -184,18 +198,20 @@ Enter `snapproxy-password`, then check that the postgres instance has started su :host: test-store :copy: :scroll: -:input: snap services postgresql -:input: snap run postgresql.psql -U postgres -d snapproxy-db +snap services postgresql +snap run postgresql.psql -U postgres -d snapproxy-db ``` This will open the postgres database. Return to the store CLI with `quit`: + ```{terminal} :user: snapproxy-db :host: :copy: :scroll: -:input: quit + +quit ``` Finally, check the store's connections: @@ -204,7 +220,8 @@ Finally, check the store's connections: :user: root :host: test-store :copy: -:input: enterprise-store check-connections + +enterprise-store check-connections http: https://dashboard.snapcraft.io: OK http: https://login.ubuntu.com: OK @@ -223,7 +240,8 @@ Within the **test-store** container, register your Enterprise Store: :user: root :host: test-store :copy: -:input: sudo enterprise-store register + +sudo enterprise-store register ``` ```{note} @@ -236,7 +254,8 @@ Use the `status` command to retrieve the ID of your store: :user: root :host: test-store :copy: -:input: enterprise-store status + +enterprise-store status Store URL: http://test-store Store DB: ok @@ -256,7 +275,8 @@ From within the **test-device** container, connect to your Enterprise Store and :user: root :host: test-device :copy: -:input: curl -sL http://test-store/v2/auth/store/assertions | sudo snap ack /dev/stdin + +curl -sL http://test-store/v2/auth/store/assertions | sudo snap ack /dev/stdin ``` Verify the assertion on your device: @@ -265,7 +285,8 @@ Verify the assertion on your device: :user: root :host: test-device :copy: -:input: snap known store + +snap known store type: store authority-id: canonical @@ -284,7 +305,8 @@ Configure **test-device** to use the store: :user: root :host: test-device :copy: -:input: snap set core proxy.store=$(snap known store | awk '/store:/{print $2}') + +snap set core proxy.store=$(snap known store | awk '/store:/{print $2}') ``` Check the store is configured: @@ -293,7 +315,8 @@ Check the store is configured: :user: root :host: test-device :copy: -:input: snap get core proxy.store + +snap get core proxy.store ``` @@ -312,7 +335,8 @@ Within the **test-device**, query the available jq version: :user: root :host: test-device :copy: -:input: snap info jq + +snap info jq ... @@ -332,7 +356,8 @@ Next, within the **test-store**, add an override for the `jq` snap: :user: root :host: test-store :copy: -:input: enterprise-store override jq stable=11 + +enterprise-store override jq stable=11 jq stable amd64 11 ``` @@ -343,7 +368,8 @@ Ensure the override is set properly: :user: root :host: test-store :copy: -:input: enterprise-store list-overrides jq + +enterprise-store list-overrides jq jq stable amd64 11 (upstream 6) ``` @@ -354,7 +380,8 @@ From the **test-device**, query the `jq` snap: :user: root :host: test-device :copy: -:input: snap info jq + +snap info jq ... @@ -378,7 +405,8 @@ Using **test-device**, try downloading the `jq` snap: :user: root :host: test-device :copy: -:input: snap install jq + +snap install jq jq 1.6 from Michael Vigt (mvo) installed ``` @@ -398,7 +426,8 @@ First, from within the **test-store**, delete the override: :user: root :host: test-store :copy: -:input: enterprise-store delete-override jq stable + +enterprise-store delete-override jq stable ``` From the test-device, query the jq snap info again: @@ -407,7 +436,8 @@ From the test-device, query the jq snap info again: :user: root :host: test-device :copy: -:input: snap info jq + +snap info jq ... @@ -425,7 +455,8 @@ Now, within the **test-device**, refresh the snap: :user: root :host: test-device :copy: -:input: sudo snap refresh jq + +sudo snap refresh jq jq 1.5+dfsg-1 from Michael Vigt (mvo) refreshed ``` @@ -436,7 +467,8 @@ Finally, from within the **test-device**, we can disconnect the device from the :user: root :host: test-device :copy: -:input: snap set core proxy.store='' + +snap set core proxy.store='' ``` (cleanup)= @@ -450,9 +482,9 @@ Make sure you remove them: :host: host :dir: :copy: -:input: sudo lxc delete test-device --force -:input: sudo lxc delete test-store --force +sudo lxc delete test-device --force +sudo lxc delete test-store --force ``` This should return your system to the state it was in before this tutorial. From f1a0450e887292387d3d7516afd93fbad2bfaa7b Mon Sep 17 00:00:00 2001 From: secondskoll Date: Mon, 8 Dec 2025 15:36:08 +1000 Subject: [PATCH 2/7] fix: terminal directive --- docs/tutorial/air-gapped-deployment.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/tutorial/air-gapped-deployment.md b/docs/tutorial/air-gapped-deployment.md index d698453..be2b3e8 100644 --- a/docs/tutorial/air-gapped-deployment.md +++ b/docs/tutorial/air-gapped-deployment.md @@ -164,7 +164,8 @@ Make a list of snaps we want to have in our store. :user: root :host: test-offline-store :copy: -:input: nano ~/packages.yaml + +nano ~/packages.yaml ``` Copy the following into `~/packages.yaml`, and save the file: From 0b018c15a638823650af280e753a1bd24305ad34 Mon Sep 17 00:00:00 2001 From: secondskoll Date: Mon, 8 Dec 2025 15:37:43 +1000 Subject: [PATCH 3/7] fix: page title --- docs/tutorial/get-started.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/tutorial/get-started.md b/docs/tutorial/get-started.md index a080b76..db6a123 100644 --- a/docs/tutorial/get-started.md +++ b/docs/tutorial/get-started.md @@ -1,10 +1,10 @@ --- -title: Getting started with the store +title: Getting started with the Enterprise Store table_of_contents: true description: A step-by-step tutorial for setting up and testing the Enterprise Store in an online environment with revision control. --- -# Getting started with the store +# Getting started with the Enterprise Store In this tutorial, we will set up and test the Enterprise Store in an online environment. We will cover how to set the store up, how From 5059c31af132346fc2f47a6351c6ccb65edcbe1f Mon Sep 17 00:00:00 2001 From: secondskoll Date: Mon, 8 Dec 2025 15:39:30 +1000 Subject: [PATCH 4/7] fix: feedback --- docs/how-to/airgap-charmhub.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/how-to/airgap-charmhub.md b/docs/how-to/airgap-charmhub.md index 175e0f7..b7c1eb8 100644 --- a/docs/how-to/airgap-charmhub.md +++ b/docs/how-to/airgap-charmhub.md @@ -6,7 +6,7 @@ description: Distribute charms and charm bundles in air-gapped environments usin # Offline Charmhub (air-gapped mode) -Version 2.30 (and above) of the Enterprise Store snap can also distribute charms and charm bundles in addition to snaps. Currently, this functionality is only available in offline mode, enabling the proxy to act as a local (on-premise) Charmhub. +The Enterprise Store snap can also distribute charms and charm bundles in addition to snaps. Currently, this functionality is only available in offline mode, enabling the proxy to act as a local (on-premise) Charmhub. This mode is particularly useful in network-restricted environments where external internet traffic is either not allowed or not possible. From 8403693bb2e1ed8131fad328d0edcd63df93ddf9 Mon Sep 17 00:00:00 2001 From: secondskoll Date: Mon, 8 Dec 2025 15:47:14 +1000 Subject: [PATCH 5/7] feat: customer feedback --- docs/tutorial/air-gapped-deployment.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/docs/tutorial/air-gapped-deployment.md b/docs/tutorial/air-gapped-deployment.md index be2b3e8..8c7a153 100644 --- a/docs/tutorial/air-gapped-deployment.md +++ b/docs/tutorial/air-gapped-deployment.md @@ -218,6 +218,29 @@ sudo iptables -I FORWARD -i lxdbr0 -j REJECT sudo iptables -I FORWARD -i lxdbr0 -o lxdbr0 -j ACCEPT ``` +````{warning} +`lxdbr0` is the default network bridge for LXD. It may not be the correct network bridge for you. Please check the network bridge used by the containers deployed for this tutorial: + +```{terminal} +:user: user +:host: host +:copy: + +lxc config show test-offline-store --expanded + +... + +devices: + eth0: + name: eth0 + network: lxdbr1 + type: nic +``` + +In this case, `lxdbr1` should be used rather than `lxdbr0`. + +```` + ```{note} This adds these two settings to the top of the `FORWARD` chain of `iptables`, which means they are processed first. From 561d4005110c3c54e2010edc38f31580217e149a Mon Sep 17 00:00:00 2001 From: secondskoll Date: Tue, 16 Dec 2025 11:26:29 +1000 Subject: [PATCH 6/7] fix: remove outdated feature-list page --- docs/conf.py | 6 +++-- docs/explanation.md | 4 +-- docs/how-to.md | 2 +- docs/reference.md | 4 +-- docs/reference/feature-list.md | 37 -------------------------- docs/tutorial.md | 2 +- docs/tutorial/air-gapped-deployment.md | 2 +- 7 files changed, 10 insertions(+), 47 deletions(-) delete mode 100644 docs/reference/feature-list.md diff --git a/docs/conf.py b/docs/conf.py index 38f32ff..e03e6ea 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -70,7 +70,7 @@ # NOTE: The Open Graph Protocol (OGP) enhances page display in a social graph # and is used by social media platforms; see https://ogp.me/ -ogp_site_url = "https://enterprise-store.readthedocs-hosted.com/" +ogp_site_url = "https://documentation.ubuntu.com/enterprise-store/" # Preview name of the documentation website @@ -206,7 +206,9 @@ # NOTE: If undefined, set to None, or empty, # the sphinx_reredirects extension will be disabled. -redirects = {} +redirects = { + "reference/feature-list/": "/", +} ########################### diff --git a/docs/explanation.md b/docs/explanation.md index cd3e1e4..46ac8e2 100644 --- a/docs/explanation.md +++ b/docs/explanation.md @@ -1,10 +1,10 @@ --- -title: Enterprise Store Explanation guides +title: Explanation guides orphan: true description: Conceptual guides explaining how the Enterprise Store works and how it can be configured and used. --- -# Enterprise Store Explanation guides +# Explanation guides Our explanatory and conceptual guides are written to provide a better understanding of how the Enterprise Store works and how it can be used and configured. They enable you diff --git a/docs/how-to.md b/docs/how-to.md index b3baafb..dbcdbec 100644 --- a/docs/how-to.md +++ b/docs/how-to.md @@ -3,7 +3,7 @@ title: How-to guides description: Step-by-step guides for installing, configuring, and operating the Enterprise Store in various deployment scenarios. --- -# Enterprise Store How-to guides +# How-to guides If you have a specific goal, but are already familiar with the Enterprise Store, our *How-to* guides have more in-depth detail than our tutorials and can be applied to diff --git a/docs/reference.md b/docs/reference.md index d102fe8..98b7cc6 100644 --- a/docs/reference.md +++ b/docs/reference.md @@ -3,7 +3,7 @@ title: Reference documentation description: Technical reference for Enterprise Store configuration, APIs, features, and cryptographic implementations. --- -# Enterprise Store Reference +# Reference documentation Our *Reference* section contains technical details (such as the Overrides API and authentication mechanism), and other supplementary reference materials. @@ -13,7 +13,6 @@ authentication mechanism), and other supplementary reference materials. | [Store Configuration](reference/configuration.rst) | A list of the store's configuration keys and default values | | [Overrides API](reference/api-overrides.md) | API specs for the Overrides API | | [API authentication](reference/api-authentication.md) | API authentication for store admins when using the Overrides API | -| [Feature list](reference/feature-list.md) | A summarised list of features of the Enterprise Store | | [Cryptography](reference/cryptography.md) | An outline of the usage of cryptographic technology | @@ -31,6 +30,5 @@ you to understand and adapt the steps to fit your specific requirements. reference/configuration reference/api-overrides reference/api-authentication - reference/feature-list reference/cryptography ``` \ No newline at end of file diff --git a/docs/reference/feature-list.md b/docs/reference/feature-list.md deleted file mode 100644 index 83f4c89..0000000 --- a/docs/reference/feature-list.md +++ /dev/null @@ -1,37 +0,0 @@ ---- -title: Feature list -description: A list of Enterprise Store features including network control, caching, overrides, and management options. ---- - -# Feature list - -* Network control - - * Provides a means to access the Snap Store for devices with restricted - network access - - * Enterprise Store can communicate with the Snap Store directly or through - an HTTPS forward proxy - -* Caching of downloaded snaps - -* [Overriding revisions](../how-to/overrides.md) of specific snaps for all connected - devices - -* Management options - - * `enterprise-store` CLI interface included with the - [Enterprise Store](https://snapcraft.io/enterprise-store) snap - - * Remote management using the - [Enterprise Store Client](https://snapcraft.io/snap-store-proxy-client) - or a [RESTful API](api-overrides.md). - -* [Offline mode](../how-to/airgap.md). - -```{note} -Unless it is deliberately set up as [offline](../how-to/airgap.md), an Enterprise Store needs to be online -and connected to the general [Snap Store](https://snapcraft.io/store). This -is a requirement, even though Enterprise Store caches downloaded snap files, -which substantially reduces internet traffic. -``` diff --git a/docs/tutorial.md b/docs/tutorial.md index e5ca648..011dbdc 100644 --- a/docs/tutorial.md +++ b/docs/tutorial.md @@ -3,7 +3,7 @@ title: Tutorials description: Hands-on tutorials for setting up and using the Enterprise Store in online and air-gapped environments. --- -# Enterprise Store Tutorials +# Tutorials The Enterprise Store tutorials are your first port of call for learning how to set up and use an Enterprise Store. They're designed to help you learn the diff --git a/docs/tutorial/air-gapped-deployment.md b/docs/tutorial/air-gapped-deployment.md index 8c7a153..2166ccd 100644 --- a/docs/tutorial/air-gapped-deployment.md +++ b/docs/tutorial/air-gapped-deployment.md @@ -1,7 +1,7 @@ --- title: Getting started with an air-gapped store table_of_contents: true -description: Step-by-step tutorial for setting up and testing the Enterprise Store in an offline, air-gapped environment. +description: A step-by-step tutorial for setting up and testing the Enterprise Store in an offline, air-gapped environment. --- # Getting started with an air-gapped store From b93cd76c7a1683dc108f4b28ee1cb8e0a2697754 Mon Sep 17 00:00:00 2001 From: secondskoll Date: Tue, 16 Dec 2025 11:44:52 +1000 Subject: [PATCH 7/7] fix: RTD canonical URL --- docs/conf.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index e03e6ea..116280c 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -175,17 +175,19 @@ # Base URL of RTD hosted project -html_baseurl = 'https://documentation.ubuntu.com/enterprise-store/' +html_baseurl = os.environ.get("READTHEDOCS_CANONICAL_URL", "/") # URL scheme. Add language and version scheme elements manually e.g. '{0}/{1}/{{link}}'.format(os.environ['READTHEDOCS_LANGUAGE'], os.environ['READTHEDOCS_VERSION']) # When configured with RTD variables, check for RTD environment so manual runs succeed: -if 'READTHEDOCS_VERSION' in os.environ: - version = os.environ["READTHEDOCS_VERSION"] - sitemap_url_scheme = '{version}{link}' -else: - sitemap_url_scheme = 'VERSION/{link}' +sitemap_url_scheme = '{link}' + +# Include `lastmod` dates in the sitemap: + +sitemap_show_lastmod = True + +# Exclude generated pages from the sitemap: sitemap_excludes = [ "genindex/",