Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1102ef08cd |
No files matched your search
@@ -6,14 +6,7 @@ npm-debug.log
|
||||
README.md
|
||||
.next
|
||||
.turbo
|
||||
.pnpm-store
|
||||
**/.pnpm-store
|
||||
coverage
|
||||
**/coverage
|
||||
**/.docusaurus
|
||||
**/build
|
||||
.git
|
||||
.github
|
||||
dev
|
||||
.build
|
||||
e2e
|
||||
|
||||
+1
-20
@@ -59,23 +59,4 @@ TURBO_TELEMETRY_DISABLED=1
|
||||
# ENABLE_KUBERNETES=true
|
||||
|
||||
# Enable mock integration
|
||||
UNSAFE_ENABLE_MOCK_INTEGRATION=true
|
||||
|
||||
# Enable demo mode (seeds a demo user "demo"/"demo", a sample board and a mock integration)
|
||||
# DEMO_MODE=true
|
||||
|
||||
# Whether demo mode is read-only. Defaults to true (the public demo blocks all mutations).
|
||||
# Set to false for writable preview deployments so the demo user can modify the dashboard.
|
||||
# DEMO_READ_ONLY=false
|
||||
# Central Workshop API is https://homarr.dev. Override only when developing
|
||||
# the standalone Docusaurus or Homarr clients against a local backend.
|
||||
# NEXT_PUBLIC_WORKSHOP_API_URL=http://localhost:8090
|
||||
# WORKSHOP_API_URL=http://localhost:8090
|
||||
|
||||
# Workshop development ports. PocketBase superusers and GitHub OAuth are
|
||||
# configured once through the persistent PocketBase dashboard, not environment variables.
|
||||
PB_EXPOSE_PORT=8090
|
||||
WORKSHOP_WEB_PORT=3003
|
||||
# Workshop is intentionally consumable by arbitrary self-hosted Homarr origins.
|
||||
# PocketBase is stateless and authorizes writes with bearer tokens, not cookies.
|
||||
PB_ALLOWED_ORIGINS=*
|
||||
UNSAFE_ENABLE_MOCK_INTEGRATION=true
|
||||
@@ -33,9 +33,6 @@ body:
|
||||
options:
|
||||
# The below comment is used to insert a new version with on-release.yml
|
||||
#NEXT_VERSION#
|
||||
- 1.70.0
|
||||
- 1.69.2
|
||||
- 1.69.0
|
||||
- 1.68.0
|
||||
- 1.67.0
|
||||
- 1.66.1
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
.secrets
|
||||
!.secrets.example
|
||||
@@ -1,4 +0,0 @@
|
||||
TURBO_API=
|
||||
TURBO_TEAM=
|
||||
TURBO_TOKEN=
|
||||
TURBO_REMOTE_CACHE_SIGNATURE_KEY=
|
||||
@@ -1,36 +0,0 @@
|
||||
# Running CI locally
|
||||
|
||||
Install [Act](https://nektosact.com/installation/index.html) and ensure Docker is running, then use:
|
||||
|
||||
```bash
|
||||
pnpm ci:act:fast
|
||||
pnpm ci:act:docker
|
||||
pnpm ci:act:all
|
||||
pnpm ci:act:cleanup
|
||||
```
|
||||
|
||||
`fast` runs the quality gate. `docker` and `all` run the gate followed by the single container build, E2E suite, and memory regression test. `cleanup` validates the manual cleanup path without calling GitHub. The wrapper supports regular clones and Git worktrees, including non-default Docker contexts. Linux dependencies are kept in Docker volumes instead of overwriting host `node_modules`. The first run downloads the Act runner and Playwright browser dependencies.
|
||||
|
||||
Remote caching is optional locally. Copy `.github/act/.secrets.example` to `.github/act/.secrets`, populate it with rotated credentials, and run with:
|
||||
|
||||
```bash
|
||||
ACT_SECRET_FILE=.github/act/.secrets pnpm ci:act:all
|
||||
```
|
||||
|
||||
Act never publishes or deletes GHCR images. The secret file is ignored by Git.
|
||||
|
||||
## PR preview images
|
||||
|
||||
Successful non-draft pull requests from this repository publish an amd64 image:
|
||||
|
||||
```bash
|
||||
docker pull ghcr.io/homarr-labs/homarr-test:pr-<number>
|
||||
```
|
||||
|
||||
The tag is replaced when the pull request is updated and deleted when it closes. Production Homarr images remain the supported multi-architecture releases. The `homarr-test` package must be configured as public in GHCR for unauthenticated testers.
|
||||
|
||||
## Dokploy preview cache
|
||||
|
||||
Keep `needs-demo` as the Dokploy preview label. Under the application's **Preview Build-time Secrets**, configure `TURBO_API`, `TURBO_TEAM`, `TURBO_TOKEN`, and `TURBO_REMOTE_CACHE_SIGNATURE_KEY`. Dokploy passes these to the Dockerfile as BuildKit secrets. Do not configure the token or signature key as Docker build arguments.
|
||||
|
||||
Rotate any credentials that have appeared in chat, logs, or committed configuration before enabling the cache.
|
||||
@@ -11,7 +11,7 @@ outputs:
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Set up Docker Buildx
|
||||
|
||||
@@ -11,11 +11,3 @@
|
||||
- [ ] Commits follow the [conventional commits guideline](https://www.conventionalcommits.org/en/v1.0.0/)
|
||||
- [ ] No shorthand variable names are used (eg. `x`, `y`, `i` or any abbrevation)
|
||||
- [ ] Documentation is up to date. Create a pull request [here](https://github.com/homarr-labs/documentation/).
|
||||
- [ ] When using AI; No temp files are checked in, the code style follows the rest of the project
|
||||
|
||||
**When contributing a new integration or widget:**
|
||||
|
||||
- [ ] I confirmed manually that the integration or widget is working with a real system and performed basic smoke
|
||||
tests (e.g. what happens if it is offline)
|
||||
- [ ] Optional but recommended: I confirm that I am willing to be added to the `CODEOWNERS` of this feature and will
|
||||
provide support and maintain the integration (e.g. in case of breaking or failure)
|
||||
@@ -1,48 +0,0 @@
|
||||
name: Auto-fix lint and format
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
branches: ["*"]
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
autofix:
|
||||
name: Auto-fix
|
||||
if: github.event.pull_request.head.repo.id == github.repository_id
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
fetch-depth: 0
|
||||
ref: ${{ github.head_ref }}
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup
|
||||
uses: ./tooling/github/setup
|
||||
|
||||
- name: Get changed files
|
||||
id: changed
|
||||
env:
|
||||
BASE_REF: ${{ github.base_ref }}
|
||||
run: |
|
||||
git diff --name-only --diff-filter=ACMR "origin/${BASE_REF}...HEAD" -- '*.ts' '*.tsx' '*.js' '*.jsx' > /tmp/changed-files
|
||||
echo "count=$(wc -l < /tmp/changed-files | tr -d '[:space:]')" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Auto-fix lint + format
|
||||
if: steps.changed.outputs.count != '0'
|
||||
run: |
|
||||
xargs -r -a /tmp/changed-files pnpm exec oxlint --fix || true
|
||||
xargs -r -a /tmp/changed-files pnpm exec oxfmt --write
|
||||
|
||||
- name: Commit fixes
|
||||
uses: stefanzweifel/git-auto-commit-action@v7
|
||||
with:
|
||||
commit_message: "style: auto-fix lint and format"
|
||||
@@ -17,7 +17,7 @@ jobs:
|
||||
if: github.actor_id == 158783068 || github.actor_id == 190541745 || github.actor_id == 210161987 || github.actor_id == 175486441
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v7
|
||||
uses: actions/checkout@v6
|
||||
- name: Obtain token
|
||||
id: obtainToken
|
||||
uses: actions/create-github-app-token@v3
|
||||
|
||||
@@ -1,347 +0,0 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened, ready_for_review]
|
||||
branches: ["*"]
|
||||
pull_request_target:
|
||||
types: [closed]
|
||||
branches: ["*"]
|
||||
push:
|
||||
branches: [main, dev, beta, next]
|
||||
merge_group:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
operation:
|
||||
description: Operation to run
|
||||
required: true
|
||||
default: validate
|
||||
type: choice
|
||||
options:
|
||||
- validate
|
||||
- publish
|
||||
- cleanup
|
||||
pr_number:
|
||||
description: PR number for publish or cleanup
|
||||
required: false
|
||||
type: number
|
||||
ref:
|
||||
description: Git ref to validate or publish
|
||||
required: false
|
||||
type: string
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.event.pull_request.number || inputs.pr_number || github.ref }}
|
||||
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
|
||||
|
||||
env:
|
||||
FORCE_COLOR: 3
|
||||
SKIP_ENV_VALIDATION: true
|
||||
TURBO_TELEMETRY_DISABLED: 1
|
||||
REGISTRY: ghcr.io
|
||||
PREVIEW_IMAGE: ghcr.io/homarr-labs/homarr-test
|
||||
|
||||
jobs:
|
||||
fast-gate:
|
||||
name: Fast gate
|
||||
if: github.event_name != 'pull_request_target' && (github.event_name != 'workflow_dispatch' || inputs.operation != 'cleanup')
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
env:
|
||||
TURBO_API: ${{ secrets.TURBO_API }}
|
||||
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
|
||||
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
|
||||
TURBO_REMOTE_CACHE_SIGNATURE_KEY: ${{ secrets.TURBO_REMOTE_CACHE_SIGNATURE_KEY }}
|
||||
TURBO_SCM_BASE: ${{ github.event.pull_request.base.sha || github.event.merge_group.base_sha || github.event.before || 'HEAD~1' }}
|
||||
TURBO_SCM_HEAD: ${{ github.sha }}
|
||||
TURBO_PLATFORM: linux/amd64
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
if: '!env.ACT'
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
ref: ${{ github.event_name == 'workflow_dispatch' && inputs.ref || '' }}
|
||||
|
||||
- name: Validate manual inputs
|
||||
if: github.event_name == 'workflow_dispatch' && inputs.operation == 'publish'
|
||||
env:
|
||||
PR_NUMBER: ${{ inputs.pr_number }}
|
||||
BUILD_REF: ${{ inputs.ref }}
|
||||
run: |
|
||||
test -n "$PR_NUMBER" || { echo "pr_number is required for publish"; exit 1; }
|
||||
test -n "$BUILD_REF" || { echo "ref is required for publish"; exit 1; }
|
||||
|
||||
- name: Setup
|
||||
uses: ./tooling/github/setup
|
||||
|
||||
- name: Copy environment defaults
|
||||
run: cp .env.example .env
|
||||
|
||||
- name: Check formatting in affected workspaces
|
||||
run: pnpm turbo run format --affected --continue -- --check
|
||||
|
||||
- name: Lint, typecheck, and build affected workspaces
|
||||
run: pnpm turbo run lint typecheck build --affected --continue=dependencies-successful
|
||||
|
||||
- name: Validate workspace configuration
|
||||
run: pnpm lint:ws
|
||||
|
||||
- name: Run unit tests
|
||||
run: pnpm test
|
||||
|
||||
- name: Report coverage
|
||||
if: always() && !env.ACT
|
||||
uses: davelosert/vitest-coverage-report-action@v2
|
||||
|
||||
container:
|
||||
name: Container, E2E, and memory
|
||||
needs: fast-gate
|
||||
if: |
|
||||
github.event_name == 'merge_group' ||
|
||||
(github.event_name == 'pull_request' && github.event.pull_request.draft == false) ||
|
||||
(github.event_name == 'workflow_dispatch' && (inputs.operation == 'validate' || inputs.operation == 'publish'))
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
if: '!env.ACT'
|
||||
with:
|
||||
persist-credentials: false
|
||||
ref: ${{ github.event_name == 'workflow_dispatch' && inputs.ref || '' }}
|
||||
|
||||
- name: Setup
|
||||
uses: ./tooling/github/setup
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
if: '!env.ACT'
|
||||
uses: docker/setup-buildx-action@v4
|
||||
|
||||
- name: Build container once
|
||||
if: '!env.ACT'
|
||||
uses: docker/build-push-action@v7
|
||||
with:
|
||||
context: .
|
||||
network: host
|
||||
platforms: linux/amd64
|
||||
load: true
|
||||
push: false
|
||||
tags: |
|
||||
homarr-ci
|
||||
homarr-e2e
|
||||
labels: |
|
||||
org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }}
|
||||
org.opencontainers.image.revision=${{ github.sha }}
|
||||
org.opencontainers.image.ref.name=pr-${{ github.event.pull_request.number || inputs.pr_number || 'none' }}
|
||||
cache-from: type=gha,scope=homarr-linux-amd64
|
||||
cache-to: type=gha,scope=homarr-linux-amd64,mode=max
|
||||
secrets: |
|
||||
TURBO_API=${{ secrets.TURBO_API }}
|
||||
TURBO_TEAM=${{ secrets.TURBO_TEAM }}
|
||||
TURBO_TOKEN=${{ secrets.TURBO_TOKEN }}
|
||||
TURBO_REMOTE_CACHE_SIGNATURE_KEY=${{ secrets.TURBO_REMOTE_CACHE_SIGNATURE_KEY }}
|
||||
|
||||
- name: Build container once with Act
|
||||
if: env.ACT
|
||||
env:
|
||||
TURBO_API: ${{ secrets.TURBO_API }}
|
||||
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
|
||||
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
|
||||
TURBO_REMOTE_CACHE_SIGNATURE_KEY: ${{ secrets.TURBO_REMOTE_CACHE_SIGNATURE_KEY }}
|
||||
run: |
|
||||
secret_args=()
|
||||
for secret in TURBO_API TURBO_TEAM TURBO_TOKEN TURBO_REMOTE_CACHE_SIGNATURE_KEY; do
|
||||
if [ -n "${!secret:-}" ]; then
|
||||
secret_args+=(--secret "id=${secret},env=${secret}")
|
||||
fi
|
||||
done
|
||||
DOCKER_BUILDKIT=1 docker build --platform linux/amd64 \
|
||||
--tag homarr-ci --tag homarr-e2e "${secret_args[@]}" .
|
||||
|
||||
- name: Cache Playwright browsers
|
||||
if: '!env.ACT'
|
||||
id: playwright-cache
|
||||
uses: actions/cache@v6
|
||||
with:
|
||||
path: ~/.cache/ms-playwright
|
||||
key: playwright-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }}
|
||||
|
||||
- name: Install Playwright Chromium
|
||||
if: steps.playwright-cache.outputs.cache-hit != 'true' || env.ACT
|
||||
run: pnpm exec playwright install --with-deps chromium
|
||||
|
||||
- name: Install Playwright system deps
|
||||
if: steps.playwright-cache.outputs.cache-hit == 'true'
|
||||
run: pnpm exec playwright install-deps chromium
|
||||
|
||||
- name: Run E2E tests
|
||||
run: pnpm test:e2e
|
||||
|
||||
- name: Upload E2E artifacts
|
||||
if: failure() && !env.ACT
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: playwright-report
|
||||
path: e2e/test-results/
|
||||
retention-days: 7
|
||||
|
||||
- name: Run memory regression test
|
||||
env:
|
||||
CONTAINER_NAME: homarr-mem-${{ github.run_id }}-${{ github.run_attempt }}
|
||||
SECRET_ENCRYPTION_KEY: 0000000000000000000000000000000000000000000000000000000000000000
|
||||
run: |
|
||||
docker run -d --name "$CONTAINER_NAME" -p 127.0.0.1::7575 \
|
||||
-e NO_EXTERNAL_CONNECTION=true \
|
||||
-e SECRET_ENCRYPTION_KEY="$SECRET_ENCRYPTION_KEY" \
|
||||
homarr-ci
|
||||
PORT=$(docker port "$CONTAINER_NAME" 7575/tcp | sed 's/.*://')
|
||||
[[ "$PORT" =~ ^[0-9]+$ ]] || { echo "ERROR: invalid port mapping: '$PORT'"; exit 1; }
|
||||
URL="http://127.0.0.1:${PORT}"
|
||||
|
||||
healthy=false
|
||||
for i in $(seq 1 30); do
|
||||
if curl -sf "$URL/api/health/ready" >/dev/null 2>&1; then
|
||||
echo "Container healthy after $((i * 2))s"
|
||||
healthy=true
|
||||
break
|
||||
fi
|
||||
sleep 2
|
||||
done
|
||||
|
||||
if [ "$healthy" != "true" ]; then
|
||||
echo "ERROR: Container never became healthy"
|
||||
docker logs "$CONTAINER_NAME" --tail 100
|
||||
exit 1
|
||||
fi
|
||||
|
||||
warmup_ok=0
|
||||
for _ in $(seq 1 5); do
|
||||
curl -sf "$URL/" >/dev/null 2>&1 && warmup_ok=$((warmup_ok + 1))
|
||||
sleep 2
|
||||
done
|
||||
if [ "$warmup_ok" -eq 0 ]; then
|
||||
echo "ERROR: All warmup requests failed"
|
||||
docker logs "$CONTAINER_NAME" --tail 50
|
||||
exit 1
|
||||
fi
|
||||
|
||||
docker inspect -f '{{.State.Running}}' "$CONTAINER_NAME" | grep -qx true || {
|
||||
echo "ERROR: Container died before memory measurement"
|
||||
docker logs "$CONTAINER_NAME" --tail 100
|
||||
exit 1
|
||||
}
|
||||
|
||||
RAW_MEMORY=$(docker stats --no-stream --format '{{.MemUsage}}' "$CONTAINER_NAME" | sed 's| /.*||')
|
||||
MEMORY_MB=$(echo "$RAW_MEMORY" | awk '
|
||||
/GiB/ { gsub(/GiB/, ""); print $1 * 1024; next }
|
||||
/MiB/ { gsub(/MiB/, ""); print $1; next }
|
||||
/KiB/ { gsub(/KiB/, ""); print $1 / 1024; next }
|
||||
{ print $1 / 1048576 }
|
||||
')
|
||||
|
||||
echo "Memory usage: ${MEMORY_MB} MB (${RAW_MEMORY})"
|
||||
[[ "$MEMORY_MB" =~ ^[0-9]+(\.[0-9]+)?$ ]] || {
|
||||
echo "ERROR: non-numeric memory value: '$MEMORY_MB' (raw: '$RAW_MEMORY')"
|
||||
docker logs "$CONTAINER_NAME" --tail 100
|
||||
exit 1
|
||||
}
|
||||
if awk "BEGIN { exit !($MEMORY_MB < 50) }"; then
|
||||
echo "ERROR: Memory suspiciously low (${MEMORY_MB} MB) — container may have crashed"
|
||||
docker logs "$CONTAINER_NAME" --tail 100
|
||||
exit 1
|
||||
elif awk "BEGIN { exit !($MEMORY_MB > 700) }"; then
|
||||
echo "ERROR: Memory usage ${MEMORY_MB} MB exceeds 700 MB"
|
||||
docker logs "$CONTAINER_NAME" --tail 100
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Clean up memory test container
|
||||
if: always()
|
||||
env:
|
||||
CONTAINER_NAME: homarr-mem-${{ github.run_id }}-${{ github.run_attempt }}
|
||||
run: docker rm -f "$CONTAINER_NAME" >/dev/null 2>&1 || true
|
||||
|
||||
- name: Determine preview tag
|
||||
id: preview
|
||||
if: |
|
||||
!env.ACT &&
|
||||
(
|
||||
(github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository) ||
|
||||
(github.event_name == 'workflow_dispatch' && inputs.operation == 'publish')
|
||||
)
|
||||
env:
|
||||
PR_NUMBER: ${{ github.event.pull_request.number || inputs.pr_number }}
|
||||
run: echo "tag=pr-${PR_NUMBER}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Log in to GHCR
|
||||
if: steps.preview.outputs.tag != ''
|
||||
uses: docker/login-action@v4
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Push preview image
|
||||
if: steps.preview.outputs.tag != ''
|
||||
env:
|
||||
PREVIEW_TAG: ${{ steps.preview.outputs.tag }}
|
||||
run: |
|
||||
docker tag homarr-ci "${PREVIEW_IMAGE}:${PREVIEW_TAG}"
|
||||
docker push "${PREVIEW_IMAGE}:${PREVIEW_TAG}"
|
||||
|
||||
- name: Show Act publication result
|
||||
if: env.ACT
|
||||
run: echo "Act validated the publication path; external GHCR writes are disabled locally."
|
||||
|
||||
cleanup:
|
||||
name: Clean up preview image
|
||||
if: github.event_name == 'pull_request_target' || (github.event_name == 'workflow_dispatch' && inputs.operation == 'cleanup')
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
permissions:
|
||||
packages: write
|
||||
env:
|
||||
IMAGE_TAG: pr-${{ github.event.pull_request.number || inputs.pr_number }}
|
||||
steps:
|
||||
- name: Validate cleanup input
|
||||
if: github.event_name == 'workflow_dispatch'
|
||||
env:
|
||||
PR_NUMBER: ${{ inputs.pr_number }}
|
||||
run: test -n "$PR_NUMBER" || { echo "pr_number is required for cleanup"; exit 1; }
|
||||
|
||||
- name: Delete GHCR package version
|
||||
if: '!env.ACT'
|
||||
uses: actions/github-script@v8
|
||||
with:
|
||||
script: |
|
||||
const packageName = "homarr-test";
|
||||
const org = context.repo.owner;
|
||||
const tag = process.env.IMAGE_TAG;
|
||||
const versions = await github.paginate(
|
||||
github.rest.packages.getAllPackageVersionsForPackageOwnedByOrg,
|
||||
{ package_type: "container", package_name: packageName, org, per_page: 100 },
|
||||
);
|
||||
const target = versions.find((version) =>
|
||||
version.metadata?.container?.tags?.includes(tag),
|
||||
);
|
||||
if (!target) {
|
||||
core.info(`No package version found with tag "${tag}"`);
|
||||
return;
|
||||
}
|
||||
await github.rest.packages.deletePackageVersionForOrg({
|
||||
package_type: "container",
|
||||
package_name: packageName,
|
||||
org,
|
||||
package_version_id: target.id,
|
||||
});
|
||||
core.info(`Deleted package version ${target.id} (${tag})`);
|
||||
|
||||
- name: Show Act cleanup result
|
||||
if: env.ACT
|
||||
run: echo "Act validated cleanup for ${IMAGE_TAG}; external GHCR writes are disabled locally."
|
||||
@@ -0,0 +1,227 @@
|
||||
name: "[Quality] Code Analysis"
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: ["*"]
|
||||
push:
|
||||
branches: ["main"]
|
||||
merge_group:
|
||||
|
||||
concurrency:
|
||||
group: "${{ github.workflow }}-${{ github.ref }}"
|
||||
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
|
||||
|
||||
env:
|
||||
FORCE_COLOR: 3
|
||||
SKIP_ENV_VALIDATION: true
|
||||
TURBO_API: ${{ secrets.TURBO_API }}
|
||||
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
|
||||
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
|
||||
TURBO_REMOTE_CACHE_SIGNATURE_KEY: ${{ secrets.TURBO_REMOTE_CACHE_SIGNATURE_KEY }}
|
||||
TURBO_SCM_BASE: ${{ github.event.pull_request.base.sha || github.event.before || 'HEAD~1' }}
|
||||
TURBO_SCM_HEAD: ${{ github.sha }}
|
||||
|
||||
jobs:
|
||||
lint-format-autofix:
|
||||
if: github.event_name == 'pull_request'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
ref: ${{ github.head_ref }}
|
||||
|
||||
- name: Setup
|
||||
uses: ./tooling/github/setup
|
||||
|
||||
- name: Get changed files
|
||||
id: changed
|
||||
run: |
|
||||
git diff --name-only --diff-filter=ACMR origin/${{ github.base_ref }}...HEAD -- '*.ts' '*.tsx' '*.js' '*.jsx' > /tmp/changed-files
|
||||
echo "count=$(wc -l < /tmp/changed-files | tr -d '[:space:]')" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Auto-fix lint + format
|
||||
if: steps.changed.outputs.count != '0'
|
||||
run: |
|
||||
xargs -r -a /tmp/changed-files pnpm exec oxlint --fix || true
|
||||
xargs -r -a /tmp/changed-files pnpm exec oxfmt --write
|
||||
|
||||
- name: Commit fixes
|
||||
uses: stefanzweifel/git-auto-commit-action@v7
|
||||
with:
|
||||
commit_message: "style: auto-fix lint and format"
|
||||
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup
|
||||
uses: ./tooling/github/setup
|
||||
|
||||
- name: Copy env
|
||||
shell: bash
|
||||
run: cp .env.example .env
|
||||
|
||||
- name: Lint
|
||||
run: pnpm turbo lint --affected && pnpm lint:ws
|
||||
|
||||
format:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup
|
||||
uses: ./tooling/github/setup
|
||||
|
||||
- name: Format
|
||||
run: pnpm turbo format --affected --continue
|
||||
|
||||
typecheck:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup
|
||||
uses: ./tooling/github/setup
|
||||
|
||||
- name: Typecheck
|
||||
run: pnpm turbo typecheck --affected
|
||||
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Setup
|
||||
uses: ./tooling/github/setup
|
||||
|
||||
- name: Test
|
||||
run: pnpm test
|
||||
|
||||
- name: "Report Coverage"
|
||||
# Set if: always() to also generate the report if tests are failing
|
||||
# Only works if you set `reportOnFailure: true` in your vite config as specified above
|
||||
if: always()
|
||||
uses: davelosert/vitest-coverage-report-action@v2
|
||||
|
||||
e2e:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- name: Setup
|
||||
uses: ./tooling/github/setup
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v4
|
||||
- name: Build docker image
|
||||
id: build-docker-image
|
||||
uses: docker/build-push-action@v7
|
||||
with:
|
||||
platforms: linux/amd64
|
||||
context: .
|
||||
push: false
|
||||
load: true
|
||||
tags: homarr-e2e
|
||||
network: host
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
env:
|
||||
SKIP_ENV_VALIDATION: true
|
||||
- name: Install playwright browsers
|
||||
run: pnpm exec playwright install chromium
|
||||
- name: Run E2E Tests
|
||||
shell: bash
|
||||
run: pnpm test:e2e
|
||||
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Setup
|
||||
uses: ./tooling/github/setup
|
||||
- name: Copy env
|
||||
shell: bash
|
||||
run: cp .env.example .env
|
||||
- name: Build
|
||||
run: pnpm turbo build --affected
|
||||
|
||||
memory-regression:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v4
|
||||
- name: Build Docker image
|
||||
uses: docker/build-push-action@v7
|
||||
with:
|
||||
platforms: linux/amd64
|
||||
context: .
|
||||
push: false
|
||||
load: true
|
||||
tags: homarr-memory-test
|
||||
network: host
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
env:
|
||||
SKIP_ENV_VALIDATION: true
|
||||
- name: Start container and measure memory
|
||||
run: |
|
||||
docker run -d --name homarr-mem-test -p 17575:7575 homarr-memory-test
|
||||
# Wait for container to be healthy (up to 60s)
|
||||
healthy=false
|
||||
for i in $(seq 1 30); do
|
||||
if curl -sf http://localhost:17575/api/health 2>/dev/null; then
|
||||
echo "Container healthy after $((i * 2))s"
|
||||
healthy=true
|
||||
break
|
||||
fi
|
||||
sleep 2
|
||||
done
|
||||
if [ "$healthy" != "true" ]; then
|
||||
echo "ERROR: Container never became healthy"
|
||||
docker logs homarr-mem-test --tail 50
|
||||
exit 1
|
||||
fi
|
||||
# Warm up: hit the dashboard page a few times
|
||||
for i in $(seq 1 5); do
|
||||
curl -sf http://localhost:17575/ > /dev/null 2>&1 || true
|
||||
sleep 2
|
||||
done
|
||||
# Measure RSS after warmup
|
||||
MEM_KB=$(docker stats --no-stream --format '{{.MemUsage}}' homarr-mem-test | sed 's| /.*||')
|
||||
echo "Raw memory: $MEM_KB"
|
||||
# Convert to numeric MB (handles GiB and MiB suffixes)
|
||||
MEM_NUM=$(echo "$MEM_KB" | awk '{
|
||||
if ($0 ~ /GiB/) { gsub(/GiB/,""); print $1 * 1024 }
|
||||
else if ($0 ~ /MiB/) { gsub(/MiB/,""); print $1 }
|
||||
else if ($0 ~ /KiB/) { gsub(/KiB/,""); print $1 / 1024 }
|
||||
else { print $1 / 1048576 }
|
||||
}')
|
||||
echo "Memory usage: ${MEM_NUM} MB"
|
||||
MAX_MB=700
|
||||
if awk "BEGIN {exit !($MEM_NUM > $MAX_MB)}"; then
|
||||
echo "ERROR: Memory usage ${MEM_NUM} MB exceeds ${MAX_MB} MB threshold"
|
||||
docker logs homarr-mem-test --tail 50
|
||||
exit 1
|
||||
fi
|
||||
echo "Memory check passed: ${MEM_NUM} MB <= ${MAX_MB} MB"
|
||||
@@ -15,7 +15,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v7
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Obtain token
|
||||
id: obtainToken
|
||||
|
||||
@@ -18,7 +18,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v7
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Upload Crowdin translations
|
||||
uses: crowdin/github-action@v2
|
||||
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
with:
|
||||
args: "Beta release has been triggered: [run ${{ github.run_number }}](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})"
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v7
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Get Next Version
|
||||
|
||||
@@ -57,7 +57,7 @@ jobs:
|
||||
client-id: ${{ secrets.RENOVATE_MERGE_APP_ID }}
|
||||
permission-contents: write # required to commit package.json & changelog changes, merge them to dev / next and publish the release
|
||||
|
||||
- uses: actions/checkout@v7
|
||||
- uses: actions/checkout@v6
|
||||
if: env.SKIP_RELEASE == 'false'
|
||||
with:
|
||||
persist-credentials: false
|
||||
@@ -119,7 +119,7 @@ jobs:
|
||||
outputs:
|
||||
digest: ${{ steps.build.outputs.digest }}
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ needs.release.outputs.git_ref }}
|
||||
|
||||
@@ -148,11 +148,6 @@ jobs:
|
||||
platforms: linux/amd64
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
outputs: type=image,"name=${{ env.GHCR_REPO }}",push-by-digest=true,name-canonical=true,push=true
|
||||
secrets: |
|
||||
TURBO_API=${{ secrets.TURBO_API }}
|
||||
TURBO_TEAM=${{ secrets.TURBO_TEAM }}
|
||||
TURBO_TOKEN=${{ secrets.TURBO_TOKEN }}
|
||||
TURBO_REMOTE_CACHE_SIGNATURE_KEY=${{ secrets.TURBO_REMOTE_CACHE_SIGNATURE_KEY }}
|
||||
env:
|
||||
SKIP_ENV_VALIDATION: true
|
||||
build-arm64:
|
||||
@@ -163,7 +158,7 @@ jobs:
|
||||
outputs:
|
||||
digest: ${{ steps.build.outputs.digest }}
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ needs.release.outputs.git_ref }}
|
||||
|
||||
@@ -192,11 +187,6 @@ jobs:
|
||||
platforms: linux/arm64
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
outputs: type=image,"name=${{ env.GHCR_REPO }}",push-by-digest=true,name-canonical=true,push=true
|
||||
secrets: |
|
||||
TURBO_API=${{ secrets.TURBO_API }}
|
||||
TURBO_TEAM=${{ secrets.TURBO_TEAM }}
|
||||
TURBO_TOKEN=${{ secrets.TURBO_TOKEN }}
|
||||
TURBO_REMOTE_CACHE_SIGNATURE_KEY=${{ secrets.TURBO_REMOTE_CACHE_SIGNATURE_KEY }}
|
||||
env:
|
||||
SKIP_ENV_VALIDATION: true
|
||||
extract-asset-amd64:
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
name: "[Deployment] Documentation"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ["dev"]
|
||||
paths:
|
||||
- "apps/docs/**"
|
||||
- "packages/definitions/src/docs/**"
|
||||
- ".github/workflows/deployment-docs.yml"
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pages: write
|
||||
id-token: write
|
||||
|
||||
concurrency:
|
||||
group: "pages"
|
||||
cancel-in-progress: false
|
||||
|
||||
env:
|
||||
SKIP_ENV_VALIDATION: true
|
||||
TURBO_API: ${{ secrets.TURBO_API }}
|
||||
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
|
||||
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
|
||||
TURBO_REMOTE_CACHE_SIGNATURE_KEY: ${{ secrets.TURBO_REMOTE_CACHE_SIGNATURE_KEY }}
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- name: Setup
|
||||
uses: ./tooling/github/setup
|
||||
- name: Build docs
|
||||
run: pnpm turbo build --filter=@homarr/docs
|
||||
env:
|
||||
CI: "true"
|
||||
- name: Setup Pages
|
||||
uses: actions/configure-pages@v6
|
||||
- name: Upload pages artifact
|
||||
uses: actions/upload-pages-artifact@v5
|
||||
with:
|
||||
path: "apps/docs/build"
|
||||
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
environment:
|
||||
name: github-pages
|
||||
url: ${{ steps.deployment.outputs.page_url }}
|
||||
steps:
|
||||
- name: Deploy to GitHub Pages
|
||||
id: deployment
|
||||
uses: actions/deploy-pages@v5
|
||||
@@ -29,7 +29,7 @@ jobs:
|
||||
with:
|
||||
args: "Automatic release has been triggered: [run ${{ github.run_number }}](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})"
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v7
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Get Next Version
|
||||
|
||||
@@ -20,7 +20,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Setup
|
||||
uses: ./tooling/github/setup
|
||||
|
||||
@@ -19,7 +19,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
ref: dev
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
ref: dev
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- uses: actions/checkout@v6
|
||||
- name: Validate prebuilt dependencies for amd64
|
||||
id: validate-amd64
|
||||
uses: ./.github/actions/prebuilt-debian
|
||||
@@ -30,7 +30,7 @@ jobs:
|
||||
runs-on: ubuntu-24.04-arm
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- uses: actions/checkout@v6
|
||||
- name: Validate prebuilt dependencies for arm64
|
||||
id: validate-arm64
|
||||
uses: ./.github/actions/prebuilt-debian
|
||||
|
||||
@@ -13,7 +13,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 2
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- uses: actions/checkout@v6
|
||||
- run: |
|
||||
npx --yes --package renovate -- \
|
||||
renovate-config-validator --strict .github/renovate.json5
|
||||
@@ -52,7 +52,7 @@ jobs:
|
||||
permission-contents: write # required to commit to branch
|
||||
permission-pull-requests: write # required to create pr & enable automerge
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v7
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
token: ${{ steps.obtainToken.outputs.token }}
|
||||
ref: dev
|
||||
|
||||
@@ -29,7 +29,7 @@ jobs:
|
||||
permission-pull-requests: write # required to create pr & enable automerge
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v7
|
||||
uses: actions/checkout@v6
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ steps.obtainToken.outputs.token }}
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ jobs:
|
||||
permission-contents: write # required to commit to branch
|
||||
permission-pull-requests: write # required to create pr & enable automerge
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v7
|
||||
uses: actions/checkout@v6
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ steps.obtainToken.outputs.token }}
|
||||
- name: Setup
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
name: Workshop
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened, ready_for_review]
|
||||
paths: &workshop-paths
|
||||
- "apps/workshop/**"
|
||||
- "apps/docs/docusaurus.config.ts"
|
||||
- "apps/docs/package.json"
|
||||
- "apps/docs/src/components/workshop/**"
|
||||
- "apps/docs/src/pages/workshop/**"
|
||||
- "apps/docs/src/theme/Root.tsx"
|
||||
- "apps/docs/static/workshop-*"
|
||||
- "apps/docs/docs/management/workshop/**"
|
||||
- "packages/workshop/**"
|
||||
- "packages/validation/**"
|
||||
- "packages/definitions/**"
|
||||
- "patches/**"
|
||||
- "package.json"
|
||||
- "pnpm-lock.yaml"
|
||||
- "pnpm-workspace.yaml"
|
||||
- "turbo.json"
|
||||
- ".github/workflows/workshop.yml"
|
||||
push:
|
||||
branches: [dev]
|
||||
paths: *workshop-paths
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: workshop-${{ github.ref }}-${{ github.event.pull_request.number || github.sha }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
production-preview:
|
||||
name: Production preview
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- name: Setup
|
||||
uses: ./tooling/github/setup
|
||||
- name: Copy environment defaults
|
||||
run: cp .env.example .env
|
||||
- name: Test Workshop backend
|
||||
run: pnpm test:workshop-backend
|
||||
- name: Build and smoke-test Workshop
|
||||
run: pnpm test:workshop-preview
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
# dependencies
|
||||
node_modules
|
||||
.pnpm-store/
|
||||
.pnp
|
||||
.pnp.js
|
||||
.idea/
|
||||
@@ -71,10 +70,7 @@ apps/nextjs/public/images/background.png
|
||||
|
||||
# personal agent config
|
||||
.opencode
|
||||
.agents/
|
||||
skills-lock.json
|
||||
|
||||
# docusaurus
|
||||
apps/docs/.docusaurus
|
||||
apps/docs/build
|
||||
.pnpm-store
|
||||
@@ -1,87 +0,0 @@
|
||||
---
|
||||
score: 9
|
||||
p0: 0
|
||||
p1: 1
|
||||
timestamp: 2026-07-13T21-26-11Z
|
||||
slug: apps-docs-src-components-workshop
|
||||
---
|
||||
# Workshop V2 audit
|
||||
|
||||
## Purpose
|
||||
|
||||
The Workshop is Homarr's central community catalog for custom widgets and CSS themes. It must let people discover, inspect, export, publish, update, report, and install creations from homarr.dev and from Homarr itself without requiring raw copy-and-paste as the default workflow.
|
||||
|
||||
### Feature North Star
|
||||
|
||||
A Homarr user can move from a useful local creation to a safe community submission, or from a community listing to an installed local creation, in one understandable flow with source visibility and an explicit confirmation boundary.
|
||||
|
||||
### Developer-experience North Star
|
||||
|
||||
Workshop behavior is defined once: one typed contract, one client, versioned PocketBase migrations, deterministic local startup, shared validation fixtures, typed failures, and UI components that contain presentation rather than PocketBase queries or authorization rules.
|
||||
|
||||
## Baseline audit
|
||||
|
||||
The abandoned prototype is preserved at `origin/feat/widget-store-prototype-backup` (`89260b875`). This rewrite starts at `dev` commit `ba7718bde`.
|
||||
|
||||
| Area | Severity | Baseline finding | V2 resolution |
|
||||
| ------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
|
||||
| Branch health | P0 | PR #5984 was based on an obsolete `dev` commit and conflicted with the current branch. | Rebuild from current `dev`; keep the old head on a backup ref. |
|
||||
| Security | P0 | Submission validation ran in browsers and could be bypassed with direct PocketBase writes. | Enforce validation, ownership, account state, immutable fields, and upload limits in PocketBase hooks and rules. |
|
||||
| Moderation | P0 | There were no moderator roles, posting bans, disabled accounts, least-privilege actions, or durable moderation history. | Add roles, account states, explicit moderation routes, a staff UI, and append-only actions. |
|
||||
| Architecture | P1 | Docs and Next.js defined separate record types, queries, mutations, sorting, voting, and error behavior. | Add `@homarr/workshop` as the only public contract and API client. |
|
||||
| Performance | P1 | The browser downloaded every submission and full content, then searched, sorted, and filtered locally. | Server-side pagination/filtering/sorting; summary views exclude content. |
|
||||
| Product | P1 | Website and app flows were disconnected and sharing still depended on manual content transfer. | Add contextual install and direct share actions backed by canonical exports. |
|
||||
| UX/UI | P1 | A large bespoke Docusaurus component set and a separate Mantine modal produced inconsistent interaction and state behavior. | Use each host's native components with shared flows, copy rules, contracts, and states. |
|
||||
| Accessibility | P1 | Several custom interactive elements bypassed semantic controls and touch targets; focus and reduced-motion behavior was incomplete. | Require semantic controls, visible focus, keyboard operation, 44px touch targets, AA contrast, and reduced motion. |
|
||||
| Operations | P1 | A third-party PocketBase image and incomplete bootstrap docs made builds and recovery non-deterministic. | Build a pinned official binary with checksum verification; document health, backup, restore, OAuth, and upgrades. |
|
||||
| Testing | P1 | Tests covered a narrow client schema and did not exercise API rules, roles, auth states, moderation, or cross-surface flows. | Add contract, backend permission-matrix, UI, and end-to-end coverage. |
|
||||
| Documentation | P2 | Documentation advertised a supported self-hosted Workshop even though V2 is a central Homarr service. | Document the central service and local-development overrides only. |
|
||||
|
||||
## Decisions
|
||||
|
||||
- Public name: **Workshop**.
|
||||
- Launch content: custom widgets (`homarr-custom-widget-v2`) and CSS (`homarr-custom-css-v1`).
|
||||
- Identity: GitHub OAuth only.
|
||||
- Community features: votes and reports; comments are removed.
|
||||
- Hosting: one Homarr-operated image and origin at `homarr.dev`; PocketBase serves both compiled docs and `/api`.
|
||||
- Resilience: the public shell and successfully loaded query data are cached independently; the rest of Homarr never depends on Workshop availability.
|
||||
- Publishing: immediate, with source inspection, reporting, and staff removal.
|
||||
- Staff: GitHub-authenticated moderators/admins use `/workshop/admin`; PocketBase superusers are bootstrap/emergency access.
|
||||
|
||||
## Launch acceptance
|
||||
|
||||
- [x] Anonymous users can browse, search, filter, inspect, copy, and download without receiving full content in listing responses.
|
||||
- [x] Members can publish, update, delete, vote, and report; ownership and schemas are enforced server-side.
|
||||
- [x] Posting-banned users cannot create or update submissions but can browse, vote, and report.
|
||||
- [x] Disabled accounts cannot authenticate or write, including with an existing token.
|
||||
- [x] Moderators can resolve reports, remove submissions, posting-ban users, and disable/restore non-admin users.
|
||||
- [x] Only admins can change roles; staff actions are append-only and attributable.
|
||||
- [x] Homarr can export/share local widgets and CSS and validate/confirm Workshop installs.
|
||||
- [x] Website and app implement loading, empty, offline, validation, permission, and destructive-action states.
|
||||
- [x] Keyboard semantics, mobile layout, dark mode, reduced motion, and horizontal overflow received a browser smoke test.
|
||||
- [x] Clean PocketBase startup, migration, health, backup, and restore procedures are reproducible and documented.
|
||||
- [x] Documentation and Workshop API are built into one reproducible image and served from `homarr.dev` without production cross-origin requests.
|
||||
- [ ] Add automated authenticated cross-surface E2E coverage before changing the PR from draft to ready.
|
||||
|
||||
## Deferred after V2
|
||||
|
||||
Comments, private Workshop deployments, automated approval queues, follows, collections, notifications, arbitrary package types, historical version installation, and personalized recommendations.
|
||||
|
||||
## Exit scorecard
|
||||
|
||||
The baseline has three P0, seven P1, and one P2 findings. The rewrite closes every product, security, architecture, moderation, performance, operations, documentation, UX, and DX finding. One P1 release-verification item remains: automated authenticated E2E across both public and in-app surfaces. Keep PR #5984 in draft until it lands.
|
||||
|
||||
| Gate | Result | Evidence |
|
||||
| --------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| Branch ancestry | Pass | Rebuilt from `dev` at `ba7718bde`; prototype preserved at `origin/feat/widget-store-prototype-backup`. |
|
||||
| Contract | Pass | Five Vitest cases cover widget/CSS fixtures, filenames, limits, screenshot validation, and round trips. |
|
||||
| Backend | Pass | Disposable PocketBase integration matrix covers roles, account states, ownership forgery, schema rejection, duplicate votes, private reports, escalation, deletion cascades, and audit snapshots. |
|
||||
| Backend operations | Pass | PocketBase 0.39.6 runs unprivileged and builds with checksum verification; clean boot and a cloned legacy SQLite volume both migrate and become healthy. |
|
||||
| Type safety | Pass | Scoped Turbo typecheck passes for Workshop, Docs, and Next.js. |
|
||||
| Lint | Pass | Scoped lint exits clean; remaining warnings predate the rewrite. Workshop has zero warnings. |
|
||||
| Format | Pass | Every changed supported file passes `oxfmt --check`; repository-wide Docs formatting still reports pre-existing drift. |
|
||||
| Production builds | Pass | Docusaurus and Next.js production builds pass under Node 24.18.0. |
|
||||
| Browser smoke | Pass | Desktop/mobile discovery and the disconnected API state render without uncaught errors; cached data remains readable and retry is explicit. |
|
||||
| Automated cross-surface E2E | Open P1 | Authenticated website-to-Homarr publish/install and moderator flows still need browser automation. |
|
||||
|
||||
Final implementation scores (10 is best): security 9, architecture/DX 9, moderation 9, performance 9, responsive UI 9, theming 9, accessibility 8, automated testing 7. Accessibility remains below 9 until authenticated dialogs and destructive flows receive a full keyboard/screen-reader pass.
|
||||
@@ -1,50 +1,3 @@
|
||||
## [1.70.0](https://github.com/homarr-labs/homarr/compare/v1.69.2...v1.70.0) (2026-07-10)
|
||||
|
||||
## [1.69.2](https://github.com/homarr-labs/homarr/compare/v1.69.1...v1.69.2) (2026-07-06)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **layout:** raise AppShellHeader z-index above Indicator dots ([687edb9](https://github.com/homarr-labs/homarr/commit/687edb9226307cb83986097abb24fe7f672e1778)), closes [#6194](https://github.com/homarr-labs/homarr/issues/6194)
|
||||
|
||||
## [1.69.1](https://github.com/homarr-labs/homarr/compare/v1.69.0...v1.69.1) (2026-07-04)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* unblock query cache mutations in demo mode and add cache buster ([#6176](https://github.com/homarr-labs/homarr/issues/6176)) ([a382c75](https://github.com/homarr-labs/homarr/commit/a382c75968e73bafff55b5890b1e95f0779b50d3))
|
||||
|
||||
## [1.69.0](https://github.com/homarr-labs/homarr/compare/v1.68.1...v1.69.0) (2026-07-03)
|
||||
|
||||
### Features
|
||||
|
||||
* add per-widget secret storage for release provider authentication ([77884a3](https://github.com/homarr-labs/homarr/commit/77884a31794e12a6f7bc59d96c8f61aa429ca68d))
|
||||
* **config:** enable pnpm global virtual store for worktree support ([#6098](https://github.com/homarr-labs/homarr/issues/6098)) ([3864727](https://github.com/homarr-labs/homarr/commit/3864727502a64052c4e3838468094b30bf04a133))
|
||||
* **docker-widget:** add per-column visibility toggle ([#6093](https://github.com/homarr-labs/homarr/issues/6093)) ([0765262](https://github.com/homarr-labs/homarr/commit/076526293d13379004461633d7ba4c2e83bd5ffe))
|
||||
* **docs:** addd in documentation for archiveteam warrior ([f66469d](https://github.com/homarr-labs/homarr/commit/f66469d79797a2da214588f4e8253b03633c59a5))
|
||||
* simplify caching — TanStack Query as single source of truth ([#6075](https://github.com/homarr-labs/homarr/issues/6075)) ([f19033e](https://github.com/homarr-labs/homarr/commit/f19033e0d53d248aed678737f3d04d5d8c7815b5))
|
||||
* **widgets:** right-click context menu with auto-generated toggle actions ([#6126](https://github.com/homarr-labs/homarr/issues/6126)) ([7e58858](https://github.com/homarr-labs/homarr/commit/7e58858353ae866efd657270d4bb16d2c7d3e551))
|
||||
* **widgets:** show calendar events on hover with pin on click ([#2637](https://github.com/homarr-labs/homarr/issues/2637)) ([#6081](https://github.com/homarr-labs/homarr/issues/6081)) ([db3d588](https://github.com/homarr-labs/homarr/commit/db3d58821e4de8d46b110125d0fa36919765248f))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add id to repository schema and handle missing ids gracefully ([8747d5f](https://github.com/homarr-labs/homarr/commit/8747d5f93ee9a295aad3c7035e8c6c7080bf5ee6))
|
||||
* address PR review - atomic upsert, MCP metadata, stale tokens, save UX ([757d598](https://github.com/homarr-labs/homarr/commit/757d59854e5ec82d91c053b8bc3d7eaf2e70efc4))
|
||||
* address PR review findings for widget-only integration refactor ([c892731](https://github.com/homarr-labs/homarr/commit/c892731479b47fab6218bd1f52fa6eccbdaa371e))
|
||||
* address review - paginate GH releases, GitLab tag_name, docs, test ([e687bc8](https://github.com/homarr-labs/homarr/commit/e687bc8c9277797acaa164a09c74b3808a2cb346))
|
||||
* apply version filter to LinuxServer.io provider ([34d976e](https://github.com/homarr-labs/homarr/commit/34d976eeea09e396334ce8fce5c492c07e7dd32b))
|
||||
* catch individual repo fetch failures to prevent batch crashes ([d893515](https://github.com/homarr-labs/homarr/commit/d893515b932608ea08c9c6e90b83f340e40f6f8e))
|
||||
* compile versionRegex once and add fetch timeouts ([64c7512](https://github.com/homarr-labs/homarr/commit/64c75122833981a49e9c8bf1789d501faff6d62d))
|
||||
* **deps:** update dependency postcss to ^8.5.16 ([#6169](https://github.com/homarr-labs/homarr/issues/6169)) ([9252fdc](https://github.com/homarr-labs/homarr/commit/9252fdc13971bd982224a31d7be577112f99ec45))
|
||||
* dns-hole widgets showing stale disconnected state ([#6096](https://github.com/homarr-labs/homarr/issues/6096)) ([2443f1c](https://github.com/homarr-labs/homarr/commit/2443f1cd3ff6765a91783d60d42aa03d9ba63fcd))
|
||||
* gracefully degrade when widget_secret table is missing or access denied ([30107e4](https://github.com/homarr-labs/homarr/commit/30107e4e76a454ff7402de9a8bc2e0dd6f422aa8))
|
||||
* handle missing widgetSecrets table and add default releases repo ([af35a50](https://github.com/homarr-labs/homarr/commit/af35a50fa05fee2f0118e98727eb108b0200f762))
|
||||
* **immich:** v3 API compatibility — use searchAssets for album assets, drop deviceAssetId ([#5977](https://github.com/homarr-labs/homarr/issues/5977)) ([a5ec880](https://github.com/homarr-labs/homarr/commit/a5ec880eec276e0814d2ab3ed7f9bc0b804d0465))
|
||||
* quick wins — calendar, about, glances, admin UX, releases, iframe, notes ([#6116](https://github.com/homarr-labs/homarr/issues/6116)) ([f9da71a](https://github.com/homarr-labs/homarr/commit/f9da71a19bdfa589aafe26b164d0e0782208ac0a))
|
||||
* restore ArchiveTeam Warrior as a proper integration ([8a5ad58](https://github.com/homarr-labs/homarr/commit/8a5ad5861ba4c2d0978f1fa3f6115a1c5974f8a2))
|
||||
* **timetable:** restore station search by removing disabled query guard ([d670b69](https://github.com/homarr-labs/homarr/commit/d670b6939672932c1a3d17ab9913901491cb46e3))
|
||||
* update CHANGES.md for ArchiveTeam Warrior restoration, remove typo translation key ([62baaf7](https://github.com/homarr-labs/homarr/commit/62baaf78130c4b74e899a948cee17097d8c02593))
|
||||
* verbose error messages in all release providers ([2155f13](https://github.com/homarr-labs/homarr/commit/2155f13f245f601b99458e227b6e058fbfa44cd9))
|
||||
* **widgets:** make downloads widget table background transparent ([#6025](https://github.com/homarr-labs/homarr/issues/6025)) ([73bed51](https://github.com/homarr-labs/homarr/commit/73bed51df895250f415efd7a87e721e403b3fd99))
|
||||
|
||||
## [1.68.1](https://github.com/homarr-labs/homarr/compare/v1.68.0...v1.68.1) (2026-06-30)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
-96
@@ -1,96 +0,0 @@
|
||||
# Changes
|
||||
|
||||
## Goal
|
||||
|
||||
Remove widget-only services from Homarr's global Integration system.
|
||||
|
||||
The affected services are release providers and Search.ch. They are only used by specific widgets, so keeping them as global integrations added unnecessary setup, UI, database rows, docs pages, and integration package code.
|
||||
|
||||
ArchiveTeam Warrior remains a proper integration but is no longer auto-seeded on first install.
|
||||
|
||||
## Why
|
||||
|
||||
These services do not need shared integration state.
|
||||
|
||||
- Release providers are selected per Releases widget repository.
|
||||
- Search.ch is only used by the Timetable widget.
|
||||
|
||||
Moving them into widget options makes configuration simpler and removes dead-weight integration code from `packages/integrations`.
|
||||
|
||||
## What Changed
|
||||
|
||||
### Release Providers
|
||||
|
||||
- Added release provider definitions in `packages/definitions/src/release-provider.ts`.
|
||||
- Removed release providers from `IntegrationKind`.
|
||||
- Moved release-fetching code out of `packages/integrations` and into `packages/request-handler/src/release-providers.ts`.
|
||||
- Updated the Releases widget to store provider configuration directly in widget options:
|
||||
- `provider`
|
||||
- `identifier`
|
||||
- `versionFilter`
|
||||
- `providerUrl`
|
||||
- Updated the Releases widget editor to use a provider select instead of an integration select.
|
||||
- Updated Docker import mapping so `ghcr.io` maps to `gitHubContainerRegistry`.
|
||||
- GitHub Container Registry now uses the public OCI registry API and accepts full image references such as `ghcr.io/homarr-labs/homarr:latest`.
|
||||
|
||||
### Timetable / Search.ch
|
||||
|
||||
- Removed Search.ch as an integration.
|
||||
- Added a Timetable widget `baseUrl` option, defaulting to `https://search.ch`.
|
||||
- Moved Search.ch request/parsing logic into `packages/request-handler/src/timetable.ts`.
|
||||
- Updated the Timetable API to receive `baseUrl` directly from widget options.
|
||||
|
||||
### ArchiveTeam Warrior
|
||||
|
||||
- Kept as a proper integration (not removed).
|
||||
- Removed `defaultUrl` from the integration definition to prevent auto-seeding on first install.
|
||||
|
||||
### Integration Cleanup
|
||||
|
||||
- Removed creators, exports, docs references, and implementation files for the deleted integrations.
|
||||
- Removed unused dependencies from `packages/integrations/package.json`.
|
||||
|
||||
### Database Migration
|
||||
|
||||
- Added `packages/db/migrations/custom/0003_remove_widget_only_integrations.ts`.
|
||||
- The migration moves old integration-linked widget config into widget options, then deletes the old widget-only integration rows.
|
||||
- Updated the earlier release widget migration to use the new `provider` field instead of `providerIntegrationId`.
|
||||
|
||||
### Docs
|
||||
|
||||
- Updated Releases widget docs to describe providers instead of integrations.
|
||||
- Updated Timetable docs to remove Search.ch integration setup.
|
||||
- Removed obsolete integration docs pages for the deleted integration kinds.
|
||||
- Updated docs sitemap and integration docs mappings.
|
||||
|
||||
### Translations
|
||||
|
||||
- Added English labels for the new widget options:
|
||||
- `widget.timetable.option.baseUrl.label`
|
||||
- `widget.releases.option.repositories.providerUrl.label`
|
||||
|
||||
## How It Works Now
|
||||
|
||||
Release provider configuration lives on each Releases widget repository entry. The request handler resolves provider metadata and fetches releases directly from the provider-specific endpoint. GHCR uses anonymous OCI registry access for public packages.
|
||||
|
||||
Timetable passes its widget URL option directly to its API router and request handler. No global integration lookup is needed.
|
||||
|
||||
ArchiveTeam Warrior continues to use the standard integration system, but users must manually add the integration (it is not auto-created on install).
|
||||
|
||||
## Deliberate Non-Goals
|
||||
|
||||
- No provider secret/token support was added.
|
||||
- No new secret storage model was introduced.
|
||||
- No compatibility layer was kept in `packages/integrations`.
|
||||
|
||||
Widget options are client-visible, so storing provider credentials there would be wrong. If authenticated provider requests are needed later, they need a separate secret-storage design.
|
||||
|
||||
## Verification So Far
|
||||
|
||||
- Focused typechecks passed for affected packages.
|
||||
- Full `pnpm turbo typecheck` passed.
|
||||
- Full `pnpm lint` passed with existing warnings.
|
||||
- Focused release provider normalization tests passed.
|
||||
- Widget translation test passed.
|
||||
- Live GHCR smoke checks passed for `ghcr.io/muchobien/pocketbase:latest` and `ghcr.io/homarr-labs/homarr:latest`.
|
||||
- `pnpm test packages/integrations/test/pi-hole.spec.ts` passed earlier, but failed on rebased reruns because Pi-hole reported `domainsBeingBlocked` as `-2`/`0` instead of `> 1`.
|
||||
-27
@@ -19,14 +19,6 @@ packages/integrations/src/anchor @ohmzi
|
||||
apps/docs/docs/integrations/archive-team-warrior @darbyjack
|
||||
packages/integrations/src/archive-team-warrior @darbyjack
|
||||
|
||||
# Bazarr - introduced in https://github.com/homarr-labs/homarr/pull/6156 by @Ayush7614
|
||||
apps/docs/docs/integrations/bazarr @Ayush7614
|
||||
apps/docs/docs/widgets/bazarr @Ayush7614
|
||||
packages/api/src/router/widgets/bazarr.ts @Ayush7614
|
||||
packages/integrations/src/bazarr @Ayush7614
|
||||
packages/request-handler/src/bazarr.ts @Ayush7614
|
||||
packages/widgets/src/bazarr @Ayush7614
|
||||
|
||||
# Audiobookshelf - introduced in https://github.com/homarr-labs/homarr/pull/5910 by @ajnart
|
||||
apps/docs/docs/integrations/audiobookshelf @ajnart
|
||||
packages/integrations/src/audiobookshelf @ajnart
|
||||
@@ -135,13 +127,6 @@ packages/integrations/src/overseerr @manuel-rw
|
||||
apps/docs/docs/integrations/paperless-ngx @ajnart
|
||||
packages/integrations/src/paperless-ngx @ajnart
|
||||
|
||||
# PatchMon - introduced in https://github.com/homarr-labs/homarr/pull/6119 by @mrjoshuap
|
||||
apps/docs/docs/integrations/patchmon @mrjoshuap
|
||||
packages/integrations/src/patchmon @mrjoshuap
|
||||
packages/widgets/src/patchmon @mrjoshuap
|
||||
packages/api/src/router/widgets/patchmon.ts @mrjoshuap
|
||||
packages/request-handler/src/patchmon.ts @mrjoshuap
|
||||
|
||||
# PeaNUT - introduced in https://github.com/homarr-labs/homarr/pull/5862 by @afonsojramos
|
||||
apps/docs/docs/integrations/peanut @afonsojramos
|
||||
packages/integrations/src/peanut @afonsojramos
|
||||
@@ -174,10 +159,6 @@ packages/integrations/src/search-ch @Meierschlumpf
|
||||
apps/docs/docs/integrations/speedtest-tracker @kylegrieder
|
||||
packages/integrations/src/speedtest-tracker @kylegrieder
|
||||
|
||||
# Synology - introduced in https://github.com/homarr-labs/homarr/pull/6121 by @mrjoshuap
|
||||
apps/docs/docs/integrations/synology @mrjoshuap
|
||||
packages/integrations/src/synology @mrjoshuap
|
||||
|
||||
# Technitium DNS - introduced in https://github.com/homarr-labs/homarr/pull/5832 by @lopsch
|
||||
apps/docs/docs/integrations/technitium-dns @lopsch
|
||||
packages/integrations/src/technitium @lopsch
|
||||
@@ -206,14 +187,6 @@ packages/integrations/src/unraid @manuel-rw
|
||||
apps/docs/docs/integrations/uptime-kuma @ajnart
|
||||
packages/integrations/src/uptime-kuma @ajnart
|
||||
|
||||
# Traefik - by @Nerdy-Technician
|
||||
apps/docs/docs/integrations/traefik @Nerdy-Technician
|
||||
apps/docs/docs/widgets/traefik @Nerdy-Technician
|
||||
packages/api/src/router/widgets/traefik.ts @Nerdy-Technician
|
||||
packages/integrations/src/traefik @Nerdy-Technician
|
||||
packages/request-handler/src/traefik.ts @Nerdy-Technician
|
||||
packages/widgets/src/traefik @Nerdy-Technician
|
||||
|
||||
# Exempt Renovate-managed files (no owners)
|
||||
package.json
|
||||
**/package.json
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
# Workshop design system
|
||||
|
||||
Workshop is a trustworthy community workbench inside Homarr, not a separate storefront brand.
|
||||
|
||||
## Visual direction
|
||||
|
||||
- Reuse the host surface: Docusaurus tokens on `homarr.dev`, Mantine components inside Homarr.
|
||||
- Use Homarr red only for primary action and focus; warnings use restrained amber and errors use red with explanatory text.
|
||||
- Keep surfaces flat, bordered, and readable. Cards may lift by 3px on pointer hover; no gradients, glass panels, or decorative dashboards.
|
||||
- Typography follows each host. Headings are compact and confident; body copy stays warm, concrete, and short.
|
||||
- Spacing follows an 8px rhythm, 12-16px corners, and 44px minimum controls.
|
||||
|
||||
## Interaction direction
|
||||
|
||||
- The catalog progression is discover → inspect → confirm → use.
|
||||
- Every remote state has a named loading, empty, cached, unavailable, validation, permission, and success outcome.
|
||||
- Community source is never executed on the website. Installation always follows source inspection and local validation.
|
||||
- Motion is limited to 150-200ms hover/state feedback and skeleton loading, with reduced-motion fallbacks.
|
||||
- Native dialogs or Mantine modals own focus, Escape, backdrop dismissal, and keyboard order.
|
||||
|
||||
## Voice
|
||||
|
||||
Clear, dependable, and community-minded. Explain what remains usable during failures and provide one direct recovery action. Avoid playful language during moderation, destructive actions, or service failures.
|
||||
+13
-32
@@ -1,44 +1,24 @@
|
||||
# syntax=docker/dockerfile:1.25
|
||||
|
||||
FROM node:24.18.0-alpine AS base
|
||||
|
||||
FROM base AS builder
|
||||
RUN apk add --no-cache libc6-compat
|
||||
RUN apk update
|
||||
|
||||
# Set working directory
|
||||
WORKDIR /app
|
||||
ARG CI='true'
|
||||
RUN apk add --no-cache libc6-compat curl bash && apk update
|
||||
|
||||
RUN corepack enable pnpm
|
||||
COPY pnpm-lock.yaml pnpm-workspace.yaml package.json ./
|
||||
COPY patches ./patches
|
||||
# Workaround for pnpm/pnpm#5268: pnpm fetch crashes when patchedDependencies
|
||||
# are configured with nodeLinker: hoisted. The applyPatchToDir function tries
|
||||
# to chdir into node_modules/<pkg> which doesn't exist during fetch (only the
|
||||
# content-addressable store is populated). By temporarily switching to the
|
||||
# isolated linker, patches apply inside the virtual store (node_modules/.pnpm/...)
|
||||
# which IS created by pnpm fetch. The original hoisted linker is restored
|
||||
# before the install step so the final node_modules layout stays flat.
|
||||
RUN --mount=type=cache,id=pnpm-store,target=/pnpm/store \
|
||||
pnpm config set store-dir /pnpm/store && \
|
||||
sed -i 's/nodeLinker: hoisted/nodeLinker: isolated/' pnpm-workspace.yaml && \
|
||||
pnpm fetch && \
|
||||
sed -i 's/nodeLinker: isolated/nodeLinker: hoisted/' pnpm-workspace.yaml
|
||||
|
||||
RUN apk add --no-cache libc6-compat curl bash
|
||||
RUN apk update
|
||||
COPY . .
|
||||
# Follow the pnpm fetch pattern from https://pnpm.io/cli/fetch
|
||||
# --offline enforces no registry access (all packages are in the store from fetch)
|
||||
# --frozen-lockfile is omitted as recommended by the pnpm fetch docs
|
||||
RUN --mount=type=cache,id=pnpm-store,target=/pnpm/store \
|
||||
pnpm install --recursive --offline
|
||||
|
||||
RUN corepack enable pnpm && pnpm install --recursive --frozen-lockfile
|
||||
|
||||
# Copy static data as it is not part of the build
|
||||
COPY static-data ./static-data
|
||||
ARG SKIP_ENV_VALIDATION='true'
|
||||
ARG CI='true'
|
||||
ARG DISABLE_REDIS_LOGS='true'
|
||||
ARG TARGETPLATFORM
|
||||
|
||||
RUN --mount=type=secret,id=TURBO_API,env=TURBO_API \
|
||||
--mount=type=secret,id=TURBO_TEAM,env=TURBO_TEAM \
|
||||
--mount=type=secret,id=TURBO_TOKEN,env=TURBO_TOKEN \
|
||||
--mount=type=secret,id=TURBO_REMOTE_CACHE_SIGNATURE_KEY,env=TURBO_REMOTE_CACHE_SIGNATURE_KEY \
|
||||
TURBO_PLATFORM="${TARGETPLATFORM:-linux/amd64}" pnpm build
|
||||
RUN corepack enable pnpm && pnpm build
|
||||
|
||||
FROM base AS runner
|
||||
WORKDIR /app
|
||||
@@ -82,6 +62,7 @@ COPY nginx.conf /etc/nginx/templates/nginx.conf
|
||||
ENV DB_URL='/appdata/db/db.sqlite'
|
||||
ENV DB_DIALECT='sqlite'
|
||||
ENV DB_DRIVER='better-sqlite3'
|
||||
ENV AUTH_PROVIDERS='credentials'
|
||||
ENV REDIS_IS_EXTERNAL='false'
|
||||
ENV NODE_ENV='production'
|
||||
|
||||
|
||||
-33
@@ -1,33 +0,0 @@
|
||||
# Product
|
||||
|
||||
## Register
|
||||
|
||||
product
|
||||
|
||||
## Users
|
||||
|
||||
Homarr serves people who organize and operate self-hosted services, from individual home-lab users to administrators managing shared instances. Workshop also serves community creators who publish and install custom widgets and CSS themes. Users need the core dashboard to remain useful even when optional community services are offline.
|
||||
|
||||
## Product Purpose
|
||||
|
||||
Homarr provides a dependable dashboard for discovering, organizing, and operating applications and integrations. Workshop adds a central community catalog where users can inspect, export, publish, report, and install creations with clear source visibility and explicit confirmation.
|
||||
|
||||
## Brand Personality
|
||||
|
||||
Clear, dependable, and community-minded. Interface copy should be direct and practical, with enough context for users to recover from failures without exposing internal implementation details.
|
||||
|
||||
## Anti-references
|
||||
|
||||
Avoid fragmented feature surfaces, generic failure messages, decorative dashboard patterns, unexpected network dependencies, and optional services that can crash or block core Homarr workflows.
|
||||
|
||||
## Design Principles
|
||||
|
||||
- Keep core Homarr workflows available when optional services fail.
|
||||
- Explain failures in user terms and always offer a useful recovery action.
|
||||
- Use native Homarr patterns and shared contracts instead of feature-specific interaction systems.
|
||||
- Make community content inspectable and require explicit confirmation before installation.
|
||||
- Keep operational behavior deterministic for contributors and instance administrators.
|
||||
|
||||
## Accessibility & Inclusion
|
||||
|
||||
Meet WCAG 2.1 AA for contrast, focus, semantics, and keyboard operation. Respect reduced-motion preferences, preserve useful layouts at 200% zoom and on mobile, and ensure status changes and recovery actions are understandable without relying on color alone.
|
||||
@@ -28,7 +28,6 @@ Read our [glossary](/docs/getting-started/glossary) if you are new to Homarr.
|
||||
Congratulations on installing Homarr. Now it's time to get yourself familiar with the concepts of Homarr and how to use it.
|
||||
|
||||
## Onboarding
|
||||
|
||||
The onboard will show the first time you start Homarr.
|
||||
This process will help you with your first steps and adding some initial data, such as the administrator account.
|
||||
|
||||
@@ -46,68 +45,50 @@ You can either import a previous dashboard using the ZIP archive or start from s
|
||||
You may refresh anytime and continue from a different device - the onboarding is saved immediately on the server for your convenience.
|
||||
|
||||
### Starting from scratch
|
||||
|
||||
Depending on your auth provider configuration you will either first see the admin user creation form or the external group creation form.
|
||||
If the credentials provider was selected, you'll be prompted to create an admin user for Homarr.
|
||||
This user cannot be deleted and has full control over Homarr and all of its boards.
|
||||
You must choose a username and password (both can later be changed).
|
||||
We enforce strict password requirements when you enter your password to protect your instance if you accidentally
|
||||
expose it to the internet.
|
||||
If credentials provider was selected, you'll be prompted to create an admin user for Homarr.
|
||||
This user cannot be deleted and has full control over Homarr and all of it's boards.
|
||||
You must choose an username and password (both can later be changed).
|
||||
We enforce strict password requirements, when you enter your password, to protect your instance if you accidentially
|
||||
expose it to the Internet.
|
||||
|
||||
<img
|
||||
src={imageStartFromScratchStepAdminUser}
|
||||
alt={"Screenshot form to create admin user"}
|
||||
width={300}
|
||||
/>
|
||||
<img src={imageStartFromScratchStepAdminUser} alt={"Screenshot form to create admin user"} width={300} />
|
||||
|
||||
If you selected external providers (such as LDAP or OIDC), you'll be prompted to create an admin group.
|
||||
This group will be used to synchronize users from your provider to Homarr.
|
||||
When a user logs in through the provider, any of their groups that match groups in Homarr will be assigned to the user.
|
||||
The name of the group must match exactly the name of the group in the provider.
|
||||
This group will be used to synchronize users from your provider to Homarr.
|
||||
When a user is logged in through the provider the groups of him that match groups with any in Homarr will be added to the user.
|
||||
The name of the group has to match exactly the name of the group in the provider.
|
||||
|
||||
<img
|
||||
src={imageStartFromScratchStepExternalAdminGroup}
|
||||
alt={"Screenshot form to create external admin group"}
|
||||
width={300}
|
||||
/>
|
||||
<img src={imageStartFromScratchStepExternalAdminGroup} alt={"Screenshot form to create external admin group"} width={300} />
|
||||
|
||||
Next, you can configure some basic settings for Homarr. We recommend to leave all settings at default for most users.
|
||||
Here you can also disable analytics. Please note that our analytics are completly anonymous and is fully compliant with GDPR and PECR.
|
||||
|
||||
<img
|
||||
src={imageStartFromScratchStepServerSettings}
|
||||
alt={"Screenshot of analytics and indexing settings in the onboarding"}
|
||||
width={300}
|
||||
/>
|
||||
<img src={imageStartFromScratchStepServerSettings} alt={"Screenshot of analytics and indexing settings in the onboarding"} width={300} />
|
||||
|
||||
### Setting up integrations
|
||||
|
||||
After configuring server settings, the onboarding guides you through setting up your integrations.
|
||||
|
||||
#### Docker service discovery
|
||||
|
||||
If Docker is available (socket mounted), Homarr automatically detects running containers and matches them to known integration types.
|
||||
Matching integrations are pre-selected in the integration list, saving you from manually picking each one.
|
||||
|
||||
#### URL templating
|
||||
|
||||
You can define a base URL template to auto-fill integration URLs in bulk.
|
||||
Two modes are supported:
|
||||
|
||||
- **Subdomain mode** - e.g. `{app}.example.com` where `{app}` is replaced with the integration name
|
||||
- **Port mode** - e.g. `192.168.1.1:{port}` where `{port}` is replaced with the integration's default port
|
||||
|
||||
#### API key helper links
|
||||
|
||||
For integrations that require API keys (such as Sonarr, Radarr, SABnzbd, or Seerr), a helper link opens the relevant settings page in the target application so you can quickly copy your key.
|
||||
|
||||
#### Creating apps from Docker containers
|
||||
|
||||
You can optionally auto-create bookmark apps for all discovered Docker containers, making them immediately available on your board.
|
||||
|
||||
:::note
|
||||
|
||||
Release providers and Search.ch are configured directly in their widgets instead of the integrations page.
|
||||
Some niche and registry integrations (GitHub, npm, Docker Hub, GitLab, Codeberg, LinuxServer.io, GHCR, Quay, Search.ch) are hidden from the onboarding selection grid but remain available on the manage integrations page.
|
||||
|
||||
:::
|
||||
|
||||
@@ -116,41 +97,30 @@ The onboarding can no longer be started again.
|
||||
From here you can click on one of the provided links to continue your journey.
|
||||
We recommend you to continue with the section [creating your first board on this page](#creating-your-first-board).
|
||||
|
||||
<img
|
||||
src={imageStartFromScratchStepFinish}
|
||||
alt={"Screenshot of the finish onboarding process with links"}
|
||||
width={300}
|
||||
/>
|
||||
<img src={imageStartFromScratchStepFinish} alt={"Screenshot of the finish onboarding process with links"} width={300} />
|
||||
|
||||
### Importing a ZIP from version before 1.0.0
|
||||
|
||||
This is recommended when you used Homarr before and want to re-import your data.
|
||||
It is important that you first migrate correctly using [the list of breaking changes for 1.0](/blog/2024/09/23/version-1.0).
|
||||
|
||||
<img
|
||||
src={manageExportOldHomarr}
|
||||
alt="Export from old Homarr"
|
||||
style={{ maxWidth: 500 }}
|
||||
/>
|
||||
<img src={manageExportOldHomarr} alt="Export from old Homarr" style={{ maxWidth: 500 }} />
|
||||
|
||||
**How to export and import data**
|
||||
|
||||
1. Navigate to your old Homarr, open your management pages, open the tools submenu and open `Migrate to 1.0`.
|
||||
1. Navigate to your old Homarr, open your management pages, open the tools submenu and open ``Migrate to 1.0``.
|
||||
2. Check the contents that you want to export. We recommend you to export all data.
|
||||
3. Click on the export data button
|
||||
4. Save the ZIP file, that is being downloaded, on your device. If you selected "integrations" or "users", you also get an encryption / decryption key.
|
||||
Copy it and do not loose it!
|
||||
5. Go to your new >1.0.0 Homarr instance and start the onboarding. Choose `Import from Homarr before 1.0`
|
||||
Copy it and do not loose it!
|
||||
5. Go to your new >1.0.0 Homarr instance and start the onboarding. Choose ``Import from Homarr before 1.0``
|
||||
6. Drag and drop your previously downloaded ZIP archive into the file upload: <br/><img src={manageImportOldHomarrDropzone} style={{ maxWidth: 500 }} />
|
||||
7. Next, choose the data that you want to import and how to import it. Since screen sizes were removed in 1.0, we create a new board for every screen size by default. We recommend you to only keep the best layout and adjust it later in 1.0.0 - but you can also import all of them and pick later.<br/><img src={manageImportOldHomarrChooseData} style={{ maxWidth: 500 }} />
|
||||
8. If you previously exported integrations or users, you will be asked to decrypt the data safely using your obtained encryption token. Paste into the modal and confirm your action.<br/><img src={manageImportDecryptionToken} style={{ maxWidth: 500 }} />
|
||||
9. Next, you can [follow the same setup as when starting from scratch](#starting-from-scratch). After you completed the onboarding, all of your data will be migrated and available.
|
||||
|
||||
## Creating your first board
|
||||
|
||||
In Homarr, you organize your apps and widgets on boards.
|
||||
Boards can be organized using the interactive drag and drop.
|
||||
Let's create your first board by navigating here: `Manage` > `Boards` > `New board`:
|
||||
Let's create your first board by navigating here: ``Manage`` > ``Boards`` > ``New board``:
|
||||

|
||||
|
||||
A board must always have a name and can either be public or private.
|
||||
@@ -163,13 +133,12 @@ Congratulations - you've just created your first board. You can see boards, that
|
||||

|
||||
|
||||
## Add your first app to Homarr
|
||||
|
||||
To add links to your dashboard, you must first add them as an "app".
|
||||
Navigate to this page to add an app:
|
||||
|
||||

|
||||
|
||||
Next, click on the `New app` button. You'll need to provide a name and an icon to create an app.
|
||||
Next, click on the ``New app`` button. You'll need to provide a name and an icon to create an app.
|
||||
The name must be at least one character long but you can provide any image URL for the icon.
|
||||
Homarr ships with over 11K icons - so you can just search by entering a search text instead.
|
||||
|
||||
@@ -180,9 +149,8 @@ After you created the app, you'll see in on the apps page:
|
||||

|
||||
|
||||
## Arrange and organize your board
|
||||
|
||||
Let's add your freshly created app, from the previous step, to the board.
|
||||
To do this, edit your previously created board by clicking on the big `Open board` button:
|
||||
To do this, edit your previously created board by clicking on the big ``Open board`` button:
|
||||
|
||||

|
||||
|
||||
@@ -197,38 +165,23 @@ Here you have the following options in the header:
|
||||
|
||||
When entering the edit mode, you'll get some additional buttons to work with:
|
||||
|
||||
<img
|
||||
src={manageBoardHeaderEditMode}
|
||||
alt={"Screenshot of buttons in the edit mode"}
|
||||
width={200}
|
||||
/>
|
||||
<img src={manageBoardHeaderEditMode} alt={"Screenshot of buttons in the edit mode"} width={200} />
|
||||
|
||||
- **Add item**: Click this button to add new elements to your page.
|
||||
- **Exit edit mode**: Click this btuton to save your changes and exit edit mode.
|
||||
|
||||
To continue, click the option "New Item":
|
||||
|
||||
<img
|
||||
src={manageBoardHeaderAddItem}
|
||||
alt={"Screenshot of the add item menu"}
|
||||
width={200}
|
||||
/>
|
||||
<img src={manageBoardHeaderAddItem} alt={"Screenshot of the add item menu"} width={200} />
|
||||
|
||||
Next, you can choose what item you'd like to add.
|
||||
We will discuss the other options in the following sections - for now choose "App" to continue.
|
||||
|
||||
<img
|
||||
src={manageBoardHeaderChooseItem}
|
||||
alt={"Screenshot of choosing the app item in the modal"}
|
||||
width={300}
|
||||
/>
|
||||
<img src={manageBoardHeaderChooseItem} alt={"Screenshot of choosing the app item in the modal"} width={300} />
|
||||
|
||||
As soon as you chose the app, the modal will close and you'll get the following item on your board:
|
||||
|
||||
<img
|
||||
src={manageBoardNoApp}
|
||||
alt="Screenshot of the No app element on the board"
|
||||
/>
|
||||
<img src={manageBoardNoApp} alt="Screenshot of the No app element on the board" />
|
||||
|
||||
This is expected, because you haven't told Homarr what app it should show in this item.
|
||||
To configure this, click on the small 3 dots at the top of the item in the edit mode and choose "Edit item" in the menu.
|
||||
@@ -275,18 +228,17 @@ Be sure to save your changes by exiting edit mode!
|
||||

|
||||
|
||||
## Adding your first integration
|
||||
|
||||
Now it's time to integrate some basic data into your board.
|
||||
To do this, we first need to create an integration.
|
||||
An integration is a connection between Homarr and a third party application - usually something that you run and deploy yourself.
|
||||
|
||||
Navigate to `Manage` > `Integrations` (for example via your user menu).
|
||||
Navigate to ``Manage`` > ``Integrations`` (for example via your user menu).
|
||||
As an example, we are going to use the public demo instance of **Dash.** - a popular application to monitor CPU / hardware usage.
|
||||
Choose the **Dash.** integration from the list of available integrations:
|
||||
|
||||

|
||||
|
||||
Next, choose a name (or leave it at default), enter `https://dash.mauz.dev/` as the URL in the form and click on `Test connection and create`.
|
||||
Next, choose a name (or leave it at default), enter ``https://dash.mauz.dev/`` as the URL in the form and click on ``Test connection and create``.
|
||||
Pressing the save button will perform a test request to the integration, to confirm that the networking setup is working as expected.
|
||||
Sometimes invalid configurations, firewalls and other mistakes can lead to connectivity problems - [follow this guide here](/docs/management/integrations/#troubleshooting-connection) if you experience issues.
|
||||
|
||||
@@ -302,14 +254,13 @@ the target application or your router.
|
||||
:::
|
||||
|
||||
## Embed integration data using widgets
|
||||
|
||||
Let's add your integration, that you have configured in the section above, as a widget to your board.
|
||||
First, navigate back to your board that you want to add it to.
|
||||
Next, enter edit mode again and add another item to your board:
|
||||
|
||||

|
||||
|
||||
Instead of choosing an app, let's choose `System Health Monitoring` this time.
|
||||
Instead of choosing an app, let's choose ``System Health Monitoring`` this time.
|
||||
Click on "Add to board":
|
||||
|
||||

|
||||
@@ -339,12 +290,10 @@ As with every change, don't forget to save your changes using the exit edit mode
|
||||

|
||||
|
||||
## Grouping your items
|
||||
|
||||
As the last step, let's organize your freshly created items using two different options.
|
||||
You can choose either or both of them on your boards.
|
||||
|
||||
### Using dynamic sections
|
||||
|
||||
Dynamic sections are a simple, resizable visual group to group items.
|
||||
|
||||

|
||||
@@ -354,7 +303,6 @@ After adding a dynamic section, you can simply drag and drop items into the sect
|
||||

|
||||
|
||||
### Using categories
|
||||
|
||||
Categories are more advanced and complicated than dynamic sections.
|
||||
To create a category, open the add item modal and choose "new category":
|
||||
|
||||
@@ -381,27 +329,22 @@ You cannot reduce their horizontal width.
|
||||
After completing the onboarding, Homarr provides interactive guided tours to help you discover key features.
|
||||
|
||||
### Management tour
|
||||
|
||||
When you first visit the management area (`/manage`), Homarr walks you through the main pages: boards, apps, integrations, and users (admin only).
|
||||
Each page highlights the list view and the create action so you know where to find and add things.
|
||||
|
||||
### Board tour
|
||||
|
||||
When you first open a board, a tour highlights the board header elements: search, edit mode, settings, board switcher, and user menu.
|
||||
|
||||
### Tour controls
|
||||
|
||||
- Press **Enter** (or click **Next**) to advance through tour steps
|
||||
- Tours auto-start on first visit and do not repeat once completed
|
||||
- Tours are skipped on mobile screens (below `sm` breakpoint)
|
||||
- You can **reset tours** from the user settings page under **Manage > Users > [Your user] > General**
|
||||
|
||||
## Next steps
|
||||
|
||||
You're done with the basic setup 🔥. Now it's time to organize and customize even more.
|
||||
We highly recommend you to experiment with different options, styles and configurations.
|
||||
See these curated pages for your next steps:
|
||||
|
||||
- Create new apps using the [apps management](/docs/management/apps/)
|
||||
- Customize your board or add more boards using the [boards management](/docs/management/boards/)
|
||||
- Integrate software that you use using the [integrations management](/docs/management/integrations/)
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
|
||||
<img src="https://raw.githubusercontent.com/homarr-labs/charts/refs/heads/main/charts/homarr/icon.svg" align="right" width="92" alt="homarr logo">
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
A Helm chart to deploy homarr for Kubernetes
|
||||
|
||||
@@ -429,7 +429,7 @@ All available values are listed on the [artifacthub](https://artifacthub.io/pack
|
||||
| httproute.rules[0].matches[0].path.value | string | `"/"` | Path value to match |
|
||||
| image.pullPolicy | string | `"IfNotPresent"` | Image pull policy |
|
||||
| image.repository | string | `"ghcr.io/homarr-labs/homarr"` | Image repository |
|
||||
| image.tag | string | `"v1.70.0"` | Overrides the image tag whose default is the chart appVersion |
|
||||
| image.tag | string | `"v1.60.0"` | Overrides the image tag whose default is the chart appVersion |
|
||||
| imagePullSecrets | list | `[]` | Secrets for Docker registry |
|
||||
| ingress.annotations | object | `{}` | Ingress annotations |
|
||||
| ingress.enabled | bool | `false` | Enable ingress |
|
||||
|
||||
@@ -4,6 +4,7 @@ description: "AdGuard Home is a network-wide software for blocking ads and track
|
||||
hide_title: true
|
||||
---
|
||||
|
||||
import { IntegrationData } from '@site/src/components/integrations/data';
|
||||
import { IntegrationHeader } from '@site/src/components/integrations/header';
|
||||
import { IntegrationCapabilites } from '@site/src/components/integrations/widgets';
|
||||
import { AddingIntegration } from '@site/src/components/integrations/adding';
|
||||
@@ -18,6 +19,10 @@ import { dnsHoleControlsWidget } from '@site/docs/widgets/dns-hole-controls';
|
||||
categories={['DNS Hole']}
|
||||
/>
|
||||
|
||||
### Data
|
||||
|
||||
<IntegrationData integration={adguardHomeIntegration} />
|
||||
|
||||
### Widgets & Capabilities
|
||||
<IntegrationCapabilites
|
||||
items={[{
|
||||
|
||||
@@ -6,4 +6,5 @@ export const adguardHomeIntegration: IntegrationDefinition = {
|
||||
"AdGuard Home is a network-wide software for blocking ads and trackers, providing a safer and faster internet experience.",
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/adguard-home.svg",
|
||||
path: "../../integrations/adguard-home",
|
||||
data: "Fetches DNS query statistics, blocking status, and filtering rules from AdGuard Home.",
|
||||
};
|
||||
@@ -4,6 +4,7 @@ description: "Anchor is a self-hosted note-taking application."
|
||||
hide_title: true
|
||||
---
|
||||
|
||||
import { IntegrationData } from '@site/src/components/integrations/data';
|
||||
import { IntegrationHeader } from '@site/src/components/integrations/header';
|
||||
import { IntegrationCapabilites } from '@site/src/components/integrations/widgets';
|
||||
import { AddingIntegration } from '@site/src/components/integrations/adding';
|
||||
@@ -19,6 +20,10 @@ import { anchorNoteWidget } from '@site/docs/widgets/anchor-note';
|
||||
|
||||
The Anchor integration lets Homarr connect to your Anchor instance so you can display and edit selected notes directly from your dashboard.
|
||||
|
||||
### Data
|
||||
|
||||
<IntegrationData integration={anchorIntegration} />
|
||||
|
||||
### Widgets & Capabilities
|
||||
<IntegrationCapabilites
|
||||
items={[{
|
||||
|
||||
@@ -6,4 +6,5 @@ export const anchorIntegration: IntegrationDefinition = {
|
||||
"Anchor is a self-hosted note-taking application. The Anchor integration lets Homarr display and edit selected notes directly from the dashboard.",
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/anchor.svg",
|
||||
path: "../../integrations/anchor",
|
||||
data: "Fetches and updates notes from an Anchor note-taking server.",
|
||||
};
|
||||
@@ -4,6 +4,7 @@ description: "ArchiveTeam Warrior is an easy-to-run virtual machine that helps p
|
||||
hide_title: true
|
||||
---
|
||||
|
||||
import { IntegrationData } from "@site/src/components/integrations/data";
|
||||
import { IntegrationHeader } from "@site/src/components/integrations/header";
|
||||
import { AddingIntegration } from "@site/src/components/integrations/adding";
|
||||
import { IntegrationSecrets } from "@site/src/components/integrations/secrets";
|
||||
@@ -13,6 +14,10 @@ import { archiveTeamWarriorIntegration } from ".";
|
||||
|
||||
The ArchiveTeam Warrior integration lets Homarr connect to your ArchiveTeam Warrior instance so you can manage and monitor your archiving tasks directly from your dashboard.
|
||||
|
||||
### Data
|
||||
|
||||
<IntegrationData integration={archiveTeamWarriorIntegration} />
|
||||
|
||||
### Adding the integration
|
||||
|
||||
<AddingIntegration />
|
||||
|
||||
@@ -6,4 +6,5 @@ export const archiveTeamWarriorIntegration: IntegrationDefinition = {
|
||||
"ArchiveTeam Warrior is an easy-to-run virtual machine that helps preserve websites by using some of your bandwidth and disk space to download and upload content to ArchiveTeam’s archive.",
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/selfhst/icons/png/archiveteam-warrior.png",
|
||||
path: "../../integrations/archiveteam-warrior",
|
||||
data: "Fetches Archive Team Warrior status including project, bandwidth stats, and item progress via WebSocket.",
|
||||
};
|
||||
@@ -4,6 +4,7 @@ description: "Lightweight multiprotocol & multi-source command-line download uti
|
||||
hide_title: true
|
||||
---
|
||||
|
||||
import { IntegrationData } from '@site/src/components/integrations/data';
|
||||
import { IntegrationHeader } from '@site/src/components/integrations/header';
|
||||
import { IntegrationCapabilites } from '@site/src/components/integrations/widgets';
|
||||
import { AddingIntegration } from '@site/src/components/integrations/adding';
|
||||
@@ -16,6 +17,10 @@ import { downloadsWidget } from '@site/docs/widgets/downloads';
|
||||
categories={['Download', 'Multiprotocol', 'Multi-source']}
|
||||
/>
|
||||
|
||||
### Data
|
||||
|
||||
<IntegrationData integration={aria2Integration} />
|
||||
|
||||
### Widgets & Capabilities
|
||||
<IntegrationCapabilites
|
||||
items={[{
|
||||
|
||||
@@ -6,4 +6,5 @@ export const aria2Integration: IntegrationDefinition = {
|
||||
"Lightweight multi-protocol & multi-source command-line download utility. It supports HTTP/HTTPS, FTP, SFTP, BitTorrent and Metalink",
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/PapirusDevelopmentTeam/papirus_icons@latest/src/system_downloads_3.svg",
|
||||
path: "../../integrations/aria2",
|
||||
data: "Manages and monitors Aria2 downloads including active, waiting, and stopped transfers.",
|
||||
};
|
||||
@@ -4,6 +4,7 @@ description: "Audiobookshelf is a self-hosted audiobook and podcast server that
|
||||
hide_title: true
|
||||
---
|
||||
|
||||
import { IntegrationData } from '@site/src/components/integrations/data';
|
||||
import { IntegrationHeader } from '@site/src/components/integrations/header';
|
||||
import { IntegrationCapabilites } from '@site/src/components/integrations/widgets';
|
||||
import { AddingIntegration } from '@site/src/components/integrations/adding';
|
||||
@@ -19,6 +20,10 @@ import { audioStatsWidget } from '@site/docs/widgets/audio-stats';
|
||||
|
||||
Audiobookshelf provides library statistics including audiobook and podcast counts, listening time, and active sessions.
|
||||
|
||||
### Data
|
||||
|
||||
<IntegrationData integration={audiobookshelfIntegration} />
|
||||
|
||||
### Widgets & Capabilities
|
||||
<IntegrationCapabilites
|
||||
items={[
|
||||
|
||||
@@ -6,4 +6,5 @@ export const audiobookshelfIntegration: IntegrationDefinition = {
|
||||
"Audiobookshelf is a self-hosted audiobook and podcast server that lets you manage and stream your audio library.",
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/audiobookshelf.svg",
|
||||
path: "../../integrations/audiobookshelf",
|
||||
data: "Fetches library statistics, listening time, and active sessions from Audiobookshelf.",
|
||||
};
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 1.3 MiB |
@@ -1,54 +0,0 @@
|
||||
---
|
||||
title: 'Bazarr'
|
||||
description: "Bazarr is a companion application to Sonarr and Radarr that manages and downloads subtitles."
|
||||
hide_title: true
|
||||
---
|
||||
|
||||
import { IntegrationHeader } from '@site/src/components/integrations/header';
|
||||
import { IntegrationCapabilites } from '@site/src/components/integrations/widgets';
|
||||
import { AddingIntegration } from '@site/src/components/integrations/adding';
|
||||
import { IntegrationSecrets } from '@site/src/components/integrations/secrets';
|
||||
import { bazarrIntegration } from '.';
|
||||
import { bazarrWidget } from '@site/docs/widgets/bazarr';
|
||||
|
||||
<IntegrationHeader
|
||||
integration={bazarrIntegration}
|
||||
categories={['Subtitles', 'Media']}
|
||||
/>
|
||||
|
||||
Bazarr complements Sonarr and Radarr by managing subtitle downloads. The Homarr integration uses your Bazarr API key to fetch missing subtitle counts and health badges.
|
||||
|
||||
### Screenshots
|
||||
|
||||
import apiKeySettings from './img/api-key-settings.png';
|
||||
import widgetOverview from '@site/docs/widgets/bazarr/img/widget-overview.png';
|
||||
|
||||
<div className="flex flex-col gap-4">
|
||||
<div>
|
||||
<p>API key location in Bazarr (<strong>Settings → General → Security</strong>):</p>
|
||||
<img src={apiKeySettings} alt="bazarr-api-key-settings" width={128 * 4} />
|
||||
</div>
|
||||
<div>
|
||||
<p>Bazarr widget on a Homarr board:</p>
|
||||
<img src={widgetOverview} alt="bazarr-widget-on-board" width={128 * 4} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
### Widgets & Capabilities
|
||||
<IntegrationCapabilites
|
||||
items={[{ widget: bazarrWidget }]}
|
||||
/>
|
||||
|
||||
### Adding the integration
|
||||
<AddingIntegration />
|
||||
|
||||
### Secrets
|
||||
You can find your Bazarr API key under **Settings → General → Security**.
|
||||
|
||||
<IntegrationSecrets secrets={[{
|
||||
credentials: ['apiKey'],
|
||||
steps: [
|
||||
"Open Bazarr and go to Settings → General → Security.",
|
||||
"Copy the API key and paste it into Homarr when creating the integration.",
|
||||
],
|
||||
}]} />
|
||||
@@ -1,9 +0,0 @@
|
||||
import { IntegrationDefinition } from "@site/src/types";
|
||||
|
||||
export const bazarrIntegration: IntegrationDefinition = {
|
||||
name: "Bazarr",
|
||||
description:
|
||||
"Bazarr is a companion application to Sonarr and Radarr that manages and downloads subtitles for your media library.",
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/bazarr.svg",
|
||||
path: "../../integrations/bazarr",
|
||||
};
|
||||
@@ -4,6 +4,7 @@ description: "Beszel is a lightweight server monitoring platform with Docker sta
|
||||
hide_title: true
|
||||
---
|
||||
|
||||
import { IntegrationData } from '@site/src/components/integrations/data';
|
||||
import { IntegrationHeader } from '@site/src/components/integrations/header';
|
||||
import { IntegrationCapabilites } from '@site/src/components/integrations/widgets';
|
||||
import { AddingIntegration } from '@site/src/components/integrations/adding';
|
||||
@@ -27,6 +28,10 @@ Beszel monitors your servers in real time via lightweight agents, collecting CPU
|
||||
|
||||
<img src={beszelDashboard} alt="Beszel dashboard overview showing all four widgets" />
|
||||
|
||||
### Data
|
||||
|
||||
<IntegrationData integration={beszelIntegration} />
|
||||
|
||||
### Widgets & Capabilities
|
||||
<IntegrationCapabilites
|
||||
items={[
|
||||
|
||||
@@ -6,4 +6,5 @@ export const beszelIntegration: IntegrationDefinition = {
|
||||
"Beszel is a lightweight server monitoring platform with Docker stats, historical data, and alert functions.",
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/beszel.svg",
|
||||
path: "../../integrations/beszel",
|
||||
data: "Fetches server monitoring data including system stats, containers, SMART devices, alerts, and real-time metrics.",
|
||||
};
|
||||
@@ -0,0 +1,42 @@
|
||||
---
|
||||
title: 'Codeberg'
|
||||
description: "Codeberg is a non-profit, community-driven platform for hosting and collaborating on software projects."
|
||||
hide_title: true
|
||||
---
|
||||
|
||||
import { IntegrationData } from '@site/src/components/integrations/data';
|
||||
import { IntegrationHeader } from '@site/src/components/integrations/header';
|
||||
import { IntegrationCapabilites } from '@site/src/components/integrations/widgets';
|
||||
import { AddingIntegration } from '@site/src/components/integrations/adding';
|
||||
import { IntegrationSecrets } from '@site/src/components/integrations/secrets';
|
||||
import { codebergIntegration } from '.';
|
||||
import { releasesWidget } from '@site/docs/widgets/releases';
|
||||
|
||||
|
||||
<IntegrationHeader
|
||||
integration={codebergIntegration}
|
||||
categories={['Version Control', 'Software']}
|
||||
/>
|
||||
|
||||
### Data
|
||||
|
||||
<IntegrationData integration={codebergIntegration} />
|
||||
|
||||
### Widgets & Capabilities
|
||||
<IntegrationCapabilites
|
||||
items={[{
|
||||
widget: releasesWidget
|
||||
}]}
|
||||
/>
|
||||
|
||||
### Adding the integration
|
||||
<AddingIntegration />
|
||||
|
||||
### Secrets
|
||||
<IntegrationSecrets secrets={[{
|
||||
credentials: [],
|
||||
steps: []
|
||||
}, {
|
||||
credentials: ['personalAccessToken'],
|
||||
steps: []
|
||||
}]} />
|
||||
@@ -0,0 +1,10 @@
|
||||
import { IntegrationDefinition } from "@site/src/types";
|
||||
|
||||
export const codebergIntegration: IntegrationDefinition = {
|
||||
name: "Codeberg",
|
||||
description:
|
||||
"Codeberg is a non-profit, community-driven platform for hosting and collaborating on software projects.",
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/codeberg.svg",
|
||||
path: "../../integrations/codeberg",
|
||||
data: "Fetches latest releases and repository metadata from a Codeberg instance.",
|
||||
};
|
||||
@@ -4,6 +4,7 @@ description: 'Self-hosted PaaS and deployment platform'
|
||||
hide_title: true
|
||||
---
|
||||
|
||||
import { IntegrationData } from '@site/src/components/integrations/data';
|
||||
import { IntegrationHeader } from '@site/src/components/integrations/header';
|
||||
import { IntegrationCapabilites } from '@site/src/components/integrations/widgets';
|
||||
import { AddingIntegration } from '@site/src/components/integrations/adding';
|
||||
@@ -16,6 +17,10 @@ import { coolifyWidget } from '../../widgets/coolify';
|
||||
categories={['System monitoring', 'Deployment']}
|
||||
/>
|
||||
|
||||
### Data
|
||||
|
||||
<IntegrationData integration={coolifyIntegration} />
|
||||
|
||||
### Widgets & Capabilities
|
||||
<IntegrationCapabilites
|
||||
items={[
|
||||
|
||||
@@ -5,4 +5,5 @@ export const coolifyIntegration: IntegrationDefinition = {
|
||||
description: "Self-hosted PaaS and deployment platform",
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/coolify.svg",
|
||||
path: "../../integrations/coolify",
|
||||
data: "Fetches Coolify instance information including applications, services, servers, projects, and health status.",
|
||||
};
|
||||
@@ -4,6 +4,7 @@ description: "Dash. is a system performance and resource monitoring tool"
|
||||
hide_title: true
|
||||
---
|
||||
|
||||
import { IntegrationData } from '@site/src/components/integrations/data';
|
||||
import { IntegrationHeader } from '@site/src/components/integrations/header';
|
||||
import { IntegrationCapabilites } from '@site/src/components/integrations/widgets';
|
||||
import { AddingIntegration } from '@site/src/components/integrations/adding';
|
||||
@@ -19,6 +20,10 @@ import { systemResourcesWidget } from '@site/docs/widgets/system-resources';
|
||||
categories={['System monitoring']}
|
||||
/>
|
||||
|
||||
### Data
|
||||
|
||||
<IntegrationData integration={dashDotIntegration} />
|
||||
|
||||
### Widgets & Capabilities
|
||||
<IntegrationCapabilites
|
||||
items={[{
|
||||
|
||||
@@ -5,4 +5,5 @@ export const dashDotIntegration: IntegrationDefinition = {
|
||||
description: "Dash. is a system performance and resource monitoring tool",
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/png/dashdot.png",
|
||||
path: "../../integrations/dash-dot",
|
||||
data: "Fetches system health monitoring data including CPU, RAM, storage, network, and GPU information.",
|
||||
};
|
||||
@@ -4,6 +4,7 @@ description: "Deluge is a lightweight, Free Software, cross-platform BitTorrent
|
||||
hide_title: true
|
||||
---
|
||||
|
||||
import { IntegrationData } from '@site/src/components/integrations/data';
|
||||
import { IntegrationHeader } from '@site/src/components/integrations/header';
|
||||
import { IntegrationCapabilites } from '@site/src/components/integrations/widgets';
|
||||
import { AddingIntegration } from '@site/src/components/integrations/adding';
|
||||
@@ -19,6 +20,10 @@ import { downloadsWidget } from '@site/docs/widgets/downloads';
|
||||
categories={['Torrent client']}
|
||||
/>
|
||||
|
||||
### Data
|
||||
|
||||
<IntegrationData integration={delugeIntegration} />
|
||||
|
||||
### Widgets & Capabilities
|
||||
<IntegrationCapabilites
|
||||
items={[{
|
||||
|
||||
@@ -5,4 +5,5 @@ export const delugeIntegration: IntegrationDefinition = {
|
||||
description: "Deluge is a lightweight, Free Software, cross-platform BitTorrent client.",
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/deluge.svg",
|
||||
path: "../../integrations/deluge",
|
||||
data: "Manages and monitors Deluge torrents including progress, speeds, and pause/resume/delete actions.",
|
||||
};
|
||||
@@ -0,0 +1,42 @@
|
||||
---
|
||||
title: 'Docker Hub'
|
||||
description: "Docker Hub is a cloud-based registry service for sharing and managing Docker images."
|
||||
hide_title: true
|
||||
---
|
||||
|
||||
import { IntegrationData } from '@site/src/components/integrations/data';
|
||||
import { IntegrationHeader } from '@site/src/components/integrations/header';
|
||||
import { IntegrationCapabilites } from '@site/src/components/integrations/widgets';
|
||||
import { AddingIntegration } from '@site/src/components/integrations/adding';
|
||||
import { IntegrationSecrets } from '@site/src/components/integrations/secrets';
|
||||
import { dockerHubIntegration } from '.';
|
||||
import { releasesWidget } from '@site/docs/widgets/releases';
|
||||
|
||||
|
||||
<IntegrationHeader
|
||||
integration={dockerHubIntegration}
|
||||
categories={['Container Management', 'Software']}
|
||||
/>
|
||||
|
||||
### Data
|
||||
|
||||
<IntegrationData integration={dockerHubIntegration} />
|
||||
|
||||
### Widgets & Capabilities
|
||||
<IntegrationCapabilites
|
||||
items={[{
|
||||
widget: releasesWidget
|
||||
}]}
|
||||
/>
|
||||
|
||||
### Adding the integration
|
||||
<AddingIntegration />
|
||||
|
||||
### Secrets
|
||||
<IntegrationSecrets secrets={[{
|
||||
credentials: [],
|
||||
steps: []
|
||||
}, {
|
||||
credentials: ['username', 'personalAccessToken'],
|
||||
steps: []
|
||||
}]} />
|
||||
@@ -0,0 +1,9 @@
|
||||
import { IntegrationDefinition } from "@site/src/types";
|
||||
|
||||
export const dockerHubIntegration: IntegrationDefinition = {
|
||||
name: "Docker Hub",
|
||||
description: "Docker Hub is a cloud-based registry service for sharing and managing Docker images.",
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/docker.svg",
|
||||
path: "../../integrations/docker-hub",
|
||||
data: "Fetches container image tags and repository metadata from Docker Hub.",
|
||||
};
|
||||
@@ -4,6 +4,7 @@ description: "Docker is a platform where you can run your applications in contai
|
||||
hide_title: true
|
||||
---
|
||||
|
||||
import { IntegrationData } from '@site/src/components/integrations/data';
|
||||
import { IntegrationHeader } from '@site/src/components/integrations/header';
|
||||
import { IntegrationCapabilites } from '@site/src/components/integrations/widgets';
|
||||
import {IntegrationSecrets} from '@site/src/components/integrations/secrets';
|
||||
@@ -17,6 +18,10 @@ import { dockerContainersWidget } from '@site/docs/widgets/docker-containers';
|
||||
categories={['Containers']}
|
||||
/>
|
||||
|
||||
### Data
|
||||
|
||||
<IntegrationData integration={dockerIntegration} />
|
||||
|
||||
### Widgets & Capabilities
|
||||
<IntegrationCapabilites
|
||||
items={[{
|
||||
|
||||
@@ -6,4 +6,5 @@ export const dockerIntegration: IntegrationDefinition = {
|
||||
"Docker is a platform where you can run your applications in containers, allowing for easy deployment and management of applications.",
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/docker.svg",
|
||||
path: "../../integrations/docker",
|
||||
data: "Fetches Docker container information including status, CPU, and memory usage via the Docker socket.",
|
||||
};
|
||||
@@ -4,6 +4,7 @@ description: "Emby is a media server platform that allows you to organize, manag
|
||||
hide_title: true
|
||||
---
|
||||
|
||||
import { IntegrationData } from '@site/src/components/integrations/data';
|
||||
import { IntegrationHeader } from '@site/src/components/integrations/header';
|
||||
import { IntegrationCapabilites } from '@site/src/components/integrations/widgets';
|
||||
import { AddingIntegration } from '@site/src/components/integrations/adding';
|
||||
@@ -19,6 +20,10 @@ import { mediaReleasesWidget } from '@site/docs/widgets/media-releases';
|
||||
categories={['Media server']}
|
||||
/>
|
||||
|
||||
### Data
|
||||
|
||||
<IntegrationData integration={embyIntegration} />
|
||||
|
||||
### Widgets & Capabilities
|
||||
<IntegrationCapabilites
|
||||
items={[
|
||||
|
||||
@@ -6,4 +6,5 @@ export const embyIntegration: IntegrationDefinition = {
|
||||
"Emby is a media server platform that allows you to organize, manage, and stream your personal media collection.",
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/emby.svg",
|
||||
path: "../../integrations/emby",
|
||||
data: "Fetches active streams and recently added media from an Emby media server.",
|
||||
};
|
||||
@@ -0,0 +1,47 @@
|
||||
---
|
||||
title: 'GitHub Container Registry'
|
||||
description: "GitHub Container Registry is a platform for hosting and managing packages."
|
||||
hide_title: true
|
||||
---
|
||||
|
||||
import { IntegrationData } from '@site/src/components/integrations/data';
|
||||
import { IntegrationHeader } from '@site/src/components/integrations/header';
|
||||
import { IntegrationCapabilites } from '@site/src/components/integrations/widgets';
|
||||
import { AddingIntegration } from '@site/src/components/integrations/adding';
|
||||
import { IntegrationSecrets } from '@site/src/components/integrations/secrets';
|
||||
import { gitHubContainerRegistryIntegration } from '.';
|
||||
import { releasesWidget } from '@site/docs/widgets/releases';
|
||||
|
||||
|
||||
<IntegrationHeader
|
||||
integration={gitHubContainerRegistryIntegration}
|
||||
categories={['Package Management', 'Software']}
|
||||
/>
|
||||
|
||||
|
||||
### Data
|
||||
|
||||
<IntegrationData integration={gitHubContainerRegistryIntegration} />
|
||||
|
||||
### Widgets & Capabilities
|
||||
<IntegrationCapabilites
|
||||
items={[{
|
||||
widget: releasesWidget
|
||||
}]}
|
||||
/>
|
||||
|
||||
### Adding the integration
|
||||
<AddingIntegration />
|
||||
|
||||
### Secrets
|
||||
<IntegrationSecrets secrets={[{
|
||||
credentials: [],
|
||||
steps: []
|
||||
}, {
|
||||
credentials: ["personalAccessToken"],
|
||||
steps: []
|
||||
}, {
|
||||
tabLabel: "GitHub App",
|
||||
credentials: ['githubAppId', 'githubInstallationId', 'privateKey'],
|
||||
steps: ['Create GitHub App', 'Create a private key for it', 'Install it to your organization / user']
|
||||
}]} />
|
||||
@@ -0,0 +1,12 @@
|
||||
import { IntegrationDefinition } from "@site/src/types";
|
||||
|
||||
export const gitHubContainerRegistryIntegration: IntegrationDefinition = {
|
||||
name: "GitHub Container Registry",
|
||||
description: "GitHub Container Registry is a platform for hosting and managing packages.",
|
||||
iconUrl: {
|
||||
light: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/github.svg",
|
||||
dark: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/github-light.svg",
|
||||
},
|
||||
path: "../../integrations/github-packages",
|
||||
data: "Fetches container image tags and package metadata from GitHub Container Registry.",
|
||||
};
|
||||
@@ -0,0 +1,46 @@
|
||||
---
|
||||
title: 'GitHub'
|
||||
description: "GitHub is a web-based platform for version control and collaboration."
|
||||
hide_title: true
|
||||
---
|
||||
|
||||
import { IntegrationData } from '@site/src/components/integrations/data';
|
||||
import { IntegrationHeader } from '@site/src/components/integrations/header';
|
||||
import { IntegrationCapabilites } from '@site/src/components/integrations/widgets';
|
||||
import { AddingIntegration } from '@site/src/components/integrations/adding';
|
||||
import { IntegrationSecrets } from '@site/src/components/integrations/secrets';
|
||||
import { githubIntegration } from '.';
|
||||
import { releasesWidget } from '@site/docs/widgets/releases';
|
||||
|
||||
|
||||
<IntegrationHeader
|
||||
integration={githubIntegration}
|
||||
categories={['Version Control', 'Software']}
|
||||
/>
|
||||
|
||||
### Data
|
||||
|
||||
<IntegrationData integration={githubIntegration} />
|
||||
|
||||
### Widgets & Capabilities
|
||||
<IntegrationCapabilites
|
||||
items={[{
|
||||
widget: releasesWidget
|
||||
}]}
|
||||
/>
|
||||
|
||||
### Adding the integration
|
||||
<AddingIntegration />
|
||||
|
||||
### Secrets
|
||||
<IntegrationSecrets secrets={[{
|
||||
credentials: [],
|
||||
steps: []
|
||||
}, {
|
||||
credentials: ['personalAccessToken'],
|
||||
steps: []
|
||||
}, {
|
||||
tabLabel: "GitHub App",
|
||||
credentials: ['githubAppId', 'githubInstallationId', 'privateKey'],
|
||||
steps: ['Create GitHub App', 'Create a private key for it', 'Install it to your organization / user']
|
||||
}]} />
|
||||
@@ -0,0 +1,12 @@
|
||||
import { IntegrationDefinition } from "@site/src/types";
|
||||
|
||||
export const githubIntegration: IntegrationDefinition = {
|
||||
name: "GitHub",
|
||||
description: "GitHub is a web-based platform for version control and collaboration.",
|
||||
iconUrl: {
|
||||
light: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/github.svg",
|
||||
dark: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/github-light.svg",
|
||||
},
|
||||
path: "../../integrations/github",
|
||||
data: "Fetches releases and repository metadata from GitHub using the Octokit API.",
|
||||
};
|
||||
@@ -0,0 +1,42 @@
|
||||
---
|
||||
title: 'GitLab'
|
||||
description: "GitLab is a web-based DevOps lifecycle tool that provides a Git repository manager."
|
||||
hide_title: true
|
||||
---
|
||||
|
||||
import { IntegrationData } from '@site/src/components/integrations/data';
|
||||
import { IntegrationHeader } from '@site/src/components/integrations/header';
|
||||
import { IntegrationCapabilites } from '@site/src/components/integrations/widgets';
|
||||
import { AddingIntegration } from '@site/src/components/integrations/adding';
|
||||
import { IntegrationSecrets } from '@site/src/components/integrations/secrets';
|
||||
import { gitlabIntegration } from '.';
|
||||
import { releasesWidget } from '@site/docs/widgets/releases';
|
||||
|
||||
|
||||
<IntegrationHeader
|
||||
integration={gitlabIntegration}
|
||||
categories={['Version Control', 'Software']}
|
||||
/>
|
||||
|
||||
### Data
|
||||
|
||||
<IntegrationData integration={gitlabIntegration} />
|
||||
|
||||
### Widgets & Capabilities
|
||||
<IntegrationCapabilites
|
||||
items={[{
|
||||
widget: releasesWidget
|
||||
}]}
|
||||
/>
|
||||
|
||||
### Adding the integration
|
||||
<AddingIntegration />
|
||||
|
||||
### Secrets
|
||||
<IntegrationSecrets secrets={[{
|
||||
credentials: [],
|
||||
steps: []
|
||||
}, {
|
||||
credentials: ['personalAccessToken'],
|
||||
steps: []
|
||||
}]} />
|
||||
@@ -0,0 +1,9 @@
|
||||
import { IntegrationDefinition } from "@site/src/types";
|
||||
|
||||
export const gitlabIntegration: IntegrationDefinition = {
|
||||
name: "GitLab",
|
||||
description: "GitLab is a web-based DevOps lifecycle tool that provides a Git repository manager.",
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/gitlab.svg",
|
||||
path: "../../integrations/gitlab",
|
||||
data: "Fetches releases and project metadata from a GitLab instance.",
|
||||
};
|
||||
@@ -4,6 +4,7 @@ description: "Glances is a cross-platform system monitoring tool."
|
||||
hide_title: true
|
||||
---
|
||||
|
||||
import { IntegrationData } from '@site/src/components/integrations/data';
|
||||
import { IntegrationHeader } from '@site/src/components/integrations/header';
|
||||
import { IntegrationCapabilites } from '@site/src/components/integrations/widgets';
|
||||
import { AddingIntegration } from '@site/src/components/integrations/adding';
|
||||
@@ -18,6 +19,10 @@ import { healthMonitoringWidget } from '@site/docs/widgets/health-monitoring';
|
||||
categories={['System Monitoring']}
|
||||
/>
|
||||
|
||||
### Data
|
||||
|
||||
<IntegrationData integration={glancesIntegration} />
|
||||
|
||||
### Widgets & Capabilities
|
||||
<IntegrationCapabilites
|
||||
items={[{
|
||||
|
||||
@@ -8,4 +8,5 @@ export const glancesIntegration: IntegrationDefinition = {
|
||||
dark: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/glances-light.svg",
|
||||
},
|
||||
path: "../../integrations/glances",
|
||||
data: "Fetches system health data including CPU, RAM, disk, network, GPU, and sensors from Glances.",
|
||||
};
|
||||
@@ -4,6 +4,7 @@ description: 'Connect Gluetun to Homarr as a VPN integration.'
|
||||
hide_title: true
|
||||
---
|
||||
|
||||
import { IntegrationData } from '@site/src/components/integrations/data';
|
||||
import { IntegrationHeader } from '@site/src/components/integrations/header';
|
||||
import { IntegrationCapabilites } from '@site/src/components/integrations/widgets';
|
||||
import { AddingIntegration } from '@site/src/components/integrations/adding';
|
||||
@@ -19,6 +20,10 @@ import { vpnWidget } from '@site/docs/widgets/vpn';
|
||||
[Gluetun](https://github.com/qdm12/gluetun) exposes a control server that Homarr can query to report the VPN tunnel
|
||||
status, DNS status, public IP address, and active provider details in the VPN widget.
|
||||
|
||||
### Data
|
||||
|
||||
<IntegrationData integration={gluetunIntegration} />
|
||||
|
||||
### Widgets & Capabilities
|
||||
|
||||
<IntegrationCapabilites
|
||||
|
||||
@@ -5,4 +5,5 @@ export const gluetunIntegration: IntegrationDefinition = {
|
||||
description: "VPN control server integration for Homarr's VPN widget.",
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/gluetun.svg",
|
||||
path: "../../integrations/gluetun",
|
||||
data: "Fetches VPN status, public IP, and DNS information from a Gluetun VPN container.",
|
||||
};
|
||||
@@ -6,6 +6,7 @@ hide_title: true
|
||||
|
||||
import { notificationsWidget } from '@site/docs/widgets/notifications';
|
||||
import { AddingIntegration } from '@site/src/components/integrations/adding';
|
||||
import { IntegrationData } from '@site/src/components/integrations/data';
|
||||
import { IntegrationHeader } from '@site/src/components/integrations/header';
|
||||
import { IntegrationSecrets } from '@site/src/components/integrations/secrets';
|
||||
import { IntegrationCapabilites } from '@site/src/components/integrations/widgets';
|
||||
@@ -16,6 +17,10 @@ import { gotifyIntegration } from '.';
|
||||
categories={['Notifications', 'Messaging']}
|
||||
/>
|
||||
|
||||
### Data
|
||||
|
||||
<IntegrationData integration={gotifyIntegration} />
|
||||
|
||||
### Widgets & Capabilities
|
||||
<IntegrationCapabilites
|
||||
items={[{
|
||||
|
||||
@@ -5,4 +5,5 @@ export const gotifyIntegration: IntegrationDefinition = {
|
||||
description: "Gotify is a simple, self-hosted server for sending and receiving messages in real time.",
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/gotify.svg",
|
||||
path: "../../integrations/gotify",
|
||||
data: "Fetches recent push notification messages from a Gotify server.",
|
||||
};
|
||||
@@ -4,6 +4,7 @@ description: "Home Assistant is an open-source home automation platform that foc
|
||||
hide_title: true
|
||||
---
|
||||
|
||||
import { IntegrationData } from '@site/src/components/integrations/data';
|
||||
import { IntegrationHeader } from '@site/src/components/integrations/header';
|
||||
import { IntegrationCapabilites } from '@site/src/components/integrations/widgets';
|
||||
import { AddingIntegration } from '@site/src/components/integrations/adding';
|
||||
@@ -19,6 +20,10 @@ import { smartHomeExecuteAutomationWidget } from '@site/docs/widgets/smart-home-
|
||||
categories={['Smart Home', 'Automation']}
|
||||
/>
|
||||
|
||||
### Data
|
||||
|
||||
<IntegrationData integration={homeAssistantIntegration} />
|
||||
|
||||
### Widgets & Capabilities
|
||||
<IntegrationCapabilites
|
||||
items={[{
|
||||
|
||||
@@ -6,4 +6,5 @@ export const homeAssistantIntegration: IntegrationDefinition = {
|
||||
"Home Assistant is an open-source home automation platform that focuses on privacy and local control, allowing you to automate and control your smart home devices.",
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/home-assistant.svg",
|
||||
path: "../../integrations/home-assistant",
|
||||
data: "Fetches entity states, triggers automations, toggles entities, and retrieves calendar events from Home Assistant.",
|
||||
};
|
||||
@@ -4,6 +4,7 @@ description: "iCal is a standard for calendar data exchange, allowing users to s
|
||||
hide_title: true
|
||||
---
|
||||
|
||||
import { IntegrationData } from '@site/src/components/integrations/data';
|
||||
import { IntegrationHeader } from '@site/src/components/integrations/header';
|
||||
import { IntegrationCapabilites } from '@site/src/components/integrations/widgets';
|
||||
import { AddingIntegration } from '@site/src/components/integrations/adding';
|
||||
@@ -17,6 +18,10 @@ import { calendarWidget } from '@site/docs/widgets/calendar';
|
||||
categories={['Calendar']}
|
||||
/>
|
||||
|
||||
### Data
|
||||
|
||||
<IntegrationData integration={icalIntegration} />
|
||||
|
||||
### Widgets & Capabilities
|
||||
<IntegrationCapabilites
|
||||
items={[{
|
||||
|
||||
@@ -6,4 +6,5 @@ export const icalIntegration: IntegrationDefinition = {
|
||||
"iCal is a standard for calendar data exchange, allowing users to share and manage calendar events across different platforms.",
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/ical.svg",
|
||||
path: "../../integrations/ical",
|
||||
data: "Fetches and parses an iCal/ICS calendar feed to retrieve calendar events.",
|
||||
};
|
||||
@@ -9,6 +9,7 @@ import { immichAlbumCarouselWidget } from '../../widgets/immich-album-carousel';
|
||||
} from '@site/src/components/integrations/header';
|
||||
import { IntegrationCapabilites } from '@site/src/components/integrations/widgets';
|
||||
import { AddingIntegration } from '@site/src/components/integrations/adding';
|
||||
import { IntegrationData } from '@site/src/components/integrations/data';
|
||||
import { IntegrationSecrets } from '@site/src/components/integrations/secrets';
|
||||
import { immichIntegration } from '.';
|
||||
import { immichServerStatsWidget } from '../../widgets/immich-server-stats';
|
||||
@@ -19,6 +20,10 @@ import { immichServerStatsWidget } from '../../widgets/immich-server-stats';
|
||||
categories={['Photo server']}
|
||||
/>
|
||||
|
||||
### Data
|
||||
|
||||
<IntegrationData integration={immichIntegration} />
|
||||
|
||||
### Widgets & Capabilities
|
||||
<IntegrationCapabilites
|
||||
items={[
|
||||
@@ -39,26 +44,13 @@ import { immichServerStatsWidget } from '../../widgets/immich-server-stats';
|
||||
'Scroll down to "API Keys"',
|
||||
'Click "New API Key"',
|
||||
'Enter a name, e.g. "Homarr"',
|
||||
(
|
||||
|
||||
<span>
|
||||
|
||||
Select the following permissions:{" "}
|
||||
<code>album.statistics</code>,{" "}
|
||||
<code>album.read</code>,{" "}
|
||||
<code>album.create</code>,{" "}
|
||||
<code>user.read</code>,{" "}
|
||||
<code>user.update</code>,{" "}
|
||||
<code>asset.read</code>,{" "}
|
||||
<code>asset.download</code>,{" "}
|
||||
<code>asset.statistics</code>,{" "}
|
||||
<code>server.statistics</code>,{" "}
|
||||
<code>server.about</code>.
|
||||
<br />
|
||||
Additional permissions may be required depending on your Immich version. If you encounter authorization errors, grant any missing permissions requested by the server.
|
||||
</span>
|
||||
|
||||
),
|
||||
(<span>
|
||||
Select the following permissions:{" "}
|
||||
<code>album.read</code>,{" "}
|
||||
<code>user.read</code>,{" "}
|
||||
<code>server.statistics</code>,{" "}
|
||||
<code>asset.download</code>
|
||||
</span>),
|
||||
'Create the integration in Homarr and copy the API key to there'
|
||||
]
|
||||
}]} />
|
||||
}]} />
|
||||
@@ -5,4 +5,5 @@ export const immichIntegration: IntegrationDefinition = {
|
||||
description: "Self-hosted photo and video management solution",
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/immich.svg",
|
||||
path: "../../integrations/immich",
|
||||
data: "Fetches server statistics, albums, and users from an Immich photo management server.",
|
||||
};
|
||||
@@ -4,6 +4,7 @@ description: "Jellyfin is a free and open-source media server software that allo
|
||||
hide_title: true
|
||||
---
|
||||
|
||||
import { IntegrationData } from '@site/src/components/integrations/data';
|
||||
import { IntegrationHeader } from '@site/src/components/integrations/header';
|
||||
import { IntegrationCapabilites } from '@site/src/components/integrations/widgets';
|
||||
import { AddingIntegration } from '@site/src/components/integrations/adding';
|
||||
@@ -19,6 +20,10 @@ import { mediaReleasesWidget } from '@site/docs/widgets/media-releases';
|
||||
categories={['Media server']}
|
||||
/>
|
||||
|
||||
### Data
|
||||
|
||||
<IntegrationData integration={jellyfinIntegration} />
|
||||
|
||||
### Widgets & Capabilities
|
||||
<IntegrationCapabilites
|
||||
items={[
|
||||
|
||||
@@ -6,4 +6,5 @@ export const jellyfinIntegration: IntegrationDefinition = {
|
||||
"Jellyfin is a free and open-source media server software that allows you to organize, manage, and share your media files.",
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/jellyfin.svg",
|
||||
path: "../../integrations/jellyfin",
|
||||
data: "Fetches active streams and recently added media from a Jellyfin media server.",
|
||||
};
|
||||
@@ -4,6 +4,7 @@ description: "Jellyseerr is a self-hosted media request management system that i
|
||||
hide_title: true
|
||||
---
|
||||
|
||||
import { IntegrationData } from '@site/src/components/integrations/data';
|
||||
import { IntegrationHeader } from '@site/src/components/integrations/header';
|
||||
import { IntegrationCapabilites } from '@site/src/components/integrations/widgets';
|
||||
import { AddingIntegration } from '@site/src/components/integrations/adding';
|
||||
@@ -19,6 +20,10 @@ import { mediaRequestStatsWidget } from '@site/docs/widgets/media-request-stats'
|
||||
categories={['Media requests']}
|
||||
/>
|
||||
|
||||
### Data
|
||||
|
||||
<IntegrationData integration={jellyseerrIntegration} />
|
||||
|
||||
### Widgets & Capabilities
|
||||
<IntegrationCapabilites
|
||||
items={[{
|
||||
|
||||
@@ -6,4 +6,5 @@ export const jellyseerrIntegration: IntegrationDefinition = {
|
||||
"Jellyseerr is a self-hosted media request management system that integrates with Jellyfin and other media servers.",
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/jellyseerr.svg",
|
||||
path: "../../integrations/jellyseerr",
|
||||
data: "Fetches media requests, search results, and user information from Jellyseerr.",
|
||||
};
|
||||
@@ -4,6 +4,7 @@ description: "Kubernetes is a powerful container orchestration platform that aut
|
||||
hide_title: true
|
||||
---
|
||||
|
||||
import { IntegrationData } from '@site/src/components/integrations/data';
|
||||
import { IntegrationHeader } from '@site/src/components/integrations/header';
|
||||
import { IntegrationCapabilites } from '@site/src/components/integrations/widgets';
|
||||
import {IntegrationSecrets} from '@site/src/components/integrations/secrets';
|
||||
@@ -24,6 +25,10 @@ This integration is read-only, meaning Homarr does not modify or manage cluster
|
||||
|
||||

|
||||
|
||||
### Data
|
||||
|
||||
<IntegrationData integration={kubernetesIntegration} />
|
||||
|
||||
### Prerequisites
|
||||
|
||||
Before deploying Homarr in Kubernetes, ensure the following prerequisites are met:
|
||||
|
||||
@@ -6,4 +6,5 @@ export const kubernetesIntegration: IntegrationDefinition = {
|
||||
"Kubernetes is a powerful container orchestration platform that automates the deployment, scaling, and management of containerized applications.",
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/kubernetes.svg",
|
||||
path: "../../integrations/kubernetes",
|
||||
data: "Fetches Kubernetes cluster information including pods, deployments, and services.",
|
||||
};
|
||||
@@ -4,6 +4,7 @@ description: "Lidarr is a music management tool that automates the process of do
|
||||
hide_title: true
|
||||
---
|
||||
|
||||
import { IntegrationData } from '@site/src/components/integrations/data';
|
||||
import { IntegrationHeader } from '@site/src/components/integrations/header';
|
||||
import { IntegrationCapabilites } from '@site/src/components/integrations/widgets';
|
||||
import { AddingIntegration } from '@site/src/components/integrations/adding';
|
||||
@@ -18,6 +19,10 @@ import { calendarWidget } from '@site/docs/widgets/calendar';
|
||||
categories={['Media manager', "Servarr"]}
|
||||
/>
|
||||
|
||||
### Data
|
||||
|
||||
<IntegrationData integration={lidarrIntegration} />
|
||||
|
||||
### Widgets & Capabilities
|
||||
<IntegrationCapabilites
|
||||
items={[{
|
||||
|
||||
@@ -6,4 +6,5 @@ export const lidarrIntegration: IntegrationDefinition = {
|
||||
"Lidarr is a music management tool that automates the process of downloading, sorting, and renaming music.",
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/lidarr.svg",
|
||||
path: "../../integrations/lidarr",
|
||||
data: "Fetches music album release calendar events from Lidarr.",
|
||||
};
|
||||
@@ -0,0 +1,39 @@
|
||||
---
|
||||
title: 'LinuxServer.io'
|
||||
description: "LinuxServer.io is a platform for managing and deploying containerized applications."
|
||||
hide_title: true
|
||||
---
|
||||
|
||||
import { IntegrationData } from '@site/src/components/integrations/data';
|
||||
import { IntegrationHeader } from '@site/src/components/integrations/header';
|
||||
import { IntegrationCapabilites } from '@site/src/components/integrations/widgets';
|
||||
import { AddingIntegration } from '@site/src/components/integrations/adding';
|
||||
import { IntegrationSecrets } from '@site/src/components/integrations/secrets';
|
||||
import { linuxServerIoIntegration } from '.';
|
||||
import { releasesWidget } from '@site/docs/widgets/releases';
|
||||
|
||||
|
||||
<IntegrationHeader
|
||||
integration={linuxServerIoIntegration}
|
||||
categories={['Container Management', 'Software']}
|
||||
/>
|
||||
|
||||
### Data
|
||||
|
||||
<IntegrationData integration={linuxServerIoIntegration} />
|
||||
|
||||
### Widgets & Capabilities
|
||||
<IntegrationCapabilites
|
||||
items={[{
|
||||
widget: releasesWidget
|
||||
}]}
|
||||
/>
|
||||
|
||||
### Adding the integration
|
||||
<AddingIntegration />
|
||||
|
||||
### Secrets
|
||||
<IntegrationSecrets secrets={[{
|
||||
credentials: [],
|
||||
steps: []
|
||||
}]} />
|
||||
@@ -0,0 +1,9 @@
|
||||
import { IntegrationDefinition } from "@site/src/types";
|
||||
|
||||
export const linuxServerIoIntegration: IntegrationDefinition = {
|
||||
name: "LinuxServer.io",
|
||||
description: "LinuxServer.io is a platform for managing and deploying containerized applications.",
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/linuxserver-io.svg",
|
||||
path: "../../integrations/linux-server-io",
|
||||
data: "Fetches Docker image versions and changelogs from LinuxServer.io.",
|
||||
};
|
||||
@@ -4,6 +4,7 @@ description: "Navidrome is a self-hosted music server and streamer compatible wi
|
||||
hide_title: true
|
||||
---
|
||||
|
||||
import { IntegrationData } from '@site/src/components/integrations/data';
|
||||
import { IntegrationHeader } from '@site/src/components/integrations/header';
|
||||
import { IntegrationCapabilites } from '@site/src/components/integrations/widgets';
|
||||
import { AddingIntegration } from '@site/src/components/integrations/adding';
|
||||
@@ -19,6 +20,10 @@ import { audioStatsWidget } from '@site/docs/widgets/audio-stats';
|
||||
|
||||
Navidrome provides music library statistics including artist, album, and song counts, as well as now playing information via the Subsonic API.
|
||||
|
||||
### Data
|
||||
|
||||
<IntegrationData integration={navidromeIntegration} />
|
||||
|
||||
### Widgets & Capabilities
|
||||
<IntegrationCapabilites
|
||||
items={[
|
||||
|
||||
@@ -5,4 +5,5 @@ export const navidromeIntegration: IntegrationDefinition = {
|
||||
description: "Navidrome is a self-hosted music server and streamer compatible with the Subsonic API.",
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/navidrome.svg",
|
||||
path: "../../integrations/navidrome",
|
||||
data: "Fetches library statistics and currently playing tracks from a Navidrome music server.",
|
||||
};
|
||||
@@ -4,6 +4,7 @@ description: "Nextcloud is a self-hosted productivity platform that provides fil
|
||||
hide_title: true
|
||||
---
|
||||
|
||||
import { IntegrationData } from '@site/src/components/integrations/data';
|
||||
import { IntegrationHeader } from '@site/src/components/integrations/header';
|
||||
import { IntegrationCapabilites } from '@site/src/components/integrations/widgets';
|
||||
import { AddingIntegration } from '@site/src/components/integrations/adding';
|
||||
@@ -19,6 +20,10 @@ import { notificationsWidget } from '@site/docs/widgets/notifications';
|
||||
categories={['Calendar', 'Notifications']}
|
||||
/>
|
||||
|
||||
### Data
|
||||
|
||||
<IntegrationData integration={nextcloudIntegration} />
|
||||
|
||||
### Widgets & Capabilities
|
||||
<IntegrationCapabilites
|
||||
items={[
|
||||
|
||||
@@ -6,4 +6,5 @@ export const nextcloudIntegration: IntegrationDefinition = {
|
||||
"Nextcloud is a self-hosted productivity platform that provides file storage, collaboration tools, and more.",
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/nextcloud.svg",
|
||||
path: "../../integrations/nextcloud",
|
||||
data: "Fetches calendar events and notifications from a Nextcloud instance.",
|
||||
};
|
||||
@@ -0,0 +1,39 @@
|
||||
---
|
||||
title: 'NPM'
|
||||
description: "NPM is a package manager for the JavaScript programming language, primarily used for managing Node.js packages."
|
||||
hide_title: true
|
||||
---
|
||||
|
||||
import { IntegrationData } from '@site/src/components/integrations/data';
|
||||
import { IntegrationHeader } from '@site/src/components/integrations/header';
|
||||
import { IntegrationCapabilites } from '@site/src/components/integrations/widgets';
|
||||
import { AddingIntegration } from '@site/src/components/integrations/adding';
|
||||
import { IntegrationSecrets } from '@site/src/components/integrations/secrets';
|
||||
import { npmIntegration } from '.';
|
||||
import { releasesWidget } from '@site/docs/widgets/releases';
|
||||
|
||||
|
||||
<IntegrationHeader
|
||||
integration={npmIntegration}
|
||||
categories={['Package Management', 'Software']}
|
||||
/>
|
||||
|
||||
### Data
|
||||
|
||||
<IntegrationData integration={npmIntegration} />
|
||||
|
||||
### Widgets & Capabilities
|
||||
<IntegrationCapabilites
|
||||
items={[{
|
||||
widget: releasesWidget
|
||||
}]}
|
||||
/>
|
||||
|
||||
### Adding the integration
|
||||
<AddingIntegration />
|
||||
|
||||
### Secrets
|
||||
<IntegrationSecrets secrets={[{
|
||||
credentials: [],
|
||||
steps: []
|
||||
}]} />
|
||||
@@ -0,0 +1,10 @@
|
||||
import { IntegrationDefinition } from "@site/src/types";
|
||||
|
||||
export const npmIntegration: IntegrationDefinition = {
|
||||
name: "NPM",
|
||||
description:
|
||||
"NPM is a package manager for the JavaScript programming language, primarily used for managing Node.js packages.",
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/npm.svg",
|
||||
path: "../../integrations/npm",
|
||||
data: "Fetches the latest npm package version and metadata from the npm registry.",
|
||||
};
|
||||
Loaded 100 of 760 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user