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/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..6ec8565 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/) 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 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..9f48b84 --- /dev/null +++ b/docs/ONCE.md @@ -0,0 +1,45 @@ +# Once Platform Deployment + +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: + +| 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