refactor: allow to run without the SYS_ADMIN cap for simple setups (#138)
Some checks failed
Release Workflow / determine-release-type (push) Has been cancelled
Release Workflow / build-images (push) Has been cancelled
Release Workflow / publish-release (push) Has been cancelled

* refactor: allow to run without the SYS_ADMIN cap for simple setups

* fix: pr feedback
This commit is contained in:
Nico 2025-12-14 20:30:33 +01:00 committed by GitHub
parent 95e4fe5547
commit b23317354c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 120 additions and 10 deletions

View file

@ -67,6 +67,34 @@ docker compose up -d
Once the container is running, you can access the web interface at `http://<your-server-ip>:4096`.
### Simplified setup (No remote mounts)
If you only need to back up locally mounted folders and don't require remote share mounting capabilities, you can remove the `SYS_ADMIN` capability and FUSE device from your `docker-compose.yml`:
```yaml
services:
zerobyte:
image: ghcr.io/nicotsx/zerobyte:v0.18
container_name: zerobyte
restart: unless-stopped
ports:
- "4096:4096"
environment:
- TZ=Europe/Paris # Set your timezone here
volumes:
- /etc/localtime:/etc/localtime:ro
- /var/lib/zerobyte:/var/lib/zerobyte
- /path/to/your/directory:/mydata
```
**Trade-offs:**
- ✅ Improved security by reducing container capabilities
- ✅ Support for local directories
- ✅ Keep support all repository types (local, S3, GCS, Azure, rclone)
- ❌ Cannot mount NFS, SMB, or WebDAV shares directly from Zerobyte
If you need remote mount capabilities, keep the original configuration with `cap_add: SYS_ADMIN` and `devices: /dev/fuse:/dev/fuse`.
## Adding your first volume
Zerobyte supports multiple volume backends including NFS, SMB, WebDAV, and local directories. A volume represents the source data you want to back up and monitor.

View file

@ -2855,6 +2855,7 @@ export type GetSystemInfoResponses = {
200: {
capabilities: {
rclone: boolean;
sysAdmin: boolean;
};
};
};

View file

@ -10,7 +10,7 @@ export function useSystemInfo() {
});
return {
capabilities: data?.capabilities ?? { rclone: false },
capabilities: data?.capabilities ?? { rclone: false, sysAdmin: false },
isLoading,
error,
systemInfo: data,

View file

@ -64,7 +64,6 @@ export const CreateVolumeForm = ({ onSubmit, mode = "create", initialValues, for
const { watch, getValues } = form;
const { capabilities } = useSystemInfo();
const watchedBackend = watch("backend");
useEffect(() => {
@ -142,16 +141,54 @@ export const CreateVolumeForm = ({ onSubmit, mode = "create", initialValues, for
</FormControl>
<SelectContent>
<SelectItem value="directory">Directory</SelectItem>
<SelectItem value="nfs">NFS</SelectItem>
<SelectItem value="smb">SMB</SelectItem>
<SelectItem value="webdav">WebDAV</SelectItem>
<Tooltip>
<TooltipTrigger>
<SelectItem disabled={!capabilities.rclone} value="rclone">
rclone (40+ cloud providers)
</SelectItem>
<TooltipTrigger asChild>
<div>
<SelectItem disabled={!capabilities.sysAdmin} value="nfs">
NFS
</SelectItem>
</div>
</TooltipTrigger>
<TooltipContent className={cn({ hidden: capabilities.rclone })}>
<TooltipContent className={cn({ hidden: capabilities.sysAdmin })}>
<p>Remote mounts require SYS_ADMIN capability</p>
</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger asChild>
<div>
<SelectItem disabled={!capabilities.sysAdmin} value="smb">
SMB
</SelectItem>
</div>
</TooltipTrigger>
<TooltipContent className={cn({ hidden: capabilities.sysAdmin })}>
<p>Remote mounts require SYS_ADMIN capability</p>
</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger asChild>
<div>
<SelectItem disabled={!capabilities.sysAdmin} value="webdav">
WebDAV
</SelectItem>
</div>
</TooltipTrigger>
<TooltipContent className={cn({ hidden: capabilities.sysAdmin })}>
<p>Remote mounts require SYS_ADMIN capability</p>
</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger asChild>
<div>
<SelectItem disabled={!capabilities.rclone || !capabilities.sysAdmin} value="rclone">
rclone
</SelectItem>
</div>
</TooltipTrigger>
<TooltipContent className={cn({ hidden: capabilities.sysAdmin })}>
<p>Remote mounts require SYS_ADMIN capability</p>
</TooltipContent>
<TooltipContent className={cn({ hidden: !capabilities.sysAdmin || capabilities.rclone })}>
<p>Setup rclone to use this backend</p>
</TooltipContent>
</Tooltip>

View file

@ -3,6 +3,7 @@ import { logger } from "../utils/logger";
export type SystemCapabilities = {
rclone: boolean;
sysAdmin: boolean;
};
let capabilitiesPromise: Promise<SystemCapabilities> | null = null;
@ -27,6 +28,7 @@ export async function getCapabilities(): Promise<SystemCapabilities> {
async function detectCapabilities(): Promise<SystemCapabilities> {
return {
rclone: await detectRclone(),
sysAdmin: await detectSysAdmin(),
};
}
@ -38,6 +40,12 @@ async function detectRclone(): Promise<boolean> {
try {
await fs.access("/root/.config/rclone");
// Make sure the folder is not empty
const files = await fs.readdir("/root/.config/rclone");
if (files.length === 0) {
throw new Error("rclone config directory is empty");
}
logger.info("rclone capability: enabled");
return true;
} catch (_) {
@ -45,3 +53,38 @@ async function detectRclone(): Promise<boolean> {
return false;
}
}
async function detectSysAdmin(): Promise<boolean> {
try {
const procStatus = await fs.readFile("/proc/self/status", "utf-8");
const capEffLine = procStatus.split("\n").find((line) => line.startsWith("CapEff:"));
if (!capEffLine) {
logger.warn("sysAdmin capability: disabled. Could not read CapEff from /proc/self/status");
return false;
}
// Extract the hex value (e.g., "00000000a80425fb")
const capEffHex = capEffLine.split(/\s+/)[1];
if (!capEffHex) {
logger.warn("sysAdmin capability: disabled. Could not parse CapEff value");
return false;
}
// Check if bit 21 (CAP_SYS_ADMIN) is set
const capValue = parseInt(capEffHex, 16) & (1 << 21);
if (capValue !== 0) {
logger.info("sysAdmin capability: enabled (CAP_SYS_ADMIN detected)");
return true;
}
logger.warn("sysAdmin capability: disabled. " + "To enable: add 'cap_add: SYS_ADMIN' in docker-compose.yml");
return false;
} catch (_error) {
logger.warn("sysAdmin capability: disabled. " + "To enable: add 'cap_add: SYS_ADMIN' in docker-compose.yml");
return false;
}
}

View file

@ -3,6 +3,7 @@ import { describeRoute, resolver } from "hono-openapi";
export const capabilitiesSchema = type({
rclone: "boolean",
sysAdmin: "boolean",
});
export const systemInfoResponse = type({