Compare commits
35
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7749458c31 | ||
|
|
69788124c1 | ||
|
|
9c31cdce37 | ||
|
|
67d146220a | ||
|
|
41db737905 | ||
|
|
3b128585a3 | ||
|
|
cdaf2a838c | ||
|
|
926a6856fb | ||
|
|
c6160da222 | ||
|
|
e729dfd891 | ||
|
|
eb71f936c6 | ||
|
|
c10ebbc863 | ||
|
|
106c3bfa90 | ||
|
|
27a6467896 | ||
|
|
8331a58471 | ||
|
|
ec74c36a6a | ||
|
|
487d98275c | ||
|
|
187a479bd5 | ||
|
|
0bfe6ddabe | ||
|
|
51d8a3cc53 | ||
|
|
06db42129c | ||
|
|
a11583feb1 | ||
|
|
446931bc0a | ||
|
|
34bcc62415 | ||
|
|
a2e3c5d3bc | ||
|
|
ed9646b0c6 | ||
|
|
e0de370340 | ||
|
|
7e5453d47c | ||
|
|
3b2fc2527e | ||
|
|
0a0b3a12c9 | ||
|
|
ba263dbe47 | ||
|
|
8a7c4875bf | ||
|
|
377f2f6a20 | ||
|
|
c69e33ac8c | ||
|
|
121a83040d |
No files matched your search
@@ -1,691 +0,0 @@
|
||||
---
|
||||
name: datatable-migration
|
||||
description: Playbook for migrating tables from mantine-react-table (MRT) to mantine-datatable. Use when converting any table component from mantine-react-table to mantine-datatable, working on DataTable column/sort/expansion/context-menu code, or implementing responsive column visibility, column persistence, or transparent tables in widgets.
|
||||
---
|
||||
|
||||
# Migrating Tables from mantine-react-table to mantine-datatable
|
||||
|
||||
This document captures every pattern, fix, and decision from the downloads widget rework (PR #6430). Use it as a playbook when porting other tables.
|
||||
|
||||
## Migration Candidates
|
||||
|
||||
Current `mantine-react-table` consumers that could be migrated:
|
||||
|
||||
**Widgets (in `packages/widgets/`):**
|
||||
|
||||
- `docker/component.tsx` — Docker container table
|
||||
- `media-server/component.tsx` — Media server sessions
|
||||
|
||||
**Admin pages (in `apps/nextjs/src/app/[locale]/manage/`):**
|
||||
|
||||
- `tools/docker/docker-table.tsx`
|
||||
- `tools/tasks/_components/tasks-table.tsx`
|
||||
- `tools/api/components/api-keys.tsx`
|
||||
- `users/_components/user-list.tsx`
|
||||
- `users/invites/_components/invite-list.tsx`
|
||||
- `tools/kubernetes/` — 8 Kubernetes resource tables
|
||||
|
||||
**Modals:**
|
||||
|
||||
- `packages/modals-collection/src/search-engines/request-media-modal.tsx`
|
||||
|
||||
**Shared hook to remove once all migrated:**
|
||||
|
||||
- `packages/ui/src/hooks/use-translated-mantine-react-table.ts`
|
||||
|
||||
## 1. Core Migration: mantine-react-table → mantine-datatable
|
||||
|
||||
### Import changes
|
||||
|
||||
```typescript
|
||||
// BEFORE
|
||||
import { MantineReactTable, useMantineReactTable } from "mantine-react-table";
|
||||
import type { MRT_ColumnDef } from "mantine-react-table";
|
||||
|
||||
// AFTER
|
||||
import { DataTable, useDataTableColumns } from "mantine-datatable";
|
||||
import type { DataTableColumn, DataTableSortStatus } from "mantine-datatable";
|
||||
```
|
||||
|
||||
### Column definition shape
|
||||
|
||||
```typescript
|
||||
// BEFORE (MRT)
|
||||
const columns: MRT_ColumnDef<MyRow>[] = [
|
||||
{
|
||||
accessorKey: "name",
|
||||
header: "Name",
|
||||
size: 200,
|
||||
enableSorting: true,
|
||||
Cell: ({ row }) => <Text>{row.original.name}</Text>,
|
||||
},
|
||||
];
|
||||
|
||||
// AFTER (mantine-datatable)
|
||||
const columns: DataTableColumn<MyRow>[] = [
|
||||
{
|
||||
accessor: "name", // not accessorKey
|
||||
title: "Name", // not header
|
||||
width: 200, // not size
|
||||
sortable: true, // not enableSorting
|
||||
render: (record) => <Text>{record.name}</Text>, // not Cell
|
||||
},
|
||||
];
|
||||
```
|
||||
|
||||
Key differences:
|
||||
|
||||
- `accessorKey` → `accessor`
|
||||
- `header` → `title`
|
||||
- `size` → `width`
|
||||
- `Cell: ({ row }) =>` → `render: (record) =>`
|
||||
- `enableSorting` → `sortable`
|
||||
- `enableColumnActions` → does not exist (no column action menus)
|
||||
- `mantineTableBodyCellProps` → `cellsStyle` in `defaultColumnProps`
|
||||
|
||||
### Table component
|
||||
|
||||
```typescript
|
||||
// BEFORE
|
||||
const table = useTranslatedMantineReactTable({ columns, data, ... });
|
||||
return <MantineReactTable table={table} />;
|
||||
|
||||
// AFTER
|
||||
return (
|
||||
<DataTable
|
||||
records={data}
|
||||
columns={columns}
|
||||
sortStatus={sortStatus}
|
||||
onSortStatusChange={setSortStatus}
|
||||
...
|
||||
/>
|
||||
);
|
||||
```
|
||||
|
||||
### Sorting is manual
|
||||
|
||||
mantine-datatable does NOT sort for you. You must sort the data yourself:
|
||||
|
||||
```typescript
|
||||
const [sortStatus, setSortStatus] = useState<DataTableSortStatus<MyRow>>({
|
||||
columnAccessor: "name",
|
||||
direction: "asc",
|
||||
});
|
||||
|
||||
const sortedData = useMemo(() => {
|
||||
const mult = sortStatus.direction === "desc" ? -1 : 1;
|
||||
return [...data].toSorted((a, b) => {
|
||||
const aVal = a[sortStatus.columnAccessor];
|
||||
const bVal = b[sortStatus.columnAccessor];
|
||||
if (typeof aVal === "string") return aVal.localeCompare(bVal as string) * mult;
|
||||
return ((aVal as number) - (bVal as number)) * mult;
|
||||
});
|
||||
}, [data, sortStatus]);
|
||||
|
||||
// Pass sortedData to records, not data
|
||||
<DataTable records={sortedData} sortStatus={sortStatus} onSortStatusChange={setSortStatus} />
|
||||
```
|
||||
|
||||
## 2. Making Tables Transparent (Widget Context)
|
||||
|
||||
Widgets sit inside board cards that have user-controlled opacity. The table must be fully transparent so the card background shows through. mantine-datatable defaults to `var(--mantine-color-body)` which is opaque.
|
||||
|
||||
Create a CSS file alongside the component:
|
||||
|
||||
```css
|
||||
/* styles.css */
|
||||
.my-table,
|
||||
.my-table .mantine-datatable-table,
|
||||
.my-table .mantine-datatable-table thead,
|
||||
.my-table .mantine-datatable-table tbody,
|
||||
.my-table .mantine-datatable-table tfoot,
|
||||
.my-table th,
|
||||
.my-table td {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
.my-table .mantine-datatable-table tr {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
/* Hover: translucent instead of opaque */
|
||||
.my-table .mantine-datatable-table tbody tr:hover {
|
||||
background-color: color-mix(
|
||||
in srgb,
|
||||
var(--mantine-color-default-hover) 40%,
|
||||
transparent
|
||||
) !important;
|
||||
}
|
||||
|
||||
/* Headers: frosted glass effect */
|
||||
.my-table th {
|
||||
white-space: nowrap;
|
||||
background-color: color-mix(
|
||||
in srgb,
|
||||
var(--mantine-color-body) 60%,
|
||||
transparent
|
||||
) !important;
|
||||
backdrop-filter: blur(8px);
|
||||
}
|
||||
|
||||
/* Cell overflow */
|
||||
.my-table td {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
/* Hide sort icon until hover */
|
||||
.my-table .mantine-datatable-header-cell-sortable-icon {
|
||||
opacity: 0;
|
||||
transition: opacity 150ms;
|
||||
}
|
||||
.my-table th:hover .mantine-datatable-header-cell-sortable-icon {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* Row expansion area */
|
||||
.my-table .mantine-datatable-row-expansion-cell {
|
||||
background-color: color-mix(
|
||||
in srgb,
|
||||
var(--mantine-color-body) 40%,
|
||||
transparent
|
||||
) !important;
|
||||
}
|
||||
```
|
||||
|
||||
Import it: `import "./styles.css";` and apply via `className="my-table"`.
|
||||
|
||||
## 3. Responsive Column Visibility
|
||||
|
||||
Use a lookup table of width breakpoints instead of media queries. The widget receives `width` as a prop.
|
||||
|
||||
```typescript
|
||||
interface SizeConfig {
|
||||
fontSize: "xs" | "sm";
|
||||
iconSize: number;
|
||||
cellPadding: number;
|
||||
showSpeedColumns: boolean;
|
||||
showTimeColumn: boolean;
|
||||
showStateColumn: boolean;
|
||||
}
|
||||
|
||||
const SIZE_BREAKPOINTS: { maxWidth: number; config: SizeConfig }[] = [
|
||||
{
|
||||
maxWidth: 300,
|
||||
config: {
|
||||
fontSize: "xs",
|
||||
iconSize: 12,
|
||||
cellPadding: 2,
|
||||
showSpeedColumns: false,
|
||||
showTimeColumn: false,
|
||||
showStateColumn: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
maxWidth: 500,
|
||||
config: {
|
||||
fontSize: "xs",
|
||||
iconSize: 14,
|
||||
cellPadding: 4,
|
||||
showSpeedColumns: false,
|
||||
showTimeColumn: true,
|
||||
showStateColumn: false,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const DEFAULT_SIZE_CONFIG: SizeConfig = {
|
||||
fontSize: "sm",
|
||||
iconSize: 14,
|
||||
cellPadding: 4,
|
||||
showSpeedColumns: true,
|
||||
showTimeColumn: true,
|
||||
showStateColumn: true,
|
||||
};
|
||||
|
||||
function getSizeConfig(width: number): SizeConfig {
|
||||
for (const { maxWidth, config } of SIZE_BREAKPOINTS) {
|
||||
if (width < maxWidth) return config;
|
||||
}
|
||||
return DEFAULT_SIZE_CONFIG;
|
||||
}
|
||||
|
||||
// Usage: memoize it
|
||||
const size = useMemo(() => getSizeConfig(width), [width]);
|
||||
```
|
||||
|
||||
Then use a visibility check lookup table for columns:
|
||||
|
||||
```typescript
|
||||
const columnVisibilityChecks: Partial<
|
||||
Record<string, (ctx: ColumnContext) => boolean>
|
||||
> = {
|
||||
upSpeed: (ctx) => ctx.hasTorrents && ctx.size.showSpeedColumns,
|
||||
downSpeed: (ctx) => ctx.size.showSpeedColumns,
|
||||
time: (ctx) => ctx.size.showTimeColumn,
|
||||
state: (ctx) => ctx.size.showStateColumn,
|
||||
};
|
||||
|
||||
function shouldIncludeColumn(accessor: string, ctx: ColumnContext): boolean {
|
||||
if (!ctx.optionsColumnSet.has(accessor)) return false;
|
||||
const check = columnVisibilityChecks[accessor];
|
||||
if (check && !check(ctx)) return false;
|
||||
return true;
|
||||
}
|
||||
```
|
||||
|
||||
## 4. Column Persistence (Drag/Resize → Server)
|
||||
|
||||
`useDataTableColumns` stores column order and widths in localStorage by default. For widgets, this must persist server-side so it works across devices.
|
||||
|
||||
### Hidden widget options
|
||||
|
||||
In `index.ts`, add hidden text options:
|
||||
|
||||
```typescript
|
||||
columnOrder: factory.text({ defaultValue: "" }),
|
||||
columnWidths: factory.text({ defaultValue: "" }),
|
||||
```
|
||||
|
||||
With visibility override:
|
||||
|
||||
```typescript
|
||||
{
|
||||
columnOrder: { shouldHide: () => true },
|
||||
columnWidths: { shouldHide: () => true },
|
||||
}
|
||||
```
|
||||
|
||||
### Translation keys required
|
||||
|
||||
Even hidden options need translation keys or the translation test fails:
|
||||
|
||||
```json
|
||||
"widget.mywidget.option.columnOrder.label": "Column order",
|
||||
"widget.mywidget.option.columnWidths.label": "Column widths"
|
||||
```
|
||||
|
||||
### Dynamic storeKey
|
||||
|
||||
Use a key that includes `itemId` and sorted column names. This prevents cross-widget localStorage bleeding:
|
||||
|
||||
```typescript
|
||||
const storeKey = `mytable-${itemId ?? "preview"}-${[...options.columns].toSorted().join(",")}`;
|
||||
```
|
||||
|
||||
### Hydration from server → localStorage
|
||||
|
||||
On mount, seed `useDataTableColumns` with server-persisted values. Use a `hydrated` ref to prevent the persist effect from immediately writing defaults back:
|
||||
|
||||
```typescript
|
||||
const {
|
||||
effectiveColumns,
|
||||
columnsOrder,
|
||||
columnsWidth,
|
||||
setColumnsOrder,
|
||||
setMultipleColumnWidths,
|
||||
} = useDataTableColumns<MyRow>({ key: storeKey, columns });
|
||||
|
||||
const lastStoreKey = useRef(storeKey);
|
||||
const hydrated = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (lastStoreKey.current !== storeKey) {
|
||||
lastStoreKey.current = storeKey;
|
||||
hydrated.current = false;
|
||||
}
|
||||
if (hydrated.current) return;
|
||||
if (savedOrder.length > 0) setColumnsOrder(savedOrder);
|
||||
if (Object.keys(savedWidths).length > 0) {
|
||||
setMultipleColumnWidths(
|
||||
Object.entries(savedWidths).map(([accessor, w]) => ({
|
||||
accessor,
|
||||
width: w,
|
||||
})),
|
||||
);
|
||||
}
|
||||
requestAnimationFrame(() => {
|
||||
hydrated.current = true;
|
||||
});
|
||||
}, [
|
||||
storeKey,
|
||||
savedOrder,
|
||||
savedWidths,
|
||||
setColumnsOrder,
|
||||
setMultipleColumnWidths,
|
||||
]);
|
||||
```
|
||||
|
||||
### Persist localStorage → server
|
||||
|
||||
Watch for changes and write back:
|
||||
|
||||
```typescript
|
||||
const prevOrder = useRef(columnsOrder);
|
||||
const prevWidths = useRef(columnsWidth);
|
||||
|
||||
useEffect(() => {
|
||||
if (!hydrated.current) return;
|
||||
const orderChanged =
|
||||
JSON.stringify(columnsOrder) !== JSON.stringify(prevOrder.current);
|
||||
const widthsChanged =
|
||||
JSON.stringify(columnsWidth) !== JSON.stringify(prevWidths.current);
|
||||
prevOrder.current = columnsOrder;
|
||||
prevWidths.current = columnsWidth;
|
||||
|
||||
if (!orderChanged && !widthsChanged) return;
|
||||
|
||||
if (orderChanged)
|
||||
persistOption({ columnOrder: JSON.stringify(columnsOrder) });
|
||||
if (widthsChanged) {
|
||||
const widthMap: Record<string, number> = {};
|
||||
for (const entry of columnsWidth) {
|
||||
const key = Object.keys(entry)[0];
|
||||
if (!key) continue;
|
||||
const v = entry[key as keyof typeof entry];
|
||||
if (typeof v === "number") widthMap[key] = v;
|
||||
else if (typeof v === "string" && v.endsWith("px"))
|
||||
widthMap[key] = parseInt(v, 10);
|
||||
}
|
||||
persistOption({ columnWidths: JSON.stringify(widthMap) });
|
||||
}
|
||||
}, [columnsOrder, columnsWidth, persistOption]);
|
||||
```
|
||||
|
||||
The `persistOption` helper calls both `setOptions` (local state) and `saveItemOptions` (server mutation).
|
||||
|
||||
## 5. Context Menu (Right-Click)
|
||||
|
||||
Do NOT install `mantine-contextmenu`. Use Mantine's `Menu` + `Portal` directly:
|
||||
|
||||
```typescript
|
||||
interface ContextMenuState {
|
||||
x: number;
|
||||
y: number;
|
||||
item: MyRow;
|
||||
}
|
||||
|
||||
const [contextMenu, setContextMenu] = useState<ContextMenuState | null>(null);
|
||||
|
||||
const handleContextMenu = useCallback(
|
||||
({ record, event }: { record: MyRow; event: React.MouseEvent }) => {
|
||||
event.preventDefault();
|
||||
setContextMenu({ x: event.clientX, y: event.clientY, item: record });
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
// On DataTable:
|
||||
<DataTable onRowContextMenu={handleContextMenu} />
|
||||
|
||||
// Render:
|
||||
{contextMenu && <RowContextMenu state={contextMenu} onClose={closeContextMenu} />}
|
||||
```
|
||||
|
||||
The menu component uses `Portal` for correct z-index:
|
||||
|
||||
```tsx
|
||||
function RowContextMenu({
|
||||
state,
|
||||
onClose,
|
||||
}: {
|
||||
state: ContextMenuState;
|
||||
onClose: () => void;
|
||||
}) {
|
||||
return (
|
||||
<Portal>
|
||||
<Menu
|
||||
opened
|
||||
onClose={onClose}
|
||||
closeOnItemClick={false}
|
||||
position="right-start"
|
||||
offset={0}
|
||||
>
|
||||
<Menu.Target>
|
||||
<Box
|
||||
style={{
|
||||
position: "fixed",
|
||||
left: state.x,
|
||||
top: state.y,
|
||||
width: 0,
|
||||
height: 0,
|
||||
}}
|
||||
/>
|
||||
</Menu.Target>
|
||||
<Menu.Dropdown>
|
||||
<Menu.Item
|
||||
onClick={() => {
|
||||
/* action */ onClose();
|
||||
}}
|
||||
>
|
||||
Action
|
||||
</Menu.Item>
|
||||
</Menu.Dropdown>
|
||||
</Menu>
|
||||
</Portal>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
**Key fix:** Use `closeOnItemClick={false}` if any menu item needs a two-step confirmation (like delete → confirm). Close the menu on scroll: `onScroll={() => { if (contextMenu) closeContextMenu(); }}`.
|
||||
|
||||
## 6. Row Expansion
|
||||
|
||||
```typescript
|
||||
<DataTable
|
||||
rowExpansion={{
|
||||
trigger: "click",
|
||||
allowMultiple: true,
|
||||
collapseProps: {
|
||||
transitionDuration: 200,
|
||||
animateOpacity: true,
|
||||
transitionTimingFunction: "ease-out",
|
||||
},
|
||||
content: ({ record, collapse }) => <ExpandedRow item={record} collapse={collapse} />,
|
||||
}}
|
||||
/>
|
||||
```
|
||||
|
||||
**Critical:** Do NOT set `onRowClick` when using `rowExpansion` with `trigger: "click"`. They conflict and the expansion won't work.
|
||||
|
||||
## 7. Common Pitfalls & Fixes
|
||||
|
||||
### Status indicator dots
|
||||
|
||||
Don't use `<Badge variant="dot" />` without children — it renders a pill with empty space. Use a plain `Box`:
|
||||
|
||||
```tsx
|
||||
<Box
|
||||
w={8}
|
||||
h={8}
|
||||
style={{
|
||||
borderRadius: "50%",
|
||||
backgroundColor: `var(--mantine-color-${color}-filled)`,
|
||||
flexShrink: 0,
|
||||
}}
|
||||
/>
|
||||
```
|
||||
|
||||
### Tooltip overflow on long text columns
|
||||
|
||||
When a column has `ellipsis: true`, the tooltip target element can still extend beyond the cell bounds if inner elements don't clip. Fix:
|
||||
|
||||
```tsx
|
||||
<Tooltip position="bottom-start" ...>
|
||||
<Group style={{ overflow: "hidden" }}>
|
||||
...
|
||||
</Group>
|
||||
</Tooltip>
|
||||
```
|
||||
|
||||
Use `position="bottom-start"` instead of `position="right"` so the tooltip never escapes the widget horizontally.
|
||||
|
||||
### Dark mode tooltips
|
||||
|
||||
Mantine tooltips default to the theme's tooltip color which may be white in light mode and stay white in dark mode. Always set `color="dark"` on informational tooltips inside widgets.
|
||||
|
||||
### Division by zero
|
||||
|
||||
Guard all ratio calculations:
|
||||
|
||||
```typescript
|
||||
if (item.size > 0) ratio = item.sent / item.size;
|
||||
if (totalDown > 0) ratio = totalUp / totalDown;
|
||||
```
|
||||
|
||||
### Composite idAccessor
|
||||
|
||||
When rows can come from multiple integrations, IDs may collide. Use a composite key:
|
||||
|
||||
```typescript
|
||||
idAccessor={(record) => `${record.integration.id}:${record.id}`}
|
||||
```
|
||||
|
||||
### JSON option parsing
|
||||
|
||||
Always wrap in try/catch:
|
||||
|
||||
```typescript
|
||||
function parseJsonOption<T>(value: string | undefined, fallback: T): T {
|
||||
if (!value) return fallback;
|
||||
try {
|
||||
return JSON.parse(value) as T;
|
||||
} catch {
|
||||
return fallback;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Clipboard API
|
||||
|
||||
`navigator.clipboard.writeText()` can throw in non-HTTPS contexts. Always catch:
|
||||
|
||||
```typescript
|
||||
void navigator.clipboard.writeText(text).catch(() => {});
|
||||
```
|
||||
|
||||
### Sync sortStatus with widget options
|
||||
|
||||
If the widget has configurable default sort, sync it:
|
||||
|
||||
```typescript
|
||||
useEffect(() => {
|
||||
setSortStatus({
|
||||
columnAccessor: options.defaultSort,
|
||||
direction: defaultSortDirection,
|
||||
});
|
||||
}, [options.defaultSort, defaultSortDirection]);
|
||||
```
|
||||
|
||||
## 8. Accessibility
|
||||
|
||||
- All `ActionIcon` components need `aria-label`
|
||||
- Filter toggles (client badges, status badges) need `aria-pressed={isActive}`
|
||||
- `textSelectionDisabled` on DataTable prevents accidental text selection during drag/resize
|
||||
|
||||
## 9. Error Handling
|
||||
|
||||
- Every mutation needs an `onError` callback with `showErrorNotification`
|
||||
- The `saveItemOptions` mutation (column persistence) also needs `onError`
|
||||
- Check `isError` on the main query and show a fallback UI
|
||||
- Use a shared `mutationOptions` object to avoid repeating `onSettled`/`onError`:
|
||||
|
||||
```typescript
|
||||
const mutationOptions = {
|
||||
onSettled: () => void utils.myQuery.invalidate(),
|
||||
onError: () =>
|
||||
showErrorNotification({
|
||||
title: t("errors.actionFailed"),
|
||||
message: t("errors.actionFailedMessage"),
|
||||
}),
|
||||
};
|
||||
```
|
||||
|
||||
## 10. i18n
|
||||
|
||||
- No hardcoded strings. Every user-visible string goes through `useScopedI18n`
|
||||
- Column titles: `t("items.{accessor}.columnTitle")`
|
||||
- Detail labels: `t("items.{accessor}.detailsTitle")`
|
||||
- States: `t("states.{state}")`
|
||||
- Actions: `t("actions.{action}")`
|
||||
- Errors: `t("errors.{errorKey}")`
|
||||
- Hidden options still need `.label` translation keys
|
||||
|
||||
## 11. Edit Mode
|
||||
|
||||
Disable pointer events on the table during edit mode to prevent conflicts with the board's drag-and-drop:
|
||||
|
||||
```typescript
|
||||
let tablePointerEvents: React.CSSProperties["pointerEvents"];
|
||||
if (isEditMode) tablePointerEvents = "none";
|
||||
|
||||
let rowContextMenuHandler = handleContextMenu;
|
||||
if (isEditMode) rowContextMenuHandler = undefined;
|
||||
|
||||
<DataTable style={{ pointerEvents: tablePointerEvents }} />
|
||||
```
|
||||
|
||||
## 12. DataTable Props Reference (Commonly Used)
|
||||
|
||||
```typescript
|
||||
<DataTable
|
||||
records={sortedData}
|
||||
columns={effectiveColumns}
|
||||
storeColumnsKey={storeKey}
|
||||
withTableBorder={false}
|
||||
borderRadius={0}
|
||||
highlightOnHover
|
||||
striped="odd"
|
||||
stripedColor={{ dark: "dark.7", light: "gray.0" }}
|
||||
highlightOnHoverColor={{ dark: "dark.5", light: "gray.1" }}
|
||||
verticalAlign="center"
|
||||
fetching={isFetching && data.length === 0}
|
||||
loaderBackgroundBlur={2}
|
||||
fz={size.fontSize}
|
||||
textSelectionDisabled
|
||||
sortStatus={sortStatus}
|
||||
onSortStatusChange={setSortStatus}
|
||||
noRecordsText={t("errors.noItems")}
|
||||
idAccessor={(record) => record.uniqueKey}
|
||||
height="100%"
|
||||
className="my-table"
|
||||
defaultColumnProps={{
|
||||
noWrap: true,
|
||||
draggable: true,
|
||||
resizable: true,
|
||||
cellsStyle: () => ({ padding: `${size.cellPadding}px 8px` }),
|
||||
}}
|
||||
scrollAreaProps={{ type: "auto", scrollbarSize: 6 }}
|
||||
rowBackgroundColor={(record) => conditionalBgMap[record.state]}
|
||||
onRowContextMenu={handleContextMenu}
|
||||
rowExpansion={{ ... }}
|
||||
onScroll={() => { if (contextMenu) closeContextMenu(); }}
|
||||
/>
|
||||
```
|
||||
|
||||
## 13. Layout Stability
|
||||
|
||||
- Don't use `table-layout: fixed` — it fights mantine-datatable's resize logic
|
||||
- Give non-name columns explicit `width` values so only the name column flexes
|
||||
- Use `ellipsis: true` on the name column — it handles `text-overflow: ellipsis` internally
|
||||
- Names should truncate at the cell boundary, not a character count, so the layout is stable regardless of content length
|
||||
- `flexShrink: 0` on badges/icons prevents them from being squeezed by long names
|
||||
|
||||
## 14. Checklist for Each Migration
|
||||
|
||||
1. Replace imports (`mantine-react-table` → `mantine-datatable`)
|
||||
2. Convert column definitions (`accessorKey` → `accessor`, `header` → `title`, `Cell` → `render`, etc.)
|
||||
3. Implement manual sorting with `useState` + `useMemo`
|
||||
4. Add CSS file for transparent backgrounds (if widget context)
|
||||
5. Add responsive column visibility (if widget context)
|
||||
6. Add hidden options + hydration logic (if column persistence needed)
|
||||
7. Add translation keys (including hidden option labels)
|
||||
8. Add context menu if row actions exist
|
||||
9. Add row expansion if detail view exists
|
||||
10. Add error handling on all mutations
|
||||
11. Add accessibility attributes
|
||||
12. Test at multiple widget widths
|
||||
13. Update docs in `apps/docs/docs/widgets/` if applicable
|
||||
14. Run `pnpm turbo typecheck`
|
||||
15. Run translation spec — catches missing keys
|
||||
@@ -1,44 +0,0 @@
|
||||
---
|
||||
name: documentation-sync
|
||||
description: Keep Homarr documentation in sync with code changes. Use when modifying code that affects user-facing behavior, or when adding/modifying widgets, integrations, cron jobs, API routes, env vars, permissions, auth providers, or UI in apps/docs. Covers the docs/ mapping table, integration doc pattern, and widget doc pattern.
|
||||
---
|
||||
|
||||
# Documentation Sync
|
||||
|
||||
When modifying code that affects user-facing behavior, you MUST also update the documentation in `apps/docs/`.
|
||||
|
||||
## What triggers a doc update
|
||||
|
||||
| Code change | Doc location |
|
||||
| ---------------------- | ----------------------------------------------------------- |
|
||||
| New integration | `apps/docs/docs/integrations/<slug>/index.mdx` + `index.ts` |
|
||||
| New widget | `apps/docs/docs/widgets/<slug>/index.mdx` + `index.ts` |
|
||||
| Changed API surface | `apps/docs/docs/management/api.mdx` |
|
||||
| New/changed env vars | `apps/docs/docs/advanced/` |
|
||||
| New CLI commands | `apps/docs/docs/advanced/command-line/` |
|
||||
| Changed auth providers | `apps/docs/docs/advanced/` SSO pages |
|
||||
| UI/UX changes | Relevant getting-started or management pages |
|
||||
| New cron job | `apps/docs/docs/management/tasks.mdx` |
|
||||
| Changed permissions | `apps/docs/docs/management/users.mdx` |
|
||||
|
||||
## Integration doc pattern
|
||||
|
||||
Each integration has a folder under `apps/docs/docs/integrations/<slug>/`:
|
||||
|
||||
- `index.ts` — exports a typed `IntegrationDefinition` with name, description, iconUrl, path
|
||||
- `index.mdx` — uses shared components: `IntegrationHeader`, `IntegrationCapabilites`, `IntegrationSecrets`
|
||||
- Import metadata from `@homarr/definitions` where possible
|
||||
|
||||
## Widget doc pattern
|
||||
|
||||
Each widget has a folder under `apps/docs/docs/widgets/<slug>/`:
|
||||
|
||||
- `index.ts` — exports a typed `WidgetDefinition` with icon, name, description, path, configuration
|
||||
- `index.mdx` — uses shared components: `WidgetHeader`, `WidgetConfig`, `WidgetAdding`
|
||||
|
||||
## Development
|
||||
|
||||
- Run docs locally: `pnpm dev:docs` from root
|
||||
- Build docs: `pnpm turbo build --filter=@homarr/docs`
|
||||
- The docs app can import from `@homarr/definitions` for type-safe integration/widget metadata
|
||||
- Docusaurus strict mode throws on broken links — fix any broken references before committing
|
||||
@@ -1 +0,0 @@
|
||||
../.agents/skills
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: codebase-context
|
||||
description: Comprehensive Homarr codebase architecture and conventions reference. Use when orienting in the monorepo, understanding package dependency layers, the three runtime services and ports, tRPC router structure, multi-database support, widget/integration/cron system architecture, routing, auth providers, env vars, or key patterns before writing code.
|
||||
description: Comprehensive Homarr codebase architecture and conventions reference
|
||||
alwaysApply: true
|
||||
---
|
||||
|
||||
# Homarr Codebase Context
|
||||
@@ -27,7 +27,7 @@ Homarr is an open-source, self-hosted dashboard for managing homelab services. I
|
||||
|
||||
## Repository Structure
|
||||
|
||||
```text
|
||||
```
|
||||
homarr/
|
||||
├── apps/
|
||||
│ ├── nextjs/ # Main Next.js application (port 3000)
|
||||
@@ -66,6 +66,8 @@ homarr/
|
||||
│ ├── analytics/ # Server-side analytics (Umami)
|
||||
│ ├── server-settings/ # Server setting keys/types
|
||||
│ ├── settings/ # User-facing settings UI context
|
||||
│ ├── old-import/ # Legacy Homarr import
|
||||
│ ├── old-schema/ # Legacy Homarr zod schemas
|
||||
│ └── cli/ # Node CLI for ops (brocli)
|
||||
├── tooling/
|
||||
│ ├── typescript/ # Base tsconfig
|
||||
@@ -77,11 +79,11 @@ homarr/
|
||||
|
||||
## Three Runtime Services
|
||||
|
||||
| Service | Port | Role |
|
||||
| --------- | ---- | -------------------------------------------- |
|
||||
| Next.js | 3000 | Main web app (SSR + API routes) |
|
||||
| WebSocket | 3001 | tRPC subscriptions via `ws` |
|
||||
| Tasks | 3002 | Cron job runner + management API via Fastify |
|
||||
| Service | Port | Role |
|
||||
|---------|------|------|
|
||||
| Next.js | 3000 | Main web app (SSR + API routes) |
|
||||
| WebSocket | 3001 | tRPC subscriptions via `ws` |
|
||||
| Tasks | 3002 | Cron job runner + management API via Fastify |
|
||||
|
||||
In Docker, nginx listens on **7575** and proxies to all three. The tasks service authenticates via `CRON_JOB_API_KEY` header.
|
||||
|
||||
@@ -110,7 +112,6 @@ In Docker, nginx listens on **7575** and proxies to all three. The tasks service
|
||||
### Multi-Database Support
|
||||
|
||||
Drizzle schemas exist in three parallel implementations:
|
||||
|
||||
- `packages/db/schema/sqlite.ts`
|
||||
- `packages/db/schema/mysql.ts`
|
||||
- `packages/db/schema/postgresql.ts`
|
||||
@@ -120,7 +121,6 @@ Selected at runtime via `DB_DRIVER` env (`better-sqlite3` | `mysql2` | `node-pos
|
||||
### Widget System
|
||||
|
||||
Widgets are registered in `packages/widgets/src/index.tsx` as `widgetImports` (must satisfy `Record<WidgetKind, ...>`). Each widget:
|
||||
|
||||
- Has a `definition` created via `createWidgetDefinition(kind, { icon, createOptions, supportedIntegrations? })`
|
||||
- Uses `optionsBuilder.from(factory => ({ ... }))` for typed options
|
||||
- Loads dynamically via `next/dynamic`
|
||||
@@ -133,7 +133,6 @@ Integrations are defined in `packages/definitions/src/integration.ts` (`integrat
|
||||
### Cron Job System
|
||||
|
||||
Jobs defined in `packages/cron-jobs/src/jobs/`. Each job:
|
||||
|
||||
- Uses `createCronJob` with a cron expression
|
||||
- Publishes results to Redis channels
|
||||
- Widget subscriptions consume those channels via tRPC subscriptions
|
||||
@@ -161,7 +160,6 @@ Configured via `AUTH_PROVIDERS` env. Supports: `credentials` (local), `ldap`, `o
|
||||
## Environment Variables
|
||||
|
||||
Key env vars (from `.env.example`):
|
||||
|
||||
- `DB_DRIVER`: `better-sqlite3` | `mysql2` | `node-postgres`
|
||||
- `DB_URL`: Database connection string
|
||||
- `AUTH_SECRET`: NextAuth secret
|
||||
@@ -193,3 +191,16 @@ Key env vars (from `.env.example`):
|
||||
- Drag-and-drop via `@dnd-kit/*`
|
||||
- `typescript.ignoreBuildErrors: true` in next.config (types checked separately via `typecheck`)
|
||||
- `serverExternalPackages`: `dockerode`, `isomorphic-dompurify`, `jsdom`
|
||||
|
||||
## Documentation (`apps/docs`)
|
||||
|
||||
Docusaurus 3 site at `apps/docs/`, package name `@homarr/docs`.
|
||||
|
||||
- Run with `pnpm dev:docs` from root
|
||||
- Build with `pnpm turbo build --filter=@homarr/docs`
|
||||
- Uses Tailwind CSS 3 for custom pages (not Mantine)
|
||||
- Imports `@homarr/definitions` for type-safe integration/widget metadata
|
||||
- When adding or modifying widgets, integrations, cron jobs, API routes, env vars, or user-facing features, you MUST also update corresponding docs in `apps/docs/docs/`
|
||||
- Integration docs: `apps/docs/docs/integrations/<slug>/index.mdx` + `index.ts`
|
||||
- Widget docs: `apps/docs/docs/widgets/<slug>/index.mdx` + `index.ts`
|
||||
- Strict link checking: broken links/anchors cause build failure
|
||||
@@ -0,0 +1,50 @@
|
||||
---
|
||||
description: Keep documentation in sync with code changes
|
||||
globs:
|
||||
- packages/definitions/src/**
|
||||
- packages/widgets/src/**
|
||||
- packages/integrations/src/**
|
||||
- packages/api/src/**
|
||||
- apps/nextjs/src/**
|
||||
- apps/docs/**
|
||||
---
|
||||
|
||||
# Documentation Sync Rule
|
||||
|
||||
When modifying code that affects user-facing behavior, you MUST also update the documentation in `apps/docs/`.
|
||||
|
||||
## What triggers a doc update
|
||||
|
||||
| Code change | Doc location |
|
||||
|---|---|
|
||||
| New integration | `apps/docs/docs/integrations/<slug>/index.mdx` + `index.ts` |
|
||||
| New widget | `apps/docs/docs/widgets/<slug>/index.mdx` + `index.ts` |
|
||||
| Changed API surface | `apps/docs/docs/management/api.mdx` |
|
||||
| New/changed env vars | `apps/docs/docs/advanced/` |
|
||||
| New CLI commands | `apps/docs/docs/advanced/command-line/` |
|
||||
| Changed auth providers | `apps/docs/docs/advanced/` SSO pages |
|
||||
| UI/UX changes | Relevant getting-started or management pages |
|
||||
| New cron job | `apps/docs/docs/management/tasks.mdx` |
|
||||
| Changed permissions | `apps/docs/docs/management/users.mdx` |
|
||||
|
||||
## Integration doc pattern
|
||||
|
||||
Each integration has a folder under `apps/docs/docs/integrations/<slug>/`:
|
||||
|
||||
- `index.ts` — exports a typed `IntegrationDefinition` with name, description, iconUrl, path
|
||||
- `index.mdx` — uses shared components: `IntegrationHeader`, `IntegrationCapabilites`, `IntegrationSecrets`
|
||||
- Import metadata from `@homarr/definitions` where possible
|
||||
|
||||
## Widget doc pattern
|
||||
|
||||
Each widget has a folder under `apps/docs/docs/widgets/<slug>/`:
|
||||
|
||||
- `index.ts` — exports a typed `WidgetDefinition` with icon, name, description, path, configuration
|
||||
- `index.mdx` — uses shared components: `WidgetHeader`, `WidgetConfig`, `WidgetAdding`
|
||||
|
||||
## Development
|
||||
|
||||
- Run docs locally: `pnpm dev:docs` from root
|
||||
- Build docs: `pnpm turbo build --filter=@homarr/docs`
|
||||
- The docs app can import from `@homarr/definitions` for type-safe integration/widget metadata
|
||||
- Docusaurus strict mode throws on broken links — fix any broken references before committing
|
||||
@@ -1,14 +1,13 @@
|
||||
---
|
||||
name: mcp-integration
|
||||
description: Expose tRPC procedures as MCP tools for AI clients. Use when modifying procedures in packages/api/src/router, adding a new tRPC procedure or router, or deciding what to expose via Homarr's MCP interface. Covers .meta({ mcp }), router registration in packages/api/src/mcp.ts, input schema rules, and what to expose vs skip.
|
||||
description: How to expose tRPC procedures as MCP tools for AI clients
|
||||
globs: packages/api/src/router/**/*.ts
|
||||
alwaysApply: false
|
||||
---
|
||||
|
||||
# MCP Integration
|
||||
# MCP Integration Guide
|
||||
|
||||
Every new tRPC procedure that provides useful functionality should be exposed as an MCP tool so AI assistants (Claude, Cursor, etc.) can use it. This is a first-class feature of Homarr.
|
||||
|
||||
Exposing a procedure makes it callable by any AI client with an API key. Before adding `.meta({ mcp })`, review what the tool exposes: authorization requirements, sensitive data, tenant isolation, auditability, and whether the action is destructive. Prefer exposing read-only queries over mutations unless the mutation is safe and permission-scoped. When in doubt, ask for a security review before shipping a new MCP tool.
|
||||
|
||||
## Adding MCP to a procedure
|
||||
|
||||
Add `.meta({ mcp: { enabled: true, description: "..." } })` to the procedure chain:
|
||||
@@ -17,7 +16,7 @@ Add `.meta({ mcp: { enabled: true, description: "..." } })` to the procedure cha
|
||||
// Read-only tool (query)
|
||||
myProcedure: protectedProcedure
|
||||
.meta({ mcp: { enabled: true, description: "Get all items with their status and metadata" } })
|
||||
.input(z.object({ limit: z.number().default(10) }))
|
||||
.input(z.object({ limit: z.number().default(50) }))
|
||||
.query(async ({ ctx, input }) => { ... })
|
||||
|
||||
// Write tool (mutation)
|
||||
@@ -39,27 +38,23 @@ The `.meta()` call must come **before** `.input()` and `.query()`/`.mutation()`
|
||||
The description is the **only thing** an AI sees to decide when and how to use the tool. Write it like you're explaining to a colleague who has never used Homarr.
|
||||
|
||||
**Must include:**
|
||||
|
||||
- What the tool returns or does
|
||||
- Which integrations/services it works with (if applicable)
|
||||
- How to get required IDs (e.g., "Use integration_all to get the integrationId")
|
||||
- Permission requirements (if non-obvious)
|
||||
|
||||
**Good:**
|
||||
|
||||
```text
|
||||
```
|
||||
"Get calendar events for upcoming and recent media releases. Fetches from all connected Sonarr (TV), Radarr (movies), Lidarr (music), and Readarr (books) integrations. Requires integrationIds from integration_all"
|
||||
```
|
||||
|
||||
**Bad:**
|
||||
|
||||
```text
|
||||
```
|
||||
"Get calendar events"
|
||||
```
|
||||
|
||||
**For tools that return permission fields**, explain what they mean:
|
||||
|
||||
```text
|
||||
```
|
||||
"List all integrations. Returns permissions.hasUseAccess (read) and permissions.hasInteractAccess (actions) — false means the API key lacks that permission, not an error"
|
||||
```
|
||||
|
||||
@@ -82,13 +77,11 @@ export const mcpRouter = createTRPCRouter({
|
||||
## What to expose vs. what to skip
|
||||
|
||||
**Expose:**
|
||||
|
||||
- Data queries (list, search, get by ID)
|
||||
- Actions users would ask an AI to do (toggle, create, delete, approve)
|
||||
- Status checks (health, stats, summaries)
|
||||
|
||||
**Skip:**
|
||||
|
||||
- Subscriptions (WebSocket-only, not supported by MCP)
|
||||
- Internal procedures (session management, onboarding steps)
|
||||
- File upload/download procedures
|
||||
+1
-2
@@ -17,8 +17,7 @@ coverage
|
||||
dev
|
||||
.build
|
||||
e2e
|
||||
apps/docs/*
|
||||
!apps/docs/package.json
|
||||
apps/docs
|
||||
apps/nextjs/.next
|
||||
.env
|
||||
.env.*
|
||||
|
||||
@@ -33,15 +33,6 @@ body:
|
||||
options:
|
||||
# The below comment is used to insert a new version with on-release.yml
|
||||
#NEXT_VERSION#
|
||||
- 1.77.0
|
||||
- 1.76.2
|
||||
- 1.76.1
|
||||
- 1.76.0
|
||||
- 1.75.0
|
||||
- 1.74.0
|
||||
- 1.73.0
|
||||
- 1.72.0
|
||||
- 1.71.0
|
||||
- 1.70.0
|
||||
- 1.69.2
|
||||
- 1.69.0
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
$schema: "https://docs.renovatebot.com/renovate-schema.json",
|
||||
extends: ["config:recommended", ":dependencyDashboardApproval"],
|
||||
extends: ["config:recommended"],
|
||||
packageRules: [
|
||||
{
|
||||
matchPackageNames: ["^@homarr/"],
|
||||
@@ -47,7 +47,7 @@
|
||||
rangeStrategy: "bump",
|
||||
automerge: false,
|
||||
baseBranchPatterns: ["dev"],
|
||||
dependencyDashboard: true,
|
||||
dependencyDashboard: false,
|
||||
// prevent special characters in branch names (otherwise git errors occur on windows)
|
||||
branchNameStrict: true,
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
name: Auto-fix lint and format
|
||||
name: Formatting
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
@@ -6,42 +6,30 @@ on:
|
||||
branches: ["*"]
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
contents: read
|
||||
|
||||
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
|
||||
format:
|
||||
name: Check formatting
|
||||
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
|
||||
- name: Check changed files
|
||||
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"
|
||||
git diff --name-only --diff-filter=ACMR -z "origin/${BASE_REF}...HEAD" -- \
|
||||
'*.css' '*.js' '*.json' '*.jsonc' '*.jsx' '*.md' '*.mdx' '*.scss' '*.ts' '*.tsx' '*.yaml' '*.yml' \
|
||||
| xargs -0 -r pnpm exec oxfmt --check
|
||||
@@ -0,0 +1,35 @@
|
||||
name: "[Dependency Updates] Auto Approve"
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize]
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
approve-automatic-prs:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 2
|
||||
# 158783068: homarr-renovate[bot]
|
||||
# 190541745: homarr-crowdin[bot]
|
||||
# 210161987: homarr-update-contributors[bot]
|
||||
# 175486441: homarr-releases[bot]
|
||||
# Extracted from https://api.github.com/users/<username>
|
||||
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
|
||||
- name: Obtain token
|
||||
id: obtainToken
|
||||
uses: actions/create-github-app-token@v3
|
||||
with:
|
||||
private-key: ${{ secrets.RENOVATE_APPROVE_PRIVATE_KEY }}
|
||||
client-id: ${{ secrets.RENOVATE_APPROVE_APP_ID }}
|
||||
permission-pull-requests: write # required to approve pull request
|
||||
|
||||
- name: Install GitHub CLI
|
||||
run: sudo apt-get install -y gh
|
||||
- name: Approve automatic PRs
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ steps.obtainToken.outputs.token }}
|
||||
run: |
|
||||
gh pr review ${{github.event.pull_request.number}} --approve --body "Automatically approved by GitHub Action"
|
||||
@@ -1,43 +0,0 @@
|
||||
name: "[Bots] Reconcile Auto-merge"
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "*/30 * * * *"
|
||||
workflow_dispatch:
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
reconcile:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v7
|
||||
|
||||
- name: Obtain token
|
||||
id: obtainToken
|
||||
uses: actions/create-github-app-token@v3
|
||||
with:
|
||||
private-key: ${{ secrets.RENOVATE_APPROVE_PRIVATE_KEY }}
|
||||
client-id: ${{ secrets.RENOVATE_APPROVE_APP_ID }}
|
||||
permission-pull-requests: write # required to enable auto-merge
|
||||
|
||||
- name: Install GitHub CLI
|
||||
run: sudo apt-get install -y gh
|
||||
|
||||
- name: Re-enable auto-merge for prepared bot PRs
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ steps.obtainToken.outputs.token }}
|
||||
run: |
|
||||
# 158783068: homarr-renovate[bot]
|
||||
# 190541745: homarr-crowdin[bot]
|
||||
# 210161987: homarr-update-contributors[bot]
|
||||
# 175441: homarr-releases[bot]
|
||||
gh pr list --repo "${{ github.repository }}" --base dev --state open --limit 100 \
|
||||
--json number,author,reviewDecision,mergeStateStatus,autoMergeRequest \
|
||||
--jq '.[] | select(.author.login as $login | ["app/homarr-renovate", "app/homarr-crowdin", "app/homarr-update-contributors", "app/homarr-releases"] | index($login)) | select(.reviewDecision == "APPROVED") | select(.mergeStateStatus == "CLEAN") | select(.autoMergeRequest == null) | .number' \
|
||||
| while read -r pr; do
|
||||
echo "Re-enabling auto-merge on PR #$pr"
|
||||
gh pr merge "$pr" --auto --squash
|
||||
done
|
||||
@@ -1,47 +0,0 @@
|
||||
name: "[Bots] Approve and Auto-merge"
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened, ready_for_review]
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
approve-and-automerge:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
# 158783068: homarr-renovate[bot]
|
||||
# 190541745: homarr-crowdin[bot]
|
||||
# 210161987: homarr-update-contributors[bot]
|
||||
# 175441: homarr-releases[bot]
|
||||
# Extracted from https://api.github.com/users/<username>
|
||||
if: |
|
||||
(github.actor_id == 158783068 || github.actor_id == 190541745 || github.actor_id == 210161987 || github.actor_id == 175441) &&
|
||||
github.event.pull_request.head.repo.full_name == github.repository &&
|
||||
github.event.pull_request.base.ref == 'dev'
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v7
|
||||
|
||||
- name: Obtain token
|
||||
id: obtainToken
|
||||
uses: actions/create-github-app-token@v3
|
||||
with:
|
||||
private-key: ${{ secrets.RENOVATE_APPROVE_PRIVATE_KEY }}
|
||||
client-id: ${{ secrets.RENOVATE_APPROVE_APP_ID }}
|
||||
permission-pull-requests: write # required to approve a pull request and enable auto-merge
|
||||
|
||||
- name: Install GitHub CLI
|
||||
run: sudo apt-get install -y gh
|
||||
|
||||
- name: Approve and enable auto-merge
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ steps.obtainToken.outputs.token }}
|
||||
run: |
|
||||
PR_NUMBER="${{ github.event.pull_request.number }}"
|
||||
if [ "$(gh pr view "$PR_NUMBER" --json reviewDecision --jq .reviewDecision)" != "APPROVED" ]; then
|
||||
gh pr review "$PR_NUMBER" --approve --body "Automatically approved by Homarr bot policy"
|
||||
fi
|
||||
if [ "$(gh pr view "$PR_NUMBER" --json autoMergeRequest --jq '.autoMergeRequest != null')" != "true" ]; then
|
||||
gh pr merge "$PR_NUMBER" --auto --squash
|
||||
fi
|
||||
+113
-17
@@ -35,7 +35,7 @@ permissions:
|
||||
|
||||
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' }}
|
||||
cancel-in-progress: false
|
||||
|
||||
env:
|
||||
FORCE_COLOR: 3
|
||||
@@ -82,7 +82,7 @@ jobs:
|
||||
run: cp .env.example .env
|
||||
|
||||
- name: Lint, typecheck, and build affected workspaces
|
||||
run: pnpm turbo run lint typecheck build --affected --continue=dependencies-successful --concurrency=2
|
||||
run: pnpm turbo run lint typecheck build --affected --continue=dependencies-successful --concurrency=4 --log-order=stream
|
||||
|
||||
- name: Validate workspace configuration
|
||||
run: pnpm lint:ws
|
||||
@@ -102,6 +102,8 @@ jobs:
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
outputs:
|
||||
digest: ${{ steps.preview-build.outputs.digest }}
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
if: "!env.ACT"
|
||||
@@ -184,38 +186,132 @@ jobs:
|
||||
path: e2e/test-results/
|
||||
retention-days: 7
|
||||
|
||||
- name: Determine preview tag
|
||||
id: preview
|
||||
- name: Log in to GHCR for preview publication
|
||||
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: Push amd64 preview image by digest
|
||||
id: preview-build
|
||||
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')
|
||||
)
|
||||
uses: docker/build-push-action@v7
|
||||
with:
|
||||
context: .
|
||||
network: host
|
||||
platforms: linux/amd64
|
||||
outputs: type=image,"name=${{ env.PREVIEW_IMAGE }}",push-by-digest=true,name-canonical=true,push=true
|
||||
cache-from: type=gha,scope=homarr-linux-amd64
|
||||
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: Show Act publication result
|
||||
if: env.ACT
|
||||
run: echo "Act validated the publication path; external GHCR writes are disabled locally."
|
||||
|
||||
preview-arm64:
|
||||
name: Build arm64 preview image
|
||||
needs: fast-gate
|
||||
if: |
|
||||
(
|
||||
(github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository) ||
|
||||
(github.event_name == 'workflow_dispatch' && inputs.operation == 'publish')
|
||||
)
|
||||
runs-on: ubuntu-24.04-arm
|
||||
timeout-minutes: 20
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
outputs:
|
||||
digest: ${{ steps.preview-build.outputs.digest }}
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
if: "!env.ACT"
|
||||
with:
|
||||
persist-credentials: false
|
||||
ref: ${{ github.event_name == 'workflow_dispatch' && inputs.ref || '' }}
|
||||
|
||||
- name: Log in to GHCR
|
||||
if: "!env.ACT"
|
||||
uses: docker/login-action@v4
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
if: "!env.ACT"
|
||||
uses: docker/setup-buildx-action@v4
|
||||
|
||||
- name: Build and push arm64 preview image by digest
|
||||
id: preview-build
|
||||
if: "!env.ACT"
|
||||
uses: docker/build-push-action@v7
|
||||
with:
|
||||
context: .
|
||||
network: host
|
||||
platforms: linux/arm64
|
||||
outputs: type=image,"name=${{ env.PREVIEW_IMAGE }}",push-by-digest=true,name-canonical=true,push=true
|
||||
cache-from: type=gha,scope=homarr-linux-arm64
|
||||
cache-to: type=gha,scope=homarr-linux-arm64,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 }}
|
||||
|
||||
publish-preview:
|
||||
name: Publish multi-platform preview image
|
||||
needs: [container, preview-arm64]
|
||||
if: |
|
||||
(
|
||||
(github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository) ||
|
||||
(github.event_name == 'workflow_dispatch' && inputs.operation == 'publish')
|
||||
)
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
permissions:
|
||||
packages: write
|
||||
env:
|
||||
PREVIEW_TAG: pr-${{ github.event.pull_request.number || inputs.pr_number }}
|
||||
steps:
|
||||
- name: Log in to GHCR
|
||||
if: "!env.ACT"
|
||||
uses: docker/login-action@v4
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
if: "!env.ACT"
|
||||
uses: docker/setup-buildx-action@v4
|
||||
|
||||
- name: Publish preview manifest
|
||||
if: "!env.ACT"
|
||||
env:
|
||||
AMD64_DIGEST: ${{ needs.container.outputs.digest }}
|
||||
ARM64_DIGEST: ${{ needs.preview-arm64.outputs.digest }}
|
||||
run: |
|
||||
docker buildx imagetools create -t "${PREVIEW_IMAGE}:${PREVIEW_TAG}" \
|
||||
"${PREVIEW_IMAGE}@${AMD64_DIGEST}" \
|
||||
"${PREVIEW_IMAGE}@${ARM64_DIGEST}"
|
||||
|
||||
cleanup:
|
||||
name: Clean up preview image
|
||||
if: github.event_name == 'pull_request_target' || (github.event_name == 'workflow_dispatch' && inputs.operation == 'cleanup')
|
||||
|
||||
@@ -68,6 +68,8 @@ jobs:
|
||||
with:
|
||||
node-version: 24.18.0
|
||||
package-manager-cache: false
|
||||
- run: npm i -g pnpm
|
||||
if: env.SKIP_RELEASE == 'false'
|
||||
- name: Install dependencies
|
||||
if: env.SKIP_RELEASE == 'false'
|
||||
run: |
|
||||
|
||||
+1
-3
@@ -71,12 +71,10 @@ apps/nextjs/public/images/background.png
|
||||
|
||||
# personal agent config
|
||||
.opencode
|
||||
.agents/*
|
||||
!.agents/skills/
|
||||
.agents/
|
||||
skills-lock.json
|
||||
|
||||
# docusaurus
|
||||
apps/docs/.docusaurus
|
||||
apps/docs/build
|
||||
.pnpm-store
|
||||
benchmark-results
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"mcpServers": {
|
||||
"mantine": {
|
||||
"command": "npx",
|
||||
"args": ["-y", "@mantine/mcp-server"]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -46,8 +46,6 @@ homarr/
|
||||
├── tooling/
|
||||
│ ├── typescript/ # Base tsconfig
|
||||
│ └── github/ # CI setup action
|
||||
├── tools/
|
||||
│ └── homarr-dev/ # Go CLI for local and PR Docker images
|
||||
├── development/ # Dev docker-compose (Redis, MySQL, PostgreSQL)
|
||||
├── e2e/ # E2E test specs
|
||||
└── Dockerfile # Multi-stage production build
|
||||
@@ -70,9 +68,6 @@ When modifying user-facing code, you MUST also update the corresponding document
|
||||
## Monorepo Commands
|
||||
|
||||
- `pnpm dev` — Next.js app only
|
||||
- `pnpm dev:cli -- dev` — run the developer CLI without installing a global binary
|
||||
- `pnpm db:seed` — seed default database data explicitly
|
||||
- `pnpm docker:dev:up` — start the Redis development service in the background
|
||||
- `pnpm dev:docs` — Docusaurus docs site only
|
||||
- `pnpm turbo build` — build all packages
|
||||
- `pnpm turbo build --filter=@homarr/docs` — build docs only
|
||||
@@ -84,19 +79,5 @@ When modifying user-facing code, you MUST also update the corresponding document
|
||||
- Lint: oxlint (not ESLint)
|
||||
- Format: oxfmt (not Prettier)
|
||||
- UI: Mantine (not Tailwind) — Tailwind is only used in docs app
|
||||
- Mantine: use the `mantine` MCP server (configured in `.mcp.json`; tools `get_item_doc` / `get_item_props` / `search_docs`) for current v9 APIs before writing component code. Prefer built-in primitives (`Combobox`/`useCombobox` for selects, the polymorphic `component` prop, `@mantine/hooks`) over hand-rolled equivalents. Check `packages/ui/` for existing conventions first.
|
||||
|
||||
## MCP servers
|
||||
|
||||
`.mcp.json` at the repo root declares project-scoped MCP servers (the standard Claude Code layout; other tools can import the same `mcpServers` block). See the `mantine` entry there for the Mantine v9 server, and the upstream docs at https://mantine.dev/guides/llms/ for per-tool setup (OpenCode: `.opencode/opencode.json`, Codex: `.codex/config.toml`). Only keyless server definitions belong in `.mcp.json`; anything needing secrets stays in local dotfiles.
|
||||
|
||||
## Agent Skills
|
||||
|
||||
Portable agent skills live in `.agents/skills/` (the Agent Skills open standard). Cursor, Codex, and OpenCode read `.agents/skills/` natively. Claude Code does not scan it, so `.claude/skills/` is a symlink back to `.agents/skills/`. Install skills at `.agents/skills/<name>/SKILL.md` — that single copy is used everywhere. See `.agents/skills/` for available skills (documentation-sync, mcp-integration, codebase-context, datatable-migration).
|
||||
|
||||
- Icons: @tabler/icons-react
|
||||
- Docs app can import from `@homarr/definitions` for shared types
|
||||
- Run `pnpm dev:cli -- dev` to browse local `homarr:*` images and remote PR images.
|
||||
- Run `pnpm dev:cli -- build <name>` from a Homarr checkout to build `homarr:<name>` with rebuild provenance.
|
||||
- Run `pnpm dev:cli -- build --pr <number>` to build a PR locally from a temporary checkout.
|
||||
- Run Go checks from `tools/homarr-dev` with `go test ./...` and `go vet ./...`.
|
||||
@@ -0,0 +1,535 @@
|
||||
# Custom Widgets Architecture Audit and North Star
|
||||
|
||||
> Status: implemented in the current working tree; baseline retained below for traceability
|
||||
> Evidence snapshot: `c5703e7e5` on PR #6304
|
||||
> Scope: built-in custom-widget displays, Custom JSX, network execution, authoring, preview, import/export, AI prompts, and MCP tools
|
||||
|
||||
## Implementation outcome
|
||||
|
||||
The migration described by this audit is implemented. `@homarr/custom-widgets` now owns the domain through explicit `core`, `jsx`, `runtime`, `server`, and `workbench` exports. API, Widgets, and Next.js retain only authorization, persistence, transport, board registration, navigation, and localized adapter concerns.
|
||||
|
||||
Key outcomes:
|
||||
|
||||
- One typed configuration and display-descriptor model drives schemas, defaults, form hydration, serialization, import/export, preview, examples, diagnostics, generated references, and AI prompts.
|
||||
- Display-data extraction moved out of API and Widgets into the domain package; the former API-to-Widgets re-export chain no longer exists.
|
||||
- The interpreter, shared security policy, registry, wrappers, named-request runtime, hardened executor, preview sessions, rate limits, and workbench UI have focused package-owned modules and tests.
|
||||
- Runtime and workbench network behavior is injected through typed ports. The domain package has no dependency on API, Widgets, Next.js routes, notification infrastructure, or modal infrastructure.
|
||||
- Public tRPC and MCP procedures are characterized, v2 and v3 import envelopes remain accepted, display-only v1 remains covered, and MCP offers iterative schema, validation, template read/write/patch, create/import/update, preview query, simulation, and journal tools.
|
||||
- The AI prompt uses a multiline two-block authoring bundle, documents the `SubFetch` render-child contract, and is generated from the canonical registry and tested examples.
|
||||
- Architecture enforcement currently scans 110 production modules for dependency direction, cycles, file budgets, dynamic loading, and untranslated JSX literals. Generated-reference drift and every Mantine export classification are test-enforced.
|
||||
- The latest production build measured the largest Custom JSX route payload at 99,852 gzip bytes across two chunks, below the 768 KiB budget.
|
||||
|
||||
Focused verification is green: 278 package tests, 35 API/MCP tests, 52 Widgets renderer tests, 7 E2E tests, affected typecheck, affected lint, docs build, production Next.js build, architecture checks, generated drift, and the bundle budget. The root `pnpm test` command is presently blocked on this workstation by unrelated Docker-backed integration suites timing out while the shared Docker daemon is under external load; no Custom Widget test failed in those runs.
|
||||
|
||||
## Executive summary
|
||||
|
||||
The feature has a strong security direction and a capable authoring experience, but its implementation has outgrown its current ownership model. PR #6304 changes 56 files with 13,206 additions. The primary feature directories contain 10,437 lines, spread across `@homarr/widgets`, `@homarr/api`, `@homarr/definitions`, `@homarr/validation`, and the Next.js management route.
|
||||
|
||||
The main problem is not the absolute line count. It is that domain policy, transport, persistence, rendering, form state, compatibility, and documentation generation are interleaved across package boundaries. A change to one display type can require synchronized edits in schemas, form defaults, import conversion, preview extraction, runtime rendering, documentation, and AI instructions. Several of those paths use untyped records, duplicated conversion logic, or direct API dependencies.
|
||||
|
||||
The north star is a single `@homarr/custom-widgets` domain package with explicit `core`, `jsx`, `runtime`, `server`, and `workbench` entry points. Existing applications and packages become thin adapters. The extraction must preserve public APIs, import formats, permission behavior, and the current UI while improving internal cohesion.
|
||||
|
||||
This audit does not authorize a big-bang rewrite. The migration should be a sequence of behavior-preserving PRs, beginning with characterization tests and ending with enforced dependency and complexity budgets.
|
||||
|
||||
## Measured baseline
|
||||
|
||||
### Change footprint
|
||||
|
||||
| Measure | Current value |
|
||||
| ------------------------------- | ------------: |
|
||||
| PR files changed | 56 |
|
||||
| PR additions | 13,206 |
|
||||
| PR deletions | 1,385 |
|
||||
| Primary feature-directory lines | 10,437 |
|
||||
|
||||
Generated component-reference JSON accounts for 2,491 added lines and is not treated as source complexity.
|
||||
|
||||
### Largest production files
|
||||
|
||||
| File | Lines | Responsibilities currently combined |
|
||||
| ------------------------------------------------------------------------------- | ----: | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `apps/nextjs/src/app/[locale]/manage/custom-widgets/_custom-widget-form.tsx` | 1,911 | Schema refinement, defaults, server-error mapping, form conversion, submission, preview state, focus management, display picker, connection fields, and every display-specific editor |
|
||||
| `packages/api/src/router/custom-widget/custom-widget-router.ts` | 978 | CRUD, import/export, template patching, MCP descriptions, preview sessions, preview execution, journals, and action simulation/execution |
|
||||
| `packages/widgets/src/custom-api/safe-jsx-interpreter.tsx` | 944 | AST model, parsing, environments, budgets, sanitization, expression evaluation, safe methods, callbacks, and React rendering |
|
||||
| `apps/nextjs/src/app/[locale]/manage/custom-widgets/_custom-widget-preview.tsx` | 834 | Preview orchestration, error boundary, tabs, response tree, diagnostics, request journal, sample data, and renderer selection |
|
||||
| `packages/widgets/src/custom-api/jsx-whitelist.ts` | 775 | Mantine imports, wrappers, icon mapping, runtime component map, registry filtering, data sanitization, and safe bindings |
|
||||
| `packages/widgets/src/custom-api/jsx-sub-fetch.tsx` | 599 | Query rendering, render props, path display, action execution, buttons, toggles, refresh, notifications, and confirmations |
|
||||
|
||||
These files exceed the proposed 300-line production-module target. The interpreter is a reasonable candidate for a documented 400-line exception after its independent policies, evaluators, and renderer are extracted.
|
||||
|
||||
### Current ownership
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
D["@homarr/definitions<br/>registry and examples"]
|
||||
V["@homarr/validation<br/>schemas and AST diagnostics"]
|
||||
W["@homarr/widgets<br/>renderers and interpreter"]
|
||||
A["@homarr/api<br/>CRUD, permissions, execution and MCP"]
|
||||
N["Next.js route<br/>workbench, preview and prompt"]
|
||||
X["Docs<br/>generated reference and guides"]
|
||||
|
||||
N --> A
|
||||
N --> V
|
||||
N --> D
|
||||
N --> W
|
||||
W --> A
|
||||
W --> D
|
||||
A --> V
|
||||
X --> D
|
||||
```
|
||||
|
||||
The `@homarr/widgets → @homarr/api` edge is particularly costly. Runtime request components call the generated tRPC client directly, so moving the domain into a package that is also consumed by the API would create a dependency cycle unless request execution is inverted behind a typed port.
|
||||
|
||||
## What is working well
|
||||
|
||||
- Custom JSX v2 replaces raw runtime URLs and methods with named, typed requests.
|
||||
- Board permissions, network scope, DNS validation, redirect policy, response limits, caching, concurrency, and action rules are server-enforced.
|
||||
- The safe interpreter no longer treats an upstream JSX parser as a sandbox.
|
||||
- Component metadata drives runtime classification, editor completion, docs generation, and part of the AI prompt.
|
||||
- The workbench supports multiline JSX, diagnostics, real preview sizes, response inspection, request journals, import review, undo/redo, and paste-to-import.
|
||||
- MCP exposes schema, validation, preview, journal, template read/write/patch, import, and update workflows without exposing stored secrets.
|
||||
- Shared examples cover simple metrics, nested queries, charts, actions, and richer interactive layouts.
|
||||
|
||||
The migration must preserve these capabilities. Package extraction is not a reason to weaken the security boundary or remove authoring features.
|
||||
|
||||
## Findings
|
||||
|
||||
### P1 — Domain ownership is fragmented
|
||||
|
||||
Custom-widget behavior is implemented in five packages/applications. None of them is the clear source of truth for the complete lifecycle. `@homarr/definitions` owns component metadata and examples, `@homarr/validation` owns schemas and template diagnostics, `@homarr/widgets` owns runtime evaluation, `@homarr/api` owns network and persistence flows, and Next.js owns authoring transformations.
|
||||
|
||||
Impact:
|
||||
|
||||
- Maintainers must search unrelated packages to understand one behavior.
|
||||
- Changes have a large synchronization surface and can drift silently.
|
||||
- The domain cannot be tested or reused as a coherent unit.
|
||||
- Package names describe infrastructure location rather than feature ownership.
|
||||
|
||||
### P1 — Giant modules mix unrelated reasons to change
|
||||
|
||||
The largest files are not merely long; they combine policy, orchestration, state, and presentation. The form contains all display editors. The router contains every management and preview procedure. The interpreter contains parsing through React emission. The request component file contains both query and mutation interaction models.
|
||||
|
||||
Impact:
|
||||
|
||||
- Reviews are broad and regression-prone.
|
||||
- Unit-test seams are difficult to identify.
|
||||
- Merge conflicts concentrate in a small number of files.
|
||||
- Small feature changes require understanding thousands of lines of adjacent behavior.
|
||||
|
||||
### P1 — Runtime code depends directly on the API client
|
||||
|
||||
`packages/widgets/src/custom-api/component.tsx` and `jsx-sub-fetch.tsx` import `@homarr/api/client`. They also directly invoke notifications and confirmation modals.
|
||||
|
||||
Impact:
|
||||
|
||||
- The renderer cannot be used or tested without application infrastructure.
|
||||
- A future `@homarr/custom-widgets` package would cycle with `@homarr/api` if both retained these imports.
|
||||
- Preview and board runtime cannot share the same renderer through different request implementations without branching inside the components.
|
||||
|
||||
Required direction: a runtime provider must receive typed query, action, confirmation, notification, and invalidation ports. The board and preview layers supply adapters.
|
||||
|
||||
### P1 — Configuration transformations are duplicated
|
||||
|
||||
Create defaults and builders live in the form, edit hydration lives in the edit page, and import hydration lives in `_custom-widget-import-utils.ts`. These paths independently translate between stored display configs and form values. They rely on display-type switches, record casts, and JSON text fields such as the request manifest.
|
||||
|
||||
Impact:
|
||||
|
||||
- Create, edit, paste replacement, import, export, and MCP can disagree.
|
||||
- Adding a field requires updating multiple transforms.
|
||||
- Round trips are difficult to prove lossless.
|
||||
|
||||
Required direction: each display type supplies one typed descriptor containing schema, defaults, `toForm`, `fromForm`, preview projection, and migration behavior.
|
||||
|
||||
### P1 — Type safety degrades at domain boundaries
|
||||
|
||||
`Record<string, unknown>`, `as never`, and `as unknown as` are frequent in form conversion, display extraction, preview state, imports, registry wrappers, and API parsing. Some casts are necessary at the interpreter/React boundary, but most domain-level casts indicate missing discriminated unions or validated adapters.
|
||||
|
||||
Impact:
|
||||
|
||||
- Invalid combinations survive until runtime.
|
||||
- Refactors do not receive complete compiler guidance.
|
||||
- Server and client transformations can diverge while continuing to compile.
|
||||
|
||||
Required direction: parse unknown values once at ingress and use typed domain objects afterward. Unsafe casts must be isolated to named boundary modules and justified by tests.
|
||||
|
||||
### P1 — Security policy is represented more than once
|
||||
|
||||
The template validator and interpreter both parse JSX and maintain concepts such as blocked properties, blocked props, AST depth, and operation limits. Runtime component filtering adds another policy layer.
|
||||
|
||||
The analyzer and interpreter have different jobs, so they should not be collapsed into one evaluator. They must, however, consume one immutable policy definition and shared AST normalization utilities.
|
||||
|
||||
Impact:
|
||||
|
||||
- A property can be rejected by one layer and missed by another.
|
||||
- Security changes require parallel edits and duplicate tests.
|
||||
- Diagnostics can describe behavior that differs from runtime enforcement.
|
||||
|
||||
### P1 — The API router combines transport and application behavior
|
||||
|
||||
The router owns tRPC metadata, Zod inputs, database writes, encryption, import/export, template revisions, preview execution, rate-limit acquisition, journal recording, and MCP documentation.
|
||||
|
||||
Impact:
|
||||
|
||||
- Domain logic is only testable through tRPC context setup.
|
||||
- MCP and browser endpoints can accidentally implement different workflows.
|
||||
- Framework-specific `TRPCError` values leak into lower-level executor and manifest code.
|
||||
|
||||
Required direction: tRPC procedures authorize, load persistence models, call a domain service, and map domain errors. They do not implement request rendering, template patching, preview execution, or import conversion inline.
|
||||
|
||||
### P2 — Metadata generation is only partially centralized
|
||||
|
||||
The component registry and examples are shared, but runtime maps, wrappers, safe bindings, display-type metadata, AI prompt prose, form choices, and some docs remain separately assembled. The AI prompt is built inside a client button component.
|
||||
|
||||
Impact:
|
||||
|
||||
- Agent instructions can drift from validation and runtime behavior.
|
||||
- Prompt generation requires UI and network context.
|
||||
- Display types lack the same single-registry guarantee as JSX components.
|
||||
|
||||
Required direction: prompt generation is a pure function in `core`, using the schema, display descriptors, component registry, and tested fixtures. The UI only fetches context and copies the result.
|
||||
|
||||
### P2 — Compatibility behavior is mixed into primary flows
|
||||
|
||||
V1 detection, migration warnings, legacy network diagnostics, v2 imports, v3 exports, and fallback display parsing are embedded beside current authoring/runtime logic.
|
||||
|
||||
Impact:
|
||||
|
||||
- Happy paths are harder to read.
|
||||
- Legacy behavior can accidentally influence v2 code.
|
||||
- Compatibility retirement has no obvious deletion boundary.
|
||||
|
||||
Required direction: version detection and migrations live under `core/migrations`. Runtime and workbench receive a normalized current model plus explicit review state.
|
||||
|
||||
### P2 — Test topology does not mirror the architecture
|
||||
|
||||
Interpreter coverage is substantial, and request-executor/router tests exist, but tests are colocated under their current infrastructure owners instead of a feature package. A focused renderer run passed 52 tests during review, while the selected API suites failed before collecting tests because the root Vitest client environment rejected server-only environment access.
|
||||
|
||||
Impact:
|
||||
|
||||
- The complete feature cannot be verified with one package command.
|
||||
- Server test failures can be configuration failures rather than behavioral failures.
|
||||
- Form conversion and workbench orchestration have limited direct characterization coverage relative to their size.
|
||||
|
||||
Required direction: the package owns focused test projects or environment-specific configs for core, DOM runtime/workbench, and Node server tests. API adapter tests remain in `@homarr/api`.
|
||||
|
||||
### P2 — A small amount of user-facing copy bypasses translation
|
||||
|
||||
Examples found during the snapshot include the raw-response tooltip and a default empty template message. Dynamic translation keys also require repeated `as never` casts, reducing translation-key safety.
|
||||
|
||||
Impact:
|
||||
|
||||
- Locale completeness gates are unreliable.
|
||||
- Refactoring copy is harder than changing typed translation keys.
|
||||
|
||||
Required direction: all package UI receives a typed message catalog or uses typed scoped translations. No visible fallback literals are allowed in production components.
|
||||
|
||||
## North-star package
|
||||
|
||||
```text
|
||||
packages/custom-widgets/
|
||||
├── src/core/
|
||||
│ ├── config/ # discriminated display configurations and form models
|
||||
│ ├── displays/ # typed display descriptors and built-in display metadata
|
||||
│ ├── import/ # v2/v3 parsing, export, review, and authoring bundles
|
||||
│ ├── migrations/ # v1 detection, normalization, and review requirements
|
||||
│ ├── prompts/ # pure AI prompt generation
|
||||
│ └── examples/ # typed fixtures shared by tests, docs, editor, and prompts
|
||||
├── src/jsx/
|
||||
│ ├── ast/ # parser adapter, AST types, traversal, and normalization
|
||||
│ ├── policy/ # blocked properties, props, URLs, and budgets
|
||||
│ ├── analyzer/ # authoring diagnostics
|
||||
│ ├── interpreter/ # expression and collection evaluation
|
||||
│ ├── registry/ # component descriptors, wrappers, and export classification
|
||||
│ └── bindings/ # branded safe helpers and sanitized data
|
||||
├── src/runtime/
|
||||
│ ├── displays/ # built-in renderers
|
||||
│ ├── jsx/ # Custom JSX renderer and error containment
|
||||
│ ├── requests/ # SubFetch, actions, toggles, refresh, and state handling
|
||||
│ └── provider/ # injected runtime capabilities
|
||||
├── src/server/
|
||||
│ ├── network/ # hardened executor and destination policy
|
||||
│ ├── requests/ # manifest resolution and parameter substitution
|
||||
│ ├── limits/ # rate and concurrency limits
|
||||
│ └── preview/ # sessions, simulation, and journals
|
||||
└── src/workbench/
|
||||
├── form/ # shared state and connection/display sections
|
||||
├── editor/ # CodeMirror and registry-driven diagnostics
|
||||
├── preview/ # widget, response, journal, and diagnostic views
|
||||
└── import/ # clipboard parsing and review UI
|
||||
```
|
||||
|
||||
Public exports are explicit:
|
||||
|
||||
```json
|
||||
{
|
||||
"exports": {
|
||||
"./core": "./src/core/index.ts",
|
||||
"./jsx": "./src/jsx/index.ts",
|
||||
"./runtime": "./src/runtime/index.ts",
|
||||
"./server": "./src/server/index.ts",
|
||||
"./workbench": "./src/workbench/index.ts"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
There is no broad root barrel. Internal folders cannot be imported by consumers.
|
||||
|
||||
### Dependency rules
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
C["custom-widgets/core"]
|
||||
J["custom-widgets/jsx"]
|
||||
R["custom-widgets/runtime"]
|
||||
S["custom-widgets/server"]
|
||||
B["custom-widgets/workbench"]
|
||||
API["@homarr/api adapter"]
|
||||
W["@homarr/widgets adapter"]
|
||||
N["Next.js route adapter"]
|
||||
DOC["Docs generator"]
|
||||
|
||||
J --> C
|
||||
R --> C
|
||||
R --> J
|
||||
S --> C
|
||||
B --> C
|
||||
B --> R
|
||||
API --> C
|
||||
API --> S
|
||||
W --> C
|
||||
W --> R
|
||||
N --> C
|
||||
N --> B
|
||||
DOC --> C
|
||||
DOC --> J
|
||||
```
|
||||
|
||||
Hard rules:
|
||||
|
||||
1. `@homarr/custom-widgets` never imports `@homarr/api`, `@homarr/widgets`, or Next.js route modules.
|
||||
2. `core` has no React, Mantine, tRPC, database, Redis, or browser dependency.
|
||||
3. `jsx` has no API, persistence, Redis, notification, or modal dependency.
|
||||
4. `runtime` performs no direct tRPC calls. It consumes `CustomWidgetRuntimePort`.
|
||||
5. `workbench` performs no direct tRPC calls. It consumes `CustomWidgetWorkbenchPort` callbacks.
|
||||
6. `server` has no tRPC or database-query dependency. API adapters load authorized records and map errors.
|
||||
7. Cross-entry imports follow the arrows above; reverse imports fail CI.
|
||||
|
||||
### Runtime ports
|
||||
|
||||
The exact transport remains owned by adapters. The domain contract should cover behavior rather than tRPC details:
|
||||
|
||||
```ts
|
||||
interface CustomWidgetRuntimePort {
|
||||
getData(input: { itemId: string; signal?: AbortSignal }): Promise<CustomWidgetRequestResult>;
|
||||
query(input: NamedRequestInvocation & { signal?: AbortSignal }): Promise<CustomWidgetRequestResult>;
|
||||
action(input: NamedRequestInvocation & { confirmed?: boolean }): Promise<CustomWidgetRequestResult>;
|
||||
confirm(input: ActionConfirmation): Promise<boolean>;
|
||||
notify(input: RuntimeNotification): void;
|
||||
invalidate(input: RuntimeInvalidation): Promise<void>;
|
||||
}
|
||||
```
|
||||
|
||||
Board runtime and preview runtime implement the same port. Preview simulation is an adapter capability, not a conditional tRPC import inside `ActionButton`.
|
||||
|
||||
### Display descriptors
|
||||
|
||||
Every display type is represented once:
|
||||
|
||||
```ts
|
||||
interface CustomWidgetDisplayDescriptor<TConfig, TForm> {
|
||||
type: CustomWidgetDisplayType;
|
||||
configSchema: ZodType<TConfig>;
|
||||
formSchema: ZodType<TForm>;
|
||||
createDefault(): TConfig;
|
||||
toForm(config: TConfig): TForm;
|
||||
fromForm(form: TForm): TConfig;
|
||||
migrate(input: unknown): DisplayMigrationResult<TConfig>;
|
||||
extract(response: unknown, config: TConfig): DisplayModel;
|
||||
capabilities(config: TConfig): CustomWidgetCapability[];
|
||||
}
|
||||
```
|
||||
|
||||
UI fields and renderer components may be registered in runtime/workbench companion maps keyed by the same typed display identifier. Domain descriptors remain serializable and framework-independent.
|
||||
|
||||
## Developer-experience north star
|
||||
|
||||
### Maintainers
|
||||
|
||||
- Start at `packages/custom-widgets` for every feature behavior.
|
||||
- Find one module per concept and one descriptor per display type.
|
||||
- Add a display type without editing central switches in multiple packages.
|
||||
- Run one package command for core, DOM, and server tests.
|
||||
- Receive compile-time failures when a configuration field is not handled by forms, preview, migration, or export.
|
||||
- Review PRs that move one domain boundary at a time.
|
||||
|
||||
### Administrators and template authors
|
||||
|
||||
- Preserve the current continuous form and live workbench.
|
||||
- Keep preview usable before a live response exists through schema-valid defaults and editable sample data.
|
||||
- Keep diagnostics actionable, localized, clickable, and consistent with runtime policy.
|
||||
- Keep imports reviewable and secrets excluded.
|
||||
- Keep query and action capabilities visible without noisy permanent warnings.
|
||||
|
||||
### AI agents
|
||||
|
||||
- Obtain schema, registry, limits, examples, and prompt rules from the same typed sources.
|
||||
- Use readable multiline templates through authoring bundles and line-based template tools.
|
||||
- Validate, preview, inspect journals, patch, and revalidate without rewriting unrelated configuration.
|
||||
- Receive stable machine-actionable issue codes in addition to localized or human-readable messages.
|
||||
- Never need to infer runtime behavior from UI-only prompt strings.
|
||||
|
||||
## Fragmentation rules
|
||||
|
||||
- Production modules target at most 300 lines, excluding generated files, fixtures, and type-only declarations.
|
||||
- Algorithmic modules can reach 400 lines only with a file-level rationale and focused tests.
|
||||
- A module has one primary reason to change; moving lines without separating policy from orchestration does not satisfy the target.
|
||||
- React components do not contain schema definitions or persistence transforms.
|
||||
- Routers do not contain reusable domain algorithms.
|
||||
- Domain functions do not throw framework-specific errors.
|
||||
- Unknown input is validated once. Parsed values are not carried as `Record<string, unknown>` through internal layers.
|
||||
- Compatibility code lives in versioned migration modules and has explicit removal criteria.
|
||||
- Generated artifacts are reproducible and checked for drift, but excluded from line budgets.
|
||||
|
||||
## Migration roadmap
|
||||
|
||||
### PR 1 — Characterize contracts
|
||||
|
||||
- Add round-trip tests for every display descriptor: default → form → config, stored config → form → config, and import → export.
|
||||
- Add fixtures for v1 display-only, v1 network-review-required, v2 import, and v3 export.
|
||||
- Add prompt snapshots sourced from the component registry and examples.
|
||||
- Split Node and DOM Vitest projects so server suites collect and run reliably.
|
||||
- Record bundle and render baselines before moving modules.
|
||||
|
||||
Gate: all existing behavior is represented by tests, with no production moves yet.
|
||||
|
||||
### PR 2 — Establish `core`
|
||||
|
||||
- Create `@homarr/custom-widgets` and explicit exports.
|
||||
- Move custom-widget schemas, typed configs, display descriptors, examples, import/export, migration, capability analysis, and prompt generation into `core`.
|
||||
- Replace form/edit/import conversion duplication with descriptor calls.
|
||||
- Temporarily re-export legacy imports from definitions and validation to keep consumers stable.
|
||||
|
||||
Gate: create, edit, import, export, MCP validation, and AI prompt generation use the same domain contracts.
|
||||
|
||||
### PR 3 — Extract JSX policy and interpreter
|
||||
|
||||
- Move AST types, parser adapter, shared security policy, analyzer, evaluator, budgets, bindings, and registry into `jsx`.
|
||||
- Separate evaluator modules by expression family and React emission.
|
||||
- Generate diagnostics and runtime enforcement from the same policy constants.
|
||||
- Keep the Mantine export-classification CI check.
|
||||
|
||||
Gate: the security suite passes unchanged, analyzer/runtime policy drift tests pass, and no consumer imports JSX internals.
|
||||
|
||||
### PR 4 — Extract runtime
|
||||
|
||||
- Move built-in renderers, Custom JSX display, SubFetch, actions, toggles, refresh, and error containment into `runtime`.
|
||||
- Introduce the runtime port and adapters for board and preview contexts.
|
||||
- Remove direct imports of the API client, modals, and notifications from domain components.
|
||||
- Split request components into query, action, toggle, refresh, feedback, and shared-state modules.
|
||||
|
||||
Gate: board rendering and workbench preview use the same runtime components through different adapters; actions retain confirmation, rollback, invalidation, and permission behavior.
|
||||
|
||||
### PR 5 — Extract server services
|
||||
|
||||
- Move executor, manifests, network policy, limits, caching/single-flight, and preview-session logic into `server`.
|
||||
- Replace `TRPCError` below the adapter with typed domain errors.
|
||||
- Split API procedures into management, templates, preview, and runtime routers that delegate to package services.
|
||||
- Keep authorization, database lookup, encryption, and tRPC/MCP metadata in `@homarr/api`.
|
||||
|
||||
Gate: browser and MCP flows call the same services; request security tests run as Node package tests; public procedure names and inputs remain compatible.
|
||||
|
||||
### PR 6 — Extract and fragment workbench
|
||||
|
||||
- Move reusable editor, preview, response tree, import review, connection section, display picker, and display-specific fields into `workbench`.
|
||||
- Introduce a workbench port for validate, preview, query, simulate action, journal, and save callbacks.
|
||||
- Keep Next.js pages responsible only for routing, loading initial data, and constructing adapters.
|
||||
- Replace the monolithic form with small sections and descriptor-driven display fields while preserving the continuous form, two-pane scrolling, autofocus, and mobile modes.
|
||||
|
||||
Gate: no workbench source file exceeds the agreed budget, and create/edit/paste replacement share one state model.
|
||||
|
||||
### PR 7 — Remove scaffolding and enforce boundaries
|
||||
|
||||
- Remove compatibility re-exports and old implementations after all consumers move.
|
||||
- Add CI checks for forbidden imports, dependency cycles, source-size budgets, untranslated literals, generated-artifact drift, component classification, and custom-JSX chunk size.
|
||||
- Update contributor and architecture documentation with package entry points and extension recipes.
|
||||
|
||||
Gate: old ownership paths contain only thin adapters or are deleted.
|
||||
|
||||
## Verification strategy
|
||||
|
||||
### Core
|
||||
|
||||
- Every display descriptor accepts valid data and rejects mismatched configuration.
|
||||
- All create/edit/import/export transformations are lossless where expected.
|
||||
- V1/v2/v3 compatibility and review states are deterministic.
|
||||
- Prompt output, authoring bundles, fixtures, docs data, and completion data are generated from shared sources.
|
||||
|
||||
### JSX
|
||||
|
||||
- Computed and Unicode-obfuscated reflective access remains blocked.
|
||||
- Analyzer and interpreter share property, prop, URL, and budget policy.
|
||||
- Own-property resolution, branded calls, callback accounting, and node/operation limits remain enforced.
|
||||
- Every upstream Mantine export is classified and every enabled runtime component resolves.
|
||||
|
||||
### Runtime and workbench
|
||||
|
||||
- Query cancellation, stale results, retries, render children, action locks, DELETE confirmation, toggle rollback, and invalidation behave identically in preview and boards.
|
||||
- Error boundaries isolate template and display failures.
|
||||
- Empty, sample, stale, loading, failure, and success states are localized and accessible.
|
||||
- Keyboard, focus, responsive, reduced-motion, and narrow-widget behavior remain covered.
|
||||
|
||||
### Server and adapters
|
||||
|
||||
- Board IDOR, permission levels, anonymous actions, named-request kinds, substitutions, DNS classes, redirects, headers, response limits, caching, and rate limits remain covered.
|
||||
- Server tests run under an explicit Node environment rather than failing during client-environment initialization.
|
||||
- MCP schemas and descriptions remain discoverable, stable, and absent from unsafe runtime procedures.
|
||||
|
||||
### Required commands
|
||||
|
||||
At the end of the migration, the package and repository must pass:
|
||||
|
||||
```sh
|
||||
pnpm --filter @homarr/custom-widgets test
|
||||
pnpm test
|
||||
pnpm test:e2e
|
||||
pnpm turbo typecheck --affected
|
||||
pnpm turbo lint --affected
|
||||
pnpm turbo build --filter=@homarr/docs
|
||||
pnpm check:custom-widget-bundle # after the Next.js production build
|
||||
```
|
||||
|
||||
CI additionally checks dependency direction, cycles, file budgets, generated references, translations, and client bundle size.
|
||||
|
||||
## Completion criteria
|
||||
|
||||
- `@homarr/custom-widgets` owns every reusable custom-widget domain behavior.
|
||||
- No circular dependencies exist.
|
||||
- The package does not import `@homarr/api`, `@homarr/widgets`, or Next.js route modules.
|
||||
- Runtime and workbench networking uses injected typed ports.
|
||||
- One typed descriptor is the source of truth for each display type.
|
||||
- One shared JSX policy drives authoring diagnostics and runtime enforcement.
|
||||
- Create, edit, import, export, preview, MCP, and board runtime use the same typed transformations.
|
||||
- Public tRPC/MCP names, import formats, stored configuration, and permission behavior remain compatible.
|
||||
- V1 display-only migration and network-review states remain tested.
|
||||
- Production modules satisfy the 300/400-line budgets.
|
||||
- No untranslated user-facing strings remain.
|
||||
- Core, JSX, runtime, server, workbench, integration, E2E, accessibility, docs, and bundle checks pass.
|
||||
- A maintainer can locate a custom-widget behavior from the package structure without searching unrelated packages.
|
||||
|
||||
## Explicit non-goals
|
||||
|
||||
- Replacing Mantine or Homarr's visual language.
|
||||
- Allowing arbitrary cross-origin requests.
|
||||
- Replacing named requests with general JavaScript execution.
|
||||
- Changing stored secrets or exposing them through export or MCP.
|
||||
- Redesigning the current management UX during the package extraction.
|
||||
- Removing compatibility formats before migration and review flows are proven.
|
||||
|
||||
## Decision record
|
||||
|
||||
- The package owns all Custom Widget behavior, not only Custom JSX.
|
||||
- Security and correctness are hard constraints.
|
||||
- Maintainer simplicity is the primary tie-breaker after those constraints.
|
||||
- Current user and agent workflows are preserved during extraction.
|
||||
- Generated artifacts do not count toward source-size budgets.
|
||||
- Migration occurs through reviewable, behavior-preserving PRs rather than a single rewrite.
|
||||
@@ -1,23 +1,3 @@
|
||||
## [1.77.1](https://github.com/homarr-labs/homarr/compare/v1.77.0...v1.77.1) (2026-09-11)
|
||||
|
||||
## [1.77.0](https://github.com/homarr-labs/homarr/compare/v1.76.2...v1.77.0) (2026-09-06)
|
||||
|
||||
## [1.76.2](https://github.com/homarr-labs/homarr/compare/v1.76.1...v1.76.2) (2026-08-27)
|
||||
|
||||
## [1.76.1](https://github.com/homarr-labs/homarr/compare/v1.76.0...v1.76.1) (2026-08-26)
|
||||
|
||||
## [1.76.0](https://github.com/homarr-labs/homarr/compare/v1.75.0...v1.76.0) (2026-08-21)
|
||||
|
||||
## [1.75.0](https://github.com/homarr-labs/homarr/compare/v1.74.0...v1.75.0) (2026-08-14)
|
||||
|
||||
## [1.74.0](https://github.com/homarr-labs/homarr/compare/v1.73.0...v1.74.0) (2026-08-07)
|
||||
|
||||
## [1.73.0](https://github.com/homarr-labs/homarr/compare/v1.72.0...v1.73.0) (2026-07-31)
|
||||
|
||||
## [1.72.0](https://github.com/homarr-labs/homarr/compare/v1.71.0...v1.72.0) (2026-07-24)
|
||||
|
||||
## [1.71.0](https://github.com/homarr-labs/homarr/compare/v1.70.0...v1.71.0) (2026-07-17)
|
||||
|
||||
## [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)
|
||||
|
||||
@@ -206,14 +206,6 @@ packages/integrations/src/unraid @manuel-rw
|
||||
apps/docs/docs/integrations/uptime-kuma @ajnart
|
||||
packages/integrations/src/uptime-kuma @ajnart
|
||||
|
||||
# What's Up Docker (WUD) - introduced in https://github.com/homarr-labs/homarr/pull/6523 by @tiimonn
|
||||
apps/docs/docs/integrations/whats-up-docker @tiimonn
|
||||
apps/docs/docs/widgets/whats-up-docker @tiimonn
|
||||
packages/api/src/router/widgets/wud.ts @tiimonn
|
||||
packages/integrations/src/wud @tiimonn
|
||||
packages/request-handler/src/wud.ts @tiimonn
|
||||
packages/widgets/src/wud @tiimonn
|
||||
|
||||
# Traefik - by @Nerdy-Technician
|
||||
apps/docs/docs/integrations/traefik @Nerdy-Technician
|
||||
apps/docs/docs/widgets/traefik @Nerdy-Technician
|
||||
|
||||
+8
-19
@@ -3,18 +3,12 @@
|
||||
FROM node:24.18.0-alpine AS base
|
||||
|
||||
FROM base AS builder
|
||||
ARG TARGETPLATFORM
|
||||
WORKDIR /app
|
||||
# Native dependencies normally use prebuilds, but the build must remain
|
||||
# reproducible when a prebuild download times out and compilation is required.
|
||||
RUN apk add --no-cache libc6-compat curl bash python3 make g++
|
||||
RUN apk add --no-cache libc6-compat curl bash && apk update
|
||||
|
||||
RUN corepack enable pnpm
|
||||
COPY .npmrc pnpm-lock.yaml pnpm-workspace.yaml package.json ./
|
||||
COPY pnpm-lock.yaml pnpm-workspace.yaml package.json ./
|
||||
COPY patches ./patches
|
||||
COPY --parents ./apps/*/package.json ./packages/*/package.json ./tooling/*/package.json ./
|
||||
# @homarr/definitions generates documentation types during install.
|
||||
COPY --parents ./packages/definitions/src ./
|
||||
# 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
|
||||
@@ -25,16 +19,14 @@ COPY --parents ./packages/definitions/src ./
|
||||
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 --ignore-scripts && \
|
||||
pnpm fetch && \
|
||||
sed -i 's/nodeLinker: isolated/nodeLinker: hoisted/' pnpm-workspace.yaml
|
||||
|
||||
# Install only from the fetched, committed lockfile so local and Docker builds
|
||||
# resolve the same dependency graph. Serial lifecycle builds avoid esbuild's
|
||||
# atomic binary replacement racing across the hoisted workspace (pnpm/pnpm#8200).
|
||||
RUN --mount=type=cache,id=pnpm-store,target=/pnpm/store \
|
||||
npm_config_nodedir=/usr/local pnpm install --recursive --offline --frozen-lockfile --child-concurrency=1
|
||||
|
||||
COPY . .
|
||||
# Follow the pnpm fetch pattern from https://pnpm.io/cli/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
|
||||
|
||||
ARG SKIP_ENV_VALIDATION='true'
|
||||
ARG CI='true'
|
||||
@@ -45,9 +37,7 @@ 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 \
|
||||
--mount=type=cache,id=homarr-next-build-${TARGETPLATFORM},target=/app/apps/nextjs/.next/cache,sharing=locked \
|
||||
TURBO_PLATFORM="${TARGETPLATFORM:-linux/amd64}" \
|
||||
pnpm turbo build --filter=@homarr/nextjs... --filter=@homarr/cli
|
||||
TURBO_PLATFORM="${TARGETPLATFORM:-linux/amd64}" pnpm build
|
||||
|
||||
FROM base AS runner
|
||||
WORKDIR /app
|
||||
@@ -95,6 +85,5 @@ ENV AUTH_PROVIDERS='credentials'
|
||||
ENV REDIS_IS_EXTERNAL='false'
|
||||
ENV NODE_ENV='production'
|
||||
|
||||
EXPOSE 7575
|
||||
ENTRYPOINT [ "/app/entrypoint.sh" ]
|
||||
CMD ["sh", "run.sh"]
|
||||
Vendored
+2
-9
@@ -1,10 +1,3 @@
|
||||
{
|
||||
"cSpell.words": [
|
||||
"Homarr",
|
||||
"Lidarr",
|
||||
"Nextcloud",
|
||||
"Radarr",
|
||||
"Readarr",
|
||||
"Sonarr"
|
||||
]
|
||||
}
|
||||
"cSpell.words": ["Homarr", "Lidarr", "Nextcloud", "Radarr", "Readarr", "Sonarr"]
|
||||
}
|
||||
+11
-11
@@ -55,8 +55,8 @@ pnpm typecheck
|
||||
docs/
|
||||
├── getting-started/ # Installation guides, glossary, prerequisites
|
||||
├── management/ # Boards, apps, users, integrations, settings, tasks, API
|
||||
├── integrations/ # 56 integration guides (one folder each)
|
||||
├── widgets/ # 43 widget guides (one folder each)
|
||||
├── integrations/ # 55 integration guides (one folder each)
|
||||
├── widgets/ # 42 widget guides (one folder each)
|
||||
├── advanced/ # SSO, env vars, proxy, styling, CLI, development
|
||||
└── community/ # FAQ, donate, license, translations, get-in-touch
|
||||
```
|
||||
@@ -71,7 +71,7 @@ Each integration/widget doc follows a consistent pattern:
|
||||
The docs app has `@homarr/definitions` as a workspace dependency. You can import integration kinds, widget kinds, and other definitions directly:
|
||||
|
||||
```typescript
|
||||
import { IntegrationKind } from '@homarr/definitions';
|
||||
import { IntegrationKind } from "@homarr/definitions";
|
||||
```
|
||||
|
||||
This enables type-safe references to homarr domain concepts within documentation code.
|
||||
@@ -90,15 +90,15 @@ pnpm verify:search
|
||||
|
||||
## Tech Stack
|
||||
|
||||
| Layer | Technology |
|
||||
|---|---|
|
||||
| Layer | Technology |
|
||||
| -------------- | ------------------------------------------- |
|
||||
| Site generator | Docusaurus 3 (`@docusaurus/preset-classic`) |
|
||||
| Styling | Tailwind CSS 3 + Docusaurus Infima |
|
||||
| Icons | `@tabler/icons-react` |
|
||||
| Diagrams | Mermaid (`@docusaurus/theme-mermaid`) |
|
||||
| Search | Algolia DocSearch |
|
||||
| Analytics | PostHog |
|
||||
| Charts | @nivo/line |
|
||||
| Styling | Tailwind CSS 3 + Docusaurus Infima |
|
||||
| Icons | `@tabler/icons-react` |
|
||||
| Diagrams | Mermaid (`@docusaurus/theme-mermaid`) |
|
||||
| Search | Algolia DocSearch |
|
||||
| Analytics | PostHog |
|
||||
| Charts | @nivo/line |
|
||||
|
||||
## Contributing
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"collapsed": true,
|
||||
"label": "Command line interface"
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
# Developer Docker CLI
|
||||
|
||||
The host-side developer CLI builds and runs local Homarr images and pull-request previews. Run it directly from a checkout with `pnpm dev:cli -- ...`; no shell-specific setup script is required. It is separate from the recovery CLI included in production images.
|
||||
|
||||
To install an optional `homarr` binary, run `pnpm dev:cli:install` and make your Go bin directory available on `PATH`. GitHub CLI authentication is only needed for pull-request features.
|
||||
|
||||
## Browse development images
|
||||
|
||||
```bash
|
||||
pnpm dev:cli --
|
||||
```
|
||||
|
||||
The main screen combines local `homarr:*` images with open pull requests and their GHCR preview status. Select an image to start or stop it, use `R` to build a remote pull request locally in the background or rebuild a local image, or use `p` to force a remote preview pull. Press `d` to switch to the instance dashboard and `d` again to return without losing the browser state. The explicit `dev` and `dash` subcommands start on either screen.
|
||||
|
||||
A locally built PR image is folded into its remote PR row only when its recorded revision exactly matches the current GitHub head SHA. The row is marked `◆ local`, and starting it prefers that local image without a registry pull. Outdated local builds remain separate rows. Navigation and dashboard switching remain available during background builds.
|
||||
|
||||
## Build images
|
||||
|
||||
```bash
|
||||
pnpm dev:cli -- build <name>
|
||||
pnpm dev:cli -- build --pr <number> [name]
|
||||
```
|
||||
|
||||
The first command builds the current checkout as `homarr:<name>`. The pull-request form uses a depth-one clone in a temporary directory and defaults to `homarr:pr-<number>` when `name` is omitted.
|
||||
|
||||
## Rebuild images
|
||||
|
||||
```bash
|
||||
pnpm dev:cli -- rebuild <name>
|
||||
```
|
||||
|
||||
Images built by this CLI record their source checkout, revision, and optional pull-request number. Rebuild uses that provenance to select the original checkout or create a fresh temporary pull-request checkout.
|
||||
|
||||
## Run images
|
||||
|
||||
```bash
|
||||
homarr run <name>
|
||||
homarr run --pr <number>
|
||||
```
|
||||
|
||||
Add `--detach` to run in the background, `--demo` to enable demo data, or repeat `--env KEY=VALUE` to provide additional environment variables.
|
||||
@@ -14,4 +14,4 @@ homarr fix-usernames
|
||||
|
||||
This will convert all credential usernames to lowercase and update the database accordingly.
|
||||
|
||||

|
||||

|
||||
@@ -1,7 +1,7 @@
|
||||
# Command line interface
|
||||
|
||||
The Homarr image ships with a built-in recovery tool. This can be useful, if you do not have access to Homarr or if something is not working.
|
||||
Please note that the CLI is an *emergency tool*, not an API tool.
|
||||
Please note that the CLI is an _emergency tool_, not an API tool.
|
||||
|
||||
:::caution
|
||||
|
||||
@@ -12,10 +12,10 @@ You should also create a backup of the database, in case you break something.
|
||||
|
||||
## Execute CLI with Docker
|
||||
|
||||
1. Execute ``docker ps | grep homarr`` on the root system with Docker to find the container ID of your Homarr container.
|
||||
2. Run ``docker exec -it <container-id> /bin/bash homarr <your-command-goes-here>``. \
|
||||
Replace ``<container-id>`` with your container ID from step 1. \
|
||||
Replace ``<your-command-goes-here>`` with the command you want to execute (see sub-pages).
|
||||
1. Execute `docker ps | grep homarr` on the root system with Docker to find the container ID of your Homarr container.
|
||||
2. Run `docker exec -it <container-id> /bin/bash homarr <your-command-goes-here>`. \
|
||||
Replace `<container-id>` with your container ID from step 1. \
|
||||
Replace `<your-command-goes-here>` with the command you want to execute (see sub-pages).
|
||||
|
||||
## Execute CLI with QNAP
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ This guide requires that you understand [how to access the CLI](../).
|
||||
:::
|
||||
|
||||
Using these commands, you can manage the integrations from the CLI:
|
||||
|
||||
```bash
|
||||
Group of commands to manage integrations
|
||||
|
||||
@@ -43,7 +44,7 @@ Flags:
|
||||
## Delete Integration
|
||||
|
||||
- Requires a database connection
|
||||
- Requires one parameter: ``id`` or ``name``
|
||||
- Requires one parameter: `id` or `name`
|
||||
- Deletes an integration permanently from the database
|
||||
|
||||
```bash
|
||||
@@ -57,4 +58,4 @@ Flags:
|
||||
-n, --name string Name of the integration
|
||||
-h, --help help for integrations delete
|
||||
-v, --version version for homarr-cli
|
||||
```
|
||||
```
|
||||
@@ -12,8 +12,8 @@ All sessions for that user will be terminated and the user must re-login.
|
||||
|
||||

|
||||
|
||||
Using the ``--username`` parameter, you can specify who you want to reset the password for.
|
||||
Using the `--username` parameter, you can specify who you want to reset the password for.
|
||||
Executing the command will create a new random password, update the database and display the new password.
|
||||
All sessions for this user will be terminated and they must re-login.
|
||||
|
||||

|
||||

|
||||
@@ -7,6 +7,7 @@ This guide requires that you understand [how to access the CLI](../).
|
||||
:::
|
||||
|
||||
Using these commands, you can manage the users from the CLI:
|
||||
|
||||
```bash
|
||||
Group of commands to manage users
|
||||
|
||||
@@ -42,7 +43,7 @@ yj52ek5cyb6du4bp4osbkjry admin xxxx@example.com
|
||||
## Delete User
|
||||
|
||||
- Requires a database connection
|
||||
- Requires one parameter: ``id`` or ``username``
|
||||
- Requires one parameter: `id` or `username`
|
||||
|
||||
```bash
|
||||
Delete a user by id or username
|
||||
@@ -76,4 +77,4 @@ Flags:
|
||||
-p, --password New password
|
||||
-h, --help help for users update-password
|
||||
-v, --version version for homarr-cli
|
||||
```
|
||||
```
|
||||
@@ -1,9 +1,8 @@
|
||||
{
|
||||
"label": "Developer guides",
|
||||
"position": 6,
|
||||
"collapsed": true,
|
||||
"link": {
|
||||
"type": "generated-index"
|
||||
}
|
||||
"label": "Developer guides",
|
||||
"position": 6,
|
||||
"collapsed": true,
|
||||
"link": {
|
||||
"type": "generated-index"
|
||||
}
|
||||
|
||||
}
|
||||
@@ -14,12 +14,11 @@ This guide will help you set up your development environment to start contributi
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- **Node.js 24.18 or newer**: You can download it from the [official website](https://nodejs.org/).
|
||||
- **Corepack**: Run `corepack enable` so the repository selects its pinned pnpm version.
|
||||
- **Node.js (lts)**: You can download it from the [official website](https://nodejs.org/).
|
||||
- **pnpm (latest)**: You can install it by running `npm install -g pnpm`.
|
||||
- **Git**: You can download it from the [official website](https://git-scm.com/).
|
||||
- **Docker or Docker Desktop**: Required for the Redis service and the developer Docker CLI.
|
||||
- **Go 1.25+**: Required only for the developer Docker CLI. You can download it from the [official website](https://go.dev/).
|
||||
- **GitHub CLI**: Required only for pull-request image features. Install it from the [official website](https://cli.github.com/), then authenticate with `gh auth login`.
|
||||
- **Docker or Docker Desktop**: You can download it from the [official website](https://www.docker.com/).
|
||||
- **A GitHub account**: You can create one on the [official website](https://github.com/).
|
||||
|
||||
### Setting up the project
|
||||
|
||||
@@ -35,11 +34,11 @@ This guide will help you set up your development environment to start contributi
|
||||
|
||||
4. Install the dependencies with `pnpm install`.
|
||||
|
||||
5. Create `.env` from `.env.example`. In a POSIX shell, run `cp .env.example .env`; in PowerShell, run `Copy-Item .env.example .env`. Set `DB_URL` to an absolute path for your SQLite file.
|
||||
5. Start redis by running `pnpm run docker:dev`. This will run a docker-compose file with a redis service.
|
||||
|
||||
6. Start Redis in the background with `pnpm docker:dev:up` if your development workflow needs it.
|
||||
6. Copy the `.env.example` file to `.env` and replace `DB_URL` with the full path you want to use for the sqlite database file. For example `DB_URL='C:\\Dev\\homarr\\packages\\db\\db.sqlite'`
|
||||
|
||||
7. Create or update the database with `pnpm db:migration:sqlite:run`. This also seeds the default data. To run the seed explicitly later, use `pnpm db:seed`.
|
||||
7. Run the migrations to create the sqlite db file with `pnpm run db:migration:sqlite:run`.
|
||||
|
||||
8. Start the development server with `pnpm dev` and wait for a message that says `✓ Ready in 10.7s`
|
||||
|
||||
@@ -49,19 +48,9 @@ This guide will help you set up your development environment to start contributi
|
||||
|
||||
#### During development
|
||||
|
||||
- `pnpm dev`: Start the Next.js development server.
|
||||
- `pnpm dev:benchmark`: Run the local authenticated-board benchmark. Set `DEV_BENCHMARK_BOARD_URL` and
|
||||
`DEV_BENCHMARK_STORAGE_STATE`; readiness-only measurements require the explicit `--smoke` flag and are not
|
||||
suitable for performance claims.
|
||||
- `pnpm dev:cli -- dev`: Browse and run local images and remote pull-request images.
|
||||
- `pnpm dev:cli -- build <name>`: Build the current checkout as `homarr:<name>`.
|
||||
- `pnpm dev:cli -- build --pr <number>`: Build a pull request locally from a temporary checkout.
|
||||
- `pnpm dev:cli -- rebuild <name>`: Rebuild a local image from its recorded checkout or pull request.
|
||||
- `pnpm dev:cli:install`: Optionally install the developer CLI as a `homarr` binary.
|
||||
- `pnpm dev`: Start the development server.
|
||||
- `pnpm cli`: Run cli commands.
|
||||
- `pnpm db:migration:sqlite:run`: Run the migrations to create or update the sqlite db file.
|
||||
- `pnpm db:seed`: Explicitly seed the default database data.
|
||||
- `pnpm docker:dev:up`: Start the Redis development service in the background.
|
||||
- `pnpm db:migration:sqlite:generate`: Generate a new migration file for the sqlite db. Also works with `mysql`.
|
||||
- `pnpm db:studio`: Open a visual studio for the database.
|
||||
- `pnpm package:new`: Create a new package in the monorepo.
|
||||
@@ -76,6 +65,5 @@ This guide will help you set up your development environment to start contributi
|
||||
#### Building the project
|
||||
|
||||
- `pnpm build`: Build the project for production.
|
||||
- `pnpm dev:cli -- build <name>`: Build a `homarr:<name>` image and record its checkout for later rebuilds.
|
||||
- `docker build -t homarr .`: Build a docker image for the project.
|
||||
- `docker run -p 7575:7575 -e SECRET_ENCRYPTION_KEY='your_64_character_hex_string' homarr:<name>`: Run the created docker image.
|
||||
- `docker run -p 7575:7575 -e SECRET_ENCRYPTION_KEY='your_64_character_hex_string' homarr:latest`: Run the created docker image.
|
||||
@@ -132,4 +132,4 @@ This command should return CPU and memory usage statistics for each node.
|
||||
|
||||
## Conclusion
|
||||
|
||||
You have successfully set up a multi-node Kubernetes cluster using kind and installed the Metrics Server for monitoring resource usage. This setup is useful for local development and testing Kubernetes workloads before deploying them to a production environment.
|
||||
You have successfully set up a multi-node Kubernetes cluster using kind and installed the Metrics Server for monitoring resource usage. This setup is useful for local development and testing Kubernetes workloads before deploying them to a production environment.
|
||||
@@ -25,14 +25,13 @@ This is especially useful for [docker secrets](https://docs.docker.com/engine/sw
|
||||
Using the `PUID` and `PGID` will require you to set the correct permissions on the mounted volumes and if used the docker socket. See more in the [Running as a different user](/docs/advanced/running-as-different-user) documentation.
|
||||
:::
|
||||
|
||||
| Environment Variable | Description | Possible values | Default |
|
||||
| ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------- | ------- |
|
||||
| `PUID` | User ID to run the container as | Any valid user ID | `0` |
|
||||
| `PGID` | Group ID to run the container as | Any valid group ID | `0` |
|
||||
| `LOG_LEVEL` | Log level to use | `debug` / `info` / `warn` / `error` | `info` |
|
||||
| `NO_EXTERNAL_CONNECTION` | Disables Homarr's internet-backed requests, including update checks, weather, and RSS feeds | `true` or `false` | `false` |
|
||||
| `ENABLE_DNS_CACHING` | Enables dns caching. Enabled by default, but if you experience issues with IPv6 or static IPs, see [#4006](https://github.com/homarr-labs/homarr/issues/4006) or disable this flag | `true` or `false` | `true` |
|
||||
| `HOMARR_DISABLE_IPV6` | Disables the IPv6 listener of the internal nginx proxy, even when the host supports IPv6. IPv6 is normally disabled automatically when the host has no IPv6 configured. See [#4596](https://github.com/homarr-labs/homarr/issues/4596) | `true` or `false` | `false` |
|
||||
| Environment Variable | Description | Possible values | Default |
|
||||
| ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------- | ------- |
|
||||
| `PUID` | User ID to run the container as | Any valid user ID | `0` |
|
||||
| `PGID` | Group ID to run the container as | Any valid group ID | `0` |
|
||||
| `LOG_LEVEL` | Log level to use | `debug` / `info` / `warn` / `error` | `info` |
|
||||
| `NO_EXTERNAL_CONNECTION` | Disables some requests that need internet connection | `true` or `false` | `false` |
|
||||
| `ENABLE_DNS_CACHING` | Enables dns caching. Enabled by default, but if you experience issues with IPv6 or static IPs, see [#4006](https://github.com/homarr-labs/homarr/issues/4006) or disable this flag | `true` or `false` | `true` |
|
||||
|
||||
## Authentication
|
||||
|
||||
@@ -52,11 +51,11 @@ A random secret can be generated by using the following command: `openssl rand -
|
||||
|
||||
## Docker
|
||||
|
||||
| Environment Variable | Description | Possible values | Default |
|
||||
| --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------- | ------- |
|
||||
| `DOCKER_HOSTNAMES` | Comma-separated list of hostnames to connect to | For example `localhost,docker.example.com` | - |
|
||||
| `DOCKER_PORTS` | Comma-separated list of ports to connect to | For example `2375,2376` | - |
|
||||
| `DOCKER_SOCKET_PATHS` | Comma-separated list of mounted Docker-compatible socket paths. See the [Docker and Podman integration guide](/docs/integrations/docker) | For example `/var/run/docker.sock,/var/run/podman.sock` | - |
|
||||
| Environment Variable | Description | Possible values | Default |
|
||||
| --------------------- | ----------------------------------------------- | -------------------------------------------------------------------- | ------- |
|
||||
| `DOCKER_HOSTNAMES` | Comma seperated list of hostnames to connect to | For example `localhost,docker.example.com` | - |
|
||||
| `DOCKER_PORTS` | Comma seperated list of ports to connect to | For example `2375,2376` | - |
|
||||
| `DOCKER_SOCKET_PATHS` | Comma seperated list of paths to sockets | For example `/var/run/docker.sock,/run/user/1000/podman/podman.sock` | - |
|
||||
|
||||
## Database
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ tags:
|
||||
|
||||
Icons in Homarr are automatically requested from multiple sources:
|
||||
|
||||
- [Dashboard Icons](https://dashboardicons.com/) - Our recommended source with more than 1,888 curated, high-quality icons
|
||||
- [Dashboard Icons](https://dashboardicons.com/) - Our recommended source with over 1888+ curated, high-quality icons
|
||||
- [selfh.st icons](https://github.com/selfhst/icons)
|
||||
- [Simple icons](https://github.com/simple-icons/simple-icons)
|
||||
- [Tabler icons](https://tabler-icons.io/)
|
||||
@@ -21,7 +21,7 @@ Icons in Homarr are automatically requested from multiple sources:
|
||||
- **Local icons**: automatically fetched from the [medias feature](/docs/management/media/)
|
||||
|
||||
Using these icon sources, Homarr provides a total collection of over 11,000 icons at your fingertips.
|
||||
The image picker searches this collection after a short typing delay. When a name is available, such as an app or search engine name, the picker uses it as the initial search. Results show your locally uploaded images first, followed by SVG icons and then other image formats.
|
||||
The icon picker is a fast searcher that helps you to find your desired icon simply by entering a search term.
|
||||
|
||||
### Finding Missing Icons
|
||||
|
||||
@@ -31,13 +31,7 @@ If you can't find the icon you're looking for in Homarr's built-in picker, we st
|
||||
|
||||
Icons that are high quality and printable ([SVG](https://wikipedia.org/wiki/Scalable_Vector_Graphics)) are marked with a red badge "SVG" at the top right.
|
||||
|
||||
You can clear the selected image with the clear button or the usual keyboard shortcut for deleting a line (Command+Backspace on macOS or Control+Backspace on other platforms). Clearing the field does not restore the previous image.
|
||||
|
||||
### Using external icons
|
||||
|
||||
If you don't find the icon you're looking for on Dashboard Icons or wish to use a different source, you can simply enter a URL that points to an icon.
|
||||
Direct HTTP(S) image URLs are used as entered instead of being searched in the built-in repositories. The picker previews the image and shows whether it loaded successfully. It is important that the URL directly points to the file without any advertisements or elements around it.
|
||||
|
||||
### Uploading a local image
|
||||
|
||||
Users with media upload permission can select **Upload image** next to the field. After the upload finishes, the new local image is selected immediately and is available at the top of future searches.
|
||||
It is important that the URL directly points to the file without any advertisements or elements around it.
|
||||
@@ -6,11 +6,11 @@ tags:
|
||||
|
||||
Homarr has the following keyboard shortcuts:
|
||||
|
||||
| Shortcut | Description |
|
||||
| -------- | ----------- |
|
||||
| Shortcut | Description |
|
||||
| ----------- | -------------------- |
|
||||
| `MOD` + `K` | Opens the search bar |
|
||||
| `MOD` + `J` | Toggles Dark Mode |
|
||||
| `MOD` + `E` | Toggles Edit Mode |
|
||||
| `MOD` + `J` | Toggles Dark Mode |
|
||||
| `MOD` + `E` | Toggles Edit Mode |
|
||||
|
||||
:::tip
|
||||
|
||||
|
||||
@@ -9,16 +9,17 @@ tags:
|
||||
- Security
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
import Tabs from "@theme/Tabs";
|
||||
import TabItem from "@theme/TabItem";
|
||||
|
||||
## Allowing self-signed certificates
|
||||
|
||||
Some users may come across a barrier, where they're unable to receive a 200 response from the Ping widget for some apps, while using self-signed certificates or a local certificate authory.
|
||||
|
||||
:::note What's going on?
|
||||
|
||||
Homarr is trying to communicate to your apps via the integrations.
|
||||
It usually doesn't matter if Homarr is running on ``http`` or ``https``.
|
||||
It usually doesn't matter if Homarr is running on `http` or `https`.
|
||||
Your apps have a self-signed certificate - Homarr will recognize that the certificate was signed by an unknown authority and requests will be blocked.
|
||||
|
||||
:::
|
||||
@@ -28,8 +29,9 @@ To allow self-signed certificates, you can configure them on the certificates ma
|
||||
## Securing Homarr with Traefik
|
||||
|
||||
Copying the configuration straight from the docker-compose file won't work if you are running Homarr behind Traefik, such as a Portainer setup, or docker-swarm. In that case, you should use the following slightly modified configuration:
|
||||
|
||||
```yaml
|
||||
version: '3'
|
||||
version: "3"
|
||||
services:
|
||||
homarr:
|
||||
container_name: homarr
|
||||
@@ -47,17 +49,19 @@ services:
|
||||
traefik.http.routers.homarr.rule: Host(`your.internal.dns.address.here.com`)
|
||||
traefik.http.routers.homarr.entrypoints: websecure
|
||||
traefik.http.routers.homarr-secure.app: homarr
|
||||
traefik.http.apps.homarr.loadbalancer.server.port: 7575
|
||||
|
||||
networks:
|
||||
proxy:
|
||||
external: true
|
||||
```
|
||||
|
||||
<br/>
|
||||
<br />
|
||||
|
||||
A sample Traefik docker-compose.yml using Cloudflare for certificate generation that works with the configuration above would be:
|
||||
|
||||
```yaml
|
||||
version: '3'
|
||||
version: "3"
|
||||
|
||||
apps:
|
||||
traefik:
|
||||
@@ -103,7 +107,6 @@ networks:
|
||||
external: true
|
||||
```
|
||||
|
||||
|
||||
:::tip
|
||||
Of particular note here is that both configurations explicitly define which network they are using, in this case "proxy", but it can be named anything. It just has to be the same across all apps for which Traefik is serving as a proxy. These are marked as external because the proxy network was manually created by running: `docker network create proxy` but this might be unnecessary depending on HOW exactly you are running Traefik. For example, if running [Traefik with Portainer](https://docs.portainer.io/advanced/reverse-proxy/traefik#deploying-in-a-docker-standalone-scenario), you can follow their official docs on how to set up Traefik and Portainer together, and you can just focus on the Homarr docker labels instead.
|
||||
:::
|
||||
@@ -114,7 +117,6 @@ If you are using Caddy as the reverse proxy for your setup, your `docker-compose
|
||||
|
||||
```yaml
|
||||
services:
|
||||
|
||||
# <-- [Homarr installation]
|
||||
|
||||
caddy:
|
||||
@@ -140,14 +142,17 @@ networks:
|
||||
proxy:
|
||||
external: true
|
||||
```
|
||||
|
||||
:::important
|
||||
Homarr needs to be on the same network so you will need to add the following property to your homarr install:
|
||||
|
||||
```yaml
|
||||
homarr:
|
||||
...
|
||||
networks:
|
||||
- proxy
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
Next, create a file named `Caddyfile` at the same level as your `docker-compose.yml` file with the following content:
|
||||
|
||||
@@ -8,7 +8,7 @@ tags:
|
||||
- PUID
|
||||
---
|
||||
|
||||
By default the container is running with user `root` and group `root`.
|
||||
By default the container is running with user `root` and group `root`.
|
||||
You can change the user and group by using the `PUID` and `PGID` environment variables.
|
||||
Important to note is, that you'll also need to change the permissions for the mounted directories accordingly.
|
||||
|
||||
@@ -32,11 +32,11 @@ services:
|
||||
volumes:
|
||||
- ./homarr/appdata:/appdata
|
||||
environment:
|
||||
- SECRET_ENCRYPTION_KEY=your_64_character_hex_string # <--- can be generated with `openssl rand -hex 32`
|
||||
- PUID=1000
|
||||
- PGID=1000
|
||||
- SECRET_ENCRYPTION_KEY=your_64_character_hex_string # <--- can be generated with `openssl rand -hex 32`
|
||||
- PUID=1000
|
||||
- PGID=1000
|
||||
ports:
|
||||
- '7575:7575'
|
||||
- "7575:7575"
|
||||
```
|
||||
|
||||
After that you'll need to create the appdata directory and set the correct permissions:
|
||||
@@ -76,11 +76,11 @@ services:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
- ./homarr/appdata:/appdata
|
||||
environment:
|
||||
- SECRET_ENCRYPTION_KEY=your_64_character_hex_string # <--- can be generated with `openssl rand -hex 32`
|
||||
- PUID=1000
|
||||
- PGID=999
|
||||
- SECRET_ENCRYPTION_KEY=your_64_character_hex_string # <--- can be generated with `openssl rand -hex 32`
|
||||
- PUID=1000
|
||||
- PGID=999
|
||||
ports:
|
||||
- '7575:7575'
|
||||
- "7575:7575"
|
||||
```
|
||||
|
||||
And set the permissions for the mounted directories:
|
||||
@@ -90,4 +90,4 @@ mkdir -p ./homarr/appdata
|
||||
chown -R 1000:999 ./homarr/appdata
|
||||
```
|
||||
|
||||
Important here is that the group ID is set to `999` as we created the `docker` group with this ID.
|
||||
Important here is that the group ID is set to `999` as we created the `docker` group with this ID.
|
||||
@@ -10,20 +10,21 @@ tags:
|
||||
- Active Directory
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
import Tabs from "@theme/Tabs";
|
||||
import TabItem from "@theme/TabItem";
|
||||
|
||||
# Single Sign On
|
||||
|
||||
Homarr supports multiple authentication options, from internal userbase (credentials), to LDAP (with Active directory support), and OIDC.
|
||||
|
||||
### Auth configuration common variables
|
||||
| Environment Variable | Description | Default Value |
|
||||
|---------------------------|-------------|---------------|
|
||||
| AUTH_PROVIDERS | Select Which provider to use between *credentials*, *ldap* and *oidc*. <br /> Multiple providers can be enabled with by separating them with `,`, (ex. `AUTH_PROVIDERS=credentials,oidc`, it is highly recommended to just enable one provider). | credentials |
|
||||
| AUTH_LOGOUT_REDIRECT_URL | URL to redirect to after clicking logging out. | --- |
|
||||
| AUTH_SESSION_EXPIRY_TIME | Time for the session to time out. Can be set as pure number, which will automatically be used in seconds, or followed by s, m, h or d for seconds, minutes, hours or days. (ex: "30m") | "30d" |
|
||||
| AUTH_COOKIE_PREFIX | Prefix used for all authentication cookies (session, csrf, callback, pkce, state, nonce). Change this if you run another Auth.js/NextAuth app on the same hostname as Homarr (browsers scope cookies by host, not by port) to avoid cookie name collisions. Only letters, numbers, hyphens and underscores are allowed. | `homarr` |
|
||||
|
||||
| Environment Variable | Description | Default Value |
|
||||
| ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- |
|
||||
| AUTH_PROVIDERS | Select Which provider to use between _credentials_, _ldap_ and _oidc_. <br /> Multiple providers can be enabled with by separating them with `,`, (ex. `AUTH_PROVIDERS=credentials,oidc`, it is highly recommended to just enable one provider). | credentials |
|
||||
| AUTH_LOGOUT_REDIRECT_URL | URL to redirect to after clicking logging out. | --- |
|
||||
| AUTH_SESSION_EXPIRY_TIME | Time for the session to time out. Can be set as pure number, which will automatically be used in seconds, or followed by s, m, h or d for seconds, minutes, hours or days. (ex: "30m") | "30d" |
|
||||
| AUTH_COOKIE_PREFIX | Prefix used for all authentication cookies (session, csrf, callback, pkce, state, nonce). Change this if you run another Auth.js/NextAuth app on the same hostname as Homarr (browsers scope cookies by host, not by port) to avoid cookie name collisions. Only letters, numbers, hyphens and underscores are allowed. | `homarr` |
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="credentials" label="Credentials Provider" default>
|
||||
@@ -127,8 +128,6 @@ Homarr supports multiple authentication options, from internal userbase (credent
|
||||
|
||||
If you'd rather manage group memberships for OIDC users locally (instead of via the IdP), set `AUTH_OIDC_GROUPS_LOCAL_MANAGEMENT=true`. While enabled, Homarr stops syncing OIDC users' group memberships from the groups claim, and admins can add/remove OIDC users on a group's members page. If you turn this back to `false`, the next login of each user resumes the regular sync and reconciles their groups with the IdP's claim again, which may remove memberships that were added manually while local management was enabled.
|
||||
|
||||
To let an existing credentials user also sign in with OIDC, set `AUTH_OIDC_ENABLE_DANGEROUS_CREDENTIALS_LINKING=true`. On the first OIDC sign-in, Homarr links a credentials user with the same email address instead of creating a second user. The user keeps their password login, local username and profile, groups, permissions, boards, settings, and API keys. OIDC profile and group claims do not overwrite the credentials-owned data. Homarr requires the OIDC profile to contain `email_verified: true` while this option is enabled. Only enable this when your OIDC provider verifies email addresses, because a matching email could otherwise be used to take over an existing account.
|
||||
|
||||
<details>
|
||||
<summary>
|
||||
Example Setup
|
||||
@@ -206,7 +205,6 @@ Homarr supports multiple authentication options, from internal userbase (credent
|
||||
| ``AUTH_OIDC_NAME_ATTRIBUTE_OVERWRITE`` | Overwrite name attribute. By default it will use preferred_username if it does not contain a `@` and otherwise name. | --- |
|
||||
| ``AUTH_OIDC_FORCE_USERINFO`` | Force userinfo endpoint to be used for user information. | false |
|
||||
| ``AUTH_OIDC_ENABLE_DANGEROUS_ACCOUNT_LINKING`` | Enable account linking for OIDC provider. This will link the OIDC accounts by email. Be sure that you have verified emails to prevent stealing accounts. | false |
|
||||
| ``AUTH_OIDC_ENABLE_DANGEROUS_CREDENTIALS_LINKING`` | Link an OIDC identity to an existing credentials user with the same email. The credentials login and existing user data are preserved. Requires the OIDC profile to contain `email_verified: true`. | false |
|
||||
| ``AUTH_OIDC_TOKEN_ENDPOINT_AUTH_METHOD`` | Override method for the token endpoint authentication. Supported values are `client_secret_basic`, `client_secret_post`, `client_secret_jwt` and `none`. | client_secret_basic |
|
||||
|
||||
### Permission System
|
||||
|
||||
@@ -19,12 +19,14 @@ Homarr uses https://mantine.dev/ under the hood. You can change the look & feel
|
||||
using custom CSS, that extends or overwrites the styles from Mantine.
|
||||
|
||||
## Getting into CSS
|
||||
|
||||
If you don't have any experience with CSS,
|
||||
we strongly recommend you to go through the courses here: https://www.w3schools.com/css/.
|
||||
Due to the infinite scenarios and setups possible with custom CSS,
|
||||
our support team will not provide any support with your own written CSS.
|
||||
|
||||
## Limitations and dangers
|
||||
|
||||
When using custom CSS, you can break the page or certain functionalities.
|
||||
Although CSS can improve the design & feel of the page, it **<ins>cannot do any of the following</ins>**:
|
||||
|
||||
@@ -33,21 +35,23 @@ Although CSS can improve the design & feel of the page, it **<ins>cannot do any
|
||||
- Completely revamp the design of the page, only smaller changes are possible
|
||||
|
||||
Here are some dangers of using custom CSS:
|
||||
|
||||
- Updates may contain breaking changes in the structure, built-in styling or how styling is processed. Therefore,
|
||||
custom CSS may break unexpectedly without any warning
|
||||
custom CSS may break unexpectedly without any warning
|
||||
- Custom CSS may make certain elements invisible or not possible to click / interact with
|
||||
- Custom CSS could make readability and accessibility worse for impaired people
|
||||
|
||||
## How to inspect the page
|
||||
When styling the page, you can use the ``Inspector`` to inspect elements on the page and try out styles.
|
||||
|
||||
When styling the page, you can use the `Inspector` to inspect elements on the page and try out styles.
|
||||
This may vary depending on your browser and operating system, but the workflow usually goes like this:
|
||||
|
||||
- Open the Homarr page
|
||||
- Press ``Ctrl`` + ``Shift`` + ``I`` at the same time.
|
||||
Alternatively, right-click on the page and choose ``Inspect`` or ``Inspect element``.
|
||||
- Open the tab ``Elements`` if it isn't open yet.
|
||||
- Press `Ctrl` + `Shift` + `I` at the same time.
|
||||
Alternatively, right-click on the page and choose `Inspect` or `Inspect element`.
|
||||
- Open the tab `Elements` if it isn't open yet.
|
||||
- Search the element you want to change in the DOM tree or use the element picker (usually at the top left)
|
||||
to select it by clicking on the page.
|
||||
to select it by clicking on the page.
|
||||
- Experiment by changing the CSS code on the right side
|
||||
|
||||
When writing custom CSS, it is important to also remember or create a selector, which tells the browser
|
||||
@@ -55,17 +59,19 @@ which element to apply the styles to. If you accidentally broke something or wan
|
||||
simply reload the page.
|
||||
|
||||
## Using Homarr's classes
|
||||
|
||||
Homarr's elements will already have a lot of classes and styles pre-applied.
|
||||
You can reuse those classes to apply additional styles or overwrite them.
|
||||
When overwriting, remember to add the ``!important`` flag to your CSS property to tell the browser to use our instruction instead.
|
||||
When overwriting, remember to add the `!important` flag to your CSS property to tell the browser to use our instruction instead.
|
||||
|
||||
Classes with random number suffixes, such as ``mantine-Card-x2fske``, should be avoided, since those are dynamic
|
||||
Classes with random number suffixes, such as `mantine-Card-x2fske`, should be avoided, since those are dynamic
|
||||
CSS classes that will change with every build. This means, every time you update Homarr,
|
||||
your styles will no longer apply to them
|
||||
|
||||
## Defining custom classes for widgets
|
||||
|
||||
If you want to apply the styles only for one widget, you can define custom class names only for that widget.
|
||||
To do that, enter edit mode on your board, click the three dots of your widget, click ``Edit item`` and click ``Advanced options``.
|
||||
To do that, enter edit mode on your board, click the three dots of your widget, click `Edit item` and click `Advanced options`.
|
||||
Then, you can enter as many custom classes as you'd like:
|
||||
|
||||

|
||||
@@ -73,6 +79,7 @@ Then, you can enter as many custom classes as you'd like:
|
||||
To save them, click `Save changes` on both modals and exit edit mode.
|
||||
|
||||
## Where to enter custom CSS
|
||||
|
||||
After you wrote your custom CSS, it is time to permanently save it, so you don't need to use the inspector anymore.
|
||||
To do this, enter the board settings:
|
||||
|
||||
@@ -84,11 +91,12 @@ Here you can enter your CSS code, which will be applied on the respective board
|
||||

|
||||
|
||||
## Using variables
|
||||
|
||||
You can also change the values of the theme variables, which are located here: https://mantine.dev/styles/css-variables-list/.
|
||||
To do this, see the following example CSS:
|
||||
|
||||
```css
|
||||
:root {
|
||||
--mantine-primary-color-filled: #ffbb00;
|
||||
--mantine-primary-color-filled: #ffbb00;
|
||||
}
|
||||
```
|
||||
```
|
||||
@@ -7,12 +7,15 @@ tags:
|
||||
---
|
||||
|
||||
# Donate
|
||||
|
||||
## Why to donate
|
||||
|
||||
Our team of 4 developers is working on Homarr in their free time.
|
||||
Homarr has become a big part of our daily things we work on - and therefore we invest a lot of time into maintaining Homarr,
|
||||
providing support and implementing new features.
|
||||
We also incur monetary costs while developing Homarr for the tools that we need.
|
||||
A few examples include:
|
||||
|
||||
- Renting for a Cloud VPS for our cloud services (status page, monitoring, email, analytics, [demo instance](https://demo.homarr.dev/), ...)
|
||||
- Paying for (IDE-) licenses
|
||||
|
||||
@@ -20,6 +23,7 @@ Using a donation, you help us to cover these expenses and enable the development
|
||||
Homarr is completely free to use. We'd highly appreciate if you consider donating, especially if you use Homarr in a commercial environment.
|
||||
|
||||
## Where to donate
|
||||
|
||||
We accept donations on our official [OpenCollective](https://opencollective.com/homarr).
|
||||
You may also sponsor us directly by providing a useful service / product to us other means.
|
||||
You can get into contact with us [here](/docs/community/get-in-touch).
|
||||
You can get into contact with us [here](/docs/community/get-in-touch).
|
||||
@@ -5,43 +5,47 @@ tags:
|
||||
- Help
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
import Tabs from "@theme/Tabs";
|
||||
import TabItem from "@theme/TabItem";
|
||||
|
||||
### Can I install Homarr on a Raspberry Pi?
|
||||
|
||||
Yes, Homarr is compatible with the architecture of the Raspberry Pi. Have a look in our <a href="/docs/category/installation-1">installation guide</a> on how to do so.
|
||||
|
||||
### I have a suggestion / idea for improvement or want to report a bug
|
||||
|
||||
You can report issues, suggestions, or ideas for improvements <a href="https://github.com/homarr-labs/homarr/issues/new/choose">here</a>.
|
||||
|
||||
### Can I use my own custom icons for apps?
|
||||
|
||||
Yes, check out our guide on <a href="/docs/advanced/customizations/icons">how to add your custom icons</a>.
|
||||
|
||||
### Some of my online icons are not working (anymore)
|
||||
|
||||
It seems the `cdn.jsdelivr.net` database of icon sometimes appear in the lists for DNS-Holes (PiHole, AdGuard Home...). <br />
|
||||
So make sure to manually whitelist this address.
|
||||
|
||||
### Can I add my own widgets? Do I need programming skills?
|
||||
We have decided, not to support custom widgets, until Homarr is in a stable and highly polished state.
|
||||
Enabling users to write their own widgets is easy at the first glance, but there are some major issues with it:
|
||||
|
||||
- There are mainly two options for adding plugins or custom widgets to Homarr. The first option is, to allow and inject custom code in Homarr. Apps like Nextcloud can do this easily, because they use the Runtime to render all components of the application (In reality, the process is still a bit more complicated). In contrast, Homarr is using Next.js - a framework that needs to be compiled in production. That means, that each time you would modify a custom widget or plugin, the app would have to be restarted. There are options to avoid this, but they are quite flakey and bring disadvantages in our experience. The second option is, to write widgets declaratively instead of imperatively. This way, we would be able to render and modify widgets without any restarting at all. However, we'd have to write our own interpreter for said declarative schema. This is totally possible, but requires a lot of planning and architecture design to function properly. The required work to implement either option is therefore quite substantial.
|
||||
- Custom widgets would need to be able to request data, so they work properly. We use tRPC for this. Many APIs use different formats and we'd have to convert and read those.
|
||||
- Using custom widgets, one could easily create harmful widgets, leak your personal data (potentially including your tokens) and trick your users. A proper implementation (eg. official plugin store, where we can ban harmful widgets from) would resolve this problem to a certain extent, but again requires additional work and time to be put in.
|
||||
Yes. Administrators can create [Custom Widgets](/docs/management/custom-widgets) that read an HTTP API and render the response with a built-in display type. These display types require no programming.
|
||||
|
||||
If you still want to add a widget, please consider to contribute it directly to Homarr. For that, basic programming skills are required. Our [Developer Guides](/docs/advanced/development/getting-started) explain this even more detailed.
|
||||
Custom JSX is available when you need a more specific layout or an interactive control. It uses a restricted declarative JSX interpreter with a classified Mantine component set. Network operations are declared as named, same-origin requests and are checked against board permissions on the server. It does not execute arbitrary JavaScript or expose browser `fetch`.
|
||||
|
||||
You should be comfortable reading JSON and JSX to author complex templates. The management workbench includes examples, diagnostics, a component reference, API response inspection, and a prompt you can copy into an AI assistant. Always review imported templates and request capabilities before saving them.
|
||||
|
||||
### Is it secure to expose Homarr to the web?
|
||||
|
||||
Yes, since v0.14.0, we are confident that Homarr is secure enough to be exposed to the web.
|
||||
Yes, since v0.14.0, we are confident that Homarr is secure enough to be exposed to the web.
|
||||
Make sure to enable the "allow anonymous access" [option in the settings](/docs/management/boards#access-control), if you want to allow people without an account to be able to see your dashboard (not edit)
|
||||
|
||||
### How do I open the console / log?
|
||||
|
||||
There are two different sources of information:
|
||||
|
||||
- console: This is your client (browser). It can tell us, if there is an issue on the client (crash, bug, ...).
|
||||
- log: This is your server. It will tell us, if there are issues with connections for the integrations, crashes and errors.
|
||||
|
||||
Both variants *can* contain personal data. It's recommended, that you don't post them publicly or remove personal data from it.
|
||||
Both variants _can_ contain personal data. It's recommended, that you don't post them publicly or remove personal data from it.
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="apple" label="Read console" default>
|
||||
@@ -49,39 +53,45 @@ Both variants *can* contain personal data. It's recommended, that you don't post
|
||||
> Note: This will not work on most mobile browsers.
|
||||
|
||||
Open your browser and ensure, that Homarr is your current tab.
|
||||
Press ``CTRL`` + ``SHIFT`` + ``I`` on your keyboard. If this shortcut doesn't work, please use one of the alternatives: https://developer.chrome.com/docs/devtools/open/.
|
||||
Click on ``Console`` in the new window, that opened. There, you should see white, orange and sometimes red messages. You can use the Windows Snip & Sketch tool (or your respective tool on Linux / MacOS) to screenshot those messages.
|
||||
Press `CTRL` + `SHIFT` + `I` on your keyboard. If this shortcut doesn't work, please use one of the alternatives: https://developer.chrome.com/docs/devtools/open/.
|
||||
Click on `Console` in the new window, that opened. There, you should see white, orange and sometimes red messages. You can use the Windows Snip & Sketch tool (or your respective tool on Linux / MacOS) to screenshot those messages.
|
||||
|
||||
> Please never execute any commands in the console. Scammers are using the console to let you execute harmful commands. If you want to resolve an issue in Homarr, **you'll never have to execute any commands here**
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="orange" label="Read log">
|
||||
|
||||
This depends on your installation method. If you use Unraid, you can simply click on the App and on ``Log``. If you have a bare Docker install, use ``docker container logs homarr``. If you used a different name than ``homarr`` for your container, you need to replace said argument. Here is some additional information about the command: https://geekflare.com/check-docker-logs/.
|
||||
This depends on your installation method. If you use Unraid, you can simply click on the App and on `Log`. If you have a bare Docker install, use `docker container logs homarr`. If you used a different name than `homarr` for your container, you need to replace said argument. Here is some additional information about the command: https://geekflare.com/check-docker-logs/.
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
### How do I post logs?
|
||||
|
||||
Please check [the section above](#how-do-i-open-the-console--log), on how to obtain logs. Logs should always be posted to https://pastebin.com/ or https://rentry.co/.
|
||||
|
||||
### Can I use Homarr at my company?
|
||||
|
||||
Yes, you can! But please consider a donation, if our work has been useful to you.
|
||||
|
||||
### I think I found a bug. What can I do?
|
||||
|
||||
If you think, that you found a bug, please create an issue <a href="https://github.com/homarr-labs/homarr/issues/new/choose">here</a>.
|
||||
|
||||
### Why can't I use my custom icons?
|
||||
|
||||
You need to restart the Homarr Docker container after adding icons.
|
||||
|
||||
### Why can't homarr see/ping containers running locally behind GluetunVPN?
|
||||
|
||||
This is because GluetunVPN, and many other docker VPN services, secure their VPN connections with strict firewalls. These firewalls often block access to local containers, even on the same docker network. The workaround for <a href="https://github.com/qdm12/gluetun-wiki/blob/main/setup/options/firewall.md">GluetunVPN</a> is to add "- FIREWALL_OUTBOUND_SUBNETS=192.168.1.0/24" into the environmental variables of the docker compose file. If you are using a firewall on your system, you will need to add a rule that allows requests from ip's in the range of 192.168.xxx.xxx . If you are using UFW the command would be "sudo ufw allow from 192.168.0.0/16".
|
||||
Tip: If you are running your torrent client behind a VPN, and running Sonarr (or something similar) normally, make sure to open your torrent client's port in your firewall. If you don't, Sonarr will not be able to see it. Furthermore, use your machines IP (192.168.x.x) rather then 127.0.0.1 or localhost.
|
||||
|
||||
### Can I get in touch with you?
|
||||
|
||||
Yes, go <a href="/docs/community/get-in-touch">here</a> to get with us in touch.
|
||||
|
||||
<hr/>
|
||||
<hr />
|
||||
|
||||
:::tip
|
||||
|
||||
|
||||
@@ -9,16 +9,16 @@ tags:
|
||||
We're happy to talk with you! Shoot us a message at the following places:
|
||||
|
||||
## E-Mail
|
||||
|
||||
We are open to discuss sponsorships, security vulnerabilities and other private requests through our email.
|
||||
Please do not report any bugs / feature requests here - they will be ignored and deleted.
|
||||
|
||||
[homarr-labs@proton.me](mailto:homarr-labs@proton.me)
|
||||
|
||||
## Discord
|
||||
|
||||
We have a Discord Server, where we announce new releases, discuss features and bugs and show off our dashboards.
|
||||
There are over 4'000 members and we're happy to answer your questions or concerns.
|
||||
You're also open to discuss new / upcoming features and any offtopic selfhosting content.
|
||||
|
||||
[__](https://discord.gg/aCsmEV5RgA)
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ tags:
|
||||
Homarr is licensed under [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0).
|
||||
|
||||
## TL;DR
|
||||
|
||||
| Action | Allowed |
|
||||
| -------------- | ------- |
|
||||
| Copy | ✅ |
|
||||
@@ -24,8 +25,6 @@ Please consider [making a donation](/docs/community/donate) to Homarr, if this p
|
||||
|
||||
:::
|
||||
|
||||
|
||||
|
||||
## Full License
|
||||
|
||||
```txt
|
||||
|
||||
@@ -5,30 +5,31 @@ tags:
|
||||
- Translations
|
||||
---
|
||||
|
||||
We want to make Homarr accessible for everyone.
|
||||
But to do that, we need your help.
|
||||
We are running a community program that enables everyone to translate Homarr into their language – no programming required.
|
||||
We are using [Crowdin](https://crowdin.com/) to manage our translations.
|
||||
We want to make Homarr accessible for everyone.
|
||||
But to do that, we need your help.
|
||||
We are running a community program that enables everyone to translate Homarr into their language – no programming required.
|
||||
We are using [Crowdin](https://crowdin.com/) to manage our translations.
|
||||
You can find the translations [here](https://translate.homarr.dev/).
|
||||
|
||||
## How to translate
|
||||
|
||||
|
||||
### Using crowdin's online platform
|
||||
Go to the [Crowdin translations](https://translate.homarr.dev/) and select a language you want to translate.
|
||||
You can then enter the translation mode and start translating.
|
||||
After you've submitted your translations, a Proofreader will review your changes.
|
||||
After a translation has been approved, it will be merged to the language development branch.
|
||||
We merge this one periodically to the development branch.
|
||||
|
||||
Go to the [Crowdin translations](https://translate.homarr.dev/) and select a language you want to translate.
|
||||
You can then enter the translation mode and start translating.
|
||||
After you've submitted your translations, a Proofreader will review your changes.
|
||||
After a translation has been approved, it will be merged to the language development branch.
|
||||
We merge this one periodically to the development branch.
|
||||
After the next update of Homarr, all changes from the development branch will be merged, including the changes in the translations.
|
||||
|
||||
### Using live translate
|
||||
|
||||
You can also help translate homarr directly from the app!
|
||||
For this you can simply change your language to "Crowdin (live translations)"
|
||||
|
||||

|
||||
|
||||
Then you'll be prompted to connect to your Crowdin account and choose the language in which you want to transalte.
|
||||
Then you'll be prompted to connect to your Crowdin account and choose the language in which you want to transalte.
|
||||
You can then start translating directly from the app by selecting some text on the app.
|
||||
If some text cannot be selected to be edited with the live translation tool, you can still translate it inside of the In-Context popup at the Home tab, it will be red if it's not translated yet.
|
||||
|
||||
@@ -47,7 +48,8 @@ Do you want to become a proofreader? [Let us know on our Discord](/docs/communit
|
||||
:::
|
||||
|
||||
## Guidelines
|
||||
|
||||
1. Translate with context: Some translations can't be translated 1:1 - they need context. Ensure that your translations make sense in the context.
|
||||
2. Keep things simple and original - no additional characters are needed.
|
||||
3. Don't translate the same thing twice - if you see a translation that is already translated, don't translate it again unless you have a better translation.
|
||||
5. Keep your translations as close to the original as possible.
|
||||
4. Keep your translations as close to the original as possible.
|
||||
@@ -54,11 +54,7 @@ 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.
|
||||
|
||||
<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.
|
||||
@@ -116,22 +112,14 @@ 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**
|
||||
|
||||
@@ -197,38 +185,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.
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
# Glossary
|
||||
|
||||
In the glossary, you'll learn the different components and their names of Homarr.
|
||||
|
||||
| Term | Definition |
|
||||
|-----------------|----------------------------------------------------------------------------------------------------------------|
|
||||
| --------------- | -------------------------------------------------------------------------------------------------------------- |
|
||||
| Apps | An piece of software that you or a third party hosts (eg. Google) |
|
||||
| Integration | A connection to a service outside of Homarr |
|
||||
| Dynamic section | Resizeable area where items on your board can be placed and grouped |
|
||||
|
||||
@@ -8,13 +8,14 @@ tags:
|
||||
- Unraid
|
||||
---
|
||||
|
||||
import Link from '@docusaurus/Link';
|
||||
import { IconArrowRight } from '@tabler/icons-react';
|
||||
import Link from "@docusaurus/Link";
|
||||
import { IconArrowRight } from "@tabler/icons-react";
|
||||
|
||||
Before you try to install Homarr, please review if your device meets the requirements.
|
||||
Installing on an unsupported device may lead to unstable behaviour and no support will be provided.
|
||||
|
||||
## Hardware
|
||||
|
||||
- At least 500MB RAM
|
||||
- At least 600MB of free disk space for the Docker image. SSD is preferred.
|
||||
- Single or dual core CPU
|
||||
@@ -24,8 +25,8 @@ Installing on an unsupported device may lead to unstable behaviour and no suppor
|
||||
|
||||
We build Homarr for a bunch of different architectures, including ARM. Please ensure that your device architecture is supported by Homarr. We won't be able to provide support for unusual or outdated architectures.
|
||||
|
||||
| Architecture | Supported |
|
||||
|--------------|-----------------|
|
||||
| Architecture | Supported |
|
||||
| ------------ | ---------------- |
|
||||
| linux/amd64 | ✅ Supported |
|
||||
| linux/arm64 | ✅ Supported |
|
||||
| linux/arm/v7 | ❌ Not supported |
|
||||
@@ -34,10 +35,10 @@ We build Homarr for a bunch of different architectures, including ARM. Please en
|
||||
|
||||
<Link
|
||||
data-attr="Go to installation"
|
||||
className={'button button--secondary button--lg rounded-3xl dark:border-zinc-600 dark:bg-zinc-800'}
|
||||
className={"button button--secondary button--lg rounded-3xl dark:border-zinc-600 dark:bg-zinc-800"}
|
||||
to="/docs/category/installation-1"
|
||||
>
|
||||
<div className={'flex items-center gap-3'}>
|
||||
<div className={"flex items-center gap-3"}>
|
||||
<span className={"dark:text-gray-200"}>Go to Installation</span>
|
||||
<IconArrowRight className={"dark:text-gray-200"} size={20} />
|
||||
</div>
|
||||
|
||||
@@ -1,21 +1,23 @@
|
||||
import { DockerInstallSnippet } from '@site/src/components/pages/installation/docker/dynamic-docker-install'
|
||||
import { DockerInstallSnippet } from "@site/src/components/pages/installation/docker/dynamic-docker-install";
|
||||
|
||||
# Docker
|
||||
|
||||
Docker is our recommended installation method for beginners and professionals.
|
||||
It is easy to use, ships with all dependencies inside a single container and is easy to use and maintain.
|
||||
It requires at least some basic knowledge of [Docker](https://www.docker.com/).
|
||||
We recommend [NetworkChuck's Docker Containers 101](https://www.youtube.com/watch?v=eGz9DS-aIeY) as a good start for complete beginners.
|
||||
|
||||
## Docker compose
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- A Linux or Windows based system that meets the [hardware prerequisites](/docs/getting-started/#hardware) and [architecture prerequisites](/docs/getting-started/#architecture).
|
||||
- [Docker](https://docs.docker.com/get-docker/)
|
||||
- [Docker Compose](https://docs.docker.com/compose/install/) - *preinstalled on many systems nowadays*
|
||||
- [Docker Compose](https://docs.docker.com/compose/install/) - _preinstalled on many systems nowadays_
|
||||
|
||||
### Installation
|
||||
|
||||
<DockerInstallSnippet data-visual-test="blackout"/>
|
||||
<DockerInstallSnippet data-visual-test="blackout" />
|
||||
Finally, run ``docker compose up -d`` in the same directory. This will start the Homarr container in the background.
|
||||
|
||||
:::caution
|
||||
@@ -26,27 +28,26 @@ Docker compose creates a network by default. Some integrations may not work (spe
|
||||
|
||||
### Updating
|
||||
|
||||
To update, navigate to the directory with the ``docker-compose.yaml`` located.
|
||||
To update, navigate to the directory with the `docker-compose.yaml` located.
|
||||
|
||||
- Stop Homarr using ``docker compose down``
|
||||
- Pull the newest image of Homarr using ``docker compose pull``
|
||||
- Start Homarr again using ``docker compose up -d`` (``-d`` for detached mode - start in background)
|
||||
- Delete the old image using ``docker image prune`` (*Warning: this also removes you other unused images - not just Homarr*)
|
||||
- Stop Homarr using `docker compose down`
|
||||
- Pull the newest image of Homarr using `docker compose pull`
|
||||
- Start Homarr again using `docker compose up -d` (`-d` for detached mode - start in background)
|
||||
- Delete the old image using `docker image prune` (_Warning: this also removes you other unused images - not just Homarr_)
|
||||
|
||||
You can automate this process using [Watchtower](https://containrrr.dev/watchtower/).
|
||||
|
||||
### Uninstalling
|
||||
|
||||
To uninstall, navigate to the directory with the ``docker-compose.yaml`` located.
|
||||
To uninstall, navigate to the directory with the `docker-compose.yaml` located.
|
||||
|
||||
- Stop Homarr using ``docker compose down``
|
||||
- Delete the created directories & files on your root file system (check ``docker-compose.yaml`` file for your specific locations)
|
||||
- Delete compose file using ``rm docker-compose.yaml``
|
||||
- Prune unused Docker images using ``docker image prune`` (*Warning: this also removes you other unused images - not just Homarr*)
|
||||
- Stop Homarr using `docker compose down`
|
||||
- Delete the created directories & files on your root file system (check `docker-compose.yaml` file for your specific locations)
|
||||
- Delete compose file using `rm docker-compose.yaml`
|
||||
- Prune unused Docker images using `docker image prune` (_Warning: this also removes you other unused images - not just Homarr_)
|
||||
- (Optional): Prune any network or volumes if you use any.
|
||||
- (Optional): Remove Homarr from your reverse proxy, VPNs or tunnels if you use any.
|
||||
|
||||
|
||||
## Docker standalone
|
||||
|
||||
:::caution
|
||||
@@ -77,15 +78,16 @@ docker run \
|
||||
|
||||
:::note
|
||||
|
||||
Docker will mount the configuration files and icons to your host machine. Please make sure to replace ``<your-path>`` from the docker run command with your desired storage location. The path must be absolute.
|
||||
Docker will mount the configuration files and icons to your host machine. Please make sure to replace `<your-path>` from the docker run command with your desired storage location. The path must be absolute.
|
||||
|
||||
:::
|
||||
|
||||
### Updating
|
||||
|
||||
To update Homarr, you must remove your container first. Make sure that you've mounted your data and that you have access to it, so your configuration doesn't get lost.
|
||||
|
||||
1. Run ``docker rm homarr`` to remove the container.
|
||||
2. Pull the latest Homarr image ``docker pull ghcr.io/homarr-labs/homarr:latest``.
|
||||
1. Run `docker rm homarr` to remove the container.
|
||||
2. Pull the latest Homarr image `docker pull ghcr.io/homarr-labs/homarr:latest`.
|
||||
3. Re-run the command you used to install Homarr.
|
||||
|
||||
This process can get tideous, if you update frequently. Thus, we recommend [using docker-compose](#docker-compose)
|
||||
@@ -98,8 +100,8 @@ Want to update all your containers automatically? Checkout [Watchtower](https://
|
||||
|
||||
### Uninstalling
|
||||
|
||||
1. Obtain the container ID of your Homarr container using ``docker ps | grep homarr``.
|
||||
2. Delete the container using ``docker rm <your-container-id>``
|
||||
3. Prune unused Docker images using ``docker image prune`` (*Warning: this also removes you other unused images - not just Homarr*)
|
||||
1. Obtain the container ID of your Homarr container using `docker ps | grep homarr`.
|
||||
2. Delete the container using `docker rm <your-container-id>`
|
||||
3. Prune unused Docker images using `docker image prune` (_Warning: this also removes you other unused images - not just Homarr_)
|
||||
4. (Optional): Prune any network or volumes if you use any.
|
||||
5. (Optional): Remove Homarr from your reverse proxy, VPNs or tunnels if you use any.
|
||||
@@ -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
|
||||
|
||||
@@ -12,7 +12,7 @@ A Helm chart to deploy homarr for Kubernetes
|
||||
|
||||
## Source Code
|
||||
|
||||
* <https://github.com/homarr-labs/homarr>
|
||||
- <https://github.com/homarr-labs/homarr>
|
||||
|
||||
## Requirements
|
||||
|
||||
@@ -76,38 +76,38 @@ To avoid including sensitive information in plain text within your version contr
|
||||
|
||||
Below is an exhaustive list of all secrets:
|
||||
|
||||
| FEATURE | SECRET NAME | SECRET KEYS | Required |
|
||||
|-----------|-------------------------|-----------------------------------------|----------|
|
||||
| OIDC | auth-oidc-secret | oidc-client-id<br>oidc-client-secret | No |
|
||||
| LDAP | auth-ldap-secret | bind-password | No |
|
||||
| DATABASE | db-secret | db-url | No |
|
||||
| DATABASE | db-encryption | db-encryption-key | yes |
|
||||
| FEATURE | SECRET NAME | SECRET KEYS | Required |
|
||||
| -------- | ---------------- | ------------------------------------ | -------- |
|
||||
| OIDC | auth-oidc-secret | oidc-client-id<br>oidc-client-secret | No |
|
||||
| LDAP | auth-ldap-secret | bind-password | No |
|
||||
| DATABASE | db-secret | db-url | No |
|
||||
| DATABASE | db-encryption | db-encryption-key | yes |
|
||||
|
||||
### Database
|
||||
|
||||
You have multiple options for configuring the database:
|
||||
|
||||
| DRIVER TYPE | Persistence mode |
|
||||
|----------------|---------------------------------|
|
||||
| better-sqlite3 | Pod disk |
|
||||
| better-sqlite3 | homarr-database PVC |
|
||||
| mysql2 | External MySql database |
|
||||
| node-postgres | External Postgresql database |
|
||||
| DRIVER TYPE | Persistence mode |
|
||||
| -------------- | ---------------------------- |
|
||||
| better-sqlite3 | Pod disk |
|
||||
| better-sqlite3 | homarr-database PVC |
|
||||
| mysql2 | External MySql database |
|
||||
| node-postgres | External Postgresql database |
|
||||
|
||||
#### Pod disk
|
||||
|
||||
No additional configuration is required. However, keep in mind that if the pod restarts, all data will be lost. This setup is not *recommended* for production use.
|
||||
No additional configuration is required. However, keep in mind that if the pod restarts, all data will be lost. This setup is not _recommended_ for production use.
|
||||
|
||||
To create the necessary database secret, execute the following command:
|
||||
|
||||
<details>
|
||||
<summary>Required Secrets</summary>
|
||||
|
||||
````yaml
|
||||
```yaml
|
||||
kubectl create secret generic db-encryption \
|
||||
--from-literal=db-encryption-key='<SECRET_ENCRYPTION_KEY_SECRET_TO_CHANGE>' \
|
||||
--namespace homarr
|
||||
````
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
@@ -120,11 +120,11 @@ Associated secret to create :
|
||||
<details>
|
||||
<summary>DB Required Secrets</summary>
|
||||
|
||||
````yaml
|
||||
```yaml
|
||||
kubectl create secret generic db-encryption \
|
||||
--from-literal=db-encryption-key='<SECRET_ENCRYPTION_KEY_SECRET_TO_CHANGE>' \
|
||||
--namespace homarr
|
||||
````
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
@@ -133,13 +133,13 @@ Bellow an example of the override value file :
|
||||
<details>
|
||||
<summary>values.yaml</summary>
|
||||
|
||||
````yaml
|
||||
```yaml
|
||||
persistence:
|
||||
homarrDatabase:
|
||||
enabled: true
|
||||
storageClassName: "default"
|
||||
size: "1Gi"
|
||||
````
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
@@ -150,17 +150,18 @@ To create the necessary database secrets, execute the following command:
|
||||
<details>
|
||||
<summary>Required Secrets</summary>
|
||||
|
||||
````yaml
|
||||
```yaml
|
||||
kubectl create secret generic db-encryption \
|
||||
--from-literal=db-encryption-key='<SECRET_ENCRYPTION_KEY_SECRET_TO_CHANGE>' \
|
||||
--namespace homarr
|
||||
````
|
||||
```
|
||||
|
||||
````yaml
|
||||
```yaml
|
||||
kubectl create secret generic db-secret \
|
||||
--from-literal=db-url='mysql://user:password@host:port/homarrdb' \
|
||||
--namespace homarr
|
||||
````
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
Below is an example of the override values file:
|
||||
@@ -168,10 +169,11 @@ Below is an example of the override values file:
|
||||
<details>
|
||||
<summary>values.yaml</summary>
|
||||
|
||||
````yaml
|
||||
```yaml
|
||||
database:
|
||||
type: mysql
|
||||
````
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
#### External Postgresql database
|
||||
@@ -181,17 +183,17 @@ To create the necessary database secrets, execute the following command:
|
||||
<details>
|
||||
<summary>Required Secrets</summary>
|
||||
|
||||
````yaml
|
||||
```yaml
|
||||
kubectl create secret generic db-encryption \
|
||||
--from-literal=db-encryption-key='<SECRET_ENCRYPTION_KEY_SECRET_TO_CHANGE>' \
|
||||
--namespace homarr
|
||||
````
|
||||
```
|
||||
|
||||
````yaml
|
||||
```yaml
|
||||
kubectl create secret generic db-secret \
|
||||
--from-literal=db-url='postgresql://user:password@host:port/homarrdb' \
|
||||
--namespace homarr
|
||||
````
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
@@ -200,10 +202,11 @@ Below is an example of the override values file:
|
||||
<details>
|
||||
<summary>values.yaml</summary>
|
||||
|
||||
````yaml
|
||||
```yaml
|
||||
database:
|
||||
type: postgresql
|
||||
````
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
### Ingress
|
||||
@@ -213,7 +216,7 @@ The ingress section in the values.yaml file allows you to configure how external
|
||||
<details>
|
||||
<summary>values.yaml</summary>
|
||||
|
||||
````yaml
|
||||
```yaml
|
||||
service:
|
||||
enabled: true # Ensure the service is enabled for Ingress to route traffic
|
||||
ingress:
|
||||
@@ -231,7 +234,8 @@ ingress:
|
||||
- "homarr.homelab.dev"
|
||||
- "www.homarr.homelab.dev"
|
||||
secretName: homelab-tls
|
||||
````
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
### HTTPRoute (Gateway API)
|
||||
@@ -242,9 +246,9 @@ This provides a more expressive and future-proof alternative to Ingress, with su
|
||||
<details>
|
||||
<summary>values.yaml</summary>
|
||||
|
||||
````yaml
|
||||
```yaml
|
||||
service:
|
||||
enabled: true # must be enabled for HTTPRoute to forward traffic
|
||||
enabled: true # must be enabled for HTTPRoute to forward traffic
|
||||
httproute:
|
||||
enabled: true
|
||||
parentRefs:
|
||||
@@ -259,15 +263,16 @@ httproute:
|
||||
value: /
|
||||
backendRefs:
|
||||
- name: homarr
|
||||
port: 7575
|
||||
````
|
||||
port: 8080
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
#### Notes:
|
||||
|
||||
- parentRefs: the Gateway(s) this route attaches to. Gateways must already exist.
|
||||
- hostnames: domain names this route applies to.
|
||||
- rules: each rule can define matches (paths, headers, queries), optional filters, and backendRefs to services
|
||||
- rules: each rule can define matches (paths, headers, queries), optional filters, and backendRefs to services
|
||||
- TLS must be configured on the Gateway (not on HTTPRoute). For example, the Gateway can have a TLS listener for homarr.homelab.dev, and this route will automatically apply once matched.
|
||||
|
||||
### Certificates
|
||||
@@ -284,7 +289,7 @@ Use inline certificates to generate a ConfigMap, mounted as individual files.
|
||||
<details>
|
||||
<summary>values.yaml</summary>
|
||||
|
||||
````yaml
|
||||
```yaml
|
||||
persistence:
|
||||
homarrTrustedCerts:
|
||||
enabled: true
|
||||
@@ -298,7 +303,8 @@ persistence:
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIID...EFGH==
|
||||
-----END CERTIFICATE-----
|
||||
````
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
Behavior:
|
||||
@@ -313,7 +319,7 @@ Use inline certificates to generate a Kubernetes Secret, mounted as files.
|
||||
<details>
|
||||
<summary>values.yaml</summary>
|
||||
|
||||
````yaml
|
||||
```yaml
|
||||
persistence:
|
||||
homarrTrustedCerts:
|
||||
enabled: true
|
||||
@@ -327,7 +333,8 @@ persistence:
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIC...XYZ==
|
||||
-----END CERTIFICATE-----
|
||||
````
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
Behavior:
|
||||
@@ -342,16 +349,17 @@ Use an existing Kubernetes Secret, assuming its keys are filenames.
|
||||
<details>
|
||||
<summary>values.yaml</summary>
|
||||
|
||||
````yaml
|
||||
```yaml
|
||||
persistence:
|
||||
homarrTrustedCerts:
|
||||
enabled: true
|
||||
type: existingSecret
|
||||
existingSecretName: "existingSecretName"
|
||||
existingSecretKeys:
|
||||
- cert3.crt
|
||||
- cert4.crt
|
||||
````
|
||||
- cert3.crt
|
||||
- cert4.crt
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
Behavior:
|
||||
@@ -372,114 +380,115 @@ All available values are listed on the [artifacthub](https://artifacthub.io/pack
|
||||
|
||||
## Values
|
||||
|
||||
| Key | Type | Default | Description |
|
||||
|-----|------|---------|-------------|
|
||||
| additionalObjects | list | `[]` | Additional resources to deploy. These objects are templated. |
|
||||
| affinity | object | `{}` | Node affinity for pod scheduling |
|
||||
| autoscaling.enabled | bool | `false` | Enable autoscaling |
|
||||
| autoscaling.maxReplicas | int | `100` | Maximum replicas |
|
||||
| autoscaling.minReplicas | int | `1` | Minimum replicas |
|
||||
| autoscaling.targetCPUUtilizationPercentage | int | `80` | Target CPU utilization for autoscaling |
|
||||
| containerPorts | object | `{"http":{"port":7575,"protocol":"TCP"}}` | containerPorts defines the ports to open on the container. It is a map where each entry specifies: - `port` (int) (required): The port number to expose inside the container. - `protocol` (string) (required): The network protocol (TCP or UDP) used for the port. - `disabled` (bool) : Optional flag to disable this port (defaults to false). Can be overridden via Helm values. By default, this configuration exposes TCP port 7575 with the name `http`. |
|
||||
| database.migrationEnabled | bool | `true` | Database migration configuration. DB_MIGRATIONS_DISABLED Set to `true` to disable database migrations. Migrations are enabled by default (`false`). |
|
||||
| database.type | string | `"sqlite"` | Database type: sqlite, mysql or postgresql |
|
||||
| env.AUTH_COOKIE_PREFIX | string | `"homarr"` | Prefix used for all authentication cookies. Change if you run another Auth.js/NextAuth app on the same hostname to avoid cookie name collisions. Only letters, numbers, hyphens and underscores. |
|
||||
| env.AUTH_LDAP_BASE | string | `nil` | Base dn of your LDAP server |
|
||||
| env.AUTH_LDAP_BIND_DN | string | `nil` | User used for finding users and groups |
|
||||
| env.AUTH_LDAP_GROUP_CLASS | string | `"groupOfUniqueNames"` | Class used for querying groups |
|
||||
| env.AUTH_LDAP_GROUP_FILTER_EXTRA_ARG | string | `nil` | Extra arguments for user's groups search filter (& based) |
|
||||
| env.AUTH_LDAP_GROUP_MEMBER_ATTRIBUTE | string | `"member"` | Attribute used for querying group member |
|
||||
| env.AUTH_LDAP_GROUP_MEMBER_USER_ATTRIBUTE | string | `"dn"` | User attribute used for comparing with group member |
|
||||
| env.AUTH_LDAP_SEARCH_SCOPE | string | `"base"` | LDAP search scope between base, one or sub |
|
||||
| env.AUTH_LDAP_URI | string | `nil` | URI of your LDAP server |
|
||||
| env.AUTH_LDAP_USERNAME_ATTRIBUTE | string | `"uid"` | Attribute used for username |
|
||||
| env.AUTH_LDAP_USERNAME_FILTER_EXTRA_ARG | string | `nil` | Extra arguments for user search filter (& based) |
|
||||
| env.AUTH_LDAP_USER_MAIL_ATTRIBUTE | string | `"mail"` | Attribute used for mail field |
|
||||
| env.AUTH_LOGOUT_REDIRECT_URL | string | `nil` | URL to redirect to after clicking logging out. |
|
||||
| env.AUTH_OIDC_AUTO_LOGIN | string | `"false"` | Automatically redirect to OIDC login |
|
||||
| env.AUTH_OIDC_CLIENT_NAME | string | `"OIDC"` | Display name of provider (in login screen) |
|
||||
| env.AUTH_OIDC_GROUPS_ATTRIBUTE | string | `"groups"` | Attribute used for groups (roles) claim |
|
||||
| env.AUTH_OIDC_ISSUER | string | `nil` | Issuer URI of OIDC provider without trailing slash (/) |
|
||||
| env.AUTH_OIDC_NAME_ATTRIBUTE_OVERWRITE | string | `nil` | Overwrite name attribute. By default, it will use preferred_username if it does not contain a @ and otherwise name. |
|
||||
| env.AUTH_OIDC_SCOPE_OVERWRITE | string | `"openid email profile groups"` | Override the OIDC scopes |
|
||||
| env.AUTH_PROVIDERS | string | `"credentials"` | Enabled authentication methods. Multiple providers can be enabled with by separating them with , (ex. AUTH_PROVIDERS=credentials,oidc, it is highly recommended to just enable one provider). |
|
||||
| env.AUTH_SESSION_EXPIRY_TIME | string | `"30d"` | Time for the session to time out. Can be set as pure number, which will automatically be used in seconds, or followed by s, m, h or d for seconds, minutes, hours or days. (ex: "30m") |
|
||||
| env.ENABLE_DNS_CACHING | string | `"false"` | Enables dns caching. This is not yet working for all users. See #4006 |
|
||||
| env.LOG_LEVEL | string | `"info"` | Log level to use. Possible values: debug/info/warn/error |
|
||||
| env.NO_EXTERNAL_CONNECTION | string | `"false"` | Disables some requests that need internet connection |
|
||||
| env.TZ | string | `"Europe/Paris"` | Your local time zone |
|
||||
| envSecrets.authLdapCredentials.existingSecret | string | `"auth-ldap-secret"` | Name of existing secret containing LDAP credentials |
|
||||
| envSecrets.authLdapCredentials.ldapBindingPassword | string | `"bind-password"` | Password for bind user secret key |
|
||||
| envSecrets.authOidcCredentials.existingSecret | string | `"auth-oidc-secret"` | Name of existing secret containing OIDC credentials |
|
||||
| envSecrets.authOidcCredentials.oidcClientId | string | `"oidc-client-id"` | ID of OIDC client (application) secret key |
|
||||
| envSecrets.authOidcCredentials.oidcClientSecret | string | `"oidc-client-secret"` | Secret of OIDC client (application) secret key |
|
||||
| envSecrets.dbCredentials.dbUrlKey | string | `"db-url"` | Secret key for DB_URL Example for external database: `mysql://username:password@host:port/homarrdb` or `postgresql://username:password@host:port/homarrdb` |
|
||||
| envSecrets.dbCredentials.existingSecret | string | `"db-secret"` | Name of existing secret containing DB credentials |
|
||||
| envSecrets.dbEncryption.existingSecret | string | `"db-encryption"` | Name of existing secret containing DB encryption |
|
||||
| envSecrets.dbEncryption.key | string | `"db-encryption-key"` | Secret key for SECRET_ENCRYPTION_KEY can be generated with `openssl rand -hex 32` |
|
||||
| fullnameOverride | string | `""` | Overrides chart's fullname |
|
||||
| hostAliases | list | `[]` | Add static entries to /etc/hosts in the Pod. This is useful in the following cases: - You are running in a dual-stack cluster (IPv4 + IPv6) and want to force usage of IPv4 for specific hostnames - Your application is having DNS resolution issues or IPv6 preference issues - You need to override or simulate DNS entries without changing global DNS - You are running in an air-gapped or isolated environment without external DNS Example: hostAliases: - ip: "192.168.1.10" hostnames: - "example.com" - "example.internal" |
|
||||
| httproute | object | `{"enabled":false,"hostnames":["chart-example.local"],"parentRefs":[{"name":"my-gateway","namespace":"default"}],"rules":[{"backendRefs":[{"name":"homarr","port":7575}],"filters":[],"matches":[{"path":{"type":"PathPrefix","value":"/"}}]}]}` | Gateway API HTTPRoute configuration |
|
||||
| httproute.enabled | bool | `false` | Enable HTTPRoute |
|
||||
| httproute.hostnames | list | `["chart-example.local"]` | Hostnames this route matches (similar to ingress.hosts.host) |
|
||||
| httproute.parentRefs | list | `[{"name":"my-gateway","namespace":"default"}]` | References to the parent Gateway(s) this route attaches to. Each item must include at least a `name`, and optionally a `namespace`. |
|
||||
| httproute.rules | list | `[{"backendRefs":[{"name":"homarr","port":7575}],"filters":[],"matches":[{"path":{"type":"PathPrefix","value":"/"}}]}]` | List of routing rules. Each rule can include: - matches: path/header/query matching - filters: optional transformations (redirects, header modifications, etc.) - backendRefs: one or more Kubernetes Services to forward traffic to |
|
||||
| httproute.rules[0].filters | list | `[]` | Optional filters for this rule (default: empty) |
|
||||
| httproute.rules[0].matches[0].path.type | string | `"PathPrefix"` | Path match type. One of: Exact, PathPrefix, RegularExpression |
|
||||
| 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.76.1"` | 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 |
|
||||
| ingress.hosts | list | `[{"host":"chart-example.local","paths":[{"path":"/"}]}]` | Ingress hosts configuration |
|
||||
| ingress.ingressClassName | string | `""` | Ingress class name |
|
||||
| ingress.tls | list | `[]` | Ingress TLS configuration |
|
||||
| livenessProbe.failureThreshold | int | `3` | Failure threshold for liveness probe - number of consecutive failures before pod is restarted |
|
||||
| livenessProbe.httpGet.path | string | `"/api/health/live"` | This is the liveness check endpoint used by Kubernetes to determine if the application is still running. |
|
||||
| livenessProbe.httpGet.port | int | `7575` | The port on which the liveness check will be performed. This must be the same as the container port exposed by the application. |
|
||||
| livenessProbe.initialDelaySeconds | int | `10` | Initial delay in seconds before the liveness probe starts |
|
||||
| livenessProbe.periodSeconds | int | `10` | Period in seconds between liveness probe checks |
|
||||
| livenessProbe.timeoutSeconds | int | `1` | Timeout in seconds for each liveness probe check |
|
||||
| nameOverride | string | `""` | Overrides chart's name |
|
||||
| nodeSelector | object | `{}` | Node selectors for pod scheduling |
|
||||
| persistence.homarrDatabase.accessMode | string | `"ReadWriteOnce"` | homarr-database access mode |
|
||||
| persistence.homarrDatabase.enabled | bool | `false` | Enable homarr-database persistent storage |
|
||||
| persistence.homarrDatabase.mountPath | string | `"/appdata"` | homarr-database mount path inside the pod |
|
||||
| persistence.homarrDatabase.name | string | `"homarr-database"` | homarr-database persistent storage name |
|
||||
| persistence.homarrDatabase.size | string | `"50Mi"` | homarr-database storage size |
|
||||
| persistence.homarrDatabase.storageClassName | string | `"local-path"` | homarr-database storage class name |
|
||||
| persistence.homarrDatabase.volumeClaimName | string | `""` | homarr-database optional volumeClaimName to target specific PV |
|
||||
| persistence.homarrTrustedCerts.certificates | string | `nil` | homarr-trusted-certificates certificates, each entry will become a new trusted certificate as a dedicated file (works only for "configmap" and "secret" mode) |
|
||||
| persistence.homarrTrustedCerts.enabled | bool | `false` | Enable trusted certificates persistence |
|
||||
| persistence.homarrTrustedCerts.existingSecretKeys | string | `nil` | List of keys (filenames) to mount from the existing secret (used only when type is "existingSecret") |
|
||||
| persistence.homarrTrustedCerts.existingSecretName | string | `""` | Name of the existing Kubernetes Secret to mount (required if type is "existingSecret") |
|
||||
| persistence.homarrTrustedCerts.mountPath | string | `"/appdata/trusted-certificates"` | homarr-trusted-certificates mount path inside the pod |
|
||||
| persistence.homarrTrustedCerts.type | string | `"configmap"` | Persistence mode can be : configmap (declarative), secret (declarative) or existingSecret (mount an existing Kubernetes Secret by name and specify which keys to mount as files) |
|
||||
| podAnnotations | object | `{}` | Pod annotations |
|
||||
| podLabels | object | `{}` | Pod labels |
|
||||
| podSecurityContext | object | `{}` | Pod security context |
|
||||
| rbac | object | `{"enabled":false}` | Enable RBAC resources for Kubernetes integration Creates Role, ClusterRole, and associated bindings for Homarr's Kubernetes features |
|
||||
| rbac.enabled | bool | `false` | Enable to create RBAC resources and activate Kubernetes integration |
|
||||
| readinessProbe.failureThreshold | int | `3` | Failure threshold for readiness probe - number of consecutive failures before pod is considered unready |
|
||||
| readinessProbe.httpGet.path | string | `"/api/health/ready"` | This is the readiness check endpoint used by Kubernetes to determine if the application is ready to handle traffic. |
|
||||
| readinessProbe.httpGet.port | int | `7575` | The port on which the readiness check will be performed. This must match the container's exposed port. |
|
||||
| readinessProbe.initialDelaySeconds | int | `10` | Initial delay in seconds before the readiness probe starts. increase this value if the pod is slow to fully start. |
|
||||
| readinessProbe.periodSeconds | int | `10` | Period in seconds between readiness probe checks |
|
||||
| readinessProbe.timeoutSeconds | int | `1` | Timeout in seconds for each readiness probe check |
|
||||
| replicaCount | int | `1` | Number of replicas |
|
||||
| resources | object | `{}` | Resource configuration |
|
||||
| securityContext | object | `{}` | Security context |
|
||||
| service.enabled | bool | `true` | Enable service |
|
||||
| service.ipFamilies | list | `[]` | List of IP families to use for the service. Examples: - ["IPv4"] - ["IPv6"] - ["IPv4", "IPv6"] for dual-stack Leave empty to use cluster default behavior |
|
||||
| service.ipFamilyPolicy | string | `"SingleStack"` | Defines how the service assigns IP families (IPv4/IPv6) Possible values: - SingleStack (default): Only one IP family, usually IPv4 - PreferDualStack: Use dual-stack if the cluster supports it, fallback to single - RequireDualStack: Fail if dual-stack cannot be assigned |
|
||||
| service.ports.app.port | int | `7575` | Service port |
|
||||
| service.ports.app.protocol | string | `"TCP"` | Service protocol |
|
||||
| service.ports.app.targetPort | string | `"http"` | Service target port |
|
||||
| service.type | string | `"ClusterIP"` | Service type |
|
||||
| strategyType | string | `"RollingUpdate"` | `strategyType` specifies the strategy used to replace old Pods by new ones. `strategyType` can be `"Recreate"` or `"RollingUpdate"`. `"RollingUpdate"` is the default value and updates Pods in a rolling update fashion. `"Recreate"` will kill all existing Pods before new ones are created. The `"Recreate"` strategy is necessary when persistent volume's `accessMode` is set to `"ReadWriteOnce"` when using `helm upgrade`, as pod volume attachments to an existing PersistentVolumeClaim need to be cleared before a new pod can attach to it. |
|
||||
| tolerations | list | `[]` | Node tolerations for pod scheduling |
|
||||
| Key | Type | Default | Description |
|
||||
| -------------------------------------------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| additionalObjects | list | `[]` | Additional resources to deploy. These objects are templated. |
|
||||
| affinity | object | `{}` | Node affinity for pod scheduling |
|
||||
| autoscaling.enabled | bool | `false` | Enable autoscaling |
|
||||
| autoscaling.maxReplicas | int | `100` | Maximum replicas |
|
||||
| autoscaling.minReplicas | int | `1` | Minimum replicas |
|
||||
| autoscaling.targetCPUUtilizationPercentage | int | `80` | Target CPU utilization for autoscaling |
|
||||
| containerPorts | object | `{"http":{"port":7575,"protocol":"TCP"}}` | containerPorts defines the ports to open on the container. It is a map where each entry specifies: - `port` (int) (required): The port number to expose inside the container. - `protocol` (string) (required): The network protocol (TCP or UDP) used for the port. - `disabled` (bool) : Optional flag to disable this port (defaults to false). Can be overridden via Helm values. By default, this configuration exposes TCP port 7575 with the name `http`. |
|
||||
| database.migrationEnabled | bool | `true` | Database migration configuration. DB_MIGRATIONS_DISABLED Set to `true` to disable database migrations. Migrations are enabled by default (`false`). |
|
||||
| database.type | string | `"sqlite"` | Database type: sqlite, mysql or postgresql |
|
||||
| env.AUTH_COOKIE_PREFIX | string | `"homarr"` | Prefix used for all authentication cookies. Change if you run another Auth.js/NextAuth app on the same hostname to avoid cookie name collisions. Only letters, numbers, hyphens and underscores. |
|
||||
| env.AUTH_LDAP_BASE | string | `nil` | Base dn of your LDAP server |
|
||||
| env.AUTH_LDAP_BIND_DN | string | `nil` | User used for finding users and groups |
|
||||
| env.AUTH_LDAP_GROUP_CLASS | string | `"groupOfUniqueNames"` | Class used for querying groups |
|
||||
| env.AUTH_LDAP_GROUP_FILTER_EXTRA_ARG | string | `nil` | Extra arguments for user's groups search filter (& based) |
|
||||
| env.AUTH_LDAP_GROUP_MEMBER_ATTRIBUTE | string | `"member"` | Attribute used for querying group member |
|
||||
| env.AUTH_LDAP_GROUP_MEMBER_USER_ATTRIBUTE | string | `"dn"` | User attribute used for comparing with group member |
|
||||
| env.AUTH_LDAP_SEARCH_SCOPE | string | `"base"` | LDAP search scope between base, one or sub |
|
||||
| env.AUTH_LDAP_URI | string | `nil` | URI of your LDAP server |
|
||||
| env.AUTH_LDAP_USERNAME_ATTRIBUTE | string | `"uid"` | Attribute used for username |
|
||||
| env.AUTH_LDAP_USERNAME_FILTER_EXTRA_ARG | string | `nil` | Extra arguments for user search filter (& based) |
|
||||
| env.AUTH_LDAP_USER_MAIL_ATTRIBUTE | string | `"mail"` | Attribute used for mail field |
|
||||
| env.AUTH_LOGOUT_REDIRECT_URL | string | `nil` | URL to redirect to after clicking logging out. |
|
||||
| env.AUTH_OIDC_AUTO_LOGIN | string | `"false"` | Automatically redirect to OIDC login |
|
||||
| env.AUTH_OIDC_CLIENT_NAME | string | `"OIDC"` | Display name of provider (in login screen) |
|
||||
| env.AUTH_OIDC_GROUPS_ATTRIBUTE | string | `"groups"` | Attribute used for groups (roles) claim |
|
||||
| env.AUTH_OIDC_ISSUER | string | `nil` | Issuer URI of OIDC provider without trailing slash (/) |
|
||||
| env.AUTH_OIDC_NAME_ATTRIBUTE_OVERWRITE | string | `nil` | Overwrite name attribute. By default, it will use preferred_username if it does not contain a @ and otherwise name. |
|
||||
| env.AUTH_OIDC_SCOPE_OVERWRITE | string | `"openid email profile groups"` | Override the OIDC scopes |
|
||||
| env.AUTH_PROVIDERS | string | `"credentials"` | Enabled authentication methods. Multiple providers can be enabled with by separating them with , (ex. AUTH_PROVIDERS=credentials,oidc, it is highly recommended to just enable one provider). |
|
||||
| env.AUTH_SESSION_EXPIRY_TIME | string | `"30d"` | Time for the session to time out. Can be set as pure number, which will automatically be used in seconds, or followed by s, m, h or d for seconds, minutes, hours or days. (ex: "30m") |
|
||||
| env.ENABLE_DNS_CACHING | string | `"false"` | Enables dns caching. This is not yet working for all users. See #4006 |
|
||||
| env.LOG_LEVEL | string | `"info"` | Log level to use. Possible values: debug/info/warn/error |
|
||||
| env.NO_EXTERNAL_CONNECTION | string | `"false"` | Disables some requests that need internet connection |
|
||||
| env.TZ | string | `"Europe/Paris"` | Your local time zone |
|
||||
| envSecrets.authLdapCredentials.existingSecret | string | `"auth-ldap-secret"` | Name of existing secret containing LDAP credentials |
|
||||
| envSecrets.authLdapCredentials.ldapBindingPassword | string | `"bind-password"` | Password for bind user secret key |
|
||||
| envSecrets.authOidcCredentials.existingSecret | string | `"auth-oidc-secret"` | Name of existing secret containing OIDC credentials |
|
||||
| envSecrets.authOidcCredentials.oidcClientId | string | `"oidc-client-id"` | ID of OIDC client (application) secret key |
|
||||
| envSecrets.authOidcCredentials.oidcClientSecret | string | `"oidc-client-secret"` | Secret of OIDC client (application) secret key |
|
||||
| envSecrets.dbCredentials.dbUrlKey | string | `"db-url"` | Secret key for DB_URL Example for external database: `mysql://username:password@host:port/homarrdb` or `postgresql://username:password@host:port/homarrdb` |
|
||||
| envSecrets.dbCredentials.existingSecret | string | `"db-secret"` | Name of existing secret containing DB credentials |
|
||||
| envSecrets.dbEncryption.existingSecret | string | `"db-encryption"` | Name of existing secret containing DB encryption |
|
||||
| envSecrets.dbEncryption.key | string | `"db-encryption-key"` | Secret key for SECRET_ENCRYPTION_KEY can be generated with `openssl rand -hex 32` |
|
||||
| fullnameOverride | string | `""` | Overrides chart's fullname |
|
||||
| hostAliases | list | `[]` | Add static entries to /etc/hosts in the Pod. This is useful in the following cases: - You are running in a dual-stack cluster (IPv4 + IPv6) and want to force usage of IPv4 for specific hostnames - Your application is having DNS resolution issues or IPv6 preference issues - You need to override or simulate DNS entries without changing global DNS - You are running in an air-gapped or isolated environment without external DNS Example: hostAliases: - ip: "192.168.1.10" hostnames: - "example.com" - "example.internal" |
|
||||
| httproute | object | `{"enabled":false,"hostnames":["chart-example.local"],"parentRefs":[{"name":"my-gateway","namespace":"default"}],"rules":[{"backendRefs":[{"name":"homarr","port":8080}],"filters":[],"matches":[{"path":{"type":"PathPrefix","value":"/"}}]}]}` | Gateway API HTTPRoute configuration |
|
||||
| httproute.enabled | bool | `false` | Enable HTTPRoute |
|
||||
| httproute.hostnames | list | `["chart-example.local"]` | Hostnames this route matches (similar to ingress.hosts.host) |
|
||||
| httproute.parentRefs | list | `[{"name":"my-gateway","namespace":"default"}]` | References to the parent Gateway(s) this route attaches to. Each item must include at least a `name`, and optionally a `namespace`. |
|
||||
| httproute.rules | list | `[{"backendRefs":[{"name":"homarr","port":8080}],"filters":[],"matches":[{"path":{"type":"PathPrefix","value":"/"}}]}]` | List of routing rules. Each rule can include: - matches: path/header/query matching - filters: optional transformations (redirects, header modifications, etc.) - backendRefs: one or more Kubernetes Services to forward traffic to |
|
||||
| httproute.rules[0].filters | list | `[]` | Optional filters for this rule (default: empty) |
|
||||
| httproute.rules[0].matches[0].path.type | string | `"PathPrefix"` | Path match type. One of: Exact, PathPrefix, RegularExpression |
|
||||
| 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 |
|
||||
| imagePullSecrets | list | `[]` | Secrets for Docker registry |
|
||||
| ingress.annotations | object | `{}` | Ingress annotations |
|
||||
| ingress.enabled | bool | `false` | Enable ingress |
|
||||
| ingress.hosts | list | `[{"host":"chart-example.local","paths":[{"path":"/"}]}]` | Ingress hosts configuration |
|
||||
| ingress.ingressClassName | string | `""` | Ingress class name |
|
||||
| ingress.tls | list | `[]` | Ingress TLS configuration |
|
||||
| livenessProbe.failureThreshold | int | `3` | Failure threshold for liveness probe - number of consecutive failures before pod is restarted |
|
||||
| livenessProbe.httpGet.path | string | `"/api/health/live"` | This is the liveness check endpoint used by Kubernetes to determine if the application is still running. |
|
||||
| livenessProbe.httpGet.port | int | `7575` | The port on which the liveness check will be performed. This must be the same as the container port exposed by the application. |
|
||||
| livenessProbe.initialDelaySeconds | int | `10` | Initial delay in seconds before the liveness probe starts |
|
||||
| livenessProbe.periodSeconds | int | `10` | Period in seconds between liveness probe checks |
|
||||
| livenessProbe.timeoutSeconds | int | `1` | Timeout in seconds for each liveness probe check |
|
||||
| nameOverride | string | `""` | Overrides chart's name |
|
||||
| nodeSelector | object | `{}` | Node selectors for pod scheduling |
|
||||
| persistence.homarrDatabase.accessMode | string | `"ReadWriteOnce"` | homarr-database access mode |
|
||||
| persistence.homarrDatabase.enabled | bool | `false` | Enable homarr-database persistent storage |
|
||||
| persistence.homarrDatabase.mountPath | string | `"/appdata"` | homarr-database mount path inside the pod |
|
||||
| persistence.homarrDatabase.name | string | `"homarr-database"` | homarr-database persistent storage name |
|
||||
| persistence.homarrDatabase.size | string | `"50Mi"` | homarr-database storage size |
|
||||
| persistence.homarrDatabase.storageClassName | string | `"local-path"` | homarr-database storage class name |
|
||||
| persistence.homarrDatabase.volumeClaimName | string | `""` | homarr-database optional volumeClaimName to target specific PV |
|
||||
| persistence.homarrTrustedCerts.certificates | string | `nil` | homarr-trusted-certificates certificates, each entry will become a new trusted certificate as a dedicated file (works only for "configmap" and "secret" mode) |
|
||||
| persistence.homarrTrustedCerts.enabled | bool | `false` | Enable trusted certificates persistence |
|
||||
| persistence.homarrTrustedCerts.existingSecretKeys | string | `nil` | List of keys (filenames) to mount from the existing secret (used only when type is "existingSecret") |
|
||||
| persistence.homarrTrustedCerts.existingSecretName | string | `""` | Name of the existing Kubernetes Secret to mount (required if type is "existingSecret") |
|
||||
| persistence.homarrTrustedCerts.mountPath | string | `"/appdata/trusted-certificates"` | homarr-trusted-certificates mount path inside the pod |
|
||||
| persistence.homarrTrustedCerts.type | string | `"configmap"` | Persistence mode can be : configmap (declarative), secret (declarative) or existingSecret (mount an existing Kubernetes Secret by name and specify which keys to mount as files) |
|
||||
| podAnnotations | object | `{}` | Pod annotations |
|
||||
| podLabels | object | `{}` | Pod labels |
|
||||
| podSecurityContext | object | `{}` | Pod security context |
|
||||
| rbac | object | `{"enabled":false}` | Enable RBAC resources for Kubernetes integration Creates Role, ClusterRole, and associated bindings for Homarr's Kubernetes features |
|
||||
| rbac.enabled | bool | `false` | Enable to create RBAC resources and activate Kubernetes integration |
|
||||
| readinessProbe.failureThreshold | int | `3` | Failure threshold for readiness probe - number of consecutive failures before pod is considered unready |
|
||||
| readinessProbe.httpGet.path | string | `"/api/health/ready"` | This is the readiness check endpoint used by Kubernetes to determine if the application is ready to handle traffic. |
|
||||
| readinessProbe.httpGet.port | int | `7575` | The port on which the readiness check will be performed. This must match the container's exposed port. |
|
||||
| readinessProbe.initialDelaySeconds | int | `10` | Initial delay in seconds before the readiness probe starts. increase this value if the pod is slow to fully start. |
|
||||
| readinessProbe.periodSeconds | int | `10` | Period in seconds between readiness probe checks |
|
||||
| readinessProbe.timeoutSeconds | int | `1` | Timeout in seconds for each readiness probe check |
|
||||
| replicaCount | int | `1` | Number of replicas |
|
||||
| resources | object | `{}` | Resource configuration |
|
||||
| securityContext | object | `{}` | Security context |
|
||||
| service.enabled | bool | `true` | Enable service |
|
||||
| service.ipFamilies | list | `[]` | List of IP families to use for the service. Examples: - ["IPv4"] - ["IPv6"] - ["IPv4", "IPv6"] for dual-stack Leave empty to use cluster default behavior |
|
||||
| service.ipFamilyPolicy | string | `"SingleStack"` | Defines how the service assigns IP families (IPv4/IPv6) Possible values: - SingleStack (default): Only one IP family, usually IPv4 - PreferDualStack: Use dual-stack if the cluster supports it, fallback to single - RequireDualStack: Fail if dual-stack cannot be assigned |
|
||||
| service.ports.app.port | int | `7575` | Service port |
|
||||
| service.ports.app.protocol | string | `"TCP"` | Service protocol |
|
||||
| service.ports.app.targetPort | string | `"http"` | Service target port |
|
||||
| service.type | string | `"ClusterIP"` | Service type |
|
||||
| strategyType | string | `"RollingUpdate"` | `strategyType` specifies the strategy used to replace old Pods by new ones. `strategyType` can be `"Recreate"` or `"RollingUpdate"`. `"RollingUpdate"` is the default value and updates Pods in a rolling update fashion. `"Recreate"` will kill all existing Pods before new ones are created. The `"Recreate"` strategy is necessary when persistent volume's `accessMode` is set to `"ReadWriteOnce"` when using `helm upgrade`, as pod volume attachments to an existing PersistentVolumeClaim need to be cleared before a new pod can attach to it. |
|
||||
| tolerations | list | `[]` | Node tolerations for pod scheduling |
|
||||
|
||||
---
|
||||
|
||||
Autogenerated from chart metadata using [helm-docs](https://github.com/norwoodj/helm-docs)
|
||||
@@ -14,7 +14,7 @@ Homarr can be seamlessly integrated into your Home Assistant setup with this add
|
||||
## Installation
|
||||
|
||||
1. **Add Repository**: Navigate to your Home Assistant Supervisor panel, and go to the Add-on Store. Add the following repository URL: `https://github.com/Wiggen94/ha-homarr-v1-server` or click this button
|
||||
[](https://my.home-assistant.io/redirect/supervisor_add_addon_repository/?repository_url=https%3A%2F%2Fgithub.com%2FWiggen94%2Fha-homarr-v1-server)
|
||||
[](https://my.home-assistant.io/redirect/supervisor_add_addon_repository/?repository_url=https%3A%2F%2Fgithub.com%2FWiggen94%2Fha-homarr-v1-server)
|
||||
|
||||
2. **Install the Add-on**: Search for "Homarr Server" in the Add-on Store and select it. Click on "Install" to start the installation process.
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# Portainer
|
||||
|
||||
## Portainer Stacks
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- [Portainer Stacks](https://www.portainer.io/blog/stacks-docker-compose-the-portainer-way)
|
||||
@@ -23,15 +25,14 @@ services:
|
||||
environment:
|
||||
- SECRET_ENCRYPTION_KEY=your_64_character_hex_string # <--- can be generated with `openssl rand -hex 32`
|
||||
ports:
|
||||
- '7575:7575'
|
||||
- "7575:7575"
|
||||
```
|
||||
|
||||
|
||||
:::note
|
||||
Portainer will mount the files to your host machine. Please make sure to replace ``<your-path>`` from the docker run command with your desired storage location. The path must be absolute.
|
||||
Portainer will mount the files to your host machine. Please make sure to replace `<your-path>` from the docker run command with your desired storage location. The path must be absolute.
|
||||
:::
|
||||
|
||||
Then, click on ``Deploy the stack`` and wait until the container has started.
|
||||
Then, click on `Deploy the stack` and wait until the container has started.
|
||||
|
||||
:::tip
|
||||
|
||||
@@ -43,5 +44,5 @@ If you're unsure how to create a stack, checkout the [Portainer Documentation](h
|
||||
|
||||
To update Homarr using Docker Compose or Portainer Stacks, simply pull the latest image and restart the container.
|
||||
|
||||
1. Run ``docker pull ghcr.io/homarr-labs/homarr:latest`` to pull the latest image.
|
||||
2. Re-run ``docker-compose up -d`` or re-deploy the stack in Portainer.
|
||||
1. Run `docker pull ghcr.io/homarr-labs/homarr:latest` to pull the latest image.
|
||||
2. Re-run `docker-compose up -d` or re-deploy the stack in Portainer.
|
||||
@@ -31,7 +31,7 @@ After you've installed Homarr, you can find a new LXC "homarr".
|
||||
|
||||
# Updating
|
||||
|
||||
Updating is as simple as executing
|
||||
Updating is as simple as executing
|
||||
|
||||
```bash
|
||||
update
|
||||
@@ -49,17 +49,19 @@ Some Homarr features require setting environment variables. When installing Homa
|
||||
|
||||
# Troubleshooting
|
||||
|
||||
If something does not work after an update, please first try rebooting the LXC or the homarr Service with
|
||||
If something does not work after an update, please first try rebooting the LXC or the homarr Service with
|
||||
|
||||
```bash
|
||||
systemctl restart homarr
|
||||
```
|
||||
|
||||
# Getting help
|
||||
|
||||
- If you are looking for help with the script, updating the LXC or troubleshooting the installer, please get in contact with the support of the community scripts. They provide support on their [Discord](https://discord.gg/2wvnMDgdnU). If you found vulnerabilities, bugs or have a request for the script, [create a new issue here](https://github.com/community-scripts/ProxmoxVE/issues/new/choose).
|
||||
- If you are looking to report a bug, vulnerability or request a new feature, use https://github.com/homarr-labs/homarr . You can ask for help on our Discord.
|
||||
|
||||
# How to find logs
|
||||
|
||||
Homarr runs under a system service called `homarr`, so getting the logs is a simple as using the following command inside the LXC (Container) Shell
|
||||
|
||||
```bash
|
||||
|
||||
@@ -4,6 +4,6 @@
|
||||
QNAP installation guide has not updated to 1.0 yet. There guide is still for 0.15.10.
|
||||
:::
|
||||
|
||||
When installing on QNAP, we recommend you to install Homarr using [Container Station]( https://www.qnap.com/en/software/container-station) or [this third-party image](https://www.myqnap.org/product/homarr/).
|
||||
When installing on QNAP, we recommend you to install Homarr using [Container Station](https://www.qnap.com/en/software/container-station) or [this third-party image](https://www.myqnap.org/product/homarr/).
|
||||
|
||||
There is also a guide available, but note that it is written in Chinese: https://post.smzdm.com/p/awzm7op2/
|
||||
@@ -1,4 +1,4 @@
|
||||
# Runtipi
|
||||
# Runtipi
|
||||
|
||||
Runtipi is a self-hosted orchestrator that makes it easy to manage and run multiple services on a single server. It is based on Docker and comes with a simple web interface to manage your services. Runtipi is designed to be easy to use, so you don't have to worry about manual configuration or networking.
|
||||
|
||||
@@ -11,6 +11,7 @@ https://runtipi.io
|
||||
# Installation
|
||||
|
||||
Installing Homarr is pretty untuitive, the app is available in the default appstore :
|
||||
|
||||
1. Search for **"Homarr"** in the App-Store page and select it
|
||||
2. Click on **"Install"**.
|
||||
3. Choose the method you want to access Homarr, through any port or domain name then select **"Install"**
|
||||
@@ -18,13 +19,15 @@ Installing Homarr is pretty untuitive, the app is available in the default appst
|
||||
# Maintenance
|
||||
|
||||
After installation, all you need is available from the WebUI in **"My apps"** > **"Homarr"** :
|
||||
- Start/Stop
|
||||
- Update
|
||||
- Backup
|
||||
- Logs
|
||||
|
||||
|
||||
- Start/Stop
|
||||
- Update
|
||||
- Backup
|
||||
- Logs
|
||||
|
||||
If you like to tinker with your apps, you can also edit your app's docker-compose with a [user-config](https://runtipi.io/docs/guides/customize-app-config).
|
||||
|
||||
# Getting help
|
||||
|
||||
- If you are looking for help with anything in Runtipi. They provide support on their [Discord](https://discord.gg/Bu9qEPnHsc) and [forums](https://forums.runtipi.io/). If you found vulnerabilities or bugs, [create a new issue here](https://github.com/runtipi/runtipi-appstore/issues).
|
||||
- If you are looking to report a bug, vulnerability or request a new feature for Homarr, use https://github.com/homarr-labs/homarr . You can ask for help on our Discord.
|
||||
- If you are looking to report a bug, vulnerability or request a new feature for Homarr, use https://github.com/homarr-labs/homarr . You can ask for help on our Discord.
|
||||
@@ -5,6 +5,7 @@ Saltbox installation command has not updated to 1.0 yet. There command still run
|
||||
:::
|
||||
|
||||
You can also use this to install on Saltbox:
|
||||
|
||||
```bash
|
||||
sb install sandbox-homarr
|
||||
```
|
||||
```
|
||||
@@ -28,15 +28,29 @@ This method is only useful, when you want to develop on Homarr or extend it with
|
||||
## Steps
|
||||
|
||||
<ol>
|
||||
<li>Clone the Repository using <code>git clone https://github.com/homarr-labs/homarr.git</code></li>
|
||||
<li>Enter the created directory using <code>cd homarr</code></li>
|
||||
<li>Install all dependencies using <code>pnpm install</code></li>
|
||||
<li>Copy the `.env.example` file to `.env` and replace `DB_URL` with the full path you want to use for the sqlite database file. For example `DB_URL='/home/username/homarr/db.sqlite`</li>
|
||||
<li>Replace `AUTH_SECRET` and `SECRET_ENCRYPTION_KEY` both with a secure random string by using `openssl rand -hex 32`</li>
|
||||
<li>
|
||||
Clone the Repository using <code>git clone https://github.com/homarr-labs/homarr.git</code>
|
||||
</li>
|
||||
<li>
|
||||
Enter the created directory using <code>cd homarr</code>
|
||||
</li>
|
||||
<li>
|
||||
Install all dependencies using <code>pnpm install</code>
|
||||
</li>
|
||||
<li>
|
||||
Copy the `.env.example` file to `.env` and replace `DB_URL` with the full path you want to use for the sqlite
|
||||
database file. For example `DB_URL='/home/username/homarr/db.sqlite`
|
||||
</li>
|
||||
<li>
|
||||
Replace `AUTH_SECRET` and `SECRET_ENCRYPTION_KEY` both with a secure random string by using `openssl rand -hex 32`
|
||||
</li>
|
||||
<li>Replace `CRON_JOB_API_KEY` with a secure random string by using `openssl rand -base64 32`</li>
|
||||
<li>Run `pnpm run db:migration:sqlite:run` and wait that it completes</li>
|
||||
<li>Build the source using `pnpm build`</li>
|
||||
<li>Copy better-sqlite3.node files with `mkdir build` and `cp ./node_modules/better-sqlite3/build/Release/better_sqlite3.node ./build/better_sqlite3.node`</li>
|
||||
<li>
|
||||
Copy better-sqlite3.node files with `mkdir build` and `cp
|
||||
./node_modules/better-sqlite3/build/Release/better_sqlite3.node ./build/better_sqlite3.node`
|
||||
</li>
|
||||
<li>Start a redis server using `redis-server`</li>
|
||||
<li>Run the server using `pnpm start`</li>
|
||||
<li>Open `http://localhost:3000` in your browser</li>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import Link from '@docusaurus/Link';
|
||||
import { IconDownload } from '@tabler/icons-react';
|
||||
import Link from "@docusaurus/Link";
|
||||
import { IconDownload } from "@tabler/icons-react";
|
||||
|
||||
# Unraid
|
||||
|
||||
@@ -14,17 +14,17 @@ You can install Homarr directly from your Unraid Dashboard, no terminal required
|
||||
|
||||
<Link
|
||||
data-attr="Install Unraid Community Apps"
|
||||
className={'button button--secondary button--lg rounded-3xl dark:border-zinc-600 dark:bg-zinc-800'}
|
||||
className={"button button--secondary button--lg rounded-3xl dark:border-zinc-600 dark:bg-zinc-800"}
|
||||
to="https://forums.unraid.net/topic/38582-plug-in-community-applications/"
|
||||
>
|
||||
<div className={'flex items-center gap-3'}>
|
||||
<div className={"flex items-center gap-3"}>
|
||||
<span className={"dark:text-gray-200"}>Install community applications plugin</span>
|
||||
<IconDownload className={"dark:text-gray-200"} size={20} />
|
||||
</div>
|
||||
</Link>
|
||||
|
||||
<br/>
|
||||
<br/>
|
||||
<br />
|
||||
<br />
|
||||
|
||||
:::tip
|
||||
|
||||
@@ -46,4 +46,3 @@ After you've installed the Community Apps Plugin, you can install Homarr from th
|
||||
After you've installed Homarr, you can find it under the tab "Docker".
|
||||
|
||||
The official support thread is located [here](https://forums.unraid.net/topic/123478-support-smartphonelover-homarr/), but we prefer to communicate over GitHub and Discord instead.
|
||||
|
||||
@@ -1,37 +1,44 @@
|
||||
---
|
||||
title: 'AdGuard Home'
|
||||
title: "AdGuard Home"
|
||||
description: "AdGuard Home is a network-wide software for blocking ads and trackers, providing a safer and faster internet experience."
|
||||
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 { 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 { IconAd, IconDeviceGamepad } from "@tabler/icons-react";
|
||||
import { adguardHomeIntegration } from '.';
|
||||
import { dnsHoleSummaryWidget } from '@site/docs/widgets/dns-hole-summary';
|
||||
import { dnsHoleControlsWidget } from '@site/docs/widgets/dns-hole-controls';
|
||||
import { adguardHomeIntegration } from ".";
|
||||
import { dnsHoleSummaryWidget } from "@site/docs/widgets/dns-hole-summary";
|
||||
import { dnsHoleControlsWidget } from "@site/docs/widgets/dns-hole-controls";
|
||||
|
||||
<IntegrationHeader
|
||||
integration={adguardHomeIntegration}
|
||||
categories={['DNS Hole']}
|
||||
/>
|
||||
<IntegrationHeader integration={adguardHomeIntegration} categories={["DNS Hole"]} />
|
||||
|
||||
### Widgets & Capabilities
|
||||
|
||||
<IntegrationCapabilites
|
||||
items={[{
|
||||
widget: dnsHoleSummaryWidget,
|
||||
},{
|
||||
widget: dnsHoleControlsWidget
|
||||
}]}
|
||||
items={[
|
||||
{
|
||||
widget: dnsHoleSummaryWidget,
|
||||
},
|
||||
{
|
||||
widget: dnsHoleControlsWidget,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
### Adding the integration
|
||||
|
||||
<AddingIntegration />
|
||||
|
||||
### Secrets
|
||||
<IntegrationSecrets secrets={[{
|
||||
credentials: ['username', 'password'],
|
||||
steps: []
|
||||
}]} />
|
||||
|
||||
<IntegrationSecrets
|
||||
secrets={[
|
||||
{
|
||||
credentials: ["username", "password"],
|
||||
steps: [],
|
||||
},
|
||||
]}
|
||||
/>
|
||||
@@ -1,40 +1,45 @@
|
||||
---
|
||||
title: 'Anchor'
|
||||
title: "Anchor"
|
||||
description: "Anchor is a self-hosted note-taking application."
|
||||
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 { anchorIntegration } from '.';
|
||||
import { anchorNoteWidget } from '@site/docs/widgets/anchor-note';
|
||||
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 { anchorIntegration } from ".";
|
||||
import { anchorNoteWidget } from "@site/docs/widgets/anchor-note";
|
||||
|
||||
|
||||
<IntegrationHeader
|
||||
integration={anchorIntegration}
|
||||
categories={['Notes']}
|
||||
/>
|
||||
<IntegrationHeader integration={anchorIntegration} categories={["Notes"]} />
|
||||
|
||||
The Anchor integration lets Homarr connect to your Anchor instance so you can display and edit selected notes directly from your dashboard.
|
||||
|
||||
### Widgets & Capabilities
|
||||
|
||||
<IntegrationCapabilites
|
||||
items={[{
|
||||
widget: anchorNoteWidget
|
||||
}]}
|
||||
items={[
|
||||
{
|
||||
widget: anchorNoteWidget,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
### Adding the integration
|
||||
|
||||
<AddingIntegration />
|
||||
|
||||
### Secrets
|
||||
<IntegrationSecrets secrets={[{
|
||||
credentials: ['apiKey'],
|
||||
steps: [
|
||||
'Open your Anchor instance.',
|
||||
'Generate or copy an API key from Anchor settings.',
|
||||
'Paste that key into the API Key secret field in Homarr.'
|
||||
]
|
||||
}]} />
|
||||
|
||||
<IntegrationSecrets
|
||||
secrets={[
|
||||
{
|
||||
credentials: ["apiKey"],
|
||||
steps: [
|
||||
"Open your Anchor instance.",
|
||||
"Generate or copy an API key from Anchor settings.",
|
||||
"Paste that key into the API Key secret field in Homarr.",
|
||||
],
|
||||
},
|
||||
]}
|
||||
/>
|
||||
@@ -1,38 +1,43 @@
|
||||
---
|
||||
title: 'Aria2'
|
||||
title: "Aria2"
|
||||
description: "Lightweight multiprotocol & multi-source command-line download utility. It supports HTTP/HTTPS, FTP, SFTP, BitTorrent and Metalink"
|
||||
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 { aria2Integration } from '.';
|
||||
import { downloadsWidget } from '@site/docs/widgets/downloads';
|
||||
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 { aria2Integration } from ".";
|
||||
import { downloadsWidget } from "@site/docs/widgets/downloads";
|
||||
|
||||
<IntegrationHeader
|
||||
integration={aria2Integration}
|
||||
categories={['Download', 'Multiprotocol', 'Multi-source']}
|
||||
/>
|
||||
<IntegrationHeader integration={aria2Integration} categories={["Download", "Multiprotocol", "Multi-source"]} />
|
||||
|
||||
### Widgets & Capabilities
|
||||
|
||||
<IntegrationCapabilites
|
||||
items={[{
|
||||
widget: downloadsWidget,
|
||||
}]}
|
||||
items={[
|
||||
{
|
||||
widget: downloadsWidget,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
### Adding the integration
|
||||
|
||||
<AddingIntegration />
|
||||
|
||||
### Secrets
|
||||
<IntegrationSecrets secrets={[
|
||||
{
|
||||
credentials: ['apiKey'],
|
||||
steps: []
|
||||
},
|
||||
{
|
||||
credentials: [],
|
||||
steps: []
|
||||
}]} />
|
||||
|
||||
<IntegrationSecrets
|
||||
secrets={[
|
||||
{
|
||||
credentials: ["apiKey"],
|
||||
steps: [],
|
||||
},
|
||||
{
|
||||
credentials: [],
|
||||
steps: [],
|
||||
},
|
||||
]}
|
||||
/>
|
||||
@@ -1,41 +1,40 @@
|
||||
---
|
||||
title: 'Audiobookshelf'
|
||||
title: "Audiobookshelf"
|
||||
description: "Audiobookshelf is a self-hosted audiobook and podcast server that lets you manage and stream your audio library."
|
||||
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 { audiobookshelfIntegration } from '.';
|
||||
import { audioStatsWidget } from '@site/docs/widgets/audio-stats';
|
||||
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 { audiobookshelfIntegration } from ".";
|
||||
import { audioStatsWidget } from "@site/docs/widgets/audio-stats";
|
||||
|
||||
|
||||
<IntegrationHeader
|
||||
integration={audiobookshelfIntegration}
|
||||
categories={['Media']}
|
||||
/>
|
||||
<IntegrationHeader integration={audiobookshelfIntegration} categories={["Media"]} />
|
||||
|
||||
Audiobookshelf provides library statistics including audiobook and podcast counts, listening time, and active sessions.
|
||||
|
||||
### Widgets & Capabilities
|
||||
<IntegrationCapabilites
|
||||
items={[
|
||||
{ widget: audioStatsWidget },
|
||||
]}
|
||||
/>
|
||||
|
||||
<IntegrationCapabilites items={[{ widget: audioStatsWidget }]} />
|
||||
|
||||
### Adding the integration
|
||||
|
||||
<AddingIntegration />
|
||||
|
||||
### Secrets
|
||||
<IntegrationSecrets secrets={[{
|
||||
credentials: ['apiKey'],
|
||||
steps: [
|
||||
'Open your Audiobookshelf instance',
|
||||
'Go to Settings → Users',
|
||||
'Click on your user account',
|
||||
'Copy the API token from the user details',
|
||||
]
|
||||
}]} />
|
||||
|
||||
<IntegrationSecrets
|
||||
secrets={[
|
||||
{
|
||||
credentials: ["apiKey"],
|
||||
steps: [
|
||||
"Open your Audiobookshelf instance",
|
||||
"Go to Settings → Users",
|
||||
"Click on your user account",
|
||||
"Copy the API token from the user details",
|
||||
],
|
||||
},
|
||||
]}
|
||||
/>
|
||||
@@ -1,31 +1,30 @@
|
||||
---
|
||||
title: 'Bazarr'
|
||||
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';
|
||||
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']}
|
||||
/>
|
||||
<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';
|
||||
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>
|
||||
<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>
|
||||
@@ -35,20 +34,25 @@ import widgetOverview from '@site/docs/widgets/bazarr/img/widget-overview.png';
|
||||
</div>
|
||||
|
||||
### Widgets & Capabilities
|
||||
<IntegrationCapabilites
|
||||
items={[{ widget: bazarrWidget }]}
|
||||
/>
|
||||
|
||||
<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.",
|
||||
],
|
||||
}]} />
|
||||
<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,25 +1,22 @@
|
||||
---
|
||||
title: 'Beszel'
|
||||
title: "Beszel"
|
||||
description: "Beszel is a lightweight server monitoring platform with Docker stats, historical data, and alert functions."
|
||||
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 { beszelIntegration } from '.';
|
||||
import { beszelSystemStatsWidget } from '@site/docs/widgets/beszel-system-stats';
|
||||
import { beszelSystemGridWidget } from '@site/docs/widgets/beszel-system-grid';
|
||||
import { beszelSystemTableWidget } from '@site/docs/widgets/beszel-system-table';
|
||||
import { beszelAlertsWidget } from '@site/docs/widgets/beszel-alerts';
|
||||
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 { beszelIntegration } from ".";
|
||||
import { beszelSystemStatsWidget } from "@site/docs/widgets/beszel-system-stats";
|
||||
import { beszelSystemGridWidget } from "@site/docs/widgets/beszel-system-grid";
|
||||
import { beszelSystemTableWidget } from "@site/docs/widgets/beszel-system-table";
|
||||
import { beszelAlertsWidget } from "@site/docs/widgets/beszel-alerts";
|
||||
|
||||
import beszelDashboard from './img/beszel-dashboard.png';
|
||||
import beszelDashboard from "./img/beszel-dashboard.png";
|
||||
|
||||
<IntegrationHeader
|
||||
integration={beszelIntegration}
|
||||
categories={['System Monitoring']}
|
||||
/>
|
||||
<IntegrationHeader integration={beszelIntegration} categories={["System Monitoring"]} />
|
||||
|
||||
Beszel monitors your servers in real time via lightweight agents, collecting CPU, memory, disk, network, GPU, temperature, and Docker container metrics. It stores historical data and supports configurable alerts.
|
||||
|
||||
@@ -28,26 +25,33 @@ Beszel monitors your servers in real time via lightweight agents, collecting CPU
|
||||
<img src={beszelDashboard} alt="Beszel dashboard overview showing all four widgets" />
|
||||
|
||||
### Widgets & Capabilities
|
||||
|
||||
<IntegrationCapabilites
|
||||
items={[
|
||||
{ widget: beszelSystemStatsWidget },
|
||||
{ widget: beszelSystemGridWidget },
|
||||
{ widget: beszelSystemTableWidget },
|
||||
{ widget: beszelAlertsWidget },
|
||||
]}
|
||||
items={[
|
||||
{ widget: beszelSystemStatsWidget },
|
||||
{ widget: beszelSystemGridWidget },
|
||||
{ widget: beszelSystemTableWidget },
|
||||
{ widget: beszelAlertsWidget },
|
||||
]}
|
||||
/>
|
||||
|
||||
### Adding the integration
|
||||
|
||||
<AddingIntegration />
|
||||
|
||||
### Secrets
|
||||
<IntegrationSecrets secrets={[{
|
||||
credentials: ['username', 'password'],
|
||||
steps: [
|
||||
'Open your Beszel hub instance (default port 8090)',
|
||||
'Use the same credentials you use to log in to the Beszel web UI',
|
||||
]
|
||||
}]} />
|
||||
|
||||
<IntegrationSecrets
|
||||
secrets={[
|
||||
{
|
||||
credentials: ["username", "password"],
|
||||
steps: [
|
||||
"Open your Beszel hub instance (default port 8090)",
|
||||
"Use the same credentials you use to log in to the Beszel web UI",
|
||||
],
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
:::tip
|
||||
Beszel uses PocketBase under the hood. The default admin credentials are set during the initial Beszel setup.
|
||||
|
||||
@@ -1,29 +1,24 @@
|
||||
---
|
||||
title: 'Coolify'
|
||||
description: 'Self-hosted PaaS and deployment platform'
|
||||
title: "Coolify"
|
||||
description: "Self-hosted PaaS and deployment platform"
|
||||
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 { coolifyIntegration } from '.';
|
||||
import { coolifyWidget } from '../../widgets/coolify';
|
||||
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 { coolifyIntegration } from ".";
|
||||
import { coolifyWidget } from "../../widgets/coolify";
|
||||
|
||||
<IntegrationHeader
|
||||
integration={coolifyIntegration}
|
||||
categories={['System monitoring', 'Deployment']}
|
||||
/>
|
||||
<IntegrationHeader integration={coolifyIntegration} categories={["System monitoring", "Deployment"]} />
|
||||
|
||||
### Widgets & Capabilities
|
||||
<IntegrationCapabilites
|
||||
items={[
|
||||
{ widget: coolifyWidget }
|
||||
]}
|
||||
/>
|
||||
|
||||
<IntegrationCapabilites items={[{ widget: coolifyWidget }]} />
|
||||
|
||||
### Adding the integration
|
||||
|
||||
<AddingIntegration />
|
||||
|
||||
:::info URL configuration
|
||||
@@ -31,16 +26,28 @@ Use the root URL of your Coolify instance, for example `https://coolify.example.
|
||||
:::
|
||||
|
||||
### Secrets
|
||||
<IntegrationSecrets secrets={[{
|
||||
credentials: ['apiKey'],
|
||||
steps: [
|
||||
'Open your Coolify instance and make sure you are in the team you want Homarr to monitor.',
|
||||
(<span>Enable API access under <strong>Settings > Advanced</strong> if it is disabled.</span>),
|
||||
(<span>Go to <strong>Security > API Tokens</strong>.</span>),
|
||||
'Enter a name, e.g. "Homarr".',
|
||||
(<span>Select the <code>read</code> permission. The Coolify widget only reads version, server, project, application, and service data.</span>),
|
||||
'Click "Create".',
|
||||
'Copy the generated token immediately. Coolify only shows it once.',
|
||||
'Paste the token into the API Key field in Homarr.'
|
||||
]
|
||||
}]} />
|
||||
|
||||
<IntegrationSecrets
|
||||
secrets={[
|
||||
{
|
||||
credentials: ["apiKey"],
|
||||
steps: [
|
||||
"Open your Coolify instance and make sure you are in the team you want Homarr to monitor.",
|
||||
<span>
|
||||
Enable API access under <strong>Settings > Advanced</strong> if it is disabled.
|
||||
</span>,
|
||||
<span>
|
||||
Go to <strong>Security > API Tokens</strong>.
|
||||
</span>,
|
||||
'Enter a name, e.g. "Homarr".',
|
||||
<span>
|
||||
Select the <code>read</code> permission. The Coolify widget only reads version, server, project, application,
|
||||
and service data.
|
||||
</span>,
|
||||
'Click "Create".',
|
||||
"Copy the generated token immediately. Coolify only shows it once.",
|
||||
"Paste the token into the API Key field in Homarr.",
|
||||
],
|
||||
},
|
||||
]}
|
||||
/>
|
||||
@@ -1,32 +1,33 @@
|
||||
---
|
||||
title: 'Dash.'
|
||||
title: "Dash."
|
||||
description: "Dash. is a system performance and resource monitoring tool"
|
||||
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 { 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 { IconHeartRateMonitor } from "@tabler/icons-react";
|
||||
import { dashDotIntegration } from '.';
|
||||
import { healthMonitoringWidget } from '@site/docs/widgets/health-monitoring';
|
||||
import { systemResourcesWidget } from '@site/docs/widgets/system-resources';
|
||||
import { dashDotIntegration } from ".";
|
||||
import { healthMonitoringWidget } from "@site/docs/widgets/health-monitoring";
|
||||
import { systemResourcesWidget } from "@site/docs/widgets/system-resources";
|
||||
|
||||
|
||||
<IntegrationHeader
|
||||
integration={dashDotIntegration}
|
||||
categories={['System monitoring']}
|
||||
/>
|
||||
<IntegrationHeader integration={dashDotIntegration} categories={["System monitoring"]} />
|
||||
|
||||
### Widgets & Capabilities
|
||||
|
||||
<IntegrationCapabilites
|
||||
items={[{
|
||||
widget: healthMonitoringWidget
|
||||
}, {
|
||||
widget: systemResourcesWidget
|
||||
}]}
|
||||
items={[
|
||||
{
|
||||
widget: healthMonitoringWidget,
|
||||
},
|
||||
{
|
||||
widget: systemResourcesWidget,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
### Adding the integration
|
||||
<AddingIntegration />
|
||||
|
||||
<AddingIntegration />
|
||||
@@ -1,36 +1,41 @@
|
||||
---
|
||||
title: 'Deluge'
|
||||
title: "Deluge"
|
||||
description: "Deluge is a lightweight, Free Software, cross-platform BitTorrent client."
|
||||
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 { 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 { IconDownload } from "@tabler/icons-react";
|
||||
import Alert from '@theme/Admonition';
|
||||
import { delugeIntegration } from '.';
|
||||
import { downloadsWidget } from '@site/docs/widgets/downloads';
|
||||
import Alert from "@theme/Admonition";
|
||||
import { delugeIntegration } from ".";
|
||||
import { downloadsWidget } from "@site/docs/widgets/downloads";
|
||||
|
||||
|
||||
<IntegrationHeader
|
||||
integration={delugeIntegration}
|
||||
categories={['Torrent client']}
|
||||
/>
|
||||
<IntegrationHeader integration={delugeIntegration} categories={["Torrent client"]} />
|
||||
|
||||
### Widgets & Capabilities
|
||||
|
||||
<IntegrationCapabilites
|
||||
items={[{
|
||||
widget: downloadsWidget
|
||||
}]}
|
||||
items={[
|
||||
{
|
||||
widget: downloadsWidget,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
### Adding the integration
|
||||
|
||||
<AddingIntegration />
|
||||
|
||||
### Secrets
|
||||
<IntegrationSecrets secrets={[{
|
||||
credentials: ['password'],
|
||||
steps: [],
|
||||
}]} />
|
||||
|
||||
<IntegrationSecrets
|
||||
secrets={[
|
||||
{
|
||||
credentials: ["password"],
|
||||
steps: [],
|
||||
},
|
||||
]}
|
||||
/>
|
||||
@@ -1,32 +1,31 @@
|
||||
---
|
||||
title: 'Docker'
|
||||
title: "Docker"
|
||||
description: "Docker is a platform where you can run your applications in containers, allowing for easy deployment and management of applications."
|
||||
hide_title: true
|
||||
---
|
||||
|
||||
import { IntegrationHeader } from '@site/src/components/integrations/header';
|
||||
import { IntegrationCapabilites } from '@site/src/components/integrations/widgets';
|
||||
import {IntegrationSecrets} from '@site/src/components/integrations/secrets';
|
||||
import { IntegrationHeader } from "@site/src/components/integrations/header";
|
||||
import { IntegrationCapabilites } from "@site/src/components/integrations/widgets";
|
||||
import { IntegrationSecrets } from "@site/src/components/integrations/secrets";
|
||||
import { IconBrandDocker, IconTool } from "@tabler/icons-react";
|
||||
import { dockerIntegration } from '.';
|
||||
import { dockerContainersWidget } from '@site/docs/widgets/docker-containers';
|
||||
import { dockerIntegration } from ".";
|
||||
import { dockerContainersWidget } from "@site/docs/widgets/docker-containers";
|
||||
|
||||
|
||||
<IntegrationHeader
|
||||
integration={dockerIntegration}
|
||||
categories={['Containers']}
|
||||
/>
|
||||
<IntegrationHeader integration={dockerIntegration} categories={["Containers"]} />
|
||||
|
||||
### Widgets & Capabilities
|
||||
|
||||
<IntegrationCapabilites
|
||||
items={[{
|
||||
items={[
|
||||
{
|
||||
widget: dockerContainersWidget,
|
||||
note: "This widget can only be used with administrator privileges"
|
||||
}]}
|
||||
note: "This widget can only be used with administrator privileges",
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
|
||||
## Enabling the Docker integration
|
||||
|
||||
This integration must first be enabled.
|
||||
If you directly run on the host system and you have docker installed, it will be enabled by default.
|
||||
Otherwise, you'll need to mount the [docker socket](https://docs.docker.com/engine/security/protect-access/) to the Homarr container,
|
||||
@@ -49,83 +48,17 @@ services:
|
||||
environment:
|
||||
- SECRET_ENCRYPTION_KEY=your_64_character_hex_string # <--- can be generated with `openssl rand -hex 32`
|
||||
ports:
|
||||
- '7575:7575'
|
||||
- "7575:7575"
|
||||
```
|
||||
|
||||
With docker run, add this parameter: `-v /var/run/docker.sock:/var/run/docker.sock`.
|
||||
In Windows, you'll need to adjust the slashes: `-v //var/run/docker.sock:/var/run/docker.sock`
|
||||
|
||||
### Using Podman
|
||||
|
||||
On Linux, Homarr can connect to Podman's [Docker-compatible API](https://docs.podman.io/en/latest/markdown/podman-system-service.1.html).
|
||||
The Podman API socket must be running and mounted into the Homarr container.
|
||||
The socket grants full access to Podman, including arbitrary code execution as the user running the API, so only mount it into trusted containers.
|
||||
|
||||
For rootless Podman, enable the socket for the user that runs Homarr:
|
||||
|
||||
```bash
|
||||
systemctl --user enable --now podman.socket
|
||||
```
|
||||
|
||||
The rootless socket is located at `$XDG_RUNTIME_DIR/podman/podman.sock` (usually `/run/user/<uid>/podman/podman.sock`).
|
||||
You can confirm the path with:
|
||||
|
||||
```bash
|
||||
podman info --format '{{.Host.RemoteSocket.Path}}'
|
||||
```
|
||||
|
||||
Mount that socket into the Homarr container and point `DOCKER_SOCKET_PATHS` to its path **inside** the container:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
homarr:
|
||||
container_name: homarr
|
||||
image: ghcr.io/homarr-labs/homarr:latest
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- ${XDG_RUNTIME_DIR}/podman/podman.sock:/var/run/podman.sock
|
||||
- ./homarr/appdata:/appdata
|
||||
environment:
|
||||
SECRET_ENCRYPTION_KEY: your_64_character_hex_string
|
||||
DOCKER_SOCKET_PATHS: /var/run/podman.sock
|
||||
ports:
|
||||
- "7575:7575"
|
||||
```
|
||||
|
||||
Start Homarr with `podman compose up -d`.
|
||||
Run the compose project as the same user that owns the rootless Podman socket so `$XDG_RUNTIME_DIR` resolves to the correct path.
|
||||
To keep the user socket available after logout and across reboots, enable lingering with `loginctl enable-linger <user>`.
|
||||
|
||||
For rootful Podman, enable the system socket with `sudo systemctl enable --now podman.socket`.
|
||||
Its default path is `/run/podman/podman.sock`; mount that path instead and start Homarr with `sudo podman compose up -d`.
|
||||
|
||||
:::note SELinux
|
||||
On systems with SELinux enabled, Podman's documentation recommends disabling label separation for a container that accesses the API socket.
|
||||
Add the following to the Homarr service:
|
||||
|
||||
```yaml
|
||||
security_opt:
|
||||
- label=disable
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
You can connect Homarr to multiple Docker-compatible sockets by mounting each socket and listing their in-container paths, separated by commas:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
homarr:
|
||||
image: ghcr.io/homarr-labs/homarr:latest
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
- ${XDG_RUNTIME_DIR}/podman/podman.sock:/var/run/podman.sock
|
||||
environment:
|
||||
DOCKER_SOCKET_PATHS: /var/run/docker.sock,/var/run/podman.sock
|
||||
```
|
||||
|
||||
### Managing your Docker containers within Homarr
|
||||
|
||||
Homarr allows you to interact with Docker containers running on your system.
|
||||
Each container row has an action menu with the following options:
|
||||
|
||||
- **View logs** - opens a dedicated logs page with a live terminal
|
||||
- **Start** - start a stopped container
|
||||
- **Stop** - stop a running container
|
||||
@@ -166,11 +99,13 @@ Currently Homarr supports one label for configuration of the containers:
|
||||
- **homarr.hide:** If set to any value, the container will be hidden from the Docker Containers widget and tools page.
|
||||
|
||||
### Security
|
||||
|
||||
Mounting docker sockets can be risky, as they permit full control over your docker service.
|
||||
As an example, a thread actor could abuse Homarr use the socket to start, stop or delete containers on your system.
|
||||
|
||||
Therefore we recommend the usage of a socket proxy, which can prohibit certain actions.
|
||||
A few examples include:
|
||||
|
||||
- https://github.com/linuxserver/docker-socket-proxy
|
||||
- https://github.com/Tecnativa/docker-socket-proxy
|
||||
|
||||
@@ -178,36 +113,44 @@ See documentation of the respective proxies on how to configure them.
|
||||
Homarr may behave in unexpected ways when you use proxies.
|
||||
|
||||
#### Permissions
|
||||
|
||||
Homarr needs the following permissions from the Docker API:
|
||||
|
||||
- Containers/Start
|
||||
- Containers/Stop
|
||||
- Containers/Restart
|
||||
- Containers/Remove
|
||||
|
||||
For socket proxies, you will need these permissions:
|
||||
|
||||
- `CONTAINERS=1`
|
||||
- `POST=1`
|
||||
|
||||
**Caution:** `POST` access is security critical as it provides extensive capabilities to modify your docker environment. Please leave it disabled if you're concerned about this.
|
||||
|
||||
As a workaround, you can use [LSIO's socket proxy](https://github.com/linuxserver/docker-socket-proxy) and set the following:
|
||||
|
||||
- ALLOW_START=1
|
||||
- ALLOW_STOP=1
|
||||
- ALLOW_RESTARTS=1
|
||||
|
||||
These will work even with `POST=0`.
|
||||
These will work even with `POST=0`.
|
||||
|
||||
You lose the ability to remove containers, but start, stop and restarts should work just fine.
|
||||
|
||||
#### Connecting to Docker via Socket Proxies
|
||||
|
||||
To connect to Docker via a socket proxy, you'll need to add these two environment variables in the compose file:
|
||||
|
||||
- `DOCKER_HOSTNAMES=<name of the socket proxy container>`
|
||||
- `DOCKER_PORTS=<socket proxy port, usually 2375>`
|
||||
|
||||
Refer: https://homarr.dev/docs/advanced/environment-variables/
|
||||
|
||||
You will also need to add Homarr to the network of your socket proxy. You can do it like this:
|
||||
|
||||
1. Add the network to your compose file (with appropriate changes):
|
||||
|
||||
```yaml
|
||||
networks:
|
||||
socket-proxy:
|
||||
@@ -216,6 +159,7 @@ networks:
|
||||
```
|
||||
|
||||
2. Add the network to the homarr service in the compose file:
|
||||
|
||||
```yaml
|
||||
networks:
|
||||
- socket-proxy
|
||||
|
||||
@@ -1,37 +1,35 @@
|
||||
---
|
||||
title: 'Emby'
|
||||
title: "Emby"
|
||||
description: "Emby is a media server platform that allows you to organize, manage, and stream your personal media collection."
|
||||
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 { 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 { IconVideo, IconTicket } from "@tabler/icons-react";
|
||||
import { embyIntegration } from '.';
|
||||
import { mediaServerWidget } from '@site/docs/widgets/media-server';
|
||||
import { mediaReleasesWidget } from '@site/docs/widgets/media-releases';
|
||||
import { embyIntegration } from ".";
|
||||
import { mediaServerWidget } from "@site/docs/widgets/media-server";
|
||||
import { mediaReleasesWidget } from "@site/docs/widgets/media-releases";
|
||||
|
||||
|
||||
<IntegrationHeader
|
||||
integration={embyIntegration}
|
||||
categories={['Media server']}
|
||||
/>
|
||||
<IntegrationHeader integration={embyIntegration} categories={["Media server"]} />
|
||||
|
||||
### Widgets & Capabilities
|
||||
<IntegrationCapabilites
|
||||
items={[
|
||||
{ widget: mediaServerWidget },
|
||||
{ widget: mediaReleasesWidget }
|
||||
]}
|
||||
/>
|
||||
|
||||
<IntegrationCapabilites items={[{ widget: mediaServerWidget }, { widget: mediaReleasesWidget }]} />
|
||||
|
||||
### Adding the integration
|
||||
|
||||
<AddingIntegration />
|
||||
|
||||
### Secrets
|
||||
<IntegrationSecrets secrets={[{
|
||||
credentials: ['apiKey'],
|
||||
steps: []
|
||||
}]} />
|
||||
|
||||
<IntegrationSecrets
|
||||
secrets={[
|
||||
{
|
||||
credentials: ["apiKey"],
|
||||
steps: [],
|
||||
},
|
||||
]}
|
||||
/>
|
||||
@@ -1,37 +1,43 @@
|
||||
---
|
||||
title: 'Glances'
|
||||
title: "Glances"
|
||||
description: "Glances is a cross-platform system monitoring tool."
|
||||
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 { glancesIntegration } from '.';
|
||||
import { systemResourcesWidget } from '@site/docs/widgets/system-resources';
|
||||
import { healthMonitoringWidget } from '@site/docs/widgets/health-monitoring';
|
||||
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 { glancesIntegration } from ".";
|
||||
import { systemResourcesWidget } from "@site/docs/widgets/system-resources";
|
||||
import { healthMonitoringWidget } from "@site/docs/widgets/health-monitoring";
|
||||
|
||||
|
||||
<IntegrationHeader
|
||||
integration={glancesIntegration}
|
||||
categories={['System Monitoring']}
|
||||
/>
|
||||
<IntegrationHeader integration={glancesIntegration} categories={["System Monitoring"]} />
|
||||
|
||||
### Widgets & Capabilities
|
||||
|
||||
<IntegrationCapabilites
|
||||
items={[{
|
||||
widget: systemResourcesWidget
|
||||
}, {
|
||||
widget: healthMonitoringWidget
|
||||
}]}
|
||||
items={[
|
||||
{
|
||||
widget: systemResourcesWidget,
|
||||
},
|
||||
{
|
||||
widget: healthMonitoringWidget,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
### Adding the integration
|
||||
|
||||
<AddingIntegration />
|
||||
|
||||
### Secrets
|
||||
<IntegrationSecrets secrets={[{
|
||||
credentials: [],
|
||||
steps: []
|
||||
}]} />
|
||||
|
||||
<IntegrationSecrets
|
||||
secrets={[
|
||||
{
|
||||
credentials: [],
|
||||
steps: [],
|
||||
},
|
||||
]}
|
||||
/>
|
||||
@@ -1,20 +1,17 @@
|
||||
---
|
||||
title: 'Gluetun'
|
||||
description: 'Connect Gluetun to Homarr as a VPN integration.'
|
||||
title: "Gluetun"
|
||||
description: "Connect Gluetun to Homarr as a VPN integration."
|
||||
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 { gluetunIntegration } from '.';
|
||||
import { vpnWidget } from '@site/docs/widgets/vpn';
|
||||
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 { gluetunIntegration } from ".";
|
||||
import { vpnWidget } from "@site/docs/widgets/vpn";
|
||||
|
||||
<IntegrationHeader
|
||||
integration={gluetunIntegration}
|
||||
categories={['VPN', 'Network']}
|
||||
/>
|
||||
<IntegrationHeader integration={gluetunIntegration} categories={["VPN", "Network"]} />
|
||||
|
||||
[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.
|
||||
@@ -22,9 +19,11 @@ status, DNS status, public IP address, and active provider details in the VPN wi
|
||||
### Widgets & Capabilities
|
||||
|
||||
<IntegrationCapabilites
|
||||
items={[{
|
||||
widget: vpnWidget,
|
||||
}]}
|
||||
items={[
|
||||
{
|
||||
widget: vpnWidget,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
### Setting up the Gluetun control server
|
||||
@@ -40,13 +39,20 @@ Refer to the [Gluetun control server documentation](https://github.com/qdm12/glu
|
||||
Gluetun's control server can be left unprotected or secured with either an API key or HTTP Basic authentication.
|
||||
Choose the option that matches how your Gluetun instance integration is set up.
|
||||
|
||||
<IntegrationSecrets secrets={[{
|
||||
credentials: [],
|
||||
steps: [],
|
||||
},{
|
||||
credentials: ['username', 'password'],
|
||||
steps: [],
|
||||
},{
|
||||
credentials: ['apiKey'],
|
||||
steps: [],
|
||||
},,]} />
|
||||
<IntegrationSecrets
|
||||
secrets={[
|
||||
{
|
||||
credentials: [],
|
||||
steps: [],
|
||||
},
|
||||
{
|
||||
credentials: ["username", "password"],
|
||||
steps: [],
|
||||
},
|
||||
{
|
||||
credentials: ["apiKey"],
|
||||
steps: [],
|
||||
},
|
||||
,
|
||||
]}
|
||||
/>
|
||||
@@ -1,33 +1,39 @@
|
||||
---
|
||||
title: 'Gotify'
|
||||
title: "Gotify"
|
||||
description: "Gotify is a simple, self-hosted server for sending and receiving messages in real time."
|
||||
hide_title: true
|
||||
---
|
||||
|
||||
import { notificationsWidget } from '@site/docs/widgets/notifications';
|
||||
import { AddingIntegration } from '@site/src/components/integrations/adding';
|
||||
import { IntegrationHeader } from '@site/src/components/integrations/header';
|
||||
import { IntegrationSecrets } from '@site/src/components/integrations/secrets';
|
||||
import { IntegrationCapabilites } from '@site/src/components/integrations/widgets';
|
||||
import { gotifyIntegration } from '.';
|
||||
import { notificationsWidget } from "@site/docs/widgets/notifications";
|
||||
import { AddingIntegration } from "@site/src/components/integrations/adding";
|
||||
import { IntegrationHeader } from "@site/src/components/integrations/header";
|
||||
import { IntegrationSecrets } from "@site/src/components/integrations/secrets";
|
||||
import { IntegrationCapabilites } from "@site/src/components/integrations/widgets";
|
||||
import { gotifyIntegration } from ".";
|
||||
|
||||
<IntegrationHeader
|
||||
integration={gotifyIntegration}
|
||||
categories={['Notifications', 'Messaging']}
|
||||
/>
|
||||
<IntegrationHeader integration={gotifyIntegration} categories={["Notifications", "Messaging"]} />
|
||||
|
||||
### Widgets & Capabilities
|
||||
|
||||
<IntegrationCapabilites
|
||||
items={[{
|
||||
widget: notificationsWidget
|
||||
}]}
|
||||
items={[
|
||||
{
|
||||
widget: notificationsWidget,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
### Adding the integration
|
||||
|
||||
<AddingIntegration />
|
||||
|
||||
### Secrets
|
||||
<IntegrationSecrets secrets={[{
|
||||
credentials: ['username', 'password'],
|
||||
steps: []
|
||||
}]} />
|
||||
|
||||
<IntegrationSecrets
|
||||
secrets={[
|
||||
{
|
||||
credentials: ["username", "password"],
|
||||
steps: [],
|
||||
},
|
||||
]}
|
||||
/>
|
||||
@@ -1,45 +1,51 @@
|
||||
---
|
||||
title: 'Home Assistant'
|
||||
title: "Home Assistant"
|
||||
description: "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."
|
||||
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 { 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 { IconBinaryTree } from "@tabler/icons-react";
|
||||
import { homeAssistantIntegration } from '.';
|
||||
import { smartHomeEntityStateWidget } from '@site/docs/widgets/smart-home-entity-state';
|
||||
import { smartHomeExecuteAutomationWidget } from '@site/docs/widgets/smart-home-execute-automation';
|
||||
import { homeAssistantIntegration } from ".";
|
||||
import { smartHomeEntityStateWidget } from "@site/docs/widgets/smart-home-entity-state";
|
||||
import { smartHomeExecuteAutomationWidget } from "@site/docs/widgets/smart-home-execute-automation";
|
||||
|
||||
|
||||
<IntegrationHeader
|
||||
integration={homeAssistantIntegration}
|
||||
categories={['Smart Home', 'Automation']}
|
||||
/>
|
||||
<IntegrationHeader integration={homeAssistantIntegration} categories={["Smart Home", "Automation"]} />
|
||||
|
||||
### Widgets & Capabilities
|
||||
|
||||
<IntegrationCapabilites
|
||||
items={[{
|
||||
widget: smartHomeEntityStateWidget
|
||||
}, {
|
||||
widget: smartHomeExecuteAutomationWidget,
|
||||
}]}
|
||||
items={[
|
||||
{
|
||||
widget: smartHomeEntityStateWidget,
|
||||
},
|
||||
{
|
||||
widget: smartHomeExecuteAutomationWidget,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
### Adding the integration
|
||||
|
||||
<AddingIntegration />
|
||||
|
||||
### Secrets
|
||||
<IntegrationSecrets secrets={[{
|
||||
credentials: ['apiKey'],
|
||||
steps: [
|
||||
|
||||
<IntegrationSecrets
|
||||
secrets={[
|
||||
{
|
||||
credentials: ["apiKey"],
|
||||
steps: [
|
||||
"Click on your profile in the bottom left corner of Home Assistant.",
|
||||
"Switch to the Security tab.",
|
||||
"Scroll down to the Long-lived access tokens section.",
|
||||
"Click on the Create Token button.",
|
||||
"Enter a name for the token (e.g., 'Homarr Integration') and click Create.",
|
||||
"Copy the generated token and paste it into the Homarr integration settings.",
|
||||
]
|
||||
}]} />
|
||||
],
|
||||
},
|
||||
]}
|
||||
/>
|
||||
@@ -1,38 +1,44 @@
|
||||
---
|
||||
title: 'iCal'
|
||||
title: "iCal"
|
||||
description: "iCal is a standard for calendar data exchange, allowing users to share and manage calendar events across different platforms."
|
||||
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 { 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 { IconZoomQuestion, IconChartBar, IconSearch } from "@tabler/icons-react";
|
||||
import { icalIntegration } from '.';
|
||||
import { calendarWidget } from '@site/docs/widgets/calendar';
|
||||
import { icalIntegration } from ".";
|
||||
import { calendarWidget } from "@site/docs/widgets/calendar";
|
||||
|
||||
<IntegrationHeader
|
||||
integration={icalIntegration}
|
||||
categories={['Calendar']}
|
||||
/>
|
||||
<IntegrationHeader integration={icalIntegration} categories={["Calendar"]} />
|
||||
|
||||
### Widgets & Capabilities
|
||||
|
||||
<IntegrationCapabilites
|
||||
items={[{
|
||||
widget: calendarWidget
|
||||
}]}
|
||||
items={[
|
||||
{
|
||||
widget: calendarWidget,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
### Adding the integration
|
||||
|
||||
<AddingIntegration />
|
||||
|
||||
### Secrets
|
||||
<IntegrationSecrets secrets={[{
|
||||
credentials: ['url'],
|
||||
steps: []
|
||||
}]} />
|
||||
|
||||
<IntegrationSecrets
|
||||
secrets={[
|
||||
{
|
||||
credentials: ["url"],
|
||||
steps: [],
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
## Using the iCal integration
|
||||
|
||||
The iCal integration allows you to add external calendars to Homarr. Any calendar that provides a public iCal URL can be integrated, such as Google Calendar, Apple Calendar, or Outlook.
|
||||
The iCal integration allows you to add external calendars to Homarr. Any calendar that provides a public iCal URL can be integrated, such as Google Calendar, Apple Calendar, or Outlook.
|
||||
@@ -1,36 +1,29 @@
|
||||
---
|
||||
title: 'Immich'
|
||||
title: "Immich"
|
||||
description: "Self-hosted photo and video management solution"
|
||||
hide_title: true
|
||||
---
|
||||
|
||||
import { immichAlbumCarouselWidget } from '../../widgets/immich-album-carousel';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 { immichIntegration } from '.';
|
||||
import { immichServerStatsWidget } from '../../widgets/immich-server-stats';
|
||||
import { immichAlbumCarouselWidget } from "../../widgets/immich-album-carousel";
|
||||
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 { immichIntegration } from ".";
|
||||
import { immichServerStatsWidget } from "../../widgets/immich-server-stats";
|
||||
|
||||
|
||||
<IntegrationHeader
|
||||
integration={immichIntegration}
|
||||
categories={['Photo server']}
|
||||
/>
|
||||
<IntegrationHeader integration={immichIntegration} categories={["Photo server"]} />
|
||||
|
||||
### Widgets & Capabilities
|
||||
<IntegrationCapabilites
|
||||
items={[
|
||||
{ widget: immichAlbumCarouselWidget },
|
||||
{ widget: immichServerStatsWidget }
|
||||
]}
|
||||
/>
|
||||
|
||||
<IntegrationCapabilites items={[{ widget: immichAlbumCarouselWidget }, { widget: immichServerStatsWidget }]} />
|
||||
|
||||
### Adding the integration
|
||||
|
||||
<AddingIntegration />
|
||||
|
||||
### Secrets
|
||||
|
||||
<IntegrationSecrets secrets={[{
|
||||
credentials: ['apiKey'],
|
||||
steps: [
|
||||
@@ -50,14 +43,16 @@ import { immichServerStatsWidget } from '../../widgets/immich-server-stats';
|
||||
<code>user.read</code>,{" "}
|
||||
<code>user.update</code>,{" "}
|
||||
<code>asset.read</code>,{" "}
|
||||
<code>asset.view</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>
|
||||
|
||||
),
|
||||
'Create the integration in Homarr and copy the API key to there'
|
||||
]
|
||||
'Create the integration in Homarr and copy the API key to there'
|
||||
]
|
||||
}]} />
|
||||
@@ -1,40 +1,39 @@
|
||||
---
|
||||
title: 'Jellyfin'
|
||||
title: "Jellyfin"
|
||||
description: "Jellyfin is a free and open-source media server software that allows you to organize, manage, and share your media files."
|
||||
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 { 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 { IconVideo, IconTicket } from "@tabler/icons-react";
|
||||
import { jellyfinIntegration } from '.';
|
||||
import { mediaServerWidget } from '@site/docs/widgets/media-server';
|
||||
import { mediaReleasesWidget } from '@site/docs/widgets/media-releases';
|
||||
import { jellyfinIntegration } from ".";
|
||||
import { mediaServerWidget } from "@site/docs/widgets/media-server";
|
||||
import { mediaReleasesWidget } from "@site/docs/widgets/media-releases";
|
||||
|
||||
|
||||
<IntegrationHeader
|
||||
integration={jellyfinIntegration}
|
||||
categories={['Media server']}
|
||||
/>
|
||||
<IntegrationHeader integration={jellyfinIntegration} categories={["Media server"]} />
|
||||
|
||||
### Widgets & Capabilities
|
||||
<IntegrationCapabilites
|
||||
items={[
|
||||
{ widget: mediaServerWidget },
|
||||
{ widget: mediaReleasesWidget }
|
||||
]}
|
||||
/>
|
||||
|
||||
<IntegrationCapabilites items={[{ widget: mediaServerWidget }, { widget: mediaReleasesWidget }]} />
|
||||
|
||||
### Adding the integration
|
||||
|
||||
<AddingIntegration />
|
||||
|
||||
### Secrets
|
||||
<IntegrationSecrets secrets={[{
|
||||
credentials: ['apiKey'],
|
||||
steps: []
|
||||
},{
|
||||
credentials: ['username', 'password'],
|
||||
steps: []
|
||||
}]} />
|
||||
|
||||
<IntegrationSecrets
|
||||
secrets={[
|
||||
{
|
||||
credentials: ["apiKey"],
|
||||
steps: [],
|
||||
},
|
||||
{
|
||||
credentials: ["username", "password"],
|
||||
steps: [],
|
||||
},
|
||||
]}
|
||||
/>
|
||||
@@ -1,45 +1,52 @@
|
||||
---
|
||||
title: 'Jellyseerr'
|
||||
title: "Jellyseerr"
|
||||
description: "Jellyseerr is a self-hosted media request management system that integrates with Jellyfin and other media servers."
|
||||
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 { 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 { IconZoomQuestion, IconChartBar, IconSearch } from "@tabler/icons-react";
|
||||
import { jellyseerrIntegration } from '.';
|
||||
import { mediaRequestListWidget } from '@site/docs/widgets/media-request-list';
|
||||
import { mediaRequestStatsWidget } from '@site/docs/widgets/media-request-stats';
|
||||
import { jellyseerrIntegration } from ".";
|
||||
import { mediaRequestListWidget } from "@site/docs/widgets/media-request-list";
|
||||
import { mediaRequestStatsWidget } from "@site/docs/widgets/media-request-stats";
|
||||
|
||||
|
||||
<IntegrationHeader
|
||||
integration={jellyseerrIntegration}
|
||||
categories={['Media requests']}
|
||||
/>
|
||||
<IntegrationHeader integration={jellyseerrIntegration} categories={["Media requests"]} />
|
||||
|
||||
### Widgets & Capabilities
|
||||
|
||||
<IntegrationCapabilites
|
||||
items={[{
|
||||
widget: mediaRequestListWidget,
|
||||
},{
|
||||
widget: mediaRequestStatsWidget,
|
||||
}, {
|
||||
capability: {
|
||||
icon: IconSearch,
|
||||
name: "Media Search",
|
||||
description: "Search for movies and TV shows directly from Spotlight and request them.",
|
||||
path: "../../management/search-engines/#media-request-search"
|
||||
}
|
||||
}]}
|
||||
items={[
|
||||
{
|
||||
widget: mediaRequestListWidget,
|
||||
},
|
||||
{
|
||||
widget: mediaRequestStatsWidget,
|
||||
},
|
||||
{
|
||||
capability: {
|
||||
icon: IconSearch,
|
||||
name: "Media Search",
|
||||
description: "Search for movies and TV shows directly from Spotlight and request them.",
|
||||
path: "../../management/search-engines/#media-request-search",
|
||||
},
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
### Adding the integration
|
||||
|
||||
<AddingIntegration />
|
||||
|
||||
### Secrets
|
||||
<IntegrationSecrets secrets={[{
|
||||
credentials: ['apiKey'],
|
||||
steps: []
|
||||
}]} />
|
||||
|
||||
<IntegrationSecrets
|
||||
secrets={[
|
||||
{
|
||||
credentials: ["apiKey"],
|
||||
steps: [],
|
||||
},
|
||||
]}
|
||||
/>
|
||||
@@ -1,20 +1,16 @@
|
||||
---
|
||||
title: 'Kubernetes'
|
||||
title: "Kubernetes"
|
||||
description: "Kubernetes is a powerful container orchestration platform that automates the deployment, scaling, and management of containerized applications."
|
||||
hide_title: true
|
||||
---
|
||||
|
||||
import { IntegrationHeader } from '@site/src/components/integrations/header';
|
||||
import { IntegrationCapabilites } from '@site/src/components/integrations/widgets';
|
||||
import {IntegrationSecrets} from '@site/src/components/integrations/secrets';
|
||||
import { IntegrationHeader } from "@site/src/components/integrations/header";
|
||||
import { IntegrationCapabilites } from "@site/src/components/integrations/widgets";
|
||||
import { IntegrationSecrets } from "@site/src/components/integrations/secrets";
|
||||
import { IconBrandDocker, IconTool } from "@tabler/icons-react";
|
||||
import { kubernetesIntegration } from '.';
|
||||
import { kubernetesIntegration } from ".";
|
||||
|
||||
|
||||
<IntegrationHeader
|
||||
integration={kubernetesIntegration}
|
||||
categories={['Containers']}
|
||||
/>
|
||||
<IntegrationHeader integration={kubernetesIntegration} categories={["Containers"]} />
|
||||
|
||||
Homarr provides a user-friendly Kubernetes dashboard that offers an overview of your cluster’s resources in a clear and structured interface. It allows users to view key Kubernetes objects, including `Pods`, `Services`, `Ingresses`, `Nodes`, `ConfigMaps`, `Secrets`, `Namespaces`, and `Volumes`, as well as basic metrics when the Kubernetes Metrics Server is enabled.
|
||||
|
||||
@@ -47,7 +43,7 @@ Enabling RBAC in the Helm chart creates the necessary Kubernetes Role-Based Acce
|
||||
- Service Account: A dedicated service account for Homarr.
|
||||
- Role & RoleBinding: Grants permissions within a specific namespace to list, watch, and get pods, services, secrets, configmaps, and more.
|
||||
- ClusterRole & ClusterRoleBinding: Extends the same permissions cluster-wide for additional flexibility.
|
||||
:::
|
||||
:::
|
||||
|
||||
### Homarr Dashboard Overview
|
||||
|
||||
|
||||
@@ -1,35 +1,40 @@
|
||||
---
|
||||
title: 'Lidarr'
|
||||
title: "Lidarr"
|
||||
description: "Lidarr is a music management tool that automates the process of downloading, sorting, and renaming music."
|
||||
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 { 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 { IconCalendar } from "@tabler/icons-react";
|
||||
import { lidarrIntegration } from '.';
|
||||
import { calendarWidget } from '@site/docs/widgets/calendar';
|
||||
import { lidarrIntegration } from ".";
|
||||
import { calendarWidget } from "@site/docs/widgets/calendar";
|
||||
|
||||
|
||||
<IntegrationHeader
|
||||
integration={lidarrIntegration}
|
||||
categories={['Media manager', "Servarr"]}
|
||||
/>
|
||||
<IntegrationHeader integration={lidarrIntegration} categories={["Media manager", "Servarr"]} />
|
||||
|
||||
### Widgets & Capabilities
|
||||
|
||||
<IntegrationCapabilites
|
||||
items={[{
|
||||
widget: calendarWidget
|
||||
}]}
|
||||
items={[
|
||||
{
|
||||
widget: calendarWidget,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
### Adding the integration
|
||||
|
||||
<AddingIntegration />
|
||||
|
||||
### Secrets
|
||||
<IntegrationSecrets secrets={[{
|
||||
credentials: ['apiKey'],
|
||||
steps: []
|
||||
}]} />
|
||||
|
||||
<IntegrationSecrets
|
||||
secrets={[
|
||||
{
|
||||
credentials: ["apiKey"],
|
||||
steps: [],
|
||||
},
|
||||
]}
|
||||
/>
|
||||
@@ -1,41 +1,38 @@
|
||||
---
|
||||
title: 'Navidrome'
|
||||
title: "Navidrome"
|
||||
description: "Navidrome is a self-hosted music server and streamer compatible with the Subsonic API."
|
||||
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 { navidromeIntegration } from '.';
|
||||
import { audioStatsWidget } from '@site/docs/widgets/audio-stats';
|
||||
import { mediaServerWidget } from '@site/docs/widgets/media-server';
|
||||
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 { navidromeIntegration } from ".";
|
||||
import { audioStatsWidget } from "@site/docs/widgets/audio-stats";
|
||||
|
||||
<IntegrationHeader integration={navidromeIntegration} categories={["Media"]} />
|
||||
|
||||
<IntegrationHeader
|
||||
integration={navidromeIntegration}
|
||||
categories={['Media', 'Media server']}
|
||||
/>
|
||||
|
||||
Navidrome provides music library statistics including artist, album, and song counts, as well as now playing audio streams via the Subsonic API.
|
||||
Navidrome provides music library statistics including artist, album, and song counts, as well as now playing information via the Subsonic API.
|
||||
|
||||
### Widgets & Capabilities
|
||||
<IntegrationCapabilites
|
||||
items={[
|
||||
{ widget: audioStatsWidget },
|
||||
{ widget: mediaServerWidget },
|
||||
]}
|
||||
/>
|
||||
|
||||
<IntegrationCapabilites items={[{ widget: audioStatsWidget }]} />
|
||||
|
||||
### Adding the integration
|
||||
|
||||
<AddingIntegration />
|
||||
|
||||
### Secrets
|
||||
<IntegrationSecrets secrets={[{
|
||||
credentials: ['username', 'password'],
|
||||
steps: [
|
||||
'Use the same credentials you use to log in to Navidrome',
|
||||
'The user must have access to the Subsonic API (enabled by default)',
|
||||
]
|
||||
}]} />
|
||||
|
||||
<IntegrationSecrets
|
||||
secrets={[
|
||||
{
|
||||
credentials: ["username", "password"],
|
||||
steps: [
|
||||
"Use the same credentials you use to log in to Navidrome",
|
||||
"The user must have access to the Subsonic API (enabled by default)",
|
||||
],
|
||||
},
|
||||
]}
|
||||
/>
|
||||
@@ -1,40 +1,43 @@
|
||||
---
|
||||
title: 'Nextcloud'
|
||||
title: "Nextcloud"
|
||||
description: "Nextcloud is a self-hosted productivity platform that provides file storage, collaboration tools, and more."
|
||||
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 { 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 { IconCalendar } from "@tabler/icons-react";
|
||||
import Alert from '@theme/Admonition';
|
||||
import { nextcloudIntegration } from '.';
|
||||
import { calendarWidget } from '@site/docs/widgets/calendar';
|
||||
import { notificationsWidget } from '@site/docs/widgets/notifications';
|
||||
import Alert from "@theme/Admonition";
|
||||
import { nextcloudIntegration } from ".";
|
||||
import { calendarWidget } from "@site/docs/widgets/calendar";
|
||||
import { notificationsWidget } from "@site/docs/widgets/notifications";
|
||||
|
||||
<IntegrationHeader
|
||||
integration={nextcloudIntegration}
|
||||
categories={['Calendar', 'Notifications']}
|
||||
/>
|
||||
<IntegrationHeader integration={nextcloudIntegration} categories={["Calendar", "Notifications"]} />
|
||||
|
||||
### Widgets & Capabilities
|
||||
<IntegrationCapabilites
|
||||
items={[
|
||||
{ widget: calendarWidget },
|
||||
{ widget: notificationsWidget }
|
||||
]}
|
||||
/>
|
||||
|
||||
<IntegrationCapabilites items={[{ widget: calendarWidget }, { widget: notificationsWidget }]} />
|
||||
|
||||
### Adding the integration
|
||||
|
||||
<AddingIntegration />
|
||||
|
||||
### Secrets
|
||||
<IntegrationSecrets secrets={[{
|
||||
credentials: ['username', 'password'],
|
||||
steps: [],
|
||||
header: <Alert type="info">
|
||||
Users who use 2FA will have to use Nextcloud's App Password tool to grant Homarr access, as 2FA breaks the traditional process. For more information, see [Nextcloud's Documentation](https://docs.nextcloud.com/server/stable/admin_manual/configuration_user/authentication.html#app-passwords)
|
||||
</Alert>,
|
||||
}]} />
|
||||
|
||||
<IntegrationSecrets
|
||||
secrets={[
|
||||
{
|
||||
credentials: ["username", "password"],
|
||||
steps: [],
|
||||
header: (
|
||||
<Alert type="info">
|
||||
Users who use 2FA will have to use Nextcloud's App Password tool to grant Homarr access, as 2FA breaks the
|
||||
traditional process. For more information, see [Nextcloud's
|
||||
Documentation](https://docs.nextcloud.com/server/stable/admin_manual/configuration_user/authentication.html#app-passwords)
|
||||
</Alert>
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
@@ -1,37 +1,44 @@
|
||||
---
|
||||
title: 'Ntfy'
|
||||
title: "Ntfy"
|
||||
description: "Ntfy is a simple, open-source notification service that allows you to send and receive notifications across devices."
|
||||
hide_title: true
|
||||
---
|
||||
|
||||
import { notificationsWidget } from '@site/docs/widgets/notifications';
|
||||
import { AddingIntegration } from '@site/src/components/integrations/adding';
|
||||
import { IntegrationHeader } from '@site/src/components/integrations/header';
|
||||
import { IntegrationSecrets } from '@site/src/components/integrations/secrets';
|
||||
import { IntegrationCapabilites } from '@site/src/components/integrations/widgets';
|
||||
import { notificationsWidget } from "@site/docs/widgets/notifications";
|
||||
import { AddingIntegration } from "@site/src/components/integrations/adding";
|
||||
import { IntegrationHeader } from "@site/src/components/integrations/header";
|
||||
import { IntegrationSecrets } from "@site/src/components/integrations/secrets";
|
||||
import { IntegrationCapabilites } from "@site/src/components/integrations/widgets";
|
||||
import { IconMessage } from "@tabler/icons-react";
|
||||
import { ntfyIntegration } from '.';
|
||||
import { ntfyIntegration } from ".";
|
||||
|
||||
<IntegrationHeader
|
||||
integration={ntfyIntegration}
|
||||
categories={['Notifications', 'Messaging']}
|
||||
/>
|
||||
<IntegrationHeader integration={ntfyIntegration} categories={["Notifications", "Messaging"]} />
|
||||
|
||||
### Widgets & Capabilities
|
||||
|
||||
<IntegrationCapabilites
|
||||
items={[{
|
||||
widget: notificationsWidget
|
||||
}]}
|
||||
items={[
|
||||
{
|
||||
widget: notificationsWidget,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
### Adding the integration
|
||||
|
||||
<AddingIntegration />
|
||||
|
||||
### Secrets
|
||||
<IntegrationSecrets secrets={[{
|
||||
credentials: ['topic'],
|
||||
steps: []
|
||||
}, {
|
||||
credentials: ['topic', 'apiKey'],
|
||||
steps: []
|
||||
}]} />
|
||||
|
||||
<IntegrationSecrets
|
||||
secrets={[
|
||||
{
|
||||
credentials: ["topic"],
|
||||
steps: [],
|
||||
},
|
||||
{
|
||||
credentials: ["topic", "apiKey"],
|
||||
steps: [],
|
||||
},
|
||||
]}
|
||||
/>
|
||||
@@ -1,36 +1,41 @@
|
||||
---
|
||||
title: 'NZBGet'
|
||||
title: "NZBGet"
|
||||
description: "NZBGet is a lightweight and efficient Usenet downloader."
|
||||
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 { 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 { IconDownload } from "@tabler/icons-react";
|
||||
import { nzbgetIntegration } from '.';
|
||||
import Alert from '@theme/Admonition';
|
||||
import { downloadsWidget } from '@site/docs/widgets/downloads';
|
||||
import { nzbgetIntegration } from ".";
|
||||
import Alert from "@theme/Admonition";
|
||||
import { downloadsWidget } from "@site/docs/widgets/downloads";
|
||||
|
||||
|
||||
<IntegrationHeader
|
||||
integration={nzbgetIntegration}
|
||||
categories={['Usenet client']}
|
||||
/>
|
||||
<IntegrationHeader integration={nzbgetIntegration} categories={["Usenet client"]} />
|
||||
|
||||
### Widgets & Capabilities
|
||||
|
||||
<IntegrationCapabilites
|
||||
items={[{
|
||||
widget: downloadsWidget,
|
||||
}]}
|
||||
items={[
|
||||
{
|
||||
widget: downloadsWidget,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
### Adding the integration
|
||||
|
||||
<AddingIntegration />
|
||||
|
||||
### Secrets
|
||||
<IntegrationSecrets secrets={[{
|
||||
credentials: ['username', 'password'],
|
||||
steps: [],
|
||||
}]} />
|
||||
|
||||
<IntegrationSecrets
|
||||
secrets={[
|
||||
{
|
||||
credentials: ["username", "password"],
|
||||
steps: [],
|
||||
},
|
||||
]}
|
||||
/>
|
||||
@@ -1,42 +1,52 @@
|
||||
---
|
||||
title: 'Open Media Vault'
|
||||
title: "Open Media Vault"
|
||||
description: "Open Media Vault is a free network-attached storage server based on the Debian operating system."
|
||||
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 { 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 { IconHeartRateMonitor } from "@tabler/icons-react";
|
||||
import { openMediaVaultIntegration } from '.';
|
||||
import Alert from '@theme/Admonition'
|
||||
import { healthMonitoringWidget } from '@site/docs/widgets/health-monitoring';
|
||||
import { systemResourcesWidget } from '@site/docs/widgets/system-resources';
|
||||
import { openMediaVaultIntegration } from ".";
|
||||
import Alert from "@theme/Admonition";
|
||||
import { healthMonitoringWidget } from "@site/docs/widgets/health-monitoring";
|
||||
import { systemResourcesWidget } from "@site/docs/widgets/system-resources";
|
||||
|
||||
<IntegrationHeader
|
||||
integration={openMediaVaultIntegration}
|
||||
categories={['System monitoring']}
|
||||
/>
|
||||
<IntegrationHeader integration={openMediaVaultIntegration} categories={["System monitoring"]} />
|
||||
|
||||
### Widgets & Capabilities
|
||||
|
||||
<IntegrationCapabilites
|
||||
items={[{
|
||||
widget: healthMonitoringWidget,
|
||||
note: "To use this widget, you must have the OpenMediaVault plugin openmediavault-cputemp installed.",
|
||||
}, {
|
||||
widget: systemResourcesWidget
|
||||
}]}
|
||||
items={[
|
||||
{
|
||||
widget: healthMonitoringWidget,
|
||||
note: "To use this widget, you must have the OpenMediaVault plugin openmediavault-cputemp installed.",
|
||||
},
|
||||
{
|
||||
widget: systemResourcesWidget,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
### Adding the integration
|
||||
|
||||
<AddingIntegration />
|
||||
|
||||
### Secrets
|
||||
<IntegrationSecrets secrets={[{
|
||||
credentials: ['username', 'password'],
|
||||
steps: [],
|
||||
body: <Alert type="caution">
|
||||
The user used here must have administrative permissions or Homarr won't be able to read the system performance and resource data.
|
||||
</Alert>
|
||||
}]} />
|
||||
|
||||
<IntegrationSecrets
|
||||
secrets={[
|
||||
{
|
||||
credentials: ["username", "password"],
|
||||
steps: [],
|
||||
body: (
|
||||
<Alert type="caution">
|
||||
The user used here must have administrative permissions or Homarr won't be able to read the system performance
|
||||
and resource data.
|
||||
</Alert>
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
@@ -1,65 +1,75 @@
|
||||
---
|
||||
title: 'OPNsense'
|
||||
title: "OPNsense"
|
||||
description: "OPNsense is an open-source, easy-to-use, and easy-to-build firewall and routing platform."
|
||||
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 { 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 { IconHeartRateMonitor } from "@tabler/icons-react";
|
||||
import { opnsenseIntegration } from '.';
|
||||
import Alert from '@theme/Admonition'
|
||||
import { firewallWidget } from '../../widgets/firewall';
|
||||
import { opnsenseIntegration } from ".";
|
||||
import Alert from "@theme/Admonition";
|
||||
import { firewallWidget } from "../../widgets/firewall";
|
||||
|
||||
<IntegrationHeader
|
||||
integration={opnsenseIntegration}
|
||||
categories={['Firewall']}
|
||||
/>
|
||||
<IntegrationHeader integration={opnsenseIntegration} categories={["Firewall"]} />
|
||||
|
||||
### Widgets & Capabilities
|
||||
|
||||
<IntegrationCapabilites
|
||||
items={[{
|
||||
widget: firewallWidget,
|
||||
}]}
|
||||
items={[
|
||||
{
|
||||
widget: firewallWidget,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
### Adding the integration
|
||||
|
||||
<AddingIntegration />
|
||||
|
||||
### Secrets
|
||||
<IntegrationSecrets secrets={[{
|
||||
tabLabel: "ApiKey (Key & Secret)",
|
||||
credentials: ['opnsenseApiKey', 'opnsenseApiSecret'],
|
||||
header: <Alert type="info">
|
||||
This integration does not require full API access to the firewall. The following guide outlines the process for creating a restricted OPNsense user with a scrambled password, designed exclusively for API access. This ensures minimal permissions and prevents local login capability.
|
||||
</Alert>,
|
||||
steps: [
|
||||
|
||||
<IntegrationSecrets
|
||||
secrets={[
|
||||
{
|
||||
tabLabel: "ApiKey (Key & Secret)",
|
||||
credentials: ["opnsenseApiKey", "opnsenseApiSecret"],
|
||||
header: (
|
||||
<Alert type="info">
|
||||
This integration does not require full API access to the firewall. The following guide outlines the process
|
||||
for creating a restricted OPNsense user with a scrambled password, designed exclusively for API access. This
|
||||
ensures minimal permissions and prevents local login capability.
|
||||
</Alert>
|
||||
),
|
||||
steps: [
|
||||
"Log in to the OPNsense web interface as an administrator",
|
||||
"Navigate to System → Access → Users",
|
||||
"Click the + (Add) button to create a new user",
|
||||
<>
|
||||
Fill in the user details:
|
||||
<ul>
|
||||
<li>Username: Enter a unique name</li>
|
||||
<li>Password: Leave empty</li>
|
||||
<li>Scrambled Password: Enable this option</li>
|
||||
<li>Full name: (Optional) Add a descriptive name</li>
|
||||
</ul>
|
||||
Fill in the user details:
|
||||
<ul>
|
||||
<li>Username: Enter a unique name</li>
|
||||
<li>Password: Leave empty</li>
|
||||
<li>Scrambled Password: Enable this option</li>
|
||||
<li>Full name: (Optional) Add a descriptive name</li>
|
||||
</ul>
|
||||
</>,
|
||||
<>
|
||||
In Privileges section → Select the option below:
|
||||
<ul>
|
||||
<li>Lobby: Dashboard</li>
|
||||
<li>Reporting: Traffic</li>
|
||||
</ul>
|
||||
In Privileges section → Select the option below:
|
||||
<ul>
|
||||
<li>Lobby: Dashboard</li>
|
||||
<li>Reporting: Traffic</li>
|
||||
</ul>
|
||||
</>,
|
||||
"Click Save",
|
||||
"Return to the Users list",
|
||||
"Find the user and click the ticket icon in the Commands column",
|
||||
"Click Yes in the popup to confirm",
|
||||
"A txt file will download automatically, containing: Key and Secret",
|
||||
"Save this file securely—this information will only be shown once"
|
||||
],
|
||||
}]} />
|
||||
"Save this file securely—this information will only be shown once",
|
||||
],
|
||||
},
|
||||
]}
|
||||
/>
|
||||
@@ -1,45 +1,52 @@
|
||||
---
|
||||
title: 'Overseerr'
|
||||
title: "Overseerr"
|
||||
description: "Overseerr is a self-hosted media request management system that integrates with Plex."
|
||||
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 { 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 { IconZoomQuestion, IconChartBar, IconSearch } from "@tabler/icons-react";
|
||||
import { overseerrIntegration } from '.';
|
||||
import { mediaRequestListWidget } from '@site/docs/widgets/media-request-list';
|
||||
import { mediaRequestStatsWidget } from '@site/docs/widgets/media-request-stats';
|
||||
import { overseerrIntegration } from ".";
|
||||
import { mediaRequestListWidget } from "@site/docs/widgets/media-request-list";
|
||||
import { mediaRequestStatsWidget } from "@site/docs/widgets/media-request-stats";
|
||||
|
||||
|
||||
<IntegrationHeader
|
||||
integration={overseerrIntegration}
|
||||
categories={['Media requests']}
|
||||
/>
|
||||
<IntegrationHeader integration={overseerrIntegration} categories={["Media requests"]} />
|
||||
|
||||
### Widgets & Capabilities
|
||||
|
||||
<IntegrationCapabilites
|
||||
items={[{
|
||||
widget: mediaRequestListWidget
|
||||
}, {
|
||||
widget: mediaRequestStatsWidget
|
||||
}, {
|
||||
capability: {
|
||||
icon: IconSearch,
|
||||
name: "Media Search",
|
||||
description: "Search for movies and TV shows directly from Spotlight and request them.",
|
||||
path: "../../management/search-engines/#media-request-search"
|
||||
}
|
||||
}]}
|
||||
items={[
|
||||
{
|
||||
widget: mediaRequestListWidget,
|
||||
},
|
||||
{
|
||||
widget: mediaRequestStatsWidget,
|
||||
},
|
||||
{
|
||||
capability: {
|
||||
icon: IconSearch,
|
||||
name: "Media Search",
|
||||
description: "Search for movies and TV shows directly from Spotlight and request them.",
|
||||
path: "../../management/search-engines/#media-request-search",
|
||||
},
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
### Adding the integration
|
||||
|
||||
<AddingIntegration />
|
||||
|
||||
### Secrets
|
||||
<IntegrationSecrets secrets={[{
|
||||
credentials: ['apiKey'],
|
||||
steps: []
|
||||
}]} />
|
||||
|
||||
<IntegrationSecrets
|
||||
secrets={[
|
||||
{
|
||||
credentials: ["apiKey"],
|
||||
steps: [],
|
||||
},
|
||||
]}
|
||||
/>
|
||||
@@ -1,41 +1,40 @@
|
||||
---
|
||||
title: 'Paperless-ngx'
|
||||
title: "Paperless-ngx"
|
||||
description: "Paperless-ngx is a document management system that transforms your physical documents into a searchable online archive."
|
||||
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 { paperlessNgxIntegration } from '.';
|
||||
import { paperlessNgxWidget } from '@site/docs/widgets/paperless-ngx';
|
||||
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 { paperlessNgxIntegration } from ".";
|
||||
import { paperlessNgxWidget } from "@site/docs/widgets/paperless-ngx";
|
||||
|
||||
|
||||
<IntegrationHeader
|
||||
integration={paperlessNgxIntegration}
|
||||
categories={['Documents']}
|
||||
/>
|
||||
<IntegrationHeader integration={paperlessNgxIntegration} categories={["Documents"]} />
|
||||
|
||||
Paperless-ngx provides document statistics including total documents, inbox count, correspondents, tags, and document types.
|
||||
|
||||
### Widgets & Capabilities
|
||||
<IntegrationCapabilites
|
||||
items={[
|
||||
{ widget: paperlessNgxWidget },
|
||||
]}
|
||||
/>
|
||||
|
||||
<IntegrationCapabilites items={[{ widget: paperlessNgxWidget }]} />
|
||||
|
||||
### Adding the integration
|
||||
|
||||
<AddingIntegration />
|
||||
|
||||
### Secrets
|
||||
<IntegrationSecrets secrets={[{
|
||||
credentials: ['apiKey'],
|
||||
steps: [
|
||||
'Open your Paperless-ngx instance',
|
||||
'Navigate to Settings → Django Admin → Tokens',
|
||||
'Create a new token for your user',
|
||||
'Copy the token value',
|
||||
]
|
||||
}]} />
|
||||
|
||||
<IntegrationSecrets
|
||||
secrets={[
|
||||
{
|
||||
credentials: ["apiKey"],
|
||||
steps: [
|
||||
"Open your Paperless-ngx instance",
|
||||
"Navigate to Settings → Django Admin → Tokens",
|
||||
"Create a new token for your user",
|
||||
"Copy the token value",
|
||||
],
|
||||
},
|
||||
]}
|
||||
/>
|
||||
@@ -1,43 +1,42 @@
|
||||
---
|
||||
title: 'PatchMon'
|
||||
title: "PatchMon"
|
||||
description: "PatchMon is a patch management and monitoring solution for Linux hosts."
|
||||
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 { patchmonIntegration } from '.';
|
||||
import { patchmonWidget } from '@site/docs/widgets/patchmon';
|
||||
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 { patchmonIntegration } from ".";
|
||||
import { patchmonWidget } from "@site/docs/widgets/patchmon";
|
||||
|
||||
|
||||
<IntegrationHeader
|
||||
integration={patchmonIntegration}
|
||||
categories={['Health Monitoring']}
|
||||
/>
|
||||
<IntegrationHeader integration={patchmonIntegration} categories={["Health Monitoring"]} />
|
||||
|
||||
PatchMon provides host and package update statistics including total hosts, hosts needing updates, and security update counts.
|
||||
|
||||
The PatchMon widget requires users to be **signed in to Homarr**; see the [PatchMon widget documentation](/docs/widgets/patchmon#authentication) for details on authentication and public boards.
|
||||
|
||||
### Widgets & Capabilities
|
||||
<IntegrationCapabilites
|
||||
items={[
|
||||
{ widget: patchmonWidget },
|
||||
]}
|
||||
/>
|
||||
|
||||
<IntegrationCapabilites items={[{ widget: patchmonWidget }]} />
|
||||
|
||||
### Adding the integration
|
||||
|
||||
<AddingIntegration />
|
||||
|
||||
### Secrets
|
||||
<IntegrationSecrets secrets={[{
|
||||
credentials: ['patchmonApiKey', 'patchmonApiSecret'],
|
||||
steps: [
|
||||
'Open your PatchMon instance',
|
||||
'Navigate to Settings and create a new GetHomepage API key',
|
||||
'Copy the API key and secret from the success dialog',
|
||||
'Paste them into Homarr — Basic authentication is handled automatically',
|
||||
]
|
||||
}]} />
|
||||
|
||||
<IntegrationSecrets
|
||||
secrets={[
|
||||
{
|
||||
credentials: ["patchmonApiKey", "patchmonApiSecret"],
|
||||
steps: [
|
||||
"Open your PatchMon instance",
|
||||
"Navigate to Settings and create a new GetHomepage API key",
|
||||
"Copy the API key and secret from the success dialog",
|
||||
"Paste them into Homarr — Basic authentication is handled automatically",
|
||||
],
|
||||
},
|
||||
]}
|
||||
/>
|
||||
@@ -1,48 +1,53 @@
|
||||
---
|
||||
title: 'PeaNUT'
|
||||
description: 'A tiny dashboard for Network UPS Tools (NUT) servers.'
|
||||
title: "PeaNUT"
|
||||
description: "A tiny dashboard for Network UPS Tools (NUT) servers."
|
||||
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 { peaNutIntegration } from '.';
|
||||
import { upsWidget } from '@site/docs/widgets/ups';
|
||||
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 { peaNutIntegration } from ".";
|
||||
import { upsWidget } from "@site/docs/widgets/ups";
|
||||
|
||||
<IntegrationHeader
|
||||
integration={peaNutIntegration}
|
||||
categories={['UPS', 'Hardware']}
|
||||
/>
|
||||
<IntegrationHeader integration={peaNutIntegration} categories={["UPS", "Hardware"]} />
|
||||
|
||||
[PeaNUT](https://github.com/Brandawg93/PeaNUT) exposes the data of your [Network UPS Tools](https://networkupstools.org/) servers
|
||||
over a REST API, which Homarr reads to display the status of your UPS devices.
|
||||
|
||||
### Widgets & Capabilities
|
||||
|
||||
<IntegrationCapabilites
|
||||
items={[{
|
||||
items={[
|
||||
{
|
||||
widget: upsWidget,
|
||||
}]}
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
### Adding the integration
|
||||
|
||||
<AddingIntegration />
|
||||
|
||||
### Secrets
|
||||
|
||||
PeaNUT secures its API with HTTP Basic authentication when the `WEB_USERNAME` and `WEB_PASSWORD` environment variables
|
||||
are set on the PeaNUT instance. Provide the same credentials here. If the instance runs with `AUTH_DISABLED=true`, no
|
||||
secrets are required.
|
||||
|
||||
<IntegrationSecrets secrets={[{
|
||||
credentials: ['username', 'password'],
|
||||
steps: [
|
||||
"Set WEB_USERNAME and WEB_PASSWORD on your PeaNUT instance.",
|
||||
"Create an integration in Homarr with the PeaNUT URL and those credentials.",
|
||||
],
|
||||
},{
|
||||
credentials: [],
|
||||
steps: [
|
||||
"If PeaNUT runs with AUTH_DISABLED=true, create the integration with no credentials.",
|
||||
],
|
||||
}]} />
|
||||
<IntegrationSecrets
|
||||
secrets={[
|
||||
{
|
||||
credentials: ["username", "password"],
|
||||
steps: [
|
||||
"Set WEB_USERNAME and WEB_PASSWORD on your PeaNUT instance.",
|
||||
"Create an integration in Homarr with the PeaNUT URL and those credentials.",
|
||||
],
|
||||
},
|
||||
{
|
||||
credentials: [],
|
||||
steps: ["If PeaNUT runs with AUTH_DISABLED=true, create the integration with no credentials."],
|
||||
},
|
||||
]}
|
||||
/>
|
||||
@@ -1,47 +1,54 @@
|
||||
---
|
||||
title: 'Pi-hole'
|
||||
title: "Pi-hole"
|
||||
description: "Pi-hole is a network-wide ad blocker that acts as a DNS sinkhole, blocking unwanted content and improving your browsing experience."
|
||||
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 { 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 { IconAd, IconDeviceGamepad } from "@tabler/icons-react";
|
||||
import Alert from '@theme/Admonition';
|
||||
import { dnsHoleSummaryWidget } from '@site/docs/widgets/dns-hole-summary';
|
||||
import { dnsHoleControlsWidget } from '@site/docs/widgets/dns-hole-controls';
|
||||
import { piHoleIntegration } from '.';
|
||||
import Alert from "@theme/Admonition";
|
||||
import { dnsHoleSummaryWidget } from "@site/docs/widgets/dns-hole-summary";
|
||||
import { dnsHoleControlsWidget } from "@site/docs/widgets/dns-hole-controls";
|
||||
import { piHoleIntegration } from ".";
|
||||
|
||||
<IntegrationHeader integration={piHoleIntegration} categories={["DNS Hole"]} />
|
||||
|
||||
<IntegrationHeader
|
||||
integration={piHoleIntegration}
|
||||
categories={['DNS Hole']}
|
||||
/>
|
||||
|
||||
<Alert type="note" className='my-2'>
|
||||
Homarr supports both Pi-hole v5 and v6. It automatically detects the version and uses the correct API.
|
||||
<Alert type="note" className="my-2">
|
||||
Homarr supports both Pi-hole v5 and v6. It automatically detects the version and uses the correct API.
|
||||
</Alert>
|
||||
|
||||
### Widgets & Capabilities
|
||||
|
||||
<IntegrationCapabilites
|
||||
items={[{
|
||||
widget: dnsHoleSummaryWidget,
|
||||
},{
|
||||
widget: dnsHoleControlsWidget
|
||||
}]}
|
||||
items={[
|
||||
{
|
||||
widget: dnsHoleSummaryWidget,
|
||||
},
|
||||
{
|
||||
widget: dnsHoleControlsWidget,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
### Adding the integration
|
||||
|
||||
<AddingIntegration />
|
||||
|
||||
### Secrets
|
||||
<IntegrationSecrets secrets={[{
|
||||
credentials: ['apiKey'],
|
||||
steps: [],
|
||||
body: "Please note, that in v6 of Pi-hole you no longer specify an API key, but instead either a password or application password. We suggest to use the application password when possible."
|
||||
}, {
|
||||
credentials: [],
|
||||
steps: []
|
||||
}]} />
|
||||
|
||||
<IntegrationSecrets
|
||||
secrets={[
|
||||
{
|
||||
credentials: ["apiKey"],
|
||||
steps: [],
|
||||
body: "Please note, that in v6 of Pi-hole you no longer specify an API key, but instead either a password or application password. We suggest to use the application password when possible.",
|
||||
},
|
||||
{
|
||||
credentials: [],
|
||||
steps: [],
|
||||
},
|
||||
]}
|
||||
/>
|
||||
@@ -1,41 +1,47 @@
|
||||
---
|
||||
title: 'Plex'
|
||||
title: "Plex"
|
||||
description: "Plex is a media server platform that allows you to organize, manage, and stream your personal media collection."
|
||||
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 { 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 { IconVideo, IconTicket } from "@tabler/icons-react";
|
||||
import { plexIntegration } from '.';
|
||||
import { mediaServerWidget } from '@site/docs/widgets/media-server';
|
||||
import { mediaReleasesWidget } from '@site/docs/widgets/media-releases';
|
||||
import { plexIntegration } from ".";
|
||||
import { mediaServerWidget } from "@site/docs/widgets/media-server";
|
||||
import { mediaReleasesWidget } from "@site/docs/widgets/media-releases";
|
||||
|
||||
|
||||
<IntegrationHeader
|
||||
integration={plexIntegration}
|
||||
categories={['Media server']}
|
||||
/>
|
||||
<IntegrationHeader integration={plexIntegration} categories={["Media server"]} />
|
||||
|
||||
### Widgets & Capabilities
|
||||
<IntegrationCapabilites
|
||||
items={[
|
||||
{ widget: mediaServerWidget },
|
||||
{ widget: mediaReleasesWidget }
|
||||
]}
|
||||
/>
|
||||
|
||||
<IntegrationCapabilites items={[{ widget: mediaServerWidget }, { widget: mediaReleasesWidget }]} />
|
||||
|
||||
### Adding the integration
|
||||
|
||||
<AddingIntegration />
|
||||
|
||||
### Secrets
|
||||
<IntegrationSecrets secrets={[{
|
||||
credentials: ['apiKey'],
|
||||
steps: [
|
||||
|
||||
<IntegrationSecrets
|
||||
secrets={[
|
||||
{
|
||||
credentials: ["apiKey"],
|
||||
steps: [
|
||||
<span>
|
||||
Follow the official <a href="https://support.plex.tv/articles/204059436-finding-an-authentication-token-x-plex-token/" target="_blank" rel="noopener noreferrer">Plex documentation</a> to find your authentication token.
|
||||
</span>
|
||||
]
|
||||
}]} />
|
||||
Follow the official{" "}
|
||||
<a
|
||||
href="https://support.plex.tv/articles/204059436-finding-an-authentication-token-x-plex-token/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Plex documentation
|
||||
</a>{" "}
|
||||
to find your authentication token.
|
||||
</span>,
|
||||
],
|
||||
},
|
||||
]}
|
||||
/>
|
||||
@@ -1,32 +1,34 @@
|
||||
---
|
||||
title: 'Prowlarr'
|
||||
title: "Prowlarr"
|
||||
description: "Prowlarr is an indexer manager for Usenet and BitTorrent, providing a unified interface to manage your indexers."
|
||||
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 { 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 { IconHeartRateMonitor } from "@tabler/icons-react";
|
||||
import { prowlarrIntegration } from '.';
|
||||
import { indexerManagerWidget } from '@site/docs/widgets/indexer-manager';
|
||||
import { prowlarrIntegration } from ".";
|
||||
import { indexerManagerWidget } from "@site/docs/widgets/indexer-manager";
|
||||
|
||||
<IntegrationHeader
|
||||
integration={prowlarrIntegration}
|
||||
categories={['Indexer']}
|
||||
/>
|
||||
<IntegrationHeader integration={prowlarrIntegration} categories={["Indexer"]} />
|
||||
|
||||
### Widgets & Capabilities
|
||||
<IntegrationCapabilites
|
||||
items={[{ widget: indexerManagerWidget }]}
|
||||
/>
|
||||
|
||||
<IntegrationCapabilites items={[{ widget: indexerManagerWidget }]} />
|
||||
|
||||
### Adding the integration
|
||||
|
||||
<AddingIntegration />
|
||||
|
||||
### Secrets
|
||||
<IntegrationSecrets secrets={[{
|
||||
credentials: ['apiKey'],
|
||||
steps: []
|
||||
}]} />
|
||||
|
||||
<IntegrationSecrets
|
||||
secrets={[
|
||||
{
|
||||
credentials: ["apiKey"],
|
||||
steps: [],
|
||||
},
|
||||
]}
|
||||
/>
|
||||
@@ -1,103 +1,107 @@
|
||||
---
|
||||
title: 'Proxmox'
|
||||
title: "Proxmox"
|
||||
description: "Proxmox is a powerful open-source virtualization platform that allows you to run virtual machines and containers."
|
||||
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 { 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 { IconHeartRateMonitor } from "@tabler/icons-react";
|
||||
import { proxmoxIntegration } from '.';
|
||||
import Alert from '@theme/Admonition'
|
||||
import { healthMonitoringWidget } from '@site/docs/widgets/health-monitoring';
|
||||
import { systemResourcesWidget } from '@site/docs/widgets/system-resources';
|
||||
import { proxmoxIntegration } from ".";
|
||||
import Alert from "@theme/Admonition";
|
||||
import { healthMonitoringWidget } from "@site/docs/widgets/health-monitoring";
|
||||
import { systemResourcesWidget } from "@site/docs/widgets/system-resources";
|
||||
|
||||
<IntegrationHeader
|
||||
integration={proxmoxIntegration}
|
||||
categories={['System monitoring']}
|
||||
/>
|
||||
<IntegrationHeader integration={proxmoxIntegration} categories={["System monitoring"]} />
|
||||
|
||||
### Widgets & Capabilities
|
||||
<IntegrationCapabilites
|
||||
items={[
|
||||
{ widget: healthMonitoringWidget },
|
||||
|
||||
]}
|
||||
/>
|
||||
|
||||
<IntegrationCapabilites items={[{ widget: healthMonitoringWidget }]} />
|
||||
|
||||
### Adding the integration
|
||||
|
||||
<AddingIntegration />
|
||||
|
||||
### Secrets
|
||||
<IntegrationSecrets secrets={[{
|
||||
header: <Alert type="caution">
|
||||
The Proxmox API may not work with Linux PAM standard authentication. It is strongly recommended to use the Proxmox VE authentication server realm when creating your API user.
|
||||
</Alert>,
|
||||
credentials: ['username', 'tokenId', 'realm', 'apiKey'],
|
||||
steps: [
|
||||
|
||||
<IntegrationSecrets
|
||||
secrets={[
|
||||
{
|
||||
header: (
|
||||
<Alert type="caution">
|
||||
The Proxmox API may not work with Linux PAM standard authentication. It is strongly recommended to use the
|
||||
Proxmox VE authentication server realm when creating your API user.
|
||||
</Alert>
|
||||
),
|
||||
credentials: ["username", "tokenId", "realm", "apiKey"],
|
||||
steps: [
|
||||
"Navigate to the Proxmox portal, click on Datacenter",
|
||||
"Expand Permissions, click on Groups",
|
||||
"Click the Create button",
|
||||
"Name the group something informative, like api-users",
|
||||
"Click on the Permissions 'folder'",
|
||||
<>
|
||||
Click Add {"->"} Group Permission
|
||||
<ul>
|
||||
<li>Path: /</li>
|
||||
<li>Group: group from Step 4 above</li>
|
||||
<li>Role: PVEAuditor</li>
|
||||
<li>Propagate: Checked</li>
|
||||
</ul>
|
||||
Click Add {"->"} Group Permission
|
||||
<ul>
|
||||
<li>Path: /</li>
|
||||
<li>Group: group from Step 4 above</li>
|
||||
<li>Role: PVEAuditor</li>
|
||||
<li>Propagate: Checked</li>
|
||||
</ul>
|
||||
</>,
|
||||
"Expand Permissions, click on Users",
|
||||
<>
|
||||
Click the Add button
|
||||
<ul>
|
||||
<li>User name: something informative like api</li>
|
||||
<li>Realm: Proxmox VE authentication server</li>
|
||||
<li>Password: create a secure password for the user</li>
|
||||
<li>Confirm Password: re-enter the password</li>
|
||||
<li>Group: group from Step 4 above</li>
|
||||
</ul>
|
||||
Click the Add button
|
||||
<ul>
|
||||
<li>User name: something informative like api</li>
|
||||
<li>Realm: Proxmox VE authentication server</li>
|
||||
<li>Password: create a secure password for the user</li>
|
||||
<li>Confirm Password: re-enter the password</li>
|
||||
<li>Group: group from Step 4 above</li>
|
||||
</ul>
|
||||
</>,
|
||||
"Expand Permissions, click on API Tokens",
|
||||
<>
|
||||
Click the Add button
|
||||
<ul>
|
||||
<li>User: user from Step 8 above</li>
|
||||
<li>Token ID: something informative like the application or purpose like homarr</li>
|
||||
<li>Privilege Separation: unchecked</li>
|
||||
</ul>
|
||||
Click the Add button
|
||||
<ul>
|
||||
<li>User: user from Step 8 above</li>
|
||||
<li>Token ID: something informative like the application or purpose like homarr</li>
|
||||
<li>Privilege Separation: unchecked</li>
|
||||
</ul>
|
||||
</>,
|
||||
"Copy the Secret that is shown in Step 10 because it is only shown once",
|
||||
"Go back to the 'Permissions' menu",
|
||||
<>
|
||||
Click Add {"->"} API Token Permission
|
||||
<ul>
|
||||
<li>Path: /</li>
|
||||
<li>API Token: select the API token created in Step 10</li>
|
||||
<li>Role: PVE Auditor</li>
|
||||
<li>Propagate: Checked</li>
|
||||
</ul>
|
||||
Click Add {"->"} API Token Permission
|
||||
<ul>
|
||||
<li>Path: /</li>
|
||||
<li>API Token: select the API token created in Step 10</li>
|
||||
<li>Role: PVE Auditor</li>
|
||||
<li>Propagate: Checked</li>
|
||||
</ul>
|
||||
</>,
|
||||
],
|
||||
body: <>
|
||||
<span>
|
||||
Sometimes you'll also find the <code>api@pve!homarr</code> format,
|
||||
within Homarr you'll have to split it up into:
|
||||
</span>
|
||||
<ul>
|
||||
],
|
||||
body: (
|
||||
<>
|
||||
<span>
|
||||
Sometimes you'll also find the <code>api@pve!homarr</code> format, within Homarr you'll have to split it up
|
||||
into:
|
||||
</span>
|
||||
<ul>
|
||||
<li>
|
||||
<b>Username:</b> api
|
||||
<b>Username:</b> api
|
||||
</li>
|
||||
<li>
|
||||
<b>Realm:</b> pve
|
||||
<b>Realm:</b> pve
|
||||
</li>
|
||||
<li>
|
||||
<b>Token ID:</b> homarr
|
||||
<b>Token ID:</b> homarr
|
||||
</li>
|
||||
</ul>
|
||||
</>
|
||||
}]} />
|
||||
</ul>
|
||||
</>
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
@@ -1,37 +1,39 @@
|
||||
---
|
||||
title: 'qBittorrent'
|
||||
title: "qBittorrent"
|
||||
description: "qBittorrent is a free and open-source BitTorrent client."
|
||||
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 { 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 { IconDownload } from "@tabler/icons-react";
|
||||
import { qBittorentIntegration } from '.';
|
||||
import Alert from '@theme/Admonition';
|
||||
import { downloadsWidget } from '@site/docs/widgets/downloads';
|
||||
import { qBittorentIntegration } from ".";
|
||||
import Alert from "@theme/Admonition";
|
||||
import { downloadsWidget } from "@site/docs/widgets/downloads";
|
||||
|
||||
|
||||
<IntegrationHeader
|
||||
integration={qBittorentIntegration}
|
||||
categories={['Torrent client']}
|
||||
/>
|
||||
<IntegrationHeader integration={qBittorentIntegration} categories={["Torrent client"]} />
|
||||
|
||||
### Widgets & Capabilities
|
||||
<IntegrationCapabilites
|
||||
items={[{ widget: downloadsWidget }]}
|
||||
/>
|
||||
|
||||
<IntegrationCapabilites items={[{ widget: downloadsWidget }]} />
|
||||
|
||||
### Adding the integration
|
||||
|
||||
<AddingIntegration />
|
||||
|
||||
### Secrets
|
||||
<IntegrationSecrets secrets={[{
|
||||
credentials: ['apiKey'],
|
||||
steps: []
|
||||
},{
|
||||
credentials: ['username', 'password'],
|
||||
steps: [],
|
||||
}]} />
|
||||
|
||||
<IntegrationSecrets
|
||||
secrets={[
|
||||
{
|
||||
credentials: ["apiKey"],
|
||||
steps: [],
|
||||
},
|
||||
{
|
||||
credentials: ["username", "password"],
|
||||
steps: [],
|
||||
},
|
||||
]}
|
||||
/>
|
||||
@@ -1,33 +1,34 @@
|
||||
---
|
||||
title: 'Radarr'
|
||||
title: "Radarr"
|
||||
description: "Radarr is a movie management tool that automates the process of downloading, sorting, and renaming movies."
|
||||
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 { 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 { IconCalendar } from "@tabler/icons-react";
|
||||
import { radarrIntegration } from '.';
|
||||
import { calendarWidget } from '@site/docs/widgets/calendar';
|
||||
import { radarrIntegration } from ".";
|
||||
import { calendarWidget } from "@site/docs/widgets/calendar";
|
||||
|
||||
|
||||
<IntegrationHeader
|
||||
integration={radarrIntegration}
|
||||
categories={['Media manager', "Servarr"]}
|
||||
/>
|
||||
<IntegrationHeader integration={radarrIntegration} categories={["Media manager", "Servarr"]} />
|
||||
|
||||
### Widgets & Capabilities
|
||||
<IntegrationCapabilites
|
||||
items={[{ widget: calendarWidget }]}
|
||||
/>
|
||||
|
||||
<IntegrationCapabilites items={[{ widget: calendarWidget }]} />
|
||||
|
||||
### Adding the integration
|
||||
|
||||
<AddingIntegration />
|
||||
|
||||
### Secrets
|
||||
<IntegrationSecrets secrets={[{
|
||||
credentials: ['apiKey'],
|
||||
steps: []
|
||||
}]} />
|
||||
|
||||
<IntegrationSecrets
|
||||
secrets={[
|
||||
{
|
||||
credentials: ["apiKey"],
|
||||
steps: [],
|
||||
},
|
||||
]}
|
||||
/>
|
||||
@@ -1,33 +1,34 @@
|
||||
---
|
||||
title: 'Readarr'
|
||||
title: "Readarr"
|
||||
description: "Readarr is a book management tool that automates the process of downloading, sorting, and renaming books."
|
||||
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 { 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 { IconCalendar } from "@tabler/icons-react";
|
||||
import { readarrIntegration } from '.';
|
||||
import { calendarWidget } from '@site/docs/widgets/calendar';
|
||||
import { readarrIntegration } from ".";
|
||||
import { calendarWidget } from "@site/docs/widgets/calendar";
|
||||
|
||||
|
||||
<IntegrationHeader
|
||||
integration={readarrIntegration}
|
||||
categories={['Media manager', "Servarr"]}
|
||||
/>
|
||||
<IntegrationHeader integration={readarrIntegration} categories={["Media manager", "Servarr"]} />
|
||||
|
||||
### Widgets & Capabilities
|
||||
<IntegrationCapabilites
|
||||
items={[{ widget: calendarWidget }]}
|
||||
/>
|
||||
|
||||
<IntegrationCapabilites items={[{ widget: calendarWidget }]} />
|
||||
|
||||
### Adding the integration
|
||||
|
||||
<AddingIntegration />
|
||||
|
||||
### Secrets
|
||||
<IntegrationSecrets secrets={[{
|
||||
credentials: ['apiKey'],
|
||||
steps: []
|
||||
}]} />
|
||||
|
||||
<IntegrationSecrets
|
||||
secrets={[
|
||||
{
|
||||
credentials: ["apiKey"],
|
||||
steps: [],
|
||||
},
|
||||
]}
|
||||
/>
|
||||
@@ -1,34 +1,35 @@
|
||||
---
|
||||
title: 'SABnzbd'
|
||||
title: "SABnzbd"
|
||||
description: "SABnzbd is an Open Source Binary Newsreader."
|
||||
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 { 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 { IconDownload } from "@tabler/icons-react";
|
||||
import { sabnzbdIntegration } from '.';
|
||||
import Alert from '@theme/Admonition';
|
||||
import { downloadsWidget } from '@site/docs/widgets/downloads';
|
||||
import { sabnzbdIntegration } from ".";
|
||||
import Alert from "@theme/Admonition";
|
||||
import { downloadsWidget } from "@site/docs/widgets/downloads";
|
||||
|
||||
|
||||
<IntegrationHeader
|
||||
integration={sabnzbdIntegration}
|
||||
categories={['Usenet client']}
|
||||
/>
|
||||
<IntegrationHeader integration={sabnzbdIntegration} categories={["Usenet client"]} />
|
||||
|
||||
### Widgets & Capabilities
|
||||
<IntegrationCapabilites
|
||||
items={[{ widget: downloadsWidget}]}
|
||||
/>
|
||||
|
||||
<IntegrationCapabilites items={[{ widget: downloadsWidget }]} />
|
||||
|
||||
### Adding the integration
|
||||
|
||||
<AddingIntegration />
|
||||
|
||||
### Secrets
|
||||
<IntegrationSecrets secrets={[{
|
||||
credentials: ['apiKey'],
|
||||
steps: [],
|
||||
}]} />
|
||||
|
||||
<IntegrationSecrets
|
||||
secrets={[
|
||||
{
|
||||
credentials: ["apiKey"],
|
||||
steps: [],
|
||||
},
|
||||
]}
|
||||
/>
|
||||
@@ -1,45 +1,52 @@
|
||||
---
|
||||
title: 'Seerr'
|
||||
title: "Seerr"
|
||||
description: "Seerr is a self-hosted media request management system that integrates with various media servers."
|
||||
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 { 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 { IconZoomQuestion, IconChartBar, IconSearch } from "@tabler/icons-react";
|
||||
import { seerrIntegration } from '.';
|
||||
import { mediaRequestListWidget } from '@site/docs/widgets/media-request-list';
|
||||
import { mediaRequestStatsWidget } from '@site/docs/widgets/media-request-stats';
|
||||
import { seerrIntegration } from ".";
|
||||
import { mediaRequestListWidget } from "@site/docs/widgets/media-request-list";
|
||||
import { mediaRequestStatsWidget } from "@site/docs/widgets/media-request-stats";
|
||||
|
||||
|
||||
<IntegrationHeader
|
||||
integration={seerrIntegration}
|
||||
categories={['Media requests']}
|
||||
/>
|
||||
<IntegrationHeader integration={seerrIntegration} categories={["Media requests"]} />
|
||||
|
||||
### Widgets & Capabilities
|
||||
|
||||
<IntegrationCapabilites
|
||||
items={[{
|
||||
widget: mediaRequestListWidget
|
||||
}, {
|
||||
widget: mediaRequestStatsWidget
|
||||
}, {
|
||||
capability: {
|
||||
icon: IconSearch,
|
||||
name: "Media Search",
|
||||
description: "Search for movies and TV shows directly from Spotlight and request them.",
|
||||
path: "../../management/search-engines/#media-request-search"
|
||||
}
|
||||
}]}
|
||||
items={[
|
||||
{
|
||||
widget: mediaRequestListWidget,
|
||||
},
|
||||
{
|
||||
widget: mediaRequestStatsWidget,
|
||||
},
|
||||
{
|
||||
capability: {
|
||||
icon: IconSearch,
|
||||
name: "Media Search",
|
||||
description: "Search for movies and TV shows directly from Spotlight and request them.",
|
||||
path: "../../management/search-engines/#media-request-search",
|
||||
},
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
### Adding the integration
|
||||
|
||||
<AddingIntegration />
|
||||
|
||||
### Secrets
|
||||
<IntegrationSecrets secrets={[{
|
||||
credentials: ['apiKey'],
|
||||
steps: []
|
||||
}]} />
|
||||
|
||||
<IntegrationSecrets
|
||||
secrets={[
|
||||
{
|
||||
credentials: ["apiKey"],
|
||||
steps: [],
|
||||
},
|
||||
]}
|
||||
/>
|
||||
@@ -1,34 +1,39 @@
|
||||
---
|
||||
title: 'Slskd'
|
||||
title: "Slskd"
|
||||
description: "A modern client-server application for the Soulseek file sharing network."
|
||||
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 { slskdIntegration } from '.';
|
||||
import { downloadsWidget } from '@site/docs/widgets/downloads';
|
||||
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 { slskdIntegration } from ".";
|
||||
import { downloadsWidget } from "@site/docs/widgets/downloads";
|
||||
|
||||
|
||||
<IntegrationHeader
|
||||
integration={slskdIntegration}
|
||||
categories={['System Monitoring']}
|
||||
/>
|
||||
<IntegrationHeader integration={slskdIntegration} categories={["System Monitoring"]} />
|
||||
|
||||
### Widgets & Capabilities
|
||||
|
||||
<IntegrationCapabilites
|
||||
items={[{
|
||||
widget: downloadsWidget
|
||||
}]}
|
||||
items={[
|
||||
{
|
||||
widget: downloadsWidget,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
### Adding the integration
|
||||
|
||||
<AddingIntegration />
|
||||
|
||||
### Secrets
|
||||
<IntegrationSecrets secrets={[{
|
||||
credentials: ["apiKey"],
|
||||
steps: []
|
||||
}]} />
|
||||
|
||||
<IntegrationSecrets
|
||||
secrets={[
|
||||
{
|
||||
credentials: ["apiKey"],
|
||||
steps: [],
|
||||
},
|
||||
]}
|
||||
/>
|
||||
Loaded 100 of 810 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user