import { useState } from 'react'; import './UsageInstructions.css'; // repoClass distinguishes the three ways a repository is consumed. remotes are // caching proxies, locals are real registries you also publish to, virtuals are // merged read-only indexes. type RepoClass = 'remote' | 'local' | 'virtual'; interface Snippet { title: string; language: string; code: string; note?: string; } // baseURL resolves the externally reachable origin of this artifactapi instance. // The UI is served on the same origin as the API (client BASE is ''), so // window.location.origin is the address a host would actually curl/pull against // — no hardcoded hostname, works in prod and in `npm run dev` behind a proxy. function baseURL(): string { if (typeof window !== 'undefined' && window.location?.origin) { return window.location.origin.replace(/\/$/, ''); } return 'https://artifactapi.k8s.syd1.au.unkin.net'; } // hostOnly is the bare host[:port] with no scheme, for docker/terraform source // addresses which are scheme-less. function hostOnly(): string { try { return new URL(baseURL()).host; } catch { return 'artifactapi.k8s.syd1.au.unkin.net'; } } // remoteProxyBase is where a remote (or virtual) repo's proxied artifacts live. function remoteProxyBase(cls: RepoClass, name: string): string { const seg = cls === 'virtual' ? 'virtual' : 'remote'; return `${baseURL()}/api/v1/${seg}/${name}`; } export function buildSnippets(packageType: string, repoClass: RepoClass, name: string): Snippet[] { const url = baseURL(); const host = hostOnly(); const proxy = remoteProxyBase(repoClass, name); const isLocal = repoClass === 'local'; switch (packageType) { case 'rpm': return [ { title: isLocal ? 'Add the yum repo (real yum repo, repodata auto-regenerated)' : 'Add the yum repo (caching proxy)', language: 'bash', code: `sudo tee /etc/yum.repos.d/${name}.repo >/dev/null <<'EOF' [${name}] name=${name} (artifactapi) baseurl=${isLocal ? `${url}/api/v2/remotes/${name}/files/` : `${proxy}/`} enabled=1 gpgcheck=0 repo_gpgcheck=0 EOF sudo dnf install `, note: isLocal ? 'gpgcheck=0: artifactapi serves the repo unsigned. If you sign your RPMs, import your key and set gpgcheck=1.' : 'gpgcheck=0 trusts upstream over the proxy. To verify package signatures, import the upstream GPG key and set gpgcheck=1.', }, ...(isLocal ? [ { title: 'Publish an RPM (repodata regenerates automatically)', language: 'bash', code: `curl -fsSL --upload-file ./my-package-1.0-1.el9.x86_64.rpm \\ ${url}/api/v2/remotes/${name}/files/my-package-1.0-1.el9.x86_64.rpm`, }, ] : []), ]; case 'pypi': return [ { title: 'Install a package (one-off)', language: 'bash', code: `pip install --index-url ${proxy}/simple/ `, }, { title: 'Configure pip persistently', language: 'bash', code: `mkdir -p ~/.config/pip cat > ~/.config/pip/pip.conf <<'EOF' [global] index-url = ${proxy}/simple/ EOF pip install `, }, ]; case 'npm': return [ { title: 'Point npm at this registry', language: 'bash', code: `npm config set registry ${proxy}/ npm install `, }, { title: 'Per-project (.npmrc)', language: 'bash', code: `echo 'registry=${proxy}/' >> .npmrc npm install`, }, ]; case 'docker': return [ { title: 'Pull an image', language: 'bash', code: `docker pull ${host}/${name}/:`, note: 'The first path segment after the host is the artifactapi repo name; the rest is the image name.', }, ...(isLocal ? [ { title: 'Push an image (this is a real Registry V2)', language: 'bash', code: `docker tag myapp:latest ${host}/${name}/myapp:latest docker push ${host}/${name}/myapp:latest`, note: 'Works with docker, podman, skopeo and buildah. If the registry requires auth, run `docker login ' + host + '` first.', }, ] : []), ]; case 'terraform': return [ { title: 'Use as a provider source (bare address, no mirror config)', language: 'hcl', code: `terraform { required_providers { ${name} = { source = "${host}/${name}/" version = ">= 0.1.0" } } }`, note: 'The namespace segment is this repo name; is the provider type. artifactapi signs SHA256SUMS server-side with its GPG key, so `terraform init` installs with no .terraformrc.', }, ...(isLocal ? [ { title: 'Publish a provider build', language: 'bash', code: `curl -fsSL --upload-file terraform-provider-_0.1.0_linux_amd64.zip \\ ${url}/api/v2/remotes/${name}/files/${name}//terraform-provider-_0.1.0_linux_amd64.zip`, }, ] : []), ]; case 'helm': return [ { title: 'Add the Helm repo', language: 'bash', code: `helm repo add ${name} ${proxy}/ helm repo update helm install ${name}/`, }, ]; case 'alpine': return [ { title: 'Add the APK repository', language: 'bash', code: `echo '${proxy}/' | sudo tee -a /etc/apk/repositories sudo apk update sudo apk add `, note: 'If the index is unsigned over the proxy, add --allow-untrusted or install the signing key into /etc/apk/keys.', }, ]; case 'goproxy': return [ { title: 'Point the Go module proxy here', language: 'bash', code: `export GOPROXY=${proxy} go mod download`, note: 'Append ,direct to fall back to VCS for modules this proxy does not cover.', }, ]; case 'puppet': return [ { title: 'Install a module from the Forge proxy', language: 'bash', code: `puppet module install - \\ --module_repository ${proxy}`, }, ]; case 'generic': default: return [ { title: 'Download a file', language: 'bash', code: `curl -fsSLO ${proxy}/`, note: packageType === 'generic' ? 'Generic repos are fetched as plain files at their upstream path.' : `No tailored client instructions for "${packageType}" yet — fetch artifacts directly by path.`, }, ...(isLocal ? [ { title: 'Publish a file', language: 'bash', code: `curl -fsSL --upload-file ./myfile \\ ${url}/api/v2/remotes/${name}/files//myfile`, }, ] : []), ]; } } function CodeBox({ snippet }: { snippet: Snippet }) { const [copied, setCopied] = useState(false); async function copy() { try { await navigator.clipboard.writeText(snippet.code); setCopied(true); setTimeout(() => setCopied(false), 1500); } catch { // Clipboard API unavailable (e.g. non-secure context); silently ignore. } } return (
{snippet.title}
{snippet.code}
{snippet.note &&
{snippet.note}
}
); } interface UsageInstructionsProps { packageType: string; repoClass: RepoClass; name: string; defaultOpen?: boolean; } export function UsageInstructions({ packageType, repoClass, name, defaultOpen = false }: UsageInstructionsProps) { const [open, setOpen] = useState(defaultOpen); const snippets = buildSnippets(packageType, repoClass, name); return (
{open && (
{snippets.map((s, i) => ( ))}
)}
); }