Skip to content

Commit

Permalink
feat(api,docker): remove the need for multiple volume mounts
Browse files Browse the repository at this point in the history
BREAKING CHANGE: Previously, you needed to volume mount every disk seperately. Now you can simply do
it once with /:/mnt/host:ro.
  • Loading branch information
MauriceNino committed Jul 1, 2022
1 parent 9fd04af commit ea8160e
Show file tree
Hide file tree
Showing 14 changed files with 106 additions and 108 deletions.
12 changes: 6 additions & 6 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
FROM node:18-alpine AS base

WORKDIR /app

ARG TARGETPLATFORM
ENV DASHDOT_RUNNING_IN_DOCKER=true

RUN \
/bin/echo ">> installing dependencies" &&\
Expand Down Expand Up @@ -40,15 +40,15 @@ RUN \
# DEV #
FROM base AS dev

EXPOSE 3001
EXPOSE 3000

RUN \
/bin/echo -e ">> installing dependencies (dev)" &&\
apk --no-cache add \
git &&\
git config --global --add safe.directory /app

EXPOSE 3001
EXPOSE 3000

# BUILD #
FROM base as build

Expand All @@ -74,13 +74,13 @@ RUN \
# PROD #
FROM base as prod

EXPOSE 3001

COPY --from=build /app/package.json .
COPY --from=build /app/version.json .
COPY --from=build /app/.yarn/releases/ .yarn/releases/
COPY --from=build /app/dist/apps/api dist/apps/api
COPY --from=build /app/dist/apps/cli dist/apps/cli
COPY --from=build /app/dist/apps/view dist/apps/view

EXPOSE 3001

CMD ["yarn", "start"]
4 changes: 1 addition & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,7 @@ and are available for both AMD64 and ARM devices.
docker container run -it \
-p 80:3001 \
-v /etc/os-release:/etc/os-release:ro \
-v /proc/1/ns/net:/mnt/host_ns_net:ro \
-v /media:/mnt/host_media:ro \
-v /mnt:/mnt/host_mnt:ro \
-v /:/mnt/host:ro \
--privileged \
mauricenino/dashdot
```
Expand Down
8 changes: 5 additions & 3 deletions apps/api/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,14 @@ const numlst = (item: string): number[] => lst(item).map(numNull);

export const CONFIG: Config = {
port: numNull(penv('PORT')) ?? 3001,
show_host: penv('SHOW_HOST') === 'true',
running_in_docker: penv('RUNNING_IN_DOCKER') === 'true',
accept_ookla_eula: penv('ACCEPT_OOKLA_EULA') === 'true',
use_imperial: penv('USE_IMPERIAL') === 'true',
use_network_interface: penv('USE_NETWORK_INTERFACE') ?? '',
disable_integrations: penv('DISABLE_INTEGRATIONS') === 'true',

show_host: penv('SHOW_HOST') === 'true',
use_imperial: penv('USE_IMPERIAL') === 'true',
enable_storage_split_view: penv('ENABLE_STORAGE_SPLIT_VIEW') === 'true',
use_network_interface: penv('USE_NETWORK_INTERFACE') ?? '',
always_show_percentages: penv('ALWAYS_SHOW_PERCENTAGES') === 'true',

widget_list: lst(
Expand Down
35 changes: 25 additions & 10 deletions apps/api/src/dynamic-info.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { interval, mergeMap, Observable, ReplaySubject } from 'rxjs';
import * as si from 'systeminformation';
import { inspect, promisify } from 'util';
import { CONFIG } from './config';
import { NET_INTERFACE } from './setup-networking';
import { NET_INTERFACE_PATH } from './setup-networking';
import { getStaticServerInfo, runSpeedTest } from './static-info';

const exec = promisify(cexec);
Expand Down Expand Up @@ -87,6 +87,8 @@ export const getDynamicServerInfo = () => {
}
);

const INVALID_FS_TYPES = ['cifs', '9p'];

const storageObs = createBufferedInterval(
'Storage',
CONFIG.widget_list.includes('storage'),
Expand All @@ -101,30 +103,43 @@ export const getDynamicServerInfo = () => {

const storageLayout = layout.storage.layout;
const validMounts = sizes.filter(
({ mount }) => mount.startsWith('/mnt/host_') || mount === '/'
({ mount, type }) =>
mount.startsWith('/mnt/host/') && !INVALID_FS_TYPES.includes(type)
);
const hostMountUsed =
sizes.filter(({ mount }) => mount === '/mnt/host' || mount === '/')[0]
?.used ?? 0;
const validParts = blocks.filter(({ type }) => type === 'part');

let hostFound = false;

return {
layout: storageLayout
.map(({ device }) => {
.map(({ device, size }) => {
const deviceParts = validParts.filter(({ name }) =>
name.startsWith(device)
);
const potentialHost = deviceParts.every(
({ mount }) => mount == null || !mount.startsWith('/mnt/host_')
);
const potentialHost =
// drives that have all partitions unmounted
deviceParts.every(
({ mount }) => mount == null || !mount.startsWith('/mnt/host/')
) ||
// drives where one of the partitions is mounted to the root or /mnt/host/boot/
deviceParts.some(
({ mount }) =>
mount === '/mnt/host' || mount.startsWith('/mnt/host/boot/')
);

// Apply all unclaimed partitions to the host disk
if (potentialHost && !hostFound) {
hostFound = true;
return validMounts
const unclaimedSpace = validMounts
.filter(
({ mount }) => !validParts.some(part => part.mount === mount)
)
.reduce((acc, { used }) => acc + used, 0);

return hostMountUsed + unclaimedSpace;
}

return potentialHost
Expand Down Expand Up @@ -152,10 +167,10 @@ export const getDynamicServerInfo = () => {
CONFIG.network_shown_datapoints,
CONFIG.network_poll_interval,
async (): Promise<NetworkLoad> => {
if (NET_INTERFACE !== 'unknown') {
if (NET_INTERFACE_PATH) {
const { stdout } = await exec(
`cat /internal_mnt/host_sys/class/net/${NET_INTERFACE}/statistics/rx_bytes;` +
`cat /internal_mnt/host_sys/class/net/${NET_INTERFACE}/statistics/tx_bytes;`
`cat ${NET_INTERFACE_PATH}/statistics/rx_bytes;` +
`cat ${NET_INTERFACE_PATH}/statistics/tx_bytes;`
);
const [rx, tx] = stdout.split('\n').map(Number);
const thisTs = performance.now();
Expand Down
92 changes: 43 additions & 49 deletions apps/api/src/setup-networking.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,65 +5,59 @@ import { CONFIG } from './config';

const exec = promisify(exaca);

export let NET_INTERFACE = 'unknown';
const NET_PATH = CONFIG.running_in_docker
? '/mnt/host/sys/class/net/'
: '/sys/class/net/';
const NS_NET = '/mnt/host/proc/1/ns/net';
export let NET_INTERFACE_PATH = undefined;

export const setupNetworking = async () => {
if (fs.existsSync('/mnt/host_ns_net')) {
try {
await exec('mkdir -p /internal_mnt/host_sys');
await exec(
'mountpoint -q /internal_mnt/host_sys || nsenter --net=/mnt/host_ns_net mount -t sysfs nodevice /internal_mnt/host_sys'
);
} catch (e) {
console.warn(e);
}

try {
if (CONFIG.use_network_interface !== '') {
if (
fs.existsSync(
`/internal_mnt/host_sys/class/net/${CONFIG.use_network_interface}`
)
) {
NET_INTERFACE = CONFIG.use_network_interface;
console.log(`Using network interface from config "${NET_INTERFACE}"`);
try {
if (CONFIG.use_network_interface !== '') {
const path = `${NET_PATH}${CONFIG.use_network_interface}`;
if (fs.existsSync(path)) {
NET_INTERFACE_PATH = path;
console.log(
`Using network interface from config "${CONFIG.use_network_interface}"`
);

return;
} else {
console.warn(
`Network interface "${CONFIG.use_network_interface}" not found, using first available interface`
);
}
return;
} else {
console.warn(
`Network interface "${CONFIG.use_network_interface}" not found, using first available interface`
);
}
}
} catch (e) {
console.warn(e);
}

const { stdout } = await exec(
"nsenter --net=/mnt/host_ns_net route | grep default | awk '{print $8}'"
);
if (CONFIG.running_in_docker && fs.existsSync(NS_NET)) {
const { stdout } = await exec(
`nsenter --net=${NS_NET} route | grep default | awk '{print $8}'`
);

const ifaces = stdout.split('\n');
const iface = ifaces[0].trim();
const ifaces = stdout.split('\n');
const iface = ifaces[0].trim();

if (ifaces.length > 1) {
console.warn(
`Multiple default network interfaces found [${ifaces.join(
', '
)}], using "${iface}"`
);
}
if (ifaces.length > 1) {
console.warn(
`Multiple default network interfaces found [${ifaces.join(
', '
)}], using "${iface}"`
);
}

if (iface !== '') {
NET_INTERFACE = iface;
if (iface !== '') {
NET_INTERFACE_PATH = `${NET_PATH}${iface}`;

console.log(`Using network interface "${NET_INTERFACE}"`);
} else {
console.warn(
'Unable to determine network interface, using default container network interface'
);
}
} catch (e) {
console.warn(e);
console.log(`Using network interface "${iface}"`);
} else {
console.warn(
'Unable to determine network interface, using default container network interface'
);
}
} else {
console.log(`Using default container network interface`);
console.log(`Using default (container) network interface`);
}
};
15 changes: 7 additions & 8 deletions apps/api/src/static-info.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import * as si from 'systeminformation';
import { SpeedUnits, UniversalSpeedtest } from 'universal-speedtest';
import { inspect, promisify } from 'util';
import { CONFIG } from './config';
import { NET_INTERFACE } from './setup-networking';
import { NET_INTERFACE_PATH } from './setup-networking';

const exec = promisify(cexec);

Expand Down Expand Up @@ -164,12 +164,11 @@ const loadStorageInfo = async (): Promise<void> => {
};

const loadNetworkInfo = async (): Promise<void> => {
if (NET_INTERFACE !== 'unknown') {
const NET_PATH = `/internal_mnt/host_sys/class/net/${NET_INTERFACE}`;
const isWireless = fs.existsSync(`${NET_PATH}/wireless`);
const isBridge = fs.existsSync(`${NET_PATH}/bridge`);
const isBond = fs.existsSync(`${NET_PATH}/bonding`);
const isTap = fs.existsSync(`${NET_PATH}/tun_flags`);
if (NET_INTERFACE_PATH) {
const isWireless = fs.existsSync(`${NET_INTERFACE_PATH}/wireless`);
const isBridge = fs.existsSync(`${NET_INTERFACE_PATH}/bridge`);
const isBond = fs.existsSync(`${NET_INTERFACE_PATH}/bonding`);
const isTap = fs.existsSync(`${NET_INTERFACE_PATH}/tun_flags`);

STATIC_INFO.next({
...STATIC_INFO.getValue(),
Expand All @@ -189,7 +188,7 @@ const loadNetworkInfo = async (): Promise<void> => {

// Wireless networks have no fixed Interface speed
if (!isWireless) {
const { stdout } = await exec(`cat ${NET_PATH}/speed`);
const { stdout } = await exec(`cat ${NET_INTERFACE_PATH}/speed`);
const numValue = Number(stdout.trim());

STATIC_INFO.next({
Expand Down
8 changes: 6 additions & 2 deletions apps/cli/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ yargs(hideBin(process.argv))
const buildInfoJson = await execpnoerr('cat version.json');
const gitHash = await execpnoerr('git log -1 --format="%H"');

const runningInDocker = await execpnoerr(
'echo $DASHDOT_RUNNING_IN_DOCKER'
);
const buildInfo = JSON.parse(buildInfoJson || '{}');
const version = buildInfo.version ?? 'unknown';
const buildhash = buildInfo.buildhash ?? gitHash;
Expand All @@ -51,6 +54,7 @@ Cwd: ${process.cwd()}
Hash: ${buildhash}
In Docker: ${isDocker}
In Podman: ${isPodman}
In Docker (env): ${runningInDocker}
`.trim()
);
}
Expand Down Expand Up @@ -105,7 +109,7 @@ In Podman: ${isPodman}
if (args.storage) {
console.log('Disk Layout:', inspectObj(await si.diskLayout()));
console.log('FS Size:', inspectObj(await si.fsSize()));
console.log('BLock Devices:', inspectObj(await si.blockDevices()));
console.log('Block Devices:', inspectObj(await si.blockDevices()));
}
if (args.network) {
console.log(
Expand All @@ -119,7 +123,7 @@ In Podman: ${isPodman}
}
if (args.custom) {
console.log(
`Custom [${args.custom}]`,
`Custom [${args.custom}]:`,
inspectObj(await si[args.custom]())
);
}
Expand Down
2 changes: 1 addition & 1 deletion apps/docs/docs/help.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ only enabled on mobile devices. If you want to bring back the old behavior, ther
<details>
<summary>The network information can not be read correctly - what should I do?</summary>

First of all, if you are running docker, make sure that you are passing the `-v /proc/1/ns/net:/mnt/host_ns_net:ro`
First of all, if you are running docker, make sure that you are passing the `-v /:/mnt/host:ro`
bind mount. If you have done so, and it still does not work, please do the following:

> Check your logs for a message like `Using network interface "xxxxx"`.
Expand Down
4 changes: 1 addition & 3 deletions apps/docs/docs/install/docker-compose.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,7 @@ services:
- '80:3001'
volumes:
- /etc/os-release:/etc/os-release:ro
- /proc/1/ns/net:/mnt/host_ns_net:ro
- /media:/mnt/host_media:ro
- /mnt:/mnt/host_mnt:ro
- /:/mnt/host:ro
```
## Configuration
Expand Down
4 changes: 1 addition & 3 deletions apps/docs/docs/install/docker.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,7 @@ docker container run -it \
-p 80:3001 \
--privileged \
-v /etc/os-release:/etc/os-release:ro \
-v /proc/1/ns/net:/mnt/host_ns_net:ro \
-v /media:/mnt/host_media:ro \
-v /mnt:/mnt/host_mnt:ro \
-v /:/mnt/host:ro \
mauricenino/dashdot
```

Expand Down
14 changes: 3 additions & 11 deletions apps/docs/docs/install/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,7 @@ and are available for both AMD64 and ARM devices.
docker container run -it \
-p 80:3001 \
-v /etc/os-release:/etc/os-release:ro \
-v /proc/1/ns/net:/mnt/host_ns_net:ro \
-v /media:/mnt/host_media:ro \
-v /mnt:/mnt/host_mnt:ro \
-v /:/mnt/host:ro \
--privileged \
mauricenino/dashdot
```
Expand All @@ -32,14 +30,8 @@ docker container run -it \
container OS instead, just remove this line. If you are not able to use this
mount, you can pass a custom OS with the `DASHDOT_OVERRIDE_OS` flag.

- The volume mount on `/proc/1/ns/net:/host_ns_net:ro` is needed to
correctly determine the network info. If you are not able to use this mount,
you will need to fall back to `--net host`, or you will only get the network
stats of the container instead of the host.

- The volume mounts on `/media:/mnt/host_media:ro` and `/mnt:/mnt/host_mnt:ro`
are needed to read the usage stats of all drives. If your drives are mounted somewhere
else, you need to pass that drive path with the following format: `-v /{path}:/mnt/host_{path}:ro`.
- The volume mounts on `/:/mnt/host:ro` is needed to read the usage stats of all drives and also
for reading the correct network stats.

:::

Expand Down
Loading

0 comments on commit ea8160e

Please sign in to comment.