workspace: reconcile latest Fluxer into the open source release and continue development there

This commit is contained in:
Hampus Kraft
2026-06-16 06:41:37 +02:00
parent ee1f27fe1a
commit fa3e852575
19032 changed files with 10716201 additions and 1865276 deletions
-54
View File
@@ -1,54 +0,0 @@
# Like dev/Caddyfile.dev, but LiveKit and Mailpit are referenced by their
# Docker Compose hostnames instead of 127.0.0.1.
{
auto_https off
admin off
}
:48763 {
handle /_caddy_health {
respond "OK" 200
}
@gateway path /gateway /gateway/*
handle @gateway {
uri strip_prefix /gateway
reverse_proxy 127.0.0.1:49107
}
@marketing path /marketing /marketing/*
handle @marketing {
uri strip_prefix /marketing
reverse_proxy 127.0.0.1:49531
}
@server path /admin /admin/* /api /api/* /s3 /s3/* /queue /queue/* /media /media/* /_health /_ready /_live /.well-known/fluxer
handle @server {
reverse_proxy 127.0.0.1:49319
}
@livekit path /livekit /livekit/*
handle @livekit {
uri strip_prefix /livekit
reverse_proxy livekit:7880
}
redir /mailpit /mailpit/
handle_path /mailpit/* {
rewrite * /mailpit{path}
reverse_proxy mailpit:8025
}
handle {
reverse_proxy 127.0.0.1:49427 {
header_up Connection {http.request.header.Connection}
header_up Upgrade {http.request.header.Upgrade}
}
}
log {
output stdout
format console
}
}
}
+175 -31
View File
@@ -1,40 +1,184 @@
# Language runtimes (Node.js, Go, Rust, Python) are installed via devcontainer
# features. This Dockerfile handles Erlang/OTP (no feature available) and
# tools like Caddy, process-compose, rebar3, uv, ffmpeg, and exiftool.
FROM chrislusf/seaweedfs:4.31 AS seaweedfs
FROM erlang:28-slim AS erlang
FROM erlang:28.5.0.1
FROM mcr.microsoft.com/devcontainers/base:debian-13
ARG USERNAME=vscode
ARG USER_UID=1000
ARG USER_GID=1000
ARG NODE_MAJOR=24
ARG ELP_VERSION=2026-02-27
ARG HELM_VERSION=4.2.0
ARG DEBIAN_FRONTEND=noninteractive
ARG REBAR3_VERSION=3.24.0
ARG PROCESS_COMPOSE_VERSION=1.90.0
ENV DEBIAN_FRONTEND=noninteractive
# Both erlang:28-slim and debian-13 are Trixie-based, so OpenSSL versions match.
COPY --from=erlang /usr/local/lib/erlang /usr/local/lib/erlang
RUN ln -sf /usr/local/lib/erlang/bin/* /usr/local/bin/
RUN apt-get update && apt-get install -y --no-install-recommends \
libncurses6 libsctp1 \
build-essential pkg-config \
ffmpeg libimage-exiftool-perl \
sqlite3 libsqlite3-dev \
libssl-dev openssl \
gettext-base lsof iproute2 \
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
bash \
build-essential \
ca-certificates \
clang \
cmake \
curl \
dbus-x11 \
desktop-file-utils \
dpkg-dev \
ffmpeg \
fakeroot \
git \
gnupg \
imagemagick \
jq \
libasound2 \
libatk-bridge2.0-0 \
libavcodec-dev \
libavfilter-dev \
libavformat-dev \
libavutil-dev \
libdbus-1-dev \
libdrm2 \
libfido2-dev \
libgbm1 \
libgtk-3-0 \
libnotify4 \
libnss3 \
libpipewire-0.3-dev \
libpulse-dev \
libsecret-1-0 \
libudev-dev \
libuv1-dev \
libcurl4-openssl-dev \
libswresample-dev \
libswscale-dev \
libvips-dev \
libwayland-dev \
libwebp-dev \
libssl-dev \
libx11-xcb1 \
libxcb-dri3-0 \
libxcomposite1 \
libxdamage1 \
libxkbcommon-dev \
libxkbcommon0 \
libxrandr2 \
libxshmfence1 \
libxss1 \
libxtst6 \
llvm \
meson \
ninja-build \
openssh-client \
pkg-config \
protobuf-compiler \
python3 \
python3-pip \
rsync \
sudo \
rpm \
unzip \
webp \
xauth \
xvfb \
xz-utils \
xdg-utils \
zstd \
&& rm -rf /var/lib/apt/lists/*
RUN curl -fsSL "https://github.com/erlang/rebar3/releases/download/${REBAR3_VERSION}/rebar3" \
-o /usr/local/bin/rebar3 \
&& chmod +x /usr/local/bin/rebar3
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
apache2-utils \
bat \
btop \
docker-cli \
dnsutils \
fd-find \
gdb \
htop \
hyperfine \
iproute2 \
iputils-ping \
kind \
kubernetes-client \
lldb \
lsof \
ltrace \
net-tools \
netcat-openbsd \
nmap \
postgresql-client \
procps \
redis-tools \
ripgrep \
shellcheck \
shfmt \
socat \
sqlite3 \
strace \
tcpdump \
tmux \
tree \
valgrind \
wrk \
yq \
&& ln -sf /usr/bin/fdfind /usr/local/bin/fd \
&& ln -sf /usr/bin/batcat /usr/local/bin/bat \
&& rm -rf /var/lib/apt/lists/*
RUN curl -fsSL "https://caddyserver.com/api/download?os=linux&arch=amd64" \
-o /usr/local/bin/caddy \
&& chmod +x /usr/local/bin/caddy
RUN curl -fsSL https://deb.nodesource.com/setup_${NODE_MAJOR}.x | bash - \
&& apt-get install -y --no-install-recommends nodejs \
&& rm -rf /var/lib/apt/lists/* \
&& corepack enable \
&& corepack prepare pnpm@10.29.3 --activate
RUN curl -fsSL "https://github.com/F1bonacc1/process-compose/releases/download/v${PROCESS_COMPOSE_VERSION}/process-compose_linux_amd64.tar.gz" \
| tar xz -C /usr/local/bin process-compose \
&& chmod +x /usr/local/bin/process-compose
RUN ARCH="$(dpkg --print-architecture)" \
&& case "$ARCH" in \
amd64) HELM_ARCH="amd64" ;; \
arm64) HELM_ARCH="arm64" ;; \
*) echo "Unsupported architecture for Helm: $ARCH" >&2; exit 1 ;; \
esac \
&& curl -fsSL "https://get.helm.sh/helm-v${HELM_VERSION}-linux-${HELM_ARCH}.tar.gz" -o /tmp/helm.tgz \
&& tar -C /tmp -xzf /tmp/helm.tgz "linux-${HELM_ARCH}/helm" \
&& mv "/tmp/linux-${HELM_ARCH}/helm" /usr/local/bin/helm \
&& chmod +x /usr/local/bin/helm \
&& rm -rf /tmp/helm.tgz "/tmp/linux-${HELM_ARCH}"
RUN curl -fsSL "https://github.com/astral-sh/uv/releases/latest/download/uv-x86_64-unknown-linux-gnu.tar.gz" \
| tar xz --strip-components=1 -C /usr/local/bin \
&& chmod +x /usr/local/bin/uv /usr/local/bin/uvx
RUN python3 -m pip install --break-system-packages --no-cache-dir awscli cqlsh
RUN if ! command -v rebar3 >/dev/null 2>&1; then \
curl -fsSL https://s3.amazonaws.com/rebar3/rebar3 -o /usr/local/bin/rebar3 \
&& chmod +x /usr/local/bin/rebar3; \
fi
RUN ARCH="$(dpkg --print-architecture)" \
&& case "$ARCH" in \
amd64) ELP_ARCH="x86_64" ;; \
arm64) ELP_ARCH="aarch64" ;; \
*) echo "Unsupported architecture for ELP: $ARCH" >&2; exit 1 ;; \
esac \
&& curl -fsSL "https://github.com/WhatsApp/erlang-language-platform/releases/download/${ELP_VERSION}/elp-linux-${ELP_ARCH}-unknown-linux-gnu-otp-28.tar.gz" -o /tmp/elp.tgz \
&& tar -C /usr/local/bin -xzf /tmp/elp.tgz elp \
&& chmod +x /usr/local/bin/elp \
&& rm /tmp/elp.tgz
RUN if ! getent group "${USER_GID}" >/dev/null; then groupadd --gid "${USER_GID}" "${USERNAME}"; fi \
&& if ! id -u "${USERNAME}" >/dev/null 2>&1; then useradd --uid "${USER_UID}" --gid "${USER_GID}" -m "${USERNAME}" --shell /bin/bash; fi \
&& echo "${USERNAME} ALL=(root) NOPASSWD:ALL" > "/etc/sudoers.d/${USERNAME}" \
&& chmod 0440 "/etc/sudoers.d/${USERNAME}"
COPY --from=seaweedfs /usr/bin/weed /usr/local/bin/weed
USER ${USERNAME}
ENV DOCKER_HOST="unix:///var/run/docker.sock" \
KUBECONFIG="/workspaces/fluxer/.fluxer/k8s/local-kubeconfig" \
PATH="/home/${USERNAME}/.cargo/bin:${PATH}" \
PNPM_HOME="/home/${USERNAME}/.local/share/pnpm"
RUN curl -fsSL https://sh.rustup.rs | sh -s -- -y --profile default --component clippy,rustfmt \
&& rustup target add wasm32-unknown-unknown \
&& cargo install cargo-watch --locked
RUN sudo apt-get update \
&& sudo apt-get install -y --no-install-recommends python3-venv \
&& sudo rm -rf /var/lib/apt/lists/*
WORKDIR /workspaces/fluxer
+52 -54
View File
@@ -1,75 +1,73 @@
{
"name": "Fluxer",
"dockerComposeFile": "docker-compose.yml",
"service": "app",
"workspaceFolder": "/workspace",
"features": {
"ghcr.io/devcontainers/features/node:1": {
"version": "24",
"pnpmVersion": "10.29.3"
},
"ghcr.io/devcontainers/features/go:1": {
"version": "1.24"
},
"ghcr.io/devcontainers/features/rust:1": {
"version": "1.93.0",
"targets": "wasm32-unknown-unknown"
},
"ghcr.io/devcontainers/features/python:1": {
"version": "os-provided",
"installTools": false
}
},
"onCreateCommand": ".devcontainer/on-create.sh",
"service": "workspace",
"workspaceFolder": "/workspaces/fluxer",
"shutdownAction": "stopCompose",
"remoteUser": "vscode",
"remoteEnv": {
"FLUXER_CONFIG": "${containerWorkspaceFolder}/config/config.json",
"FLUXER_DATABASE": "sqlite"
"DOCKER_HOST": "unix:///var/run/docker.sock",
"KUBECONFIG": "/workspaces/fluxer/.fluxer/k8s/local-kubeconfig"
},
"forwardPorts": [48763, 6379, 7700, 7880],
"runServices": ["workspace", "cassandra", "valkey", "nats", "livekit", "elasticsearch"],
"forwardPorts": [
3000, 8088, 8080, 8771, 8082, 3010, 3020, 8100, 8101, 8102, 8103, 8104, 8105, 8106, 8107, 8108, 8109, 8110, 8111,
8112, 8113, 8114, 8115, 8116, 8117, 8118, 8119, 8120, 8121, 8122, 8123, 8124, 8125, 3900, 8888, 9333, 9340, 23646,
4222, 7880, 9200, 8000
],
"portsAttributes": {
"48763": {
"label": "Fluxer (Caddy)",
"onAutoForward": "openBrowser",
"protocol": "http"
"8000": {
"label": "Zensical docs",
"onAutoForward": "openBrowserOnce"
},
"6379": {
"label": "Valkey",
"onAutoForward": "silent"
"8088": {
"label": "Fluxer dev proxy",
"onAutoForward": "notify"
},
"7700": {
"label": "Meilisearch",
"onAutoForward": "silent"
"3000": {
"label": "Rspack dev server"
},
"3020": {
"label": "Fluxer admin"
},
"8100": {
"label": "Fluxer Rust service health"
},
"3900": {
"label": "SeaweedFS S3"
},
"8888": {
"label": "SeaweedFS filer"
},
"9333": {
"label": "SeaweedFS master"
},
"9340": {
"label": "SeaweedFS volume"
},
"23646": {
"label": "SeaweedFS admin"
},
"7880": {
"label": "LiveKit",
"onAutoForward": "silent"
"label": "LiveKit"
},
"9229": {
"label": "Node.js Debugger",
"onAutoForward": "silent"
"9200": {
"label": "Elasticsearch"
}
},
"postCreateCommand": "find /workspaces/fluxer -maxdepth 4 -type d -name node_modules -prune -exec sudo chown -R vscode:vscode {} + && sudo chown -R vscode:vscode /home/vscode/.local/share/pnpm && cargo run -p fluxer-dev -- bootstrap && bash /workspaces/fluxer/fluxer_docs/serve.sh --bootstrap",
"postStartCommand": "cargo run -p fluxer-dev -- post-start && bash /workspaces/fluxer/fluxer_docs/serve.sh --daemon",
"customizations": {
"vscode": {
"extensions": [
"TypeScriptTeam.native-preview",
"biomejs.biome",
"clinyong.vscode-css-modules",
"rust-lang.rust-analyzer",
"tamasfe.even-better-toml",
"TypeScriptTeam.native-preview",
"unifiedjs.vscode-mdx",
"pgourlain.erlang",
"golang.go",
"rust-lang.rust-analyzer"
],
"settings": {
"typescript.preferences.includePackageJsonAutoImports": "auto",
"typescript.suggest.autoImports": true,
"typescript.experimental.useTsgo": true
}
"clinyong.vscode-css-modules"
]
}
}
}
+377 -47
View File
@@ -1,64 +1,394 @@
name: fluxer-dev
services:
app:
workspace:
build:
context: .
dockerfile: Dockerfile
volumes:
- ..:/workspace:cached
context: ..
dockerfile: .devcontainer/Dockerfile
command: sleep infinity
init: true
env_file:
- ../config/env/development.env
environment:
FLUXER_SEARCH_URL: http://elasticsearch:9200
FLUXER_SEARCH_USERNAME: elastic
FLUXER_SEARCH_PASSWORD: fluxer-dev-elasticsearch
FLUXER_POSTGRES_HOST: postgres
FLUXER_SELF_HOSTED: "true"
DOCKER_HOST: unix:///var/run/docker.sock
KUBECONFIG: /workspaces/fluxer/.fluxer/k8s/local-kubeconfig
volumes:
- ..:/workspaces/fluxer:cached
- type: volume
source: root-node-modules
target: /workspaces/fluxer/node_modules
volume:
nocopy: true
- type: volume
source: fluxer-api-node-modules
target: /workspaces/fluxer/fluxer_api/node_modules
volume:
nocopy: true
- type: volume
source: fluxer-app-node-modules
target: /workspaces/fluxer/fluxer_app/node_modules
volume:
nocopy: true
- type: volume
source: fluxer-desktop-node-modules
target: /workspaces/fluxer/fluxer_desktop/node_modules
volume:
nocopy: true
- type: volume
source: fluxer-marketing-node-modules
target: /workspaces/fluxer/fluxer_marketing/node_modules
volume:
nocopy: true
- type: volume
source: fluxer-admin-node-modules
target: /workspaces/fluxer/fluxer_admin/node_modules
volume:
nocopy: true
- type: volume
source: package-config-node-modules
target: /workspaces/fluxer/packages/config/node_modules
volume:
nocopy: true
- type: volume
source: package-constants-node-modules
target: /workspaces/fluxer/packages/constants/node_modules
volume:
nocopy: true
- type: volume
source: package-date-utils-node-modules
target: /workspaces/fluxer/packages/date_utils/node_modules
volume:
nocopy: true
- type: volume
source: package-errors-node-modules
target: /workspaces/fluxer/packages/errors/node_modules
volume:
nocopy: true
- type: volume
source: package-geo-utils-node-modules
target: /workspaces/fluxer/packages/geo_utils/node_modules
volume:
nocopy: true
- type: volume
source: package-hono-node-modules
target: /workspaces/fluxer/packages/hono/node_modules
volume:
nocopy: true
- type: volume
source: package-hono-types-node-modules
target: /workspaces/fluxer/packages/hono_types/node_modules
volume:
nocopy: true
- type: volume
source: package-i18n-node-modules
target: /workspaces/fluxer/packages/i18n/node_modules
volume:
nocopy: true
- type: volume
source: package-instance-bootstrap-node-modules
target: /workspaces/fluxer/packages/instance_bootstrap/node_modules
volume:
nocopy: true
- type: volume
source: package-ip-utils-node-modules
target: /workspaces/fluxer/packages/ip_utils/node_modules
volume:
nocopy: true
- type: volume
source: package-limits-node-modules
target: /workspaces/fluxer/packages/limits/node_modules
volume:
nocopy: true
- type: volume
source: package-logger-node-modules
target: /workspaces/fluxer/packages/logger/node_modules
volume:
nocopy: true
- type: volume
source: package-openapi-node-modules
target: /workspaces/fluxer/packages/openapi/node_modules
volume:
nocopy: true
- type: volume
source: package-schema-node-modules
target: /workspaces/fluxer/packages/schema/node_modules
volume:
nocopy: true
- type: volume
source: package-snowflake-node-modules
target: /workspaces/fluxer/packages/snowflake/node_modules
volume:
nocopy: true
- type: volume
source: fluxer-api-cache-node-modules
target: /workspaces/fluxer/fluxer_api/pkgs/cache/node_modules
volume:
nocopy: true
- type: volume
source: fluxer-api-captcha-node-modules
target: /workspaces/fluxer/fluxer_api/pkgs/captcha/node_modules
volume:
nocopy: true
- type: volume
source: fluxer-api-cassandra-node-modules
target: /workspaces/fluxer/fluxer_api/pkgs/cassandra/node_modules
volume:
nocopy: true
- type: volume
source: fluxer-api-elasticsearch-search-node-modules
target: /workspaces/fluxer/fluxer_api/pkgs/elasticsearch_search/node_modules
volume:
nocopy: true
- type: volume
source: fluxer-api-email-node-modules
target: /workspaces/fluxer/fluxer_api/pkgs/email/node_modules
volume:
nocopy: true
- type: volume
source: fluxer-api-geoip-node-modules
target: /workspaces/fluxer/fluxer_api/pkgs/geoip/node_modules
volume:
nocopy: true
- type: volume
source: fluxer-api-http-client-node-modules
target: /workspaces/fluxer/fluxer_api/pkgs/http_client/node_modules
volume:
nocopy: true
- type: volume
source: fluxer-api-initialization-node-modules
target: /workspaces/fluxer/fluxer_api/pkgs/initialization/node_modules
volume:
nocopy: true
- type: volume
source: fluxer-api-kv-client-node-modules
target: /workspaces/fluxer/fluxer_api/pkgs/kv_client/node_modules
volume:
nocopy: true
- type: volume
source: fluxer-api-locale-node-modules
target: /workspaces/fluxer/fluxer_api/pkgs/locale/node_modules
volume:
nocopy: true
- type: volume
source: fluxer-api-media-proxy-utils-node-modules
target: /workspaces/fluxer/fluxer_api/pkgs/media_proxy_utils/node_modules
volume:
nocopy: true
- type: volume
source: fluxer-api-mime-utils-node-modules
target: /workspaces/fluxer/fluxer_api/pkgs/mime_utils/node_modules
volume:
nocopy: true
- type: volume
source: fluxer-api-nats-node-modules
target: /workspaces/fluxer/fluxer_api/pkgs/nats/node_modules
volume:
nocopy: true
- type: volume
source: fluxer-api-rate-limit-node-modules
target: /workspaces/fluxer/fluxer_api/pkgs/rate_limit/node_modules
volume:
nocopy: true
- type: volume
source: fluxer-api-sms-node-modules
target: /workspaces/fluxer/fluxer_api/pkgs/sms/node_modules
volume:
nocopy: true
- type: volume
source: fluxer-api-virus-scan-node-modules
target: /workspaces/fluxer/fluxer_api/pkgs/virus_scan/node_modules
volume:
nocopy: true
- type: volume
source: fluxer-api-worker-node-modules
target: /workspaces/fluxer/fluxer_api/pkgs/worker/node_modules
volume:
nocopy: true
- type: volume
source: fluxer-app-list-utils-node-modules
target: /workspaces/fluxer/fluxer_app/pkgs/list_utils/node_modules
volume:
nocopy: true
- type: volume
source: fluxer-app-livekit-client-node-modules
target: /workspaces/fluxer/fluxer_app/pkgs/livekit-client/node_modules
volume:
nocopy: true
- type: volume
source: fluxer-app-number-utils-node-modules
target: /workspaces/fluxer/fluxer_app/pkgs/number_utils/node_modules
volume:
nocopy: true
- pnpm-store:/home/vscode/.local/share/pnpm/store
- cargo-registry:/home/vscode/.cargo/registry
- cargo-git:/home/vscode/.cargo/git
- rust-target:/workspaces/fluxer/target
- type: bind
source: ${FLUXER_DOCKER_SOCKET:-/var/run/docker.sock}
target: /var/run/docker.sock
ports:
- "8000:8000"
- "3000:3000"
- "8088:8088"
- "8080:8080"
- "8771:8771"
- "8082:8082"
- "3010:3010"
- "3020:3020"
- "8100-8125:8100-8125"
- "3900:8333"
- "8888:8888"
- "9333:9333"
- "9340:9340"
- "23646:23646"
depends_on:
- cassandra
- postgres
- valkey
- nats
- livekit
- elasticsearch
- mailpit
extra_hosts:
- "host.docker.internal:host-gateway"
cassandra:
image: cassandra:5.0.8
environment:
CASSANDRA_CLUSTER_NAME: fluxer-dev
CASSANDRA_DC: datacenter1
CASSANDRA_ENDPOINT_SNITCH: GossipingPropertyFileSnitch
HEAP_NEWSIZE: 128M
MAX_HEAP_SIZE: 768M
volumes:
- cassandra-data:/var/lib/cassandra
ports:
- "9042:9042"
postgres:
image: postgres:16-alpine
environment:
POSTGRES_DB: fluxer
POSTGRES_USER: fluxer
POSTGRES_PASSWORD: fluxer
volumes:
- postgres-data:/var/lib/postgresql/data
ports:
- "5432:5432"
valkey:
image: valkey/valkey:8-alpine
restart: unless-stopped
command: ['valkey-server', '--appendonly', 'yes', '--save', '60', '1', '--loglevel', 'warning']
volumes:
- valkey-data:/data
healthcheck:
test: ['CMD', 'valkey-cli', 'ping']
interval: 10s
timeout: 5s
retries: 5
image: valkey/valkey:8.1.7-alpine
command: ["valkey-server", "--save", "", "--appendonly", "no"]
ports:
- "6379:6379"
meilisearch:
image: getmeili/meilisearch:v1.14
restart: unless-stopped
environment:
MEILI_NO_ANALYTICS: 'true'
MEILI_ENV: development
MEILI_MASTER_KEY: fluxer-devcontainer-meili-master-key
nats:
image: nats:2.14.2-alpine
command: ["-js", "-sd", "/data", "-m", "8222"]
volumes:
- meilisearch-data:/meili_data
healthcheck:
test: ['CMD', 'curl', '-f', 'http://localhost:7700/health']
interval: 10s
timeout: 5s
retries: 5
- nats-data:/data
ports:
- "4222:4222"
- "8222:8222"
livekit:
image: livekit/livekit-server:v1.9
restart: unless-stopped
command: --config /etc/livekit.yaml
image: livekit/livekit-server:v1.12.0
command: ["--config", "/etc/livekit.yaml", "--bind", "0.0.0.0"]
volumes:
- ./livekit.yaml:/etc/livekit.yaml:ro
ports:
- "7880:7880"
- "7881:7881"
- "7882-7892:7882-7892/udp"
elasticsearch:
image: docker.elastic.co/elasticsearch/elasticsearch:9.3.2
environment:
discovery.type: single-node
xpack.security.enabled: "true"
xpack.security.http.ssl.enabled: "false"
ELASTIC_PASSWORD: fluxer-dev-elasticsearch
ES_JAVA_OPTS: "-Xms512m -Xmx512m"
volumes:
- elasticsearch-data:/usr/share/elasticsearch/data
ports:
- "9200:9200"
meilisearch:
image: getmeili/meilisearch:v1.12
environment:
MEILI_NO_ANALYTICS: "true"
MEILI_MASTER_KEY: fluxer-dev-meilisearch
volumes:
- meilisearch-data:/meili_data
ports:
- "7700:7700"
mailpit:
image: axllent/mailpit:latest
restart: unless-stopped
command: ['--webroot', '/mailpit/']
nats-core:
image: nats:2-alpine
restart: unless-stopped
command: ['--port', '4222']
nats-jetstream:
image: nats:2-alpine
restart: unless-stopped
command: ['--port', '4223', '--jetstream', '--store_dir', '/data']
image: axllent/mailpit:v1.30
environment:
MP_DATABASE: /data/mailpit.db
MP_MAX_MESSAGES: 5000
MP_SMTP_AUTH_ACCEPT_ANY: "1"
MP_SMTP_AUTH_ALLOW_INSECURE: "1"
MP_WEBROOT: devmail
volumes:
- nats-jetstream-data:/data
- mailpit-data:/data
volumes:
valkey-data:
pnpm-store:
root-node-modules:
fluxer-api-node-modules:
fluxer-app-node-modules:
fluxer-desktop-node-modules:
fluxer-marketing-node-modules:
fluxer-admin-node-modules:
package-config-node-modules:
package-constants-node-modules:
package-date-utils-node-modules:
package-errors-node-modules:
package-geo-utils-node-modules:
package-hono-node-modules:
package-hono-types-node-modules:
package-i18n-node-modules:
package-instance-bootstrap-node-modules:
package-ip-utils-node-modules:
package-limits-node-modules:
package-logger-node-modules:
package-openapi-node-modules:
package-schema-node-modules:
package-snowflake-node-modules:
fluxer-api-cache-node-modules:
fluxer-api-captcha-node-modules:
fluxer-api-cassandra-node-modules:
fluxer-api-elasticsearch-search-node-modules:
fluxer-api-email-node-modules:
fluxer-api-geoip-node-modules:
fluxer-api-http-client-node-modules:
fluxer-api-initialization-node-modules:
fluxer-api-kv-client-node-modules:
fluxer-api-locale-node-modules:
fluxer-api-media-proxy-utils-node-modules:
fluxer-api-mime-utils-node-modules:
fluxer-api-nats-node-modules:
fluxer-api-rate-limit-node-modules:
fluxer-api-sms-node-modules:
fluxer-api-virus-scan-node-modules:
fluxer-api-worker-node-modules:
fluxer-app-list-utils-node-modules:
fluxer-app-livekit-client-node-modules:
fluxer-app-number-utils-node-modules:
cargo-registry:
cargo-git:
rust-target:
cassandra-data:
nats-data:
elasticsearch-data:
meilisearch-data:
nats-jetstream-data:
mailpit-data:
postgres-data:
+6 -23
View File
@@ -1,30 +1,13 @@
# Credentials here must match the values on-create.sh writes to config.json.
port: 7880
keys:
fluxer-devcontainer-key: fluxer-devcontainer-secret-key-00000000
devkey: secret
rtc:
tcp_port: 7881
port_range_start: 50000
port_range_end: 50100
use_external_ip: false
udp_port: 7882-7892
node_ip: 127.0.0.1
turn:
enabled: true
domain: localhost
udp_port: 3478
webhook:
api_key: fluxer-devcontainer-key
urls:
- http://app:49319/api/webhooks/livekit
room:
auto_create: true
max_participants: 100
empty_timeout: 300
development: true
use_mdns: true
stun_servers:
- stun.l.google.com:19302
- stun1.l.google.com:19302
-70
View File
@@ -1,70 +0,0 @@
#!/usr/bin/env bash
# Runs once when the container is first created.
set -euo pipefail
REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
export FLUXER_CONFIG="${FLUXER_CONFIG:-$REPO_ROOT/config/config.json}"
GREEN='\033[0;32m'
NC='\033[0m'
info() { printf "%b\n" "${GREEN}[devcontainer]${NC} $1"; }
info "Installing pnpm dependencies..."
pnpm install
# Codegen outputs (e.g. MasterZodSchema.generated.tsx) are gitignored.
info "Generating config schema..."
pnpm --filter @fluxer/config generate
if [ ! -f "$FLUXER_CONFIG" ]; then
info "Creating config from development template..."
cp "$REPO_ROOT/config/config.dev.template.json" "$FLUXER_CONFIG"
fi
# Point services at Docker Compose hostnames and adjust settings that differ
# from the default dev template.
info "Patching config for Docker Compose networking..."
jq '
# rspack defaults public_scheme to "https" when unset
.domain.public_scheme = "http" |
# Relative path so the app works on any hostname (localhost, 127.0.0.1, etc.)
.app_public.bootstrap_api_endpoint = "/api" |
.internal.kv = "redis://valkey:6379/0" |
.integrations.search.url = "http://meilisearch:7700" |
.integrations.search.api_key = "fluxer-devcontainer-meili-master-key" |
# Credentials must match .devcontainer/livekit.yaml
.integrations.voice.url = "ws://livekit:7880" |
.integrations.voice.webhook_url = "http://app:49319/api/webhooks/livekit" |
.integrations.voice.api_key = "fluxer-devcontainer-key" |
.integrations.voice.api_secret = "fluxer-devcontainer-secret-key-00000000" |
.integrations.email.smtp.host = "mailpit" |
.integrations.email.smtp.port = 1025 |
.services.nats.core_url = "nats://nats-core:4222" |
.services.nats.jetstream_url = "nats://nats-jetstream:4223" |
# Bluesky OAuth requires HTTPS + loopback IPs (RFC 8252), incompatible with
# the HTTP-only devcontainer setup.
.auth.bluesky.enabled = false
' "$FLUXER_CONFIG" > "$FLUXER_CONFIG.tmp" && mv "$FLUXER_CONFIG.tmp" "$FLUXER_CONFIG"
info "Running bootstrap..."
"$REPO_ROOT/scripts/dev_bootstrap.sh"
info "Pre-compiling Erlang gateway dependencies..."
(cd "$REPO_ROOT/fluxer_gateway" && rebar3 compile) || {
info "Gateway pre-compilation failed (non-fatal, will compile on first start)"
}
info "Devcontainer setup complete."
info ""
info " Start all dev processes: process-compose -f .devcontainer/process-compose.yml up"
info " Open the app: http://127.0.0.1:48763"
info " Dev email inbox: http://127.0.0.1:48763/mailpit/"
info ""
-57
View File
@@ -1,57 +0,0 @@
# Application processes only — backing services (Valkey, Meilisearch, LiveKit,
# Mailpit, NATS) run via Docker Compose.
# process-compose -f .devcontainer/process-compose.yml up
is_tui_disabled: false
log_level: info
log_configuration:
flush_each_line: true
processes:
caddy:
command: caddy run --config .devcontainer/Caddyfile.dev --adapter caddyfile
log_location: dev/logs/caddy.log
readiness_probe:
http_get:
host: 127.0.0.1
port: 48763
path: /_caddy_health
availability:
restart: always
fluxer_server:
command: pnpm --filter fluxer_server dev
log_location: dev/logs/fluxer_server.log
availability:
restart: always
fluxer_app:
command: ./scripts/dev_fluxer_app.sh
environment:
- FORCE_COLOR=1
- FLUXER_APP_DEV_PORT=49427
log_location: dev/logs/fluxer_app.log
availability:
restart: always
fluxer_gateway:
command: ./scripts/dev_gateway.sh
environment:
- FLUXER_GATEWAY_NO_SHELL=1
log_location: dev/logs/fluxer_gateway.log
availability:
restart: always
marketing_dev:
command: pnpm --filter fluxer_marketing dev
environment:
- FORCE_COLOR=1
log_location: dev/logs/marketing_dev.log
availability:
restart: always
css_watch:
command: ./scripts/dev_css_watch.sh
log_location: dev/logs/css_watch.log
availability:
restart: always
+60 -4
View File
@@ -1,45 +1,101 @@
**/*.dump
**/*.lock
!**/Cargo.lock
!fluxer_gateway/rebar.lock
**/*.log
**/*.swo
**/*.swp
**/*.tmp
**/*~
/.claude
/.claude/**
/.fluxer
/.fluxer/**
/.git
/.git/**
/.pnpm-store
/.pnpm-store/**
/.tmp
/.tmp/**
/_build
/_build/**
/node_modules
/node_modules/**
/target
/target/**
/tmp
/tmp/**
/fluxer_admin/node_modules
/fluxer_admin/node_modules/**
/fluxer_api/node_modules
/fluxer_api/node_modules/**
/fluxer_app/dist
/fluxer_app/dist/**
/fluxer_app/node_modules
/fluxer_app/node_modules/**
/fluxer_desktop
/fluxer_desktop/**
/fluxer_gateway/_build
/fluxer_gateway/_build/**
/fluxer_marketing/node_modules
/fluxer_marketing/node_modules/**
/fluxer_marketing/target
/fluxer_marketing/target/**
**/.cache
**/.claude
**/.dev.vars
**/.DS_Store
**/.env
**/.env.*.local
**/.env.local
**/.fluxer
.fluxer
.claude
.tmp
**/.git
.git
**/.idea
**/.pnpm-store
**/.rebar
**/.rebar3
**/.turbo
**/.vscode
**/.benchmark-cache
**/.zig-cache
**/_build
_build
**/_checkouts
**/_vendor
**/bench-results
**/build
!fluxer_app/scripts/build
!fluxer_app/scripts/build/**
**/certificates
**/coverage
**/dist
**/Dockerfile*
**/erl_crash.dump
**/generated
**/log
**/logs
**/node_modules
node_modules
**/npm-debug.log*
**/pnpm-debug.log*
**/rebar3.crashdump
**/target
**/target-*
target
target-*
tmp
**/Thumbs.db
**/yarn-debug.log*
**/yarn-error.log*
/fluxer_app/src/data/emojis.json
**/zig-out
/fluxer_app/src/locales/*/messages.js
/fluxer_app/src/locales/*/messages.mjs
dev
!fluxer_app/dist
!fluxer_app/dist/**
.github
.next
*.md
fluxer_desktop
!fluxer_devops/cassandra/migrations
+4 -3
View File
@@ -1,4 +1,5 @@
#!/usr/bin/env bash
# SPDX-License-Identifier: AGPL-3.0-or-later
eval "$(devenv direnvrc)"
use devenv
dotenv config/env/development.env
dotenv_if_exists config/env/local.env
dotenv_if_exists .env.local
+1
View File
@@ -1 +1,2 @@
* text=auto
fluxer_static/** -text -diff
+7 -12
View File
@@ -2,32 +2,27 @@ body:
- type: markdown
attributes:
value: |
Thanks for the suggestion.
For larger changes, please align with maintainers before investing time.
Security issues should go to https://fluxer.app/security.
Search existing discussions before posting.
Report security issues at https://fluxer.app/security.
- type: textarea
id: problem
attributes:
label: Problem
description: What problem are you trying to solve, and for whom?
placeholder: "Right now, users can't ..., which causes ..."
validations:
required: true
- type: textarea
id: proposal
attributes:
label: Proposed solution
description: What would you like to see happen?
placeholder: "Add ..., so that ..."
label: Proposal
description: What should change?
validations:
required: true
- type: textarea
id: notes
attributes:
label: Notes (optional)
description: Constraints, rough plan, or links to relevant code.
placeholder: "Notes: ...\nPotential files/areas: ..."
label: Notes
description: Add constraints, tradeoffs, screenshots, or links.
validations:
required: false
- type: checkboxes
@@ -35,5 +30,5 @@ body:
attributes:
label: Checks
options:
- label: I searched for existing discussions and didn't find a duplicate.
- label: I searched existing discussions.
required: true
+20 -20
View File
@@ -1,27 +1,26 @@
name: Bug report
description: Report a reproducible problem in Fluxer
labels: ['bug']
description: Report a reproducible problem in Fluxer.
labels: ["bug"]
body:
- type: markdown
attributes:
value: |
Thanks for the report.
Please check our status page at https://fluxerstatus.com and search for existing issues before filing.
Security issues should go to https://fluxer.app/security.
Search existing issues before filing.
Report security issues at https://fluxer.app/security.
Keep AI-generated text out of bug reports, except for direct translation if English is not your native language.
- type: textarea
id: summary
attributes:
label: Summary
description: What happened, and what did you expect instead?
placeholder: "When I ..., the app ..., but I expected ..."
placeholder: When I ..., the app ..., but I expected ...
validations:
required: true
- type: textarea
id: repro
id: steps
attributes:
label: Steps to reproduce
description: Provide clear, numbered steps.
description: Use numbered steps.
placeholder: |
1. Go to ...
2. Click ...
@@ -31,21 +30,20 @@ body:
- type: textarea
id: environment
attributes:
label: Environment (optional)
description: Include versions that matter (commit/tag, OS, runtime, browser/device).
label: Environment
description: Add versions, OS, browser, device, or commit when relevant.
placeholder: |
- Commit/Tag:
- OS:
- Runtime:
- Browser (if applicable):
Version:
OS:
Browser:
Device:
validations:
required: false
- type: textarea
id: logs
id: evidence
attributes:
label: Logs or screenshots (optional)
description: Paste logs (redact secrets) or attach screenshots/recordings.
placeholder: "Paste stack traces, console output, network errors, etc."
label: Logs or screenshots
description: Add logs, screenshots, recordings, or links. Redact secrets.
validations:
required: false
- type: checkboxes
@@ -53,5 +51,7 @@ body:
attributes:
label: Checks
options:
- label: I searched for existing issues and didn't find a duplicate.
- label: I searched existing issues.
required: true
- label: I wrote this report in my own words, except for direct translation if needed.
required: true
+3 -3
View File
@@ -1,8 +1,8 @@
blank_issues_enabled: false
contact_links:
- name: Feature requests
- name: Ideas and feature requests
url: https://github.com/orgs/fluxerapp/discussions
about: Suggest an improvement or new capability.
- name: Security vulnerability report
- name: Security reports
url: https://fluxer.app/security
about: Please report security issues privately using our security policy.
about: Report vulnerabilities privately.
+15 -16
View File
@@ -1,36 +1,33 @@
name: Documentation
description: Report a docs issue or suggest an improvement
labels: ['docs']
description: Report a docs issue or suggest a docs improvement.
labels: ["docs"]
body:
- type: markdown
attributes:
value: |
Thanks.
Please check our status page at https://fluxerstatus.com and search for existing issues before filing.
Security issues should go to https://fluxer.app/security.
Search existing issues before filing.
Report security issues at https://fluxer.app/security.
Keep AI-generated text out of bug reports, except for direct translation if English is not your native language.
- type: textarea
id: issue
attributes:
label: What needs fixing?
description: Describe the gap, error, or outdated content.
placeholder: "The README says ..., but actually ..."
description: Describe the missing, incorrect, or unclear documentation.
validations:
required: true
- type: textarea
- type: input
id: location
attributes:
label: Where is it? (optional)
description: Link the file/section if possible.
placeholder: "File: ...\nSection/heading: ...\nLink: ..."
label: Location
description: Link the page, file, or heading if you can.
placeholder: https://...
validations:
required: false
- type: textarea
id: suggestion
attributes:
label: Suggested wording (optional)
description: If you already know how it should read, propose text.
placeholder: "Proposed text: ..."
label: Suggested change
description: Add proposed wording or a short outline if useful.
validations:
required: false
- type: checkboxes
@@ -38,5 +35,7 @@ body:
attributes:
label: Checks
options:
- label: I searched for existing issues and didn't find a duplicate.
- label: I searched existing issues.
required: true
- label: I wrote this report in my own words, except for direct translation if needed.
required: true
+10
View File
@@ -0,0 +1,10 @@
self-hosted-runner:
labels:
- blacksmith-4vcpu-ubuntu-2404
- blacksmith-4vcpu-ubuntu-2404-arm
- blacksmith-32vcpu-ubuntu-2404
- blacksmith-32vcpu-ubuntu-2404-arm
- blacksmith-32vcpu-windows-2025
- fluxer-desktop-macos-arm64
config-variables: null
+12 -23
View File
@@ -1,32 +1,21 @@
## Summary
<!-- A few bullets is perfect: what changed, why it changed, and anything reviewers should pay attention to. -->
- What changed:
- Why it is correct:
- Risk:
- **What:**
- **Why:**
- **Notes for reviewers:**
## Verification
## How to verify
<!-- Concrete steps to validate the change. Include screenshots/recordings for UI changes when helpful. -->
1.
2.
3.
## Tests
<!-- List what you ran, or explain why tests weren't added/changed. -->
- [ ] Added/updated unit tests (where it makes sense)
- [ ] Manual verification:
- Tests run:
- Manual checks:
- Screenshots or recordings:
## Checklist
- [ ] PR targets `canary`
- [ ] PR title follows Conventional Commits (mostly lowercase)
- [ ] CI is green (or I'm actively addressing failures)
- [ ] I understand every change in this PR.
- [ ] I can explain what it does and why it is correct.
- [ ] I disclosed any LLM coding help below.
## Screenshots / recordings (UI changes)
## LLM Disclosure
<!-- Drag and drop images/videos here. -->
- None, or:
+141
View File
@@ -0,0 +1,141 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
name: build image (reusable)
on:
workflow_call:
inputs:
image:
description: "Image name under ghcr.io/<owner>/ (for example fluxer-api)"
type: string
required: true
dockerfile:
description: "Path to the Dockerfile to build"
type: string
required: true
context:
description: "Docker build context"
type: string
required: false
default: "."
build-version:
description: "Explicit Fluxer CalVer build version (YYYY.MDD.MICRO, UTC HHMMSS without leading zeroes) to use instead of automatic UTC clock allocation"
type: string
required: false
default: ""
moving-tags:
description: "Comma-separated moving tags to repoint at this build"
type: string
required: false
default: "v1,latest"
extra-build-args:
description: "Additional Docker build args, one KEY=VALUE entry per line"
type: string
required: false
default: ""
permissions:
actions: read
contents: read
packages: write
defaults:
run:
shell: bash
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
GHCR_OWNER: ${{ github.repository_owner }}
jobs:
meta:
name: resolve metadata
runs-on: ubuntu-24.04
timeout-minutes: 5
outputs:
build_version: ${{ steps.vars.outputs.build_version }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
env:
GIT_CONFIG_GLOBAL: ${{ runner.temp }}/gitconfig
- name: Set up Rust toolchain (CI helpers)
uses: dtolnay/rust-toolchain@b3b07ba8b418998c39fb20f53e8b695cdcc8de1b
with:
toolchain: "1.93.0"
- name: set variables
id: vars
env:
GH_TOKEN: ${{ github.token }}
FLUXER_BUILD_VERSION: ${{ inputs.build-version }}
run: >-
cargo run --locked --quiet --manifest-path tools/ci/Cargo.toml -- resolve-calver
--github-output
build:
name: build ${{ matrix.platform }}
needs: meta
runs-on: ${{ matrix.runner }}
timeout-minutes: 75
strategy:
fail-fast: false
matrix:
include:
- platform: amd64
runner: blacksmith-4vcpu-ubuntu-2404
- platform: arm64
runner: blacksmith-4vcpu-ubuntu-2404-arm
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
env:
GIT_CONFIG_GLOBAL: ${{ runner.temp }}/gitconfig
- uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f
- uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8
with:
context: ${{ inputs.context }}
file: ${{ inputs.dockerfile }}
push: true
provenance: false
platforms: linux/${{ matrix.platform }}
tags: ghcr.io/${{ env.GHCR_OWNER }}/${{ inputs.image }}:${{ needs.meta.outputs.build_version }}-${{ matrix.platform }}
build-args: |
BUILD_VERSION=${{ needs.meta.outputs.build_version }}
${{ inputs.extra-build-args }}
cache-from: type=gha,scope=${{ inputs.image }}-${{ matrix.platform }}
cache-to: type=gha,scope=${{ inputs.image }}-${{ matrix.platform }},mode=max,ignore-error=true
env:
DOCKER_BUILD_SUMMARY: false
DOCKER_BUILD_RECORD_UPLOAD: false
merge:
name: merge multi-arch manifest
needs: [meta, build]
runs-on: ubuntu-24.04
timeout-minutes: 10
steps:
- uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f
- uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: create and push multi-arch manifest
env:
IMAGE: ghcr.io/${{ env.GHCR_OWNER }}/${{ inputs.image }}
VERSION: ${{ needs.meta.outputs.build_version }}
MOVING_TAGS: ${{ inputs.moving-tags }}
run: |
set -euo pipefail
tag_args=( "-t" "${IMAGE}:${VERSION}" )
IFS=',' read -ra moving <<< "${MOVING_TAGS}"
for raw in "${moving[@]}"; do
t="$(echo "$raw" | xargs)"
[ -n "$t" ] && tag_args+=( "-t" "${IMAGE}:${t}" )
done
docker buildx imagetools create "${tag_args[@]}" \
"${IMAGE}:${VERSION}-amd64" \
"${IMAGE}:${VERSION}-arm64"
docker buildx imagetools inspect "${IMAGE}:${VERSION}"
+32
View File
@@ -0,0 +1,32 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
name: build admin
on:
workflow_dispatch:
inputs:
build-version:
description: "Explicit Fluxer CalVer build version (YYYY.MDD.MICRO, UTC HHMMSS without leading zeroes) to use instead of automatic UTC clock allocation"
type: string
required: false
default: ""
workflow_call:
inputs:
build-version:
description: "Explicit Fluxer CalVer build version (YYYY.MDD.MICRO, UTC HHMMSS without leading zeroes) to use instead of automatic UTC clock allocation"
type: string
required: false
default: ""
permissions:
actions: read
contents: read
packages: write
jobs:
image:
uses: ./.github/workflows/_build-image.yaml
with:
image: fluxer-admin
dockerfile: fluxer_admin/Dockerfile
build-version: ${{ inputs.build-version }}
secrets: inherit
+32
View File
@@ -0,0 +1,32 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
name: build api
on:
workflow_dispatch:
inputs:
build-version:
description: "Explicit Fluxer CalVer build version (YYYY.MDD.MICRO, UTC HHMMSS without leading zeroes) to use instead of automatic UTC clock allocation"
type: string
required: false
default: ""
workflow_call:
inputs:
build-version:
description: "Explicit Fluxer CalVer build version (YYYY.MDD.MICRO, UTC HHMMSS without leading zeroes) to use instead of automatic UTC clock allocation"
type: string
required: false
default: ""
permissions:
actions: read
contents: read
packages: write
jobs:
image:
uses: ./.github/workflows/_build-image.yaml
with:
image: fluxer-api
dockerfile: fluxer_api/Dockerfile
build-version: ${{ inputs.build-version }}
secrets: inherit
@@ -0,0 +1,33 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
name: build app-proxy self-hosted
on:
workflow_dispatch:
inputs:
build-version:
description: "Explicit Fluxer CalVer build version (YYYY.MDD.MICRO, UTC HHMMSS without leading zeroes) to use instead of automatic UTC clock allocation"
type: string
required: false
default: ""
workflow_call:
inputs:
build-version:
description: "Explicit Fluxer CalVer build version (YYYY.MDD.MICRO, UTC HHMMSS without leading zeroes) to use instead of automatic UTC clock allocation"
type: string
required: false
default: ""
permissions:
actions: read
contents: read
packages: write
jobs:
build:
uses: ./.github/workflows/_build-image.yaml
with:
image: fluxer-app-proxy-self-hosted
dockerfile: fluxer_app_proxy/Dockerfile
build-version: ${{ inputs['build-version'] }}
extra-build-args: |
FLUXER_APP_PROXY_TIME_FREEZE_ENABLED=false
+162
View File
@@ -0,0 +1,162 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
name: build app-proxy
on:
workflow_dispatch:
inputs:
build-version:
description: "Explicit Fluxer CalVer build version (YYYY.MDD.MICRO, UTC HHMMSS without leading zeroes) to use instead of automatic UTC clock allocation"
type: string
required: false
default: ""
workflow_call:
inputs:
build-version:
description: "Explicit Fluxer CalVer build version (YYYY.MDD.MICRO, UTC HHMMSS without leading zeroes) to use instead of automatic UTC clock allocation"
type: string
required: false
default: ""
permissions:
actions: read
contents: read
packages: write
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
GHCR_OWNER: ${{ github.repository_owner }}
jobs:
meta:
name: resolve metadata
runs-on: ubuntu-24.04
timeout-minutes: 5
outputs:
build_version: ${{ steps.vars.outputs.build_version }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
env:
GIT_CONFIG_GLOBAL: ${{ runner.temp }}/gitconfig
- name: Set up Rust toolchain (CI helpers)
uses: dtolnay/rust-toolchain@b3b07ba8b418998c39fb20f53e8b695cdcc8de1b
with:
toolchain: "1.93.0"
- name: set variables
id: vars
run: >-
cargo run --locked --quiet --manifest-path tools/ci/Cargo.toml -- build-app-proxy
--step set_metadata
--build-version "${{ inputs['build-version'] }}"
build:
name: build app-proxy (amd64)
needs: meta
runs-on: blacksmith-4vcpu-ubuntu-2404
timeout-minutes: 45
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
env:
GIT_CONFIG_GLOBAL: ${{ runner.temp }}/gitconfig
- name: Set up Rust toolchain (CI helpers)
uses: dtolnay/rust-toolchain@b3b07ba8b418998c39fb20f53e8b695cdcc8de1b
with:
toolchain: "1.93.0"
- name: prepare docker config
run: >-
cargo run --locked --quiet --manifest-path tools/ci/Cargo.toml -- build-app-proxy
--step prepare_docker_config
- uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f
- name: configure ghcr auth
env:
GHCR_USERNAME: ${{ github.actor }}
GHCR_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: >-
cargo run --locked --quiet --manifest-path tools/ci/Cargo.toml -- build-app-proxy
--step configure_ghcr_auth
- name: build and push image + extract assets
env:
BUILD_VERSION: ${{ needs.meta.outputs.build_version }}
PUBLIC_ASSET_BASE_URL: https://fluxerstatic.com
CACHE_FROM: type=gha,scope=fluxer-app-proxy
CACHE_TO: type=gha,scope=fluxer-app-proxy,mode=max
DOCKER_BUILD_SUMMARY: false
DOCKER_BUILD_RECORD_UPLOAD: false
run: >-
cargo run --locked --quiet --manifest-path tools/ci/Cargo.toml -- build-app-proxy
--step build_and_extract
- name: generate asset manifest
run: >-
cargo run --locked --quiet --manifest-path tools/ci/Cargo.toml -- build-app-proxy
--step generate_asset_manifest
- name: upload assets to S3 static bucket
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
S3_ENDPOINT: https://ewr1.vultrobjects.com
STATIC_BUCKET: fluxer-static
run: >-
cargo run --locked --quiet --manifest-path tools/ci/Cargo.toml -- build-app-proxy
--step upload_assets
build-arm64:
name: build app-proxy (arm64)
needs: meta
runs-on: blacksmith-4vcpu-ubuntu-2404-arm
timeout-minutes: 60
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
env:
GIT_CONFIG_GLOBAL: ${{ runner.temp }}/gitconfig
- uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f
- uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8
with:
context: .
file: fluxer_app_proxy/Dockerfile
push: true
provenance: false
platforms: linux/arm64
tags: ghcr.io/${{ env.GHCR_OWNER }}/fluxer-app-proxy:${{ needs.meta.outputs.build_version }}-arm64
build-args: |
BUILD_VERSION=${{ needs.meta.outputs.build_version }}
PUBLIC_ASSET_BASE_URL=https://fluxerstatic.com
cache-from: type=gha,scope=fluxer-app-proxy-arm64
cache-to: type=gha,scope=fluxer-app-proxy-arm64,mode=max,ignore-error=true
env:
DOCKER_BUILD_SUMMARY: false
DOCKER_BUILD_RECORD_UPLOAD: false
merge:
name: merge multi-arch manifest
needs: [meta, build, build-arm64]
runs-on: ubuntu-24.04
timeout-minutes: 10
steps:
- uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f
- uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: fuse amd64 + arm64 into a multi-arch manifest
env:
IMAGE: ghcr.io/${{ env.GHCR_OWNER }}/fluxer-app-proxy
VERSION: ${{ needs.meta.outputs.build_version }}
run: |
set -euo pipefail
amd64_digest="$(docker buildx imagetools inspect "${IMAGE}:${VERSION}" --format '{{json .Manifest}}' | jq -r '.digest')"
echo "amd64 digest: ${amd64_digest}"
docker buildx imagetools create \
-t "${IMAGE}:${VERSION}" \
-t "${IMAGE}:v1" \
-t "${IMAGE}:latest" \
"${IMAGE}@${amd64_digest}" \
"${IMAGE}:${VERSION}-arm64"
docker buildx imagetools inspect "${IMAGE}:${VERSION}"
+382 -161
View File
@@ -4,202 +4,227 @@ on:
workflow_dispatch:
inputs:
channel:
description: Channel to build (stable or canary)
description: Release channel label (stable or canary). Source is always this repository's main branch.
required: false
type: choice
options:
- stable
- canary
default: stable
ref:
description: Git ref to build (branch, tag, or commit SHA)
test_build:
description: Stash artifacts under desktop-test/ instead of desktop/ (API will not pick these up as a release).
required: false
default: ''
default: false
type: boolean
build_version:
description: Explicit Fluxer CalVer build version (YYYY.MDD.MICRO, UTC HHMMSS without leading zeroes) to use instead of automatic UTC clock allocation.
required: false
default: ""
type: string
skip_windows:
description: Skip Windows builds
skip_targets:
description: Comma-separated platforms or targets to skip, such as windows, macos-arm64, linux-x64.
required: false
default: false
type: boolean
skip_macos:
description: Skip macOS builds
required: false
default: false
type: boolean
skip_linux:
description: Skip Linux builds
required: false
default: false
type: boolean
skip_windows_x64:
description: Skip Windows x64 builds
required: false
default: false
type: boolean
skip_windows_arm64:
description: Skip Windows ARM64 builds
required: false
default: false
type: boolean
skip_macos_x64:
description: Skip macOS x64 builds
required: false
default: false
type: boolean
skip_macos_arm64:
description: Skip macOS ARM64 builds
required: false
default: false
type: boolean
skip_linux_x64:
description: Skip Linux x64 builds
required: false
default: false
type: boolean
skip_linux_arm64:
description: Skip Linux ARM64 builds
required: false
default: false
type: boolean
default: ""
type: string
permissions:
contents: write
id-token: write
actions: read
concurrency:
group: desktop-${{ inputs.channel }}
group: desktop-${{ inputs.channel }}-${{ inputs.test_build && 'test' || 'release' }}
cancel-in-progress: true
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
CHANNEL: ${{ inputs.channel }}
BUILD_CHANNEL: ${{ inputs.channel == 'canary' && 'canary' || 'stable' }}
TEST_BUILD: ${{ inputs.test_build && 'true' || 'false' }}
jobs:
meta:
name: Resolve build metadata
runs-on: blacksmith-8vcpu-ubuntu-2404
runs-on: ubuntu-24.04-arm
timeout-minutes: 25
outputs:
version: ${{ steps.meta.outputs.version }}
pub_date: ${{ steps.meta.outputs.pub_date }}
channel: ${{ steps.meta.outputs.channel }}
build_channel: ${{ steps.meta.outputs.build_channel }}
test_build: ${{ steps.meta.outputs.test_build }}
s3_prefix: ${{ steps.meta.outputs.s3_prefix }}
source_sha: ${{ steps.meta.outputs.source_sha }}
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Checkout source
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
with:
sparse-checkout: scripts/ci
sparse-checkout-cone-mode: false
ref: main
- name: Set up Rust toolchain (CI helpers)
uses: dtolnay/rust-toolchain@b3b07ba8b418998c39fb20f53e8b695cdcc8de1b
with:
toolchain: "1.93.0"
- name: Set metadata
id: meta
env:
GH_TOKEN: ${{ github.token }}
FLUXER_BUILD_VERSION: ${{ inputs.build_version }}
run: >-
python3 scripts/ci/workflows/build_desktop.py
cargo run --locked --quiet --manifest-path tools/ci/Cargo.toml -- build-desktop
--step set_metadata
--channel "${{ inputs.channel }}"
--ref "${{ inputs.ref }}"
--test-build "${{ inputs.test_build }}"
matrix:
name: Resolve build matrix
runs-on: blacksmith-8vcpu-ubuntu-2404
runs-on: ubuntu-24.04-arm
timeout-minutes: 25
outputs:
matrix: ${{ steps.set-matrix.outputs.matrix }}
windows_x64: ${{ steps.set-matrix.outputs.windows_x64 }}
windows_arm64: ${{ steps.set-matrix.outputs.windows_arm64 }}
windows_x64_default: ${{ steps.set-matrix.outputs.windows_x64_default }}
windows_arm64_default: ${{ steps.set-matrix.outputs.windows_arm64_default }}
windows_game_capture_x64: ${{ steps.set-matrix.outputs.windows_game_capture_x64 }}
windows_game_capture_arm64: ${{ steps.set-matrix.outputs.windows_game_capture_arm64 }}
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Checkout source
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
- name: Set up Rust toolchain (CI helpers)
uses: dtolnay/rust-toolchain@b3b07ba8b418998c39fb20f53e8b695cdcc8de1b
with:
sparse-checkout: scripts/ci
sparse-checkout-cone-mode: false
toolchain: "1.93.0"
- name: Build platform matrix
id: set-matrix
run: >-
python3 scripts/ci/workflows/build_desktop.py
cargo run --locked --quiet --manifest-path tools/ci/Cargo.toml -- build-desktop
--step set_matrix
--skip-windows "${{ inputs.skip_windows }}"
--skip-windows-x64 "${{ inputs.skip_windows_x64 }}"
--skip-windows-arm64 "${{ inputs.skip_windows_arm64 }}"
--skip-macos "${{ inputs.skip_macos }}"
--skip-macos-x64 "${{ inputs.skip_macos_x64 }}"
--skip-macos-arm64 "${{ inputs.skip_macos_arm64 }}"
--skip-linux "${{ inputs.skip_linux }}"
--skip-linux-x64 "${{ inputs.skip_linux_x64 }}"
--skip-linux-arm64 "${{ inputs.skip_linux_arm64 }}"
--skip-targets "${{ inputs.skip_targets }}"
build:
name: Build ${{ matrix.platform }} (${{ matrix.arch }})
name: Build ${{ matrix.platform }} (${{ matrix.arch }}, ${{ matrix.desktop_variant }})
needs:
- meta
- matrix
runs-on: ${{ matrix.os }}
timeout-minutes: 25
timeout-minutes: 60
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.matrix.outputs.matrix) }}
env:
CHANNEL: ${{ needs.meta.outputs.channel }}
BUILD_CHANNEL: ${{ needs.meta.outputs.build_channel }}
RELEASE_CHANNEL: ${{ needs.meta.outputs.build_channel }}
PUBLIC_RELEASE_CHANNEL: ${{ needs.meta.outputs.build_channel }}
VERSION: ${{ needs.meta.outputs.version }}
BUILD_VERSION: ${{ needs.meta.outputs.version }}
PUBLIC_BUILD_VERSION: ${{ needs.meta.outputs.version }}
PUB_DATE: ${{ needs.meta.outputs.pub_date }}
SOURCE_SHA: ${{ needs.meta.outputs.source_sha }}
S3_DESKTOP_PREFIX: ${{ needs.meta.outputs.s3_prefix }}
DESKTOP_HANDOFF_PREFIX: _handoff/desktop/${{ needs.meta.outputs.build_channel }}/${{ needs.meta.outputs.version }}/${{ needs.meta.outputs.source_sha }}
S3_ENDPOINT: https://ewr1.vultrobjects.com
S3_BUCKET: fluxer-downloads
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
DESKTOP_PLATFORM: ${{ matrix.platform }}
DESKTOP_ARCH: ${{ matrix.arch }}
DESKTOP_VARIANT: ${{ matrix.desktop_variant }}
FLUXER_DESKTOP_BUILD_VARIANT: ${{ matrix.desktop_variant }}
PLATFORM: ${{ matrix.platform }}
ARCH: ${{ matrix.arch }}
ELECTRON_ARCH: ${{ matrix.electron_arch }}
FLUXER_WINDOWS_GAME_CAPTURE_MODULE_ENABLED: ${{ matrix.desktop_variant == 'windows-game-capture' && 'true' || 'false' }}
steps:
- name: Checkout source
uses: actions/checkout@v6
- name: Checkout CI helpers
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
with:
ref: ${{ inputs.ref || '' }}
ref: ${{ needs.meta.outputs.source_sha }}
path: _ci
- name: Shorten Windows paths (workspace + temp for Squirrel) and pin pnpm store
- name: Checkout source
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
with:
ref: ${{ needs.meta.outputs.source_sha }}
path: source
- name: Set up Rust toolchain (CI helpers)
uses: dtolnay/rust-toolchain@b3b07ba8b418998c39fb20f53e8b695cdcc8de1b
with:
toolchain: "1.93.0"
- name: Set up Python (Windows)
if: runner.os == 'Windows'
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405
with:
python-version: "3.13"
- name: Ensure python3 command (Windows)
if: runner.os == 'Windows'
run: >-
python3 ${{ github.workspace }}/scripts/ci/workflows/build_desktop.py
cargo run --locked --quiet --manifest-path ${{ github.workspace }}/_ci/tools/ci/Cargo.toml -- build-desktop
--step ensure_python3_windows
- name: Shorten Windows paths and pin pnpm store
if: runner.os == 'Windows'
run: >-
cargo run --locked --quiet --manifest-path ${{ github.workspace }}/_ci/tools/ci/Cargo.toml -- build-desktop
--step windows_paths
env:
SUBST_TARGET: ${{ github.workspace }}/source
- name: Set workdir (Unix)
if: runner.os != 'Windows'
env:
SUBST_TARGET: ${{ github.workspace }}/source
run: >-
python3 ${{ github.workspace }}/scripts/ci/workflows/build_desktop.py
cargo run --locked --quiet --manifest-path ${{ github.workspace }}/_ci/tools/ci/Cargo.toml -- build-desktop
--step set_workdir_unix
- name: Set up pnpm
uses: pnpm/action-setup@v4
- name: Set up Node.js
uses: actions/setup-node@v6
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238
with:
node-version: 24
- name: Set up pnpm via corepack
run: >-
cargo run --locked --quiet --manifest-path ${{ github.workspace }}/_ci/tools/ci/Cargo.toml -- build-desktop
--step setup_pnpm_corepack
- name: Resolve pnpm store path (Windows)
if: runner.os == 'Windows'
run: >-
python3 ${{ github.workspace }}/scripts/ci/workflows/build_desktop.py
cargo run --locked --quiet --manifest-path ${{ github.workspace }}/_ci/tools/ci/Cargo.toml -- build-desktop
--step resolve_pnpm_store_windows
- name: Resolve pnpm store path (Unix)
if: runner.os != 'Windows'
run: >-
python3 ${{ github.workspace }}/scripts/ci/workflows/build_desktop.py
cargo run --locked --quiet --manifest-path ${{ github.workspace }}/_ci/tools/ci/Cargo.toml -- build-desktop
--step resolve_pnpm_store_unix
- name: Cache pnpm store
uses: actions/cache@v4
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae
with:
path: ${{ env.PNPM_STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
key: ${{ runner.os }}-${{ matrix.arch }}-pnpm-store-${{ hashFiles('source/**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-
${{ runner.os }}-${{ matrix.arch }}-pnpm-store-
- name: Install Python setuptools (Windows ARM64)
if: matrix.platform == 'windows' && matrix.arch == 'arm64'
run: >-
python3 ${{ github.workspace }}/scripts/ci/workflows/build_desktop.py
cargo run --locked --quiet --manifest-path ${{ github.workspace }}/_ci/tools/ci/Cargo.toml -- build-desktop
--step install_setuptools_windows_arm64
- name: Install Python setuptools (macOS)
if: matrix.platform == 'macos'
run: >-
python3 ${{ github.workspace }}/scripts/ci/workflows/build_desktop.py
cargo run --locked --quiet --manifest-path ${{ github.workspace }}/_ci/tools/ci/Cargo.toml -- build-desktop
--step install_setuptools_macos
- name: Install Linux dependencies
@@ -207,19 +232,46 @@ jobs:
env:
DEBIAN_FRONTEND: noninteractive
run: >-
python3 ${{ github.workspace }}/scripts/ci/workflows/build_desktop.py
cargo run --locked --quiet --manifest-path ${{ github.workspace }}/_ci/tools/ci/Cargo.toml -- build-desktop
--step install_linux_deps
- name: Set up Rust toolchain (Unix)
if: matrix.platform != 'windows'
uses: dtolnay/rust-toolchain@b3b07ba8b418998c39fb20f53e8b695cdcc8de1b
with:
toolchain: "1.93.0"
targets: ${{ matrix.platform == 'macos' && (matrix.arch == 'arm64' && 'aarch64-apple-darwin' || 'x86_64-apple-darwin') || (matrix.arch == 'arm64' && 'aarch64-unknown-linux-gnu' || 'x86_64-unknown-linux-gnu') }}
- name: Install MSVC ARM64 build tools
if: matrix.platform == 'windows' && matrix.arch == 'arm64'
run: >-
cargo run --locked --quiet --manifest-path ${{ github.workspace }}/_ci/tools/ci/Cargo.toml -- build-desktop
--step install_msvc_arm64_tools
- name: Set up MSVC env (Windows)
if: matrix.platform == 'windows'
uses: TheMrMilchmann/setup-msvc-dev@79dac248aac9d0059f86eae9d8b5bfab4e95e97c
with:
arch: ${{ matrix.arch == 'arm64' && 'amd64_arm64' || 'amd64' }}
- name: Set up Rust toolchain (Windows)
if: matrix.platform == 'windows'
env:
ARCH: ${{ matrix.arch }}
run: >-
cargo run --locked --quiet --manifest-path ${{ github.workspace }}/_ci/tools/ci/Cargo.toml -- build-desktop
--step install_rust_windows_targets
- name: Install dependencies
working-directory: ${{ env.WORKDIR }}/fluxer_desktop
run: >-
python3 ${{ github.workspace }}/scripts/ci/workflows/build_desktop.py
cargo run --locked --quiet --manifest-path ${{ github.workspace }}/_ci/tools/ci/Cargo.toml -- build-desktop
--step install_dependencies
- name: Update version
working-directory: ${{ env.WORKDIR }}/fluxer_desktop
run: >-
python3 ${{ github.workspace }}/scripts/ci/workflows/build_desktop.py
cargo run --locked --quiet --manifest-path ${{ github.workspace }}/_ci/tools/ci/Cargo.toml -- build-desktop
--step update_version
- name: Set build channel
@@ -227,20 +279,30 @@ jobs:
env:
BUILD_CHANNEL: ${{ env.BUILD_CHANNEL }}
run: >-
python3 ${{ github.workspace }}/scripts/ci/workflows/build_desktop.py
cargo run --locked --quiet --manifest-path ${{ github.workspace }}/_ci/tools/ci/Cargo.toml -- build-desktop
--step set_build_channel
- name: Build Electron main process
working-directory: ${{ env.WORKDIR }}/fluxer_desktop
env:
BUILD_CHANNEL: ${{ env.BUILD_CHANNEL }}
TURBO_API: https://turborepo.fluxer.dev
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_TEAM: team_fluxer
run: >-
python3 ${{ github.workspace }}/scripts/ci/workflows/build_desktop.py
cargo run --locked --quiet --manifest-path ${{ github.workspace }}/_ci/tools/ci/Cargo.toml -- build-desktop
--step build_electron_main
- name: Set up .NET SDK (Windows)
if: matrix.platform == 'windows'
uses: actions/setup-dotnet@9a946fdbd5fb07b82b2f5a4466058b876ab72bb2
with:
dotnet-version: "8.0.x"
- name: Install Velopack CLI
if: matrix.platform == 'windows'
working-directory: ${{ env.WORKDIR }}/fluxer_desktop
run: >-
cargo run --locked --quiet --manifest-path ${{ github.workspace }}/_ci/tools/ci/Cargo.toml -- build-desktop
--step install_velopack_cli
- name: Build Electron app (macOS)
if: matrix.platform == 'macos'
working-directory: ${{ env.WORKDIR }}/fluxer_desktop
@@ -252,16 +314,16 @@ jobs:
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
run: >-
python3 ${{ github.workspace }}/scripts/ci/workflows/build_desktop.py
cargo run --locked --quiet --manifest-path ${{ github.workspace }}/_ci/tools/ci/Cargo.toml -- build-desktop
--step build_app_macos
- name: Verify macOS bundle ID (fail fast if wrong channel)
- name: Verify macOS artifact (bundle ID + profile + signature)
if: matrix.platform == 'macos'
working-directory: ${{ env.WORKDIR }}/fluxer_desktop
env:
BUILD_CHANNEL: ${{ env.BUILD_CHANNEL }}
run: >-
python3 ${{ github.workspace }}/scripts/ci/workflows/build_desktop.py
cargo run --locked --quiet --manifest-path ${{ github.workspace }}/_ci/tools/ci/Cargo.toml -- build-desktop
--step verify_bundle_id
- name: Build Electron app (Windows)
@@ -271,22 +333,31 @@ jobs:
BUILD_CHANNEL: ${{ env.BUILD_CHANNEL }}
TEMP: C:\t
TMP: C:\t
SQUIRREL_TEMP: C:\sq
ELECTRON_BUILDER_CACHE: C:\ebcache
run: >-
python3 ${{ github.workspace }}/scripts/ci/workflows/build_desktop.py
cargo run --locked --quiet --manifest-path ${{ github.workspace }}/_ci/tools/ci/Cargo.toml -- build-desktop
--step build_app_windows
- name: Analyze Squirrel nupkg for long paths
- name: Package Windows app with Velopack
if: matrix.platform == 'windows'
working-directory: ${{ env.WORKDIR }}/fluxer_desktop
env:
BUILD_CHANNEL: ${{ env.BUILD_CHANNEL }}
run: >-
cargo run --locked --quiet --manifest-path ${{ github.workspace }}/_ci/tools/ci/Cargo.toml -- build-desktop
--step package_app_windows_velopack
- name: Analyze Velopack nupkg for long paths
if: matrix.platform == 'windows'
working-directory: ${{ env.WORKDIR }}/fluxer_desktop
env:
BUILD_VERSION: ${{ env.VERSION }}
BUILD_CHANNEL: ${{ env.BUILD_CHANNEL }}
MAX_WINDOWS_PATH_LEN: 260
PATH_HEADROOM: 10
run: >-
python3 ${{ github.workspace }}/scripts/ci/workflows/build_desktop.py
--step analyse_squirrel_paths
cargo run --locked --quiet --manifest-path ${{ github.workspace }}/_ci/tools/ci/Cargo.toml -- build-desktop
--step analyse_velopack_paths
- name: Build Electron app (Linux)
if: matrix.platform == 'linux'
@@ -295,121 +366,271 @@ jobs:
BUILD_CHANNEL: ${{ env.BUILD_CHANNEL }}
USE_SYSTEM_FPM: true
run: >-
python3 ${{ github.workspace }}/scripts/ci/workflows/build_desktop.py
cargo run --locked --quiet --manifest-path ${{ github.workspace }}/_ci/tools/ci/Cargo.toml -- build-desktop
--step build_app_linux
- name: Create portable ZIP (Windows)
if: matrix.platform == 'windows'
working-directory: ${{ env.WORKDIR }}/fluxer_desktop
env:
BUILD_CHANNEL: ${{ env.BUILD_CHANNEL }}
run: >-
cargo run --locked --quiet --manifest-path ${{ github.workspace }}/_ci/tools/ci/Cargo.toml -- build-desktop
--step create_portable_zip_windows
- name: Prepare artifacts (Windows)
if: runner.os == 'Windows'
run: >-
python3 ${{ github.workspace }}/scripts/ci/workflows/build_desktop.py
cargo run --locked --quiet --manifest-path ${{ github.workspace }}/_ci/tools/ci/Cargo.toml -- build-desktop
--step prepare_artifacts_windows
- name: Prepare artifacts (Unix)
if: runner.os != 'Windows'
run: >-
python3 ${{ github.workspace }}/scripts/ci/workflows/build_desktop.py
cargo run --locked --quiet --manifest-path ${{ github.workspace }}/_ci/tools/ci/Cargo.toml -- build-desktop
--step prepare_artifacts_unix
- name: Normalize updater YAML (arm64)
if: matrix.arch == 'arm64'
run: >-
python3 ${{ github.workspace }}/scripts/ci/workflows/build_desktop.py
cargo run --locked --quiet --manifest-path ${{ github.workspace }}/_ci/tools/ci/Cargo.toml -- build-desktop
--step normalise_updater_yaml
- name: Generate SHA256 checksums (Unix)
if: runner.os != 'Windows'
run: >-
python3 ${{ github.workspace }}/scripts/ci/workflows/build_desktop.py
cargo run --locked --quiet --manifest-path ${{ github.workspace }}/_ci/tools/ci/Cargo.toml -- build-desktop
--step generate_checksums_unix
- name: Generate SHA256 checksums (Windows)
if: runner.os == 'Windows'
run: >-
python3 ${{ github.workspace }}/scripts/ci/workflows/build_desktop.py
cargo run --locked --quiet --manifest-path ${{ github.workspace }}/_ci/tools/ci/Cargo.toml -- build-desktop
--step generate_checksums_windows
- name: Upload artifacts
uses: actions/upload-artifact@v4
- name: Build desktop source tarball
if: matrix.platform == 'linux' && matrix.arch == 'x64' && needs.meta.outputs.build_channel == 'canary'
run: >-
cargo run --locked --quiet --manifest-path ${{ github.workspace }}/_ci/tools/ci/Cargo.toml -- build-desktop
--step build_source_tarball
- name: Upload artifacts to S3 handoff
run: >-
cargo run --locked --quiet --manifest-path ${{ github.workspace }}/_ci/tools/ci/Cargo.toml -- build-desktop
--step upload_handoff
check_signing:
name: Check signing secrets
runs-on: ubuntu-24.04-arm
timeout-minutes: 5
outputs:
enabled: ${{ steps.check.outputs.enabled }}
steps:
- name: Checkout source
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
- name: Set up Rust toolchain (CI helpers)
uses: dtolnay/rust-toolchain@b3b07ba8b418998c39fb20f53e8b695cdcc8de1b
with:
name: fluxer-desktop-${{ env.BUILD_CHANNEL }}-${{ matrix.platform }}-${{ matrix.arch }}
path: |
upload_staging/*.exe
upload_staging/*.exe.blockmap
upload_staging/*.exe.sha256
upload_staging/*.dmg
upload_staging/*.dmg.sha256
upload_staging/*.zip
upload_staging/*.zip.blockmap
upload_staging/*.zip.sha256
upload_staging/*.AppImage
upload_staging/*.AppImage.sha256
upload_staging/*.deb
upload_staging/*.deb.sha256
upload_staging/*.rpm
upload_staging/*.rpm.sha256
upload_staging/*.tar.gz
upload_staging/*.tar.gz.sha256
upload_staging/*.yml
upload_staging/*.nupkg
upload_staging/*.nupkg.blockmap
upload_staging/*.nupkg.sha256
upload_staging/RELEASES*
retention-days: 30
toolchain: "1.93.0"
- name: Check for Azure signing secrets
id: check
env:
AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
run: >-
cargo run --locked --quiet --manifest-path tools/ci/Cargo.toml -- build-desktop
--step check_signing_secrets
sign_windows:
name: Sign Windows artifacts (${{ matrix.arch }}, ${{ matrix.desktop_variant }})
if: ${{ needs.check_signing.outputs.enabled == 'true' }}
needs:
- meta
- matrix
- build
- check_signing
runs-on: blacksmith-32vcpu-windows-2025
timeout-minutes: 25
env:
BUILD_CHANNEL: ${{ needs.meta.outputs.build_channel }}
DESKTOP_HANDOFF_PREFIX: _handoff/desktop/${{ needs.meta.outputs.build_channel }}/${{ needs.meta.outputs.version }}/${{ needs.meta.outputs.source_sha }}
S3_ENDPOINT: https://ewr1.vultrobjects.com
S3_BUCKET: fluxer-downloads
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
EXPECT_WINDOWS_X64: ${{ needs.matrix.outputs.windows_x64 }}
EXPECT_WINDOWS_ARM64: ${{ needs.matrix.outputs.windows_arm64 }}
EXPECT_WINDOWS_X64_DEFAULT: ${{ needs.matrix.outputs.windows_x64_default }}
EXPECT_WINDOWS_ARM64_DEFAULT: ${{ needs.matrix.outputs.windows_arm64_default }}
EXPECT_WINDOWS_GAME_CAPTURE_X64: ${{ needs.matrix.outputs.windows_game_capture_x64 }}
EXPECT_WINDOWS_GAME_CAPTURE_ARM64: ${{ needs.matrix.outputs.windows_game_capture_arm64 }}
EXPECT_WINDOWS_ARTIFACTS: ${{ (matrix.desktop_variant == 'default' && matrix.arch == 'x64' && needs.matrix.outputs.windows_x64_default == 'true') || (matrix.desktop_variant == 'default' && matrix.arch == 'arm64' && needs.matrix.outputs.windows_arm64_default == 'true') || (matrix.desktop_variant == 'windows-game-capture' && matrix.arch == 'x64' && needs.matrix.outputs.windows_game_capture_x64 == 'true') || (matrix.desktop_variant == 'windows-game-capture' && matrix.arch == 'arm64' && needs.matrix.outputs.windows_game_capture_arm64 == 'true') }}
DESKTOP_VARIANT: ${{ matrix.desktop_variant }}
strategy:
fail-fast: false
matrix:
include:
- arch: x64
desktop_variant: default
- arch: arm64
desktop_variant: default
- arch: x64
desktop_variant: windows-game-capture
- arch: arm64
desktop_variant: windows-game-capture
steps:
- name: Checkout CI helpers
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
with:
ref: ${{ needs.meta.outputs.source_sha }}
- name: Set up Rust toolchain (CI helpers)
uses: dtolnay/rust-toolchain@b3b07ba8b418998c39fb20f53e8b695cdcc8de1b
with:
toolchain: "1.93.0"
- name: Download Windows artifacts from S3 handoff
id: download_artifact
if: env.EXPECT_WINDOWS_ARTIFACTS == 'true'
env:
ARCH: ${{ matrix.arch }}
run: >-
cargo run --locked --quiet --manifest-path tools/ci/Cargo.toml -- build-desktop
--step download_windows_handoff
- name: Check whether artifacts exist for this arch
id: check_artifacts
env:
ARCH: ${{ matrix.arch }}
run: >-
cargo run --locked --quiet --manifest-path tools/ci/Cargo.toml -- build-desktop
--step check_windows_artifacts
- name: Azure login for Artifact Signing
if: steps.check_artifacts.outputs.found == 'true'
uses: azure/login@1384c340ab2dda50fed2bee3041d1d87018aa5e8
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
- name: Sign Windows executables with Artifact Signing
if: steps.check_artifacts.outputs.found == 'true'
uses: azure/artifact-signing-action@5289deff81257f39600de2188534e17b633ba835
with:
endpoint: ${{ secrets.AZURE_ARTIFACT_SIGNING_ENDPOINT }}
signing-account-name: ${{ secrets.AZURE_ARTIFACT_SIGNING_ACCOUNT_NAME }}
certificate-profile-name: ${{ secrets.AZURE_ARTIFACT_SIGNING_CERTIFICATE_PROFILE_NAME }}
files-folder: ${{ github.workspace }}\artifacts\windows-${{ matrix.arch }}${{ matrix.desktop_variant == 'windows-game-capture' && '-windows-game-capture' || '' }}
files-folder-filter: exe
files-folder-recurse: true
file-digest: SHA256
timestamp-rfc3161: http://timestamp.acs.microsoft.com
timestamp-digest: SHA256
- name: Verify Authenticode signatures
if: steps.check_artifacts.outputs.found == 'true'
env:
ARCH: ${{ matrix.arch }}
run: >-
cargo run --locked --quiet --manifest-path tools/ci/Cargo.toml -- build-desktop
--step verify_authenticode
- name: Regenerate SHA256 checksums for signed executables
if: steps.check_artifacts.outputs.found == 'true'
env:
ARCH: ${{ matrix.arch }}
run: >-
cargo run --locked --quiet --manifest-path tools/ci/Cargo.toml -- build-desktop
--step regenerate_signed_checksums
- name: Re-upload signed Windows artifacts to S3 handoff
if: steps.check_artifacts.outputs.found == 'true'
env:
DESKTOP_PLATFORM: windows
DESKTOP_ARCH: ${{ matrix.arch }}
DESKTOP_VARIANT: ${{ matrix.desktop_variant }}
run: >-
cargo run --locked --quiet --manifest-path tools/ci/Cargo.toml -- build-desktop
--step stage_signed_windows_artifacts
upload:
name: Upload to S3 (rclone)
name: Upload to S3
if: ${{ !failure() && !cancelled() }}
needs:
- meta
- build
runs-on: blacksmith-8vcpu-ubuntu-2404
timeout-minutes: 25
- sign_windows
runs-on: ubuntu-24.04-arm
timeout-minutes: 60
env:
CHANNEL: ${{ needs.meta.outputs.build_channel }}
DISPLAY_CHANNEL: ${{ needs.meta.outputs.channel }}
RELEASE_CHANNEL: ${{ needs.meta.outputs.build_channel }}
PUBLIC_RELEASE_CHANNEL: ${{ needs.meta.outputs.build_channel }}
VERSION: ${{ needs.meta.outputs.version }}
BUILD_VERSION: ${{ needs.meta.outputs.version }}
PUBLIC_BUILD_VERSION: ${{ needs.meta.outputs.version }}
PUB_DATE: ${{ needs.meta.outputs.pub_date }}
S3_ENDPOINT: https://s3.us-east-va.io.cloud.ovh.us
TEST_BUILD: ${{ needs.meta.outputs.test_build }}
SOURCE_SHA: ${{ needs.meta.outputs.source_sha }}
S3_DESKTOP_PREFIX: ${{ needs.meta.outputs.s3_prefix }}
DESKTOP_HANDOFF_PREFIX: _handoff/desktop/${{ needs.meta.outputs.build_channel }}/${{ needs.meta.outputs.version }}/${{ needs.meta.outputs.source_sha }}
S3_ENDPOINT: https://ewr1.vultrobjects.com
S3_BUCKET: fluxer-downloads
PUBLIC_DL_BASE: https://api.fluxer.app/dl
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Checkout source
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
with:
sparse-checkout: scripts/ci
sparse-checkout-cone-mode: false
ref: ${{ needs.meta.outputs.source_sha }}
- name: Download all artifacts
uses: actions/download-artifact@v4
- name: Set up Rust toolchain (CI helpers)
uses: dtolnay/rust-toolchain@b3b07ba8b418998c39fb20f53e8b695cdcc8de1b
with:
path: artifacts
pattern: fluxer-desktop-${{ needs.meta.outputs.build_channel }}-*
toolchain: "1.93.0"
- name: Install rclone
- name: Download S3 handoff artifacts
run: >-
python3 scripts/ci/workflows/build_desktop.py
--step install_rclone
- name: Configure rclone (OVH S3)
run: >-
python3 scripts/ci/workflows/build_desktop.py
--step configure_rclone
cargo run --locked --quiet --manifest-path tools/ci/Cargo.toml -- build-desktop
--step download_handoff
- name: Build S3 payload layout (+ manifest.json)
env:
VERSION: ${{ needs.meta.outputs.version }}
PUB_DATE: ${{ needs.meta.outputs.pub_date }}
run: >-
python3 scripts/ci/workflows/build_desktop.py
cargo run --locked --quiet --manifest-path tools/ci/Cargo.toml -- build-desktop
--step build_payload
- name: Upload payload to S3
run: >-
python3 scripts/ci/workflows/build_desktop.py
cargo run --locked --quiet --manifest-path tools/ci/Cargo.toml -- build-desktop
--step upload_payload
- name: Verify uploaded source tarball
run: >-
cargo run --locked --quiet --manifest-path tools/ci/Cargo.toml -- build-desktop
--step verify_source_tarball
- name: Build summary
run: >-
python3 scripts/ci/workflows/build_desktop.py
cargo run --locked --quiet --manifest-path tools/ci/Cargo.toml -- build-desktop
--step build_summary
- name: Notify canary desktop webhook
if: ${{ success() && needs.meta.outputs.channel == 'canary' }}
env:
FLUXER_WEBHOOK_URL: ${{ secrets.FLUXER_WEBHOOK_URL }}
run: >-
cargo run --locked --quiet --manifest-path tools/ci/Cargo.toml -- build-desktop
--step notify_webhook
- name: Cleanup S3 handoff
if: ${{ success() }}
run: >-
cargo run --locked --quiet --manifest-path tools/ci/Cargo.toml -- build-desktop
--step cleanup_handoff
+33
View File
@@ -0,0 +1,33 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
name: build docs
on:
workflow_dispatch:
inputs:
build-version:
description: "Explicit Fluxer CalVer build version (YYYY.MDD.MICRO, UTC HHMMSS without leading zeroes) to use instead of automatic UTC clock allocation"
type: string
required: false
default: ""
workflow_call:
inputs:
build-version:
description: "Explicit Fluxer CalVer build version (YYYY.MDD.MICRO, UTC HHMMSS without leading zeroes) to use instead of automatic UTC clock allocation"
type: string
required: false
default: ""
permissions:
actions: read
contents: read
packages: write
jobs:
image:
uses: ./.github/workflows/_build-image.yaml
with:
image: fluxer-docs
dockerfile: fluxer_docs/Dockerfile
context: fluxer_docs
build-version: ${{ inputs.build-version }}
secrets: inherit
+32
View File
@@ -0,0 +1,32 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
name: build gateway
on:
workflow_dispatch:
inputs:
build-version:
description: "Explicit Fluxer CalVer build version (YYYY.MDD.MICRO, UTC HHMMSS without leading zeroes) to use instead of automatic UTC clock allocation"
type: string
required: false
default: ""
workflow_call:
inputs:
build-version:
description: "Explicit Fluxer CalVer build version (YYYY.MDD.MICRO, UTC HHMMSS without leading zeroes) to use instead of automatic UTC clock allocation"
type: string
required: false
default: ""
permissions:
actions: read
contents: read
packages: write
jobs:
image:
uses: ./.github/workflows/_build-image.yaml
with:
image: fluxer-gateway
dockerfile: fluxer_gateway/Dockerfile
build-version: ${{ inputs.build-version }}
secrets: inherit
+32
View File
@@ -0,0 +1,32 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
name: build marketing
on:
workflow_dispatch:
inputs:
build-version:
description: "Explicit Fluxer CalVer build version (YYYY.MDD.MICRO, UTC HHMMSS without leading zeroes) to use instead of automatic UTC clock allocation"
type: string
required: false
default: ""
workflow_call:
inputs:
build-version:
description: "Explicit Fluxer CalVer build version (YYYY.MDD.MICRO, UTC HHMMSS without leading zeroes) to use instead of automatic UTC clock allocation"
type: string
required: false
default: ""
permissions:
actions: read
contents: read
packages: write
jobs:
image:
uses: ./.github/workflows/_build-image.yaml
with:
image: fluxer-marketing
dockerfile: fluxer_marketing/Dockerfile
build-version: ${{ inputs.build-version }}
secrets: inherit
+32
View File
@@ -0,0 +1,32 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
name: build media-proxy
on:
workflow_dispatch:
inputs:
build-version:
description: "Explicit Fluxer CalVer build version (YYYY.MDD.MICRO, UTC HHMMSS without leading zeroes) to use instead of automatic UTC clock allocation"
type: string
required: false
default: ""
workflow_call:
inputs:
build-version:
description: "Explicit Fluxer CalVer build version (YYYY.MDD.MICRO, UTC HHMMSS without leading zeroes) to use instead of automatic UTC clock allocation"
type: string
required: false
default: ""
permissions:
actions: read
contents: read
packages: write
jobs:
image:
uses: ./.github/workflows/_build-image.yaml
with:
image: fluxer-media-proxy
dockerfile: fluxer_media_proxy/Dockerfile
build-version: ${{ inputs.build-version }}
secrets: inherit
+32
View File
@@ -0,0 +1,32 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
name: build messages
on:
workflow_dispatch:
inputs:
build-version:
description: "Explicit Fluxer CalVer build version (YYYY.MDD.MICRO, UTC HHMMSS without leading zeroes) to use instead of automatic UTC clock allocation"
type: string
required: false
default: ""
workflow_call:
inputs:
build-version:
description: "Explicit Fluxer CalVer build version (YYYY.MDD.MICRO, UTC HHMMSS without leading zeroes) to use instead of automatic UTC clock allocation"
type: string
required: false
default: ""
permissions:
actions: read
contents: read
packages: write
jobs:
image:
uses: ./.github/workflows/_build-image.yaml
with:
image: fluxer-messages
dockerfile: fluxer_messages/Dockerfile
build-version: ${{ inputs.build-version }}
secrets: inherit
+32
View File
@@ -0,0 +1,32 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
name: build snowflakes
on:
workflow_dispatch:
inputs:
build-version:
description: "Explicit Fluxer CalVer build version (YYYY.MDD.MICRO, UTC HHMMSS without leading zeroes) to use instead of automatic UTC clock allocation"
type: string
required: false
default: ""
workflow_call:
inputs:
build-version:
description: "Explicit Fluxer CalVer build version (YYYY.MDD.MICRO, UTC HHMMSS without leading zeroes) to use instead of automatic UTC clock allocation"
type: string
required: false
default: ""
permissions:
actions: read
contents: read
packages: write
jobs:
image:
uses: ./.github/workflows/_build-image.yaml
with:
image: fluxer-snowflakes
dockerfile: fluxer_snowflakes/Dockerfile
build-version: ${{ inputs.build-version }}
secrets: inherit
+32
View File
@@ -0,0 +1,32 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
name: build static
on:
workflow_dispatch:
inputs:
build-version:
description: "Explicit Fluxer CalVer build version (YYYY.MDD.MICRO, UTC HHMMSS without leading zeroes) to use instead of automatic UTC clock allocation"
type: string
required: false
default: ""
workflow_call:
inputs:
build-version:
description: "Explicit Fluxer CalVer build version (YYYY.MDD.MICRO, UTC HHMMSS without leading zeroes) to use instead of automatic UTC clock allocation"
type: string
required: false
default: ""
permissions:
actions: read
contents: read
packages: write
jobs:
image:
uses: ./.github/workflows/_build-image.yaml
with:
image: fluxer-static
dockerfile: fluxer_static/Dockerfile
build-version: ${{ inputs.build-version }}
secrets: inherit
+32
View File
@@ -0,0 +1,32 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
name: build unfurl
on:
workflow_dispatch:
inputs:
build-version:
description: "Explicit Fluxer CalVer build version (YYYY.MDD.MICRO, UTC HHMMSS without leading zeroes) to use instead of automatic UTC clock allocation"
type: string
required: false
default: ""
workflow_call:
inputs:
build-version:
description: "Explicit Fluxer CalVer build version (YYYY.MDD.MICRO, UTC HHMMSS without leading zeroes) to use instead of automatic UTC clock allocation"
type: string
required: false
default: ""
permissions:
actions: read
contents: read
packages: write
jobs:
image:
uses: ./.github/workflows/_build-image.yaml
with:
image: fluxer-unfurl
dockerfile: fluxer_unfurl/Dockerfile
build-version: ${{ inputs.build-version }}
secrets: inherit
+32
View File
@@ -0,0 +1,32 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
name: build users
on:
workflow_dispatch:
inputs:
build-version:
description: "Explicit Fluxer CalVer build version (YYYY.MDD.MICRO, UTC HHMMSS without leading zeroes) to use instead of automatic UTC clock allocation"
type: string
required: false
default: ""
workflow_call:
inputs:
build-version:
description: "Explicit Fluxer CalVer build version (YYYY.MDD.MICRO, UTC HHMMSS without leading zeroes) to use instead of automatic UTC clock allocation"
type: string
required: false
default: ""
permissions:
actions: read
contents: read
packages: write
jobs:
image:
uses: ./.github/workflows/_build-image.yaml
with:
image: fluxer-users
dockerfile: fluxer_users/Dockerfile
build-version: ${{ inputs.build-version }}
secrets: inherit
-48
View File
@@ -1,48 +0,0 @@
name: channel vars
on:
workflow_call:
inputs:
github_event_name:
type: string
github_ref_name:
type: string
required: false
workflow_dispatch_channel:
type: string
required: false
outputs:
channel:
description: 'Computed release channel (stable|canary)'
value: ${{ jobs.emit.outputs.channel }}
is_canary:
description: 'Whether this is a canary deploy (true|false)'
value: ${{ jobs.emit.outputs.is_canary }}
stack_suffix:
description: "Suffix for stack/image names ('' or '-canary')"
value: ${{ jobs.emit.outputs.stack_suffix }}
jobs:
emit:
runs-on: ubuntu-latest
timeout-minutes: 25
outputs:
channel: ${{ steps.compute.outputs.channel }}
is_canary: ${{ steps.compute.outputs.is_canary }}
stack_suffix: ${{ steps.compute.outputs.stack_suffix }}
steps:
- name: Checkout repository
uses: actions/checkout@v6
with:
sparse-checkout: scripts/ci
sparse-checkout-cone-mode: false
- name: Determine channel
id: compute
shell: bash
run: >-
python3 scripts/ci/workflows/channel_vars.py
--event-name "${{ inputs.github_event_name }}"
--ref-name "${{ inputs.github_ref_name || '' }}"
--dispatch-channel "${{ inputs.workflow_dispatch_channel || '' }}"
+251 -50
View File
@@ -1,78 +1,156 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
name: CI
on:
pull_request:
types: [opened, reopened, synchronize]
workflow_dispatch:
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
GHCR_REGISTRY: ghcr.io/${{ github.repository_owner }}
jobs:
typecheck:
runs-on: blacksmith-8vcpu-ubuntu-2404
runs-on: ubuntu-24.04
timeout-minutes: 25
steps:
- name: Checkout code
uses: actions/checkout@v6
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
- name: Set up Rust toolchain (CI helpers)
uses: dtolnay/rust-toolchain@b3b07ba8b418998c39fb20f53e8b695cdcc8de1b
with:
toolchain: "1.93.0"
- name: Install pnpm
uses: pnpm/action-setup@v4
uses: pnpm/action-setup@c5ba7f7862a0f64c1b1a05fbac13e0b8e86ba08c
- name: Install Node.js
uses: actions/setup-node@v4
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
with:
node-version: '24'
cache: 'pnpm'
- name: Install dependencies
run: python3 scripts/ci/workflows/ci.py --step install_dependencies
run: >-
cargo run --locked --quiet --manifest-path tools/ci/Cargo.toml -- ci
--step install_dependencies
- name: Run typecheck
run: python3 scripts/ci/workflows/ci.py --step typecheck
env:
TURBO_API: https://turborepo.fluxer.dev
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_TEAM: team_fluxer
run: >-
cargo run --locked --quiet --manifest-path tools/ci/Cargo.toml -- ci
--step typecheck
test:
runs-on: blacksmith-8vcpu-ubuntu-2404
runs-on: ubuntu-24.04
timeout-minutes: 25
steps:
- name: Checkout code
uses: actions/checkout@v6
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
- name: Set up Rust toolchain (CI helpers)
uses: dtolnay/rust-toolchain@b3b07ba8b418998c39fb20f53e8b695cdcc8de1b
with:
toolchain: "1.93.0"
- name: Install pnpm
uses: pnpm/action-setup@v4
uses: pnpm/action-setup@c5ba7f7862a0f64c1b1a05fbac13e0b8e86ba08c
- name: Install Node.js
uses: actions/setup-node@v4
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
with:
node-version: '24'
cache: 'pnpm'
- name: Install dependencies
run: python3 scripts/ci/workflows/ci.py --step install_dependencies
run: >-
cargo run --locked --quiet --manifest-path tools/ci/Cargo.toml -- ci
--step install_dependencies
- name: Run tests
run: python3 scripts/ci/workflows/ci.py --step test
run: >-
cargo run --locked --quiet --manifest-path tools/ci/Cargo.toml -- ci
--step test
rust:
runs-on: blacksmith-4vcpu-ubuntu-2404
timeout-minutes: 30
steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8
with:
toolchain: stable
components: clippy, rustfmt
- name: Cache cargo
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: rust-${{ runner.os }}-${{ hashFiles('Cargo.lock') }}
restore-keys: |
rust-${{ runner.os }}-
- name: Check formatting
run: cargo fmt --all -- --check
- name: Clippy (warnings as errors)
run: cargo clippy --workspace -- -D warnings
- name: Run tests
run: cargo test --workspace
marketing-docker:
runs-on: blacksmith-4vcpu-ubuntu-2404
timeout-minutes: 45
steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
- name: Set up Rust toolchain (CI helpers)
uses: dtolnay/rust-toolchain@b3b07ba8b418998c39fb20f53e8b695cdcc8de1b
with:
toolchain: "1.93.0"
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f
- name: Build marketing image
run: |
BUILD_VERSION="$(cargo run --locked --quiet --manifest-path tools/ci/Cargo.toml -- resolve-calver)"
docker build \
-f fluxer_marketing/Dockerfile \
-t "fluxer-marketing:${BUILD_VERSION}" \
--build-arg "BUILD_VERSION=${BUILD_VERSION}" \
.
env:
FLUXER_CONFIG: config/config.test.json
TURBO_API: https://turborepo.fluxer.dev
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_TEAM: team_fluxer
DOCKER_BUILD_SUMMARY: false
DOCKER_BUILD_RECORD_UPLOAD: false
gateway:
runs-on: blacksmith-8vcpu-ubuntu-2404
runs-on: ubuntu-24.04
timeout-minutes: 25
steps:
- name: Checkout code
uses: actions/checkout@v6
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
- name: Set up Rust toolchain (CI helpers)
uses: dtolnay/rust-toolchain@b3b07ba8b418998c39fb20f53e8b695cdcc8de1b
with:
toolchain: "1.93.0"
- name: Set up Erlang
uses: erlef/setup-beam@v1
uses: erlef/setup-beam@3559ac3b631a9560f28817e8e7fdde1638664336
with:
otp-version: '28'
rebar3-version: '3.24.0'
- name: Cache rebar3 dependencies
uses: actions/cache@v4
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830
with:
path: |
fluxer_gateway/_build
@@ -81,57 +159,180 @@ jobs:
restore-keys: |
rebar3-${{ runner.os }}-
- name: Check formatting
run: >-
cargo run --locked --quiet --manifest-path tools/ci/Cargo.toml -- ci
--step gateway_fmt
- name: Compile
run: python3 scripts/ci/workflows/ci.py --step gateway_compile
run: >-
cargo run --locked --quiet --manifest-path tools/ci/Cargo.toml -- ci
--step gateway_compile
- name: Run dialyzer
run: python3 scripts/ci/workflows/ci.py --step gateway_dialyzer
run: >-
cargo run --locked --quiet --manifest-path tools/ci/Cargo.toml -- ci
--step gateway_dialyzer
- name: Run eunit tests
run: python3 scripts/ci/workflows/ci.py --step gateway_eunit
env:
FLUXER_CONFIG: ../config/config.test.json
run: >-
cargo run --locked --quiet --manifest-path tools/ci/Cargo.toml -- ci
--step gateway_eunit
knip:
runs-on: blacksmith-8vcpu-ubuntu-2404
runs-on: ubuntu-24.04
timeout-minutes: 25
steps:
- name: Checkout code
uses: actions/checkout@v6
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
- name: Set up Rust toolchain (CI helpers)
uses: dtolnay/rust-toolchain@b3b07ba8b418998c39fb20f53e8b695cdcc8de1b
with:
toolchain: "1.93.0"
- name: Install pnpm
uses: pnpm/action-setup@v4
uses: pnpm/action-setup@c5ba7f7862a0f64c1b1a05fbac13e0b8e86ba08c
- name: Install Node.js
uses: actions/setup-node@v4
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
with:
node-version: '24'
cache: 'pnpm'
- name: Install dependencies
run: python3 scripts/ci/workflows/ci.py --step install_dependencies
run: >-
cargo run --locked --quiet --manifest-path tools/ci/Cargo.toml -- ci
--step install_dependencies
- name: Run knip
run: python3 scripts/ci/workflows/ci.py --step knip
env:
TURBO_API: https://turborepo.fluxer.dev
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_TEAM: team_fluxer
run: >-
cargo run --locked --quiet --manifest-path tools/ci/Cargo.toml -- ci
--step knip
ci-scripts:
runs-on: blacksmith-8vcpu-ubuntu-2404
runs-on: ubuntu-24.04
timeout-minutes: 25
steps:
- name: Checkout code
uses: actions/checkout@v6
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
- name: Set up uv
uses: astral-sh/setup-uv@v7
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8
with:
python-version: "3.12"
toolchain: stable
components: rustfmt
- name: Sync ci python dependencies
run: python3 scripts/ci/workflows/ci_scripts.py --step sync
- name: Sync ci helper dependencies
run: >-
cargo run --locked --quiet --manifest-path tools/ci/Cargo.toml -- ci-scripts
--step sync
- name: Run ci python tests
run: python3 scripts/ci/workflows/ci_scripts.py --step test
- name: Run ci helper tests
run: >-
cargo run --locked --quiet --manifest-path tools/ci/Cargo.toml -- ci-scripts
--step test
helm-and-scripts:
runs-on: ubuntu-24.04
timeout-minutes: 10
steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
- name: Set up Rust toolchain (CI helpers)
uses: dtolnay/rust-toolchain@b3b07ba8b418998c39fb20f53e8b695cdcc8de1b
with:
toolchain: "1.93.0"
- name: Install helm
uses: azure/setup-helm@bf6a7d304bc2fdb57e0331155b7ebf2c504acf0a
- name: Resolve Helm test build version
run: >-
cargo run --locked --quiet --manifest-path tools/ci/Cargo.toml -- resolve-calver
--github-env
--env-name HELM_TEST_BUILD_VERSION
- name: Helm dependency update (all charts)
run: |
set -euo pipefail
for chart_dir in deploy/helm/*/; do
if [[ -f "${chart_dir}Chart.yaml" ]]; then
helm dependency update "$chart_dir"
fi
done
- name: Helm lint (all charts)
run: |
set -euo pipefail
FAILED=0
for chart_dir in deploy/helm/*/; do
if [[ -f "${chart_dir}Chart.yaml" ]]; then
echo "--- Linting ${chart_dir} ---"
VALUES_ARGS=()
if [[ -f "${chart_dir}values.yaml" ]]; then
VALUES_ARGS=(-f "${chart_dir}values.yaml")
fi
EXTRA_SETS=(--set-string "global.registry=${GHCR_REGISTRY}")
case "${chart_dir}" in
*gateway*)
EXTRA_SETS+=(--set-string "gateway.tag=${HELM_TEST_BUILD_VERSION}" --set-string "gateway.build.version=${HELM_TEST_BUILD_VERSION}")
;;
*api*)
EXTRA_SETS+=(--set-string app.name=api --set-string "app.tag=${HELM_TEST_BUILD_VERSION}" --set-string app.config=stable --set-string "app.build.version=${HELM_TEST_BUILD_VERSION}")
;;
*app-proxy*)
EXTRA_SETS+=(--set-string app.name=app-proxy --set-string "app.tag=${HELM_TEST_BUILD_VERSION}" --set-string app.config=stable --set-string "app.build.version=${HELM_TEST_BUILD_VERSION}")
;;
*admin*)
EXTRA_SETS+=(--set-string app.name=admin --set-string "app.tag=${HELM_TEST_BUILD_VERSION}" --set-string app.config=stable --set-string "app.build.version=${HELM_TEST_BUILD_VERSION}")
;;
*marketing*)
EXTRA_SETS+=(--set-string app.name=marketing --set-string "app.tag=${HELM_TEST_BUILD_VERSION}" --set-string app.config=stable --set-string "app.build.version=${HELM_TEST_BUILD_VERSION}")
;;
*media-proxy*)
EXTRA_SETS+=(--set-string "mediaProxy.tag=${HELM_TEST_BUILD_VERSION}" --set-string "staticProxy.tag=${HELM_TEST_BUILD_VERSION}" --set-string "mediaProxy.build.version=${HELM_TEST_BUILD_VERSION}" --set-string "staticProxy.build.version=${HELM_TEST_BUILD_VERSION}")
;;
*uploads*)
EXTRA_SETS+=(--set-string app.name=uploads --set-string "app.tag=${HELM_TEST_BUILD_VERSION}" --set-string app.config=stable --set-string "app.build.version=${HELM_TEST_BUILD_VERSION}")
;;
*worker*)
EXTRA_SETS+=(--set-string "workerRealtime.tag=${HELM_TEST_BUILD_VERSION}" --set-string "workerUnfurl.tag=${HELM_TEST_BUILD_VERSION}" --set-string "workerLifecycle.tag=${HELM_TEST_BUILD_VERSION}" --set-string "workerBatch.tag=${HELM_TEST_BUILD_VERSION}" --set-string "workerRealtime.build.version=${HELM_TEST_BUILD_VERSION}" --set-string "workerUnfurl.build.version=${HELM_TEST_BUILD_VERSION}" --set-string "workerLifecycle.build.version=${HELM_TEST_BUILD_VERSION}" --set-string "workerBatch.build.version=${HELM_TEST_BUILD_VERSION}")
;;
*member-lists*|*messages*|*presence*|*snowflakes*|*unfurl*|*users*|*voice-states*)
EXTRA_SETS+=(--set-string "svc.tag=${HELM_TEST_BUILD_VERSION}" --set-string "svc.build.version=${HELM_TEST_BUILD_VERSION}" --set-string svc.build.channel=stable)
;;
esac
if ! helm lint "$chart_dir" "${VALUES_ARGS[@]}" "${EXTRA_SETS[@]}" --strict; then
FAILED=1
fi
fi
done
if [[ "$FAILED" -ne 0 ]]; then
echo "::error::One or more Helm charts failed linting"
exit 1
fi
- name: Helm template (gateway)
run: |
set -euo pipefail
helm template fluxer-gateway deploy/helm/gateway \
-f deploy/helm/gateway/values.yaml \
--set-string "global.registry=${GHCR_REGISTRY}" \
--set-string "gateway.tag=${HELM_TEST_BUILD_VERSION}" \
--set-string "gateway.build.version=${HELM_TEST_BUILD_VERSION}" \
-n fluxer > /dev/null
echo "Gateway chart templates render successfully."
- name: Validate gateway manifests with kubeconform
run: |
set -euo pipefail
helm template fluxer-gateway deploy/helm/gateway \
-f deploy/helm/gateway/values.yaml \
--set-string "global.registry=${GHCR_REGISTRY}" \
--set-string "gateway.tag=${HELM_TEST_BUILD_VERSION}" \
--set-string "gateway.build.version=${HELM_TEST_BUILD_VERSION}" \
-n fluxer \
| docker run -i --rm ghcr.io/yannh/kubeconform:v0.6.7 \
-strict -summary -kubernetes-version 1.31.0
-112
View File
@@ -1,112 +0,0 @@
name: deploy admin
on:
push:
branches:
- main
- canary
paths:
- fluxer_admin/**
- .github/workflows/deploy-admin.yaml
workflow_dispatch:
inputs:
channel:
type: choice
options:
- stable
- canary
default: stable
description: Release channel to deploy
ref:
type: string
required: false
default: ''
description: Optional git ref (defaults to the triggering branch)
concurrency:
group: deploy-fluxer-admin-${{ github.event_name == 'workflow_dispatch' && inputs.channel || (github.ref_name == 'canary' && 'canary') || 'stable' }}
cancel-in-progress: true
permissions:
contents: read
jobs:
channel-vars:
uses: ./.github/workflows/channel-vars.yaml
with:
github_event_name: ${{ github.event_name }}
github_ref_name: ${{ github.ref_name }}
workflow_dispatch_channel: ${{ github.event_name == 'workflow_dispatch' && inputs.channel || '' }}
deploy:
name: Deploy admin
needs: channel-vars
runs-on: blacksmith-8vcpu-ubuntu-2404
timeout-minutes: 25
env:
CHANNEL: ${{ needs.channel-vars.outputs.channel }}
IS_CANARY: ${{ needs.channel-vars.outputs.is_canary }}
STACK_SUFFIX: ${{ needs.channel-vars.outputs.stack_suffix }}
STACK: ${{ format('fluxer-admin{0}', needs.channel-vars.outputs.stack_suffix) }}
CACHE_SCOPE: ${{ format('deploy-fluxer-admin{0}', needs.channel-vars.outputs.stack_suffix) }}
CADDY_DOMAIN: ${{ needs.channel-vars.outputs.is_canary == 'true' && 'admin.canary.fluxer.app' || 'admin.fluxer.app' }}
REPLICAS: ${{ needs.channel-vars.outputs.is_canary == 'true' && 1 || 2 }}
RELEASE_CHANNEL: ${{ needs.channel-vars.outputs.channel }}
steps:
- uses: actions/checkout@v6
with:
ref: ${{ inputs.ref || '' }}
fetch-depth: 0
- name: Record deploy commit
run: python3 scripts/ci/workflows/deploy_admin.py --step record_deploy_commit
- name: Set build timestamp
run: python3 scripts/ci/workflows/deploy_admin.py --step set_build_timestamp
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
- name: Build image
uses: docker/build-push-action@v6
with:
context: .
file: fluxer_admin/Dockerfile
tags: ${{ env.STACK }}:${{ env.DEPLOY_SHA }}
load: true
platforms: linux/amd64
cache-from: type=gha,scope=${{ env.CACHE_SCOPE }}
cache-to: type=gha,mode=max,scope=${{ env.CACHE_SCOPE }}
build-args: |
BUILD_SHA=${{ env.DEPLOY_SHA }}
BUILD_NUMBER=${{ github.run_number }}
BUILD_TIMESTAMP=${{ env.BUILD_TIMESTAMP }}
RELEASE_CHANNEL=${{ env.RELEASE_CHANNEL }}
env:
DOCKER_BUILD_SUMMARY: false
DOCKER_BUILD_RECORD_UPLOAD: false
- name: Install docker-pussh
run: python3 scripts/ci/workflows/deploy_admin.py --step install_docker_pussh
- name: Set up SSH agent
uses: webfactory/ssh-agent@v0.9.1
with:
ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY_SERVER }}
- name: Add server to known hosts
run: python3 scripts/ci/workflows/deploy_admin.py --step add_known_hosts --server-ip ${{ secrets.SERVER_IP }}
- name: Push image and deploy
env:
IMAGE_TAG: ${{ env.STACK }}:${{ env.DEPLOY_SHA }}
SERVER: ${{ secrets.SERVER_USER }}@${{ secrets.SERVER_IP }}
STACK: ${{ env.STACK }}
CADDY_DOMAIN: ${{ env.CADDY_DOMAIN }}
REPLICAS: ${{ env.REPLICAS }}
run: python3 scripts/ci/workflows/deploy_admin.py --step push_and_deploy
-119
View File
@@ -1,119 +0,0 @@
name: deploy api
on:
push:
branches:
- main
- canary
paths:
- fluxer_api/**
- .github/workflows/deploy-api.yaml
workflow_dispatch:
inputs:
channel:
type: choice
options:
- stable
- canary
default: stable
description: Release channel to deploy
ref:
type: string
required: false
default: ''
description: Optional git ref (defaults to the triggering branch)
concurrency:
group: deploy-fluxer-api-${{ github.event_name == 'workflow_dispatch' && inputs.channel || (github.ref_name == 'canary' && 'canary') || 'stable' }}
cancel-in-progress: true
permissions:
contents: read
jobs:
channel-vars:
uses: ./.github/workflows/channel-vars.yaml
with:
github_event_name: ${{ github.event_name }}
github_ref_name: ${{ github.ref_name }}
workflow_dispatch_channel: ${{ github.event_name == 'workflow_dispatch' && inputs.channel || '' }}
deploy:
name: Deploy api
needs: channel-vars
runs-on: blacksmith-8vcpu-ubuntu-2404
timeout-minutes: 25
env:
CHANNEL: ${{ needs.channel-vars.outputs.channel }}
IS_CANARY: ${{ needs.channel-vars.outputs.is_canary }}
STACK_SUFFIX: ${{ needs.channel-vars.outputs.stack_suffix }}
STACK: ${{ format('fluxer-api{0}', needs.channel-vars.outputs.stack_suffix) }}
WORKER_STACK: fluxer-api-worker
CANARY_WORKER_REPLICAS: 3
CACHE_SCOPE: ${{ format('deploy-fluxer-api{0}', needs.channel-vars.outputs.stack_suffix) }}
CADDY_DOMAIN: ${{ needs.channel-vars.outputs.is_canary == 'true' && 'api.canary.fluxer.app' || 'api.fluxer.app' }}
RELEASE_CHANNEL: ${{ needs.channel-vars.outputs.channel }}
steps:
- uses: actions/checkout@v6
with:
ref: ${{ inputs.ref || '' }}
fetch-depth: 0
- name: Record deploy commit
run: python3 scripts/ci/workflows/deploy_api.py --step record_deploy_commit
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
- name: Build image(s)
uses: docker/build-push-action@v6
with:
context: .
file: fluxer_api/Dockerfile
tags: |
${{ env.STACK }}:${{ env.DEPLOY_SHA }}
${{ env.WORKER_STACK }}:${{ env.DEPLOY_SHA }}
load: true
platforms: linux/amd64
cache-from: type=gha,scope=${{ env.CACHE_SCOPE }}
cache-to: type=gha,mode=max,scope=${{ env.CACHE_SCOPE }}
build-args: |
BUILD_SHA=${{ env.SENTRY_BUILD_SHA }}
BUILD_NUMBER=${{ env.SENTRY_BUILD_NUMBER }}
BUILD_TIMESTAMP=${{ env.SENTRY_BUILD_TIMESTAMP }}
RELEASE_CHANNEL=${{ env.RELEASE_CHANNEL }}
env:
DOCKER_BUILD_SUMMARY: false
DOCKER_BUILD_RECORD_UPLOAD: false
- name: Install docker-pussh
run: python3 scripts/ci/workflows/deploy_api.py --step install_docker_pussh
- name: Set up SSH agent
uses: webfactory/ssh-agent@v0.9.1
with:
ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY_SERVER }}
- name: Add server to known hosts
run: python3 scripts/ci/workflows/deploy_api.py --step add_known_hosts --server-ip ${{ secrets.SERVER_IP }}
- name: Push image(s) and deploy
env:
SERVER: ${{ secrets.SERVER_USER }}@${{ secrets.SERVER_IP }}
IMAGE_TAG_APP: ${{ env.STACK }}:${{ env.DEPLOY_SHA }}
IMAGE_TAG_WORKER: ${{ env.WORKER_STACK }}:${{ env.DEPLOY_SHA }}
CANARY_WORKER_REPLICAS: ${{ env.CANARY_WORKER_REPLICAS }}
SENTRY_BUILD_SHA: ${{ env.SENTRY_BUILD_SHA }}
SENTRY_BUILD_NUMBER: ${{ env.SENTRY_BUILD_NUMBER }}
SENTRY_BUILD_TIMESTAMP: ${{ env.SENTRY_BUILD_TIMESTAMP }}
RELEASE_CHANNEL: ${{ env.CHANNEL }}
SENTRY_RELEASE: ${{ format('fluxer-api@{0}', env.SENTRY_BUILD_SHA) }}
run: python3 scripts/ci/workflows/deploy_api.py --step push_and_deploy
-191
View File
@@ -1,191 +0,0 @@
name: deploy app
on:
push:
branches:
- main
- canary
paths:
- fluxer_app/**
- fluxer_app_proxy/**
- .github/workflows/deploy-app.yaml
workflow_dispatch:
inputs:
channel:
type: choice
options:
- stable
- canary
default: stable
description: Release channel to deploy
ref:
type: string
required: false
default: ''
description: Optional git ref (defaults to the triggering branch)
concurrency:
group: deploy-fluxer-app-${{ github.event_name == 'workflow_dispatch' && inputs.channel || (github.ref_name == 'canary' && 'canary') || 'stable' }}
cancel-in-progress: true
permissions:
contents: write
jobs:
channel-vars:
uses: ./.github/workflows/channel-vars.yaml
with:
github_event_name: ${{ github.event_name }}
github_ref_name: ${{ github.ref_name }}
workflow_dispatch_channel: ${{ github.event_name == 'workflow_dispatch' && inputs.channel || '' }}
deploy:
name: Deploy app
needs: channel-vars
runs-on: blacksmith-8vcpu-ubuntu-2404
timeout-minutes: 25
env:
CHANNEL: ${{ needs.channel-vars.outputs.channel }}
IS_CANARY: ${{ needs.channel-vars.outputs.is_canary }}
STACK_SUFFIX: ${{ needs.channel-vars.outputs.stack_suffix }}
SERVICE_NAME: ${{ format('fluxer-app{0}', needs.channel-vars.outputs.stack_suffix) }}
DOCKERFILE: fluxer_app_proxy/Dockerfile
CACHE_SCOPE: ${{ format('fluxer-app{0}', needs.channel-vars.outputs.stack_suffix) }}
RELEASE_CHANNEL: ${{ needs.channel-vars.outputs.channel }}
APP_REPLICAS: ${{ needs.channel-vars.outputs.is_canary == 'true' && 1 || 2 }}
steps:
- uses: actions/checkout@v6
with:
ref: ${{ inputs.ref || '' }}
fetch-depth: 0
- name: Set up pnpm
uses: pnpm/action-setup@v4
- name: Set up Node.js
uses: actions/setup-node@v6
with:
node-version: 24
cache: pnpm
cache-dependency-path: fluxer_app/pnpm-lock.yaml
- name: Install dependencies
run: python3 scripts/ci/workflows/deploy_app.py --step install_dependencies
- name: Run Lingui i18n tasks
run: python3 scripts/ci/workflows/deploy_app.py --step run_lingui
env:
TURBO_API: https://turborepo.fluxer.dev
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_TEAM: team_fluxer
- name: Record deploy commit
run: python3 scripts/ci/workflows/deploy_app.py --step record_deploy_commit
- name: Set up Rust
uses: dtolnay/rust-toolchain@stable
with:
targets: wasm32-unknown-unknown
- name: Cache Rust dependencies
uses: actions/cache@v5
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
fluxer_app/crates/gif_wasm/target/
key: ${{ runner.os }}-cargo-${{ hashFiles('fluxer_app/crates/gif_wasm/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-cargo-
- name: Install wasm-pack
run: python3 scripts/ci/workflows/deploy_app.py --step install_wasm_pack
- name: Generate wasm artifacts
run: python3 scripts/ci/workflows/deploy_app.py --step generate_wasm
env:
TURBO_API: https://turborepo.fluxer.dev
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_TEAM: team_fluxer
- name: Set up SSH agent
uses: webfactory/ssh-agent@v0.9.1
with:
ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY_SERVER }}
- name: Add server to known hosts
run: python3 scripts/ci/workflows/deploy_app.py --step add_known_hosts --server-ip ${{ secrets.SERVER_IP }}
- name: Fetch deployment config
env:
SERVER: ${{ secrets.SERVER_USER }}@${{ secrets.SERVER_IP }}
RELEASE_CHANNEL: ${{ env.RELEASE_CHANNEL }}
run: python3 scripts/ci/workflows/deploy_app.py --step fetch_deployment_config
- name: Build application
env:
FLUXER_CONFIG: ${{ github.workspace }}/fluxer_app/config.json
TURBO_API: https://turborepo.fluxer.dev
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_TEAM: team_fluxer
run: python3 scripts/ci/workflows/deploy_app.py --step build_application
- name: Install rclone
run: python3 scripts/ci/workflows/deploy_app.py --step install_rclone
- name: Upload assets to S3 static bucket
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
run: python3 scripts/ci/workflows/deploy_app.py --step upload_assets
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
- name: Set build timestamp
run: python3 scripts/ci/workflows/deploy_app.py --step set_build_timestamp
- name: Build image
uses: docker/build-push-action@v6
with:
context: .
file: ${{ env.DOCKERFILE }}
tags: ${{ env.SERVICE_NAME }}:${{ env.DEPLOY_SHA }}
load: true
platforms: linux/amd64
cache-from: type=gha,scope=${{ env.CACHE_SCOPE }}
cache-to: type=gha,mode=max,scope=${{ env.CACHE_SCOPE }}
build-args: |
BUILD_SHA=${{ env.DEPLOY_SHA }}
BUILD_NUMBER=${{ github.run_number }}
BUILD_TIMESTAMP=${{ env.BUILD_TIMESTAMP }}
RELEASE_CHANNEL=${{ env.RELEASE_CHANNEL }}
env:
DOCKER_BUILD_SUMMARY: false
DOCKER_BUILD_RECORD_UPLOAD: false
- name: Install docker-pussh
run: python3 scripts/ci/workflows/deploy_app.py --step install_docker_pussh
- name: Push image and deploy
env:
IMAGE_TAG: ${{ env.SERVICE_NAME }}:${{ env.DEPLOY_SHA }}
SERVER: ${{ secrets.SERVER_USER }}@${{ secrets.SERVER_IP }}
SERVICE_NAME: ${{ env.SERVICE_NAME }}
COMPOSE_STACK: ${{ env.SERVICE_NAME }}
RELEASE_CHANNEL: ${{ env.RELEASE_CHANNEL }}
APP_REPLICAS: ${{ env.APP_REPLICAS }}
run: python3 scripts/ci/workflows/deploy_app.py --step push_and_deploy
-62
View File
@@ -1,62 +0,0 @@
name: deploy gateway
on:
workflow_dispatch:
inputs:
ref:
type: string
required: false
default: ''
description: Optional git ref (defaults to the triggering branch)
push:
branches:
- canary
paths:
- 'fluxer_gateway/**'
concurrency:
group: deploy-gateway
cancel-in-progress: true
permissions:
contents: read
jobs:
deploy:
name: Deploy (hot patch)
runs-on: blacksmith-8vcpu-ubuntu-2404
timeout-minutes: 25
steps:
- uses: actions/checkout@v6
with:
ref: ${{ inputs.ref || '' }}
sparse-checkout: |
fluxer_gateway
scripts/ci
- name: Set up Erlang
uses: erlef/setup-beam@v1
with:
otp-version: '28'
rebar3-version: '3.24.0'
- name: Compile
run: python3 scripts/ci/workflows/deploy_gateway.py --step compile
- name: Set up SSH
uses: webfactory/ssh-agent@v0.9.1
with:
ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY_SERVER }}
- name: Add server to known hosts
run: python3 scripts/ci/workflows/deploy_gateway.py --step add_known_hosts --server-ip ${{ secrets.SERVER_IP }}
- name: Record deploy commit
run: python3 scripts/ci/workflows/deploy_gateway.py --step record_deploy_commit
- name: Deploy
env:
SERVER: ${{ secrets.SERVER_USER }}@${{ secrets.SERVER_IP }}
GATEWAY_ADMIN_SECRET: ${{ secrets.GATEWAY_ADMIN_SECRET }}
run: python3 scripts/ci/workflows/deploy_gateway.py --step deploy
-117
View File
@@ -1,117 +0,0 @@
name: deploy marketing
on:
push:
branches:
- main
- canary
paths:
- fluxer_marketing/**
- .github/workflows/deploy-marketing.yaml
workflow_dispatch:
inputs:
channel:
type: choice
options:
- stable
- canary
default: stable
description: Release channel to deploy
ref:
type: string
required: false
default: ''
description: Optional git ref (defaults to the triggering branch)
concurrency:
group: deploy-fluxer-marketing-${{ github.event_name == 'workflow_dispatch' && inputs.channel || (github.ref_name == 'canary' && 'canary') || 'stable' }}
cancel-in-progress: true
permissions:
contents: read
jobs:
channel-vars:
uses: ./.github/workflows/channel-vars.yaml
with:
github_event_name: ${{ github.event_name }}
github_ref_name: ${{ github.ref_name }}
workflow_dispatch_channel: ${{ github.event_name == 'workflow_dispatch' && inputs.channel || '' }}
deploy:
name: Deploy marketing
needs: channel-vars
runs-on: blacksmith-8vcpu-ubuntu-2404
timeout-minutes: 25
env:
CHANNEL: ${{ needs.channel-vars.outputs.channel }}
IS_CANARY: ${{ needs.channel-vars.outputs.is_canary }}
STACK_SUFFIX: ${{ needs.channel-vars.outputs.stack_suffix }}
STACK: ${{ format('fluxer-marketing{0}', needs.channel-vars.outputs.stack_suffix) }}
IMAGE_NAME: ${{ format('fluxer-marketing{0}', needs.channel-vars.outputs.stack_suffix) }}
CACHE_SCOPE: ${{ format('deploy-fluxer-marketing{0}', needs.channel-vars.outputs.stack_suffix) }}
APP_REPLICAS: ${{ needs.channel-vars.outputs.is_canary == 'true' && 1 || 2 }}
CADDY_DOMAIN: ${{ needs.channel-vars.outputs.is_canary == 'true' && 'canary.fluxer.app' || 'fluxer.app' }}
RELEASE_CHANNEL: ${{ needs.channel-vars.outputs.channel }}
steps:
- uses: actions/checkout@v6
with:
ref: ${{ inputs.ref || '' }}
fetch-depth: 0
- name: Record deploy commit
run: python3 scripts/ci/workflows/deploy_marketing.py --step record_deploy_commit
- name: Set build timestamp
run: python3 scripts/ci/workflows/deploy_marketing.py --step set_build_timestamp
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
- name: Build image
uses: docker/build-push-action@v6
with:
context: .
file: fluxer_marketing/Dockerfile
tags: ${{ env.IMAGE_NAME }}:${{ env.DEPLOY_SHA }}
load: true
platforms: linux/amd64
cache-from: type=gha,scope=${{ env.CACHE_SCOPE }}
cache-to: type=gha,mode=max,scope=${{ env.CACHE_SCOPE }}
build-args: |
BUILD_SHA=${{ env.DEPLOY_SHA }}
BUILD_NUMBER=${{ github.run_number }}
BUILD_TIMESTAMP=${{ env.BUILD_TIMESTAMP }}
RELEASE_CHANNEL=${{ env.RELEASE_CHANNEL }}
env:
DOCKER_BUILD_SUMMARY: false
DOCKER_BUILD_RECORD_UPLOAD: false
- name: Install docker-pussh
run: python3 scripts/ci/workflows/deploy_marketing.py --step install_docker_pussh
- name: Set up SSH agent
uses: webfactory/ssh-agent@v0.9.1
with:
ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY_SERVER }}
- name: Add server to known hosts
run: python3 scripts/ci/workflows/deploy_marketing.py --step add_known_hosts --server-ip ${{ secrets.SERVER_IP }}
- name: Push image and deploy
env:
IMAGE_TAG: ${{ env.IMAGE_NAME }}:${{ env.DEPLOY_SHA }}
SERVER: ${{ secrets.SERVER_USER }}@${{ secrets.SERVER_IP }}
STACK: ${{ env.STACK }}
IS_CANARY: ${{ env.IS_CANARY }}
CADDY_DOMAIN: ${{ env.CADDY_DOMAIN }}
RELEASE_CHANNEL: ${{ env.RELEASE_CHANNEL }}
APP_REPLICAS: ${{ env.APP_REPLICAS }}
run: python3 scripts/ci/workflows/deploy_marketing.py --step push_and_deploy
-92
View File
@@ -1,92 +0,0 @@
name: deploy media-proxy
on:
push:
branches:
- main
paths:
- fluxer_media_proxy/**
- .github/workflows/deploy-media-proxy.yaml
workflow_dispatch:
inputs:
ref:
type: string
required: false
default: ''
description: Optional git ref (defaults to the triggering branch)
concurrency:
group: deploy-fluxer-media-proxy
cancel-in-progress: true
permissions:
contents: read
env:
SERVICE_NAME: fluxer-media-proxy
IMAGE_NAME: fluxer-media-proxy
CONTEXT_DIR: fluxer_media_proxy
COMPOSE_STACK: fluxer-media-proxy
jobs:
deploy:
name: Deploy media proxy
runs-on: blacksmith-8vcpu-ubuntu-2404
timeout-minutes: 25
env:
RELEASE_CHANNEL: stable
steps:
- uses: actions/checkout@v6
with:
ref: ${{ inputs.ref || '' }}
- name: Record deploy commit
run: python3 scripts/ci/workflows/deploy_media_proxy.py --step record_deploy_commit
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
- name: Set build timestamp
run: python3 scripts/ci/workflows/deploy_media_proxy.py --step set_build_timestamp
- name: Build image
uses: docker/build-push-action@v6
with:
context: .
file: ${{ env.CONTEXT_DIR }}/Dockerfile
tags: ${{ env.IMAGE_NAME }}:${{ env.DEPLOY_SHA }}
load: true
platforms: linux/amd64
cache-from: type=gha,scope=${{ env.SERVICE_NAME }}
cache-to: type=gha,mode=max,scope=${{ env.SERVICE_NAME }}
build-args: |
BUILD_SHA=${{ env.DEPLOY_SHA }}
BUILD_NUMBER=${{ github.run_number }}
BUILD_TIMESTAMP=${{ env.BUILD_TIMESTAMP }}
RELEASE_CHANNEL=${{ env.RELEASE_CHANNEL }}
env:
DOCKER_BUILD_SUMMARY: false
DOCKER_BUILD_RECORD_UPLOAD: false
- name: Install docker-pussh
run: python3 scripts/ci/workflows/deploy_media_proxy.py --step install_docker_pussh
- name: Set up SSH agent
uses: webfactory/ssh-agent@v0.9.1
with:
ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY_SERVER }}
- name: Add server to known hosts
run: python3 scripts/ci/workflows/deploy_media_proxy.py --step add_known_hosts --server-ip ${{ secrets.SERVER_IP }}
- name: Push image and deploy
env:
IMAGE_TAG: ${{ env.IMAGE_NAME }}:${{ env.DEPLOY_SHA }}
SERVER: ${{ secrets.SERVER_USER }}@${{ secrets.SERVER_IP }}
run: python3 scripts/ci/workflows/deploy_media_proxy.py --step push_and_deploy
@@ -1,91 +0,0 @@
name: deploy relay directory
on:
push:
branches:
- canary
paths:
- fluxer_relay_directory/**
- .github/workflows/deploy-relay-directory.yaml
workflow_dispatch:
inputs:
ref:
type: string
required: false
default: ''
description: Optional git ref (defaults to the triggering branch)
concurrency:
group: deploy-fluxer-relay-directory
cancel-in-progress: true
permissions:
contents: read
jobs:
deploy:
name: Deploy relay directory
runs-on: blacksmith-8vcpu-ubuntu-2404
timeout-minutes: 25
env:
STACK: fluxer-relay-directory
CACHE_SCOPE: deploy-fluxer-relay-directory
IS_CANARY: true
steps:
- uses: actions/checkout@v6
with:
ref: ${{ inputs.ref || '' }}
fetch-depth: 0
- name: Record deploy commit
run: python3 scripts/ci/workflows/deploy_relay_directory.py --step record_deploy_commit
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
- name: Set build timestamp
run: python3 scripts/ci/workflows/deploy_relay_directory.py --step set_build_timestamp
- name: Build image
uses: docker/build-push-action@v6
with:
context: .
file: fluxer_relay_directory/Dockerfile
tags: |
${{ env.STACK }}:${{ env.DEPLOY_SHA }}
load: true
platforms: linux/amd64
cache-from: type=gha,scope=${{ env.CACHE_SCOPE }}
cache-to: type=gha,mode=max,scope=${{ env.CACHE_SCOPE }}
build-args: |
BUILD_SHA=${{ env.DEPLOY_SHA }}
BUILD_NUMBER=${{ github.run_number }}
BUILD_TIMESTAMP=${{ env.BUILD_TIMESTAMP }}
RELEASE_CHANNEL=canary
env:
DOCKER_BUILD_SUMMARY: false
DOCKER_BUILD_RECORD_UPLOAD: false
- name: Install docker-pussh
run: python3 scripts/ci/workflows/deploy_relay_directory.py --step install_docker_pussh
- name: Set up SSH agent
uses: webfactory/ssh-agent@v0.9.1
with:
ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY_SERVER }}
- name: Add server to known hosts
run: python3 scripts/ci/workflows/deploy_relay_directory.py --step add_known_hosts --server-ip ${{ secrets.SERVER_IP }}
- name: Push image and deploy
env:
SERVER: ${{ secrets.SERVER_USER }}@${{ secrets.SERVER_IP }}
IMAGE_TAG: ${{ env.STACK }}:${{ env.DEPLOY_SHA }}
run: python3 scripts/ci/workflows/deploy_relay_directory.py --step push_and_deploy
-62
View File
@@ -1,62 +0,0 @@
name: deploy relay
on:
workflow_dispatch:
inputs:
ref:
type: string
required: false
default: ''
description: Optional git ref (defaults to the triggering branch)
push:
branches:
- canary
paths:
- 'fluxer_relay/**'
concurrency:
group: deploy-relay
cancel-in-progress: true
permissions:
contents: read
jobs:
deploy:
name: Deploy (hot patch)
runs-on: blacksmith-8vcpu-ubuntu-2404
timeout-minutes: 25
steps:
- uses: actions/checkout@v6
with:
ref: ${{ inputs.ref || '' }}
sparse-checkout: |
fluxer_relay
scripts/ci
- name: Set up Erlang
uses: erlef/setup-beam@v1
with:
otp-version: '28'
rebar3-version: '3.24.0'
- name: Compile
run: python3 scripts/ci/workflows/deploy_relay.py --step compile
- name: Set up SSH
uses: webfactory/ssh-agent@v0.9.1
with:
ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY_SERVER }}
- name: Add server to known hosts
run: python3 scripts/ci/workflows/deploy_relay.py --step add_known_hosts --server-ip ${{ secrets.SERVER_IP }}
- name: Record deploy commit
run: python3 scripts/ci/workflows/deploy_relay.py --step record_deploy_commit
- name: Deploy
env:
SERVER: ${{ secrets.SERVER_USER }}@${{ secrets.SERVER_IP }}
RELAY_ADMIN_SECRET: ${{ secrets.RELAY_ADMIN_SECRET }}
run: python3 scripts/ci/workflows/deploy_relay.py --step deploy
+510
View File
@@ -0,0 +1,510 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
name: deploy service
on:
workflow_dispatch:
inputs:
service:
description: "Helm chart name to deploy"
type: choice
required: true
options:
- api
- app-proxy
- admin
- docs
- marketing
- media-proxy
- gateway
- data-service
- messages
- search
- snowflakes
- users
- unfurl
- uploads
- worker
channel:
description: "Release channel (stable or canary)"
type: choice
required: true
options:
- stable
- canary
image-tag:
description: "Docker image tag to deploy (Fluxer CalVer: YYYY.MDD.MICRO)"
type: string
required: true
build-version:
description: "Fluxer CalVer build version to inject into runtime env vars"
type: string
required: false
default: ""
allow-rollback:
description: "Allow deploying an older image tag than the newest GHCR tag"
type: boolean
required: false
default: false
workflow_call:
inputs:
service:
description: "Helm chart name to deploy"
type: string
required: true
channel:
description: "Release channel (stable or canary)"
type: string
required: true
image-tag:
description: "Docker image tag to deploy (Fluxer CalVer: YYYY.MDD.MICRO)"
type: string
required: true
build-version:
description: "Fluxer CalVer build version to inject into runtime env vars"
type: string
required: false
default: ""
allow-rollback:
description: "Allow deploying an older image tag than the newest GHCR tag"
type: boolean
required: false
default: false
secrets:
KUBE_CONFIG:
required: true
GHCR_USERNAME:
required: false
GHCR_TOKEN:
required: false
FLUXER_WEBHOOK_URL:
required: false
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
GHCR_OWNER: ${{ github.repository_owner }}
GHCR_REGISTRY: ghcr.io/${{ github.repository_owner }}
jobs:
deploy:
name: deploy ${{ inputs.service }}
runs-on: ubuntu-24.04
timeout-minutes: 60
environment: ${{ inputs.channel }}
permissions:
contents: read
packages: read
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
env:
GIT_CONFIG_GLOBAL: ${{ runner.temp }}/gitconfig
- name: install helm
uses: azure/setup-helm@bf6a7d304bc2fdb57e0331155b7ebf2c504acf0a
- name: configure kubectl
shell: bash
run: |
mkdir -p "$HOME/.kube"
echo "${{ secrets.KUBE_CONFIG }}" | base64 -d > "$HOME/.kube/config"
chmod 600 "$HOME/.kube/config"
- name: resolve helm args
id: helm
shell: bash
run: |
SERVICE="${{ inputs.service }}"
CHANNEL="${{ inputs.channel }}"
TAG="${{ inputs['image-tag'] }}"
BUILD_VERSION="${{ inputs['build-version'] }}"
GHCR_REGISTRY="${GHCR_REGISTRY:?GHCR_REGISTRY is required}"
if [[ -z "$BUILD_VERSION" ]]; then
BUILD_VERSION="$TAG"
fi
CALVER_RE='^[1-9][0-9]{3}\.[1-9][0-9]{2,3}\.(0|[1-9][0-9]{0,5})$'
if [[ ! "$TAG" =~ $CALVER_RE ]]; then
echo "::error::image-tag must be a Fluxer CalVer tag (YYYY.MDD.MICRO). Channel tags, latest tags, and suffixed tags are not deployable."
exit 1
fi
if [[ ! "$BUILD_VERSION" =~ $CALVER_RE ]]; then
echo "::error::build-version must be a Fluxer CalVer value (YYYY.MDD.MICRO)."
exit 1
fi
CHART_DIR="./deploy/helm/${SERVICE}"
VALUES_ARGS="-f ${CHART_DIR}/values.yaml"
SETS=""
BUILD_PATHS=""
DEPLOY_IMAGE=""
SYNC_WORKER_RELEASE=""
SYNC_WORKER_CHART_DIR=""
SYNC_WORKER_VALUES_ARGS=""
SYNC_WORKER_SETS=""
case "$SERVICE" in
uploads)
if [[ "$CHANNEL" != "stable" ]]; then
echo "::error::uploads deployments are stable-only (single relay serves both channels)."
exit 1
fi
RELEASE="fluxer-uploads"
DEPLOY_IMAGE="fluxer-media-proxy"
SETS="--set-string app.name=uploads --set-string app.image=fluxer-media-proxy --set-string app.tag=${TAG} --set-string app.config=stable"
SETS="${SETS} --set-string app.build.version=${BUILD_VERSION}"
SETS="${SETS} --set-string app.build.channel=stable"
;;
api|app-proxy|admin|docs|marketing)
BASE_IMAGE="fluxer-${SERVICE}"
if [[ "$SERVICE" == "docs" && "$CHANNEL" != "stable" ]]; then
echo "::error::docs deployments are stable-only."
exit 1
fi
if [[ "$CHANNEL" == "canary" ]]; then
NAME="${SERVICE}-canary"
else
NAME="${SERVICE}"
fi
DEPLOY_IMAGE="${BASE_IMAGE}"
RELEASE="fluxer-${SERVICE}-${CHANNEL}"
VALUES_ARGS="${VALUES_ARGS} -f ${CHART_DIR}/values.${CHANNEL}.prod.yaml"
SETS="--set-string app.name=${NAME} --set-string app.image=${DEPLOY_IMAGE} --set-string app.tag=${TAG}"
SETS="${SETS} --set-string app.build.version=${BUILD_VERSION}"
SETS="${SETS} --set-string app.build.channel=${CHANNEL}"
;;
media-proxy)
if [[ "$CHANNEL" != "canary" ]]; then
echo "::error::Media-proxy deployments are only supported on the canary lane."
exit 1
fi
RELEASE="fluxer-${SERVICE}"
DEPLOY_IMAGE="fluxer-media-proxy"
VALUES_ARGS="${VALUES_ARGS} -f ${CHART_DIR}/values.prod.yaml"
SETS="--set-string mediaProxy.image=fluxer-media-proxy --set-string staticProxy.image=fluxer-media-proxy --set-string mediaProxy.tag=${TAG} --set-string staticProxy.tag=${TAG} --set mediaProxy.replicas=16 --set staticProxy.replicas=4 --set-string mediaProxy.nsfwServiceEndpoint=http://int.flx-nyc-misc1.srv.fluxer.dev:8000"
BUILD_PATHS="mediaProxy staticProxy"
;;
gateway)
if [[ "$CHANNEL" != "stable" ]]; then
echo "::error::gateway deployments are stable-only."
exit 1
fi
RELEASE="fluxer-${SERVICE}"
DEPLOY_IMAGE="fluxer-gateway"
VALUES_ARGS="${VALUES_ARGS} -f ${CHART_DIR}/values.prod.yaml"
SETS="--set-string gateway.image=${DEPLOY_IMAGE} --set-string gateway.tag=${TAG}"
BUILD_PATHS="gateway"
;;
data-service)
DEPLOY_IMAGE="fluxer-data-service"
if [[ "$CHANNEL" == "stable" ]]; then
echo "::error::data-service stable in-place upgrades are blocked. Deploy channel=canary to create the standby color, mirror writes and invalidations through API grpc_standby_host_template, then promote shards through grpc_promoted_standby_shards."
exit 1
fi
RELEASE="fluxer-${SERVICE}-canary"
SETS="--set-string dataService.name=data-service-canary --set-string dataService.image=${DEPLOY_IMAGE} --set-string dataService.tag=${TAG} --set-string dataService.configSecret=fluxer-config-shared"
SETS="${SETS} --set-string dataService.rolloutRole=standby --set-string dataService.updateStrategy=RollingUpdate --set rolloutAdmission.enabled=false"
BUILD_PATHS="dataService"
;;
worker)
if [[ "$CHANNEL" != "stable" ]]; then
echo "::error::Worker deployments are only supported on the stable lane."
exit 1
fi
RELEASE="fluxer-${SERVICE}"
DEPLOY_IMAGE="fluxer-api"
VALUES_ARGS="${VALUES_ARGS} -f ${CHART_DIR}/values.prod.yaml"
SETS="--set-string workerRealtime.image=fluxer-api --set-string workerUnfurl.image=fluxer-api --set-string workerLifecycle.image=fluxer-api --set-string workerBatch.image=fluxer-api --set-string workerRealtime.tag=${TAG} --set-string workerUnfurl.tag=${TAG} --set-string workerLifecycle.tag=${TAG} --set-string workerBatch.tag=${TAG}"
BUILD_PATHS="workerRealtime workerUnfurl workerLifecycle workerBatch"
;;
messages|search|snowflakes|users|unfurl)
if [[ "$CHANNEL" != "stable" ]]; then
echo "::error::Shared microservice deployments are stable-only; canary traffic selection is done by the callers."
exit 1
fi
DEPLOY_IMAGE="fluxer-${SERVICE}"
RELEASE="fluxer-${SERVICE}"
VALUES_ARGS="${VALUES_ARGS} -f ${CHART_DIR}/values.prod.yaml"
SETS="--set-string svc.image=${DEPLOY_IMAGE} --set-string svc.tag=${TAG}"
SETS="${SETS} --set-string svc.build.version=${BUILD_VERSION}"
SETS="${SETS} --set-string svc.build.channel=stable"
;;
*)
echo "::error::Unknown service chart: ${SERVICE}"
exit 1
;;
esac
for BUILD_PATH in $BUILD_PATHS; do
SETS="${SETS} --set-string ${BUILD_PATH}.build.version=${BUILD_VERSION}"
SETS="${SETS} --set-string ${BUILD_PATH}.build.channel=${CHANNEL}"
done
SETS="--set-string global.registry=${GHCR_REGISTRY} ${SETS}"
if [[ "$SERVICE" == "api" && "$CHANNEL" == "canary" ]]; then
SYNC_WORKER_RELEASE="fluxer-worker"
SYNC_WORKER_CHART_DIR="./deploy/helm/worker"
SYNC_WORKER_VALUES_ARGS="-f ${SYNC_WORKER_CHART_DIR}/values.yaml -f ${SYNC_WORKER_CHART_DIR}/values.prod.yaml"
SYNC_WORKER_SETS="--set-string workerRealtime.image=fluxer-api --set-string workerUnfurl.image=fluxer-api --set-string workerLifecycle.image=fluxer-api --set-string workerBatch.image=fluxer-api"
SYNC_WORKER_SETS="${SYNC_WORKER_SETS} --set-string workerRealtime.tag=${TAG} --set-string workerUnfurl.tag=${TAG} --set-string workerLifecycle.tag=${TAG} --set-string workerBatch.tag=${TAG}"
for BUILD_PATH in workerRealtime workerUnfurl workerLifecycle workerBatch; do
SYNC_WORKER_SETS="${SYNC_WORKER_SETS} --set-string ${BUILD_PATH}.build.version=${BUILD_VERSION}"
SYNC_WORKER_SETS="${SYNC_WORKER_SETS} --set-string ${BUILD_PATH}.build.channel=${CHANNEL}"
done
SYNC_WORKER_SETS="--set-string global.registry=${GHCR_REGISTRY} ${SYNC_WORKER_SETS}"
fi
{
echo "chart-dir=${CHART_DIR}"
echo "release=${RELEASE}"
echo "values-args=${VALUES_ARGS}"
echo "sets=${SETS}"
echo "deploy-image=${DEPLOY_IMAGE}"
echo "deploy-tag=${TAG}"
echo "sync-worker-release=${SYNC_WORKER_RELEASE}"
echo "sync-worker-chart-dir=${SYNC_WORKER_CHART_DIR}"
echo "sync-worker-values-args=${SYNC_WORKER_VALUES_ARGS}"
echo "sync-worker-sets=${SYNC_WORKER_SETS}"
} >> "$GITHUB_OUTPUT"
- name: helm dependency update
shell: bash
run: |
helm dependency update "${{ steps.helm.outputs.chart-dir }}"
if [[ -n "${{ steps.helm.outputs.sync-worker-chart-dir }}" ]]; then
helm dependency update "${{ steps.helm.outputs.sync-worker-chart-dir }}"
fi
- name: prepare docker config
if: steps.helm.outputs.deploy-image != ''
shell: bash
run: |
echo "DOCKER_CONFIG=${RUNNER_TEMP}/docker-config" >> "$GITHUB_ENV"
mkdir -p "${RUNNER_TEMP}/docker-config"
- name: configure ghcr auth
if: steps.helm.outputs.deploy-image != ''
shell: bash
env:
GHCR_USERNAME: ${{ github.actor }}
GHCR_TOKEN: ${{ github.token }}
run: |
auth="$(printf '%s:%s' "$GHCR_USERNAME" "$GHCR_TOKEN" | base64 | tr -d '\n')"
printf '{"auths":{"ghcr.io":{"auth":"%s"}}}\n' "$auth" > "$DOCKER_CONFIG/config.json"
- name: verify deploy image exists
if: steps.helm.outputs.deploy-image != ''
shell: bash
run: |
IMAGE_REF="${GHCR_REGISTRY}/${{ steps.helm.outputs.deploy-image }}:${{ steps.helm.outputs.deploy-tag }}"
echo "Verifying ${IMAGE_REF}"
docker manifest inspect "${IMAGE_REF}" > /dev/null
env:
DOCKER_CLI_EXPERIMENTAL: enabled
- name: verify api deploy uses latest image
if: ${{ steps.helm.outputs.deploy-image == 'fluxer-api' && !inputs['allow-rollback'] }}
shell: bash
env:
GH_TOKEN: ${{ github.token }}
GHCR_OWNER: ${{ env.GHCR_OWNER }}
DEPLOY_TAG: ${{ steps.helm.outputs.deploy-tag }}
run: |
set -euo pipefail
CALVER_RE='^[1-9][0-9]{3}\.[1-9][0-9]{2,3}\.(0|[1-9][0-9]{0,5})$'
OWNER_TYPE="$(
curl -fsS \
-H "Authorization: Bearer ${GH_TOKEN}" \
-H "Accept: application/vnd.github+json" \
-H "X-GitHub-Api-Version: 2022-11-28" \
"${GITHUB_API_URL:-https://api.github.com}/repos/${GITHUB_REPOSITORY}" \
| jq -r '.owner.type'
)"
case "$OWNER_TYPE" in
Organization) PACKAGE_OWNER_PATH="orgs/${GHCR_OWNER}" ;;
User) PACKAGE_OWNER_PATH="users/${GHCR_OWNER}" ;;
*)
echo "::error::Unsupported GitHub owner type for package lookup: ${OWNER_TYPE}"
exit 1
;;
esac
LATEST_TAG="$(
curl -fsS \
-H "Authorization: Bearer ${GH_TOKEN}" \
-H "Accept: application/vnd.github+json" \
-H "X-GitHub-Api-Version: 2022-11-28" \
"${GITHUB_API_URL:-https://api.github.com}/${PACKAGE_OWNER_PATH}/packages/container/fluxer-api/versions?per_page=100" \
| jq -r --arg re "$CALVER_RE" '
[.[].metadata.container.tags[]? |
select(test($re)) |
{tag: ., parts: (split(".") | map(tonumber))}
] | max_by(.parts) | .tag // empty
'
)"
if [[ -z "$LATEST_TAG" ]]; then
echo "::error::Could not resolve the latest fluxer-api CalVer tag from GHCR."
exit 1
fi
if [[ "$DEPLOY_TAG" != "$LATEST_TAG" ]]; then
echo "::error::Refusing to deploy fluxer-api:${DEPLOY_TAG}; latest GHCR tag is fluxer-api:${LATEST_TAG}. Re-run with allow-rollback=true only for an intentional rollback."
exit 1
fi
- name: approve api image for admission policy
if: ${{ inputs.service == 'api' }}
shell: bash
run: |
DEPLOYMENT="api"
if [[ "${{ inputs.channel }}" == "canary" ]]; then
DEPLOYMENT="api-canary"
fi
IMAGE_REF="${GHCR_REGISTRY}/${{ steps.helm.outputs.deploy-image }}:${{ steps.helm.outputs.deploy-tag }}"
PREVIOUS_IMAGE="$(kubectl -n fluxer get deployment "$DEPLOYMENT" -o jsonpath='{.spec.template.spec.containers[0].image}' 2>/dev/null || true)"
PREVIOUS_TAG=""
if [[ -n "$PREVIOUS_IMAGE" && "$PREVIOUS_IMAGE" != "$IMAGE_REF" && "$PREVIOUS_IMAGE" == *:* ]]; then
PREVIOUS_TAG="${PREVIOUS_IMAGE##*:}"
else
PREVIOUS_IMAGE=""
fi
kubectl -n fluxer create configmap fluxer-api-approved-image \
--from-literal=tag="${{ steps.helm.outputs.deploy-tag }}" \
--from-literal=image="${IMAGE_REF}" \
--from-literal=previousTag="${PREVIOUS_TAG}" \
--from-literal=previousImage="${PREVIOUS_IMAGE}" \
--dry-run=client -o yaml \
| kubectl apply -f -
- name: ensure api admission policy
if: ${{ inputs.service == 'api' }}
shell: bash
run: kubectl apply -f deploy/k8s/fluxer-api-approved-image-policy.yaml
- name: helm upgrade
shell: bash
run: |
RELEASE="${{ steps.helm.outputs.release }}"
CHART_DIR="${{ steps.helm.outputs.chart-dir }}"
VALUES_ARGS="${{ steps.helm.outputs.values-args }}"
SETS="${{ steps.helm.outputs.sets }}"
wait_for_release_idle() {
local release="$1"
local max_checks="$2"
local check=0
local status="unknown"
while (( check < max_checks )); do
check=$((check + 1))
status=$(helm status "$release" -n fluxer -o json 2>/dev/null | jq -r '.info.status // "unknown"' || echo "unknown")
if [[ "$status" != pending-* ]]; then
echo "Release ${release} is ${status}; continuing."
return 0
fi
echo "Release ${release} is ${status}; waiting 10s (${check}/${max_checks})."
sleep 10
done
echo "::warning::Release ${release} still ${status} after ${max_checks} checks; forcing rollback."
if helm rollback "$release" -n fluxer --wait --timeout 5m 2>&1; then
echo "Rollback succeeded; continuing."
return 0
fi
echo "::error::Release ${release} is stuck in ${status} and rollback failed."
return 1
}
helm_upgrade_with_retries() {
local release="$1"
local chart_dir="$2"
local values_args="$3"
local sets="$4"
local values_args_array=()
local sets_array=()
read -r -a values_args_array <<< "$values_args"
read -r -a sets_array <<< "$sets"
wait_for_release_idle "$release" 18
local max_attempts=4
for attempt in $(seq 1 "$max_attempts"); do
echo "Running helm upgrade for ${release}, attempt ${attempt}/${max_attempts}."
set +e
upgrade_output=$(helm upgrade --install "$release" \
"$chart_dir" \
"${values_args_array[@]}" \
-n fluxer \
"${sets_array[@]}" \
--wait --timeout 20m --atomic --history-max 10 2>&1)
exit_code=$?
set -e
printf '%s\n' "$upgrade_output"
if [[ $exit_code -eq 0 ]]; then
return 0
fi
if ! grep -q "another operation (install/upgrade/rollback) is in progress" <<< "$upgrade_output"; then
return "$exit_code"
fi
if [[ $attempt -eq $max_attempts ]]; then
echo "::error::Helm upgrade failed for ${release} after ${max_attempts} attempts because another operation remained in progress."
return "$exit_code"
fi
wait_for_release_idle "$release" 18
done
}
helm_upgrade_with_retries "$RELEASE" "$CHART_DIR" "$VALUES_ARGS" "$SETS"
if [[ -n "${{ steps.helm.outputs.sync-worker-release }}" ]]; then
helm_upgrade_with_retries \
"${{ steps.helm.outputs.sync-worker-release }}" \
"${{ steps.helm.outputs.sync-worker-chart-dir }}" \
"${{ steps.helm.outputs.sync-worker-values-args }}" \
"${{ steps.helm.outputs.sync-worker-sets }}"
fi
- name: seal api admission approved image
if: ${{ success() && inputs.service == 'api' }}
shell: bash
run: |
IMAGE_REF="${GHCR_REGISTRY}/${{ steps.helm.outputs.deploy-image }}:${{ steps.helm.outputs.deploy-tag }}"
kubectl -n fluxer create configmap fluxer-api-approved-image \
--from-literal=tag="${{ steps.helm.outputs.deploy-tag }}" \
--from-literal=image="${IMAGE_REF}" \
--from-literal=previousTag="" \
--from-literal=previousImage="" \
--dry-run=client -o yaml \
| kubectl apply -f -
- name: notify web app canary deploy
if: ${{ success() && inputs.service == 'app-proxy' && inputs.channel == 'canary' }}
shell: bash
env:
FLUXER_WEBHOOK_URL: ${{ secrets.FLUXER_WEBHOOK_URL }}
IMAGE_TAG: ${{ inputs['image-tag'] }}
BUILD_VERSION: ${{ inputs['build-version'] }}
run: |
set -euo pipefail
if [[ -z "${FLUXER_WEBHOOK_URL:-}" ]]; then
echo "FLUXER_WEBHOOK_URL is not set; skipping web app canary deploy notification."
exit 0
fi
web_app_version="${BUILD_VERSION:-$IMAGE_TAG}"
markdown_tick=$(printf '\140')
content=$(printf '## Canary Web App Deployed\n\nWeb app version: %s%s%s' \
"$markdown_tick" "$web_app_version" "$markdown_tick")
if [[ "$IMAGE_TAG" != "$web_app_version" ]]; then
content=$(printf '%s\nContainer image tag: %s%s%s' "$content" "$markdown_tick" "$IMAGE_TAG" "$markdown_tick")
fi
jq -n --arg content "$content" \
'{content: $content, allowed_mentions: {parse: []}}' \
| curl -fsS --retry 3 \
-H 'Content-Type: application/json' \
--data-binary @- \
"$FLUXER_WEBHOOK_URL"
- name: recover stuck release on failure
if: failure() || cancelled()
shell: bash
run: |
RELEASE="${{ steps.helm.outputs.release }}"
for RELEASE in "$RELEASE" "${{ steps.helm.outputs.sync-worker-release }}"; do
if [[ -z "$RELEASE" ]]; then
continue
fi
STATUS=$(helm status "$RELEASE" -n fluxer -o json 2>/dev/null | jq -r '.info.status' 2>/dev/null || echo "unknown")
if [[ "$STATUS" == "pending-upgrade" || "$STATUS" == "pending-install" || "$STATUS" == "pending-rollback" ]]; then
echo "::warning::Release ${RELEASE} stuck in ${STATUS}, rolling back..."
helm rollback "$RELEASE" -n fluxer --wait --timeout 5m || true
fi
done
@@ -1,92 +0,0 @@
name: deploy static-proxy
on:
push:
branches:
- main
paths:
- fluxer_media_proxy/**
- .github/workflows/deploy-static-proxy.yaml
workflow_dispatch:
inputs:
ref:
type: string
required: false
default: ''
description: Optional git ref (defaults to the triggering branch)
concurrency:
group: deploy-fluxer-static-proxy
cancel-in-progress: true
permissions:
contents: read
env:
SERVICE_NAME: fluxer-static-proxy
IMAGE_NAME: fluxer-static-proxy
CONTEXT_DIR: fluxer_media_proxy
COMPOSE_STACK: fluxer-static-proxy
jobs:
deploy:
name: Deploy static proxy
runs-on: blacksmith-8vcpu-ubuntu-2404
timeout-minutes: 25
env:
RELEASE_CHANNEL: stable
steps:
- uses: actions/checkout@v6
with:
ref: ${{ inputs.ref || '' }}
- name: Record deploy commit
run: python3 scripts/ci/workflows/deploy_static_proxy.py --step record_deploy_commit
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
- name: Set build timestamp
run: python3 scripts/ci/workflows/deploy_static_proxy.py --step set_build_timestamp
- name: Build image
uses: docker/build-push-action@v6
with:
context: .
file: ${{ env.CONTEXT_DIR }}/Dockerfile
tags: ${{ env.IMAGE_NAME }}:${{ env.DEPLOY_SHA }}
load: true
platforms: linux/amd64
cache-from: type=gha,scope=${{ env.SERVICE_NAME }}
cache-to: type=gha,mode=max,scope=${{ env.SERVICE_NAME }}
build-args: |
BUILD_SHA=${{ env.DEPLOY_SHA }}
BUILD_NUMBER=${{ github.run_number }}
BUILD_TIMESTAMP=${{ env.BUILD_TIMESTAMP }}
RELEASE_CHANNEL=${{ env.RELEASE_CHANNEL }}
env:
DOCKER_BUILD_SUMMARY: false
DOCKER_BUILD_RECORD_UPLOAD: false
- name: Install docker-pussh
run: python3 scripts/ci/workflows/deploy_static_proxy.py --step install_docker_pussh
- name: Set up SSH agent
uses: webfactory/ssh-agent@v0.9.1
with:
ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY_SERVER }}
- name: Add server to known hosts
run: python3 scripts/ci/workflows/deploy_static_proxy.py --step add_known_hosts --server-ip ${{ secrets.SERVER_IP }}
- name: Push image and deploy
env:
IMAGE_TAG: ${{ env.IMAGE_NAME }}:${{ env.DEPLOY_SHA }}
SERVER: ${{ secrets.SERVER_USER }}@${{ secrets.SERVER_IP }}
run: python3 scripts/ci/workflows/deploy_static_proxy.py --step push_and_deploy
+87
View File
@@ -0,0 +1,87 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
name: i18n source sync
on:
workflow_dispatch:
permissions:
contents: write
pull-requests: write
concurrency:
group: i18n-source-sync
cancel-in-progress: false
defaults:
run:
shell: bash
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
SOURCE_BRANCH: automation/i18n-source-catalogs
GH_TOKEN: ${{ github.token }}
jobs:
sync:
name: refresh catalogs
runs-on: ubuntu-24.04
timeout-minutes: 30
steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
with:
fetch-depth: 0
- name: Set up Rust toolchain
uses: dtolnay/rust-toolchain@b3b07ba8b418998c39fb20f53e8b695cdcc8de1b
with:
toolchain: "1.93.0"
- name: Install pnpm
uses: pnpm/action-setup@c5ba7f7862a0f64c1b1a05fbac13e0b8e86ba08c
- name: Install Node.js
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
with:
node-version: "24"
cache: "pnpm"
- name: Install dependencies
run: >-
cargo run --locked --quiet --manifest-path tools/ci/Cargo.toml -- ci
--step install_dependencies
- name: Refresh source catalogs
run: pnpm i18n:source-sync
- name: Format generated catalogs
run: pnpm biome format --write package.json packages/i18n/package.json packages/i18n/scripts/SyncStaticI18nCatalogs.ts packages/errors/src/i18n fluxer_api/pkgs/email/src/email_i18n fluxer_api/src/api/content_i18n
- name: Open source catalog PR
run: |
set -euo pipefail
if [[ -z "$(git status --porcelain -- fluxer_app/src/features/i18n/locales fluxer_marketing/locales packages/errors/src/i18n fluxer_api/pkgs/email/src/email_i18n fluxer_api/src/api/content_i18n)" ]]; then
echo "No source catalog changes."
exit 0
fi
git config user.name "fluxer-i18n-bot"
git config user.email "github-actions[bot]@users.noreply.github.com"
git switch -c "$SOURCE_BRANCH"
git add fluxer_app/src/features/i18n/locales fluxer_marketing/locales packages/errors/src/i18n fluxer_api/pkgs/email/src/email_i18n fluxer_api/src/api/content_i18n
git commit -m "i18n: refresh source catalogs"
git fetch origin "$SOURCE_BRANCH" || true
git push --force-with-lease="refs/heads/$SOURCE_BRANCH" origin "HEAD:$SOURCE_BRANCH"
pr_number="$(gh pr list --base main --head "$SOURCE_BRANCH" --state open --json number --jq '.[0].number // empty')"
body="$(cat <<'BODY'
Refreshes extracted i18n source catalogs and generated locale artifacts after changes on main.
Weblate will scan these catalog updates after this PR is merged.
BODY
)"
if [[ -z "$pr_number" ]]; then
gh pr create --base main --head "$SOURCE_BRANCH" --title "i18n: refresh source catalogs" --body "$body"
else
gh pr edit "$pr_number" --title "i18n: refresh source catalogs" --body "$body"
fi
+94
View File
@@ -0,0 +1,94 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
name: i18n Weblate PR
on:
workflow_dispatch:
inputs:
branch:
description: "Weblate branch to compile and open as a PR"
required: false
default: "weblate/translations"
permissions:
contents: write
pull-requests: write
concurrency:
group: i18n-weblate-${{ github.event.inputs.branch || github.ref_name }}
cancel-in-progress: false
defaults:
run:
shell: bash
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
GH_TOKEN: ${{ github.token }}
WEBLATE_BRANCH: ${{ github.event.inputs.branch || github.ref_name }}
jobs:
compile:
name: compile Weblate catalogs
runs-on: ubuntu-24.04
timeout-minutes: 30
steps:
- name: Checkout Weblate branch
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
with:
ref: ${{ env.WEBLATE_BRANCH }}
fetch-depth: 0
- name: Set up Rust toolchain
uses: dtolnay/rust-toolchain@b3b07ba8b418998c39fb20f53e8b695cdcc8de1b
with:
toolchain: "1.93.0"
- name: Install pnpm
uses: pnpm/action-setup@c5ba7f7862a0f64c1b1a05fbac13e0b8e86ba08c
- name: Install Node.js
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
with:
node-version: "24"
cache: "pnpm"
- name: Install dependencies
run: >-
cargo run --locked --quiet --manifest-path tools/ci/Cargo.toml -- ci
--step install_dependencies
- name: Compile translated catalogs
run: pnpm i18n:weblate-compile
- name: Format generated catalogs
run: pnpm biome format --write package.json packages/i18n/package.json packages/i18n/scripts/SyncStaticI18nCatalogs.ts packages/errors/src/i18n fluxer_api/pkgs/email/src/email_i18n fluxer_api/src/api/content_i18n
- name: Commit generated artifacts
run: |
set -euo pipefail
if [[ -z "$(git status --porcelain -- fluxer_app/src/features/i18n/locales packages/errors/src/i18n fluxer_api/pkgs/email/src/email_i18n fluxer_api/src/api/content_i18n)" ]]; then
echo "No generated catalog changes."
exit 0
fi
git config user.name "fluxer-i18n-bot"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add fluxer_app/src/features/i18n/locales packages/errors/src/i18n fluxer_api/pkgs/email/src/email_i18n fluxer_api/src/api/content_i18n
git commit -m "i18n: compile Weblate catalogs"
git push origin "HEAD:$WEBLATE_BRANCH"
- name: Open Weblate PR
run: |
set -euo pipefail
pr_number="$(gh pr list --base main --head "$WEBLATE_BRANCH" --state open --json number --jq '.[0].number // empty')"
body="$(cat <<'BODY'
Pulls reviewed translation updates from Weblate and includes generated runtime artifacts.
Weblate project: https://weblate.fluxer.tools/projects/fluxer/
BODY
)"
if [[ -z "$pr_number" ]]; then
gh pr create --base main --head "$WEBLATE_BRANCH" --title "i18n: update translations from Weblate" --body "$body"
else
gh pr edit "$pr_number" --title "i18n: update translations from Weblate" --body "$body"
fi
-67
View File
@@ -1,67 +0,0 @@
name: migrate cassandra
on:
push:
branches:
- canary
paths:
- fluxer_devops/cassandra/migrations/**/*.cql
workflow_dispatch:
concurrency:
group: migrate-cassandra-prod
cancel-in-progress: false
permissions:
contents: read
jobs:
migrate:
name: Run database migrations
runs-on: blacksmith-8vcpu-ubuntu-2404
timeout-minutes: 25
steps:
- uses: actions/checkout@v6
- name: Set up pnpm
uses: pnpm/action-setup@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: 24
cache: pnpm
cache-dependency-path: pnpm-lock.yaml
- name: Install dependencies
run: python3 scripts/ci/workflows/migrate_cassandra.py --step install_dependencies
- name: Validate migrations
run: python3 scripts/ci/workflows/migrate_cassandra.py --step validate_migrations
- name: Set up SSH agent
uses: webfactory/ssh-agent@v0.9.1
with:
ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY_SERVER }}
- name: Add server to known hosts
run: python3 scripts/ci/workflows/migrate_cassandra.py --step add_known_hosts --server-ip ${{ secrets.SERVER_IP }}
- name: Set up SSH tunnel for Cassandra
run: python3 scripts/ci/workflows/migrate_cassandra.py --step setup_tunnel --server-user ${{ secrets.SERVER_USER }} --server-ip ${{ secrets.SERVER_IP }}
- name: Test Cassandra connection
env:
CASSANDRA_USERNAME: ${{ secrets.CASSANDRA_USERNAME }}
CASSANDRA_PASSWORD: ${{ secrets.CASSANDRA_PASSWORD }}
run: python3 scripts/ci/workflows/migrate_cassandra.py --step test_connection
- name: Run migrations
env:
CASSANDRA_USERNAME: ${{ secrets.CASSANDRA_USERNAME }}
CASSANDRA_PASSWORD: ${{ secrets.CASSANDRA_PASSWORD }}
run: python3 scripts/ci/workflows/migrate_cassandra.py --step run_migrations
- name: Close SSH tunnel
if: always()
run: python3 scripts/ci/workflows/migrate_cassandra.py --step close_tunnel
@@ -1,67 +0,0 @@
name: promote canary -> main
on:
workflow_dispatch:
inputs:
dry_run:
type: boolean
default: false
description: "Show what would change, but don't push"
src:
type: string
default: canary
description: 'Source branch'
dst:
type: string
default: main
description: 'Destination branch'
concurrency:
group: promote-${{ inputs.dst }}
cancel-in-progress: false
permissions:
contents: read
jobs:
promote:
runs-on: blacksmith-8vcpu-ubuntu-2404
timeout-minutes: 25
steps:
- name: Create GitHub App token
id: app-token
uses: actions/create-github-app-token@v2
with:
app-id: ${{ secrets.PROMOTE_APP_ID }}
private-key: ${{ secrets.PROMOTE_APP_PRIVATE_KEY }}
- name: Checkout source
uses: actions/checkout@v6
with:
ref: ${{ inputs.src }}
fetch-depth: 0
token: ${{ steps.app-token.outputs.token }}
- name: Verify ff-only + summarize
id: verify
run: >-
python3 scripts/ci/workflows/promote_canary_to_main.py
--step verify
--src "${{ inputs.src }}"
--dst "${{ inputs.dst }}"
- name: Push fast-forward
if: ${{ steps.verify.outputs.ahead != '0' && inputs.dry_run != true }}
run: >-
python3 scripts/ci/workflows/promote_canary_to_main.py
--step push
--dst "${{ inputs.dst }}"
- name: Dry run / no-op
if: ${{ steps.verify.outputs.ahead == '0' || inputs.dry_run == true }}
run: >-
python3 scripts/ci/workflows/promote_canary_to_main.py
--step dry_run
--dry-run "${{ inputs.dry_run }}"
--ahead "${{ steps.verify.outputs.ahead }}"
-151
View File
@@ -1,151 +0,0 @@
name: release livekitctl
on:
push:
tags:
- 'livekitctl-v*'
workflow_dispatch:
inputs:
version:
description: Version to release (e.g., 1.0.0)
required: true
type: string
permissions:
contents: write
concurrency:
group: release-livekitctl
cancel-in-progress: false
env:
GO_VERSION: '1.24'
jobs:
build:
name: Build ${{ matrix.goos }}/${{ matrix.goarch }}
runs-on: blacksmith-8vcpu-ubuntu-2404
timeout-minutes: 25
strategy:
fail-fast: false
matrix:
include:
- goos: linux
goarch: amd64
- goos: linux
goarch: arm64
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: ${{ env.GO_VERSION }}
cache-dependency-path: fluxer_devops/livekitctl/go.sum
- name: Determine version
id: version
run: >-
python3 scripts/ci/workflows/release_livekitctl.py
--step determine_version
--event-name "${{ github.event_name }}"
--input-version "${{ inputs.version }}"
--ref-name "${{ github.ref_name }}"
- name: Build binary
env:
GOOS: ${{ matrix.goos }}
GOARCH: ${{ matrix.goarch }}
CGO_ENABLED: 0
run: >-
python3 ${{ github.workspace }}/scripts/ci/workflows/release_livekitctl.py
--step build_binary
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: livekitctl-${{ matrix.goos }}-${{ matrix.goarch }}
path: fluxer_devops/livekitctl/livekitctl-${{ matrix.goos }}-${{ matrix.goarch }}
retention-days: 1
release:
name: Create release
runs-on: blacksmith-8vcpu-ubuntu-2404
timeout-minutes: 25
needs: build
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Determine version
id: version
run: >-
python3 scripts/ci/workflows/release_livekitctl.py
--step determine_version
--event-name "${{ github.event_name }}"
--input-version "${{ inputs.version }}"
--ref-name "${{ github.ref_name }}"
- name: Download all artifacts
uses: actions/download-artifact@v4
with:
path: artifacts
- name: Prepare release assets
run: >-
python3 ${{ github.workspace }}/scripts/ci/workflows/release_livekitctl.py
--step prepare_release_assets
- name: Generate checksums
run: >-
python3 ${{ github.workspace }}/scripts/ci/workflows/release_livekitctl.py
--step generate_checksums
--release-dir release
- name: Create tag (workflow_dispatch only)
if: github.event_name == 'workflow_dispatch'
run: >-
python3 ${{ github.workspace }}/scripts/ci/workflows/release_livekitctl.py
--step create_tag
--tag "${{ steps.version.outputs.tag }}"
--version "${{ steps.version.outputs.version }}"
- name: Create GitHub release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ steps.version.outputs.tag }}
name: livekitctl v${{ steps.version.outputs.version }}
body: |
## livekitctl v${{ steps.version.outputs.version }}
Self-hosted LiveKit bootstrap and operations CLI.
### Installation
```bash
curl -fsSL https://fluxer.app/get/livekitctl | sudo bash
```
### Manual download
Download the appropriate binary for your system:
- `livekitctl-linux-amd64` - Linux x86_64
- `livekitctl-linux-arm64` - Linux ARM64
Then make it executable and move to your PATH:
```bash
chmod +x livekitctl-linux-*
sudo mv livekitctl-linux-* /usr/local/bin/livekitctl
```
### Checksums
See `checksums.txt` for SHA256 checksums.
files: |
release/livekitctl-linux-amd64
release/livekitctl-linux-arm64
release/checksums.txt
draft: false
prerelease: false
@@ -1,259 +0,0 @@
name: release relay directory
on:
push:
branches: [canary]
paths:
- fluxer_relay_directory/**
- .github/workflows/release-relay-directory.yaml
workflow_dispatch:
inputs:
channel:
description: Release channel
type: choice
options: [stable, nightly]
default: nightly
required: false
ref:
description: Git ref (branch, tag, or commit SHA)
type: string
default: ''
required: false
version:
description: Stable version (e.g. 1.0.0). Defaults to 0.0.<run_number>
type: string
required: false
permissions:
contents: write
packages: write
id-token: write
attestations: write
concurrency:
group: release-relay-directory-${{ (github.event_name == 'workflow_dispatch' && github.event.inputs.channel) || 'nightly' }}
cancel-in-progress: true
defaults:
run:
shell: bash
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository_owner }}/fluxer-relay-directory
CHANNEL: ${{ (github.event_name == 'workflow_dispatch' && github.event.inputs.channel) || 'nightly' }}
SOURCE_REF: >-
${{ (github.event_name == 'workflow_dispatch' && github.event.inputs.ref)
|| ((github.event_name == 'workflow_dispatch' && github.event.inputs.channel == 'stable') && 'main')
|| 'canary' }}
jobs:
meta:
name: resolve build metadata
runs-on: blacksmith-8vcpu-ubuntu-2404
timeout-minutes: 25
outputs:
version: ${{ steps.meta.outputs.version }}
channel: ${{ steps.meta.outputs.channel }}
source_ref: ${{ steps.meta.outputs.source_ref }}
sha_short: ${{ steps.meta.outputs.sha_short }}
timestamp: ${{ steps.meta.outputs.timestamp }}
date: ${{ steps.meta.outputs.date }}
build_number: ${{ steps.meta.outputs.build_number }}
steps:
- name: checkout
uses: actions/checkout@v6
with:
ref: ${{ env.SOURCE_REF }}
- name: metadata
id: meta
run: >-
python3 scripts/ci/workflows/release_relay_directory.py
--step metadata
--version-input "${{ github.event.inputs.version }}"
--channel "${{ env.CHANNEL }}"
--source-ref "${{ env.SOURCE_REF }}"
build:
name: build fluxer relay directory
needs: meta
runs-on: blacksmith-8vcpu-ubuntu-2404
timeout-minutes: 25
outputs:
image_tags: ${{ steps.docker_meta.outputs.tags }}
image_digest: ${{ steps.build.outputs.digest }}
steps:
- name: checkout
uses: actions/checkout@v6
with:
ref: ${{ needs.meta.outputs.source_ref }}
- name: set up buildx
uses: docker/setup-buildx-action@v3
- name: login
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: docker metadata
id: docker_meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=raw,value=nightly,enable=${{ needs.meta.outputs.channel == 'nightly' }}
type=raw,value=nightly-${{ needs.meta.outputs.date }},enable=${{ needs.meta.outputs.channel == 'nightly' }}
type=raw,value=sha-${{ needs.meta.outputs.sha_short }},enable=${{ needs.meta.outputs.channel == 'nightly' }}
type=raw,value=stable,enable=${{ needs.meta.outputs.channel == 'stable' }}
type=raw,value=latest,enable=${{ needs.meta.outputs.channel == 'stable' }}
type=raw,value=v${{ needs.meta.outputs.version }},enable=${{ needs.meta.outputs.channel == 'stable' }}
type=semver,pattern={{version}},value=${{ needs.meta.outputs.version }},enable=${{ needs.meta.outputs.channel == 'stable' && !startsWith(needs.meta.outputs.version, '0.0.') }}
type=semver,pattern={{major}}.{{minor}},value=${{ needs.meta.outputs.version }},enable=${{ needs.meta.outputs.channel == 'stable' && !startsWith(needs.meta.outputs.version, '0.0.') }}
- name: build and push
id: build
uses: docker/build-push-action@v6
with:
context: .
file: fluxer_relay_directory/Dockerfile
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.docker_meta.outputs.tags }}
labels: |
${{ steps.docker_meta.outputs.labels }}
org.opencontainers.image.version=v${{ needs.meta.outputs.version }}
org.opencontainers.image.revision=${{ github.sha }}
org.opencontainers.image.created=${{ needs.meta.outputs.timestamp }}
org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }}
dev.fluxer.build.channel=${{ needs.meta.outputs.channel }}
dev.fluxer.build.number=${{ needs.meta.outputs.build_number }}
dev.fluxer.build.sha=${{ github.sha }}
dev.fluxer.build.short_sha=${{ needs.meta.outputs.sha_short }}
dev.fluxer.build.date=${{ needs.meta.outputs.date }}
build-args: |
BUILD_SHA=${{ github.sha }}
BUILD_NUMBER=${{ needs.meta.outputs.build_number }}
BUILD_TIMESTAMP=${{ needs.meta.outputs.timestamp }}
RELEASE_CHANNEL=${{ needs.meta.outputs.channel }}
cache-from: type=gha,scope=relay-directory-${{ needs.meta.outputs.channel }}
cache-to: type=gha,mode=max,scope=relay-directory-${{ needs.meta.outputs.channel }}
provenance: true
sbom: true
- name: attest
uses: actions/attest-build-provenance@v2
with:
subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
subject-digest: ${{ steps.build.outputs.digest }}
push-to-registry: true
create-release:
name: create release
needs: [meta, build]
if: |
always() &&
needs.meta.outputs.version != '' &&
needs.build.result == 'success'
runs-on: blacksmith-8vcpu-ubuntu-2404
timeout-minutes: 25
steps:
- name: checkout
uses: actions/checkout@v6
with:
ref: ${{ needs.meta.outputs.source_ref }}
- name: stable release
if: needs.meta.outputs.channel == 'stable'
uses: softprops/action-gh-release@v2
with:
tag_name: relay-directory-v${{ needs.meta.outputs.version }}
name: Fluxer Relay Directory v${{ needs.meta.outputs.version }}
draft: false
prerelease: false
generate_release_notes: true
body: |
Fluxer Relay Directory
Pull:
```bash
docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:v${{ needs.meta.outputs.version }}
docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
```
Build:
- version: v${{ needs.meta.outputs.version }}
- build: ${{ needs.meta.outputs.build_number }}
- sha: ${{ github.sha }}
- time: ${{ needs.meta.outputs.timestamp }}
- channel: stable
Docs: https://docs.fluxer.app/federation
- name: nightly release
if: needs.meta.outputs.channel == 'nightly'
uses: softprops/action-gh-release@v2
with:
tag_name: relay-directory-nightly-${{ needs.meta.outputs.date }}-${{ needs.meta.outputs.sha_short }}
name: Relay Directory nightly ${{ needs.meta.outputs.date }} (${{ needs.meta.outputs.sha_short }})
draft: false
prerelease: true
generate_release_notes: true
body: |
Nightly Fluxer Relay Directory image from canary.
Pull:
```bash
docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:nightly
docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:nightly-${{ needs.meta.outputs.date }}
docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:sha-${{ needs.meta.outputs.sha_short }}
```
Build:
- version: v${{ needs.meta.outputs.version }}
- build: ${{ needs.meta.outputs.build_number }}
- sha: ${{ github.sha }}
- time: ${{ needs.meta.outputs.timestamp }}
- channel: nightly
- branch: canary
release-summary:
name: release summary
needs: [meta, build]
if: always()
runs-on: blacksmith-8vcpu-ubuntu-2404
timeout-minutes: 25
steps:
- name: Checkout repository
uses: actions/checkout@v6
with:
sparse-checkout: scripts/ci
sparse-checkout-cone-mode: false
- name: summary
run: >-
python3 scripts/ci/workflows/release_relay_directory.py
--step summary
--build-result "${{ needs.build.result }}"
--channel "${{ needs.meta.outputs.channel }}"
--version "${{ needs.meta.outputs.version }}"
--build-number "${{ needs.meta.outputs.build_number }}"
--sha-short "${{ needs.meta.outputs.sha_short }}"
--timestamp "${{ needs.meta.outputs.timestamp }}"
--date-ymd "${{ needs.meta.outputs.date }}"
--source-ref "${{ needs.meta.outputs.source_ref }}"
--image-tags "${{ needs.build.outputs.image_tags }}"
--image-digest "${{ needs.build.outputs.image_digest }}"
--registry "${{ env.REGISTRY }}"
--image-name "${{ env.IMAGE_NAME }}"
-259
View File
@@ -1,259 +0,0 @@
name: release relay
on:
push:
branches: [canary]
paths:
- fluxer_relay/**
- .github/workflows/release-relay.yaml
workflow_dispatch:
inputs:
channel:
description: Release channel
type: choice
options: [stable, nightly]
default: nightly
required: false
ref:
description: Git ref (branch, tag, or commit SHA)
type: string
default: ''
required: false
version:
description: Stable version (e.g. 1.0.0). Defaults to 0.0.<run_number>
type: string
required: false
permissions:
contents: write
packages: write
id-token: write
attestations: write
concurrency:
group: release-relay-${{ (github.event_name == 'workflow_dispatch' && github.event.inputs.channel) || 'nightly' }}
cancel-in-progress: true
defaults:
run:
shell: bash
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository_owner }}/fluxer-relay
CHANNEL: ${{ (github.event_name == 'workflow_dispatch' && github.event.inputs.channel) || 'nightly' }}
SOURCE_REF: >-
${{ (github.event_name == 'workflow_dispatch' && github.event.inputs.ref)
|| ((github.event_name == 'workflow_dispatch' && github.event.inputs.channel == 'stable') && 'main')
|| 'canary' }}
jobs:
meta:
name: resolve build metadata
runs-on: blacksmith-8vcpu-ubuntu-2404
timeout-minutes: 25
outputs:
version: ${{ steps.meta.outputs.version }}
channel: ${{ steps.meta.outputs.channel }}
source_ref: ${{ steps.meta.outputs.source_ref }}
sha_short: ${{ steps.meta.outputs.sha_short }}
timestamp: ${{ steps.meta.outputs.timestamp }}
date: ${{ steps.meta.outputs.date }}
build_number: ${{ steps.meta.outputs.build_number }}
steps:
- name: checkout
uses: actions/checkout@v6
with:
ref: ${{ env.SOURCE_REF }}
- name: metadata
id: meta
run: >-
python3 scripts/ci/workflows/release_relay.py
--step metadata
--version-input "${{ github.event.inputs.version }}"
--channel "${{ env.CHANNEL }}"
--source-ref "${{ env.SOURCE_REF }}"
build:
name: build fluxer relay
needs: meta
runs-on: blacksmith-8vcpu-ubuntu-2404
timeout-minutes: 25
outputs:
image_tags: ${{ steps.docker_meta.outputs.tags }}
image_digest: ${{ steps.build.outputs.digest }}
steps:
- name: checkout
uses: actions/checkout@v6
with:
ref: ${{ needs.meta.outputs.source_ref }}
- name: set up buildx
uses: docker/setup-buildx-action@v3
- name: login
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: docker metadata
id: docker_meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=raw,value=nightly,enable=${{ needs.meta.outputs.channel == 'nightly' }}
type=raw,value=nightly-${{ needs.meta.outputs.date }},enable=${{ needs.meta.outputs.channel == 'nightly' }}
type=raw,value=sha-${{ needs.meta.outputs.sha_short }},enable=${{ needs.meta.outputs.channel == 'nightly' }}
type=raw,value=stable,enable=${{ needs.meta.outputs.channel == 'stable' }}
type=raw,value=latest,enable=${{ needs.meta.outputs.channel == 'stable' }}
type=raw,value=v${{ needs.meta.outputs.version }},enable=${{ needs.meta.outputs.channel == 'stable' }}
type=semver,pattern={{version}},value=${{ needs.meta.outputs.version }},enable=${{ needs.meta.outputs.channel == 'stable' && !startsWith(needs.meta.outputs.version, '0.0.') }}
type=semver,pattern={{major}}.{{minor}},value=${{ needs.meta.outputs.version }},enable=${{ needs.meta.outputs.channel == 'stable' && !startsWith(needs.meta.outputs.version, '0.0.') }}
- name: build and push
id: build
uses: docker/build-push-action@v6
with:
context: fluxer_relay
file: fluxer_relay/Dockerfile
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.docker_meta.outputs.tags }}
labels: |
${{ steps.docker_meta.outputs.labels }}
org.opencontainers.image.version=v${{ needs.meta.outputs.version }}
org.opencontainers.image.revision=${{ github.sha }}
org.opencontainers.image.created=${{ needs.meta.outputs.timestamp }}
org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }}
dev.fluxer.build.channel=${{ needs.meta.outputs.channel }}
dev.fluxer.build.number=${{ needs.meta.outputs.build_number }}
dev.fluxer.build.sha=${{ github.sha }}
dev.fluxer.build.short_sha=${{ needs.meta.outputs.sha_short }}
dev.fluxer.build.date=${{ needs.meta.outputs.date }}
build-args: |
BUILD_SHA=${{ github.sha }}
BUILD_NUMBER=${{ needs.meta.outputs.build_number }}
BUILD_TIMESTAMP=${{ needs.meta.outputs.timestamp }}
RELEASE_CHANNEL=${{ needs.meta.outputs.channel }}
cache-from: type=gha,scope=relay-${{ needs.meta.outputs.channel }}
cache-to: type=gha,mode=max,scope=relay-${{ needs.meta.outputs.channel }}
provenance: true
sbom: true
- name: attest
uses: actions/attest-build-provenance@v2
with:
subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
subject-digest: ${{ steps.build.outputs.digest }}
push-to-registry: true
create-release:
name: create release
needs: [meta, build]
if: |
always() &&
needs.meta.outputs.version != '' &&
needs.build.result == 'success'
runs-on: blacksmith-8vcpu-ubuntu-2404
timeout-minutes: 25
steps:
- name: checkout
uses: actions/checkout@v6
with:
ref: ${{ needs.meta.outputs.source_ref }}
- name: stable release
if: needs.meta.outputs.channel == 'stable'
uses: softprops/action-gh-release@v2
with:
tag_name: relay-v${{ needs.meta.outputs.version }}
name: Fluxer Relay v${{ needs.meta.outputs.version }}
draft: false
prerelease: false
generate_release_notes: true
body: |
Fluxer Relay
Pull:
```bash
docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:v${{ needs.meta.outputs.version }}
docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
```
Build:
- version: v${{ needs.meta.outputs.version }}
- build: ${{ needs.meta.outputs.build_number }}
- sha: ${{ github.sha }}
- time: ${{ needs.meta.outputs.timestamp }}
- channel: stable
Docs: https://docs.fluxer.app/federation
- name: nightly release
if: needs.meta.outputs.channel == 'nightly'
uses: softprops/action-gh-release@v2
with:
tag_name: relay-nightly-${{ needs.meta.outputs.date }}-${{ needs.meta.outputs.sha_short }}
name: Relay nightly ${{ needs.meta.outputs.date }} (${{ needs.meta.outputs.sha_short }})
draft: false
prerelease: true
generate_release_notes: true
body: |
Nightly Fluxer Relay image from canary.
Pull:
```bash
docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:nightly
docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:nightly-${{ needs.meta.outputs.date }}
docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:sha-${{ needs.meta.outputs.sha_short }}
```
Build:
- version: v${{ needs.meta.outputs.version }}
- build: ${{ needs.meta.outputs.build_number }}
- sha: ${{ github.sha }}
- time: ${{ needs.meta.outputs.timestamp }}
- channel: nightly
- branch: canary
release-summary:
name: release summary
needs: [meta, build]
if: always()
runs-on: blacksmith-8vcpu-ubuntu-2404
timeout-minutes: 25
steps:
- name: Checkout repository
uses: actions/checkout@v6
with:
sparse-checkout: scripts/ci
sparse-checkout-cone-mode: false
- name: summary
run: >-
python3 scripts/ci/workflows/release_relay.py
--step summary
--build-result "${{ needs.build.result }}"
--channel "${{ needs.meta.outputs.channel }}"
--version "${{ needs.meta.outputs.version }}"
--build-number "${{ needs.meta.outputs.build_number }}"
--sha-short "${{ needs.meta.outputs.sha_short }}"
--timestamp "${{ needs.meta.outputs.timestamp }}"
--date-ymd "${{ needs.meta.outputs.date }}"
--source-ref "${{ needs.meta.outputs.source_ref }}"
--image-tags "${{ needs.build.outputs.image_tags }}"
--image-digest "${{ needs.build.outputs.image_digest }}"
--registry "${{ env.REGISTRY }}"
--image-name "${{ env.IMAGE_NAME }}"
-278
View File
@@ -1,278 +0,0 @@
name: release server
on:
push:
branches: [canary]
paths:
- packages/**
- fluxer_server/**
- fluxer_gateway/**
- pnpm-lock.yaml
- .github/workflows/release-server.yaml
workflow_dispatch:
inputs:
channel:
description: Release channel
type: choice
options: [stable, nightly]
default: nightly
required: false
ref:
description: Git ref (branch, tag, or commit SHA)
type: string
default: ''
required: false
version:
description: Stable version (e.g. 1.0.0). Defaults to 0.0.<run_number>
type: string
required: false
build_server:
description: Build Fluxer Server
type: boolean
default: true
required: false
permissions:
contents: write
packages: write
id-token: write
attestations: write
concurrency:
group: release-server-${{ (github.event_name == 'workflow_dispatch' && github.event.inputs.channel) || 'nightly' }}
cancel-in-progress: true
defaults:
run:
shell: bash
env:
REGISTRY: ghcr.io
IMAGE_NAME_SERVER: ${{ github.repository_owner }}/fluxer-server
CHANNEL: ${{ (github.event_name == 'workflow_dispatch' && github.event.inputs.channel) || 'nightly' }}
SOURCE_REF: >-
${{ (github.event_name == 'workflow_dispatch' && github.event.inputs.ref)
|| ((github.event_name == 'workflow_dispatch' && github.event.inputs.channel == 'stable') && 'main')
|| 'canary' }}
jobs:
meta:
name: resolve build metadata
runs-on: blacksmith-8vcpu-ubuntu-2404
timeout-minutes: 25
outputs:
version: ${{ steps.meta.outputs.version }}
channel: ${{ steps.meta.outputs.channel }}
source_ref: ${{ steps.meta.outputs.source_ref }}
sha_short: ${{ steps.meta.outputs.sha_short }}
timestamp: ${{ steps.meta.outputs.timestamp }}
date: ${{ steps.meta.outputs.date }}
build_number: ${{ steps.meta.outputs.build_number }}
build_server: ${{ steps.should_build.outputs.server }}
steps:
- name: checkout
uses: actions/checkout@v6
with:
ref: ${{ env.SOURCE_REF }}
- name: metadata
id: meta
run: >-
python3 scripts/ci/workflows/release_server.py
--step metadata
--version-input "${{ github.event.inputs.version }}"
--channel "${{ env.CHANNEL }}"
--source-ref "${{ env.SOURCE_REF }}"
- name: determine build targets
id: should_build
run: >-
python3 scripts/ci/workflows/release_server.py
--step determine_build_targets
--event-name "${{ github.event_name }}"
--build-server-input "${{ github.event.inputs.build_server }}"
build-server:
name: build fluxer server
needs: meta
if: needs.meta.outputs.build_server == 'true'
runs-on: blacksmith-8vcpu-ubuntu-2404
timeout-minutes: 25
outputs:
image_tags: ${{ steps.docker_meta.outputs.tags }}
image_digest: ${{ steps.build.outputs.digest }}
steps:
- name: checkout
uses: actions/checkout@v6
with:
ref: ${{ needs.meta.outputs.source_ref }}
- name: set up buildx
uses: docker/setup-buildx-action@v3
- name: login
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: docker metadata
id: docker_meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_SERVER }}
tags: |
type=raw,value=nightly,enable=${{ needs.meta.outputs.channel == 'nightly' }}
type=raw,value=nightly-${{ needs.meta.outputs.date }},enable=${{ needs.meta.outputs.channel == 'nightly' }}
type=raw,value=sha-${{ needs.meta.outputs.sha_short }},enable=${{ needs.meta.outputs.channel == 'nightly' }}
type=raw,value=stable,enable=${{ needs.meta.outputs.channel == 'stable' }}
type=raw,value=latest,enable=${{ needs.meta.outputs.channel == 'stable' }}
type=raw,value=v${{ needs.meta.outputs.version }},enable=${{ needs.meta.outputs.channel == 'stable' }}
type=semver,pattern={{version}},value=${{ needs.meta.outputs.version }},enable=${{ needs.meta.outputs.channel == 'stable' && !startsWith(needs.meta.outputs.version, '0.0.') }}
type=semver,pattern={{major}}.{{minor}},value=${{ needs.meta.outputs.version }},enable=${{ needs.meta.outputs.channel == 'stable' && !startsWith(needs.meta.outputs.version, '0.0.') }}
- name: build and push
id: build
uses: docker/build-push-action@v6
with:
context: .
file: fluxer_server/Dockerfile
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.docker_meta.outputs.tags }}
labels: |
${{ steps.docker_meta.outputs.labels }}
org.opencontainers.image.version=v${{ needs.meta.outputs.version }}
org.opencontainers.image.revision=${{ github.sha }}
org.opencontainers.image.created=${{ needs.meta.outputs.timestamp }}
org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }}
dev.fluxer.build.channel=${{ needs.meta.outputs.channel }}
dev.fluxer.build.number=${{ needs.meta.outputs.build_number }}
dev.fluxer.build.sha=${{ github.sha }}
dev.fluxer.build.short_sha=${{ needs.meta.outputs.sha_short }}
dev.fluxer.build.date=${{ needs.meta.outputs.date }}
build-args: |
BUILD_SHA=${{ github.sha }}
BUILD_NUMBER=${{ needs.meta.outputs.build_number }}
BUILD_TIMESTAMP=${{ needs.meta.outputs.timestamp }}
RELEASE_CHANNEL=${{ needs.meta.outputs.channel }}
cache-from: type=gha,scope=server-${{ needs.meta.outputs.channel }}
cache-to: type=gha,mode=max,scope=server-${{ needs.meta.outputs.channel }}
provenance: true
sbom: true
- name: attest
uses: actions/attest-build-provenance@v2
with:
subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_SERVER }}
subject-digest: ${{ steps.build.outputs.digest }}
push-to-registry: true
create-release:
name: create release
needs: [meta, build-server]
if: |
always() &&
needs.meta.outputs.version != '' &&
(needs.build-server.result == 'success' || needs.build-server.result == 'skipped')
runs-on: blacksmith-8vcpu-ubuntu-2404
timeout-minutes: 25
steps:
- name: checkout
uses: actions/checkout@v6
with:
ref: ${{ needs.meta.outputs.source_ref }}
- name: stable release
if: needs.meta.outputs.channel == 'stable'
uses: softprops/action-gh-release@v2
with:
tag_name: v${{ needs.meta.outputs.version }}
name: Fluxer Server v${{ needs.meta.outputs.version }}
draft: false
prerelease: false
generate_release_notes: true
body: |
Fluxer Server
Pull:
```bash
docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_SERVER }}:v${{ needs.meta.outputs.version }}
docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_SERVER }}:latest
```
Build:
- version: v${{ needs.meta.outputs.version }}
- build: ${{ needs.meta.outputs.build_number }}
- sha: ${{ github.sha }}
- time: ${{ needs.meta.outputs.timestamp }}
- channel: stable
Docs: https://docs.fluxer.app/self-hosting
- name: nightly release
if: needs.meta.outputs.channel == 'nightly'
uses: softprops/action-gh-release@v2
with:
tag_name: nightly-${{ needs.meta.outputs.date }}-${{ needs.meta.outputs.sha_short }}
name: Nightly build ${{ needs.meta.outputs.date }} (${{ needs.meta.outputs.sha_short }})
draft: false
prerelease: true
generate_release_notes: true
body: |
Nightly Fluxer Server image from canary.
Pull:
```bash
docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_SERVER }}:nightly
docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_SERVER }}:nightly-${{ needs.meta.outputs.date }}
docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_NAME_SERVER }}:sha-${{ needs.meta.outputs.sha_short }}
```
Build:
- version: v${{ needs.meta.outputs.version }}
- build: ${{ needs.meta.outputs.build_number }}
- sha: ${{ github.sha }}
- time: ${{ needs.meta.outputs.timestamp }}
- channel: nightly
- branch: canary
release-summary:
name: release summary
needs: [meta, build-server]
if: always()
runs-on: blacksmith-8vcpu-ubuntu-2404
timeout-minutes: 25
steps:
- name: Checkout repository
uses: actions/checkout@v6
with:
sparse-checkout: scripts/ci
sparse-checkout-cone-mode: false
- name: summary
run: >-
python3 scripts/ci/workflows/release_server.py
--step summary
--build-result "${{ needs.build-server.result }}"
--channel "${{ needs.meta.outputs.channel }}"
--version "${{ needs.meta.outputs.version }}"
--build-number "${{ needs.meta.outputs.build_number }}"
--sha-short "${{ needs.meta.outputs.sha_short }}"
--timestamp "${{ needs.meta.outputs.timestamp }}"
--date-ymd "${{ needs.meta.outputs.date }}"
--source-ref "${{ needs.meta.outputs.source_ref }}"
--image-tags "${{ needs.build-server.outputs.image_tags }}"
--image-digest "${{ needs.build-server.outputs.image_digest }}"
--registry "${{ env.REGISTRY }}"
--image-name-server "${{ env.IMAGE_NAME_SERVER }}"
@@ -0,0 +1,40 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
name: repair static asset metadata
on:
workflow_dispatch:
inputs:
prefix:
description: "S3 key prefix to repair"
type: string
required: false
default: "assets/"
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
jobs:
repair:
runs-on: ubuntu-24.04
timeout-minutes: 30
permissions:
contents: read
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
STATIC_BUCKET: fluxer-static
S3_ENDPOINT: https://ewr1.vultrobjects.com
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
- name: Set up Rust
uses: dtolnay/rust-toolchain@a02741459ec5e501b9843ed30b535ca0a0376ae4
with:
toolchain: 1.93.0
- name: Repair app asset metadata
run: >-
cargo run --locked --quiet --manifest-path tools/ci/Cargo.toml -- repair-static-asset-metadata
--bucket "${STATIC_BUCKET}"
--prefix "${{ inputs.prefix }}"
-78
View File
@@ -1,78 +0,0 @@
name: restart gateway
on:
workflow_dispatch:
inputs:
confirmation:
description: this will cause service interruption for all users. type RESTART to confirm.
required: true
type: string
concurrency:
group: restart-gateway
cancel-in-progress: true
permissions:
contents: read
env:
SERVICE_NAME: fluxer-gateway
IMAGE_NAME: fluxer-gateway
CONTEXT_DIR: fluxer_gateway
COMPOSE_STACK: fluxer-gateway
RELEASE_CHANNEL: ${{ github.ref_name == 'canary' && 'staging' || 'production' }}
jobs:
restart:
name: Restart gateway
runs-on: blacksmith-8vcpu-ubuntu-2404
timeout-minutes: 25
steps:
- name: Validate confirmation
if: ${{ github.event.inputs.confirmation != 'RESTART' }}
run: python3 scripts/ci/workflows/restart_gateway.py --step validate_confirmation --confirmation "${{ github.event.inputs.confirmation }}"
- uses: actions/checkout@v6
- name: Record deploy commit
run: python3 scripts/ci/workflows/restart_gateway.py --step record_deploy_commit
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
- name: Build image
uses: docker/build-push-action@v6
with:
context: ${{ env.CONTEXT_DIR }}
file: ${{ env.CONTEXT_DIR }}/Dockerfile
tags: ${{ env.IMAGE_NAME }}:${{ env.DEPLOY_SHA }}
load: true
platforms: linux/amd64
cache-from: type=gha,scope=${{ env.SERVICE_NAME }}
cache-to: type=gha,mode=max,scope=${{ env.SERVICE_NAME }}
env:
DOCKER_BUILD_SUMMARY: false
DOCKER_BUILD_RECORD_UPLOAD: false
- name: Install docker-pussh
run: python3 scripts/ci/workflows/restart_gateway.py --step install_docker_pussh
- name: Set up SSH agent
uses: webfactory/ssh-agent@v0.9.1
with:
ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY_SERVER }}
- name: Add server to known hosts
run: python3 scripts/ci/workflows/restart_gateway.py --step add_known_hosts --server-ip ${{ secrets.SERVER_IP }}
- name: Push image and deploy
env:
IMAGE_TAG: ${{ env.IMAGE_NAME }}:${{ env.DEPLOY_SHA }}
SERVER: ${{ secrets.SERVER_USER }}@${{ secrets.SERVER_IP }}
run: python3 scripts/ci/workflows/restart_gateway.py --step push_and_deploy
-102
View File
@@ -1,102 +0,0 @@
name: sync desktop
on:
push:
branches:
- main
- canary
paths:
- 'fluxer_desktop/**'
workflow_dispatch:
inputs:
branch:
description: Branch to sync (main or canary)
required: false
default: ''
type: string
concurrency:
group: sync-desktop-${{ github.ref_name }}
cancel-in-progress: true
permissions:
contents: read
jobs:
sync:
name: Sync to fluxerapp/fluxer_desktop
runs-on: blacksmith-8vcpu-ubuntu-2404
timeout-minutes: 25
steps:
- name: Checkout CI scripts
uses: actions/checkout@v6
with:
sparse-checkout: scripts/ci
sparse-checkout-cone-mode: false
- name: Create GitHub App token
id: app-token
uses: actions/create-github-app-token@v2
with:
app-id: ${{ secrets.SYNC_APP_ID }}
private-key: ${{ secrets.SYNC_APP_PRIVATE_KEY }}
owner: fluxerapp
repositories: fluxer_desktop
- name: Get GitHub App user ID
id: get-user-id
run: >-
python3 scripts/ci/workflows/sync_desktop.py
--step get_user_id
--app-slug "${{ steps.app-token.outputs.app-slug }}"
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
- name: Checkout source repository
uses: actions/checkout@v6
with:
path: source
fetch-depth: 1
- name: Determine target branch
id: branch
run: >-
python3 scripts/ci/workflows/sync_desktop.py
--step determine_branch
--input-branch "${{ inputs.branch }}"
--ref-name "${{ github.ref_name }}"
- name: Clone target repository
run: >-
python3 scripts/ci/workflows/sync_desktop.py
--step clone_target
--token "${{ steps.app-token.outputs.token }}"
- name: Configure git
run: >-
python3 scripts/ci/workflows/sync_desktop.py
--step configure_git
--app-slug "${{ steps.app-token.outputs.app-slug }}"
--user-id "${{ steps.get-user-id.outputs.user-id }}"
- name: Checkout or create target branch
run: >-
python3 scripts/ci/workflows/sync_desktop.py
--step checkout_or_create_branch
--branch-name "${{ steps.branch.outputs.name }}"
- name: Sync files
run: python3 scripts/ci/workflows/sync_desktop.py --step sync_files
- name: Commit and push
run: >-
python3 scripts/ci/workflows/sync_desktop.py
--step commit_and_push
--branch-name "${{ steps.branch.outputs.name }}"
- name: Summary
run: >-
python3 scripts/ci/workflows/sync_desktop.py
--step summary
--branch-name "${{ steps.branch.outputs.name }}"
+41
View File
@@ -0,0 +1,41 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
name: sync static bucket
on:
workflow_dispatch:
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
jobs:
push:
runs-on: ubuntu-24.04
timeout-minutes: 30
permissions:
contents: read
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
STATIC_BUCKET: fluxer-static
S3_ENDPOINT: https://ewr1.vultrobjects.com
S3_WRITE_CONCURRENCY: 8
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
- name: Set up Rust
uses: dtolnay/rust-toolchain@a02741459ec5e501b9843ed30b535ca0a0376ae4
with:
toolchain: 1.93.0
- name: Append static assets to S3
run: >-
cargo run --locked --quiet --manifest-path tools/ci/Cargo.toml -- sync-static-bucket
--source fluxer_static
--bucket "${STATIC_BUCKET}"
- name: Repair app asset metadata
run: >-
cargo run --locked --quiet --manifest-path tools/ci/Cargo.toml -- repair-static-asset-metadata
--bucket "${STATIC_BUCKET}"
--prefix assets/
-42
View File
@@ -1,42 +0,0 @@
name: sync static-bucket
on:
push:
branches:
- main
paths:
- 'fluxer_static/**'
workflow_dispatch:
concurrency:
group: sync-fluxer-static
cancel-in-progress: true
jobs:
push:
runs-on: ubuntu-latest
timeout-minutes: 25
permissions:
contents: read
env:
RCLONE_REMOTE: ovh
RCLONE_BUCKET: fluxer-static
RCLONE_SOURCE: fluxer_static
RCLONE_ENDPOINT: https://s3.us-east-va.io.cloud.ovh.us
RCLONE_REGION: us-east-1
RCLONE_SOURCE_DIR: fluxer_static
steps:
- name: Checkout repository
uses: actions/checkout@v6
with:
lfs: true
fetch-depth: 0
- name: Install rclone
run: python3 scripts/ci/workflows/sync_static.py --step install_rclone
- name: Push repo contents to bucket
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
run: python3 scripts/ci/workflows/sync_static.py --step push
@@ -1,90 +0,0 @@
name: test cassandra-backup
on:
workflow_dispatch:
schedule:
- cron: '0 */2 * * *'
concurrency:
group: test-cassandra-backup
cancel-in-progress: true
permissions:
contents: read
jobs:
test-backup:
name: Test latest Cassandra backup
runs-on: blacksmith-8vcpu-ubuntu-2404
timeout-minutes: 45
env:
CASSANDRA_IMAGE: cassandra:5.0.6
CASS_CONTAINER: cass-${{ github.run_id }}-${{ github.run_attempt }}
UTIL_CONTAINER: cass-util-${{ github.run_id }}-${{ github.run_attempt }}
CASS_VOLUME: cassandra-data-${{ github.run_id }}-${{ github.run_attempt }}
BACKUP_VOLUME: cassandra-backup-${{ github.run_id }}-${{ github.run_attempt }}
MAX_HEAP_SIZE: 2G
HEAP_NEWSIZE: 512M
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Set temp paths
run: >-
python3 scripts/ci/workflows/test_cassandra_backup.py
--step set_temp_paths
- name: Pre-clean
run: >-
python3 scripts/ci/workflows/test_cassandra_backup.py
--step pre_clean
- name: Install tools
run: >-
python3 scripts/ci/workflows/test_cassandra_backup.py
--step install_tools
- name: Find latest backup, validate freshness, download, decrypt, extract into Docker volume
env:
B2_KEY_ID: ${{ secrets.B2_KEY_ID }}
B2_APPLICATION_KEY: ${{ secrets.B2_APPLICATION_KEY }}
AGE_PRIVATE_KEY: ${{ secrets.CASSANDRA_AGE_PRIVATE_KEY }}
run: >-
python3 scripts/ci/workflows/test_cassandra_backup.py
--step fetch_backup
- name: Create data volume
run: >-
python3 scripts/ci/workflows/test_cassandra_backup.py
--step create_data_volume
- name: Restore keyspaces into volume and promote snapshot SSTables
run: >-
python3 scripts/ci/workflows/test_cassandra_backup.py
--step restore_keyspaces
- name: Start Cassandra
run: >-
python3 scripts/ci/workflows/test_cassandra_backup.py
--step start_cassandra
- name: Verify data
run: >-
python3 scripts/ci/workflows/test_cassandra_backup.py
--step verify_data
- name: Cleanup
if: always()
run: >-
python3 scripts/ci/workflows/test_cassandra_backup.py
--step cleanup
- name: Report status
if: always()
env:
JOB_STATUS: ${{ job.status }}
run: >-
python3 scripts/ci/workflows/test_cassandra_backup.py
--step report_status
-57
View File
@@ -1,57 +0,0 @@
name: update word-lists
on:
schedule:
- cron: '0 3 1 * *'
workflow_dispatch:
jobs:
update-word-lists:
runs-on: blacksmith-8vcpu-ubuntu-2404
timeout-minutes: 25
permissions:
contents: write
pull-requests: write
steps:
- name: Checkout repository
uses: actions/checkout@v6
with:
ref: canary
- name: Download latest word lists
run: python3 scripts/ci/workflows/update_word_lists.py --step download
- name: Check for changes
id: check_changes
run: python3 scripts/ci/workflows/update_word_lists.py --step check_changes
- name: Update word lists
if: steps.check_changes.outputs.changes_detected == 'true'
run: python3 scripts/ci/workflows/update_word_lists.py --step update
- name: Create pull request for updated word lists
if: steps.check_changes.outputs.changes_detected == 'true'
uses: peter-evans/create-pull-request@v5
with:
token: ${{ secrets.GITHUB_TOKEN }}
branch: word-lists-update-${{ github.run_id }}
base: canary
title: 'chore: update word lists from Tailscale upstream'
body: |
Automated update of scales.txt and tails.txt from the Tailscale repository.
These files are used to generate connection IDs for voice connections.
Source:
- https://github.com/tailscale/tailscale/blob/main/words/scales.txt
- https://github.com/tailscale/tailscale/blob/main/words/tails.txt
commit-message: 'chore: update word lists from Tailscale upstream'
files: |
fluxer_api/src/words/scales.txt
fluxer_api/src/words/tails.txt
labels: automation
- name: No changes detected
if: steps.check_changes.outputs.changes_detected == 'false'
run: python3 scripts/ci/workflows/update_word_lists.py --step no_changes
+36 -14
View File
@@ -6,7 +6,12 @@
**/*.iml
**/*.log
**/*.o
**/*.node
**/*.plt
**/*.so
**/*.so.*
!fluxer_desktop/native/webrtc-sender/vendor/webrtc-sys/src/lazy_load_deps_for/**/*.so.init.c
!fluxer_desktop/native/webrtc-sender/vendor/webrtc-sys/src/lazy_load_deps_for/**/*.so.tramp.S
**/*.source
**/*.swo
**/*.swp
@@ -41,7 +46,6 @@ devenv.local.nix
**/.rebar3
**/.source
**/.swc
**/.turbo
**/.vercel
**/_build
**/_checkouts
@@ -51,6 +55,7 @@ devenv.local.nix
**/dist
**/ebin
**/erl_crash.dump
/erl_crash.dump
**/fluxer.env
**/generated
**/log
@@ -69,27 +74,44 @@ devenv.local.nix
/.devserver-cache.json
**/.devserver-cache.json
/.fluxer/
/config/config.json
/fluxer_app/src/assets/emoji-sprites/
/fluxer_app/src/components/uikit/AvatarStatusGeometry.ts
/fluxer_app/src/components/uikit/SVGMasks.tsx
/fluxer_app/src/locales/*/messages.js
/fluxer_app/src/locales/*/messages.mjs
/fluxer_app/src/locales/*/messages.ts
/fluxer_admin/public/static/app.css
/scripts/remote/hosts.json
/config/env/local.env
/fluxer_app/src/features/ui/constants/AvatarStatusGeometry.ts
/fluxer_app/src/features/ui/components/SVGMasks.tsx
/fluxer_app/src/features/i18n/locales/*/messages.js
/fluxer_app/src/features/i18n/locales/*/messages.mjs
/fluxer_app/src/features/i18n/locales/*/messages.ts
/fluxer_app/pkgs/libfluxcore/
/fluxer_gateway/config/sys.config
/fluxer_gateway/config/vm.args
/fluxer_marketing/public/static/app.css
/fluxer_server/data/
/packages/admin/public/static/app.css
/packages/marketing/public/static/app.css
/packages/config/src/ConfigSchema.json
/packages/config/src/MasterZodSchema.generated.tsx
AGENTS.md
CLAUDE.md
AGENTS.md
fluxer.yaml
GEMINI.md
geoip_data
tmp/
next-env.d.ts
deploy/kubeconfig/
/deploy/helm/**/Chart.lock
/deploy/helm/**/charts/
.github/agents
.github/prompts
**/public/static/app.css
**/public/static/app.*.css
**/public/static/tailwind.css
**/public/static/tailwind.*.css
**/zig-out/
**/.zig-cache/
fluxer_media_proxy/bench-results/
fluxer_media_proxy/.benchmark-cache/
.claude/
# Generated by tools/ci build-desktop --step set_build_channel
fluxer_desktop/src/common/BuildChannel.ts
# Generated by tools/ci build-markdown-parser-wasm
fluxer_app/src/features/messaging/utils/markdown/parser/MarkdownParserWasmBytes.ts
-3
View File
@@ -1,3 +0,0 @@
[submodule "fluxer_static"]
path = fluxer_static
url = https://github.com/fluxerapp/static.git
+1 -2
View File
@@ -16,6 +16,7 @@
**/*~
**/.*cache
**/.cache
**/.claude
**/__pycache__
**/.dev.vars
**/.direnv
@@ -34,7 +35,6 @@
**/.rebar3
**/.source
**/.swc
**/.turbo
**/.vercel
**/_build
**/_checkouts
@@ -65,7 +65,6 @@
fluxer.yaml
fluxer_app/dist/
/fluxer_marketing/public/static/app.css
/fluxer_server/data/
fluxer_static
geoip_data
livekit.yaml
-1
View File
@@ -1 +0,0 @@
update-notifier=false
-1
View File
@@ -1 +0,0 @@
24
-1
View File
@@ -3,7 +3,6 @@
**/.cache
**/.pnpm-store
**/.swc
**/.turbo
**/node_modules
**/package-lock.json
**/pnpm-lock.yaml
View File
-10
View File
@@ -1,10 +0,0 @@
{
"recommendations": [
"TypeScriptTeam.native-preview",
"biomejs.biome",
"clinyong.vscode-css-modules",
"pgourlain.erlang",
"golang.go",
"rust-lang.rust-analyzer"
]
}
-84
View File
@@ -1,84 +0,0 @@
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Debug: fluxer_server",
"program": "${workspaceFolder}/fluxer_server/src/startServer.tsx",
"runtimeArgs": ["--import", "tsx"],
"cwd": "${workspaceFolder}/fluxer_server",
"env": {
"FLUXER_CONFIG": "${workspaceFolder}/config/config.json",
"FLUXER_DATABASE": "sqlite"
},
"console": "integratedTerminal",
"skipFiles": ["<node_internals>/**", "**/node_modules/**"]
},
{
"type": "node",
"request": "launch",
"name": "Debug: fluxer_api (standalone)",
"program": "${workspaceFolder}/fluxer_api/src/AppEntrypoint.tsx",
"runtimeArgs": ["--import", "tsx"],
"cwd": "${workspaceFolder}/fluxer_api",
"env": {
"FLUXER_CONFIG": "${workspaceFolder}/config/config.json",
"FLUXER_DATABASE": "sqlite"
},
"console": "integratedTerminal",
"skipFiles": ["<node_internals>/**", "**/node_modules/**"]
},
{
"type": "node",
"request": "launch",
"name": "Debug: fluxer_marketing",
"program": "${workspaceFolder}/fluxer_marketing/src/index.tsx",
"runtimeArgs": ["--import", "tsx"],
"cwd": "${workspaceFolder}/fluxer_marketing",
"env": {
"FLUXER_CONFIG": "${workspaceFolder}/config/config.json"
},
"console": "integratedTerminal",
"skipFiles": ["<node_internals>/**", "**/node_modules/**"]
},
{
"type": "node",
"request": "launch",
"name": "Debug: fluxer_app (DevServer)",
"program": "${workspaceFolder}/fluxer_app/scripts/DevServer.tsx",
"runtimeArgs": ["--import", "tsx"],
"cwd": "${workspaceFolder}/fluxer_app",
"env": {
"FLUXER_APP_DEV_PORT": "49427",
"FORCE_COLOR": "1"
},
"console": "integratedTerminal",
"skipFiles": ["<node_internals>/**", "**/node_modules/**"]
},
{
"type": "node",
"request": "launch",
"name": "Debug: Test Current File",
"program": "${workspaceFolder}/node_modules/vitest/vitest.mjs",
"args": ["run", "--no-coverage", "${relativeFile}"],
"autoAttachChildProcesses": true,
"console": "integratedTerminal",
"skipFiles": ["<node_internals>/**", "**/node_modules/**"]
},
{
"type": "node",
"request": "attach",
"name": "Attach to Node Process",
"port": 9229,
"restart": true,
"skipFiles": ["<node_internals>/**", "**/node_modules/**"]
}
],
"compounds": [
{
"name": "Debug: Server + App",
"configurations": ["Debug: fluxer_server", "Debug: fluxer_app (DevServer)"]
}
]
}
-5
View File
@@ -1,5 +0,0 @@
{
"typescript.preferences.includePackageJsonAutoImports": "auto",
"typescript.suggest.autoImports": true,
"typescript.experimental.useTsgo": true
}
+4 -145
View File
@@ -1,148 +1,7 @@
# Contributing to Fluxer
# Contributing
Thanks for contributing. This document explains how we work so your changes can land smoothly and nobody wastes time on work we can't merge.
Understand every change in your PR. You should be able to explain what it does and why it is correct.
## Quick rules (please read)
Keep AI-generated text out of bug reports, pull request descriptions, and GitHub comments, except for direct translation if English is not your native language.
### 1) All PRs must target `canary`
`canary` is our trunk branch. Open all pull requests against `canary`. PRs targeting other branches will be closed or retargeted.
### 2) All PRs must include a short description
Every PR must include a short description covering:
- what changed
- why it changed
- anything reviewers should pay attention to
A few bullets is fine.
### 3) Open an issue before submitting a PR
We strongly prefer that every PR addresses an existing issue. If one doesn't exist yet, open one describing the problem or improvement and your proposed approach. This gives maintainers a chance to weigh in on direction before you invest time, and avoids the mutual displeasure of:
- you doing significant work, and
- us having to reject or postpone the change because it doesn't align with current goals, or because we aren't ready to maintain what it introduces
For small, obvious fixes (typos, broken links, trivial one-liners) you can skip the issue and go straight to a PR.
Ways to coordinate on larger work:
- open an issue describing the problem and your proposed approach
- open a draft PR early to confirm direction
- discuss with a maintainer in any channel you already share
If you're unsure whether something needs an issue first, it probably does.
### 4) Understand the code you submit
You should understand every change in your PR well enough to explain and defend it during review. You dont need to write an essay, but you should be able to give a brief summary of what the patch does and why its correct. You may not use AI to generate a bug report, pull request description, or GitHub comment in any form, except for a 1:1 translation if English isn't your native language.
The maintainer [uses LLMs in a limited capacity](https://blog.fluxer.app/how-i-built-fluxer-a-discord-like-chat-app/#:~:text=The%20LLMephant%20in%20the%20room). Thats how he was able to build the final version of Fluxer largely on his own over five years, with help from a supportive group of early testers. Without limited, controlled LLM use, he likely would have needed more starting capital to achieve the same result and hire a team of engineers.
If you use LLMs, use them responsibly. They can be helpful for rubber-ducking and for scaffolding boilerplate from thorough specifications, detailed guidance, and test coverage that verifies behaviour rather than implementation. This kind of platform cannot be built via autonomous code generation. Please disclose any LLM usage in your contribution.
We also ask contributors to treat each other with respect on this topic. People hold a wide range of views on LLMs, often rooted in ethical conviction. A contribution that is reviewable, understandable, and properly tested should be evaluated on its merits.
## Workflow
1. Fork the repo (or create a branch if you have access).
2. Create a feature branch from `canary`.
3. Make changes.
4. Open a PR into `canary` with a short description.
5. Address review feedback and CI results.
6. We squash-merge approved PRs into `canary`.
We strongly prefer small, focused PRs that are easy to review.
### Commit style and history
We squash-merge PRs, so the PR title becomes the single commit message on `canary`. For that reason:
- PR titles must follow Conventional Commits.
- Individual commits inside the PR don't need to follow Conventional Commits.
If you like to commit in small increments, feel free. If you prefer a tidier PR history, force-pushes are welcome (for example, to squash or reorder commits before review). Just avoid rewriting history in a way that makes it hard for reviewers to follow along.
## Conventional Commits (required for PR titles)
Because the PR title becomes the squash commit message, we require Conventional Commits for PR titles.
We prefer type/subject to be mostly lowercase.
Format:
- `type(scope optional): short description`
Examples:
- `fix: handle empty response from api`
- `feat(auth): add passkey login`
- `docs: clarify canary workflow`
- `refactor: simplify retry logic`
- `chore(ci): speed up checks`
Breaking changes:
- `feat!: remove legacy auth endpoints`
- `refactor(api)!: change pagination shape`
Common types:
`feat`, `fix`, `docs`, `refactor`, `perf`, `test`, `chore`, `ci`, `build`, `revert`
## Tests (guidance)
We care about confidence more than ceremony. Add tests when they provide real value.
### Backend changes
For backend changes, add a unit test.
- If a unit test would require heavy mocking to be meaningful, restructure the code so it can be tested cleanly through its interfaces.
- If you're unsure how to approach this, discuss it with a maintainer before investing time.
### Frontend changes
We don't generally encourage new unit tests for frontend code unless:
- the area already has unit tests, or
- the change is complex or sensitive, and a unit test clearly reduces risk
In most cases, clear PR notes and practical verification are more valuable.
## Formatting and linting
Don't block on formatting or linting before opening a PR. CI enforces required checks and will tell you what needs fixing before merge.
Open the PR when it's ready for review, then iterate based on CI and feedback.
## PR checklist
Before requesting review:
- [ ] PR targets `canary`
- [ ] PR title follows Conventional Commits (mostly lowercase)
- [ ] PR includes a short description of what/why
- [ ] You understand every change in the PR and can explain it during review
- [ ] Tests added or updated where it makes sense (especially backend changes)
- [ ] CI is green (or you're actively addressing failures)
Optional but helpful:
- screenshots or a short recording for UI changes
- manual verification steps
## Code of Conduct
This project follows a Code of Conduct. By participating, you're expected to uphold it:
- See [`CODE_OF_CONDUCT.md`](./CODE_OF_CONDUCT.md)
## Security
Please don't report security issues via public GitHub issues.
Use our security policy and reporting instructions here:
- https://fluxer.app/security
If you use LLMs for coding help, disclose it. The contribution still needs to be understandable, reviewable, and tested well.
Generated
+6732
View File
File diff suppressed because it is too large Load Diff
+29
View File
@@ -0,0 +1,29 @@
[workspace]
members = [
"fluxer_admin",
"fluxer_app_proxy",
"fluxer_common",
"fluxer_marketing",
"fluxer_media_proxy",
"fluxer_svc",
"fluxer_messages",
"fluxer_snowflakes",
"tools/ci",
"tools/content/update-frozen-snapshot",
"tools/dev",
"tools/i18n_auto",
"tools/marketing/update-gettext-catalogs",
"fluxer_users",
"fluxer_unfurl",
"packages/markdown_parser/rust",
]
exclude = [
"packages/markdown_parser/rust/fuzz",
"fluxer_desktop/native/webrtc-sender/vendor/tract-linalg-0.19.16",
"fluxer_desktop/native/webrtc-sender/vendor/tract-linalg-0.23.1",
]
resolver = "2"
[workspace.package]
edition = "2024"
license = "AGPL-3.0-or-later"
-43
View File
@@ -1,43 +0,0 @@
# Licensing
Fluxer is licensed under the **GNU Affero General Public License v3.0 (AGPLv3)**. See [`LICENSE`](./LICENSE).
AGPLv3 is a strong copyleft licence designed to keep improvements available to the community, including when the software is used over a network.
## Self-hosting: fully unlocked
If you self-host Fluxer on your own hardware, all features are available by default. We don't charge to unlock functionality, remove limits, or increase instance caps for deployments you run yourself.
If Fluxer is useful to you, please consider [donating to support development](https://fluxer.app/donate).
## Commercial licensing
Some organisations can't use AGPLv3 due to policy or compliance requirements, or because they don't want to take on AGPL obligations for private modifications.
In these cases, Fluxer Platform AB can offer Fluxer under a separate commercial licence (sometimes called dual licensing). This is the same software, but the commercial terms remove AGPLv3's copyleft obligations for internal deployments.
Fluxer remains AGPLv3 and publicly available. The only difference is your obligations for private modifications. Under the commercial licence, you may keep internal modifications private rather than being required to publish them solely because you run the modified software.
A core requirement of the commercial licence is internal use only. You may not redistribute a modified version (or your modifications) to third parties under the commercial licence.
If you want to share changes, you can upstream them to this repository under Fluxer's AGPLv3 licence. The commercial licence makes upstreaming optional rather than required, but it doesn't grant permission to distribute modifications under any other licence.
To request a commercial licence, email [support@fluxer.app](mailto:support@fluxer.app) and include your employee count so we can provide an initial estimate. Commercial licences are offered at a custom price point.
## Contributor License Agreement
Code contributions require a signed contributor licence agreement: see [`CLA.md`](./CLA.md). You will be prompted to sign electronically via CLA Assistant when you open your first pull request.
Our CLA is based on the widely used Harmony Individual CLA. It is intended to be clear and fair:
- You keep ownership of your contribution and can still use it elsewhere.
- You grant Fluxer Platform AB the rights needed to distribute your contribution as part of Fluxer, including a patent licence to reduce patent-related risk for users.
- It includes standard warranty and liability disclaimers that protect contributors.
It also includes an outbound licensing clause. If Fluxer Platform AB relicenses your contribution (including commercially), Fluxer Platform AB will continue to license your contribution under the project licence(s) that applied when you contributed. Signing the CLA doesn't remove Fluxer from the community.
## Our FOSS commitment
Fluxer is committed to remaining 100% FOSS for public development and distribution.
The CLA doesn't change that. It ensures Fluxer Platform AB has the legal permission to offer a commercial licence to organisations that need different terms, while keeping the community version open, fully featured, and AGPLv3-licensed.
+12 -166
View File
@@ -1,29 +1,20 @@
> [!CAUTION]
> I'm repeating it again: Holy smokes, what a ride. Fluxer is taking off much earlier than I'd expected.
> As of this writing (15 June 2026), we are working to finalise the API and self-hosting documentation over the next few days.
>
> I know it's hard to resist, but please wait a little longer before you dive deep into the current codebase or try to set up self-hosting. I'm aware the current stack isn't very lightweight. I'm working on making self-hosting as straightforward as possible and the development environment likewise.
> We apologise for the brief delay in open-source releases. We paused after spam waves created safety concerns while we built out Fluxer's trust and safety infrastructure. During that same stretch, we have been fixing hundreds of bugs, adding new features, and preparing a much improved audio and video system.
>
> Self-hosted deployments won't include any traces of Plutonium, and nothing is paywalled. You can still configure your own tiers and limits in the admin panel.
>
> Thanks for bearing with me. Development on Fluxer is about to get much easier, and the project will be made sustainable through community contributions and bounties for development work. Stay tuned there's not much left now.
>
> I thought I could take it a bit easier while shipping this stabilising update, but Discord's announcement in Februrary has changed things.
>
> There's just been a lot of work involved in keeping the production deployment up and running, handling trust & safety concerns, answering support emails, handling billing issues, and working on the refactor at the same time. I'm really excited to open up development and make it easier for others to contribute, and I can't wait to see what the community builds on Fluxer!
>
> As soon as the refactor is ready (not much longer now!), I'll enable PRs and interact more actively and push updates to this repository more frequently. The remaining parts of the refactor are currently being worked on and being tested live in production that has over 125,000 users (and we're only two full-time employees for now). After that, all work will happen openly in public.
>
> The team is also growing, though we remain small and can't offer very competitive salaries just yet but if you want to work part-time or contract on projects, or you think you're a great fit for the roles we're hiring for (though not as actively across all roles at this time, but we'll keep you on file for when we are), check out the [careers page](https://fluxer.app/careers) :D
>
> ❤️
> You can already try that work in the Fluxer Canary client: [download Canary](https://canary.fluxer.app/download) or [open Canary on the web](https://web.canary.fluxer.app). The latest stable client remains out of date for now, but over the coming weeks we are finalising the remaining work needed to stabilise the current latest code out in the open.
> [!NOTE]
> Learn about the developer behind Fluxer, the goals of the project, the tech stack, and what's coming next.
>
> [Read the launch blog post](https://blog.fluxer.app/how-i-built-fluxer-a-discord-like-chat-app/) · [View full roadmap](https://blog.fluxer.app/roadmap-2026/)
> [Read the launch blog post](https://blog.fluxer.app/how-i-built-fluxer-a-discord-like-chat-app/) | [View full roadmap](https://blog.fluxer.app/roadmap-2026/)
<p align="center">
<img src="./media/logo-graphic.png" alt="Fluxer graphic logo" width="400">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="./fluxer_static/marketing/branding/logo-white.svg">
<img src="./fluxer_static/marketing/branding/logo-color.svg" alt="Fluxer logo" width="400">
</picture>
</p>
<p align="center">
@@ -37,153 +28,8 @@
# Fluxer
Fluxer is a **free and open source instant messaging and VoIP platform** for friends, groups, and communities. Self-host it and every feature is unlocked.
Fluxer is a free and open source instant messaging and VoIP chat app built for friends, groups, and communities.
## Quick links
- [Self-hosting guide](https://docs.fluxer.app/self-hosting)
- [Documentation](https://docs.fluxer.app)
- [Donate to support development](https://fluxer.app/donate)
- [Security](https://fluxer.app/security)
## Features
<img src="./media/app-showcase.png" alt="Fluxer showcase" align="right" width="45%" />
**Real-time messaging** typing indicators, reactions, and threaded replies.
**Voice & video** calls in communities and DMs with screen sharing, powered by LiveKit.
**Rich media** link previews, image and video attachments, and GIF search via KLIPY.
**Communities and channels** text and voice channels organised into categories with granular permissions.
**Custom expressions** upload custom emojis and stickers for your community.
**Self-hostable** run your own instance with full control of your data and no vendor lock-in.
> [!NOTE]
> Native mobile apps and federation are top priorities. If you'd like to support this work, [donations](https://fluxer.app/donate) are greatly appreciated. You can also share feedback by emailing developers@fluxer.app.
## Self-hosting
> [!NOTE]
> New to Fluxer? Follow the [self-hosting guide](https://docs.fluxer.app/self-hosting) for step-by-step setup instructions.
TBD
### Deployment helpers
- [`livekitctl`](./fluxer_devops/livekitctl/README.md) bootstrap a LiveKit SFU for voice and video
## Development
### Tech stack
- [TypeScript](https://www.typescriptlang.org/) and [Node.js](https://nodejs.org/) for backend services
- [Hono](https://hono.dev/) as the web framework for all HTTP services
- [Erlang/OTP](https://www.erlang.org/) for the real-time WebSocket gateway (message routing and presence)
- [React](https://react.dev/) and [Electron](https://www.electronjs.org/) for the desktop and web client
- [Rust](https://www.rust-lang.org/) compiled to WebAssembly for performance-critical client code
- [SQLite](https://www.sqlite.org/) for storage by default, with optional [Cassandra](https://cassandra.apache.org/) for distributed deployments
- [Meilisearch](https://www.meilisearch.com/) for full-text search and indexing
- [Valkey](https://valkey.io/) (Redis-compatible) for caching, rate limiting, and ephemeral coordination
- [LiveKit](https://livekit.io/) for voice and video infrastructure
### Devenv development environment
Fluxer supports development through **devenv** only. It provides a reproducible Nix environment and a single, declarative process manager for the dev stack.
1. Install Nix and devenv using the [devenv getting started guide](https://devenv.sh/getting-started/).
2. Enter the environment:
```bash
devenv shell
```
If you use direnv, the repo includes a `.envrc` that loads devenv automatically run `direnv allow` once.
### Getting started
Start all services and the development server with:
```bash
devenv up
```
Open the instance in a browser at your dev server URL (e.g. `http://localhost:48763/`).
Emails sent during development (verification codes, notifications, etc.) are captured by a local Mailpit instance. Access the inbox at your dev server URL + `/mailpit/` (e.g. `http://localhost:48763/mailpit/`).
### Voice on a remote VM
If you develop on a remote VM behind Cloudflare Tunnels (or a similar HTTP-only tunnel), voice and video won't work out of the box. Cloudflare Tunnels only proxy HTTP/WebSocket traffic, so WebRTC media transport needs a direct path to the server. Open these ports on the VM's firewall:
| Port | Protocol | Purpose |
| ----------- | -------- | ---------------- |
| 3478 | UDP | TURN/STUN |
| 7881 | TCP | ICE-TCP fallback |
| 50000-50100 | UDP | RTP/RTCP media |
The bootstrap script configures LiveKit automatically based on `domain.base_domain` in your `config.json`. When set to a non-localhost domain, it enables external IP discovery so clients can connect directly for media while signaling continues through the tunnel.
### Devcontainer (experimental)
There is experimental support for developing in a **VS Code Dev Container** / GitHub Codespace without Nix. The `.devcontainer/` directory provides a Docker Compose setup with all required tooling and backing services.
```bash
# Inside the dev container, start all processes:
process-compose -f .devcontainer/process-compose.yml up
```
Open the app at `http://localhost:48763` and the dev email inbox at `http://localhost:48763/mailpit/`. Predefined VS Code debugging targets are available in `.vscode/launch.json`.
> [!WARNING]
> Bluesky OAuth is disabled in the devcontainer because it requires HTTPS. All other features work normally.
### Documentation
To develop the documentation site with live preview:
```bash
pnpm dev:docs
```
## Contributing
Fluxer is **free and open source software** licensed under **AGPLv3**. Contributions are welcome.
See [`CONTRIBUTING.md`](./CONTRIBUTING.md) for development processes and how to propose changes, and [`CODE_OF_CONDUCT.md`](./CODE_OF_CONDUCT.md) for community guidelines.
## Security
Report vulnerabilities at [fluxer.app/security](https://fluxer.app/security). Do not use public issues for security reports.
<details>
<summary><strong>License</strong></summary>
<br>
Copyright (c) 2026 Fluxer Contributors
Licensed under the [GNU Affero General Public License v3](./LICENSE):
```text
Copyright (c) 2026 Fluxer Contributors
This program is free software: you can redistribute it and/or modify it under
the terms of the GNU Affero General Public License as published by the Free
Software Foundation, either version 3 of the License, or (at your option) any
later version.
This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
details.
You should have received a copy of the GNU Affero General Public License along
with this program. If not, see https://www.gnu.org/licenses/
```
See [`LICENSING.md`](./LICENSING.md) for details on commercial licensing and the CLA.
</details>
<p align="center">
<img src="./fluxer_static/marketing/screenshots/desktop-1920w.png" alt="Fluxer app showcase" width="900">
</p>
+3 -3
View File
@@ -1,5 +1,5 @@
# Security Policy
# Security
Please **do not** report security vulnerabilities via public GitHub issues.
Please report security issues through the Fluxer security page:
Report security issues here: https://fluxer.app/security
https://fluxer.app/security
+13 -7
View File
@@ -6,7 +6,8 @@
"indentStyle": "tab",
"lineWidth": 120,
"lineEnding": "lf",
"bracketSpacing": false
"bracketSpacing": false,
"includes": ["**", "!**/fluxer_app/src/features/theme/variables/ThemeVariableManifest.ts"]
},
"javascript": {
"formatter": {
@@ -128,18 +129,23 @@
"!**/build",
"fluxer_app/scripts/build",
"!**/dist",
"!**/fluxer_app/src/data/emojis.json",
"!**/fluxer_app/src/locales/*/messages.js",
"!**/fluxer_app/src/media/data/emojis.json",
"!**/fluxer_app/src/features/i18n/locales/*/messages.js",
"!**/fluxer_app/src/features/i18n/locales/*/messages.mjs",
"!**/fluxer_app/src/env.d.ts",
"!**/*.generated.*",
"!**/*.pb.*",
"!**/*_pb.*",
"!**/node_modules",
"!packages/schema/src/gen",
"!**/tailwind.css",
"!**/*.html",
"!**/*.module.css.d.ts",
"!**/fluxer_app/src/components/uikit/SVGMasks.tsx",
"!fluxer_marketing/public/static/app.css",
"!packages/marketing/public/static/app.css",
"!**/fluxer_app/src/features/ui/components/SVGMasks.tsx",
"!fluxer_static",
"!fluxer_docs/api-reference/openapi.json"
"!fluxer_admin/static/htmx.min.js",
"!fluxer_marketing/static/htmx.min.js",
"!fluxer_api/src/api/openapi/openapi.json"
],
"ignoreUnknown": true
}
-112
View File
@@ -1,112 +0,0 @@
x-logging: &default-logging
driver: json-file
options:
max-size: '10m'
max-file: '5'
services:
valkey:
image: valkey/valkey:8.0.6-alpine
container_name: valkey
restart: unless-stopped
command: ['valkey-server', '--appendonly', 'yes', '--save', '60', '1', '--loglevel', 'warning']
volumes:
- valkey_data:/data
healthcheck:
test: ['CMD', 'valkey-cli', 'ping']
interval: 10s
timeout: 5s
retries: 5
logging: *default-logging
fluxer_server:
image: ${FLUXER_SERVER_IMAGE:-ghcr.io/fluxerapp/fluxer-server:stable}
container_name: fluxer_server
restart: unless-stopped
init: true
environment:
FLUXER_CONFIG: /usr/src/app/config/config.json
NODE_ENV: production
ports:
- '${FLUXER_HTTP_PORT:-8080}:8080'
depends_on:
valkey:
condition: service_healthy
volumes:
- ./config:/usr/src/app/config:ro
- fluxer_data:/usr/src/app/data
healthcheck:
test: ['CMD-SHELL', 'curl -fsS http://127.0.0.1:8080/_health || exit 1']
interval: 15s
timeout: 5s
retries: 5
start_period: 15s
logging: *default-logging
meilisearch:
image: getmeili/meilisearch:v1.14
container_name: meilisearch
profiles: ['search']
restart: unless-stopped
environment:
MEILI_ENV: production
MEILI_MASTER_KEY: ${MEILI_MASTER_KEY:?Set MEILI_MASTER_KEY in .env or environment}
MEILI_DB_PATH: /meili_data
MEILI_HTTP_ADDR: 0.0.0.0:7700
ports:
- '${MEILI_PORT:-7700}:7700'
volumes:
- meilisearch_data:/meili_data
healthcheck:
test: ['CMD-SHELL', 'curl -fsS http://127.0.0.1:7700/health || exit 1']
interval: 15s
timeout: 5s
retries: 5
logging: *default-logging
elasticsearch:
image: elasticsearch:8.19.11
container_name: elasticsearch
profiles: ['search']
restart: unless-stopped
environment:
discovery.type: single-node
xpack.security.enabled: 'false'
xpack.security.http.ssl.enabled: 'false'
ES_JAVA_OPTS: '-Xms512m -Xmx512m'
ports:
- '${ELASTICSEARCH_PORT:-9200}:9200'
volumes:
- elasticsearch_data:/usr/share/elasticsearch/data
healthcheck:
test: ['CMD-SHELL', 'curl -fsS http://127.0.0.1:9200/_cluster/health || exit 1']
interval: 15s
timeout: 5s
retries: 5
logging: *default-logging
livekit:
image: livekit/livekit-server:v1.9.11
container_name: livekit
profiles: ['voice']
restart: unless-stopped
command: ['--config', '/etc/livekit/livekit.yaml']
volumes:
- ./config/livekit.yaml:/etc/livekit/livekit.yaml:ro
ports:
- '${LIVEKIT_PORT:-7880}:7880'
- '7881:7881'
- '3478:3478/udp'
- '50000-50100:50000-50100/udp'
healthcheck:
test: ['CMD-SHELL', 'wget -qO- http://127.0.0.1:7880 || exit 1']
interval: 15s
timeout: 5s
retries: 5
logging: *default-logging
volumes:
valkey_data:
fluxer_data:
meilisearch_data:
elasticsearch_data:
-116
View File
@@ -1,116 +0,0 @@
{
"$schema": "../packages/config/src/ConfigSchema.json",
"env": "development",
"domain": {
"base_domain": "localhost",
"public_port": 48763
},
"database": {
"backend": "sqlite",
"sqlite_path": "./data/dev.db"
},
"internal": {
"kv": "redis://127.0.0.1:6379/0",
"kv_mode": "standalone"
},
"s3": {
"access_key_id": "",
"secret_access_key": "",
"endpoint": "http://127.0.0.1:49319/s3"
},
"services": {
"server": {
"port": 49319,
"host": "0.0.0.0"
},
"media_proxy": {
"secret_key": ""
},
"admin": {
"secret_key_base": "",
"oauth_client_secret": ""
},
"marketing": {
"enabled": true,
"port": 49531,
"host": "0.0.0.0",
"secret_key_base": ""
},
"gateway": {
"port": 49107,
"admin_reload_secret": "",
"media_proxy_endpoint": "http://localhost:49319/media",
"logger_level": "debug"
},
"nats": {
"core_url": "nats://127.0.0.1:4222",
"jetstream_url": "nats://127.0.0.1:4223"
}
},
"auth": {
"sudo_mode_secret": "",
"connection_initiation_secret": "",
"vapid": {
"public_key": "",
"private_key": ""
},
"bluesky": {
"enabled": true,
"keys": []
}
},
"discovery": {
"min_member_count": 1
},
"dev": {
"disable_rate_limits": true
},
"integrations": {
"email": {
"enabled": true,
"provider": "smtp",
"from_email": "noreply@localhost",
"smtp": {
"host": "localhost",
"port": 49621,
"username": "dev",
"password": "",
"secure": false
}
},
"gif": {
"provider": "klipy"
},
"klipy": {
"api_key": ""
},
"tenor": {
"api_key": ""
},
"voice": {
"enabled": true,
"api_key": "",
"api_secret": "",
"url": "ws://localhost:7880",
"webhook_url": "http://localhost:49319/api/webhooks/livekit",
"default_region": {
"id": "default",
"name": "Default",
"emoji": "\ud83c\udf10",
"latitude": 0.0,
"longitude": 0.0
}
},
"search": {
"engine": "meilisearch",
"url": "http://127.0.0.1:7700",
"api_key": ""
}
},
"instance": {
"private_key_path": ""
},
"federation": {
"enabled": false
}
}
-64
View File
@@ -1,64 +0,0 @@
{
"$schema": "../packages/config/src/ConfigSchema.json",
"env": "production",
"domain": {
"base_domain": "chat.example.com",
"public_scheme": "https",
"public_port": 443
},
"database": {
"backend": "sqlite",
"sqlite_path": "./data/fluxer.db"
},
"internal": {
"kv": "redis://valkey:6379/0",
"kv_mode": "standalone"
},
"s3": {
"access_key_id": "YOUR_S3_ACCESS_KEY",
"secret_access_key": "YOUR_S3_SECRET_KEY",
"endpoint": "http://127.0.0.1:8080/s3"
},
"services": {
"server": {
"port": 8080,
"host": "0.0.0.0"
},
"media_proxy": {
"secret_key": "GENERATE_A_64_CHAR_HEX_SECRET"
},
"admin": {
"secret_key_base": "GENERATE_A_64_CHAR_HEX_SECRET",
"oauth_client_secret": "GENERATE_A_64_CHAR_HEX_SECRET"
},
"marketing": {
"enabled": true,
"secret_key_base": "GENERATE_A_64_CHAR_HEX_SECRET"
},
"gateway": {
"port": 8082,
"admin_reload_secret": "GENERATE_A_64_CHAR_HEX_SECRET",
"media_proxy_endpoint": "http://127.0.0.1:8080/media"
},
"nats": {
"core_url": "nats://nats:4222",
"jetstream_url": "nats://nats:4222",
"auth_token": "GENERATE_A_NATS_AUTH_TOKEN"
}
},
"auth": {
"sudo_mode_secret": "GENERATE_A_64_CHAR_HEX_SECRET",
"connection_initiation_secret": "GENERATE_A_64_CHAR_HEX_SECRET",
"vapid": {
"public_key": "YOUR_VAPID_PUBLIC_KEY",
"private_key": "YOUR_VAPID_PRIVATE_KEY"
}
},
"integrations": {
"search": {
"engine": "meilisearch",
"url": "http://meilisearch:7700",
"api_key": "YOUR_MEILISEARCH_API_KEY"
}
}
}
-4
View File
@@ -1,4 +0,0 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$ref": "../packages/config/src/ConfigSchema.json"
}
-73
View File
@@ -1,73 +0,0 @@
{
"env": "test",
"instance": {
"self_hosted": false
},
"domain": {
"base_domain": "localhost"
},
"database": {
"backend": "sqlite",
"sqlite_path": "./data/test.db"
},
"s3": {
"access_key_id": "test-access-key",
"secret_access_key": "test-secret-key"
},
"services": {
"media_proxy": {
"secret_key": "test-media-proxy-secret-key-minimum-32-chars"
},
"admin": {
"secret_key_base": "test-admin-secret-key-base-minimum-32-chars",
"oauth_client_secret": "test-oauth-client-secret"
},
"gateway": {
"admin_reload_secret": "test-gateway-admin-reload-secret-32-chars",
"media_proxy_endpoint": "http://localhost:8088/media"
}
},
"auth": {
"sudo_mode_secret": "test-sudo-mode-secret-minimum-32-chars",
"connection_initiation_secret": "test-connection-initiation-secret-32ch",
"vapid": {
"public_key": "test-vapid-public-key",
"private_key": "test-vapid-private-key"
},
"bluesky": {
"enabled": true,
"keys": []
}
},
"discovery": {
"min_member_count": 1
},
"dev": {
"disable_rate_limits": true,
"test_mode_enabled": true,
"relax_registration_rate_limits": true
},
"proxy": {
"trust_cf_connecting_ip": false
},
"integrations": {
"search": {
"url": "http://127.0.0.1:7700",
"api_key": "test-meilisearch-master-key"
},
"photo_dna": {
"enabled": true,
"hash_service_url": "https://api.microsoftmoderator.com/photodna/v1.0/Hash",
"hash_service_timeout_ms": 30000,
"match_endpoint": "https://api.microsoftmoderator.com/photodna/v1.0/Match",
"subscription_key": "test-subscription-key",
"match_enhance": false,
"rate_limit_rps": 10
},
"stripe": {
"enabled": true,
"secret_key": "sk_test_mock_key_for_testing",
"webhook_secret": "whsec_test_mock_webhook_secret"
}
}
}
+146
View File
@@ -0,0 +1,146 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
#
# Checked-in local development defaults. Private credentials and personal
# overrides belong in .env.local or config/env/local.env, both ignored by git.
FLUXER_ENV=development
NODE_ENV=development
FLUXER_BASE_DOMAIN=localhost
FLUXER_PUBLIC_SCHEME=http
FLUXER_PUBLIC_PORT=8088
FLUXER_STATIC_CDN_DOMAIN=localhost
FLUXER_PUBLIC_URL=http://localhost:8088
FLUXER_API_ENDPOINT=http://localhost:8088/api
FLUXER_API_CLIENT_ENDPOINT=http://localhost:8088/api
FLUXER_APP_ENDPOINT=http://localhost:8088
FLUXER_GATEWAY_ENDPOINT=ws://localhost:8088/gateway
FLUXER_MEDIA_ENDPOINT=http://localhost:8088/media
FLUXER_STATIC_CDN_ENDPOINT=http://localhost:8088
FLUXER_ADMIN_ENDPOINT=http://localhost:8088/admin
FLUXER_MARKETING_ENDPOINT=http://localhost:8088/marketing
FLUXER_TRUST_CLIENT_IP_HEADER=true
FLUXER_CLIENT_IP_HEADER_NAME=x-forwarded-for
FLUXER_DATABASE_BACKEND=postgres
FLUXER_POSTGRES_HOST=postgres
FLUXER_POSTGRES_PORT=5432
FLUXER_POSTGRES_DATABASE=fluxer
FLUXER_POSTGRES_USERNAME=fluxer
FLUXER_POSTGRES_PASSWORD=fluxer
FLUXER_POSTGRES_SSL=false
FLUXER_POSTGRES_MAX_CONNECTIONS=20
FLUXER_POSTGRES_KV_TABLE=fluxer_kv
FLUXER_CASSANDRA_HOSTS=cassandra
FLUXER_CASSANDRA_PORT=9042
FLUXER_CASSANDRA_KEYSPACE=fluxer
FLUXER_CASSANDRA_LOCAL_DC=datacenter1
FLUXER_CASSANDRA_USERNAME=fluxer
FLUXER_CASSANDRA_PASSWORD=fluxer
FLUXER_KV_URL=redis://valkey:6379/0
FLUXER_NATS_URL=nats://nats:4222
FLUXER_NATS_JETSTREAM_URL=nats://nats:4222
FLUXER_INTERNAL_API_ENDPOINT=http://127.0.0.1:8080
FLUXER_INTERNAL_GATEWAY_ENDPOINT=http://127.0.0.1:8771
FLUXER_INTERNAL_MEDIA_PROXY_ENDPOINT=http://127.0.0.1:8082
FLUXER_MEDIA_PROXY_ENDPOINT=http://127.0.0.1:8082
FLUXER_MEDIA_PROXY_PUBLIC_ENDPOINT=http://localhost:8088/media
FLUXER_SVC_NATS_URL=nats://nats:4222
FLUXER_SVC_SHARD_COUNT=1
FLUXER_SVC_CACHE_TTL_MS=30000
FLUXER_SVC_CACHE_HARD_TTL_MS=600000
FLUXER_SVC_MAX_CONCURRENT_REQUESTS=64
FLUXER_S3_ENDPOINT=http://127.0.0.1:8333
FLUXER_S3_PUBLIC_ENDPOINT=http://localhost:8088
FLUXER_S3_REGION=us-east-1
FLUXER_S3_ACCESS_KEY_ID=fluxer
FLUXER_S3_SECRET_ACCESS_KEY=fluxer-secret
FLUXER_S3_FORCE_PATH_STYLE=true
FLUXER_S3_BUCKET_CDN=fluxer
FLUXER_S3_BUCKET_UPLOADS=fluxer-uploads
FLUXER_S3_BUCKET_DOWNLOADS=fluxer-downloads
FLUXER_S3_BUCKET_REPORTS=fluxer-reports
FLUXER_S3_BUCKET_HARVESTS=fluxer-harvests
FLUXER_S3_BUCKET_STATIC=fluxer-static
FLUXER_LIVEKIT_ENABLED=true
FLUXER_LIVEKIT_URL=ws://localhost:8088/livekit
FLUXER_LIVEKIT_API_KEY=devkey
FLUXER_LIVEKIT_API_SECRET=secret
FLUXER_LIVEKIT_WEBHOOK_URL=http://localhost:8088/api/webhooks/livekit
FLUXER_LIVEKIT_DEFAULT_REGION={"id":"local","name":"Local","emoji":"LC","latitude":59.3293,"longitude":18.0686}
FLUXER_API_PORT=8080
FLUXER_API_PRESIGNED_ATTACHMENT_UPLOADS_ENABLED=true
FLUXER_API_WORKER_MODE=all_lanes
FLUXER_API_WORKER_ENABLE_VOICE_RECONCILIATION=true
FLUXER_APP_DEV_PORT=3000
FLUXER_APP_PROXY_PORT=8773
FLUXER_STATIC_DIR=fluxer_app/dist
FLUXER_MEDIA_PROXY_HOST=0.0.0.0
FLUXER_MEDIA_PROXY_PORT=8082
FLUXER_MEDIA_PROXY_SECRET_KEY=dev-media-proxy-secret
FLUXER_MEDIA_PROXY_MODE=upload
FLUXER_MEDIA_PROXY_STORAGE_BACKEND=s3
FLUXER_MEDIA_PROXY_UPLOAD_RELAY_ENDPOINT=http://localhost:8088/media
FLUXER_MEDIA_PROXY_UPLOAD_RELAY_KEEP_DIRECT_COUNTRIES=
FLUXER_GATEWAY_PORT=8771
FLUXER_GATEWAY_MEDIA_PROXY_ENDPOINT=http://localhost:8088/media
FLUXER_GATEWAY_STATIC_CDN_ENDPOINT=http://localhost:8088
FLUXER_GATEWAY_RPC_AUTH_TOKEN=dev-gateway-rpc-token
FLUXER_GATEWAY_LOGGER_LEVEL=info
FLUXER_GATEWAY_PRESENCE_PUSH_BUFFER_MAX_ENTRIES=128
FLUXER_GATEWAY_PRESENCE_PUSH_BUFFER_MAX_BYTES=1048576
FLUXER_ADMIN_PORT=3020
FLUXER_ADMIN_BASE_PATH=/admin
FLUXER_ADMIN_SECRET_KEY_BASE=dev-admin-secret-key-base
FLUXER_ADMIN_OAUTH_CLIENT_ID=1234567890123456789
FLUXER_ADMIN_OAUTH_CLIENT_SECRET=dev-admin-oauth-secret
FLUXER_ADMIN_OAUTH_REDIRECT_URI=http://localhost:8088/admin/oauth2_callback
FLUXER_MARKETING_PORT=3010
FLUXER_MARKETING_HOST=0.0.0.0
FLUXER_MARKETING_BASE_PATH=/marketing
FLUXER_MARKETING_SECRET_KEY_BASE=dev-marketing-secret-key-base
FLUXER_SUDO_MODE_SECRET=dev-sudo-secret
FLUXER_CONNECTION_INITIATION_SECRET=dev-connection-initiation-secret
FLUXER_VAPID_PUBLIC_KEY=dev-vapid-public-key
FLUXER_VAPID_PRIVATE_KEY=dev-vapid-private-key
FLUXER_VAPID_EMAIL=dev@localhost
FLUXER_PASSKEY_RP_NAME=Fluxer Dev
FLUXER_PASSKEY_RP_ID=localhost
FLUXER_PASSKEY_ADDITIONAL_ALLOWED_ORIGINS=http://localhost,http://localhost:8088
FLUXER_EMAIL_ENABLED=true
FLUXER_EMAIL_PROVIDER=smtp
FLUXER_EMAIL_FROM_EMAIL=dev@localhost
FLUXER_EMAIL_FROM_NAME=Fluxer Dev
FLUXER_EMAIL_SMTP_HOST=mailpit
FLUXER_EMAIL_SMTP_PORT=1025
FLUXER_EMAIL_SMTP_USERNAME=dev
FLUXER_EMAIL_SMTP_PASSWORD=dev
FLUXER_EMAIL_SMTP_SECURE=false
FLUXER_SMS_ENABLED=false
FLUXER_CAPTCHA_ENABLED=false
FLUXER_CAPTCHA_PROVIDER=none
FLUXER_SEARCH_ENGINE=elasticsearch
FLUXER_SEARCH_URL=http://localhost:9200
FLUXER_SEARCH_TLS_REJECT_UNAUTHORIZED=false
FLUXER_STRIPE_ENABLED=false
FLUXER_NCMEC_ENABLED=false
FLUXER_CLAMAV_ENABLED=false
FLUXER_GIF_PROVIDER=tenor
FLUXER_DISCOVERY_ENABLED=true
FLUXER_SELF_HOSTED=true
FLUXER_DISABLE_RATE_LIMITS=true
FLUXER_TEST_MODE_ENABLED=false
PUBLIC_BUILD_VERSION=dev
PUBLIC_RELEASE_CHANNEL=canary
PUBLIC_BOOTSTRAP_API_ENDPOINT=/api
PUBLIC_BOOTSTRAP_API_PUBLIC_ENDPOINT=http://localhost:8088/api
FLUXER_MEDIA_PROXY_UPLOAD_RELAY_SECRET_BASE64=Zmx1eGVyLWRldi11cGxvYWQtcmVsYXktc2VjcmV0LTAwMDA=
AWS_EC2_METADATA_DISABLED=true
AWS_ACCESS_KEY_ID=fluxer
AWS_SECRET_ACCESS_KEY=fluxer-secret
AWS_DEFAULT_REGION=us-east-1
-16
View File
@@ -1,16 +0,0 @@
port: 7880
keys:
'<replace-with-api-key>': '<replace-with-api-secret>'
rtc:
tcp_port: 7881
turn:
enabled: true
udp_port: 3478
room:
auto_create: true
max_participants: 100
empty_timeout: 300
+102
View File
@@ -0,0 +1,102 @@
# cargo-deny configuration for the Fluxer workspace.
#
# Applies to the root workspace (Cargo.toml at the repo root) AND to every
# per-addon crate under fluxer_desktop/native/* (each addon has its own
# [workspace], so we invoke cargo-deny with --config pointing here).
#
# Used by the native desktop security gate in CI.
[graph]
all-features = false
no-default-features = false
[output]
feature-depth = 1
# ---------------------------------------------------------------------------
# Advisories (RustSec advisory DB)
# ---------------------------------------------------------------------------
[advisories]
version = 2
yanked = "deny"
unmaintained = "all"
# Placeholder for future per-advisory ignores. Prefer fixing/upgrading the
# crate before adding an entry here. Each entry should be a string ID, e.g.
# "RUSTSEC-2024-0000", optionally as { id = "...", reason = "..." }.
ignore = []
# ---------------------------------------------------------------------------
# Licenses
# ---------------------------------------------------------------------------
# This repo is AGPL-3.0-or-later. Dependency licenses below are vetted to be
# compatible with AGPL distribution. If a crate is essential and ships under a
# license not listed here, add it explicitly (do NOT route around via
# [[licenses.exceptions]] or [[licenses.clarify]] without justification in the
# commit message).
[licenses]
version = 2
confidence-threshold = 0.93
allow = [
"MIT",
"Apache-2.0",
"Apache-2.0 WITH LLVM-exception",
"BSD-2-Clause",
"BSD-3-Clause",
"ISC",
"MPL-2.0",
"Unicode-DFS-2016",
"Unicode-3.0",
"Zlib",
"CC0-1.0",
"AGPL-3.0-or-later",
"BSL-1.0",
"OpenSSL",
"CDLA-Permissive-2.0",
]
# Explicitly deny GPL-only / strong-copyleft licenses that don't compose with
# AGPL-3.0-or-later linking semantics in a library context.
exceptions = []
# ring uses a custom BSD-ish + OpenSSL license combo that cargo-deny can't
# auto-classify; clarify it as the ISC/OpenSSL/MIT triple it actually is.
[[licenses.clarify]]
crate = "ring"
expression = "ISC AND MIT AND OpenSSL"
license-files = [
{ path = "LICENSE", hash = 0xbd0eed23 },
]
# ---------------------------------------------------------------------------
# Bans
# ---------------------------------------------------------------------------
[bans]
multiple-versions = "warn"
wildcards = "deny"
# Per-addon crates path-depend on ../rust (the shared `fluxer_desktop_native`
# crate) without a version. cargo-deny flags that as a wildcard; we allow it
# because path deps can't realistically pin a SemVer range, and this only
# affects intra-repo workspace links (registry wildcards remain denied).
allow-wildcard-paths = true
highlight = "all"
workspace-default-features = "allow"
external-default-features = "allow"
# Keep desktop packaging and native addons away from the obsolete libfuse2 stack.
# AppImage packaging must use the static electron-builder runtime instead.
deny = [
{ crate = "fuse", reason = "libfuse2-based Rust wrapper; use a maintained FUSE3-native crate only if Fluxer ever needs FUSE directly" },
{ crate = "fuse-sys", reason = "libfuse2 FFI crate; Fluxer AppImages must not reintroduce libfuse2 through native Rust dependencies" },
]
skip = []
skip-tree = []
# ---------------------------------------------------------------------------
# Sources
# ---------------------------------------------------------------------------
[sources]
unknown-registry = "deny"
unknown-git = "deny"
allow-registry = ["https://github.com/rust-lang/crates.io-index"]
allow-git = []
[sources.allow-org]
github = []
+11
View File
@@ -0,0 +1,11 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
apiVersion: v2
name: admin
description: Fluxer admin service
type: application
version: 0.1.0
dependencies:
- name: common
version: 0.1.0
repository: file://../common
@@ -0,0 +1,3 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
{{include "fluxer.deployment" (dict "name" .Values.app.name "values" .Values.app "context" .)}}
+3
View File
@@ -0,0 +1,3 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
{{include "fluxer.pdb" (dict "name" .Values.app.name "minAvailable" .Values.pdb.minAvailable "context" .)}}
+3
View File
@@ -0,0 +1,3 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
{{include "fluxer.service" (dict "name" .Values.app.name "values" .Values.app "context" .)}}
+41
View File
@@ -0,0 +1,41 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
#
# Live user-supplied values for the fluxer-admin-canary release as of 2026-05-17T20:42:36Z.
# Captured via: helm -n fluxer get values fluxer-admin-canary
# Apply with: helm upgrade fluxer-admin-canary deploy/helm/admin -f deploy/helm/admin/values.yaml -f deploy/helm/admin/values.canary.prod.yaml
app:
build:
channel: canary
version: ""
image: fluxer-admin
name: admin-canary
port: 8080
replicas: 2
minReadySeconds: 10
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
terminationGracePeriodSeconds: 60
startupProbe:
enabled: true
path: /_health
periodSeconds: 5
timeoutSeconds: 2
failureThreshold: 24
resources:
limits:
memory: 512Mi
requests:
cpu: 100m
memory: 256Mi
tag: ""
global:
imagePullSecret: ghcr-pull-secret
namespace: fluxer
registry: ""
envFrom:
- secretRef:
name: fluxer-runtime-env-canary
pdb:
minAvailable: 50%
+41
View File
@@ -0,0 +1,41 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
#
# Live user-supplied values for the fluxer-admin-stable release as of 2026-06-03T19:37:50Z.
# Captured via: helm -n fluxer get values fluxer-admin-stable
# Apply with: helm upgrade fluxer-admin-stable deploy/helm/admin -f deploy/helm/admin/values.yaml -f deploy/helm/admin/values.stable.prod.yaml
app:
build:
channel: stable
version: ""
image: fluxer-admin
name: admin
port: 8080
replicas: 2
minReadySeconds: 10
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
terminationGracePeriodSeconds: 60
startupProbe:
enabled: true
path: /_health
periodSeconds: 5
timeoutSeconds: 2
failureThreshold: 24
resources:
limits:
memory: 512Mi
requests:
cpu: 100m
memory: 256Mi
tag: ""
global:
imagePullSecret: ghcr-pull-secret
namespace: fluxer
registry: ""
envFrom:
- secretRef:
name: fluxer-runtime-env-stable
pdb:
minAvailable: 50%
+34
View File
@@ -0,0 +1,34 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
global:
namespace: fluxer
imagePullSecret: ghcr-pull-secret
registry: ""
app:
name: ''
image: ''
tag: ''
replicas: 2
port: 8080
config: ''
minReadySeconds: 10
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
terminationGracePeriodSeconds: 60
startupProbe:
enabled: true
path: /_health
periodSeconds: 5
timeoutSeconds: 2
failureThreshold: 24
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
memory: 512Mi
pdb:
minAvailable: '50%'
+11
View File
@@ -0,0 +1,11 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
apiVersion: v2
name: api
description: Fluxer API service
type: application
version: 0.1.0
dependencies:
- name: common
version: 0.1.0
repository: file://../common
@@ -0,0 +1,3 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
{{include "fluxer.deployment" (dict "name" .Values.app.name "values" .Values.app "context" .)}}
+3
View File
@@ -0,0 +1,3 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
{{include "fluxer.pdb" (dict "name" .Values.app.name "minAvailable" .Values.pdb.minAvailable "context" .)}}
+3
View File
@@ -0,0 +1,3 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
{{include "fluxer.service" (dict "name" .Values.app.name "values" .Values.app "context" .)}}
+105
View File
@@ -0,0 +1,105 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
#
# Live user-supplied values for the fluxer-api-canary release as of 2026-05-23T21:25:52Z.
# Captured via: helm -n fluxer get values fluxer-api-canary
# Apply with: helm upgrade fluxer-api-canary deploy/helm/api -f deploy/helm/api/values.yaml -f deploy/helm/api/values.canary.prod.yaml
app:
build:
channel: canary
version: ""
env:
- name: NODE_TLS_REJECT_UNAUTHORIZED
value: "0"
- name: FLUXER_SNOWFLAKE_SERVICE_BATCH_SIZE
value: "128"
- name: FLUXER_SNOWFLAKE_SERVICE_LOW_WATERMARK
value: "32"
- name: FLUXER_SNOWFLAKE_SERVICE_MAX_BUFFER_AGE_MS
value: "5000"
- name: FLUXER_MEDIA_PROXY_UPLOAD_RELAY_SECRET_BASE64
valueFrom:
secretKeyRef:
key: relay_secret_base64
name: fluxer-upload-relay
image: fluxer-api
name: api-canary
port: 8080
replicas: 4
minReadySeconds: 15
terminationGracePeriodSeconds: 90
preStopDrain:
enabled: true
path: /_health
sleepSeconds: 25
timeoutSeconds: 2
retryCount: 3
retryIntervalSeconds: 1
resources:
limits:
memory: 4Gi
requests:
cpu: 200m
memory: 512Mi
startupProbe:
enabled: true
failureThreshold: 24
path: /_health
periodSeconds: 5
timeoutSeconds: 2
rollingUpdate:
maxSurge: 0
maxUnavailable: 1
tag: ""
canary:
env:
- name: NODE_TLS_REJECT_UNAUTHORIZED
value: "0"
- name: FLUXER_SNOWFLAKE_SERVICE_BATCH_SIZE
value: "128"
- name: FLUXER_SNOWFLAKE_SERVICE_LOW_WATERMARK
value: "32"
- name: FLUXER_SNOWFLAKE_SERVICE_MAX_BUFFER_AGE_MS
value: "5000"
- name: FLUXER_MEDIA_PROXY_UPLOAD_RELAY_SECRET_BASE64
valueFrom:
secretKeyRef:
key: relay_secret_base64
name: fluxer-upload-relay
image: fluxer-api
port: 8080
replicas: 2
minReadySeconds: 15
terminationGracePeriodSeconds: 90
preStopDrain:
enabled: true
path: /_health
sleepSeconds: 25
timeoutSeconds: 2
retryCount: 3
retryIntervalSeconds: 1
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
resources:
limits:
memory: 4Gi
requests:
cpu: 200m
memory: 512Mi
startupProbe:
enabled: true
failureThreshold: 24
path: /_health
periodSeconds: 5
timeoutSeconds: 2
tag: ""
global:
imagePullSecret: ghcr-pull-secret
namespace: fluxer
registry: ""
envFrom:
- secretRef:
name: fluxer-runtime-env-canary
pdb:
minAvailable: 75%
+107
View File
@@ -0,0 +1,107 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
#
# Live user-supplied values for the fluxer-api-stable release as of 2026-06-03T19:37:50Z.
# Captured via: helm -n fluxer get values fluxer-api-stable
# Apply with: helm upgrade fluxer-api-stable deploy/helm/api -f deploy/helm/api/values.yaml -f deploy/helm/api/values.stable.prod.yaml
app:
build:
channel: stable
version: ""
env:
- name: NODE_TLS_REJECT_UNAUTHORIZED
value: "0"
- name: FLUXER_SNOWFLAKE_SERVICE_BATCH_SIZE
value: "128"
- name: FLUXER_SNOWFLAKE_SERVICE_LOW_WATERMARK
value: "32"
- name: FLUXER_SNOWFLAKE_SERVICE_MAX_BUFFER_AGE_MS
value: "5000"
- name: FLUXER_USERS_SERVICE_TIMEOUT_MS
value: "6000"
- name: FLUXER_MEDIA_PROXY_UPLOAD_RELAY_SECRET_BASE64
valueFrom:
secretKeyRef:
key: relay_secret_base64
name: fluxer-upload-relay
image: fluxer-api
name: api
port: 8080
replicas: 31
minReadySeconds: 15
terminationGracePeriodSeconds: 90
preStopDrain:
enabled: true
path: /_health
sleepSeconds: 25
timeoutSeconds: 2
retryCount: 3
retryIntervalSeconds: 1
resources:
limits:
memory: 4Gi
requests:
cpu: 200m
memory: 512Mi
startupProbe:
enabled: true
failureThreshold: 24
path: /_health
periodSeconds: 5
timeoutSeconds: 2
rollingUpdate:
maxSurge: 0
maxUnavailable: 1
tag: ""
canary:
env:
- name: NODE_TLS_REJECT_UNAUTHORIZED
value: "0"
- name: FLUXER_SNOWFLAKE_SERVICE_BATCH_SIZE
value: "128"
- name: FLUXER_SNOWFLAKE_SERVICE_LOW_WATERMARK
value: "32"
- name: FLUXER_SNOWFLAKE_SERVICE_MAX_BUFFER_AGE_MS
value: "5000"
- name: FLUXER_MEDIA_PROXY_UPLOAD_RELAY_SECRET_BASE64
valueFrom:
secretKeyRef:
key: relay_secret_base64
name: fluxer-upload-relay
image: fluxer-api
port: 8080
replicas: 2
minReadySeconds: 15
terminationGracePeriodSeconds: 90
preStopDrain:
enabled: true
path: /_health
sleepSeconds: 25
timeoutSeconds: 2
retryCount: 3
retryIntervalSeconds: 1
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
resources:
limits:
memory: 4Gi
requests:
cpu: 200m
memory: 512Mi
startupProbe:
enabled: true
failureThreshold: 24
path: /_health
periodSeconds: 5
timeoutSeconds: 2
tag: ""
global:
imagePullSecret: ghcr-pull-secret
namespace: fluxer
registry: ""
envFrom:
- secretRef:
name: fluxer-runtime-env-stable
pdb:
minAvailable: 75%
+98
View File
@@ -0,0 +1,98 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
global:
namespace: fluxer
imagePullSecret: ghcr-pull-secret
registry: ""
app:
name: ''
image: ''
tag: ''
replicas: 2
port: 8080
minReadySeconds: 15
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
terminationGracePeriodSeconds: 90
preStopDrain:
enabled: true
path: /_health
sleepSeconds: 25
timeoutSeconds: 2
retryCount: 3
retryIntervalSeconds: 1
startupProbe:
enabled: true
path: /_health
periodSeconds: 5
timeoutSeconds: 2
failureThreshold: 24
env:
- name: NODE_TLS_REJECT_UNAUTHORIZED
value: '0'
- name: FLUXER_SNOWFLAKE_SERVICE_BATCH_SIZE
value: '128'
- name: FLUXER_SNOWFLAKE_SERVICE_LOW_WATERMARK
value: '32'
- name: FLUXER_SNOWFLAKE_SERVICE_MAX_BUFFER_AGE_MS
value: '5000'
- name: FLUXER_MEDIA_PROXY_UPLOAD_RELAY_SECRET_BASE64
valueFrom:
secretKeyRef:
name: fluxer-upload-relay
key: relay_secret_base64
resources:
requests:
cpu: 200m
memory: 512Mi
limits:
memory: 4Gi
canary:
image: fluxer-api
tag: ''
replicas: 2
port: 8080
minReadySeconds: 15
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
terminationGracePeriodSeconds: 90
preStopDrain:
enabled: true
path: /_health
sleepSeconds: 25
timeoutSeconds: 2
retryCount: 3
retryIntervalSeconds: 1
startupProbe:
enabled: true
path: /_health
periodSeconds: 5
timeoutSeconds: 2
failureThreshold: 24
env:
- name: NODE_TLS_REJECT_UNAUTHORIZED
value: '0'
- name: FLUXER_SNOWFLAKE_SERVICE_BATCH_SIZE
value: '128'
- name: FLUXER_SNOWFLAKE_SERVICE_LOW_WATERMARK
value: '32'
- name: FLUXER_SNOWFLAKE_SERVICE_MAX_BUFFER_AGE_MS
value: '5000'
- name: FLUXER_MEDIA_PROXY_UPLOAD_RELAY_SECRET_BASE64
valueFrom:
secretKeyRef:
name: fluxer-upload-relay
key: relay_secret_base64
resources:
requests:
cpu: 200m
memory: 512Mi
limits:
memory: 4Gi
pdb:
minAvailable: '75%'
+11
View File
@@ -0,0 +1,11 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
apiVersion: v2
name: app-proxy
description: Fluxer app proxy service
type: application
version: 0.1.0
dependencies:
- name: common
version: 0.1.0
repository: file://../common
@@ -0,0 +1,3 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
{{include "fluxer.deployment" (dict "name" .Values.app.name "values" .Values.app "context" .)}}
+3
View File
@@ -0,0 +1,3 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
{{include "fluxer.pdb" (dict "name" .Values.app.name "minAvailable" .Values.pdb.minAvailable "context" .)}}

Some files were not shown because too many files have changed in this diff Show More