Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
43 changes: 43 additions & 0 deletions .github/workflows/goreleaser.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,46 @@ jobs:
dist/*.tar.gz
dist/*.zip
dist/*.txt

- name: Mirror release assets to S3-compatible storage
env:
AWS_ACCESS_KEY_ID: ${{ secrets.MIRROR_S3_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.MIRROR_S3_SECRET_ACCESS_KEY }}
AWS_DEFAULT_REGION: ${{ secrets.MIRROR_S3_REGION }}
BUCKET: ${{ secrets.MIRROR_S3_BUCKET }}
ENDPOINT: ${{ secrets.MIRROR_S3_ENDPOINT }}
PREFIX: ${{ secrets.MIRROR_S3_PATH_PREFIX }}
VERSION: ${{ github.ref_name }}
run: |
set -eu
if [ -z "${BUCKET:-}" ] || [ -z "${ENDPOINT:-}" ]; then
echo "Mirror not configured (need MIRROR_S3_BUCKET + MIRROR_S3_ENDPOINT). Skipping."
exit 0
fi

# Normalize PREFIX: strip both leading and trailing slashes so a
# value of "/" or "/foo/" doesn't produce a doubled or leading slash
# in the resulting key.
PREFIX="${PREFIX#/}"; PREFIX="${PREFIX%/}"
base="${PREFIX:+${PREFIX}/}releases/download/${VERSION}"
uploaded=0
for f in dist/*.tar.gz dist/*.zip dist/checksums.txt; do
[ -f "$f" ] || continue
name=$(basename "$f")
echo "Uploading $f -> s3://${BUCKET}/${base}/${name}"
aws --endpoint-url="$ENDPOINT" s3 cp "$f" "s3://${BUCKET}/${base}/${name}" \
--cache-control "public, max-age=31536000, immutable"
uploaded=$((uploaded + 1))
done
if [ "$uploaded" -eq 0 ]; then
echo "No release artifacts found in dist/ — refusing to update latest pointer."
exit 1
fi

# Latest pointer used by install.sh resolve_version when MIRROR_URL is set.
# Updated last so a partial upload doesn't make the mirror advertise a broken version.
latest_key="${PREFIX:+${PREFIX}/}releases/latest"
printf '%s\n' "$VERSION" > /tmp/latest
aws --endpoint-url="$ENDPOINT" s3 cp /tmp/latest "s3://${BUCKET}/${latest_key}" \
--cache-control "public, max-age=60" \
--content-type "text/plain; charset=utf-8"
27 changes: 27 additions & 0 deletions .github/workflows/install-sh.yml
Original file line number Diff line number Diff line change
Expand Up @@ -109,3 +109,30 @@ jobs:
echo ""
echo "ALL CHECKS PASSED"
'

mirror:
name: mirror install.sh
runs-on: ubuntu-latest
needs: smoke
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v6
- name: Upload install.sh to S3-compatible storage
env:
AWS_ACCESS_KEY_ID: ${{ secrets.MIRROR_S3_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.MIRROR_S3_SECRET_ACCESS_KEY }}
AWS_DEFAULT_REGION: ${{ secrets.MIRROR_S3_REGION }}
BUCKET: ${{ secrets.MIRROR_S3_BUCKET }}
ENDPOINT: ${{ secrets.MIRROR_S3_ENDPOINT }}
PREFIX: ${{ secrets.MIRROR_S3_PATH_PREFIX }}
run: |
set -eu
if [ -z "${BUCKET:-}" ] || [ -z "${ENDPOINT:-}" ]; then
echo "Mirror not configured (need MIRROR_S3_BUCKET + MIRROR_S3_ENDPOINT). Skipping."
exit 0
fi
PREFIX="${PREFIX#/}"; PREFIX="${PREFIX%/}"
key="${PREFIX:+${PREFIX}/}install.sh"
aws --endpoint-url="$ENDPOINT" s3 cp install.sh "s3://${BUCKET}/${key}" \
--cache-control "public, max-age=300" \
--content-type "text/x-shellscript; charset=utf-8"
51 changes: 44 additions & 7 deletions install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,18 @@ set -eu
: "${URL:=wss://api.flashcat.cloud/safari/environment/ws}"
: "${VERSION:=}"
: "${TOKEN:=}"
# When set, all release downloads are fetched from this prefix instead of
# github.com — the mirror must replicate GitHub's release layout
# (<MIRROR_URL>/releases/download/<tag>/<asset>) and expose a plain-text
# <MIRROR_URL>/releases/latest file containing the latest tag.
: "${MIRROR_URL:=}"
MIRROR_URL="${MIRROR_URL%/}"
if [ -n "$MIRROR_URL" ]; then
case "$MIRROR_URL" in
https://*) : ;;
*) printf 'MIRROR_URL must use https:// scheme, got: %s\n' "$MIRROR_URL" >&2; exit 1 ;;
esac
fi

BINARY_NAME="flashduty-runner"
CONFIG_DIR="/etc/flashduty-runner"
Expand Down Expand Up @@ -85,6 +97,7 @@ ENVIRONMENT:
URL Runtime WebSocket URL (default: wss://api.flashcat.cloud/…)
INSTALL_DIR Binary install directory (default: /usr/local/bin)
REPO GitHub owner/repo override (default: flashcatcloud/flashduty-runner)
MIRROR_URL Download release assets from this mirror prefix instead of github.com

EXAMPLES:
curl -fsSL https://raw.githubusercontent.com/flashcatcloud/flashduty-runner/main/install.sh | sudo bash
Expand Down Expand Up @@ -180,13 +193,33 @@ resolve_version() {
return
fi

info "Resolving latest release from github.com/${REPO}"
effective="$(curl --proto '=https' --tlsv1.2 -fsSLI -o /dev/null -w '%{url_effective}' "https://github.com/${REPO}/releases/latest" || true)"
VERSION="${effective##*/}"

if [ -z "$VERSION" ] || [ "$VERSION" = "latest" ]; then
die 5 "Could not resolve latest version from github.com/${REPO}/releases/latest"
if [ -n "$MIRROR_URL" ]; then
info "Resolving latest release from mirror"
if ! raw="$(curl --proto '=https' --tlsv1.2 -fsSL "${MIRROR_URL}/releases/latest")"; then
die 5 "Could not fetch ${MIRROR_URL}/releases/latest (curl exit $?)"
fi
# Trim only leading/trailing whitespace — internal whitespace would
# signal a corrupted pointer and should fail the regex check below
# rather than be silently collapsed.
VERSION="$(printf '%s' "$raw" | awk 'NR==1 {gsub(/^[[:space:]]+|[[:space:]]+$/, ""); print; exit}')"
else
info "Resolving latest release from github.com/${REPO}"
effective="$(curl --proto '=https' --tlsv1.2 -fsSLI -o /dev/null -w '%{url_effective}' "https://github.com/${REPO}/releases/latest" || true)"
VERSION="${effective##*/}"
if [ "$VERSION" = "latest" ]; then VERSION=""; fi
fi

# Reject anything that doesn't look like a release tag. Two-stage check:
# first reject characters outside the tag charset (path-traversal guard —
# case glob's bare `*` would otherwise swallow slashes), then require the
# leading 'v' + digit shape.
case "$VERSION" in
*[!A-Za-z0-9.+-]*) die 5 "Resolved version contains illegal characters: '${VERSION}'" ;;
esac
case "$VERSION" in
v[0-9]*) : ;;
*) die 5 "Resolved version is not a valid release tag: '${VERSION}'" ;;
esac
info "Latest version: $VERSION"
}

Expand Down Expand Up @@ -214,7 +247,11 @@ sha256_of() {

download_and_verify() {
asset="${BINARY_NAME}_${OS_TITLE}_${ARCH_GR}.tar.gz"
base="https://github.com/${REPO}/releases/download/${VERSION}"
if [ -n "$MIRROR_URL" ]; then
base="${MIRROR_URL}/releases/download/${VERSION}"
else
base="https://github.com/${REPO}/releases/download/${VERSION}"
fi

TMPDIR_="$(mktemp -d 2>/dev/null || mktemp -d -t frdl)"
trap 'rm -rf "$TMPDIR_"' EXIT INT TERM
Expand Down
26 changes: 13 additions & 13 deletions protocol/messages.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,19 +105,19 @@ type HeartbeatMetrics struct {
type TaskOperation string

const (
TaskOpRead TaskOperation = "read"
TaskOpWrite TaskOperation = "write"
TaskOpList TaskOperation = "list"
TaskOpGlob TaskOperation = "glob"
TaskOpGrep TaskOperation = "grep"
TaskOpBash TaskOperation = "bash"
TaskOpWebFetch TaskOperation = "webfetch"
TaskOpMCPCall TaskOperation = "mcp_call"
TaskOpMCPListTools TaskOperation = "mcp_list_tools"
TaskOpSyncSkill TaskOperation = "sync_skill"
TaskOpStageKnowledgeFiles TaskOperation = "stage_knowledge_files"
TaskOpDeleteKnowledgeFiles TaskOperation = "delete_knowledge_files"
TaskOpReconcileKnowledgeManifest TaskOperation = "reconcile_knowledge_manifest"
TaskOpRead TaskOperation = "read"
TaskOpWrite TaskOperation = "write"
TaskOpList TaskOperation = "list"
TaskOpGlob TaskOperation = "glob"
TaskOpGrep TaskOperation = "grep"
TaskOpBash TaskOperation = "bash"
TaskOpWebFetch TaskOperation = "webfetch"
TaskOpMCPCall TaskOperation = "mcp_call"
TaskOpMCPListTools TaskOperation = "mcp_list_tools"
TaskOpSyncSkill TaskOperation = "sync_skill"
TaskOpStageKnowledgeFiles TaskOperation = "stage_knowledge_files"
TaskOpDeleteKnowledgeFiles TaskOperation = "delete_knowledge_files"
TaskOpReconcileKnowledgeManifest TaskOperation = "reconcile_knowledge_manifest"
)

// TaskRequestPayload is the payload for task request messages.
Expand Down
Loading