ttyd serves a single-file xterm.js UI styled with the LotusGuild TDS (web_template base.css). Sessions live in a dedicated tmux server unit (devdesk-tmux.service), so restarting or upgrading the web unit never stops Claude. Each named session maps to a project directory. A Claude SessionStart hook records the conversation ID, so after a reboot the session resumes that exact conversation. - bin/devdesk: attach / new / ls / end / pick, with an fzf picker - ttyd requires a credential that NPM injects; origin check enabled - debian/: dh 13 with --no-stop-on-upgrade for the tmux unit, bundled sha256-verified ttyd 1.7.7, lintian overrides, man page - CI: lint, test, build, smoke-test, artifact; v* tags publish to the Gitea apt registry and create a release Refs: tinker ticket 887531976 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
159 lines
6.4 KiB
YAML
159 lines
6.4 KiB
YAML
name: build-deb
|
|
|
|
on:
|
|
push:
|
|
branches: ["**"]
|
|
tags: ["v*"]
|
|
pull_request:
|
|
|
|
concurrency:
|
|
group: build-deb-${{ github.ref }}
|
|
cancel-in-progress: true
|
|
|
|
env:
|
|
GITEA_URL: https://code.lotusguild.org
|
|
OWNER: LotusGuild
|
|
REPO: LotusGuild/devdesk
|
|
|
|
jobs:
|
|
build:
|
|
name: Lint, test, package
|
|
runs-on: ubuntu-latest
|
|
# Bookworm (tmux 3.3, debhelper 13); the node image keeps actions/checkout working.
|
|
container:
|
|
image: node:22-bookworm
|
|
steps:
|
|
- uses: actions/checkout@v3
|
|
|
|
- name: Install build tools
|
|
run: |
|
|
apt-get update -qq
|
|
apt-get install -y -qq --no-install-recommends \
|
|
debhelper devscripts lintian fakeroot bats tmux fzf jq curl ca-certificates xz-utils >/dev/null
|
|
VER=v0.10.0
|
|
curl -fsSL "https://github.com/koalaman/shellcheck/releases/download/${VER}/shellcheck-${VER}.linux.x86_64.tar.xz" | tar -xJ
|
|
install "shellcheck-${VER}/shellcheck" /usr/local/bin/shellcheck
|
|
shellcheck --version | head -2
|
|
|
|
- name: npm ci
|
|
run: |
|
|
for i in 1 2 3; do
|
|
npm ci --no-audit --no-fund && break
|
|
echo "npm ci failed (attempt $i), retrying"; sleep 10
|
|
done
|
|
git diff --exit-code package-lock.json
|
|
|
|
- name: Lint
|
|
run: make lint
|
|
|
|
- name: Test
|
|
run: make test
|
|
|
|
- name: Set package version
|
|
id: version
|
|
env:
|
|
DEBEMAIL: ci@lotusguild.org
|
|
DEBFULLNAME: Lotus CI
|
|
run: |
|
|
base=$(dpkg-parsechangelog -S Version)
|
|
case "$GITHUB_REF" in
|
|
refs/tags/v*) version=${GITHUB_REF#refs/tags/v} ;;
|
|
*) version="${base}+ci${GITHUB_RUN_NUMBER}.g${GITHUB_SHA::7}" ;;
|
|
esac
|
|
if [ "$version" != "$base" ]; then
|
|
dch --newversion "$version" --distribution bookworm --force-distribution "CI build of ${GITHUB_SHA::7}"
|
|
fi
|
|
echo "version=$version" >> "$GITHUB_OUTPUT"
|
|
echo "Building devdesk $version"
|
|
|
|
- name: Build package
|
|
run: |
|
|
DEB_BUILD_OPTIONS=nocheck dpkg-buildpackage -us -uc -b
|
|
mkdir -p out && mv ../devdesk_*.deb out/
|
|
ls -l out/
|
|
|
|
- name: Lintian
|
|
run: lintian --fail-on error --info out/*.deb
|
|
|
|
- name: Smoke test
|
|
run: |
|
|
apt-get install -y -qq ./out/devdesk_*.deb >/dev/null
|
|
/usr/lib/devdesk/bin/ttyd --version
|
|
test -s /etc/devdesk/secret.env
|
|
. /etc/devdesk/secret.env
|
|
mkdir -p /run/devdesk
|
|
tmux -u -D -S /run/devdesk/tmux.sock -f /usr/share/devdesk/tmux.conf &
|
|
/usr/lib/devdesk/devdesk-tmux-ready
|
|
DEVDESK_CREDENTIAL="$DEVDESK_CREDENTIAL" /usr/lib/devdesk/devdesk-serve &
|
|
for _ in $(seq 1 30); do curl -s -o /dev/null localhost:7681/ && break; sleep 0.2; done
|
|
code=$(curl -s -o /dev/null -w '%{http_code}' localhost:7681/)
|
|
[ "$code" = 401 ] || { echo "expected 401 without credential, got $code"; exit 1; }
|
|
curl -sf -u "$DEVDESK_CREDENTIAL" localhost:7681/ | grep -q 'name="generator" content="devdesk"'
|
|
curl -sf -u "$DEVDESK_CREDENTIAL" localhost:7681/token | jq -e '.token | length > 0'
|
|
DEVDESK_NO_SYSTEMD=1 devdesk attach --list | jq -e '.default == "main"'
|
|
echo "smoke test passed"
|
|
|
|
- name: Upload artifact
|
|
uses: actions/upload-artifact@v3
|
|
with:
|
|
name: devdesk_${{ steps.version.outputs.version }}_amd64
|
|
path: out/*.deb
|
|
|
|
- name: Publish to Gitea apt registry
|
|
if: startsWith(github.ref, 'refs/tags/v')
|
|
env:
|
|
TOKEN: ${{ secrets.PACKAGE_TOKEN || secrets.NPM_PUBLISH_TOKEN }}
|
|
VERSION: ${{ steps.version.outputs.version }}
|
|
run: |
|
|
deb=$(ls out/devdesk_*.deb)
|
|
pool="$GITEA_URL/api/packages/$OWNER/debian/pool/bookworm/main"
|
|
upload() { curl -s -o resp.txt -w '%{http_code}' -H "Authorization: token $TOKEN" --upload-file "$deb" "$pool/upload"; }
|
|
code=$(upload)
|
|
if [ "$code" = 409 ]; then
|
|
echo "devdesk $VERSION already published; replacing it"
|
|
curl -sf -X DELETE -H "Authorization: token $TOKEN" "$pool/devdesk/$VERSION/amd64"
|
|
code=$(upload)
|
|
fi
|
|
[ "$code" = 201 ] || { echo "registry upload failed: HTTP $code"; cat resp.txt; exit 1; }
|
|
echo "published devdesk $VERSION to $GITEA_URL/$OWNER/-/packages/debian/devdesk"
|
|
|
|
- name: Create Gitea release
|
|
if: startsWith(github.ref, 'refs/tags/v')
|
|
env:
|
|
TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
|
TAG: ${{ github.ref_name }}
|
|
run: |
|
|
api="$GITEA_URL/api/v1/repos/$REPO"
|
|
auth=(-H "Authorization: token $TOKEN")
|
|
id=$(curl -sf "${auth[@]}" "$api/releases/tags/$TAG" | jq -r '.id // empty' || true)
|
|
if [ -z "$id" ]; then
|
|
body=$(jq -n --arg tag "$TAG" '{tag_name: $tag, name: ("devdesk " + $tag), draft: false, prerelease: false,
|
|
body: "Install: `apt update && apt install devdesk` (Gitea apt registry), or download the .deb below."}')
|
|
id=$(curl -sf "${auth[@]}" -H 'Content-Type: application/json' -d "$body" "$api/releases" | jq -r '.id')
|
|
fi
|
|
deb=$(ls out/devdesk_*.deb)
|
|
name=$(basename "$deb")
|
|
existing=$(curl -sf "${auth[@]}" "$api/releases/$id/assets" | jq -r --arg n "$name" '.[] | select(.name == $n) | .id')
|
|
[ -n "$existing" ] && curl -sf -X DELETE "${auth[@]}" "$api/releases/$id/assets/$existing"
|
|
curl -sf "${auth[@]}" -H 'Content-Type: application/octet-stream' --data-binary "@$deb" \
|
|
"$api/releases/$id/assets?name=$name" >/dev/null
|
|
echo "release $TAG: $GITEA_URL/$REPO/releases/tag/$TAG"
|
|
|
|
notify-failure:
|
|
name: Notify on failure
|
|
runs-on: ubuntu-latest
|
|
needs: [build]
|
|
if: failure() && github.event_name == 'push'
|
|
steps:
|
|
- name: Send Matrix alert
|
|
env:
|
|
MATRIX_WEBHOOK_URL: ${{ secrets.MATRIX_WEBHOOK_URL }}
|
|
REPO: ${{ github.repository }}
|
|
BRANCH: ${{ github.ref_name }}
|
|
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
|
run: |
|
|
if [ -z "$MATRIX_WEBHOOK_URL" ] || [ "$MATRIX_WEBHOOK_URL" = "CONFIGURE_ME" ]; then exit 0; fi
|
|
curl -sf -X POST "$MATRIX_WEBHOOK_URL" \
|
|
-H "Content-Type: application/json" \
|
|
-d "{\"text\":\"CI FAILED: ${REPO} @ ${BRANCH} — ${RUN_URL}\"}"
|