refactor: allow to run without the SYS_ADMIN cap for simple setups (#138)
* refactor: allow to run without the SYS_ADMIN cap for simple setups * fix: pr feedback
This commit is contained in:
parent
95e4fe5547
commit
b23317354c
6 changed files with 120 additions and 10 deletions
28
README.md
28
README.md
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -2855,6 +2855,7 @@ export type GetSystemInfoResponses = {
|
|||
200: {
|
||||
capabilities: {
|
||||
rclone: boolean;
|
||||
sysAdmin: boolean;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ export function useSystemInfo() {
|
|||
});
|
||||
|
||||
return {
|
||||
capabilities: data?.capabilities ?? { rclone: false },
|
||||
capabilities: data?.capabilities ?? { rclone: false, sysAdmin: false },
|
||||
isLoading,
|
||||
error,
|
||||
systemInfo: data,
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { describeRoute, resolver } from "hono-openapi";
|
|||
|
||||
export const capabilitiesSchema = type({
|
||||
rclone: "boolean",
|
||||
sysAdmin: "boolean",
|
||||
});
|
||||
|
||||
export const systemInfoResponse = type({
|
||||
|
|
|
|||
Loading…
Reference in a new issue