Skip to content
Open
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
163 changes: 163 additions & 0 deletions .github/workflows/flatpak.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
name: Flatpak

on:
push:
branches: [main]
paths: ['flatpak/**', 'src/**', 'package.json', 'package-lock.json', 'electron-builder.yml', '.github/workflows/flatpak.yml']
pull_request:
paths: ['flatpak/**', 'src/**', 'package.json', 'package-lock.json', 'electron-builder.yml', '.github/workflows/flatpak.yml']
workflow_dispatch:

concurrency:
group: flatpak-${{ github.ref }}
cancel-in-progress: true

env:
MANIFEST: flatpak/com.andersonlaverde.slacky.yml
APP_ID: com.andersonlaverde.slacky

jobs:
metadata:
name: Validate metadata
runs-on: ubuntu-24.04-arm
steps:
- uses: actions/checkout@v7

- name: Install validators
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends appstream desktop-file-utils

# --no-net keeps this to structural validation. The screenshot URL is
# checked separately below, because it is a Flathub submission
# requirement rather than something that blocks a build.
- name: Validate AppStream metainfo
run: appstreamcli validate --explain --no-net flatpak/${{ env.APP_ID }}.metainfo.xml

- name: Check screenshots resolve
run: |
urls=$(grep -oP '(?<=<image>)[^<]+' flatpak/${{ env.APP_ID }}.metainfo.xml)
missing=0
for url in $urls; do
if curl -sSfLI --max-time 30 "$url" >/dev/null 2>&1; then
echo "ok $url"
else
echo "::warning file=flatpak/${{ env.APP_ID }}.metainfo.xml::Screenshot $url does not resolve. Flathub rejects submissions with unreachable screenshots."
missing=1
fi
done
if [ "$missing" = 1 ]; then
echo "Screenshots are still outstanding; see the checklist in flatpak/README.md."
fi

- name: Validate desktop entry
run: desktop-file-validate flatpak/${{ env.APP_ID }}.desktop

build:
name: Build flatpak (arm64)
runs-on: ubuntu-24.04-arm
timeout-minutes: 90
steps:
- uses: actions/checkout@v7

# The build sandbox has no network, so every npm tarball and the Electron
# binary have to be declared as sources up front. See flatpak/README.md.
- name: Generate offline npm sources
run: |
python3 -m venv .venv
.venv/bin/pip install --quiet PyYAML \
'flatpak-node-generator @ git+https://github.com/flatpak/flatpak-builder-tools.git#subdirectory=node'
.venv/bin/flatpak-node-generator npm package-lock.json -o flatpak/generated-sources.json

# The committed manifest builds a published tag, which is what Flathub
# needs but would make CI test the last release instead of this commit.
# Swap that one source for the checked-out tree.
- name: Point the manifest at the checked-out tree
run: |
.venv/bin/python - <<'PY'
import sys, os, yaml
path = os.environ['MANIFEST']
with open(path) as f:
manifest = yaml.safe_load(f)
local = {'type': 'dir', 'path': '..',
'skip': ['.git', '.venv', 'node_modules', 'dist', 'build-dir']}
swapped = 0
# Every module, not a hardcoded index: the app module is not
# necessarily first, and silently rewriting the wrong one means CI
# builds the last release instead of the commit under test.
for module in manifest['modules']:
sources = module.get('sources')
if not sources:
continue
for i, source in enumerate(sources):
if isinstance(source, dict) and source.get('type') == 'git':
sources[i] = local
swapped += 1
if swapped != 1:
sys.exit(f'expected exactly one git source to swap, found {swapped}')
with open(path, 'w') as f:
yaml.safe_dump(manifest, f, sort_keys=False)
PY
cat "$MANIFEST"

- name: Install flatpak-builder
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends flatpak flatpak-builder elfutils

# ~1.5 GB of runtimes, and dl.flathub.org times out often enough to
# matter. Exact key only: a half-restored ostree repo is worse than none,
# and the install step below repairs anything the cache got wrong.
- name: Cache flatpak runtimes
uses: actions/cache@v6
with:
path: ~/.local/share/flatpak
key: flatpak-runtimes-24.08-node24-electron2

- name: Install runtimes
run: |
# dl.flathub.org has served truncated objects and 503s for the Node
# SDK extension. Back off far enough to ride out a bad spell rather
# than hammering a CDN that is already unwell.
retry() {
attempts=4
delay=30
for attempt in $(seq $attempts); do
"$@" && return 0
if [ "$attempt" -eq "$attempts" ]; then
echo "::error::flathub failed $attempts times, giving up"
return 1
fi
echo "::warning::flathub attempt $attempt failed, retrying in ${delay}s"
sleep $delay
delay=$((delay * 2))
done
}
retry flatpak remote-add --user --if-not-exists flathub https://dl.flathub.org/repo/flathub.flatpakrepo
retry flatpak install --user -y --noninteractive flathub \
org.freedesktop.Platform//24.08 \
org.freedesktop.Sdk//24.08 \
org.freedesktop.Sdk.Extension.node24//24.08 \
org.electronjs.Electron2.BaseApp//24.08

- name: Cache build downloads
uses: actions/cache@v6
with:
path: .flatpak-builder/downloads
key: flatpak-downloads-${{ hashFiles('package-lock.json') }}
restore-keys: flatpak-downloads-

- name: Build
run: |
flatpak-builder --user --force-clean --disable-rofiles-fuse \
--repo=repo build-dir "$MANIFEST"

- name: Bundle
run: |
flatpak build-bundle repo slacky.flatpak "$APP_ID" --runtime-repo=https://dl.flathub.org/repo/flathub.flatpakrepo

- uses: actions/upload-artifact@v7
with:
name: slacky-flatpak-arm64
path: slacky.flatpak
if-no-files-found: error
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
node_modules/
dist/*
.DS_Store

# flatpak build artifacts
flatpak/generated-sources.json
flatpak-node/
flatpak-node-generator.py
.flatpak-builder/
build-dir/
3 changes: 3 additions & 0 deletions electron-builder.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ protocols:
linux:
category: Network
synopsis: Slack client for Linux arm64 systems
# Pinned so the binary inside the unpacked build has a predictable name; the
# flatpak wrapper in flatpak/slacky.sh execs /app/main/slacky.
executableName: slacky
target:
- target: AppImage
arch:
Expand Down
103 changes: 103 additions & 0 deletions flatpak/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
# Flatpak / Flathub packaging

Files here build Slacky as a flatpak and prepare the Flathub submission for
[#10](https://github.com/andirsun/Slacky/issues/10).

| File | Purpose |
| --- | --- |
| `com.andersonlaverde.slacky.yml` | flatpak-builder manifest |
| `com.andersonlaverde.slacky.metainfo.xml` | AppStream metadata (mandatory on Flathub) |
| `com.andersonlaverde.slacky.desktop` | Desktop entry |
| `slacky.sh` | Launcher, wraps the app in zypak |

## Two things that are not obvious

`electronDist` is passed explicitly because flatpak-node-generator writes the
Electron zip flat into `$XDG_CACHE_HOME/electron`, while `@electron/get` looks
for it under a `sha256`-of-the-URL subdirectory. The cached zip is therefore
invisible to electron-builder, which then tries to reach github.com and fails
in the network-less build sandbox. Unpacking it and passing `electronDist`
sidesteps the cache layout entirely.

`npm ci`, not `npm install`: install re-resolves the dependency tree and
reaches for packages outside the lockfile, which are by definition absent from
the generated offline sources (`ENOTCACHED`).

## Why this does not use electron-builder's flatpak target

electron-builder's `flatpak` target shells out to `flatpak-builder` itself. The
first attempt at this manifest called `npm run pack -- --linux flatpak` from
inside `build-commands`, which runs flatpak-builder inside a flatpak build — that
is what wedged it. The manifest now runs `electron-builder --linux dir`, which
only produces an unpacked tree, and the flatpak is assembled around that tree.

## Prerequisites

flatpak-builder does not run on macOS. Build on Linux; to produce the arm64
package you need arm64 hardware or `qemu-user-static` binfmt emulation.

```sh
flatpak install -y flathub org.freedesktop.Platform//24.08 org.freedesktop.Sdk//24.08 \
org.freedesktop.Sdk.Extension.node24//24.08 org.electronjs.Electron2.BaseApp//24.08
```

## Generating `generated-sources.json`

The build sandbox has no network, so every npm tarball and the Electron binary
have to be declared as sources up front. That file is generated, not written by
hand, and is not committed — regenerate it whenever `package-lock.json` changes:

```sh
# Needs network. Run from the repo root.
pipx install 'git+https://github.com/flatpak/flatpak-builder-tools.git#subdirectory=node'
flatpak-node-generator npm package-lock.json -o flatpak/generated-sources.json
```

The generator also emits `flatpak-node/electron-builder-arch-args.sh`, which the
manifest sources so electron-builder targets the architecture being built.

## CI

`.github/workflows/flatpak.yml` builds this manifest on `ubuntu-24.04-arm` for
every pull request that touches the app or the packaging, and uploads the
resulting `slacky.flatpak` as a run artifact you can install with
`flatpak install --user slacky.flatpak`. A separate job validates the AppStream
and desktop metadata.

CI generates `generated-sources.json` itself, and rewrites the manifest's `git`
source to a `dir` source so it builds the commit under test rather than the last
published tag. The committed manifest keeps the `git` source, which is what
Flathub requires.

## Building and running locally

```sh
flatpak-builder --user --install --force-clean build-dir \
flatpak/com.andersonlaverde.slacky.yml
flatpak run com.andersonlaverde.slacky
```

## Before submitting to Flathub

- [ ] **Add a real screenshot.** `com.andersonlaverde.slacky.metainfo.xml` points
at `build/screenshots/main-window.png`, which does not exist yet. Flathub
rejects submissions whose screenshot URLs do not resolve.
- [ ] Validate the metadata:
```sh
appstreamcli validate flatpak/com.andersonlaverde.slacky.metainfo.xml
desktop-file-validate flatpak/com.andersonlaverde.slacky.desktop
flatpak run --command=flatpak-builder-lint org.flatpak.Builder \
manifest flatpak/com.andersonlaverde.slacky.yml
```
- [ ] Confirm huddles work — mic, camera and screen share — since `--device=all`
is the permission most likely to be questioned in review.
- [ ] Bump `tag:` and `commit:` in the manifest to the release being published,
and add a matching `<release>` entry to the metainfo.
- [ ] Submit: open a pull request against
[flathub/flathub](https://github.com/flathub/flathub) on the `new-pr`
branch adding this manifest. Flathub then creates
`flathub/com.andersonlaverde.slacky`, and future releases are published by
updating the manifest in *that* repo, not this one.
- [ ] Slacky bundles no Slack trademark assets beyond the app icon; double-check
the icon before submission, since Flathub review looks at third-party
branding.
12 changes: 12 additions & 0 deletions flatpak/com.andersonlaverde.slacky.desktop
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[Desktop Entry]
Type=Application
Name=Slacky
GenericName=Slack Client
Comment=Slack client for Linux arm64 systems
Exec=slacky %U
Icon=com.andersonlaverde.slacky
Terminal=false
Categories=Network;InstantMessaging;
Keywords=slack;chat;messaging;huddle;workspace;
StartupWMClass=Slacky
MimeType=x-scheme-handler/slack;
72 changes: 72 additions & 0 deletions flatpak/com.andersonlaverde.slacky.metainfo.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<?xml version="1.0" encoding="UTF-8"?>
<component type="desktop-application">
<id>com.andersonlaverde.slacky</id>

<name>Slacky</name>
<summary>Slack client for Linux arm64 systems</summary>

<metadata_license>CC0-1.0</metadata_license>
<project_license>MIT</project_license>

<developer id="com.andersonlaverde">
<name>Anderson Laverde</name>
</developer>

<description>
<p>
Slacky is a desktop Slack client for Linux machines running on arm64
hardware, where the official Slack desktop app is not available. It wraps
the Slack web client in a native window so it behaves like a normal
desktop application.
</p>
<p>Features:</p>
<ul>
<li>Sign in to several Slack workspaces and switch between them</li>
<li>Desktop notifications that focus the window when clicked</li>
<li>Huddles, including pop-out huddle windows</li>
<li>Google single sign-on completed inside the app</li>
<li>slack:// deep links open in Slacky rather than a browser</li>
</ul>
</description>

<launchable type="desktop-id">com.andersonlaverde.slacky.desktop</launchable>

<!--
TODO before submitting to Flathub: this screenshot URL has to resolve to a
real image. Flathub's review will reject the submission otherwise. Add a
PNG at the path below on the main branch, or point these at wherever the
screenshots end up living.
-->
<screenshots>
<screenshot type="default">
<image>https://raw.githubusercontent.com/andirsun/Slacky/main/build/screenshots/main-window.png</image>
<caption>The Slack workspace running in Slacky</caption>
</screenshot>
</screenshots>

<url type="homepage">https://github.com/andirsun/Slacky</url>
<url type="bugtracker">https://github.com/andirsun/Slacky/issues</url>
<url type="vcs-browser">https://github.com/andirsun/Slacky</url>

<!--
Slacky is a client for a third-party chat service, so users can exchange
arbitrary messages through it.
-->
<content_rating type="oars-1.1">
<content_attribute id="social-chat">intense</content_attribute>
<content_attribute id="social-audio">intense</content_attribute>
</content_rating>

<releases>
<release version="1.0.0" date="2026-08-24">
<description>
<p>
First stable release: multiple workspaces, native huddle pop-out
windows, in-app Google SSO, window focus on notification click, and
arm64 AppImage, deb and rpm packages.
</p>
</description>
<url>https://github.com/andirsun/Slacky/releases/tag/v1.0.0</url>
</release>
</releases>
</component>
Loading