mirror of
https://github.com/fluffos/fluffos
synced 2026-08-12 18:26:06 -04:00
The lws output wedge addressed in the previous commit was incompletely understood: under genuine backpressure (peer slow or paused, kernel send buffer full) a connection still froze permanently. Root cause, established by tracing the writeable-request plumbing end to end: lws_send_pipe_choked() is true not only when lws holds a truncated send (lws re-arms the writeable callback itself then) but also when a zero-timeout poll(POLLOUT) reports the socket simply full -- and in that case every lws_write() has fully succeeded, lws has nothing pending, and nobody re-arms anything. Fix, per lws README.coding.md: whenever the drain loop exits with data still queued in pss->buffer, request the next writeable callback. Queued data always has a callback requested, so no exit path can strand output. Also: - LWS_CALLBACK_CLOSED frees the session evbuffer unconditionally: on driver-initiated closes (e.g. the mudlib destructing the interactive) close_user_websocket() nulls pss->user first, and the old early return leaked the buffer every time. - src/www/README.md + src/www/AGENTS.md: architecture doc and agent checklist for the web terminal pages (xterm.js/telnet.js layering, vendor policy, packaging, testing, the wedge mechanism). - tools/ws-smoke.js, wired into CI on the Clang Debug matrix entries (with and without sanitizers): a dependency-free node websocket client that boots the real driver and exercises the http mount, telnet + ascii subprotocols through the shared src/www/telnet.js, SGA char-mode switching, live TUI streaming, TLS, and -- the actual regression gate -- forced-backpressure bursts (paused socket, ~4.8MB) on the plain and TLS ports plus a destruct-while-choked teardown check. All three backpressure checks fail on the unfixed driver; neither GTest nor the LPC suite exercises any websocket client traffic. - Fix stale src/www/wasm/vendor/ path references left from the vendor directory move (src/wasm/README.md, docs/build-wasm.md, release.yml). Validated on the ASan Debug build: forced-backpressure repros recover the full burst on both subprotocols, destruct-while-choked clean under ASan, ws-smoke 17/17, GTest 312/312, LPC testsuite clean.
488 lines
17 KiB
YAML
488 lines
17 KiB
YAML
name: Release
|
|
|
|
on:
|
|
workflow_dispatch:
|
|
inputs:
|
|
prerelease:
|
|
description: 'Mark as pre-release'
|
|
required: false
|
|
type: boolean
|
|
default: false
|
|
schedule:
|
|
# Monthly auto-release: 09:00 UTC on the 1st. Skipped automatically
|
|
# when master has no new commits since the latest release tag
|
|
# (see the check-changes job).
|
|
- cron: '0 9 1 * *'
|
|
push:
|
|
# Pushing a version tag also cuts a release for that tag; pushing a
|
|
# release/vX branch cuts a release named vX (the workflow creates the
|
|
# tag and deletes the trigger branch) -- useful for tooling that can
|
|
# push refs but lacks the actions:write scope to dispatch workflows.
|
|
tags:
|
|
- 'v*'
|
|
branches:
|
|
- 'release/v*'
|
|
|
|
env:
|
|
REGISTRY: ghcr.io
|
|
IMAGE_NAME: ${{ github.repository }}
|
|
# Per-platform build configuration lives in the composite actions under
|
|
# .github/actions/, shared with the CI workflow.
|
|
|
|
permissions:
|
|
contents: write
|
|
packages: write
|
|
|
|
jobs:
|
|
check-changes:
|
|
name: Check for Changes Since Last Release
|
|
runs-on: ubuntu-latest
|
|
outputs:
|
|
should_release: ${{ steps.check.outputs.should_release }}
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
with:
|
|
fetch-depth: 0
|
|
|
|
- name: Check for changes
|
|
id: check
|
|
run: |
|
|
git fetch --tags
|
|
|
|
# Manual dispatch and tag pushes always release; only the
|
|
# monthly schedule is gated on changes
|
|
if [ "${{ github.event_name }}" != "schedule" ]; then
|
|
echo "${{ github.event_name }} event: releasing"
|
|
echo "should_release=true" >> $GITHUB_OUTPUT
|
|
exit 0
|
|
fi
|
|
|
|
# Most recent release tag by creation date (tag formats have
|
|
# varied over the years, so date order beats version sort)
|
|
LAST_TAG=$(git tag -l 'v*' --sort=-creatordate | head -n 1)
|
|
|
|
if [ -z "$LAST_TAG" ]; then
|
|
echo "No release tag found: releasing"
|
|
echo "should_release=true" >> $GITHUB_OUTPUT
|
|
exit 0
|
|
fi
|
|
|
|
COUNT=$(git rev-list --count "$LAST_TAG"..HEAD)
|
|
echo "$COUNT commit(s) since $LAST_TAG"
|
|
if [ "$COUNT" -gt 0 ]; then
|
|
echo "should_release=true" >> $GITHUB_OUTPUT
|
|
else
|
|
echo "No changes since $LAST_TAG: skipping release"
|
|
echo "should_release=false" >> $GITHUB_OUTPUT
|
|
fi
|
|
|
|
generate-version:
|
|
name: Generate Version Number
|
|
needs: check-changes
|
|
if: needs.check-changes.outputs.should_release == 'true'
|
|
runs-on: ubuntu-latest
|
|
outputs:
|
|
version: ${{ steps.generate.outputs.version }}
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
with:
|
|
fetch-depth: 0
|
|
|
|
- name: Generate Version
|
|
id: generate
|
|
run: |
|
|
# A pushed tag (or release/vX trigger branch) IS the version
|
|
if [ "${{ github.event_name }}" = "push" ]; then
|
|
VERSION="${GITHUB_REF_NAME#release/}"
|
|
echo "Using pushed ref version: ${VERSION}"
|
|
echo "version=${VERSION}" >> $GITHUB_OUTPUT
|
|
exit 0
|
|
fi
|
|
|
|
# Get current date in YYYY.MMDD format
|
|
DATE_VERSION=$(date -u '+%Y.%m%d')
|
|
|
|
# Fetch all tags
|
|
git fetch --tags
|
|
|
|
# Find existing tags for today's date
|
|
EXISTING_TAGS=$(git tag -l "v${DATE_VERSION}.*" | sort -V)
|
|
|
|
# Find the highest increment for today
|
|
if [ -z "$EXISTING_TAGS" ]; then
|
|
INCREMENT=0
|
|
else
|
|
LAST_TAG=$(echo "$EXISTING_TAGS" | tail -n 1)
|
|
LAST_INCREMENT=$(echo "$LAST_TAG" | sed -E "s/v${DATE_VERSION}\.([0-9]+)/\1/")
|
|
INCREMENT=$((LAST_INCREMENT + 1))
|
|
fi
|
|
|
|
# Generate new version
|
|
VERSION="v${DATE_VERSION}.${INCREMENT}"
|
|
|
|
echo "Generated version: $VERSION"
|
|
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
|
|
|
create-release:
|
|
name: Create Release
|
|
needs: generate-version
|
|
runs-on: ubuntu-latest
|
|
outputs:
|
|
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
|
release_id: ${{ steps.create_release.outputs.id }}
|
|
version: ${{ needs.generate-version.outputs.version }}
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
with:
|
|
fetch-depth: 0
|
|
|
|
- name: Create Git Tag
|
|
# A pushed tag already exists; every other trigger tags HEAD here
|
|
if: ${{ !startsWith(github.ref, 'refs/tags/') }}
|
|
run: |
|
|
VERSION="${{ needs.generate-version.outputs.version }}"
|
|
git config user.name "github-actions[bot]"
|
|
git config user.email "github-actions[bot]@users.noreply.github.com"
|
|
git tag -a "$VERSION" -m "Release $VERSION"
|
|
git push origin "$VERSION"
|
|
|
|
# A release/vX trigger branch has done its job once the tag exists
|
|
case "${GITHUB_REF}" in
|
|
refs/heads/release/*)
|
|
git push origin --delete "${GITHUB_REF_NAME}" || true
|
|
;;
|
|
esac
|
|
|
|
- name: Create Release
|
|
id: create_release
|
|
uses: actions/github-script@v7
|
|
with:
|
|
script: |
|
|
const version = '${{ needs.generate-version.outputs.version }}';
|
|
const release = await github.rest.repos.createRelease({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
tag_name: version,
|
|
name: `FluffOS ${version}`,
|
|
generate_release_notes: true,
|
|
prerelease: ${{ inputs.prerelease || false }},
|
|
draft: false
|
|
});
|
|
core.setOutput('upload_url', release.data.upload_url);
|
|
core.setOutput('id', release.data.id);
|
|
return release.data.id;
|
|
|
|
# Windows binary. The build+test is the same build-windows composite the
|
|
# CI `windows` job runs; this job adds packaging + release upload.
|
|
build-windows:
|
|
name: Build Windows Binary
|
|
needs: create-release
|
|
runs-on: windows-latest
|
|
defaults:
|
|
run:
|
|
shell: msys2 {0}
|
|
steps:
|
|
- name: Setup MSYS2
|
|
uses: msys2/setup-msys2@v2
|
|
with:
|
|
update: true
|
|
|
|
- uses: actions/checkout@v4
|
|
with:
|
|
ref: ${{ needs.create-release.outputs.version }}
|
|
|
|
- name: Build & test
|
|
uses: ./.github/actions/build-windows
|
|
with:
|
|
build-type: Release
|
|
db-sqlite: '1'
|
|
extra-packages: zip # for packaging the release archive below
|
|
|
|
- name: Package
|
|
run: |
|
|
ASSET_NAME="fluffos-${{ needs.create-release.outputs.version }}-windows-x86_64.zip"
|
|
cd build/bin && zip -r "../../${ASSET_NAME}" .
|
|
|
|
- name: Upload Release Asset
|
|
uses: actions/upload-release-asset@v1
|
|
env:
|
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
with:
|
|
upload_url: ${{ needs.create-release.outputs.upload_url }}
|
|
asset_path: ./fluffos-${{ needs.create-release.outputs.version }}-windows-x86_64.zip
|
|
asset_name: fluffos-${{ needs.create-release.outputs.version }}-windows-x86_64.zip
|
|
asset_content_type: application/zip
|
|
|
|
# Linux static binary. The build+test is the same build-alpine-static
|
|
# composite the CI `alpine-static` job runs; this job adds packaging +
|
|
# release upload.
|
|
build-linux:
|
|
name: Build Linux Binary
|
|
needs: create-release
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
with:
|
|
ref: ${{ needs.create-release.outputs.version }}
|
|
|
|
- name: Build & test
|
|
uses: ./.github/actions/build-alpine-static
|
|
with:
|
|
build-type: Release
|
|
|
|
- name: Package
|
|
run: |
|
|
ASSET_NAME="fluffos-${{ needs.create-release.outputs.version }}-linux-x86_64-static.tar.gz"
|
|
cd build/bin && tar czf "../../${ASSET_NAME}" .
|
|
|
|
- name: Upload Release Asset
|
|
uses: actions/upload-release-asset@v1
|
|
env:
|
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
with:
|
|
upload_url: ${{ needs.create-release.outputs.upload_url }}
|
|
asset_path: ./fluffos-${{ needs.create-release.outputs.version }}-linux-x86_64-static.tar.gz
|
|
asset_name: fluffos-${{ needs.create-release.outputs.version }}-linux-x86_64-static.tar.gz
|
|
asset_content_type: application/gzip
|
|
|
|
# WebAssembly driver: cross-compile with the pinned emsdk (see
|
|
# .github/actions/build-wasm/action.yml), gate on the LPC testsuite
|
|
# running inside the wasm driver under node, then ship driver + web
|
|
# terminal + mudlib packer as a self-contained zip. See docs/build-wasm.md
|
|
# for the workflow this asset supports.
|
|
build-wasm:
|
|
name: Build WASM Binary
|
|
needs: create-release
|
|
runs-on: ubuntu-24.04
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
with:
|
|
ref: ${{ needs.create-release.outputs.version }}
|
|
|
|
# Same build-wasm composite the CI `wasm` job runs; this job adds
|
|
# packaging + release upload.
|
|
- name: Build & test
|
|
uses: ./.github/actions/build-wasm
|
|
|
|
- name: Package
|
|
run: |
|
|
VERSION="${{ needs.create-release.outputs.version }}"
|
|
ASSET_NAME="fluffos-${VERSION}-wasm.zip"
|
|
STAGE=$(mktemp -d)
|
|
cp build-wasm/src/fluffos.js build-wasm/src/fluffos.wasm "$STAGE/"
|
|
cp src/www/wasm/index.html "$STAGE/"
|
|
cp src/www/telnet.js "$STAGE/"
|
|
cp -r src/www/vendor "$STAGE/"
|
|
cp tools/wasm/pack-mudlib.sh "$STAGE/"
|
|
cat > "$STAGE/README.md" << EOF
|
|
# FluffOS ${VERSION} -- WebAssembly driver
|
|
|
|
The full FluffOS driver compiled to WebAssembly: it runs your
|
|
mudlib inside a browser tab and speaks telnet with the page.
|
|
|
|
Contents:
|
|
- fluffos.js / fluffos.wasm -- the driver
|
|
- index.html -- the web terminal (xterm.js under vendor/,
|
|
telnet client in telnet.js)
|
|
- pack-mudlib.sh -- bundles YOUR mudlib with this driver into a
|
|
static site (requires emscripten's file_packager, i.e. emsdk on PATH)
|
|
|
|
Quick start:
|
|
|
|
./pack-mudlib.sh --mudlib /path/to/mylib --config etc/config
|
|
python3 -m http.server -d dist 8080
|
|
|
|
Docs: https://www.fluffos.info/build-wasm
|
|
Cookbook (packer + JsBridge): https://www.fluffos.info/driver/wasm
|
|
EOF
|
|
(cd "$STAGE" && zip -r - .) > "$ASSET_NAME"
|
|
|
|
- name: Upload Release Asset
|
|
uses: actions/upload-release-asset@v1
|
|
env:
|
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
with:
|
|
upload_url: ${{ needs.create-release.outputs.upload_url }}
|
|
asset_path: ./fluffos-${{ needs.create-release.outputs.version }}-wasm.zip
|
|
asset_name: fluffos-${{ needs.create-release.outputs.version }}-wasm.zip
|
|
asset_content_type: application/zip
|
|
|
|
build-docker:
|
|
name: Build and Push Docker Image
|
|
needs: create-release
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
with:
|
|
ref: ${{ needs.create-release.outputs.version }}
|
|
|
|
- name: Set up Docker Buildx
|
|
uses: docker/setup-buildx-action@v3
|
|
|
|
- name: Log into registry ${{ env.REGISTRY }}
|
|
uses: docker/login-action@v3
|
|
with:
|
|
registry: ${{ env.REGISTRY }}
|
|
username: ${{ github.actor }}
|
|
password: ${{ secrets.GITHUB_TOKEN }}
|
|
|
|
- name: Extract Docker metadata
|
|
id: meta
|
|
uses: docker/metadata-action@v5
|
|
with:
|
|
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
|
tags: |
|
|
type=semver,pattern={{version}},value=${{ needs.create-release.outputs.version }}
|
|
type=semver,pattern={{major}}.{{minor}},value=${{ needs.create-release.outputs.version }}
|
|
type=semver,pattern={{major}},value=${{ needs.create-release.outputs.version }}
|
|
type=raw,value=${{ needs.create-release.outputs.version }},enable=true
|
|
type=raw,value=latest,enable=${{ !inputs.prerelease }}
|
|
|
|
- name: Build and push Docker image
|
|
uses: docker/build-push-action@v5
|
|
with:
|
|
context: .
|
|
push: true
|
|
tags: ${{ steps.meta.outputs.tags }}
|
|
labels: ${{ steps.meta.outputs.labels }}
|
|
platforms: linux/amd64
|
|
cache-from: type=gha
|
|
cache-to: type=gha,mode=max
|
|
|
|
finalize-release:
|
|
name: Finalize Release
|
|
needs: [create-release, build-windows, build-linux, build-wasm, build-docker]
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
with:
|
|
ref: ${{ needs.create-release.outputs.version }}
|
|
|
|
- name: Generate Release Documentation
|
|
id: docs
|
|
run: |
|
|
VERSION="${{ needs.create-release.outputs.version }}"
|
|
cat > release-notes.md << EOF
|
|
## Release Assets
|
|
|
|
This release includes the following pre-built distributions:
|
|
|
|
### Binary Distributions
|
|
- **Windows x86_64**: \`fluffos-${VERSION}-windows-x86_64.zip\`
|
|
- Built with MSYS2/MinGW64
|
|
- Complete distribution with all tools and libraries
|
|
- SQLite support enabled
|
|
|
|
- **Linux x86_64 (Static)**: \`fluffos-${VERSION}-linux-x86_64-static.tar.gz\`
|
|
- Statically linked binaries (no external dependencies)
|
|
- Built on Alpine Linux
|
|
- Complete distribution with all tools and libraries
|
|
- Portable across most Linux distributions
|
|
|
|
- **WebAssembly**: \`fluffos-${VERSION}-wasm.zip\`
|
|
- The driver compiled to WebAssembly: runs a full mudlib in a
|
|
browser tab (the page is the telnet client)
|
|
- Contains \`fluffos.js\`/\`fluffos.wasm\`, a self-contained web
|
|
terminal (\`index.html\`), and \`pack-mudlib.sh\` to bundle your
|
|
mudlib into a static site
|
|
- Validated by running the LPC testsuite inside the wasm driver
|
|
- See the [WebAssembly build guide](https://www.fluffos.info/build-wasm)
|
|
and the [WASM driver cookbook](https://www.fluffos.info/driver/wasm)
|
|
|
|
### What's Included
|
|
Each distribution contains:
|
|
- **driver** - Main FluffOS driver executable
|
|
- **lpcc** - LPC compiler
|
|
- **symbol** - Symbol table utility
|
|
- **o2json/json2o** - Object file converters
|
|
- **portbind** - Port binding helper (Linux only)
|
|
- **std/** - LPC standard library
|
|
- **include/** - LPC header files
|
|
- **www/** - WebSocket client files
|
|
- **keywords.json** - Language keywords database
|
|
|
|
### Docker Images
|
|
Docker images are available at \`ghcr.io/${{ github.repository }}\`:
|
|
- \`${VERSION}\` - This release version
|
|
${{ !inputs.prerelease && '- `latest` - Latest stable release' || '' }}
|
|
|
|
Pull with:
|
|
\`\`\`bash
|
|
docker pull ghcr.io/${{ github.repository }}:${VERSION}
|
|
\`\`\`
|
|
|
|
## Installation
|
|
|
|
### Windows
|
|
\`\`\`bash
|
|
# Extract distribution
|
|
unzip fluffos-${VERSION}-windows-x86_64.zip -d fluffos
|
|
cd fluffos
|
|
|
|
# Run driver with your config
|
|
./driver.exe /path/to/mudlib/config.cfg
|
|
\`\`\`
|
|
|
|
### Linux
|
|
\`\`\`bash
|
|
# Extract distribution
|
|
mkdir fluffos && cd fluffos
|
|
tar xzf ../fluffos-${VERSION}-linux-x86_64-static.tar.gz
|
|
|
|
# Run driver with your config
|
|
./driver /path/to/mudlib/config.cfg
|
|
\`\`\`
|
|
|
|
### Docker
|
|
\`\`\`bash
|
|
docker run -v /path/to/mudlib:/mudlib ghcr.io/${{ github.repository }}:${VERSION} /mudlib/config.cfg
|
|
\`\`\`
|
|
|
|
### WebAssembly (browser)
|
|
\`\`\`bash
|
|
# Bundle your mudlib with the wasm driver into a static site
|
|
# (needs emscripten's file_packager, i.e. emsdk on PATH)
|
|
unzip fluffos-${VERSION}-wasm.zip -d fluffos-wasm
|
|
cd fluffos-wasm
|
|
./pack-mudlib.sh --mudlib /path/to/mudlib --config etc/config
|
|
python3 -m http.server -d dist 8080 # then open http://localhost:8080/
|
|
\`\`\`
|
|
|
|
---
|
|
EOF
|
|
|
|
echo "RELEASE_NOTES<<EOF" >> $GITHUB_OUTPUT
|
|
cat release-notes.md >> $GITHUB_OUTPUT
|
|
echo "EOF" >> $GITHUB_OUTPUT
|
|
|
|
- name: Update Release Notes
|
|
uses: actions/github-script@v7
|
|
with:
|
|
script: |
|
|
const fs = require('fs');
|
|
const release = await github.rest.repos.getRelease({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
release_id: ${{ needs.create-release.outputs.release_id }}
|
|
});
|
|
|
|
const additionalNotes = fs.readFileSync('release-notes.md', 'utf8');
|
|
|
|
await github.rest.repos.updateRelease({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
release_id: ${{ needs.create-release.outputs.release_id }},
|
|
body: release.data.body + '\n\n' + additionalNotes
|
|
});
|
|
|
|
- name: Release Summary
|
|
run: |
|
|
VERSION="${{ needs.create-release.outputs.version }}"
|
|
cat << EOF
|
|
✅ Release ${VERSION} created successfully!
|
|
📦 Windows binary: fluffos-${VERSION}-windows-x86_64.zip
|
|
📦 Linux static binary: fluffos-${VERSION}-linux-x86_64-static.tar.gz
|
|
📦 WebAssembly driver + packer: fluffos-${VERSION}-wasm.zip
|
|
🐳 Docker image: ghcr.io/${{ github.repository }}:${VERSION}
|
|
🔗 View release: https://github.com/${{ github.repository }}/releases/tag/${VERSION}
|
|
EOF
|