From c81ab8e9ef042fe4e3adeadee17046fd0dbdf309 Mon Sep 17 00:00:00 2001 From: Mark Allen Date: Sat, 28 Mar 2026 13:12:04 +0000 Subject: [PATCH 1/3] feat: add 37signals Once platform compatibility - Add /storage/log support for Once persistent volume (falls back to log/ for non-Once deployments) - Use NUM_CPUS env var from Once for Puma worker count - Add no-op backup/restore hooks (app is stateless) - Update Dockerfile with /storage and hook setup - Add Once deployment documentation Closes #35 Co-Authored-By: Claude Opus 4.6 (1M context) --- Dockerfile | 5 +++++ README.md | 4 ++++ app.rb | 6 +++++- config/puma.rb | 2 +- docs/ONCE.md | 35 +++++++++++++++++++++++++++++++++++ once/hooks/post-restore | 3 +++ once/hooks/pre-backup | 3 +++ 7 files changed, 56 insertions(+), 2 deletions(-) create mode 100644 docs/ONCE.md create mode 100755 once/hooks/post-restore create mode 100755 once/hooks/pre-backup diff --git a/Dockerfile b/Dockerfile index a549e0f..60a371a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -10,4 +10,9 @@ ENV RACK_ENV=production ENV PORT=80 EXPOSE 80 +# Once platform: persistent storage and backup/restore hooks +RUN mkdir -p /storage/log +COPY once/hooks/ /hooks/ +RUN chmod +x /hooks/* + CMD ["bundle", "exec", "puma", "-C", "config/puma.rb"] diff --git a/README.md b/README.md index c3656fd..ae50302 100644 --- a/README.md +++ b/README.md @@ -220,6 +220,10 @@ This project is configured for deployment using [Kamal](https://kamal-deploy.org kamal deploy ``` +## Once Platform + +This app is compatible with [37signals' Once](https://once.com/). See [docs/ONCE.md](docs/ONCE.md) for setup details. + ## Docker You can also run the application using Docker: diff --git a/app.rb b/app.rb index fe801ea..5b41bda 100644 --- a/app.rb +++ b/app.rb @@ -20,7 +20,11 @@ set :debug_mode, ENV.fetch('DEBUG', nil) == 'true' || development? # Setup logging - log_dir = File.join(settings.root, 'log') + log_dir = if Dir.exist?('/storage') + '/storage/log' + else + File.join(settings.root, 'log') + end FileUtils.mkdir_p(log_dir) log_file = File.join(log_dir, "#{settings.environment}.log") diff --git a/config/puma.rb b/config/puma.rb index 121417c..0b092e1 100644 --- a/config/puma.rb +++ b/config/puma.rb @@ -5,7 +5,7 @@ environment ENV.fetch('RACK_ENV', 'production') # Number of worker processes -workers ENV.fetch('WEB_CONCURRENCY', 2) +workers ENV.fetch('WEB_CONCURRENCY') { ENV.fetch('NUM_CPUS', 2) } # Number of threads per worker threads_count = ENV.fetch('RAILS_MAX_THREADS', 5) diff --git a/docs/ONCE.md b/docs/ONCE.md new file mode 100644 index 0000000..3ab9113 --- /dev/null +++ b/docs/ONCE.md @@ -0,0 +1,35 @@ +# Once Platform Deployment + +This application is compatible with [37signals' Once](https://once.com/) platform. + +## Compatibility + +The app meets all Once requirements: + +| Requirement | Status | +|-------------|--------| +| Docker container | Yes | +| HTTP on port 80 | Yes | +| Health check at `/up` | Yes (returns 200 OK) | +| Persistent data in `/storage` | Yes (logs only) | + +## Environment Variables + +**Required** (set in Once admin UI): + +- `SLACK_BOT_TOKEN` — Slack bot token (starts with `xoxb-`) +- `GITHUB_TOKEN` — GitHub personal access token (starts with `ghp_`) + +**Automatically provided by Once:** + +- `NUM_CPUS` — Used to set Puma worker count (falls back to `WEB_CONCURRENCY`, then default of 2) +- `SECRET_KEY_BASE` — Injected but not used by this app +- `DISABLE_SSL` — Injected but not used (SSL is handled by the reverse proxy) + +## Storage + +The app is stateless — no database or file uploads. The `/storage` volume is used only for log files at `/storage/log/`. When running outside Once, logs fall back to `log/` in the app directory. + +## Backup and Restore + +The Once hook scripts at `/hooks/pre-backup` and `/hooks/post-restore` are no-ops because the app has no stateful data to back up. Source files are in `once/hooks/` in the repository. diff --git a/once/hooks/post-restore b/once/hooks/post-restore new file mode 100755 index 0000000..ef0e6cc --- /dev/null +++ b/once/hooks/post-restore @@ -0,0 +1,3 @@ +#!/bin/sh +# No-op: app has no database or stateful data to restore. +exit 0 diff --git a/once/hooks/pre-backup b/once/hooks/pre-backup new file mode 100755 index 0000000..7aec8c8 --- /dev/null +++ b/once/hooks/pre-backup @@ -0,0 +1,3 @@ +#!/bin/sh +# No-op: app has no database or stateful data to flush before backup. +exit 0 From 4750a9c8916a1f5bcde33bdf58ac44e2d12742ef Mon Sep 17 00:00:00 2001 From: Mark Allen Date: Sat, 28 Mar 2026 20:02:21 +0000 Subject: [PATCH 2/3] ci: publish Docker image to GHCR on release Add a docker job to the release workflow that builds and pushes to ghcr.io on version tags. Tags images with version and latest. Update Once docs with the GHCR image path. Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/release.yml | 38 +++++++++++++++++++++++++++++++++++ docs/ONCE.md | 10 +++++++++ 2 files changed, 48 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 393a159..b395fff 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -10,6 +10,7 @@ env: permissions: contents: write + packages: write jobs: release: @@ -85,3 +86,40 @@ jobs: files: slack-github-threads-v${{ steps.version.outputs.VERSION }}.tar.gz draft: false prerelease: false + + docker: + runs-on: ubuntu-latest + needs: release + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Extract version from tag + id: version + run: | + VERSION=${GITHUB_REF#refs/tags/v} + echo "VERSION=$VERSION" >> $GITHUB_OUTPUT + + - name: Log in to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Build and push Docker image + uses: docker/build-push-action@v6 + with: + context: . + push: true + tags: | + ghcr.io/${{ github.repository }}:${{ steps.version.outputs.VERSION }} + ghcr.io/${{ github.repository }}:latest + labels: | + org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }} + org.opencontainers.image.version=${{ steps.version.outputs.VERSION }} + org.opencontainers.image.revision=${{ github.sha }} diff --git a/docs/ONCE.md b/docs/ONCE.md index 3ab9113..9f48b84 100644 --- a/docs/ONCE.md +++ b/docs/ONCE.md @@ -2,6 +2,16 @@ This application is compatible with [37signals' Once](https://once.com/) platform. +## Installation + +When the Once installer prompts for a Docker image path, enter: + +``` +ghcr.io/markhallen/slack-github-threads:latest +``` + +Or pin to a specific version (e.g., `ghcr.io/markhallen/slack-github-threads:1.0.0`). + ## Compatibility The app meets all Once requirements: From c6b0a12a524d76e457eacb07c9c5f04c2fbc1be4 Mon Sep 17 00:00:00 2001 From: Mark Allen Date: Sat, 28 Mar 2026 20:12:41 +0000 Subject: [PATCH 3/3] docs: expand Once section in README with GHCR context Co-Authored-By: Claude Opus 4.6 (1M context) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ae50302..6ec8565 100644 --- a/README.md +++ b/README.md @@ -222,7 +222,7 @@ kamal deploy ## Once Platform -This app is compatible with [37signals' Once](https://once.com/). See [docs/ONCE.md](docs/ONCE.md) for setup details. +This app is compatible with [37signals' Once](https://once.com/) for self-hosted, single-tenant deployment. Docker images are published to GHCR on each release and can be installed directly from the Once installer. See [docs/ONCE.md](docs/ONCE.md) for setup details. ## Docker