diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml new file mode 100644 index 0000000..59daf37 --- /dev/null +++ b/.gitea/workflows/release.yml @@ -0,0 +1,149 @@ +name: Release + +on: + push: + tags: + - 'v*' + +env: + CARGO_TERM_COLOR: always + +jobs: + build: + strategy: + matrix: + include: + - arch: x86_64 + runs-on: ubuntu-latest + - arch: aarch64 + runs-on: aarch64 + + runs-on: ${{ matrix.runs-on }} + + steps: + - uses: actions/checkout@v4 + + - name: Install Rust stable + uses: dtolnay/rust-toolchain@stable + with: + targets: wasm32-unknown-unknown + + - name: Cache cargo registry and tools + uses: actions/cache@v3 + with: + path: | + ~/.cargo/registry + ~/.cargo/bin + key: ${{ runner.os }}-${{ matrix.arch }}-cargo-leptos-${{ hashFiles('Cargo.lock') }} + + - name: Install cargo-leptos + run: command -v cargo-leptos || cargo install cargo-leptos --locked + + - name: Build + run: cargo leptos build --release + + - name: Package + run: | + TAG=${{ gitea.ref_name }} + TARBALL="cnats-${TAG}-${{ matrix.arch }}.tar.gz" + mkdir pkg + cp target/release/cnats pkg/ + cp -r target/site pkg/site + cp packaging/cnats.service packaging/cnats.env pkg/ + cp LICENSE README.md pkg/ + tar -czf "${TARBALL}" -C pkg . + sha256sum "${TARBALL}" > "${TARBALL}.sha256" + echo "TARBALL=${TARBALL}" >> $GITHUB_ENV + + - name: Create release + run: | + curl -sX POST \ + -H "Authorization: token ${{ secrets.GITEA_TOKEN }}" \ + -H "Content-Type: application/json" \ + "${{ gitea.server_url }}/api/v1/repos/${{ gitea.repository }}/releases" \ + -d "{\"tag_name\":\"${{ gitea.ref_name }}\",\"name\":\"${{ gitea.ref_name }}\"}" \ + --fail-with-body || true + + - name: Upload assets + run: | + RELEASE_ID=$(curl -s \ + -H "Authorization: token ${{ secrets.GITEA_TOKEN }}" \ + "${{ gitea.server_url }}/api/v1/repos/${{ gitea.repository }}/releases/tags/${{ gitea.ref_name }}" \ + | jq -r '.id') + + for FILE in "${{ env.TARBALL }}" "${{ env.TARBALL }}.sha256"; do + # Remove any existing asset with the same name so re-runs stay clean + EXISTING=$(curl -s \ + -H "Authorization: token ${{ secrets.GITEA_TOKEN }}" \ + "${{ gitea.server_url }}/api/v1/repos/${{ gitea.repository }}/releases/${RELEASE_ID}/assets" \ + | jq -r ".[] | select(.name == \"${FILE}\") | .id") + for AID in $EXISTING; do + curl -sX DELETE \ + -H "Authorization: token ${{ secrets.GITEA_TOKEN }}" \ + "${{ gitea.server_url }}/api/v1/repos/${{ gitea.repository }}/releases/${RELEASE_ID}/assets/${AID}" + done + + curl -sX POST \ + -H "Authorization: token ${{ secrets.GITEA_TOKEN }}" \ + -H "Content-Type: application/octet-stream" \ + "${{ gitea.server_url }}/api/v1/repos/${{ gitea.repository }}/releases/${RELEASE_ID}/assets?name=${FILE}" \ + --data-binary "@${FILE}" --fail-with-body + done + + docker: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Log in to Docker Hub + run: echo "${{ secrets.DOCKERHUB_TOKEN }}" | docker login -u bendik --password-stdin + + - name: Build and push + run: | + TAG=${{ gitea.ref_name }} + docker build -t "bendik/cnats:${TAG#v}" -t bendik/cnats:latest . + docker push "bendik/cnats:${TAG#v}" + docker push bendik/cnats:latest + + update-aur: + needs: build + runs-on: aarch64 + + steps: + - uses: actions/checkout@v4 + + - name: Compute checksums and update PKGBUILD + run: | + TAG=${{ gitea.ref_name }} + BASE="${{ gitea.server_url }}/${{ gitea.repository }}/releases/download/${TAG}" + + SUM_X86=$(curl -sL "${BASE}/cnats-${TAG}-x86_64.tar.gz" | sha256sum | cut -d' ' -f1) + SUM_AARCH=$(curl -sL "${BASE}/cnats-${TAG}-aarch64.tar.gz" | sha256sum | cut -d' ' -f1) + + sed -i "s/^pkgver=.*/pkgver=${TAG#v}/" aur/PKGBUILD + sed -i "s/sha256sums_x86_64=('.*')/sha256sums_x86_64=('${SUM_X86}')/" aur/PKGBUILD + sed -i "s/sha256sums_aarch64=('.*')/sha256sums_aarch64=('${SUM_AARCH}')/" aur/PKGBUILD + + - name: Push to AUR + env: + AUR_SSH_KEY: ${{ secrets.AUR_SSH_KEY }} + run: | + install -dm700 ~/.ssh + echo "$AUR_SSH_KEY" > ~/.ssh/aur + chmod 600 ~/.ssh/aur + echo "Host aur.archlinux.org" >> ~/.ssh/config + echo " IdentityFile ~/.ssh/aur" >> ~/.ssh/config + echo " User aur" >> ~/.ssh/config + ssh-keyscan aur.archlinux.org >> ~/.ssh/known_hosts 2>/dev/null + + rm -rf /tmp/aur-cnats + git clone ssh://aur@aur.archlinux.org/cnats.git /tmp/aur-cnats + cp aur/PKGBUILD /tmp/aur-cnats/ + cd /tmp/aur-cnats + makepkg --printsrcinfo > .SRCINFO + git config user.name "Bendik Aagaard Lynghaug" + git config user.email "bendik.lynghaug@gmail.com" + git add PKGBUILD .SRCINFO + git commit -m "Update to ${{ gitea.ref_name }}" + git push origin master diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..add8a78 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Bendik Aagaard Lynghaug + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index feb62cd..39703d7 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,17 @@ signed-in session (sender identity comes from the session, never the client) and fans messages out to browsers over Server-Sent Events. Any other NATS client on the bus can publish/subscribe to `chat.room.*` and participate. +## Install + +- **Arch (AUR):** `yay -S cnats` — installs the binary, `cnats.service`, and + config at `/etc/cnats/env`; then `systemctl enable --now cnats`. +- **Docker:** `docker run --env-file .env -p 3000:3000 bendik/cnats` (or the + compose file below). + +Releases are cut by the Gitea workflow in `.gitea/workflows/release.yml`: a +`v*` tag builds x86_64 + aarch64 tarballs, publishes them as release assets, +pushes the image to Docker Hub, and updates the AUR package. + ## Prerequisites - Rust (stable) with the `wasm32-unknown-unknown` target diff --git a/aur/PKGBUILD b/aur/PKGBUILD new file mode 100644 index 0000000..28bde2b --- /dev/null +++ b/aur/PKGBUILD @@ -0,0 +1,34 @@ +# Maintainer: Bendik Aagaard Lynghaug +pkgname=cnats +pkgver=0.1.0 +pkgrel=1 +pkgdesc="Web chat over NATS subjects with Kanidm SSO (Leptos SSR)" +arch=('x86_64' 'aarch64') +url="https://prosjekt.klingenbergbygg.no/bl/cnats" +license=('MIT') +depends=('glibc' 'gcc-libs') +optdepends=( + 'nats-server: local message bus' + 'postgresql: local message history archive' +) +provides=('cnats') +conflicts=('cnats-git' 'cnats-bin') +backup=('etc/cnats/env') +source_x86_64=("cnats-v${pkgver}-x86_64.tar.gz::https://prosjekt.klingenbergbygg.no/bl/cnats/releases/download/v${pkgver}/cnats-v${pkgver}-x86_64.tar.gz") +source_aarch64=("cnats-v${pkgver}-aarch64.tar.gz::https://prosjekt.klingenbergbygg.no/bl/cnats/releases/download/v${pkgver}/cnats-v${pkgver}-aarch64.tar.gz") +sha256sums_x86_64=('SKIP') +sha256sums_aarch64=('SKIP') + +package() { + install -Dm755 cnats "$pkgdir/usr/bin/cnats" + + # Hydration wasm/js + css served by the binary (LEPTOS_SITE_ROOT). + install -dm755 "$pkgdir/usr/share/cnats" + cp -r site "$pkgdir/usr/share/cnats/site" + + install -Dm644 cnats.service "$pkgdir/usr/lib/systemd/system/cnats.service" + install -Dm600 cnats.env "$pkgdir/etc/cnats/env" + + install -Dm644 LICENSE "$pkgdir/usr/share/licenses/$pkgname/LICENSE" + install -Dm644 README.md "$pkgdir/usr/share/doc/$pkgname/README.md" +} diff --git a/packaging/cnats.env b/packaging/cnats.env new file mode 100644 index 0000000..1efe98c --- /dev/null +++ b/packaging/cnats.env @@ -0,0 +1,19 @@ +# cnats configuration — read by cnats.service (EnvironmentFile). +# See https://prosjekt.klingenbergbygg.no/bl/cnats for details. + +LEPTOS_SITE_ADDR=127.0.0.1:3000 +NATS_URL=nats://127.0.0.1:4222 +DATABASE_URL=postgres://cnats:cnats@127.0.0.1:5432/cnats + +KANIDM_URL=https://idm.example.com +OAUTH2_CLIENT_ID=cnats +OAUTH2_CLIENT_SECRET=change-me + +# Must match a redirect URL registered on the Kanidm oauth2 client: +# ${PUBLIC_URL}/auth/callback +PUBLIC_URL=http://localhost:3000 + +# Set true when served over HTTPS (production). +COOKIE_SECURE=false + +RUST_LOG=info diff --git a/packaging/cnats.service b/packaging/cnats.service new file mode 100644 index 0000000..8477e62 --- /dev/null +++ b/packaging/cnats.service @@ -0,0 +1,32 @@ +[Unit] +Description=cnats - web chat over NATS subjects with Kanidm SSO +Documentation=https://prosjekt.klingenbergbygg.no/bl/cnats +After=network-online.target nats-server.service postgresql.service +Wants=network-online.target + +[Service] +DynamicUser=yes +EnvironmentFile=/etc/cnats/env +Environment=LEPTOS_SITE_ROOT=/usr/share/cnats/site +ExecStart=/usr/bin/cnats +Restart=on-failure +RestartSec=5 + +# Hardening: the app only needs outbound sockets and its static site dir. +NoNewPrivileges=yes +ProtectSystem=strict +ProtectHome=yes +PrivateTmp=yes +PrivateDevices=yes +ProtectKernelTunables=yes +ProtectKernelModules=yes +ProtectControlGroups=yes +RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX +RestrictNamespaces=yes +LockPersonality=yes +MemoryDenyWriteExecute=yes +SystemCallArchitectures=native +CapabilityBoundingSet= + +[Install] +WantedBy=multi-user.target