From 40699a47e8002053d403b835740c282096bf489f Mon Sep 17 00:00:00 2001 From: Rob Emanuele Date: Thu, 23 Apr 2026 14:14:13 -0700 Subject: [PATCH 1/2] Extend builder on github --- .github/workflows/build-on-change.yml | 69 ++++++++++++++++++++++++ .github/workflows/detect-releases.yml | 3 +- .github/workflows/docker.yml | 2 +- .github/workflows/manual-build.yml | 76 ++++++++++++++++++++++++++ ibkr-version.sh | 78 +++++++++++++++++++++++++++ 5 files changed, 225 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/build-on-change.yml create mode 100644 .github/workflows/manual-build.yml create mode 100755 ibkr-version.sh diff --git a/.github/workflows/build-on-change.yml b/.github/workflows/build-on-change.yml new file mode 100644 index 0000000..9e93efb --- /dev/null +++ b/.github/workflows/build-on-change.yml @@ -0,0 +1,69 @@ +name: "Build on Image Changes" + +# Builds both channels when image-files or Dockerfile.template change on any branch. +# Tags include a branch suffix (e.g. 10.44.1e-latest-add-tint2) except on master. + +on: + push: + branches: ['**'] + paths: + - 'image-files/**' + - 'Dockerfile.template' + +jobs: + build: + name: Build ${{ matrix.channel }} + runs-on: ubuntu-latest + strategy: + matrix: + channel: [latest, stable] + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + + - name: Extract version from committed Dockerfile + id: version + run: | + ver=$(grep -oP '(?<=INSTALL_FILENAME="ibgateway-)[^-]+' ${{ matrix.channel }}/Dockerfile) + echo "version=$ver" >> $GITHUB_OUTPUT + echo "major_minor=$(echo "$ver" | grep -oP '^\d+\.\d+')" >> $GITHUB_OUTPUT + + - name: Regenerate build context with current image-files + run: ./build.sh ${{ matrix.channel }} ${{ steps.version.outputs.version }} + + - name: Sanitize branch name for Docker tag + id: branch + run: | + name=$(echo "${GITHUB_REF_NAME}" | sed 's/[^a-zA-Z0-9]/-/g' | tr '[:upper:]' '[:lower:]') + echo "name=$name" >> $GITHUB_OUTPUT + [[ "${GITHUB_REF_NAME}" == "master" ]] && echo "is_master=true" >> $GITHUB_OUTPUT || echo "is_master=false" >> $GITHUB_OUTPUT + + - name: Compute image tags + id: tags + env: + IMAGE: ghcr.io/rjemanuele/ibkr + VERSION: ${{ steps.version.outputs.version }} + MAJOR_MINOR: ${{ steps.version.outputs.major_minor }} + CHANNEL: ${{ matrix.channel }} + BRANCH: ${{ steps.branch.outputs.name }} + IS_MASTER: ${{ steps.branch.outputs.is_master }} + run: | + if [[ "$IS_MASTER" == "true" ]]; then + echo "tags=${IMAGE}:${VERSION}-${CHANNEL},${IMAGE}:${MAJOR_MINOR},${IMAGE}:${CHANNEL}" >> $GITHUB_OUTPUT + else + echo "tags=${IMAGE}:${VERSION}-${CHANNEL}-${BRANCH},${IMAGE}:${MAJOR_MINOR}-${BRANCH},${IMAGE}:${CHANNEL}-${BRANCH}" >> $GITHUB_OUTPUT + fi + + - name: Log in to Container Registry + uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build and push + uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6 + with: + context: ${{ matrix.channel }} + push: true + tags: ${{ steps.tags.outputs.tags }} diff --git a/.github/workflows/detect-releases.yml b/.github/workflows/detect-releases.yml index 37d1f95..a8fff6e 100644 --- a/.github/workflows/detect-releases.yml +++ b/.github/workflows/detect-releases.yml @@ -21,8 +21,7 @@ jobs: - name: Get Latest IB Gateway Version id: version run: | - res=$(curl -s https://download2.interactivebrokers.com/installers/ibgateway/${{ inputs.channel }}-standalone/version.json) - build_version=$(grep -oP '(?<=buildVersion":")[^"]+' <<< "$res") + build_version=$(./ibkr-version.sh ${{ inputs.channel }}) echo "build_version=$build_version" >> $GITHUB_OUTPUT - name: Check Latest Version against Releases diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 276c135..5f151de 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -35,7 +35,7 @@ jobs: id: meta uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6 with: - images: ghcr.io/extrange/ibkr + images: ghcr.io/rjemanuele/ibkr flavor: | latest=false diff --git a/.github/workflows/manual-build.yml b/.github/workflows/manual-build.yml new file mode 100644 index 0000000..3c6257c --- /dev/null +++ b/.github/workflows/manual-build.yml @@ -0,0 +1,76 @@ +name: "Manual Build" + +# Trigger from Actions tab: pick a channel and optionally pin a version. +# Useful for rebuilding an older IBKR version with newer image-files changes. + +on: + workflow_dispatch: + inputs: + channel: + description: 'Channel' + required: true + type: choice + options: [latest, stable] + version: + description: 'IBKR version (e.g. 10.44.1e). Leave blank for current.' + required: false + type: string + +jobs: + build: + name: Build ${{ inputs.channel }} ${{ inputs.version || '(current)' }} + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + + - name: Resolve version + id: version + run: | + if [[ -n "${{ inputs.version }}" ]]; then + ver="${{ inputs.version }}" + else + ver=$(./ibkr-version.sh ${{ inputs.channel }}) + fi + echo "version=$ver" >> $GITHUB_OUTPUT + echo "major_minor=$(echo "$ver" | sed 's/\([0-9]*\.[0-9]*\).*/\1/')" >> $GITHUB_OUTPUT + + - name: Regenerate build context with current image-files + run: ./build.sh ${{ inputs.channel }} ${{ steps.version.outputs.version }} + + - name: Sanitize branch name for Docker tag + id: branch + run: | + name=$(echo "${GITHUB_REF_NAME}" | sed 's/[^a-zA-Z0-9]/-/g' | tr '[:upper:]' '[:lower:]') + echo "name=$name" >> $GITHUB_OUTPUT + [[ "${GITHUB_REF_NAME}" == "master" ]] && echo "is_master=true" >> $GITHUB_OUTPUT || echo "is_master=false" >> $GITHUB_OUTPUT + + - name: Compute image tags + id: tags + env: + IMAGE: ghcr.io/rjemanuele/ibkr + VERSION: ${{ steps.version.outputs.version }} + MAJOR_MINOR: ${{ steps.version.outputs.major_minor }} + CHANNEL: ${{ inputs.channel }} + BRANCH: ${{ steps.branch.outputs.name }} + IS_MASTER: ${{ steps.branch.outputs.is_master }} + run: | + if [[ "$IS_MASTER" == "true" ]]; then + echo "tags=${IMAGE}:${VERSION}-${CHANNEL},${IMAGE}:${MAJOR_MINOR},${IMAGE}:${CHANNEL}" >> $GITHUB_OUTPUT + else + echo "tags=${IMAGE}:${VERSION}-${CHANNEL}-${BRANCH},${IMAGE}:${MAJOR_MINOR}-${BRANCH},${IMAGE}:${CHANNEL}-${BRANCH}" >> $GITHUB_OUTPUT + fi + + - name: Log in to Container Registry + uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build and push + uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6 + with: + context: ${{ inputs.channel }} + push: true + tags: ${{ steps.tags.outputs.tags }} diff --git a/ibkr-version.sh b/ibkr-version.sh new file mode 100755 index 0000000..88bfd09 --- /dev/null +++ b/ibkr-version.sh @@ -0,0 +1,78 @@ +#!/bin/bash +# Fetches current or historical IBKR build versions. +# +# Usage: +# ./ibkr-version.sh list current versions (both channels) +# ./ibkr-version.sh latest|stable print current version for one channel +# ./ibkr-version.sh history [latest|stable] list versions from GitHub releases +# +# Override the GitHub repo used for history with IBKR_DOCKER_REPO=owner/repo. +# Defaults to the origin remote of this repo, then extrange/ibkr-docker. + +set -euo pipefail + +CHANNELS=(latest stable) +IBKR_BASE="https://download2.interactivebrokers.com/installers/ibgateway" + +detect_repo() { + git remote get-url origin 2>/dev/null \ + | sed 's|.*github\.com[:/]\(.*\)\.git|\1|; s|.*github\.com[:/]\(.*\)|\1|' \ + || echo "extrange/ibkr-docker" +} + +fetch_current() { + local channel="$1" + local res + res=$(curl -fsSL "${IBKR_BASE}/${channel}-standalone/version.json") + sed 's/.*"buildVersion":"\([^"]*\)".*/\1/' <<< "$res" +} + +list_history() { + local channel_filter="${1:-}" + local repo="${IBKR_DOCKER_REPO:-$(detect_repo)}" + printf "Releases from %s:\n\n" "$repo" >&2 + + curl -fsSL "https://api.github.com/repos/${repo}/releases?per_page=100" \ + | python3 -c " +import json, sys +releases = json.load(sys.stdin) +cf = sys.argv[1] if len(sys.argv) > 1 else '' +rows = [] +for r in releases: + tag = r['tag_name'] + date = r['published_at'][:10] + parts = tag.rsplit('-', 1) + if len(parts) == 2: + version, channel = parts + if not cf or channel == cf: + rows.append((date, channel, version)) +rows.sort(reverse=True) +for date, channel, version in rows: + print(f'{date} {channel:<8} {version}') +" "$channel_filter" +} + +usage() { + sed -n '2,8p' "$0" | sed 's/^# \?//' >&2 + exit 1 +} + +case "${1:-}" in + "") + for channel in "${CHANNELS[@]}"; do + printf "%-8s %s\n" "${channel}:" "$(fetch_current "$channel")" + done + ;; + latest|stable) + fetch_current "$1" + ;; + history) + list_history "${2:-}" + ;; + -h|--help|help) + usage + ;; + *) + usage + ;; +esac From 3d7fce3e56391bd144d2164b4bab0d6f1837fb5d Mon Sep 17 00:00:00 2001 From: Rob Emanuele Date: Sun, 4 Jan 2026 09:27:06 +0000 Subject: [PATCH 2/2] Add the tint2 panel (with restart) to have control over the windows in OpenBox Cleanups: * Add proper timezone info and default to UTC, you can override it at runtime * Don't drop core by default * Optionally override openbox config * Root config files broken out --- Dockerfile.template | 8 +++++--- image-files/root/.config/openbox/autostart | 1 + image-files/start.sh | 16 +++++++++++++++- 3 files changed, 21 insertions(+), 4 deletions(-) create mode 100644 image-files/root/.config/openbox/autostart diff --git a/Dockerfile.template b/Dockerfile.template index 86e4c98..708b22e 100644 --- a/Dockerfile.template +++ b/Dockerfile.template @@ -1,10 +1,11 @@ FROM debian:13@sha256:5cf544fad978371b3df255b61e209b373583cb88b733475c86e49faa15ac2104 AS setup ENV IBC_VERSION=3.22.0 +ENV TZ=UTC RUN apt-get update && \ - apt-get install --no-install-recommends -y \ - ca-certificates git libxtst6 libgtk-3-0 openbox procps python3 socat tigervnc-standalone-server unzip wget2 xterm \ + DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y \ + ca-certificates git libxtst6 libgtk-3-0 openbox procps python3 socat tigervnc-standalone-server unzip wget2 xterm tint2 tzdata \ # https://github.com/extrange/ibkr-docker/issues/74 libasound2 \ libnss3 \ @@ -40,9 +41,10 @@ RUN wget2 "https://github.com/extrange/ibkr-docker/releases/download/${VERSION}- # Copy scripts COPY image-files/start.sh image-files/replace.sh / +COPY image-files/root/ /root/ RUN mkdir -p ~/ibc && mv /opt/ibc/config.ini ~/ibc/config.ini RUN chmod a+x ./*.sh /opt/ibc/*.sh /opt/ibc/scripts/*.sh -CMD [ "/start.sh" ] \ No newline at end of file +CMD [ "/start.sh" ] diff --git a/image-files/root/.config/openbox/autostart b/image-files/root/.config/openbox/autostart new file mode 100644 index 0000000..599623b --- /dev/null +++ b/image-files/root/.config/openbox/autostart @@ -0,0 +1 @@ +(while true; do tint2; sleep 2; done) & diff --git a/image-files/start.sh b/image-files/start.sh index a725dc4..3d10cd5 100644 --- a/image-files/start.sh +++ b/image-files/start.sh @@ -3,6 +3,13 @@ # Fail fast set -Eeuo pipefail +# Don't drop core files by default +_acf="${ALLOW_CORE_FILES:-}" +if [[ "${_acf,,}" != "true" ]]; then + ulimit -c 0 +fi +unset _acf + export DISPLAY=:0 # Clear previous lockfile @@ -14,8 +21,15 @@ Xvnc -SecurityTypes None -AlwaysShared=1 -geometry 1920x1080 :0 & # Start noVNC server ./noVNC/utils/novnc_proxy --vnc localhost:5900 & +# Wait for Xvnc to be ready before starting openbox +until [ -S /tmp/.X11-unix/X0 ]; do sleep 0.1; done + # Start openbox -openbox & +opts=() +if [ -f "$HOME/.config/openbox/rc.xml" ]; then + opts+=(--config-file "$HOME/.config/openbox/rc.xml") +fi +openbox-session "${opts[@]}" & # Start either TWS or IB Gateway if [[ -z ${GATEWAY_OR_TWS:-} ]]; then