mirror of
https://github.com/git-pkgs/proxy.git
synced 2026-07-07 06:43:20 -04:00
Adds UIBaseURL (env PROXY_UI_URL), advertised separately from BaseURL for deployments where the UI is reached on a public domain while build machines hit a Docker network alias for the package endpoints. Defaults to BaseURL when unset. Consumers: - Logged alongside base_url at startup. - <link rel="canonical"> and og:url / og:title / og:site_name in every UI page, omitted when UIBaseURL is empty. - Banner on the install guide when UIBaseURL differs from BaseURL, clarifying that the URLs in the snippets are the package endpoint and that the UI itself lives elsewhere. Docs call out that the proxy serves UI and package endpoints on the same listener, so reverse proxies fronting the UI publicly must restrict the public route to PathPrefix(/ui) to avoid exposing /npm, /pypi, etc. nginx and Traefik examples both show the path-split pattern.
448 lines
12 KiB
Go
448 lines
12 KiB
Go
package server
|
|
|
|
import (
|
|
"html/template"
|
|
|
|
"github.com/git-pkgs/proxy/internal/database"
|
|
)
|
|
|
|
// DashboardData contains data for rendering the dashboard.
|
|
type DashboardData struct {
|
|
Layout
|
|
Stats DashboardStats
|
|
EnrichmentStats EnrichmentStatsView
|
|
RecentPackages []PackageInfo
|
|
PopularPackages []PackageInfo
|
|
}
|
|
|
|
// DashboardStats contains cache statistics for the dashboard.
|
|
type DashboardStats struct {
|
|
CachedArtifacts int64
|
|
TotalSize string
|
|
TotalPackages int64
|
|
TotalVersions int64
|
|
}
|
|
|
|
// EnrichmentStatsView contains enrichment statistics for display.
|
|
type EnrichmentStatsView struct {
|
|
EnrichedPackages int64
|
|
VulnSyncedPackages int64
|
|
TotalVulnerabilities int64
|
|
CriticalVulns int64
|
|
HighVulns int64
|
|
MediumVulns int64
|
|
LowVulns int64
|
|
HasVulns bool
|
|
}
|
|
|
|
// PackageInfo contains information about a cached package.
|
|
type PackageInfo struct {
|
|
Ecosystem string
|
|
Name string
|
|
Version string
|
|
Size string
|
|
Hits int64
|
|
CachedAt string
|
|
License string
|
|
LicenseCategory string
|
|
VulnCount int64
|
|
LatestVersion string
|
|
IsOutdated bool
|
|
}
|
|
|
|
// RegistryConfig contains configuration instructions for a package registry.
|
|
type RegistryConfig struct {
|
|
ID string
|
|
Name string
|
|
Language string
|
|
Endpoint string
|
|
Instructions template.HTML
|
|
}
|
|
|
|
// PackageShowData contains data for rendering the package show page.
|
|
type PackageShowData struct {
|
|
Layout
|
|
Package *database.Package
|
|
Versions []database.Version
|
|
Vulnerabilities []database.Vulnerability
|
|
LicenseCategory string
|
|
}
|
|
|
|
// VersionShowData contains data for rendering the version show page.
|
|
type VersionShowData struct {
|
|
Layout
|
|
Package *database.Package
|
|
Version *database.Version
|
|
Artifacts []database.Artifact
|
|
Vulnerabilities []database.Vulnerability
|
|
IsOutdated bool
|
|
LicenseCategory string
|
|
HasCachedArtifact bool
|
|
}
|
|
|
|
// SearchPageData contains data for rendering the search results page.
|
|
type SearchPageData struct {
|
|
Layout
|
|
Query string
|
|
Ecosystem string
|
|
Results []SearchResultItem
|
|
Count int
|
|
Page int
|
|
PerPage int
|
|
TotalPages int
|
|
}
|
|
|
|
// SearchResultItem represents a single search result for display.
|
|
type SearchResultItem struct {
|
|
Ecosystem string
|
|
Name string
|
|
LatestVersion string
|
|
License string
|
|
LicenseCategory string
|
|
Hits int64
|
|
Size int64
|
|
SizeFormatted string
|
|
CachedAt string
|
|
VulnCount int64
|
|
}
|
|
|
|
// PackagesListPageData contains data for rendering the packages list page.
|
|
type PackagesListPageData struct {
|
|
Layout
|
|
Ecosystem string
|
|
SortBy string
|
|
Results []SearchResultItem
|
|
Count int
|
|
Page int
|
|
PerPage int
|
|
TotalPages int
|
|
}
|
|
|
|
func supportedEcosystems() []string {
|
|
// this list should be kept sorted in lexicographic order so
|
|
// that the 'select' list in the UI will be in the expected
|
|
// order
|
|
return []string{
|
|
"cargo",
|
|
"composer",
|
|
"conan",
|
|
"conda",
|
|
"cran",
|
|
"deb",
|
|
"gem",
|
|
"golang",
|
|
"hex",
|
|
"julia",
|
|
"maven",
|
|
"npm",
|
|
"nuget",
|
|
"oci",
|
|
"pub",
|
|
"pypi",
|
|
"rpm",
|
|
}
|
|
}
|
|
|
|
func ecosystemBadgeLabel(ecosystem string) string {
|
|
switch ecosystem {
|
|
case "oci":
|
|
return "container"
|
|
case "deb":
|
|
return "debian"
|
|
default:
|
|
return ecosystem
|
|
}
|
|
}
|
|
|
|
func ecosystemBadgeClasses(ecosystem string) string {
|
|
base := "inline-flex items-center px-2 py-0.5 rounded text-xs font-medium"
|
|
|
|
switch ecosystem {
|
|
case "npm", "maven":
|
|
return base + " bg-red-100 text-red-700 dark:bg-red-900/50 dark:text-red-300"
|
|
case "cargo":
|
|
return base + " bg-orange-100 text-orange-700 dark:bg-orange-900/50 dark:text-orange-300"
|
|
case "gem":
|
|
return base + " bg-pink-100 text-pink-700 dark:bg-pink-900/50 dark:text-pink-300"
|
|
case "go":
|
|
return base + " bg-cyan-100 text-cyan-700 dark:bg-cyan-900/50 dark:text-cyan-300"
|
|
case "hex":
|
|
return base + " bg-purple-100 text-purple-700 dark:bg-purple-900/50 dark:text-purple-300"
|
|
case "pub":
|
|
return base + " bg-blue-100 text-blue-700 dark:bg-blue-900/50 dark:text-blue-300"
|
|
case "pypi":
|
|
return base + " bg-yellow-100 text-yellow-700 dark:bg-yellow-900/50 dark:text-yellow-300"
|
|
case "nuget":
|
|
return base + " bg-indigo-100 text-indigo-700 dark:bg-indigo-900/50 dark:text-indigo-300"
|
|
case "composer":
|
|
return base + " bg-violet-100 text-violet-700 dark:bg-violet-900/50 dark:text-violet-300"
|
|
case "conan":
|
|
return base + " bg-teal-100 text-teal-700 dark:bg-teal-900/50 dark:text-teal-300"
|
|
case "conda":
|
|
return base + " bg-green-100 text-green-700 dark:bg-green-900/50 dark:text-green-300"
|
|
case "cran":
|
|
return base + " bg-slate-100 text-slate-700 dark:bg-slate-800 dark:text-slate-300"
|
|
case "julia":
|
|
return base + " bg-emerald-100 text-emerald-700 dark:bg-emerald-900/50 dark:text-emerald-300"
|
|
case "oci":
|
|
return base + " bg-sky-100 text-sky-700 dark:bg-sky-900/50 dark:text-sky-300"
|
|
case "deb":
|
|
return base + " bg-red-100 text-red-800 dark:bg-red-900/50 dark:text-red-300"
|
|
case "rpm":
|
|
return base + " bg-amber-100 text-amber-800 dark:bg-amber-900/50 dark:text-amber-300"
|
|
default:
|
|
return base + " bg-gray-100 text-gray-700 dark:bg-gray-800 dark:text-gray-300"
|
|
}
|
|
}
|
|
|
|
func getRegistryConfigs(baseURL string) []RegistryConfig {
|
|
return []RegistryConfig{
|
|
{
|
|
ID: "npm",
|
|
Name: "npm",
|
|
Language: "JavaScript",
|
|
Endpoint: "/npm/",
|
|
Instructions: template.HTML(`<p class="config-note">Configure npm to use the proxy:</p>
|
|
<pre><code># In ~/.npmrc or project .npmrc
|
|
registry=` + baseURL + `/npm/
|
|
|
|
# Or via environment variable
|
|
npm_config_registry=` + baseURL + `/npm/ npm install</code></pre>`),
|
|
},
|
|
{
|
|
ID: "cargo",
|
|
Name: "Cargo",
|
|
Language: "Rust",
|
|
Endpoint: "/cargo/",
|
|
Instructions: template.HTML(`<p class="config-note">Configure Cargo to use the proxy (sparse index protocol):</p>
|
|
<pre><code># In ~/.cargo/config.toml or project .cargo/config.toml
|
|
[source.crates-io]
|
|
replace-with = "proxy"
|
|
|
|
[source.proxy]
|
|
registry = "sparse+` + baseURL + `/cargo/"</code></pre>`),
|
|
},
|
|
{
|
|
ID: "gem",
|
|
Name: "RubyGems",
|
|
Language: "Ruby",
|
|
Endpoint: "/gem/",
|
|
Instructions: template.HTML(`<p class="config-note">Configure Bundler/RubyGems to use the proxy:</p>
|
|
<pre><code># In Gemfile
|
|
source "` + baseURL + `/gem"
|
|
|
|
# Or configure globally
|
|
gem sources --add ` + baseURL + `/gem/
|
|
bundle config mirror.https://rubygems.org ` + baseURL + `/gem</code></pre>`),
|
|
},
|
|
{
|
|
ID: "go",
|
|
Name: "Go Modules",
|
|
Language: "Go",
|
|
Endpoint: "/go/",
|
|
Instructions: template.HTML(`<p class="config-note">Set the GOPROXY environment variable:</p>
|
|
<pre><code>export GOPROXY=` + baseURL + `/go,direct
|
|
|
|
# Add to your shell profile for persistence</code></pre>`),
|
|
},
|
|
{
|
|
ID: "hex",
|
|
Name: "Hex",
|
|
Language: "Elixir",
|
|
Endpoint: "/hex/",
|
|
Instructions: template.HTML(`<p class="config-note">Configure Hex to use the proxy:</p>
|
|
<pre><code># In ~/.hex/hex.config
|
|
{default_url, <<"` + baseURL + `/hex">>}.
|
|
|
|
# Or via environment variable
|
|
export HEX_MIRROR=` + baseURL + `/hex</code></pre>`),
|
|
},
|
|
{
|
|
ID: "pub",
|
|
Name: "pub.dev",
|
|
Language: "Dart/Flutter",
|
|
Endpoint: "/pub/",
|
|
Instructions: template.HTML(`<p class="config-note">Set the PUB_HOSTED_URL environment variable:</p>
|
|
<pre><code>export PUB_HOSTED_URL=` + baseURL + `/pub</code></pre>`),
|
|
},
|
|
{
|
|
ID: "pypi",
|
|
Name: "PyPI",
|
|
Language: "Python",
|
|
Endpoint: "/pypi/",
|
|
Instructions: template.HTML(`<p class="config-note">Configure pip to use the proxy:</p>
|
|
<pre><code># Via command line
|
|
pip install --index-url ` + baseURL + `/pypi/simple/ package_name
|
|
|
|
# In ~/.pip/pip.conf
|
|
[global]
|
|
index-url = ` + baseURL + `/pypi/simple/</code></pre>`),
|
|
},
|
|
{
|
|
ID: "maven",
|
|
Name: "Maven",
|
|
Language: "Java",
|
|
Endpoint: "/maven/",
|
|
Instructions: template.HTML(`<p class="config-note">Configure Maven to use the proxy:</p>
|
|
<pre><code><!-- In ~/.m2/settings.xml -->
|
|
<settings>
|
|
<mirrors>
|
|
<mirror>
|
|
<id>proxy</id>
|
|
<mirrorOf>central</mirrorOf>
|
|
<url>` + baseURL + `/maven/</url>
|
|
</mirror>
|
|
</mirrors>
|
|
</settings></code></pre>`),
|
|
},
|
|
{
|
|
ID: "gradle",
|
|
Name: "Gradle Build Cache",
|
|
Language: "Java/Kotlin",
|
|
Endpoint: "/gradle/",
|
|
Instructions: template.HTML(`<p class="config-note">Configure Gradle to use the proxy for HttpBuildCache:</p>
|
|
<pre><code>// In settings.gradle(.kts)
|
|
buildCache {
|
|
remote<HttpBuildCache> {
|
|
url = uri("` + baseURL + `/gradle/")
|
|
push = true
|
|
}
|
|
}</code></pre>`),
|
|
},
|
|
{
|
|
ID: "nuget",
|
|
Name: "NuGet",
|
|
Language: ".NET",
|
|
Endpoint: "/nuget/",
|
|
Instructions: template.HTML(`<p class="config-note">Configure NuGet to use the proxy:</p>
|
|
<pre><code><!-- In nuget.config -->
|
|
<configuration>
|
|
<packageSources>
|
|
<clear />
|
|
<add key="proxy" value="` + baseURL + `/nuget/v3/index.json" />
|
|
</packageSources>
|
|
</configuration>
|
|
|
|
# Or via CLI
|
|
dotnet nuget add source ` + baseURL + `/nuget/v3/index.json -n proxy</code></pre>`),
|
|
},
|
|
{
|
|
ID: "composer",
|
|
Name: "Composer",
|
|
Language: "PHP",
|
|
Endpoint: "/composer/",
|
|
Instructions: template.HTML(`<p class="config-note">Configure Composer to use the proxy:</p>
|
|
<pre><code>// In composer.json
|
|
{
|
|
"repositories": [
|
|
{
|
|
"type": "composer",
|
|
"url": "` + baseURL + `/composer"
|
|
}
|
|
]
|
|
}
|
|
|
|
# Or globally
|
|
composer config -g repositories.proxy composer ` + baseURL + `/composer</code></pre>`),
|
|
},
|
|
{
|
|
ID: "conan",
|
|
Name: "Conan",
|
|
Language: "C/C++",
|
|
Endpoint: "/conan/",
|
|
Instructions: template.HTML(`<p class="config-note">Configure Conan to use the proxy:</p>
|
|
<pre><code>conan remote add proxy ` + baseURL + `/conan
|
|
conan remote disable conancenter</code></pre>`),
|
|
},
|
|
{
|
|
ID: "conda",
|
|
Name: "Conda",
|
|
Language: "Python/R",
|
|
Endpoint: "/conda/",
|
|
Instructions: template.HTML(`<p class="config-note">Configure Conda to use the proxy:</p>
|
|
<pre><code># In ~/.condarc
|
|
channels:
|
|
- ` + baseURL + `/conda/main
|
|
- ` + baseURL + `/conda/conda-forge
|
|
default_channels:
|
|
- ` + baseURL + `/conda/main
|
|
|
|
# Or via command
|
|
conda config --add channels ` + baseURL + `/conda/main</code></pre>`),
|
|
},
|
|
{
|
|
ID: "cran",
|
|
Name: "CRAN",
|
|
Language: "R",
|
|
Endpoint: "/cran/",
|
|
Instructions: template.HTML(`<p class="config-note">Configure R to use the proxy:</p>
|
|
<pre><code># In R session
|
|
options(repos = c(CRAN = "` + baseURL + `/cran"))
|
|
|
|
# In ~/.Rprofile for persistence
|
|
local({
|
|
r <- getOption("repos")
|
|
r["CRAN"] <- "` + baseURL + `/cran"
|
|
options(repos = r)
|
|
})</code></pre>`),
|
|
},
|
|
{
|
|
ID: "julia",
|
|
Name: "Julia",
|
|
Language: "Julia",
|
|
Endpoint: "/julia/",
|
|
Instructions: template.HTML(`<p class="config-note">Set the Pkg server before starting Julia:</p>
|
|
<pre><code>export JULIA_PKG_SERVER=` + baseURL + `/julia</code></pre>
|
|
<p class="config-note">Or inside a running session:</p>
|
|
<pre><code>ENV["JULIA_PKG_SERVER"] = "` + baseURL + `/julia"
|
|
using Pkg; Pkg.update()</code></pre>`),
|
|
},
|
|
{
|
|
ID: "oci",
|
|
Name: "Container Registry",
|
|
Language: "Docker/OCI",
|
|
Endpoint: "/v2/",
|
|
Instructions: template.HTML(`<p class="config-note">Configure Docker to use the proxy as a mirror:</p>
|
|
<pre><code># In /etc/docker/daemon.json
|
|
{
|
|
"registry-mirrors": ["` + baseURL + `"]
|
|
}
|
|
|
|
# Then restart Docker
|
|
sudo systemctl restart docker
|
|
|
|
# Or pull directly
|
|
docker pull ` + baseURL[8:] + `/library/nginx:latest</code></pre>`),
|
|
},
|
|
{
|
|
ID: "deb",
|
|
Name: "Debian/APT",
|
|
Language: "Debian/Ubuntu",
|
|
Endpoint: "/debian/",
|
|
Instructions: template.HTML(`<p class="config-note">Configure APT to use the proxy:</p>
|
|
<pre><code># In /etc/apt/sources.list or /etc/apt/sources.list.d/proxy.list
|
|
deb ` + baseURL + `/debian stable main contrib
|
|
|
|
# Replace your existing sources.list entries with the proxy URL
|
|
# Then run:
|
|
sudo apt update</code></pre>`),
|
|
},
|
|
{
|
|
ID: "rpm",
|
|
Name: "RPM/Yum",
|
|
Language: "Fedora/RHEL",
|
|
Endpoint: "/rpm/",
|
|
Instructions: template.HTML(`<p class="config-note">Configure yum/dnf to use the proxy:</p>
|
|
<pre><code># In /etc/yum.repos.d/proxy.repo
|
|
[proxy-fedora]
|
|
name=Fedora via Proxy
|
|
baseurl=` + baseURL + `/rpm/releases/$releasever/Everything/$basearch/os/
|
|
enabled=1
|
|
gpgcheck=0
|
|
|
|
# Then run:
|
|
sudo dnf clean all
|
|
sudo dnf update</code></pre>`),
|
|
},
|
|
}
|
|
}
|