diff --git a/Dockerfile.template b/Dockerfile.template index a66d6582..575b26e7 100644 --- a/Dockerfile.template +++ b/Dockerfile.template @@ -37,7 +37,6 @@ RUN strip /usr/local/bin/node # Install fatrw RUN curl -SLO "${FATRW_LOCATION}" && \ - echo curl -SLO "${FATRW_LOCATION}" && \ ls -la "${FATRW_ARCHIVE}" && \ tar -xzf "${FATRW_ARCHIVE}" -C /usr/local/bin && \ rm -f "${FATRW_ARCHIVE}" @@ -45,6 +44,21 @@ RUN curl -SLO "${FATRW_LOCATION}" && \ # Just install dev dependencies first RUN npm ci --build-from-source --sqlite=/usr/lib +################################################################### +# Journal access. +# The supervisor is built on an alpine image but still needs +# to use journalctl (from systemd) which cannot be built for +# musl. We hack around this by copying the binary and its library +# dependencies to the final image +################################################################### +FROM balenalib/${ARCH}-debian:bullseye-run as journal + +RUN apt-get update && apt-get install -y --no-install-recommends systemd + +COPY ./build-utils/setup-journal.sh / +RUN /setup-journal.sh + + ################################################### # Extra dependencies. This uses alpine 3.11 as the # procmail package was removed on 3.12 @@ -70,6 +84,9 @@ COPY --from=build-base /usr/local/bin/fatrw /usr/local/bin/fatrw # Similarly, from the procmail package we just need the lockfile binary COPY --from=extra /usr/bin/lockfile /usr/bin/lockfile +# Copy journalctl and library dependecies to the final image +COPY --from=journal /sysroot / + # Runtime dependencies RUN apk add --no-cache \ ca-certificates \ diff --git a/build-utils/setup-journal.sh b/build-utils/setup-journal.sh new file mode 100755 index 00000000..398baaa6 --- /dev/null +++ b/build-utils/setup-journal.sh @@ -0,0 +1,12 @@ +#!/bin/sh + +set -e + +mkdir -p /sysroot/bin +cp /bin/journalctl /sysroot/bin/ +# Get all library dependencies from the binary +for lib in $(ldd /bin/journalctl | grep -oE '(\/.+?) '); do + mkdir -p "/sysroot/$(dirname "$lib")" + # Copy the dependency dereferencing any symlinks + cp -L "$lib" "/sysroot/$lib" +done diff --git a/entry.sh b/entry.sh index 7f510e85..974dbf5a 100755 --- a/entry.sh +++ b/entry.sh @@ -26,6 +26,16 @@ if [ -n "${BALENA_ROOT_CA}" ]; then fi fi +# Setup necessary directories for journalctl +# NOTE: this won't be necessary once the supervisor can update +# itself, as using the label io.balena.features.journal-logs will +# achieve the same objective +if [ -d /mnt/root/run/log/journal ]; then + mkdir -p /run/log + ln -sf /mnt/root/run/log/journal /run/log/journal + ln -sf /mnt/root/etc/machine-id /etc/machine-id +fi + # Mount the host kernel module path onto the expected location # We need to do this as busybox doesn't support using a custom location if [ ! -d /lib/modules ]; then diff --git a/src/lib/journald.ts b/src/lib/journald.ts index 219433dd..0512204d 100644 --- a/src/lib/journald.ts +++ b/src/lib/journald.ts @@ -1,6 +1,5 @@ import { ChildProcess, spawn } from 'child_process'; -import constants = require('./constants'); import log from './supervisor-console'; export function spawnJournalctl(opts: { @@ -13,11 +12,7 @@ export function spawnJournalctl(opts: { filterString?: string; since?: number; }): ChildProcess { - const args = [ - // The directory we want to run the chroot from - constants.rootMountPoint, - 'journalctl', - ]; + const args: string[] = []; if (opts.all) { args.push('-a'); } @@ -52,9 +47,9 @@ export function spawnJournalctl(opts: { args.push(opts.filterString); } - log.debug('Spawning journald with: chroot ', args.join(' ')); + log.debug('Spawning journalctl', args.join(' ')); - const journald = spawn('chroot', args, { + const journald = spawn('journalctl', args, { stdio: 'pipe', }); diff --git a/test/unit/lib/journald.spec.ts b/test/unit/lib/journald.spec.ts index 3e261e14..f6b74993 100644 --- a/test/unit/lib/journald.spec.ts +++ b/test/unit/lib/journald.spec.ts @@ -1,7 +1,6 @@ import { SinonStub, stub } from 'sinon'; import { expect } from 'chai'; -import constants = require('~/lib/constants'); import { spawnJournalctl } from '~/lib/journald'; describe('lib/journald', () => { @@ -29,8 +28,7 @@ describe('lib/journald', () => { format: 'json-pretty', }); - const expectedCommand = `chroot`; - const expectedCoreArgs = [`${constants.rootMountPoint}`, 'journalctl']; + const expectedCommand = `journalctl`; const expectedOptionalArgs = [ '-a', '--follow', @@ -45,13 +43,11 @@ describe('lib/journald', () => { ]; const actualCommand = spawn.firstCall.args[0]; - const actualCoreArgs = spawn.firstCall.args[1].slice(0, 2); - const actualOptionalArgs = spawn.firstCall.args[1].slice(2); + const actualOptionalArgs = spawn.firstCall.args[1]; expect(spawn.calledOnce).to.be.true; expect(actualCommand).deep.equal(expectedCommand); - expect(actualCoreArgs).deep.equal(expectedCoreArgs); expectedOptionalArgs.forEach((arg) => { expect(actualOptionalArgs).to.include(arg);