Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Umbrel v1.2.2 #311

Open
wants to merge 6 commits into
base: development
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions tfgrid3/umbrel/Readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@

# Umbrel v1.2.2 Flist for Threefold Grid

This repository provides instructions to build and deploy the Umbrel v1.2.2 application on the Threefold Grid as a flist. The process includes building a Docker image for the Umbrel app, pushing it to the Threefold registry, and creating flist.

### Umbrel
Umbrel is a personal server OS designed to run various self-hosted applications. For more information, visit the [Umbrel repository](https://github.com/getumbrel/umbrel).


- `app/`: Contains the Docker setup for the Umbrel v1.2.2 application
- `flist/`: Directory for creating the flist based on the Docker image
- `docker-compose.yml`: Compose file for setting up the necessary containers locally for testing (optional)

## Building the Umbrel App Docker Image

The base image for the Umbrel v1.2.2 app is derived from [dockur/umbrel](https://github.com/dockur/umbrel).

1. Navigate to the `app` directory:
```bash
cd app
```

2. Build the Docker image:
```bash
docker build -t threefolddev/umbrel_app:v1.2.2 .
```

3. Push the image to your Docker Hub:
```bash
docker push threefolddev/umbrel_app:v1.2.2
```

## Creating the Umbrel Flist

The next step is to create the flist for the Umbrel application based on the Docker image.

1. Navigate to the `flist` directory and build the flist docker image :
```bash
cd ../flist
docker build -t threefolddev/umbrel-flist:v1.2.2 .
```

2. Use the following command to Build the Docker image of umbrel flist suitable for deployment on the Threefold Grid:
```bash
docker push threefolddev/umbrel-flist:v1.2.2
```

3. The resulting flist, `threefolddev/umbrel-flist`, is now ready for deployment on the Threefold Grid.

## Deploying on Threefold Grid

- Use Upload or Docker Convert the flist to the [Threefold Hub](https://hub.grid.tf/) if not already done.


92 changes: 92 additions & 0 deletions tfgrid3/umbrel/app/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
FROM --platform=$BUILDPLATFORM debian:bookworm-slim AS base

Check failure

Code scanning / Trivy

Image user should not be 'root' High

Artifact: tfgrid3/umbrel/app/Dockerfile
Type: dockerfile
Vulnerability DS002
Severity: HIGH
Message: Specify at least 1 USER command in Dockerfile with non-root user as argument
Link: DS002
Fixed Show fixed Hide fixed

# Install Git
RUN apt-get update --no-install-recommends && \
apt-get install -y git && \
rm -rf /var/lib/apt/lists/*
Fixed Show fixed Hide fixed

# Clone the Umbrel repository at tag 1.2.2
RUN git clone --branch 1.2.2 --single-branch https://github.com/getumbrel/umbrel.git /umbrel

# Apply custom patches
COPY source /umbrel/packages/umbreld/source

#########################################################################
# ui build stage
#########################################################################

FROM --platform=$BUILDPLATFORM node:18 AS ui-build

# Install pnpm
RUN npm install -g pnpm@8

# Set the working directory
WORKDIR /app

# Copy the package.json and package-lock.json
COPY --from=base /umbrel/packages/ui/ .

# Install the dependencies
RUN rm -rf node_modules || true
RUN pnpm install

# Build the app
RUN pnpm run build

#########################################################################
# backend build stage
#########################################################################

FROM node:18 AS be-build

COPY --from=base /umbrel/packages/umbreld /tmp/umbreld
COPY --from=ui-build /app/dist /tmp/umbreld/ui
WORKDIR /tmp/umbreld
RUN chmod +x /tmp/umbreld/source/modules/apps/legacy-compat/app-script

# Install the dependencies
RUN rm -rf node_modules || true
RUN npm install

# Build the app
RUN npm run build -- --native

#########################################################################
# umbrelos build stage
#########################################################################

FROM debian:bookworm-slim AS umbrelos
ENV NODE_ENV=production

ARG TARGETARCH
ARG VERSION_ARG="0.0"
ARG YQ_VERSION="v4.44.3"
ARG DEBCONF_NOWARNINGS="yes"
ARG DEBIAN_FRONTEND="noninteractive"
ARG DEBCONF_NONINTERACTIVE_SEEN="true"

RUN set -eu \
&& apt-get update -y \
&& apt-get --no-install-recommends -y install sudo nano vim less man iproute2 iputils-ping curl wget ca-certificates dmidecode \
&& apt-get --no-install-recommends -y install python3 fswatch jq rsync curl git gettext-base gnupg libnss-mdns procps tini \
&& curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /usr/share/keyrings/docker.gpg \
&& echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker.gpg] https://download.docker.com/linux/debian bookworm stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null \
&& apt-get update -y \
&& apt-get --no-install-recommends -y install docker-ce-cli docker-compose-plugin \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* \
&& curl -sLo /usr/local/bin/yq https://github.com/mikefarah/yq/releases/download/${YQ_VERSION}/yq_linux_${TARGETARCH} \
&& chmod +x /usr/local/bin/yq \
&& echo "$VERSION_ARG" > /run/version \
&& adduser --gecos "" --disabled-password umbrel \
&& echo "umbrel:umbrel" | chpasswd \
&& usermod -aG sudo umbrel

# Install umbreld
COPY --chmod=755 ./entry.sh /run/
COPY --from=be-build --chmod=755 /tmp/umbreld/build/umbreld /usr/local/bin/umbreld

VOLUME /data
EXPOSE 80 443

ENTRYPOINT ["/usr/bin/tini", "-s", "/run/entry.sh"]
61 changes: 61 additions & 0 deletions tfgrid3/umbrel/app/entry.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
#!/usr/bin/env bash
set -Eeuo pipefail

if [ ! -S /var/run/docker.sock ]; then
echo "ERROR: Docker socket is missing? Please bind /var/run/docker.sock in your compose file." && exit 13
fi

if ! docker network inspect umbrel_main_network &>/dev/null; then
if ! docker network create --driver=bridge --subnet="10.21.0.0/16" umbrel_main_network >/dev/null; then
echo "ERROR: Failed to create network 'umbrel_main_network'!" && exit 14
fi
if ! docker network inspect umbrel_main_network &>/dev/null; then
echo "ERROR: Network 'umbrel_main_network' does not exist?" && exit 15
fi
fi

target=$(hostname)

if ! docker inspect "$target" &>/dev/null; then
echo "ERROR: Failed to find a container with name '$target'!" && exit 16
fi

resp=$(docker inspect "$target")
network=$(echo "$resp" | jq -r '.[0].NetworkSettings.Networks["umbrel_main_network"]')

if [ -z "$network" ] || [[ "$network" == "null" ]]; then
if ! docker network connect umbrel_main_network "$target"; then
echo "ERROR: Failed to connect container to network!" && exit 17
fi
fi

mount=$(echo "$resp" | jq -r '.[0].Mounts[] | select(.Destination == "/data").Source')

if [ -z "$mount" ] || [[ "$mount" == "null" ]] || [ ! -d "/data" ]; then
echo "ERROR: You did not bind the /data folder!" && exit 18
fi

# Create directories
mkdir -p "/images"

# Convert Windows paths to Linux path
if [[ "$mount" == *":\\"* ]]; then
mount="${mount,,}"
mount="${mount//\\//}"
mount="//${mount/:/}"
fi

if [[ "$mount" != "/"* ]]; then
echo "ERROR: Please bind the /data folder to an absolute path!" && exit 19
fi

# Mirror external folder to local filesystem
if [[ "$mount" != "/data" ]]; then
mkdir -p "$mount"
rm -rf "$mount"
ln -s /data "$mount"
fi

trap "pkill -SIGINT -f umbreld; while pgrep umbreld >/dev/null; do sleep 1; done" SIGINT SIGTERM

umbreld --data-directory "$mount" & wait $!
86 changes: 86 additions & 0 deletions tfgrid3/umbrel/app/source/cli.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
#!/usr/bin/env tsx
import process from 'node:process'

import arg from 'arg'
import camelcaseKeys from 'camelcase-keys'

import {cliClient} from './modules/cli-client.js'
import Umbreld, {type UmbreldOptions} from './index.js'
import {setSystemStatus} from './modules/server/trpc/routes/system.js'

// Quick trpc client for testing
if (process.argv.includes('client')) {
const clientIndex = process.argv.indexOf('client')
const query = process.argv[clientIndex + 1]
const args = process.argv.slice(clientIndex + 2)

await cliClient({query, args})
process.exit(0)
}

const showHelp = () =>
console.log(`
Usage
$ umbreld

Options
--help Shows this help message
--data-directory Your Umbrel data directory
--port The port to listen on
--log-level The logging intensity: silent|normal|verbose
--default-app-store-repo The default app store repository

Examples
$ umbreld --data-directory ~/umbrel
`)

const args = camelcaseKeys(
arg({
'--help': Boolean,
'--data-directory': String,
'--port': Number,
'--log-level': String,
'--default-app-store-repo': String,
}),
)

if (args.help) {
showHelp()
process.exit(0)
}

// TODO: Validate these args are valid
const umbreld = new Umbreld(args as UmbreldOptions)

// Shutdown cleanly on SIGINT and SIGTERM
let isShuttingDown = false
async function cleanShutdown(signal: string) {
if (isShuttingDown) return
isShuttingDown = true

umbreld.logger.log(`Received ${signal}, shutting down cleanly...`)
await umbreld.stop()
process.exit(130)
}
process.on('SIGINT', cleanShutdown.bind(null, 'SIGINT'))
process.on('SIGTERM', cleanShutdown.bind(null, 'SIGTERM'))

let isRebooting = false
async function doReboot(signal: string) {
if (isRebooting) return
isRebooting = true

umbreld.logger.log(`Rebooting...`)
await Promise.all([umbreld.apps.stop(), umbreld.appStore.stop()])
await Promise.all([umbreld.apps.start(), umbreld.appStore.start()])
setSystemStatus('running')
isRebooting = false
}
process.on('SIGUSR1', doReboot.bind(null, 'SIGUSR1'))

try {
await umbreld.start()
} catch (error) {
console.error(process.env.NODE_ENV === 'production' ? (error as Error).message : error)
process.exit(1)
}
Loading
Loading