A simple Maven repository mirror server built with Spring Boot. Kagami (鏡, meaning "mirror" in Japanese) provides efficient caching and proxying of Maven artifacts from multiple remote repositories.
- Local Caching: Automatically caches artifacts from remote repositories to reduce download times
- Pluggable Storage: Local file system (default) or Amazon S3 / S3-compatible object storage
- Multiple Repository Support: Configure multiple remote repositories with individual settings
- Private Repository Support: JWT-based authentication for secure repository access
- REST API: Simple REST endpoints for artifact retrieval and cache management
- Web Dashboard: Server-rendered UI built with htmx for repository browsing and management with unified header navigation
- Authentication: Form-based authentication or OIDC/OAuth2 login for web UI access with styled login/logout pages
- Token Management: Web-based JWT token generation with configurable expiration, permissions, and build tool configuration examples
- User Interface: Consistent header across all pages showing logged-in username, logout functionality, and token generation access
- Security Features: OAuth2 Resource Server with JWT tokens, repository-specific access control, group-based RBAC, scope-capped token generation, CSRF protection partially disabled for API usage
- Sigstore Attestation Support: Fetches sigstore attestation bundles distributed by the upstream as sidecar files, and verifies them in the web UI with cosign against a pinned public key or keyless identity constraints
- OIDC Support: OpenID Connect authentication with multiple identity providers (Google, Microsoft Entra ID, etc.)
The fastest way to get started with Kagami is using the pre-built Docker image:
# Run Kagami with Docker
docker run --rm --pull always -p 8080:8080 \
-v /tmp/kagami:/var/kagami/storage \
-e kagami.storage.path=/var/kagami/storage \
-e kagami.repositories.central.url=https://repo.maven.apache.org/maven2 \
ghcr.io/making/kagami:jvmAccess the application:
- Open http://localhost:8080 in your browser
- Log in with default credentials:
- Username:
demo - Password:
demo
- Username:
- Access artifacts:
wget http://localhost:8080/artifacts/central/org/springframework/spring-core/6.0.0/spring-core-6.0.0.jar
kagami.jwt.private-key and kagami.jwt.public-key to enable JWT token functionality. See Private Repository Configuration for key generation instructions. The Docker Compose example below shows the recommended production setup.
Docker Configuration Options:
# With private repository with authentication
docker run --rm --pull always -p 8080:8080 \
-v /tmp/kagami:/var/kagami/storage \
-e kagami.storage.path=/var/kagami/storage \
-e kagami.repositories.spring-enterprise.url=https://packages.broadcom.com/artifactory/spring-enterprise \
-e kagami.repositories.spring-enterprise.username=your-bc-username \
-e kagami.repositories.spring-enterprise.password=your-bc-token \
-e kagami.repositories.spring-enterprise.is-private=true \
ghcr.io/making/kagami:jvm
# With custom authentication
docker run --rm --pull always -p 8080:8080 \
-v /tmp/kagami:/var/kagami/storage \
-e kagami.storage.path=/var/kagami/storage \
-e kagami.repositories.central.url=https://repo.maven.apache.org/maven2 \
-e spring.security.user.name=admin \
-e spring.security.user.password='{noop}mypassword' \
ghcr.io/making/kagami:jvm
# With S3 storage instead of a mounted volume
docker run --rm --pull always -p 8080:8080 \
-e kagami.storage.type=s3 \
-e kagami.storage.s3.bucket=kagami-mirror \
-e spring.cloud.aws.region.static=ap-northeast-1 \
-e spring.cloud.aws.credentials.access-key=your-access-key \
-e spring.cloud.aws.credentials.secret-key=your-secret-key \
-e kagami.repositories.central.url=https://repo.maven.apache.org/maven2 \
ghcr.io/making/kagami:jvmUsing Docker Compose:
For production deployments with JWT token support, create a docker-compose.yml file:
services:
kagami:
image: ghcr.io/making/kagami:jvm
pull_policy: always
ports:
- "8080:8080"
volumes:
- ./kagami-storage:/var/kagami/storage
- ./kagami-private.pem:/etc/kagami/kagami-private.pem:ro
- ./kagami-public.pem:/etc/kagami/kagami-public.pem:ro
environment:
kagami.storage.path: /var/kagami/storage
kagami.repositories.central.url: https://repo.maven.apache.org/maven2
# Configure private repository (requires JWT keys)
kagami.repositories.private-repo.url: https://internal.repo.com/maven2
kagami.repositories.private-repo.is-private: true
# JWT key configuration
kagami.jwt.private-key: file:/etc/kagami/kagami-private.pem
kagami.jwt.public-key: file:/etc/kagami/kagami-public.pem
# Optional: Custom authentication
# spring.security.user.name: admin
# spring.security.user.password: '{noop}mypassword'Before running:
- Generate JWT key pair following the instructions in Private Repository Configuration
- Place
kagami-private.pemandkagami-public.pemin the same directory as yourdocker-compose.yml - Set appropriate file permissions:
mkdir -p ./kagami-storage chmod a+w ./kagami-storage chmod a+r ./kagami-private.pem ./kagami-public.pem
- Run with:
docker compose up -d
- Java 25 or later
- Clone the repository:
git clone https://github.com/making/kagami.git
cd kagami- Build and run:
./mvnw spring-boot:run- Access the web dashboard:
open http://localhost:8080-
Log in to the web dashboard:
- Default username:
demo - Default password:
demo
- Default username:
-
Access artifacts via HTTP:
wget http://localhost:8080/artifacts/central/org/springframework/spring-core/6.0.0/spring-core-6.0.0.jarConfigure repositories and settings in application.properties or environment variables:
# Storage path for cached artifacts
kagami.storage.path=/var/kagami/storage
# Public repositories
kagami.repositories.central.url=https://repo.maven.apache.org/maven2
kagami.repositories.jcenter.url=https://jcenter.bintray.comArtifacts can be mirrored into Amazon S3 or any S3-compatible object storage (MinIO, RustFS, etc.) instead of the local file system. The bucket must exist; Kagami does not create it.
kagami.storage.type=s3
kagami.storage.s3.bucket=kagami-mirror
# Optional prefix in front of every key, which is <prefix>/<repository id>/<artifact path>
kagami.storage.s3.key-prefix=mirror
# Region and credentials are resolved by Spring Cloud AWS; the default chain
# (environment variables, instance profile, ...) works without any of these.
spring.cloud.aws.region.static=ap-northeast-1
spring.cloud.aws.credentials.access-key=...
spring.cloud.aws.credentials.secret-key=...For an S3-compatible server, point the client at its endpoint and use path-style access:
spring.cloud.aws.s3.endpoint=http://minio.example.com:9000
spring.cloud.aws.s3.path-style-access-enabled=trueNotes:
kagami.storage.typedefaults tolocal; then the S3 client is not created and no AWS settings are needed.- With
s3, the disk space health indicator and metric that point atkagami.storage.pathare disabled automatically. - The storage type is a bean condition, so a GraalVM native image is bound to the type it was
built with. Set
kagami.storage.type=s3at build time to build a native image for S3.
# Private repository with Basic authentication
kagami.repositories.private.url=https://private.repo.example.com/maven2
kagami.repositories.private.username=your-username
kagami.repositories.private.password=your-password# Mark repository as private (requires JWT authentication)
kagami.repositories.private-repo.url=https://internal.repo.com/maven2
kagami.repositories.private-repo.is-private=true
# Optional display priority (default 0); higher priorities are listed first
# on the web UI and in the generated configuration examples
kagami.repositories.private-repo.priority=10
# JWT key pair configuration
kagami.jwt.private-key=classpath:kagami-private.pem
kagami.jwt.public-key=classpath:kagami-public.pemTo generate the JWT key pair, run the following commands:
# Generate RSA private key
openssl genrsa -out private.pem 2048
# Extract public key
openssl rsa -in private.pem -outform PEM -pubout -out kagami-public.pem
# Convert private key to PKCS#8 format
openssl pkcs8 -topk8 -inform PEM -in private.pem -out kagami-private.pem -nocrypt
# Clean up temporary file
rm -f private.pemPlace the generated kagami-private.pem and kagami-public.pem files in your src/main/resources directory.
Besides file references, you can configure JWT keys using the following methods:
File path (recommended for Docker/containers):
kagami.jwt.private-key=file:/path/to/kagami-private.pem
kagami.jwt.public-key=file:/path/to/kagami-public.pemBase64 encoded strings (useful when file mounting is difficult):
kagami.jwt.private-key=base64:LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0t...
kagami.jwt.public-key=base64:LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0K...To generate base64 encoded values:
# For private key
echo "kagami.jwt.private-key=base64:$(cat kagami-private.pem | base64 -w0)"
# For public key
echo "kagami.jwt.public-key=base64:$(cat kagami-public.pem | base64 -w0)"The base64 format is particularly useful in container environments where file mounting is challenging, such as certain cloud platforms like Cloud Foundry.
For more information on configuration value conversion, see the Spring Boot documentation.
# Configure web UI authentication (default: demo/demo)
kagami.authentication.type=simple
spring.security.user.name=your-username
spring.security.user.password={noop}plainpassword
# For production, use encoded password:
# spring.security.user.password={bcrypt}$2a$10$...Only one user can be configured with this method. For multiple users, use OIDC authentication.
Configure OIDC authentication for enterprise single sign-on:
# Enable OIDC authentication
kagami.authentication.type=oidc
# Restrict access to specific email patterns (regex)
kagami.authentication.allowed-name-patterns=.*@example\\.com,.*@example\\.org
# Configure Google as identity provider
spring.security.oauth2.client.provider.google.issuer-uri=https://accounts.google.com
spring.security.oauth2.client.provider.google.user-name-attribute=email
spring.security.oauth2.client.registration.google.client-id=your-google-client-id
spring.security.oauth2.client.registration.google.client-secret=your-google-client-secret
spring.security.oauth2.client.registration.google.client-name=Google
spring.security.oauth2.client.registration.google.scope=openid,email
# Configure Microsoft Entra ID (formerly Azure AD)
spring.security.oauth2.client.provider.microsoft-entra-id.issuer-uri=https://sts.windows.net/{tenant-id}/
spring.security.oauth2.client.provider.microsoft-entra-id.user-name-attribute=email
spring.security.oauth2.client.registration.microsoft-entra-id.client-id=your-client-id
spring.security.oauth2.client.registration.microsoft-entra-id.client-secret=your-client-secret
spring.security.oauth2.client.registration.microsoft-entra-id.client-name=Microsoft Entra ID
spring.security.oauth2.client.registration.microsoft-entra-id.scope=openid,emailNotes:
- When OIDC is enabled, users will see provider-specific login buttons instead of username/password fields
- The
allowed-name-patternsproperty restricts access to users whose name matches the specified patterns - Multiple identity providers can be configured simultaneously
- Users must have matching email patterns to be allowed to log in
Group-based RBAC for OIDC users reads the IdP group memberships from a claim in the ID
token. The claim name defaults to groups and can be changed with
kagami.rbac.groups-claim. Most providers do not include group claims by default; you
must request them with a scope and/or configure the provider to embed the claim in the
ID token:
# Claim Kagami reads the IdP group memberships from (default: groups)
kagami.rbac.groups-claim=groups
# Request the claim via a scope. Examples:
# - Keycloak: add the "groups" scope of the dedicated "groups" client scope mapper,
# or create a protocol mapper that adds the user's groups to the ID token
spring.security.oauth2.client.registration.keycloak.scope=openid,email,groups
# - Microsoft Entra ID: request "Group.Read.All" so tokens carry the user's groups
spring.security.oauth2.client.registration.microsoft-entra-id.scope=openid,email,Group.Read.AllWithout a group claim in the ID token, the kagami.rbac.mappings.groups.* translations
never match and OIDC users fall into the default group. Verify the claim is actually
present in the token before debugging RBAC mappings.
See the Spring Boot documentation for more details on configuring OIDC authentication.
A group is a named set of authorities and users are mapped to groups through properties.
Group names are not roles: authorization rules only see the authorities a group expands
into, which reuse the JWT scope vocabulary (artifacts:read, artifacts:delete,
artifacts:admin), so JWT scopes and group membership satisfy the same rules. Users
absent from every mapping fall into the default group, which is editors by default.
The default therefore grants read and delete access but not cache administration; assign
administrators or another group with artifacts:admin explicitly when maintenance access
is required.
# Group definitions: group name -> authorities (same vocabulary as JWT scopes).
# The built-in groups are:
# administrators: artifacts:read,artifacts:delete,artifacts:admin
# editors: artifacts:read,artifacts:delete
# viewers: artifacts:read
# Entries here override or add groups. An empty value defines a group with no authorities.
kagami.rbac.groups.administrators=artifacts:read,artifacts:delete,artifacts:admin
kagami.rbac.groups.no-access=
# Username -> groups, common to simple and OIDC authentication. Keys containing @ or .
# need the bracket notation so that relaxed binding does not mangle them.
kagami.rbac.mappings.users.demo=administrators
kagami.rbac.mappings.users[taro@example.com]=editors
# OIDC groups claim (IdP group names) -> Kagami groups.
# The claim that carries the IdP group names is "groups" by default;
# change it with kagami.rbac.groups-claim (see the OIDC section above).
kagami.rbac.mappings.groups.my-team-admins=administrators
# Group applied to users absent from every mapping (default: editors)
kagami.rbac.default-group=no-accessWith this configuration, the user demo gets the authorities of administrators,
taro@example.com gets those of editors, and OIDC users carrying the my-team-admins
group get those of administrators. Everyone else falls into no-access, which grants
nothing: such users can browse the web UI but see no delete actions and cannot issue any
token.
Notes:
- Login grants the union of the authorities of all groups the user belongs to
- Every referenced group must be defined under
kagami.rbac.groups.*; the application fails to start otherwise - The token generation page only offers the scopes the logged in user holds, and the
token API (
POST /token) rejects requests beyond that cap with 403 - The delete actions in the web UI are only rendered for users holding
artifacts:delete - The
allowed-name-patternsOIDC gate is orthogonal to RBAC: patterns decide who may log in, RBAC decides what admitted users can do artifacts:adminis required by the cache garbage collection API; it is not granted by the defaulteditorsgroup
The administrator API removes directories whose only files are either maven-metadata.xml
and maven-metadata.xml.sha1, or only resolver-status.properties. All files in a
candidate directory must be at least one hour old. Preview candidates first, then run the
collection explicitly:
# Dry-run; olderThan accepts ISO-8601 durations such as PT30M or PT0S
curl -H "Authorization: Bearer $TOKEN" \
'http://localhost:8080/artifacts/central/gc?olderThan=PT1H'
# Collect eligible directories
curl -X POST -H "Authorization: Bearer $TOKEN" \
'http://localhost:8080/artifacts/central/gc?olderThan=PT1H'The operation works with both local and S3 storage. It scans the repository before collecting, so very large S3 repositories may take time. A concurrent artifact download is preserved; the collector removes only the eligible bookkeeping files and then removes an empty local directory when possible.
Kagami can fetch sigstore attestation bundles distributed by the upstream as sidecar files of
artifacts (e.g. lib-1.0.jar.attestation.sigstore.json) and store them next to the artifact:
kagami.repositories.tanzu.sigstore.enabled=true
# Optional: extra bundle suffixes to try; defaults to attestation.sigstore.json (Tanzu Spring)
# and sigstore.json (Maven Central)
kagami.repositories.tanzu.sigstore.bundle-suffixes[0]=attestation.sigstore.jsonBundles that reached the storage are shown in the browse UI and can be verified with the
external cosign binary, which must be installed in the Kagami runtime environment
(configurable via kagami.sigstore.cosign-path, default cosign). Verification runs entirely
on trust anchors from the repository configuration; user input is never passed to cosign.
Two verification modes exist, configured per repository:
# Pinned key mode: verify against a public key, skipping transparency log verification
kagami.repositories.tanzu.sigstore.verification=key
kagami.repositories.tanzu.sigstore.public-key-url=https://storage.googleapis.com/tanzu-signing-bucket/build-factory/public-key.pem
# Keyless mode: verify the Fulcio certificate embedded in the bundle, with full
# transparency log verification
kagami.repositories.central.sigstore.verification=keyless
kagami.repositories.central.sigstore.certificate-identity-regexp=https://github.com/<owner>/<repo>/.*
kagami.repositories.central.sigstore.certificate-oidc-issuer=https://token.actions.githubusercontent.comThe verification result (including the cosign output on failure) is shown in the file information dialog of the browse UI, together with the equivalent command line for running the verification with a locally installed cosign.
The Docker image built by CI embeds a cosign binary in the application jar (Maven profile
embedded-cosign); the embedded binary takes precedence over the one on the PATH when
kagami.sigstore.cosign-path is left at its default, so the image needs no cosign
installation.
Kagami sends every outgoing request through the configured proxy, both the artifact
downloads performed by Maven Resolver and the direct downloads of the files Maven Resolver
does not handle, such as maven-metadata.xml.
# Proxy for http repositories, also used for https repositories unless kagami.proxy.https-url is set
kagami.proxy.url=http://proxy.company.com:8080
# Proxy for https repositories
kagami.proxy.https-url=http://proxy.company.com:8443
# Credentials for proxies that require Basic authentication
kagami.proxy.username=user
kagami.proxy.password=password
# Hosts to reach without the proxy, sub domains included
kagami.proxy.non-proxy-hosts=localhost,127.0.0.1,.internal.company.comAlternatively, use the standard environment variables:
export http_proxy=http://proxy.company.com:8080
export https_proxy=http://proxy.company.com:8080
export no_proxy=localhost,127.0.0.1,.internal.company.comNotes:
- Properties take precedence over environment variables, and lower case environment
variables take precedence over upper case ones (
http_proxybeforeHTTP_PROXY) - Credentials can also be embedded in the URL, e.g.
kagami.proxy.url=http://user:password@proxy.company.com:8080. Reserved characters must be percent encoded - The scheme may be omitted, e.g.
kagami.proxy.url=proxy.company.com:8080 - Only
httpproxies are supported, which is what legacy proxies use even forhttpsrepositories, where the connection is tunneled withCONNECT - The JDK refuses to send Basic credentials over a tunneled connection by default. If an
https repository is behind a proxy requiring authentication, start Kagami with
-Djdk.http.auth.tunneling.disabledSchemes=
See the API documentation for details on available endpoints.
Configure your Maven settings to use Kagami as a mirror:
<settings>
<profiles>
<profile>
<id>kagami-public</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<repositories>
<repository>
<id>kagami-central</id>
<name>Kagami Public Repository</name>
<url>http://localhost:8080/artifacts/central</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>kagami-central</id>
<name>Kagami Public Repository</name>
<url>http://localhost:8080/artifacts/central</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>
</profile>
</profiles>
<!-- ALTERNATIVE: Mirror configuration -->
<!-- Use mirrors when you want to redirect ALL Maven repository requests through Kagami -->
<!-- This is useful for: -->
<!-- - Corporate environments where all external access must go through a proxy -->
<!-- - Offline environments where only Kagami has access to external repositories -->
<!-- - Performance optimization when Kagami has better network access to upstream repos -->
<!--
<mirrors>
<mirror>
<id>kagami</id>
<mirrorOf>*</mirrorOf>
<name>Kagami Mirror</name>
<url>http://localhost:8080/artifacts/central</url>
</mirror>
</mirrors>
-->
</settings>For private repositories, generate a JWT token using the web interface:
- Log in to the web dashboard at
http://localhost:8080 - Click "Generate Token" in the header navigation
- Select repositories and permissions using checkboxes
- Set token expiration with human-friendly units (default: 6 months)
- Click "Generate Token" and copy the generated JWT
- Use the provided Maven, Gradle Groovy, or Gradle Kotlin configuration examples
Note: JWT tokens cannot be used to generate new tokens. You must use the web interface.
Two authentication methods are supported for build tools:
- Username/Password (Basic authentication): the username can be any value and the password is the JWT token
- Bearer token: send the JWT token in the
Authorization: Bearerheader
Then configure Maven with the JWT token. With the standard username/password configuration:
<settings>
<servers>
<server>
<id>kagami-private</id>
<username>any-username</username>
<password>YOUR_JWT_TOKEN</password>
</server>
</servers>
<profiles>
<profile>
<id>kagami-private</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<repositories>
<repository>
<id>kagami-private</id>
<name>Kagami Private Repository</name>
<url>http://localhost:8080/artifacts/private-repo</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>kagami-private</id>
<name>Kagami Private Repository</name>
<url>http://localhost:8080/artifacts/private-repo</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>
</profile>
</profiles>
<!-- ALTERNATIVE: Mirror configuration -->
<!-- Use mirrors when you want to redirect ALL Maven repository requests through Kagami -->
<!-- This is useful for: -->
<!-- - Corporate environments where all external access must go through a proxy -->
<!-- - Offline environments where only Kagami has access to external repositories -->
<!-- - Performance optimization when Kagami has better network access to upstream repos -->
<!--
<mirrors>
<mirror>
<id>kagami-private</id>
<mirrorOf>*</mirrorOf>
<name>Kagami Private Mirror</name>
<url>http://localhost:8080/artifacts/private-repo</url>
</mirror>
</mirrors>
-->
</settings>Alternatively, configure the Bearer token method by replacing the <server> element above with:
<server>
<id>kagami-private</id>
<configuration>
<httpHeaders>
<property>
<name>Authorization</name>
<value>Bearer YOUR_JWT_TOKEN</value>
</property>
</httpHeaders>
</configuration>
</server>repositories {
maven {
url 'http://localhost:8080/artifacts/central'
}
}The examples below use the username/password method. The Bearer token method is also available (see the end of this section).
Groovy DSL ($HOME/.gradle/init.gradle)
// $HOME/.gradle/init.gradle - Groovy DSL version
def repoUrl = "http://localhost:8080/artifacts/private-repo"
def repoToken = "YOUR_JWT_TOKEN"
// For regular dependencies (legacy projects)
allprojects {
repositories {
maven {
url = repoUrl
allowInsecureProtocol = true
credentials {
username = "kagami" // can be any value
password = repoToken
}
}
mavenCentral() // fallback
}
}
// Configure settings.gradle
settingsEvaluated { settings ->
// For plugin resolution
settings.pluginManagement {
repositories {
maven {
url = repoUrl
allowInsecureProtocol = true
credentials {
username = "kagami" // can be any value
password = repoToken
}
}
gradlePluginPortal() // fallback
mavenCentral() // fallback
}
}
// Dependency resolution management (Gradle 6.8+)
settings.dependencyResolutionManagement {
// Ignore repositories defined in build.gradle
repositoriesMode.set(RepositoriesMode.PREFER_SETTINGS)
repositories {
maven {
url = repoUrl
allowInsecureProtocol = true
credentials {
username = "kagami" // can be any value
password = repoToken
}
}
mavenCentral()
}
}
}Kotlin DSL ($HOME/.gradle/init.gradle.kts)
// $HOME/.gradle/init.gradle.kts - Kotlin DSL version
val repoUrl = "http://localhost:8080/artifacts/private-repo"
val repoToken = "YOUR_JWT_TOKEN"
// Extension function to configure repository
fun RepositoryHandler.addKagamiRepository() {
maven {
url = uri(repoUrl)
isAllowInsecureProtocol = true
credentials {
username = "kagami" // can be any value
password = repoToken
}
}
}
// For regular dependencies (legacy projects)
allprojects {
repositories {
addKagamiRepository()
mavenCentral() // fallback
}
}
// Configure settings.gradle
settingsEvaluated {
// For plugin resolution
pluginManagement {
repositories {
addKagamiRepository()
gradlePluginPortal() // fallback
mavenCentral() // fallback
}
}
// Dependency resolution management (Gradle 6.8+)
dependencyResolutionManagement {
// Ignore repositories defined in build.gradle
@Suppress("UnstableApiUsage")
repositoriesMode.set(RepositoriesMode.PREFER_SETTINGS)
repositories {
addKagamiRepository()
mavenCentral() // fallback
}
}
}To use the Bearer token method instead, replace each credentials block in the examples above
with the following.
Groovy DSL:
authentication {
header(HttpHeaderAuthentication)
}
credentials(HttpHeaderCredentials) {
name = "Authorization"
value = "Bearer YOUR_JWT_TOKEN"
}Kotlin DSL (add the imports at the top of the init script):
import org.gradle.authentication.http.HttpHeaderAuthentication
import org.gradle.kotlin.dsl.*
// ...
authentication {
create<HttpHeaderAuthentication>("header")
}
credentials(HttpHeaderCredentials::class) {
name = "Authorization"
value = "Bearer YOUR_JWT_TOKEN"
}Generate the JWT key pair as documented above and paste base64-encoded values in manifest.yaml as below:
applications:
- name: kagami
instances: 1
memory: 768m
docker:
image: ghcr.io/making/kagami:jvm
env:
kagami.repositories.central.url: https://repo.maven.apache.org/maven2
kagami.jwt.private-key: base64:LS0tLS1CRUd...
kagami.jwt.public-key: base64:LS0tLS1CRUdJ...Then deploy it:
cf push
On Cloud Foundry, applications are ephemeral by default. Therefore, all data will be lost when Kagami is restarted or updated. Kagami is a mirror repository, so even if data is lost, it will be re-downloaded the next time it is accessed. Aside from the slow initial download time, ephemeral containers are not a problem. Similarly, when scaling out, each container has its own cache with shared nothing. If initial download time or disk capacity are issues, consider using Volume Services.
# Build the application
./mvnw clean package
# Run all tests
./mvnw test
# Run with development profile
./mvnw spring-boot:runKagami supports creating optimized Docker images using Spring Boot's buildpacks integration:
# Create Docker image using buildpacks (requires Docker)
./mvnw spring-boot:build-image
# The generated image will be tagged as: kagami:0.0.1-SNAPSHOT
# You can run it with:
docker run --pull always -p 8080:8080 \
-v /tmp/kagami:/var/kagami/storage \
-e kagami.storage.path=/var/kagami/storage \
-e kagami.repositories.central.url=https://repo.maven.apache.org/maven2 \
kagami:0.0.1-SNAPSHOT
# Custom image name and tag
./mvnw spring-boot:build-image -Dspring-boot.build-image.imageName=myregistry/kagami:latestEnable debug logging for troubleshooting:
logging.level.am.ik.kagami=DEBUG
logging.level.org.eclipse.aether=DEBUG
# For security troubleshooting:
logging.level.org.springframework.security=DEBUGThe application provides health check endpoints via Spring Actuator:
# Health status
curl http://localhost:8080/actuator/health
# Prometheus metrics
curl http://localhost:8080/actuator/prometheusLicensed under the Apache License, Version 2.0. See LICENSE for details.