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
2 changes: 1 addition & 1 deletion .claude/rules/devcontainer.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ This project includes a fully configured DevContainer for VS Code and Cursor.
## Pre-installed Tools

- **Bun** - JavaScript runtime and package manager
- **Node.js 20** - For npm packages requiring Node
- **Node.js 24** - For npm packages requiring Node
- **Claude Code CLI** - AI-powered coding assistant
- **mkcert** - Local HTTPS certificate generation

Expand Down
1 change: 0 additions & 1 deletion .devcontainer/compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,6 @@ services:
dragonfly
--maxmemory=128mb
--proactor_threads=1
--cache_mode=true
--default_lua_flags=allow-undeclared-keys
ports:
- "6379:6379"
Expand Down
2 changes: 1 addition & 1 deletion .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
"features": {
"ghcr.io/shyim/devcontainers-features/bun:0": {},
"ghcr.io/devcontainers/features/node:1": {
"version": "20"
"version": "24"
}
},
"containerEnv": {
Expand Down
6 changes: 4 additions & 2 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -219,8 +219,10 @@ ENABLE_REGISTRATION=true

# Redis URL for caching and job queues
# Required for ActivityPub delivery queue in production
# (DRAGONFLY_URL is also accepted as a fallback)
REDIS_URL=redis://localhost:6379
# Defaults to redis://localhost:6379. DRAGONFLY_URL is also accepted as a
# fallback; docker/compose.yml and the devcontainer set DRAGONFLY_URL, so leave
# this commented out there (REDIS_URL takes precedence over DRAGONFLY_URL)
# REDIS_URL=redis://localhost:6379

# Disable queue and use synchronous delivery (default: false)
# Set to "true" to disable BullMQ queue (useful when Redis/Dragonfly has issues)
Expand Down
4 changes: 3 additions & 1 deletion .github/workflows/auto-tag.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,9 @@ jobs:
echo "Creating tag: $TAG"
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git tag -a "$TAG" -m "Release $TAG"
# The "[auto-tag]" marker in the tag message tells release.yml to skip
# this tag (#278, #282). Keep it in sync with the guard job there.
git tag -a "$TAG" -m "Release $TAG [auto-tag]"
git push origin "$TAG"
echo "Successfully created and pushed tag: $TAG"
echo "created=true" >> $GITHUB_OUTPUT
Expand Down
45 changes: 41 additions & 4 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name: Manual Release
# Manual release workflow for:
# - Hotfix releases (bypassing normal dev -> main flow)
# - Re-creating failed releases
# - Creating releases from manually pushed tags
# - Creating releases from manually pushed tags (tags pushed by auto-tag.yml are skipped)
#
# For normal releases, use auto-tag.yml (triggered by version bump in package.json)

Expand All @@ -29,15 +29,52 @@ permissions:
contents: read

jobs:
# 0. Skip tags pushed by auto-tag.yml. auto-tag pushes with RELEASE_PAT, which
# (unlike GITHUB_TOKEN) fires this workflow too; without this guard both
# workflows create the same release and race on the SBOM upload (#278).
# auto-tag puts an explicit "[auto-tag]" marker in the tag message (#282).
# The github-actions[bot] tagger check covers tags created before the
# marker was introduced.
guard:
runs-on: ubuntu-latest
outputs:
skip: ${{ steps.check.outputs.skip }}
steps:
- name: Check tagger
id: check
env:
GH_TOKEN: ${{ github.token }}
TAG: ${{ github.ref_name }}
run: |
if [ "${{ github.event_name }}" != "push" ]; then
echo "skip=false" >> "$GITHUB_OUTPUT"
exit 0
fi
read -r TYPE SHA < <(gh api "repos/${{ github.repository }}/git/ref/tags/${TAG}" --jq '.object.type + " " + .object.sha')
TAGGER=""
MESSAGE=""
if [ "$TYPE" = "tag" ]; then
TAGGER=$(gh api "repos/${{ github.repository }}/git/tags/${SHA}" --jq '.tagger.name')
MESSAGE=$(gh api "repos/${{ github.repository }}/git/tags/${SHA}" --jq '.message')
fi
echo "Tag ${TAG}: type=${TYPE}, tagger=${TAGGER:-<none>}, message=${MESSAGE:-<none>}"
if [[ "$MESSAGE" == *"[auto-tag]"* ]] || [ "$TAGGER" = "github-actions[bot]" ]; then
echo "Tag was created by auto-tag.yml, which handles the release. Skipping."
echo "skip=true" >> "$GITHUB_OUTPUT"
else
echo "skip=false" >> "$GITHUB_OUTPUT"
fi

# 1. Run CI workflow first (unless skipped)
call-ci:
if: ${{ github.event_name == 'push' || !inputs.skip_ci }}
needs: guard
if: ${{ needs.guard.outputs.skip != 'true' && (github.event_name == 'push' || !inputs.skip_ci) }}
uses: ./.github/workflows/ci.yml

# 2. Create release after CI passes
create-release:
needs: call-ci
if: ${{ always() && (needs.call-ci.result == 'success' || needs.call-ci.result == 'skipped') }}
needs: [guard, call-ci]
if: ${{ always() && needs.guard.result == 'success' && needs.guard.outputs.skip != 'true' && (needs.call-ci.result == 'success' || needs.call-ci.result == 'skipped') }}
runs-on: ubuntu-latest
permissions:
contents: write
Expand Down
9 changes: 3 additions & 6 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion docker/compose.dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@ services:
dragonfly
--maxmemory=128mb
--proactor_threads=1
--cache_mode=true
--default_lua_flags=allow-undeclared-keys
ports:
- "6379:6379"
Expand Down
3 changes: 1 addition & 2 deletions docker/compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ services:
postgres:
condition: service_healthy
dragonfly:
condition: service_started
condition: service_healthy
environment:
- NODE_ENV=production
- PORT=3000
Expand Down Expand Up @@ -195,7 +195,6 @@ services:
dragonfly
--maxmemory=256mb
--proactor_threads=2
--cache_mode=true
--default_lua_flags=allow-undeclared-keys
volumes:
- dragonfly-data:/data
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "rox",
"version": "2026.10.0",
"version": "2026.10.1",
"license": "AGPL-3.0-only",
"devDependencies": {
"@playwright/test": "^1.64.0",
Expand Down
3 changes: 1 addition & 2 deletions packages/backend/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "hono_rox",
"version": "1.10.0",
"version": "1.10.1",
"private": true,
"license": "AGPL-3.0-only",
"type": "module",
Expand All @@ -25,7 +25,6 @@
"@sentry/bun": "^11.7.0",
"@sentry/core": "11.7.0",
"@simplewebauthn/server": "^14.0.3",
"@simplewebauthn/types": "^12.0.0",
"blurhash": "^2.0.5",
"bullmq": "^6.3.14",
"drizzle-orm": "^0.45.4",
Expand Down
28 changes: 10 additions & 18 deletions packages/backend/src/adapters/cache/DragonflyCacheAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@
* Dragonfly Cache Adapter
*
* Implements ICacheService using Dragonfly (Redis-compatible) as the backend.
* Uses ioredis for connection management.
* Uses ioredis for connection management (see lib/redis.ts).
*
* @module adapters/cache/DragonflyCacheAdapter
*/

import { Redis } from "ioredis";
import type { Redis } from "ioredis";
import type { ICacheService, CacheSetOptions } from "../../interfaces/ICacheService.js";
import { logger } from "../../lib/logger.js";
import { createRedisClient, waitForRedisReady } from "../../lib/redis.js";
import { recordCacheOperation } from "../../lib/metrics.js";

/**
Expand Down Expand Up @@ -61,31 +62,22 @@ export class DragonflyCacheAdapter implements ICacheService {
* Initialize Redis connection
*/
private async initialize(): Promise<void> {
// docker/compose.yml と K8s secret は DRAGONFLY_URL を設定しているため両方を受け付ける
const redisUrl = process.env.REDIS_URL || process.env.DRAGONFLY_URL || "redis://localhost:6379";

try {
this.redis = new Redis(redisUrl, {
this.redis = createRedisClient("cache", {
maxRetriesPerRequest: 3,
// ioredis v6 は既定で RESP3 になったため、v5 と同じ RESP2 を明示(Dragonfly/BullMQ 互換性のため)
protocol: 2,
retryStrategy: (times) => {
if (times > 3) return null;
return Math.min(times * 100, 1000);
},
connectTimeout: 5000,
enableOfflineQueue: false,
lazyConnect: true,
enableOfflineQueue: false, // Cache misses while disconnected instead of queueing commands
});

await this.redis.connect();
await this.redis.ping();
// 起動直後は Dragonfly がまだ接続を受け付けていないことがあるため、一定時間は再試行を待つ
await waitForRedisReady(this.redis);

this.available = true;
logger.debug("Dragonfly cache connected");
logger.info("Dragonfly cache connected");
} catch (error) {
logger.warn({ err: error }, "Dragonfly cache not available, caching disabled");
this.available = false;
// 失敗した接続が裏で再接続を続けないように切断する
this.redis?.disconnect();
this.redis = null;
}
}
Expand Down
Loading
Loading