mirror of
https://github.com/balena-os/balena-supervisor.git
synced 2024-12-23 23:42:29 +00:00
Merge pull request #1981 from balena-os/sv-base-image
Refactor supervisor Dockerfile to remove custom dependencies
This commit is contained in:
commit
6945f61a24
@ -1,70 +1,125 @@
|
|||||||
ARG ARCH=%%BALENA_ARCH%%
|
ARG ARCH=%%BALENA_ARCH%%
|
||||||
ARG NODE_VERSION=12.16.2
|
|
||||||
|
|
||||||
FROM balenalib/$ARCH-alpine-supervisor-base:3.11 as BUILD
|
# Used by livepush to support multi arch images in older
|
||||||
|
# balenaOS with buggy platform support
|
||||||
|
# see https://github.com/balena-os/balena-engine/issues/269
|
||||||
|
ARG PREFIX=library
|
||||||
|
|
||||||
ARG ARCH
|
###################################################
|
||||||
ARG NODE_VERSION
|
# Build the supervisor dependencies
|
||||||
ARG NODE_ARCHIVE="node-no-intl-v${NODE_VERSION}-linux-alpine-${ARCH}.tar.gz"
|
###################################################
|
||||||
ARG S3_BASE="https://resin-packages.s3.amazonaws.com"
|
FROM balenalib/${ARCH}-alpine-node:12-run as build-base
|
||||||
ARG NODE_LOCATION="${S3_BASE}/node/v${NODE_VERSION}/${NODE_ARCHIVE}"
|
|
||||||
|
|
||||||
# DO NOT REMOVE THE cross-build-* COMMANDS
|
ARG PREFIX
|
||||||
# The following commands are absolutely needed. When we
|
# Sanity check to prevent a prefix for a non-official docker image being
|
||||||
# build for ARM architectures, we run this Dockerfile
|
# inserted. Only 'library' and 'arm32v6' are allowed right now
|
||||||
# through sed, which uncomments these lines. There were
|
RUN for allowed in "library" "arm32v6"; do [ "${PREFIX}" = "${allowed}" ] && break; done
|
||||||
# other options for achieving the same setup, but this seems
|
|
||||||
# to be the least intrusive. The commands start commented
|
|
||||||
# out because the default build for balenaCI is amd64 (and
|
|
||||||
# we can't run any sed preprocessing on it there)
|
|
||||||
# RUN ["cross-build-start"]
|
|
||||||
|
|
||||||
WORKDIR /usr/src/app
|
WORKDIR /usr/src/app
|
||||||
|
|
||||||
RUN apk add --no-cache \
|
RUN apk add --no-cache \
|
||||||
g++ \
|
g++ \
|
||||||
git \
|
|
||||||
make \
|
make \
|
||||||
python \
|
python3 \
|
||||||
curl \
|
|
||||||
binutils \
|
|
||||||
libgcc \
|
libgcc \
|
||||||
libstdc++ \
|
|
||||||
libuv \
|
libuv \
|
||||||
sqlite-libs \
|
|
||||||
sqlite-dev \
|
sqlite-dev \
|
||||||
dmidecode \
|
dbus-dev
|
||||||
dbus-dev \
|
|
||||||
procmail
|
|
||||||
# procmail is installed for the lockfile binary
|
|
||||||
|
|
||||||
COPY build-utils/node-sums.txt .
|
|
||||||
|
|
||||||
# Install node from balena's prebuilt cache
|
|
||||||
RUN curl -SLO "${NODE_LOCATION}" \
|
|
||||||
&& grep "${NODE_ARCHIVE}" node-sums.txt | sha256sum -c - \
|
|
||||||
&& tar -xzf "${NODE_ARCHIVE}" -C /usr/local --strip-components=1 \
|
|
||||||
&& rm -f "${NODE_ARCHIVE}" \
|
|
||||||
&& strip /usr/local/bin/node
|
|
||||||
|
|
||||||
COPY package*.json ./
|
COPY package*.json ./
|
||||||
|
|
||||||
|
RUN strip /usr/local/bin/node
|
||||||
|
|
||||||
|
# Just install dev dependencies first
|
||||||
RUN npm ci --build-from-source --sqlite=/usr/lib
|
RUN npm ci --build-from-source --sqlite=/usr/lib
|
||||||
|
|
||||||
# We only run these commands when executing through
|
###################################################
|
||||||
# livepush, so they are presented as livepush directives
|
# Extra dependencies. This uses alpine 3.11 as the
|
||||||
#dev-run=apk add --no-cache ip6tables iptables
|
# procmail package was removed on 3.12
|
||||||
|
###################################################
|
||||||
|
FROM ${PREFIX}/alpine:3.11 as extra
|
||||||
|
|
||||||
|
RUN apk add --update --no-cache procmail
|
||||||
|
|
||||||
|
###################################################
|
||||||
|
# Image with the final production dependencies.
|
||||||
|
# This image will also be be used for testing
|
||||||
|
###################################################
|
||||||
|
FROM ${PREFIX}/alpine:3.16 as runtime-base
|
||||||
|
|
||||||
|
WORKDIR /usr/src/app
|
||||||
|
|
||||||
|
# We just need the node binary in the final image
|
||||||
|
COPY --from=build-base /usr/local/bin/node /usr/local/bin/node
|
||||||
|
|
||||||
|
# Similarly, from the procmail package we just need the lockfile binary
|
||||||
|
COPY --from=extra /usr/bin/lockfile /usr/bin/lockfile
|
||||||
|
|
||||||
|
# Runtime dependencies
|
||||||
|
RUN apk add --no-cache \
|
||||||
|
ca-certificates \
|
||||||
|
iptables \
|
||||||
|
ip6tables \
|
||||||
|
rsync \
|
||||||
|
dbus \
|
||||||
|
libstdc++ \
|
||||||
|
dmidecode \
|
||||||
|
sqlite-libs
|
||||||
|
|
||||||
|
ARG ARCH
|
||||||
|
ARG VERSION=master
|
||||||
|
ARG DEFAULT_MIXPANEL_TOKEN=bananasbananas
|
||||||
|
ENV CONFIG_MOUNT_POINT=/boot/config.json \
|
||||||
|
LED_FILE=/dev/null \
|
||||||
|
SUPERVISOR_IMAGE=balena/$ARCH-supervisor \
|
||||||
|
VERSION=$VERSION \
|
||||||
|
DEFAULT_MIXPANEL_TOKEN=$DEFAULT_MIXPANEL_TOKEN
|
||||||
|
|
||||||
|
###################################################
|
||||||
|
# Use the base image to run the tests as livepush
|
||||||
|
###################################################
|
||||||
|
FROM runtime-base as test
|
||||||
|
|
||||||
|
WORKDIR /usr/src/app
|
||||||
|
|
||||||
|
# Copy node install from the build folder
|
||||||
|
COPY --from=build-base /usr/local/bin /usr/local/bin
|
||||||
|
COPY --from=build-base /usr/local/lib/node_modules /usr/local/lib/node_modules
|
||||||
|
|
||||||
|
# Copy build dependencies
|
||||||
|
COPY --from=build-base /usr/src/app/package.json ./
|
||||||
|
COPY --from=build-base /usr/src/app/node_modules ./node_modules
|
||||||
|
|
||||||
|
# Run livepush here
|
||||||
#dev-copy=entry.sh .
|
#dev-copy=entry.sh .
|
||||||
#dev-cmd-live=LIVEPUSH=1 ./entry.sh
|
#dev-cmd-live=LIVEPUSH=1 ./entry.sh
|
||||||
|
|
||||||
|
# Copy build files
|
||||||
COPY build-utils ./build-utils
|
COPY build-utils ./build-utils
|
||||||
COPY webpack.config.js tsconfig.json tsconfig.release.json ./
|
COPY webpack.config.js tsconfig.json tsconfig.release.json ./
|
||||||
COPY src ./src
|
COPY src ./src
|
||||||
COPY test ./test
|
COPY test ./test
|
||||||
COPY typings ./typings
|
COPY typings ./typings
|
||||||
|
|
||||||
RUN npm run test-nolint \
|
# Run the tests
|
||||||
&& npm run build
|
RUN npm run test-nolint
|
||||||
|
|
||||||
|
###################################################
|
||||||
|
# Build the production package
|
||||||
|
###################################################
|
||||||
|
FROM build-base as build-prod
|
||||||
|
|
||||||
|
WORKDIR /usr/src/app
|
||||||
|
|
||||||
|
# Copy build files
|
||||||
|
COPY build-utils ./build-utils
|
||||||
|
COPY webpack.config.js tsconfig.json tsconfig.release.json ./
|
||||||
|
COPY src ./src
|
||||||
|
COPY typings ./typings
|
||||||
|
|
||||||
|
# Compile the sources using the dev
|
||||||
|
# dependencies
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
# Run the production install here, to avoid the npm dependency on
|
# Run the production install here, to avoid the npm dependency on
|
||||||
# the later stage
|
# the later stage
|
||||||
@ -86,52 +141,21 @@ RUN npm ci --production --no-optional --unsafe-perm --build-from-source --sqlite
|
|||||||
&& find . -type f -path '*/node_modules/knex/build*' -delete \
|
&& find . -type f -path '*/node_modules/knex/build*' -delete \
|
||||||
&& rm -rf node_modules/sqlite3/node.dtps
|
&& rm -rf node_modules/sqlite3/node.dtps
|
||||||
|
|
||||||
|
###################################################
|
||||||
# RUN ["cross-build-end"]
|
# Build the production image
|
||||||
|
###################################################
|
||||||
FROM balenalib/$ARCH-alpine-supervisor-base:3.11
|
FROM runtime-base
|
||||||
|
|
||||||
# RUN ["cross-build-start"]
|
|
||||||
|
|
||||||
RUN apk add --no-cache \
|
|
||||||
ca-certificates \
|
|
||||||
kmod \
|
|
||||||
iptables \
|
|
||||||
ip6tables \
|
|
||||||
rsync \
|
|
||||||
avahi \
|
|
||||||
dbus \
|
|
||||||
libstdc++ \
|
|
||||||
dmidecode \
|
|
||||||
sqlite-libs
|
|
||||||
|
|
||||||
WORKDIR /usr/src/app
|
WORKDIR /usr/src/app
|
||||||
|
|
||||||
COPY --from=BUILD /usr/local/bin/node /usr/local/bin/node
|
COPY --from=build-prod /usr/src/app/dist ./dist
|
||||||
COPY --from=BUILD /usr/bin/lockfile /usr/bin/lockfile
|
COPY --from=build-prod /usr/src/app/package.json ./
|
||||||
COPY --from=BUILD /usr/src/app/dist ./dist
|
COPY --from=build-prod /usr/src/app/node_modules ./node_modules
|
||||||
COPY --from=BUILD /usr/src/app/package.json ./
|
|
||||||
COPY --from=BUILD /usr/src/app/node_modules ./node_modules
|
|
||||||
|
|
||||||
COPY entry.sh .
|
COPY entry.sh .
|
||||||
|
|
||||||
RUN mkdir -p rootfs-overlay && \
|
|
||||||
(([ ! -d rootfs-overlay/lib64 ] && ln -s /lib rootfs-overlay/lib64) || true)
|
|
||||||
|
|
||||||
ARG ARCH
|
|
||||||
ARG VERSION=master
|
|
||||||
ARG DEFAULT_MIXPANEL_TOKEN=bananasbananas
|
|
||||||
ENV CONFIG_MOUNT_POINT=/boot/config.json \
|
|
||||||
LED_FILE=/dev/null \
|
|
||||||
SUPERVISOR_IMAGE=balena/$ARCH-supervisor \
|
|
||||||
VERSION=$VERSION \
|
|
||||||
DEFAULT_MIXPANEL_TOKEN=$DEFAULT_MIXPANEL_TOKEN
|
|
||||||
COPY avahi-daemon.conf /etc/avahi/avahi-daemon.conf
|
|
||||||
|
|
||||||
VOLUME /data
|
VOLUME /data
|
||||||
HEALTHCHECK --interval=5m --start-period=1m --timeout=30s --retries=3 \
|
HEALTHCHECK --interval=5m --start-period=1m --timeout=30s --retries=3 \
|
||||||
CMD wget http://127.0.0.1:${LISTEN_PORT:-48484}/v1/healthy -O - -q
|
CMD wget http://127.0.0.1:${LISTEN_PORT:-48484}/v1/healthy -O - -q
|
||||||
|
|
||||||
# RUN ["cross-build-end"]
|
|
||||||
|
|
||||||
CMD ["/usr/src/app/entry.sh"]
|
CMD ["/usr/src/app/entry.sh"]
|
||||||
|
@ -1,67 +0,0 @@
|
|||||||
# This file is part of avahi.
|
|
||||||
#
|
|
||||||
# avahi is free software; you can redistribute it and/or modify it
|
|
||||||
# under the terms of the GNU Lesser General Public License as
|
|
||||||
# published by the Free Software Foundation; either version 2 of the
|
|
||||||
# License, or (at your option) any later version.
|
|
||||||
#
|
|
||||||
# avahi is distributed in the hope that it will be useful, but WITHOUT
|
|
||||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
|
||||||
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
|
|
||||||
# License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU Lesser General Public
|
|
||||||
# License along with avahi; if not, write to the Free Software
|
|
||||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
|
|
||||||
# USA.
|
|
||||||
|
|
||||||
# See avahi-daemon.conf(5) for more information on this configuration
|
|
||||||
# file!
|
|
||||||
|
|
||||||
[server]
|
|
||||||
#host-name=foo
|
|
||||||
#domain-name=local
|
|
||||||
#browse-domains=0pointer.de, zeroconf.org
|
|
||||||
use-ipv4=yes
|
|
||||||
use-ipv6=yes
|
|
||||||
#allow-interfaces=eth0
|
|
||||||
#deny-interfaces=eth1
|
|
||||||
#check-response-ttl=no
|
|
||||||
#use-iff-running=no
|
|
||||||
#enable-dbus=yes
|
|
||||||
#disallow-other-stacks=no
|
|
||||||
#allow-point-to-point=no
|
|
||||||
#cache-entries-max=4096
|
|
||||||
#clients-max=4096
|
|
||||||
#objects-per-client-max=1024
|
|
||||||
#entries-per-entry-group-max=32
|
|
||||||
ratelimit-interval-usec=1000000
|
|
||||||
ratelimit-burst=1000
|
|
||||||
|
|
||||||
[wide-area]
|
|
||||||
enable-wide-area=yes
|
|
||||||
|
|
||||||
[publish]
|
|
||||||
#disable-publishing=no
|
|
||||||
#disable-user-service-publishing=no
|
|
||||||
#add-service-cookie=no
|
|
||||||
#publish-addresses=yes
|
|
||||||
publish-hinfo=no
|
|
||||||
publish-workstation=no
|
|
||||||
#publish-domain=yes
|
|
||||||
#publish-dns-servers=192.168.50.1, 192.168.50.2
|
|
||||||
#publish-resolv-conf-dns-servers=yes
|
|
||||||
#publish-aaaa-on-ipv4=yes
|
|
||||||
#publish-a-on-ipv6=no
|
|
||||||
|
|
||||||
[reflector]
|
|
||||||
#enable-reflector=no
|
|
||||||
#reflect-ipv=no
|
|
||||||
|
|
||||||
[rlimits]
|
|
||||||
#rlimit-as=
|
|
||||||
rlimit-core=0
|
|
||||||
rlimit-data=4194304
|
|
||||||
rlimit-fsize=0
|
|
||||||
rlimit-nofile=768
|
|
||||||
rlimit-stack=4194304
|
|
@ -1,5 +0,0 @@
|
|||||||
6d4ab189ece76bed2f40cffe1f1b4dd2d2a805e6fe10c577ae5ce89fce2ad53b node-no-intl-v12.16.2-linux-alpine-amd64.tar.gz
|
|
||||||
6d81db43dc1285656f6d95807e237e51d33774bed5daafad7f706a4e68b6b546 node-no-intl-v12.16.2-linux-alpine-i386.tar.gz
|
|
||||||
d964c84be94d0cf3f30f4c4d61d3f2e0d5439b43137c938d0a0df1b6860961fb node-no-intl-v12.16.2-linux-alpine-aarch64.tar.gz
|
|
||||||
00ac31e6e43319cc3786c0eb97784dad91b5152c5f8be31889215801dcab7712 node-no-intl-v12.16.2-linux-alpine-armv7hf.tar.gz
|
|
||||||
8c1cfa75a1523a5b21060201178f4a8cd51f492c93693ecfbd67e8b0a49b23f1 node-no-intl-v12.16.2-linux-alpine-rpi.tar.gz
|
|
@ -5,7 +5,7 @@ import { Builder } from 'resin-docker-build';
|
|||||||
|
|
||||||
import { promises as fs } from 'fs';
|
import { promises as fs } from 'fs';
|
||||||
import * as Path from 'path';
|
import * as Path from 'path';
|
||||||
import { Duplex, Readable } from 'stream';
|
import { Duplex, Readable, PassThrough, Stream } from 'stream';
|
||||||
import * as tar from 'tar-stream';
|
import * as tar from 'tar-stream';
|
||||||
|
|
||||||
import { exec } from '../src/lib/fs-utils';
|
import { exec } from '../src/lib/fs-utils';
|
||||||
@ -68,26 +68,30 @@ export async function getCacheFrom(docker: Docker): Promise<string[]> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// perform the build and return the image id
|
|
||||||
export async function performBuild(
|
export async function performBuild(
|
||||||
docker: Docker,
|
docker: Docker,
|
||||||
dockerfile: Dockerfile,
|
dockerfile: Dockerfile,
|
||||||
dockerOpts: { [key: string]: any },
|
dockerOpts: { [key: string]: any },
|
||||||
): Promise<void> {
|
): Promise<string> {
|
||||||
const builder = Builder.fromDockerode(docker);
|
const builder = Builder.fromDockerode(docker);
|
||||||
|
|
||||||
// tar the directory, but replace the dockerfile with the
|
// tar the directory, but replace the dockerfile with the
|
||||||
// livepush generated one
|
// livepush generated one
|
||||||
const tarStream = await tarDirectory(Path.join(__dirname, '..'), dockerfile);
|
const tarStream = await tarDirectory(Path.join(__dirname, '..'), dockerfile);
|
||||||
|
const bufStream = new PassThrough();
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
const chunks = [] as Buffer[];
|
||||||
|
bufStream.on('data', (chunk) => chunks.push(Buffer.from(chunk)));
|
||||||
builder.createBuildStream(dockerOpts, {
|
builder.createBuildStream(dockerOpts, {
|
||||||
buildSuccess: () => {
|
buildSuccess: () => {
|
||||||
resolve();
|
// Return the build logs
|
||||||
|
resolve(Buffer.concat(chunks).toString('utf8'));
|
||||||
},
|
},
|
||||||
buildFailure: reject,
|
buildFailure: reject,
|
||||||
buildStream: (stream: Duplex) => {
|
buildStream: (stream: Duplex) => {
|
||||||
stream.pipe(process.stdout);
|
stream.pipe(process.stdout);
|
||||||
|
stream.pipe(bufStream);
|
||||||
tarStream.pipe(stream);
|
tarStream.pipe(stream);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@ -190,3 +194,20 @@ LED_FILE=/dev/null
|
|||||||
`echo '${fileStr}' > /tmp/update-supervisor.conf`,
|
`echo '${fileStr}' > /tmp/update-supervisor.conf`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function readBuildCache(address: string): Promise<string[]> {
|
||||||
|
const cache = await runSshCommand(
|
||||||
|
address,
|
||||||
|
`cat /tmp/livepush-cache.json || true`,
|
||||||
|
);
|
||||||
|
return JSON.parse(cache || '[]');
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function writeBuildCache(address: string, stageImages: string[]) {
|
||||||
|
// Convert the list to JSON with escaped quotes
|
||||||
|
const contents = JSON.stringify(stageImages).replace(/["]/g, '\\"');
|
||||||
|
return runSshCommand(
|
||||||
|
address,
|
||||||
|
`echo '${contents}' > /tmp/livepush-cache.json`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
80
sync/init.ts
80
sync/init.ts
@ -14,24 +14,92 @@ interface Opts {
|
|||||||
arch?: string;
|
arch?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Source: https://github.com/balena-io/balena-cli/blob/f6d668684a6f5ea8102a964ca1942b242eaa7ae2/lib/utils/device/live.ts#L539-L547
|
||||||
|
function extractDockerArrowMessage(outputLine: string): string | undefined {
|
||||||
|
const arrowTest = /^.*\s*-+>\s*(.+)/i;
|
||||||
|
const match = arrowTest.exec(outputLine);
|
||||||
|
if (match != null) {
|
||||||
|
return match[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Source: https://github.com/balena-io/balena-cli/blob/f6d668684a6f5ea8102a964ca1942b242eaa7ae2/lib/utils/device/live.ts#L300-L325
|
||||||
|
function getMultiStateImageIDs(buildLog: string): string[] {
|
||||||
|
const ids = [] as string[];
|
||||||
|
const lines = buildLog.split(/\r?\n/);
|
||||||
|
let lastArrowMessage: string | undefined;
|
||||||
|
for (const line of lines) {
|
||||||
|
// If this was a from line, take the last found
|
||||||
|
// image id and save it
|
||||||
|
if (
|
||||||
|
/step \d+(?:\/\d+)?\s*:\s*FROM/i.test(line) &&
|
||||||
|
lastArrowMessage != null
|
||||||
|
) {
|
||||||
|
ids.push(lastArrowMessage);
|
||||||
|
} else {
|
||||||
|
const msg = extractDockerArrowMessage(line);
|
||||||
|
if (msg != null) {
|
||||||
|
lastArrowMessage = msg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ids;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPathPrefix(arch: string) {
|
||||||
|
switch (arch) {
|
||||||
|
/**
|
||||||
|
* Proper paths are
|
||||||
|
* - armv6 - arm32v6
|
||||||
|
* - armv7hf - arm32v7
|
||||||
|
* - aarch64 - arm64v8
|
||||||
|
* - amd64 - amd64
|
||||||
|
* - i386 - i386
|
||||||
|
*
|
||||||
|
* We only set the prefix for v6 images since rpi devices are
|
||||||
|
* the only ones that seem to have the issue
|
||||||
|
* https://github.com/balena-os/balena-engine/issues/269
|
||||||
|
*/
|
||||||
|
case 'rpi':
|
||||||
|
return 'arm32v6';
|
||||||
|
default:
|
||||||
|
return 'library';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export async function initDevice(opts: Opts) {
|
export async function initDevice(opts: Opts) {
|
||||||
const arch = opts.arch ?? (await device.getDeviceArch(opts.docker));
|
const arch = opts.arch ?? (await device.getDeviceArch(opts.docker));
|
||||||
const image = `${opts.imageName}-${opts.imageTag}`;
|
const image = `${opts.imageName}:${opts.imageTag}`;
|
||||||
|
|
||||||
await device.performBuild(opts.docker, opts.dockerfile, {
|
const buildCache = await device.readBuildCache(opts.address);
|
||||||
buildargs: { ARCH: arch },
|
|
||||||
|
const buildLog = await device.performBuild(opts.docker, opts.dockerfile, {
|
||||||
|
buildargs: { ARCH: arch, PREFIX: getPathPrefix(arch) },
|
||||||
t: image,
|
t: image,
|
||||||
labels: { 'io.balena.livepush-image': '1', 'io.balena.architecture': arch },
|
labels: { 'io.balena.livepush-image': '1', 'io.balena.architecture': arch },
|
||||||
cachefrom: (await device.getCacheFrom(opts.docker)).concat(image),
|
cachefrom: (await device.getCacheFrom(opts.docker))
|
||||||
|
.concat(image)
|
||||||
|
.concat(buildCache),
|
||||||
nocache: opts.nocache,
|
nocache: opts.nocache,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const stageImages = getMultiStateImageIDs(buildLog);
|
||||||
|
|
||||||
|
// Store the list of stage images for the next time the sync
|
||||||
|
// command is called. This will only live until the device is rebooted
|
||||||
|
await device.writeBuildCache(opts.address, stageImages);
|
||||||
|
|
||||||
// Now that we have our new image on the device, we need
|
// Now that we have our new image on the device, we need
|
||||||
// to stop the supervisor, update
|
// to stop the supervisor, update
|
||||||
// /tmp/update-supervisor.conf with our version, and
|
// /tmp/update-supervisor.conf with our version, and
|
||||||
// restart the supervisor
|
// restart the supervisor
|
||||||
await device.stopSupervisor(opts.address);
|
await device.stopSupervisor(opts.address);
|
||||||
await device.replaceSupervisorImage(opts.address, image, 'latest');
|
await device.replaceSupervisorImage(
|
||||||
|
opts.address,
|
||||||
|
opts.imageName,
|
||||||
|
opts.imageTag,
|
||||||
|
);
|
||||||
await device.startSupervisor(opts.address);
|
await device.startSupervisor(opts.address);
|
||||||
|
|
||||||
let supervisorContainer: undefined | Docker.ContainerInfo;
|
let supervisorContainer: undefined | Docker.ContainerInfo;
|
||||||
@ -45,5 +113,5 @@ export async function initDevice(opts: Opts) {
|
|||||||
await Bluebird.delay(500);
|
await Bluebird.delay(500);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return supervisorContainer.Id;
|
return { containerId: supervisorContainer.Id, stageImages };
|
||||||
}
|
}
|
||||||
|
@ -11,11 +11,12 @@ export async function startLivepush(opts: {
|
|||||||
containerId: string;
|
containerId: string;
|
||||||
docker: Docker;
|
docker: Docker;
|
||||||
noinit: boolean;
|
noinit: boolean;
|
||||||
|
stageImages?: string[];
|
||||||
}) {
|
}) {
|
||||||
const livepush = await Livepush.init({
|
const livepush = await Livepush.init({
|
||||||
|
stageImages: [],
|
||||||
...opts,
|
...opts,
|
||||||
context: Path.join(__dirname, '..'),
|
context: Path.join(__dirname, '..'),
|
||||||
stageImages: [],
|
|
||||||
});
|
});
|
||||||
|
|
||||||
livepush.addListener('commandExecute', ({ command }) => {
|
livepush.addListener('commandExecute', ({ command }) => {
|
||||||
@ -34,8 +35,7 @@ export async function startLivepush(opts: {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const livepushExecutor = getExecutor(livepush);
|
const livepushExecutor = getExecutor(livepush);
|
||||||
|
const watcher = chokidar
|
||||||
chokidar
|
|
||||||
.watch('.', {
|
.watch('.', {
|
||||||
ignored: /((^|[\/\\])\..|(node_modules|sync|test)\/.*)/,
|
ignored: /((^|[\/\\])\..|(node_modules|sync|test)\/.*)/,
|
||||||
ignoreInitial: opts.noinit,
|
ignoreInitial: opts.noinit,
|
||||||
@ -43,6 +43,11 @@ export async function startLivepush(opts: {
|
|||||||
.on('add', (path) => livepushExecutor(path))
|
.on('add', (path) => livepushExecutor(path))
|
||||||
.on('change', (path) => livepushExecutor(path))
|
.on('change', (path) => livepushExecutor(path))
|
||||||
.on('unlink', (path) => livepushExecutor(undefined, path));
|
.on('unlink', (path) => livepushExecutor(undefined, path));
|
||||||
|
|
||||||
|
return async () => {
|
||||||
|
await watcher.close();
|
||||||
|
await livepush.cleanupIntermediateContainers();
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const getExecutor = (livepush: Livepush) => {
|
const getExecutor = (livepush: Livepush) => {
|
||||||
|
31
sync/sync.ts
31
sync/sync.ts
@ -35,13 +35,15 @@ const argv = yargs
|
|||||||
alias: 'i',
|
alias: 'i',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
description: 'Specify the name to use for the supervisor image on device',
|
description: 'Specify the name to use for the supervisor image on device',
|
||||||
default: 'livepush-supervisor',
|
default: `livepush-supervisor-${packageJson.version}`,
|
||||||
})
|
})
|
||||||
.options('image-tag', {
|
.options('image-tag', {
|
||||||
alias: 't',
|
alias: 't',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
description: 'Specify the tag to use for the supervisor image on device',
|
description:
|
||||||
default: packageJson.version,
|
'Specify the tag to use for the supervisor image on device. It will not have any effect on balenaOS >= v2.89.0',
|
||||||
|
default: 'latest',
|
||||||
|
deprecated: true,
|
||||||
})
|
})
|
||||||
.options('nocache', {
|
.options('nocache', {
|
||||||
description: 'Run the intial build without cache',
|
description: 'Run the intial build without cache',
|
||||||
@ -59,9 +61,14 @@ const argv = yargs
|
|||||||
await fs.readFile('Dockerfile.template'),
|
await fs.readFile('Dockerfile.template'),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let cleanup = () => Promise.resolve();
|
||||||
|
let sigint = () => {
|
||||||
|
/** ignore empty */
|
||||||
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const docker = device.getDocker(address);
|
const docker = device.getDocker(address);
|
||||||
const containerId = await init.initDevice({
|
const { containerId, stageImages } = await init.initDevice({
|
||||||
address,
|
address,
|
||||||
docker,
|
docker,
|
||||||
dockerfile,
|
dockerfile,
|
||||||
@ -74,14 +81,24 @@ const argv = yargs
|
|||||||
console.log(`Supervisor container: ${containerId}\n`);
|
console.log(`Supervisor container: ${containerId}\n`);
|
||||||
|
|
||||||
await setupLogs(docker, containerId);
|
await setupLogs(docker, containerId);
|
||||||
await startLivepush({
|
cleanup = await startLivepush({
|
||||||
dockerfile,
|
dockerfile,
|
||||||
containerId,
|
containerId,
|
||||||
docker,
|
docker,
|
||||||
noinit: true,
|
noinit: true,
|
||||||
|
stageImages,
|
||||||
|
});
|
||||||
|
|
||||||
|
await new Promise((_, reject) => {
|
||||||
|
sigint = () => reject(new Error('User interrupt (Ctrl+C) received'));
|
||||||
|
process.on('SIGINT', sigint);
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Error:');
|
console.error('Error:', e.message);
|
||||||
console.error(e.message);
|
} finally {
|
||||||
|
console.info('Cleaning up. Please wait ...');
|
||||||
|
await cleanup();
|
||||||
|
process.removeListener('SIGINT', sigint);
|
||||||
|
process.exit(1);
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
Loading…
Reference in New Issue
Block a user