* feat: optional auto reconnect for dropped ssh sessions (#1321)

* feat: tighter compact host rows with inline tags (#1325)

* feat: bring back the alert feed homepage widget

* feat: pick target terminals for snippets again

* feat: share button on connection tabs again

* feat: recent servers list when linking the desktop app

* feat: list the fixed app shortcuts in keyboard shortcuts

* fix: translate hardcoded toasts and drop em dashes from ui text

* fix: translate leftover hardcoded ui text

* fix: toolbar files button opens at the shell directory again

* fix: show the remote desktop note under connection origin again

* chore: openapi docs for host metrics manager routes

* chore: openapi docs for undocumented routes and fix two broken doc blocks

* fix: declare terminal.listSessions use for snippet targets

* revert: drop the alert feed widget, the notification center replaces it

* feat: restore the alert feed homepage widget

* fix: quick connect fills the host address from plugin auth editors like tailscale

* fix: docker container logs use the base background color

* chore: fix dependabot alerts and apply safe npm upgrades

* fix: proxmox stats tab reads its switch from plugin host settings

* fix: count plugin ssh logins toward host status and stop showing reachable as a warning

* fix: hide the built-in source pill in the host editor

* fix: host status shows online when the port answers and only warns on a failed login

* fix: drop the top border on the split layout menu

* fix: host defaults tabs sit where the host editor puts them and skip plugins with nothing to default

* fix: remote desktop auth method labels and notes for vnc and telnet

* fix: add description to the web endpoint enable switch

* fix: crowdin sync and pretranslate for plugin locales, run plugin tests on release

* feat: move telemetry out of core into a usage statistics plugin

* fix: recent activity card no longer cuts off host names early

* fix: remove duplicate top border on split screen menu

* fix: redesign session log player controls, fix tooltip overlap and drop dot separators

* fix: file manager and sftp text showing raw i18n keys

* fix: onboarding rerunning for existing users after upgrading

* fix: fleet detail buttons overflowing the sidebar

* fix: session log player scrolling off screen horizontally

* fix: network graph lag when adding hosts and on every render

* fix: maintenance index name, shared host read only view and badge polling

* fix: meeting tabs, workspace tab merging, rail panels as tabs and general ui lag

* chore: update release notes

* fix: remote desktop tabs reconnecting on every tab switch

* fix: build plugin sdk before linting in release workflow

* chore: lint, format, and bump version to 2.9.0

* chore: skip crowdin pre-translate in release for now

* chore: switch to calver, bump version to 26.10.0

* fix(sso): send the PKCE verifier for GitHub login (#1534)

* fix(remote-desktop): clip cursor overflow without disabling zoom (#1535)

* test(remote-desktop): preserve sessions beyond one hour (#1536)

* fix(hosts): expose controls for overflowing editor tabs (#1537)

* fix(hosts): expose controls for overflowing editor tabs

* test(hosts): mock resize observation in admin panel tests

* fix(status): skip TCP probes while host sessions are active (#1538)

* fix(database): batch session activity persistence (#1539)

* docs(api): describe cookie and API key authentication (#1540)

* fix(snippets): repair missing note column on SQLite upgrades (#1541)

* fix(docker): retain stored SSH credential association (#1543)

* fix(file-manager): render transfer toasts without plugin hooks (#1546)

* feat(hosts): keep compact row actions inline (#1547)

* fix(auth): allow retrying unavailable second-factor interfaces (#1549)

* fix(auth): reject unresolved legacy identity provisioning (#1550)

* fix(desktop): ship the plugin SDK inside Mac App Store builds (#1551)

* fix(tmux): pass full session info to the terminal's session picker (#1552)

The tmux.sessions service reduced detected sessions to bare names, but the
picker reads session.name, so every entry rendered blank and selecting one
sent an empty name, which created a new session instead of attaching.

* fix(ssh): read force keyboard-interactive as a boolean when resolving hosts (#1553)

The column holds the text "true"/"false", and the resolver passed it
through as-is. The string "false" is truthy, so the password provider
treated every saved host as forced keyboard-interactive and left the
password out. Jump hops are built straight from the resolved host, so a
password hop whose server doesn't offer keyboard-interactive failed with
"All configured authentication methods failed".

* fix(database): batch database saves for bulk host writes (#1554)

* fix(database): yield to the event loop between database saves

Each SQLite save serializes and encrypts the whole database into new
buffers, which V8 only releases once the event loop turns. Boot
migrations and bulk host import write plugin settings per host and per
key in an awaited loop, so hundreds of full copies piled up and the
container ran out of memory.

* fix(database): save once per bulk host write instead of once per row

On SQLite every repository write force-saves the whole database. Boot
plugin data migrations, host defaults materialization and the bulk
host routes write one row per host per setting, so they serialized and
encrypted the full database hundreds of times in a row.

DatabaseSaveTrigger.batched wraps a function so force saves made while
it runs collapse into a single save when it finishes. Nested scopes fold
into the outer one, and work that outlives its scope saves normally.

* feat(hosts): add instance-wide predefined tag suggestions (#1548)

* feat(hosts): add shared predefined tag suggestions

* style(hosts): format tag catalog routes

* test(database): advance fake timers past the save yield (#1555)

* fix(i18n): complete Simplified Chinese core and plugin translations (#1542)

* fix(i18n): complete Chinese onboarding and navigation labels

* fix(i18n): translate remaining Chinese core and plugin interfaces

* fix(i18n): translate host editor tab overflow controls

* fix(i18n): use consistent Chinese fleet terminology

* fix(i18n): localize hardcoded controls and plugin views

* fix(i18n): translate built-in homepage widget catalog

* test(homepage): follow translated timezone placeholder

* fix(i18n): translate plugin-provided homepage widgets

* fix(i18n): localize feature settings section titles

* fix: 2.8 oidc accounts unable to sign in after upgrading (#1381)

* fix: plugins losing the saved ssh login when copying a host (#1391)

* fix: fleets, proxmox and automations not getting the host sudo password

* fix: host imports running a defaults pass and metrics restart per host (#1384)

* fix: high cpu from status probes and full database saves on every sample (#1300)

* fix: user data export freezing the server (#1393)

* fix: op:// secret references rejected as ssh keys on credentials (#1394)

* fix: slow file deletes from the trash lookups on every delete (#1390)

* fix: add openapi docs and error handling to host tag routes

* test: compare download stream buffers directly so it stops timing out

* chore: drop em dash from host row comment

* chore: update release notes for 2.9.1

* feat(ai): add remote coding agent sessions for SSH hosts (#1545)

* feat(ai): add host coding agent sessions with configured providers

* Wait for agent initialization and shutdown before resuming sessions

* Keep Pi transcripts outside its configuration migration directory

* Cover Pi session directory separation

* Update core permission catalog expectations for remote agents

* Install remote agent runtimes from verified official sources

* Wait for the identity panel credentials request in its test

* Add scoped SSH forwarding setup for remote agents

* Verify forwarding setup through a fresh SSH connection

* Bound the SSH forwarding verification timeout

* Return the forwarding verification callback result

* fix(ai): support agent send and interrupt keyboard shortcuts

* feat(ai): add agent queues attachments worktrees and session recovery

* fix(ai): handle deleted workspace paths and duplicate status events

* fix(ai): accept file references in root workspaces

* fix(ai): refresh paused queues after agent errors

* fix(ai): store agent sessions in plugin tables and lock down OpenCode

Agent sessions lived in a single ctx.kv value holding up to 2000 events,
which passed the 256 KB kv limit within minutes of a normal run. Every
save after that failed, so history, nativeId, queue and archive changes
were lost and every event wrote a kv audit row.

Sessions, events and queued prompts now have their own tables. A running
agent appends events in batches at most once a second with a lazy save;
session and queue changes save immediately. Prompts, drafts and stored
event text are capped in UTF-8 bytes to fit MySQL TEXT. kv:own is no
longer requested.

The OpenCode server now requires a per-session password, so other local
accounts on the host cannot drive the agent through its loopback port.

* test(upgrade): include agent session tables in the reviewed upgrade SQL

* test: run the posix-only ai agent tests on linux only

* fix: restore space to add host tags and redesign predefined tags editor

* fix: host key silently accepted when the host is missing from the database (#1397)

* fix: clearer host login failed status label and fix its translations (#1396)

* chore: add host key and status label fixes to release notes

* fix: sso and ldap provider dialogs overflowing the screen and using mismatched toggles

* chore: increment ver

* fix(audit): store plugin entries with no acting user as a null user_id (#1556)

Plugin audit entries written outside a request used the literal "system"
as user_id. That column references users.id, so the insert was refused
(Postgres logs it as an FK violation) and the entry was silently dropped.
Write null instead and keep "system" / plugin:<id> in username.

* chore: add sso/ldap dialog and audit log fixes to release notes

* fix: tunnels and host settings missing from shared hosts on desktop

* fix: remote desktop logins missing from shared hosts on desktop

* fix: simplify host status to online/offline and keep it live without a refresh

* chore: sync Crowdin translations for 2.9.1

* fix: build docker frontend/backend natively to stop arm64 hang, build sdk before openapi

* chore: rework plugin readmes, json changelogs and standalone repo setup

* chore: move plugins to their own repos and drop the plugins folder

* chore(deps-dev): bump globals (#1560)

Bumps the dev-minor-updates group with 1 update: [globals](https://github.com/sindresorhus/globals).


Updates `globals` from 17.12.0 to 17.13.0
- [Release notes](https://github.com/sindresorhus/globals/releases)
- [Commits](https://github.com/sindresorhus/globals/compare/v17.12.0...v17.13.0)

---
updated-dependencies:
- dependency-name: globals
  dependency-version: 17.13.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: dev-minor-updates
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* feat: drop the 2.8 columns, moves and aliases kept for a downgrade

* chore: remove the 2.9 finish list

* feat: port the demo redesign with Manage and Settings tabs

* chore: pin the plugin signing key and use TERMIX_PAT in workflows

* chore: rename the plugin sdk to @termix-ssh/plugin-sdk

* feat: add a publish workflow for the plugin sdk

* chore: cleanup

* fix: harden plugin sockets, public routes, prebuilt binaries and login fallback

* chore: add a 2.9.1 upgrade check for postgres and mysql

* fix: stop full database rewrites on every plugin db call

* fix: stop full database rewrites on every plugin db call

* fix: restore release video link in 2.9.2 notes

* fix: use TERMIX_PAT in workflows

* feat: pin the official plugins in the docker image

* feat: implement plugin management UI

* feat: shared list kit, settings search, rail order and ui consistency fixes

* chore: run all workflows on blacksmith runners

* chore: split dashboard quick actions into quarters and drop host status row lines

* fix: open plain settings from the profile button, navigation only from the rail menu

* feat: consent check, developer mode file installs, install counts and issue links for plugins

* feat: row detail options for hosts and credentials, open manage from sidebar header

* fix: unify connection failure screens and rebuild stale local plugins

* feat: dev:all runner for web, electron and docker with plugin hot reload, termix-plugin dev

* chore: clean up npm scripts, make npm run dev the full runner and drop concurrently

* fix: settle quick connect panel loads in tests so they stop flaking

* fix: stop the dev runner rebuilding plugins when windows reports file reads as changes

* chore: update the plugin sdk ui export snapshot

* feat: full screen onboarding with a plugin picker and per step versions

* feat: changelog.md release notes, v tags, plugin release notes and videos

* chore: add temp changelog

* chore: release the plugin sdk as 1.0.1

* feat: show only the latest plugin release notes and cap long ones

* chore: publish docker images to termix-ssh on ghcr and termixssh on docker hub

* chore: rename docker hub token secret to DOCKERHUB_BUGATTIGUY527_TOKEN

* chore: renew mac app store provisioning profile

* chore: drop docs job from release workflow

* chore: run sdk publish on github hosted runner

* chore: remove dependabot

* chore: general file cleanup

* chore: rewrite readme and taglines for plugin-based termix

* chore: drop optional from ai plugin row in readme

* feat: per-repo issue forms and prefilled bug reports instead of the support repo

* feat: add report a bug link to the your account settings section

* fix: open settings goes to the plugin's page and hides when it has none

* feat: show plugin features from the manifest on the plugin page

* fix: retry failed or blocked plugins on restart instead of leaving them disabled

* fix: refresh plugin settings after enabling a plugin and match select and disabled bar styling

* feat: request a feature link for plugins and use the bug form for unverified plugins

* feat: beta channels for core and plugins with one stable/beta release workflow

* fix(sync): resolve shared hosts for connect-only recipients (#1568)

* fix(ssh): accept full passwords in keyboard-interactive prompts (#1569)

* fix(ssh): normalize PKCS8 private keys before connecting (#1570)

* fix(sidebar): reclaim space from hidden folder actions (#1571)

* fix(hosts): display protocol credential usernames for non-SSH hosts (#1572)

* fix: ask for a push/TOTP menu choice as text instead of a push confirm

* fix: keep client tunnels open when one of their connections fails (Support#1417)

* fix: send plugin form uploads to the plugin's route (Support#1406, Support#1420)

* fix: add missing sync ids to old snippet tables so snippets sync again (Support#1419)

* fix: start the macOS local terminal when the shell or spawn helper is broken (Support#1418)

* fix: refresh cached plugin translations when only a translation changes (Support#1411)

* fix: import sshpk as a default so the backend starts under ESM

* feat: add a plugin sdk changelog and tag sdk releases, release 1.0.2

* fix: drop the ./ from the plugin sdk bin path so npm stops warning

* chore: name core and sdk releases Termix vX and Plugin SDK vX

* fix: lock the desktop backend to the app's own origin (GHSA-frx8-qqgg-7j7g)

* fix: trust devices by a server-issued token, not a client header (GHSA-6gr3-r7jx-wfmh)

* fix: keep global settings out of non-admin data exports (GHSA-v3vp-ffww-q7pw)

* fix: refuse sessionless tokens and keep the secrets file owner only

* feat: add TRUSTED_PROXIES to choose who nginx takes client IPs from (GHSA-7j7m-85p4-2cpm)

* chore: standardize repo setup files

* chore: remove unused knip config

* chore: add LICENSE and use Node 24 from .nvmrc

* chore: update translation guidance, only en.json needs editing

* feat: plugin docs and env manifest fields, in-app docs links, SDK 1.0.3

* fix: header separators render as borders and center the status divider

* fix: lighter confirm prompt overlay to stop lag when it opens

* fix: put plugin-only tailwind classes in core's utilities layer, SDK 1.0.4

* fix: dev stages local plugin builds on paths with spaces and builds them with the local SDK

* fix: keep plugin tailwind variants in core's layer so overrides keep their order

* fix: manage tab refetches before giving up on a host or credential it cannot find

* fix: tab buttons like share show as soon as the terminal is ready

* fix: folder rows no longer nest action buttons inside a button

* fix: repin remote-desktop after its 1.0.0 re-release

* chore: repin bundled plugins after the 1.0.0 re-release

* fix: recent activity rows use the same separator as host status

* feat: upload banner

* fix: reject expired sessions on sockets, api key desktop links and 192.0.0.0/24

* fix: plugin sdk 1.0.5 with optional ui peers, safer timeouts and validate checks

* fix: bundled plugins path for source runs and backend cleanup

* fix: load plugin strings on guest pages, plugin wording and drop unused keys

* fix: beta version stamping, homebrew cask bump, dockerignore and readme platforms

* docs: 26.10.0 changelog

* fix: current version in the version route example

* chore: stop pushing the release- docker tag

* feat: remove the donation reminder and its column

* docs: note the removed donation reminder

* fix: link server wizard always opens inline, onboarding card grows for it

* feat: plugin text columns are longtext on mysql so they are not capped at 64KB

* fix: linked desktops keep a plugin when its update from the server fails

* chore: default mobile download links to v tags

* fix: sso only accounts cannot sign in with a passkey or other local method

* feat: note in the plugins tab that community plugins are coming

* fix: plugin validate accepts beta versions

* chore: pin bundled plugins rebuilt on sdk 1.0.5

* fix: skip locale fetches for disabled plugins

* fix: show translated names for the built-in admin and user roles

* fix: show host keys as SHA256 fingerprints like ssh-keygen

* fix: host editor scrolls to a newly enabled protocol section

* fix: pass the host to dashboard host row plugins

* fix: plugin cards show dividers between author, version and category

* fix: refuse plugin archives with paths outside the plugin folder

* fix: theme picker shows one dropdown arrow

* fix: read activity and audit times as UTC so they show in local time

* fix: show the real error for a wrong password or code on plugin sign in

* fix: explain that external sign in needs Auto-create external accounts

* fix: clear leftover plugin unpack folders at startup

* fix: shared session links open for guests again

* fix: share panels no longer list yourself as a share target

* fix: a host update without an auth type keeps the saved password

* fix: guest pages get the tab ids a guest plugin registers

* fix: refuse a 2.8 database, which already had a plugins table

* fix: dashboard cards fit on a phone

* fix: desktop setup no longer asks to secure the local account

* fix: the desktop app keeps the desktop layout in a narrow window

* fix: the desktop app shows the rail and no phone bar in a narrow window

* fix: name every sync type in the Sync panel and pluralize the link prompt

* chore: update the sdk ui snapshot for the host key dialog

* fix: turning a plugin back on restarts the plugins it turned off

* fix: quick connect asks to trust the host key

* fix: revoking a manager also removes the shares they handed out

* fix: database export works for users with TOTP on (#1426)

* fix: rdp and vnc logins use the folder credential when none is picked (#1429)

* fix: imported hosts get status checks without a restart

* fix: re-pin bundled plugins after 1.0.0 re-release

* fix: re-pin bundled plugins after search bar fix

* chore: update app icon to smaller version

* chore: round the readme logo, keep square source for app icons

* feat: grid columns and table view in the plugins tab

* fix: match the host key copy button to the fingerprint height

* fix: use brand color for toggles in sync settings

* fix: re-pin bundled plugins after 1.0.0 re-release

* fix: web terminals failing auth behind a proxy that rewrites Host (Support#651)

* fix: re-pin snippets and termix-identity after 1.0.0 overwrite

* fix: re-pin file-manager after 1.0.0 overwrite

* fix: re-pin file-manager after 1.0.0 overwrite

* chore: remove Crowdin from the release workflow

* chore: remove crowdin config

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: ZacharyZcR <zacharyzcr1984@gmail.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
This commit is contained in:
authored and GitHub committed 2026-10-10 14:58:27 -05:00
1 parent a075ff4808
commit 0149260926
3656 files changed
+116210 -460999

No files matched your search

+75
View File
@@ -0,0 +1,75 @@
name: Beta feedback
description: A bug, rough edge or idea from a Termix beta
labels: [beta]
body:
- type: markdown
attributes:
value: |
Thanks for testing a beta. Short reports are fine. If you opened this from Termix, the version and setup are already filled in.
- type: input
id: termix-version
attributes:
label: Beta version
description: Shown in Settings under Updates, or your image tag.
placeholder: "e.g. 26.11.0-beta.2"
validations:
required: true
- type: textarea
id: what-happened
attributes:
label: What happened?
description: The bug, what felt off, or your idea. Screenshots help a lot.
validations:
required: true
- type: dropdown
id: regression
attributes:
label: Does this happen on the latest stable release?
options:
- "Not tested on stable"
- "Yes, it happens on stable too"
- "No, only on this beta"
- "Not a bug, just feedback"
validations:
required: false
- type: textarea
id: steps
attributes:
label: Steps to reproduce
description: As few steps as you can.
placeholder: |
1.
2.
3.
validations:
required: false
- type: textarea
id: beta-plugins
attributes:
label: Plugins on a beta
description: Filled in by Termix. One plugin and version per line.
render: text
validations:
required: false
- type: textarea
id: environment
attributes:
label: Environment
description: Browser or desktop app, OS, and how the server is installed.
validations:
required: false
- type: textarea
id: logs
attributes:
label: Logs
description: Errors from the browser console, `docker logs termix`, or the error Termix shows. This is shown as code, so no backticks needed.
render: text
validations:
required: false
- type: checkboxes
id: checks
attributes:
label: Before you submit
options:
- label: I searched open issues and this is not a duplicate
required: true
+107
View File
@@ -0,0 +1,107 @@
name: Bug report
description: Something in Termix itself is broken
labels: [bug]
type: Bug
body:
- type: markdown
attributes:
value: |
Thanks for the report. A maintainer reads every issue, even when there is no reply right away. We usually reply once there is a fix or a workaround.
If you came from **Report an issue** in Termix, your versions and environment are already filled in below.
Is it about a plugin (terminal, file manager, Docker, remote desktop and so on)? Report it on that plugin's repo instead. **Report an issue** on the plugin's page in Termix takes you there with your versions filled in.
- type: textarea
id: what-happened
attributes:
label: What happened?
description: Describe the bug. Screenshots help a lot.
validations:
required: true
- type: textarea
id: steps
attributes:
label: Steps to reproduce
description: As few steps as you can.
placeholder: |
1.
2.
3.
validations:
required: true
- type: textarea
id: expected
attributes:
label: What did you expect?
description: What should have happened instead?
validations:
required: false
- type: input
id: termix-version
attributes:
label: Termix version
description: Shown in Settings under About, or in your User Profile.
placeholder: "e.g. 26.10.0"
validations:
required: true
- type: dropdown
id: platform
attributes:
label: Platform
description: Where do you see the problem?
options:
- "Web browser"
- "Desktop app (Windows)"
- "Desktop app (macOS)"
- "Desktop app (Linux)"
- "Mobile app (iOS)"
- "Mobile app (Android)"
- "Other"
validations:
required: true
- type: dropdown
id: install-method
attributes:
label: Server install
description: How is the Termix server installed?
options:
- "Docker"
- "Manual build"
- "Proxmox community script"
- "Desktop app (built-in server)"
- "Not sure"
validations:
required: true
- type: textarea
id: environment
attributes:
label: Environment
description: Browser and OS, plus anything special about your setup (reverse proxy, database, SSO).
placeholder: |
Web, Chrome on Windows. Behind nginx, Postgres.
validations:
required: false
- type: textarea
id: logs
attributes:
label: Logs
description: Errors from the browser console, `docker logs termix`, or the error Termix shows. This is shown as code, so no backticks needed.
render: text
validations:
required: false
- type: textarea
id: context
attributes:
label: Anything else?
description: Links, config, or anything that might help.
validations:
required: false
- type: checkboxes
id: checks
attributes:
label: Before you submit
options:
- label: I searched open and closed issues and this is not a duplicate
required: true
- label: I am on the latest version, or I said which version I am on
required: true
+8 -5
View File
@@ -1,8 +1,11 @@
blank_issues_enabled: false
contact_links:
- name: Support Center
url: https://github.com/Termix-SSH/Support/issues
about: Report any feature requests or bugs in the support center
- name: Discord
- name: Problem with a plugin?
url: https://github.com/Termix-SSH/Termix#plugins
about: Each plugin has its own repo. Find it in this list, or use Report an issue on the plugin's page in Termix.
- name: Questions and chat
url: https://discord.gg/jVQGdvHDrf
about: Official Termix Discord server for general discussion (not recommended for support)
about: Ask the community on Discord. Bugs and ideas belong in issues.
- name: Business or priority support
url: https://termix.site/contact
about: For teams that need a faster response. Contact us to discuss terms.
@@ -0,0 +1,41 @@
name: Feature request
description: Suggest an idea for Termix itself
labels: [feature]
type: Feature
body:
- type: markdown
attributes:
value: |
Is it about a plugin (terminal, file manager, Docker, remote desktop and so on)? Report it on that plugin's repo instead. **Report an issue** on the plugin's page in Termix takes you there with your versions filled in.
- type: markdown
attributes:
value: |
Thanks for the idea. Explain the problem first, then how you would solve it.
- type: textarea
id: problem
attributes:
label: What problem would this solve?
description: What are you trying to do, and what gets in the way today?
validations:
required: true
- type: textarea
id: solution
attributes:
label: What would you like?
description: Describe how it should work.
validations:
required: true
- type: textarea
id: alternatives
attributes:
label: Alternatives
description: Anything you tried or considered instead.
validations:
required: false
- type: textarea
id: context
attributes:
label: Anything else?
description: Screenshots, mockups or links.
validations:
required: false
-91
View File
@@ -1,91 +0,0 @@
version: 2
updates:
# npm dependencies (single root package.json, no workspaces)
- package-ecosystem: "npm"
directory: "/"
schedule:
interval: "weekly"
day: "monday"
open-pull-requests-limit: 15
labels:
- "dependencies"
- "npm"
commit-message:
prefix: "chore"
prefix-development: "chore"
include: "scope"
groups:
dev-patch-updates:
dependency-type: "development"
update-types:
- "patch"
dev-minor-updates:
dependency-type: "development"
update-types:
- "minor"
prod-patch-updates:
dependency-type: "production"
update-types:
- "patch"
prod-minor-updates:
dependency-type: "production"
update-types:
- "minor"
# Major bumps grouped so they land as a single reviewable PR instead of
# one-per-package noise. These often need manual follow-up (Electron,
# React, Vite, Tailwind, Express 5, etc.).
major-updates:
update-types:
- "major"
ignore:
# typescript-eslint declares `typescript: >=4.8.4 <6.1.0`, and TypeScript 7
# removed `ts.Extension`, which @typescript-eslint/typescript-estree reads
# at import time. Bumping to 7 makes `eslint .` fail to load its own config,
# so `npm run lint` cannot run at all. Drop this once typescript-eslint
# supports TypeScript 7.
- dependency-name: "typescript"
update-types: ["version-update:semver-major"]
# Docker base images (docker/Dockerfile + docker-compose / compose-dev)
- package-ecosystem: "docker"
directory: "/docker"
schedule:
interval: "weekly"
day: "monday"
open-pull-requests-limit: 10
labels:
- "dependencies"
- "docker"
commit-message:
prefix: "chore"
include: "scope"
groups:
docker-patch-updates:
update-types:
- "patch"
docker-minor-updates:
update-types:
- "minor"
docker-major-updates:
update-types:
- "major"
# GitHub Actions used across the workflows in .github/workflows
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
day: "monday"
open-pull-requests-limit: 10
labels:
- "dependencies"
- "github-actions"
commit-message:
prefix: "ci"
include: "scope"
groups:
github-actions:
update-types:
- "patch"
- "minor"
- "major"
-1
View File
@@ -1 +0,0 @@
custom: https://donate.termix.site/
-32
View File
@@ -1,32 +0,0 @@
# Overview
_Short summary of what this PR does_
- [ ] Added: ...
- [ ] Updated: ...
- [ ] Removed: ...
- [ ] Fixed: ...
# Changes Made
_Detailed explanation of changes (if needed)_
- ...
# Related Issues
_Link any issues this PR addresses_
- Closes #ISSUE_NUMBER
- Related to #ISSUE_NUMBER
# Screenshots / Demos
_(Optional: add before/after screenshots, GIFs, or console output)_
# Checklist
- [ ] Code follows project style guidelines
- [ ] Supports mobile and desktop UI/app (if applicable)
- [ ] I have read [Contributing.md](https://github.com/Termix-SSH/Termix/blob/main/CONTRIBUTING.md)
- [ ] This is not a translation request. See [docs](https://docs.termix.site/translations)
-206
View File
@@ -1,206 +0,0 @@
name: Weekly Beta Release
on:
schedule:
- cron: "15 6 * * 1"
workflow_dispatch:
inputs:
dry_run:
description: "Build and test but do not push images, upload installers, or publish a release"
required: false
default: false
type: boolean
permissions:
contents: write
jobs:
prep:
runs-on: blacksmith-2vcpu-ubuntu-2404
outputs:
dev_branch: ${{ steps.dev.outputs.branch }}
beta_version: ${{ steps.dev.outputs.beta_version }}
sha: ${{ steps.dev.outputs.sha }}
steps:
- name: Checkout repository
uses: actions/checkout@v7
with:
fetch-depth: 1
- name: Setup Node.js
uses: actions/setup-node@v7
with:
node-version-file: ".nvmrc"
- name: Resolve newest dev branch and compute beta version
id: dev
env:
GH_TOKEN: ${{ secrets.TERMIX_PAT }}
run: |
REFS=$(gh api "repos/${{ github.repository }}/branches" --paginate -q '.[].name')
if DEV_BRANCH=$(printf '%s\n' "$REFS" | node scripts/latest-dev-branch.cjs 2>/dev/null); then
echo "Newest dev branch: $DEV_BRANCH"
else
echo "No dev-X.Y.Z branch open; nothing to snapshot for this week's beta."
echo "branch=" >> "$GITHUB_OUTPUT"
exit 0
fi
BASE_VERSION=$(node scripts/parse-dev-branch.cjs "$DEV_BRANCH")
BETA_VERSION="${BASE_VERSION}-beta.$(date -u +%Y%m%d)"
SHA=$(gh api "repos/${{ github.repository }}/branches/$DEV_BRANCH" -q .commit.sha)
echo "Beta version: $BETA_VERSION"
echo "branch=$DEV_BRANCH" >> "$GITHUB_OUTPUT"
echo "beta_version=$BETA_VERSION" >> "$GITHUB_OUTPUT"
echo "sha=$SHA" >> "$GITHUB_OUTPUT"
verify:
needs: [prep]
if: ${{ needs.prep.outputs.dev_branch != '' }}
runs-on: blacksmith-2vcpu-ubuntu-2404
steps:
- name: Checkout dev branch
uses: actions/checkout@v7
with:
ref: ${{ needs.prep.outputs.dev_branch }}
fetch-depth: 1
- name: Setup Node.js
uses: actions/setup-node@v7
with:
node-version-file: ".nvmrc"
cache: "npm"
- name: Install dependencies
run: npm ci
- name: Build plugin SDK
run: npm run build:sdk
- name: Run ESLint
run: npx eslint .
- name: Type check
run: npm run type-check
# Before the tests: the plugin boot tests load the built bundles.
- name: Build
run: npm run build
- name: Run unit tests
run: npm run test
- name: Run plugin tests
run: npm run test:plugins
create-release:
needs: [prep, verify]
if: ${{ needs.prep.outputs.dev_branch != '' && inputs.dry_run != true }}
runs-on: blacksmith-2vcpu-ubuntu-2404
permissions:
contents: write
steps:
- name: Checkout dev branch
uses: actions/checkout@v7
with:
ref: ${{ needs.prep.outputs.dev_branch }}
fetch-depth: 0
- name: Resolve previous beta commit
id: prev
env:
GH_TOKEN: ${{ secrets.TERMIX_PAT }}
run: |
SHA="${{ needs.prep.outputs.sha }}"
PREV_SHA=$(gh release view beta --repo ${{ github.repository }} --json targetCommitish -q .targetCommitish 2>/dev/null || true)
if [ -z "$PREV_SHA" ] || ! git cat-file -e "$PREV_SHA^{commit}" 2>/dev/null; then
PREV_SHA=$(git rev-parse -q --verify "refs/tags/beta^{commit}" || true)
fi
# The last beta may come from an older dev branch, so diff from where the two meet.
if [ -n "$PREV_SHA" ] && ! git merge-base --is-ancestor "$PREV_SHA" "$SHA"; then
PREV_SHA=$(git merge-base "$PREV_SHA" "$SHA" || true)
fi
echo "Previous beta commit: ${PREV_SHA:-none}"
echo "sha=$PREV_SHA" >> "$GITHUB_OUTPUT"
- name: Generate rolling beta release notes
run: |
PREV="${{ steps.prev.outputs.sha }}"
SHA="${{ needs.prep.outputs.sha }}"
MAX=150
if [ -z "$PREV" ]; then
CHANGES="- Could not find the previous beta commit."
else
LOG=$(git log --oneline --no-merges "$PREV..$SHA" -- . ':!package-lock.json')
COUNT=$(printf '%s' "$LOG" | grep -c . || true)
if [ "$COUNT" -eq 0 ]; then
CHANGES="- No new commits since the last beta."
else
CHANGES=$(printf '%s\n' "$LOG" | head -n "$MAX" | sed 's/^/- /')
if [ "$COUNT" -gt "$MAX" ]; then
CHANGES="$CHANGES
- ...and $((COUNT - MAX)) more"
fi
CHANGES="$CHANGES
[Full diff](https://github.com/${{ github.repository }}/compare/$PREV...$SHA)"
fi
fi
cat > BETA_RELEASE_BODY.md << EOF
> [!WARNING]
> This is an automated weekly beta build, snapshotted from the \`${{ needs.prep.outputs.dev_branch }}\` branch. It is not a stable release: it may contain unfinished features, regressions, or breaking changes, and this tag is overwritten every week. Do not run it in production.
>
> Found a bug? [Open a Beta Feedback report](https://github.com/Termix-SSH/Support/issues/new?template=beta_feedback.yml) and mention this build: \`${{ needs.prep.outputs.beta_version }}\`.
**Snapshot of:** \`${{ needs.prep.outputs.dev_branch }}\` @ \`${{ needs.prep.outputs.sha }}\`
**Docker image:** \`ghcr.io/lukegus/termix:beta\` / \`docker.io/bugattiguy527/termix:beta\` (rolling), or pin to \`:beta-${{ needs.prep.outputs.beta_version }}\` for this exact build.
**Built:** $(date -u +"%Y-%m-%d %H:%M UTC")
### Changes since last beta
$CHANGES
EOF
- name: Create or update rolling beta release
env:
GH_TOKEN: ${{ secrets.TERMIX_PAT }}
run: |
TAG="beta"
TITLE="Beta (rolling) - ${{ needs.prep.outputs.beta_version }}"
if gh release view "$TAG" --repo ${{ github.repository }} >/dev/null 2>&1; then
# --target only applies to a new tag, so move the existing one by hand.
gh api -X PATCH "repos/${{ github.repository }}/git/refs/tags/$TAG" \
-f sha="${{ needs.prep.outputs.sha }}" -F force=true >/dev/null
gh release edit "$TAG" --repo ${{ github.repository }} \
--title "$TITLE" --notes-file BETA_RELEASE_BODY.md \
--prerelease --target "${{ needs.prep.outputs.sha }}"
else
gh release create "$TAG" --repo ${{ github.repository }} \
--title "$TITLE" --notes-file BETA_RELEASE_BODY.md \
--prerelease --target "${{ needs.prep.outputs.sha }}"
fi
docker:
needs: [prep, verify, create-release]
if: ${{ always() && needs.prep.outputs.dev_branch != '' && needs.verify.result == 'success' && (needs.create-release.result == 'success' || needs.create-release.result == 'skipped') }}
uses: ./.github/workflows/docker.yml
with:
version: ${{ needs.prep.outputs.beta_version }}
build_type: Beta
dry_run: ${{ inputs.dry_run == true }}
source_ref: ${{ needs.prep.outputs.sha }}
secrets: inherit
electron-release:
needs: [prep, verify, create-release]
if: ${{ always() && needs.prep.outputs.dev_branch != '' && inputs.dry_run != true && needs.create-release.result == 'success' }}
uses: ./.github/workflows/electron.yml
with:
build_type: all
artifact_destination: release
release_tag: beta
version_override: ${{ needs.prep.outputs.beta_version }}
source_ref: ${{ needs.prep.outputs.sha }}
secrets: inherit
-83
View File
@@ -1,83 +0,0 @@
name: Crowdin Sync
on:
schedule:
- cron: "0 6 * * *"
workflow_dispatch:
inputs:
branch:
description: "Branch to sync translations into"
required: false
type: string
permissions:
contents: write
jobs:
crowdin:
runs-on: blacksmith-2vcpu-ubuntu-2404
steps:
- name: Resolve target branch
id: branch
run: |
BRANCH="${{ inputs.branch }}"
if [ -z "$BRANCH" ]; then
BRANCH="${{ github.event.repository.default_branch }}"
fi
echo "name=$BRANCH" >> "$GITHUB_OUTPUT"
- name: Checkout branch
uses: actions/checkout@v7
with:
ref: ${{ steps.branch.outputs.name }}
fetch-depth: 0
token: ${{ secrets.TERMIX_PAT }}
- name: Setup Node.js
uses: actions/setup-node@v7
with:
node-version-file: ".nvmrc"
- name: Upload sources to Crowdin
uses: crowdin/github-action@v3
with:
upload_sources: true
upload_translations: false
download_translations: false
create_pull_request: false
push_translations: false
token: ${{ secrets.CROWDIN_API_KEY }}
project_id: "858252"
env:
CROWDIN_API_TOKEN: ${{ secrets.CROWDIN_API_KEY }}
- name: Pre-translate untranslated strings
env:
CROWDIN_API_KEY: ${{ secrets.CROWDIN_API_KEY }}
run: node scripts/crowdin-pretranslate.cjs
- name: Download translations from Crowdin
uses: crowdin/github-action@v3
with:
upload_sources: false
upload_translations: false
download_translations: true
create_pull_request: false
push_translations: false
token: ${{ secrets.CROWDIN_API_KEY }}
project_id: "858252"
env:
CROWDIN_API_TOKEN: ${{ secrets.CROWDIN_API_KEY }}
- name: Commit translations
run: |
git config user.name "LukeGus"
git config user.email "bugattiguy527@gmail.com"
git add src/ui/locales/translated plugins/*/locales/translated
if git diff --cached --quiet; then
echo "No translation changes to commit."
exit 0
fi
git commit -m "chore: sync Crowdin translations"
git push origin HEAD:"${{ steps.branch.outputs.name }}"
-157
View File
@@ -1,157 +0,0 @@
name: Retarget and Merge Dependabot PRs
on:
schedule:
- cron: "0 6 * * *"
workflow_dispatch:
permissions:
contents: write
pull-requests: write
jobs:
retarget-and-merge:
runs-on: blacksmith-2vcpu-ubuntu-2404
steps:
- name: Checkout repository
uses: actions/checkout@v7
with:
fetch-depth: 1
- name: Resolve newest dev branch
id: dev
env:
GH_TOKEN: ${{ secrets.TERMIX_PAT }}
run: |
REFS=$(gh api "repos/${{ github.repository }}/branches" --paginate -q '.[].name')
# The helper exits non-zero when no dev-X.Y.Z branch exists; treat that
# as "nothing to do" rather than a workflow failure.
if DEV_BRANCH=$(printf '%s\n' "$REFS" | node scripts/latest-dev-branch.cjs 2>/dev/null); then
echo "Newest dev branch: $DEV_BRANCH"
echo "branch=$DEV_BRANCH" >> "$GITHUB_OUTPUT"
else
echo "No dev-X.Y.Z branch open; nothing to retarget."
echo "branch=" >> "$GITHUB_OUTPUT"
fi
- name: Retarget and merge Dependabot PRs
if: ${{ steps.dev.outputs.branch != '' }}
env:
GH_TOKEN: ${{ secrets.TERMIX_PAT }}
DEV_BRANCH: ${{ steps.dev.outputs.branch }}
REPO: ${{ github.repository }}
run: |
set -uo pipefail
CONFLICT_LABEL="dependabot-rebase-requested"
# Ensure the bookkeeping label exists (no-op if it already does).
gh label create "$CONFLICT_LABEL" --repo "$REPO" \
--color "D93F0B" --description "Retarget workflow asked Dependabot to rebase a conflicting PR" \
2>/dev/null || true
# True if the PR already carries the conflict label.
has_conflict_label() {
gh pr view "$1" --repo "$REPO" --json labels \
-q '.labels[].name' | grep -qx "$CONFLICT_LABEL"
}
# Wait until GitHub has a definite mergeable verdict for a PR (it
# returns UNKNOWN while recomputing after a base change or a push).
# Echoes "<mergeable> <mergeStateStatus>".
wait_for_verdict() {
local pr="$1" mergeable state
for _ in $(seq 1 30); do
read -r mergeable state < <(gh pr view "$pr" --repo "$REPO" \
--json mergeable,mergeStateStatus \
-q '.mergeable + " " + .mergeStateStatus')
if [ "$mergeable" != "UNKNOWN" ] && [ "$state" != "UNKNOWN" ]; then
echo "$mergeable $state"
return 0
fi
sleep 20
done
echo "$mergeable $state"
}
# Phase 1: retarget every open Dependabot PR from main onto the dev
# branch. This kicks off a Dependabot rebase for each.
PR_NUMBERS=$(gh pr list --repo "$REPO" \
--author "app/dependabot" \
--base main \
--state open \
--json number -q '.[].number')
# Pick up PRs already sitting on the dev branch from a previous run too.
PR_NUMBERS="$PR_NUMBERS $(gh pr list --repo "$REPO" \
--author "app/dependabot" \
--base "$DEV_BRANCH" \
--state open \
--json number -q '.[].number')"
PR_NUMBERS=$(printf '%s\n' $PR_NUMBERS | sort -un)
if [ -z "$PR_NUMBERS" ]; then
echo "No open Dependabot PRs to process."
exit 0
fi
for PR in $PR_NUMBERS; do
BASE=$(gh pr view "$PR" --repo "$REPO" --json baseRefName -q .baseRefName)
if [ "$BASE" != "$DEV_BRANCH" ]; then
echo "Retargeting PR #$PR ($BASE -> $DEV_BRANCH)"
gh pr edit "$PR" --repo "$REPO" --base "$DEV_BRANCH"
fi
done
# Phase 2: merge one at a time. Each merge can make the remaining npm
# PRs stale, so re-check immediately before merging and rebase stragglers.
for PR in $PR_NUMBERS; do
echo "::group::PR #$PR"
read -r MERGEABLE STATE < <(wait_for_verdict "$PR")
echo " mergeable=$MERGEABLE mergeStateStatus=$STATE"
# BEHIND = clean but needs the latest base; ask Dependabot to rebase
# and skip for now (next run merges it once it is up to date).
if [ "$STATE" = "BEHIND" ]; then
echo " PR #$PR is behind $DEV_BRANCH; asking Dependabot to rebase."
gh pr comment "$PR" --repo "$REPO" --body "@dependabot rebase"
echo "::endgroup::"
continue
fi
# DIRTY / CONFLICTING = a real conflict. Try a rebase once (label it so
# we can tell next time); if it is STILL conflicting on a later run
# despite already being labelled, the rebase failed for good - close it
# so Dependabot reopens a fresh PR against the current dev branch.
if [ "$MERGEABLE" = "CONFLICTING" ] || [ "$STATE" = "DIRTY" ]; then
if has_conflict_label "$PR"; then
echo " PR #$PR still conflicts after a prior rebase request; closing so Dependabot reopens it fresh."
gh pr close "$PR" --repo "$REPO" --delete-branch \
--comment "Closing: this PR still conflicts with $DEV_BRANCH after a rebase attempt (its changes are likely already merged). Dependabot will reopen a fresh PR computed against the current $DEV_BRANCH."
else
echo " PR #$PR conflicts with $DEV_BRANCH; requesting a rebase and labelling it."
gh pr edit "$PR" --repo "$REPO" --add-label "$CONFLICT_LABEL"
gh pr comment "$PR" --repo "$REPO" --body "@dependabot rebase"
fi
echo "::endgroup::"
continue
fi
# A clean PR that was previously flagged has recovered - drop the label.
if has_conflict_label "$PR"; then
gh pr edit "$PR" --repo "$REPO" --remove-label "$CONFLICT_LABEL" || true
fi
echo " Squash-merging PR #$PR"
if gh pr merge "$PR" --repo "$REPO" --squash --admin; then
echo " Merged PR #$PR"
# Give GitHub a moment to mark the now-stale siblings BEHIND.
sleep 15
else
echo " Could not merge PR #$PR now; it will be retried next run."
fi
echo "::endgroup::"
done
-76
View File
@@ -1,76 +0,0 @@
name: Deploy Helm
on:
workflow_dispatch:
inputs:
namespace:
description: "Kubernetes namespace"
required: true
default: termix
release:
description: "Helm release name"
required: true
default: termix
values_file:
description: "Values file to use"
required: true
default: charts/termix/values-gitops-example.yaml
image_tag:
description: "Image tag to deploy"
required: false
default: ""
permissions:
contents: read
jobs:
deploy:
runs-on: ubuntu-latest
environment: production
steps:
- name: Checkout
uses: actions/checkout@v7
- name: Set up Helm
uses: azure/setup-helm@v5
with:
version: v3.15.4
- name: Configure kubeconfig
env:
KUBE_CONFIG: ${{ secrets.KUBE_CONFIG }}
run: |
test -n "$KUBE_CONFIG"
echo "$KUBE_CONFIG" | base64 -d > "$RUNNER_TEMP/kubeconfig"
chmod 600 "$RUNNER_TEMP/kubeconfig"
- name: Lint chart
run: helm lint charts/termix
- name: Deploy
env:
KUBECONFIG: ${{ runner.temp }}/kubeconfig
IMAGE_TAG: ${{ inputs.image_tag }}
RELEASE_NAME: ${{ inputs.release }}
TARGET_NAMESPACE: ${{ inputs.namespace }}
VALUES_FILE: ${{ inputs.values_file }}
run: |
set -euo pipefail
[[ "$RELEASE_NAME" =~ ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ ]]
[[ "$TARGET_NAMESPACE" =~ ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ ]]
case "$VALUES_FILE" in
charts/termix/*.yaml) ;;
*) echo "values_file must be a YAML file under charts/termix" >&2; exit 1 ;;
esac
test -f "$VALUES_FILE"
ARGS=()
if [ -n "$IMAGE_TAG" ]; then
ARGS+=(--set "image.tag=$IMAGE_TAG")
fi
helm upgrade --install "$RELEASE_NAME" charts/termix \
--namespace "$TARGET_NAMESPACE" \
--create-namespace \
--values "$VALUES_FILE" \
--atomic \
--timeout 10m \
"${ARGS[@]}"
+44 -19
View File
@@ -4,7 +4,7 @@ on:
workflow_dispatch:
inputs:
version:
description: "Version to build (e.g., 1.8.0)"
description: "Version to build (e.g., 26.10.0)"
required: true
build_type:
description: "Build type"
@@ -22,11 +22,11 @@ on:
workflow_call:
inputs:
version:
description: "Version to build (e.g., 1.8.0)"
description: "Version to build (e.g., 26.10.0)"
required: true
type: string
build_type:
description: "Build type (Development or Production)"
description: "Build type (Development, Beta or Production)"
required: true
type: string
dry_run:
@@ -52,7 +52,14 @@ jobs:
fetch-depth: 1
- name: Resolve source revision
run: echo "SOURCE_SHA=$(git rev-parse HEAD)" >> "$GITHUB_ENV"
run: |
echo "SOURCE_SHA=$(git rev-parse HEAD)" >> "$GITHUB_ENV"
echo "BUILD_CREATED=$(date -u +%Y-%m-%dT%H:%M:%SZ)" >> "$GITHUB_ENV"
# The image reads its version from package.json, so a beta has to say so.
- name: Stamp beta version
if: ${{ inputs.build_type == 'Beta' }}
run: node scripts/sync-version.cjs --version "${{ inputs.version }}"
- name: Set up QEMU
uses: docker/setup-qemu-action@v4
@@ -74,25 +81,23 @@ jobs:
ALL_TAGS=()
if [ "$BUILD_TYPE" = "Production" ]; then
TAGS+=("release-$VERSION" "$VERSION" "latest")
for tag in "${TAGS[@]}"; do
ALL_TAGS+=("ghcr.io/lukegus/termix:$tag")
ALL_TAGS+=("docker.io/bugattiguy527/termix:$tag")
done
TAGS+=("$VERSION" "latest")
elif [ "$BUILD_TYPE" = "Beta" ]; then
TAGS+=("beta" "beta-$VERSION")
for tag in "${TAGS[@]}"; do
ALL_TAGS+=("ghcr.io/lukegus/termix:$tag")
ALL_TAGS+=("docker.io/bugattiguy527/termix:$tag")
done
TAGS+=("beta" "$VERSION")
else
TAGS+=("dev-$VERSION")
for tag in "${TAGS[@]}"; do
ALL_TAGS+=("ghcr.io/lukegus/termix:$tag")
done
fi
for tag in "${TAGS[@]}"; do
ALL_TAGS+=("ghcr.io/lukegus/termix:$tag")
ALL_TAGS+=("ghcr.io/termix-ssh/termix:$tag")
if [ "$BUILD_TYPE" != "Development" ]; then
ALL_TAGS+=("docker.io/bugattiguy527/termix:$tag")
fi
done
echo "ALL_TAGS=$(IFS=,; echo "${ALL_TAGS[*]}")" >> $GITHUB_ENV
echo "TAGS=${TAGS[*]}" >> $GITHUB_ENV
- name: Login to GHCR
if: ${{ !inputs.dry_run }}
@@ -107,7 +112,7 @@ jobs:
uses: docker/login-action@v4
with:
username: bugattiguy527
password: ${{ secrets.DOCKERHUB_TOKEN }}
password: ${{ secrets.DOCKERHUB_BUGATTIGUY527_TOKEN }}
- name: Build and push multi-arch image
uses: useblacksmith/build-push-action@v2
@@ -122,9 +127,29 @@ jobs:
labels: |
org.opencontainers.image.source=https://github.com/${{ github.repository }}
org.opencontainers.image.revision=${{ env.SOURCE_SHA }}
org.opencontainers.image.created=${{ github.run_id }}
org.opencontainers.image.created=${{ env.BUILD_CREATED }}
outputs: ${{ inputs.dry_run && 'type=cacheonly' || 'type=registry,compression=zstd' }}
- name: Login to Docker Hub as termixssh
id: termix_hub
if: ${{ (inputs.build_type == 'Production' || inputs.build_type == 'Beta') && !inputs.dry_run }}
env:
TOKEN: ${{ secrets.DOCKERHUB_TERMIXSSH_TOKEN }}
run: |
if [ -z "$TOKEN" ]; then
echo "DOCKERHUB_TERMIXSSH_TOKEN is not set, skipping docker.io/termixssh"
exit 0
fi
echo "$TOKEN" | docker login docker.io --username termixssh --password-stdin
echo "ready=true" >> "$GITHUB_OUTPUT"
- name: Copy image to docker.io/termixssh
if: ${{ steps.termix_hub.outputs.ready == 'true' }}
run: |
for tag in $TAGS; do
docker buildx imagetools create --tag "docker.io/termixssh/termix:$tag" "ghcr.io/termix-ssh/termix:$tag"
done
- name: Cleanup Docker
if: always()
run: |
+34 -18
View File
@@ -117,6 +117,11 @@ jobs:
- name: Install dependencies
run: npm ci
# A beta build has to know it is a beta, or it reports the stable version.
- name: Stamp beta version
if: inputs.version_override != ''
run: node scripts/sync-version.cjs --version "${{ inputs.version_override }}"
- name: Get version
id: package-version
run: |
@@ -220,6 +225,11 @@ jobs:
npm install --force @rollup/rollup-linux-arm64-gnu
npm install --force @rollup/rollup-linux-arm-gnueabihf
# A beta build has to know it is a beta, or it reports the stable version.
- name: Stamp beta version
if: inputs.version_override != ''
run: node scripts/sync-version.cjs --version "${{ inputs.version_override }}"
- name: Build Linux x64
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -355,8 +365,8 @@ jobs:
convert public/icon.png -resize 128x128 flatpak-build/icon-128.png
cd flatpak-build
sed -i "s|https://github.com/Termix-SSH/Termix/releases/download/release-VERSION_PLACEHOLDER-tag/termix_linux_x64_appimage.AppImage|file://$(realpath ../release/termix_linux_x64_appimage.AppImage)|g" com.karmaa.termix.yml
sed -i "s|https://github.com/Termix-SSH/Termix/releases/download/release-VERSION_PLACEHOLDER-tag/termix_linux_arm64_appimage.AppImage|file://$(realpath ../release/termix_linux_arm64_appimage.AppImage)|g" com.karmaa.termix.yml
sed -i "s|https://github.com/Termix-SSH/Termix/releases/download/vVERSION_PLACEHOLDER/termix_linux_x64_appimage.AppImage|file://$(realpath ../release/termix_linux_x64_appimage.AppImage)|g" com.karmaa.termix.yml
sed -i "s|https://github.com/Termix-SSH/Termix/releases/download/vVERSION_PLACEHOLDER/termix_linux_arm64_appimage.AppImage|file://$(realpath ../release/termix_linux_arm64_appimage.AppImage)|g" com.karmaa.termix.yml
sed -i "s/CHECKSUM_X64_PLACEHOLDER/$CHECKSUM_X64/g" com.karmaa.termix.yml
sed -i "s/CHECKSUM_ARM64_PLACEHOLDER/$CHECKSUM_ARM64/g" com.karmaa.termix.yml
sed -i "s/VERSION_PLACEHOLDER/$VERSION/g" com.karmaa.termix.metainfo.xml
@@ -382,7 +392,7 @@ jobs:
run: |
VERSION="${{ steps.flatpak-version.outputs.version }}"
cp packaging/flatpak/com.karmaa.termix.flatpakref release/
sed -i "s|VERSION_PLACEHOLDER|release-${VERSION}-tag|g" release/com.karmaa.termix.flatpakref
sed -i "s|VERSION_PLACEHOLDER|v${VERSION}|g" release/com.karmaa.termix.flatpakref
- name: Upload Flatpak bundle
uses: actions/upload-artifact@v7
@@ -429,6 +439,11 @@ jobs:
npm install dmg-license
node scripts/install-macos-sharp.cjs
# A beta build has to know it is a beta, or it reports the stable version.
- name: Stamp beta version
if: inputs.version_override != ''
run: node scripts/sync-version.cjs --version "${{ inputs.version_override }}"
- name: Check for Code Signing Certificates
id: check_certs
run: |
@@ -468,14 +483,15 @@ jobs:
security find-identity -v -p codesigning $KEYCHAIN_PATH
# Betas never go to the App Store, so they skip the MAS package.
- name: Build macOS App Store Package
if: steps.check_certs.outputs.has_certs == 'true'
id: mas_build
if: steps.check_certs.outputs.has_certs == 'true' && inputs.version_override == ''
env:
ELECTRON_BUILDER_ALLOW_UNRESOLVED_DEPENDENCIES: true
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NODE_OPTIONS: --max-old-space-size=4096
run: |
CURRENT_VERSION=$(node -p "require('./package.json').version")
BUILD_VERSION="${{ github.run_number }}"
npm run build && npx electron-builder --mac mas --universal --config.buildVersion="$BUILD_VERSION"
@@ -534,7 +550,7 @@ jobs:
NODE_OPTIONS: --max-old-space-size=4096
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
if [ "${{ steps.check_certs.outputs.has_certs }}" != "true" ]; then
if [ "${{ steps.mas_build.outcome }}" != "success" ]; then
npm run build
fi
npx electron-builder --mac dmg --universal --x64 --arm64 --publish never
@@ -555,7 +571,7 @@ jobs:
echo "path=$MAS_PKG" >> "$GITHUB_OUTPUT"
- name: Upload macOS MAS PKG
if: steps.check_certs.outputs.has_certs == 'true' && steps.locate_mas_pkg.outputs.path != '' && (inputs.artifact_destination == 'file' || inputs.artifact_destination == 'release' || inputs.artifact_destination == 'submit')
if: steps.mas_build.outcome == 'success' && steps.locate_mas_pkg.outputs.path != '' && (inputs.artifact_destination == 'file' || inputs.artifact_destination == 'release' || inputs.artifact_destination == 'submit')
uses: actions/upload-artifact@v7
with:
name: termix_macos_universal_mas
@@ -618,7 +634,7 @@ jobs:
sed -i '' "s/CHECKSUM_PLACEHOLDER/$CHECKSUM/g" homebrew-generated/termix.rb
sed -i '' "s|version \".*\"|version \"$VERSION\"|g" homebrew-generated/termix.rb
sed -i '' "s|sha256 \".*\"|sha256 \"$CHECKSUM\"|g" homebrew-generated/termix.rb
sed -i '' "s|release-[0-9.]*-tag|release-$VERSION-tag|g" homebrew-generated/termix.rb
sed -i '' "s|download/[^/]*/termix_macos|download/v$VERSION/termix_macos|g" homebrew-generated/termix.rb
- name: Upload Homebrew Cask as artifact
uses: actions/upload-artifact@v7
@@ -634,7 +650,7 @@ jobs:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
VERSION="${{ steps.homebrew-version.outputs.version }}"
RELEASE_TAG="release-$VERSION-tag"
RELEASE_TAG="v$VERSION"
gh release list --repo ${{ github.repository }} --limit 100 | grep -q "$RELEASE_TAG" || {
echo "Release $RELEASE_TAG not found"
@@ -673,7 +689,7 @@ jobs:
run: |
$VERSION = "${{ steps.package-version.outputs.version }}"
$MSI_NAME = "termix_windows_x64_msi.msi"
$DOWNLOAD_URL = "https://github.com/Termix-SSH/Termix/releases/download/release-$($VERSION)-tag/$($MSI_NAME)"
$DOWNLOAD_URL = "https://github.com/Termix-SSH/Termix/releases/download/v$($VERSION)/$($MSI_NAME)"
Write-Host "Downloading from $DOWNLOAD_URL"
New-Item -ItemType Directory -Force -Path "release_asset"
@@ -696,7 +712,7 @@ jobs:
$CHECKSUM = "${{ steps.msi-info.outputs.checksum }}"
$MSI_NAME = "${{ steps.msi-info.outputs.msi_name }}"
$DOWNLOAD_URL = "https://github.com/Termix-SSH/Termix/releases/download/release-$VERSION-tag/$MSI_NAME"
$DOWNLOAD_URL = "https://github.com/Termix-SSH/Termix/releases/download/v$VERSION/$MSI_NAME"
New-Item -ItemType Directory -Force -Path "choco-build"
Copy-Item -Path "packaging\chocolatey\*" -Destination "choco-build" -Recurse -Force
@@ -722,7 +738,7 @@ jobs:
choco pack termix-ssh.nuspec
if ($LASTEXITCODE -ne 0) {
throw "Chocolatey push failed with exit code $LASTEXITCODE"
throw "Chocolatey pack failed with exit code $LASTEXITCODE"
}
- name: Check for Chocolatey API Key
@@ -784,7 +800,7 @@ jobs:
mkdir -p release_assets
APPIMAGE_X64_NAME="termix_linux_x64_appimage.AppImage"
URL_X64="https://github.com/Termix-SSH/Termix/releases/download/release-$VERSION-tag/$APPIMAGE_X64_NAME"
URL_X64="https://github.com/Termix-SSH/Termix/releases/download/v$VERSION/$APPIMAGE_X64_NAME"
PATH_X64="release_assets/$APPIMAGE_X64_NAME"
echo "Downloading x64 AppImage from $URL_X64"
curl -L -o "$PATH_X64" "$URL_X64"
@@ -792,7 +808,7 @@ jobs:
CHECKSUM_X64=$(sha256sum "$PATH_X64" | awk '{print $1}')
APPIMAGE_ARM64_NAME="termix_linux_arm64_appimage.AppImage"
URL_ARM64="https://github.com/Termix-SSH/Termix/releases/download/release-$VERSION-tag/$APPIMAGE_ARM64_NAME"
URL_ARM64="https://github.com/Termix-SSH/Termix/releases/download/v$VERSION/$APPIMAGE_ARM64_NAME"
PATH_ARM64="release_assets/$APPIMAGE_ARM64_NAME"
echo "Downloading arm64 AppImage from $URL_ARM64"
curl -L -o "$PATH_ARM64" "$URL_ARM64"
@@ -918,7 +934,7 @@ jobs:
run: |
VERSION="${{ steps.package-version.outputs.version }}"
DMG_NAME="termix_macos_universal_dmg.dmg"
URL="https://github.com/Termix-SSH/Termix/releases/download/release-$VERSION-tag/$DMG_NAME"
URL="https://github.com/Termix-SSH/Termix/releases/download/v$VERSION/$DMG_NAME"
mkdir -p release_asset
DOWNLOAD_PATH="release_asset/$DMG_NAME"
@@ -952,8 +968,8 @@ jobs:
cp Casks/termix.rb homebrew-submission/Casks/t/termix.rb
sed -i '' "s/VERSION_PLACEHOLDER/$VERSION/g" homebrew-submission/Casks/t/termix.rb
sed -i '' "s/CHECKSUM_PLACEHOLDER/$CHECKSUM/g" homebrew-submission/Casks/t/termix.rb
sed -i '' "s|version \".*\"|version \"$VERSION\"|g" homebrew-submission/Casks/t/termix.rb
sed -i '' "s|sha256 \".*\"|sha256 \"$CHECKSUM\"|g" homebrew-submission/Casks/t/termix.rb
- name: Verify Cask syntax
run: |
@@ -1191,7 +1207,7 @@ jobs:
META_DIR="$RUNNER_TEMP/asc_metadata"
rm -rf "$META_DIR"
node scripts/generate-appstore-notes.cjs \
--notes RELEASE_NOTES.md \
--changelog CHANGELOG.md \
--out-dir "$META_DIR" \
--locales "en-US"
echo "metadata_path=$META_DIR" >> "$GITHUB_OUTPUT"
-73
View File
@@ -1,73 +0,0 @@
name: Helm
on:
pull_request:
paths:
- "charts/**"
- ".github/workflows/helm.yml"
push:
branches: [main]
paths:
- "charts/**"
- ".github/workflows/helm.yml"
workflow_dispatch:
inputs:
publish:
description: "Publish chart to GHCR OCI registry"
required: true
type: boolean
default: false
permissions:
contents: read
jobs:
lint:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v7
- name: Set up Helm
uses: azure/setup-helm@v5
with:
version: v3.15.4
- name: Lint chart
run: helm lint charts/termix
- name: Render default chart
run: helm template termix charts/termix --namespace termix
- name: Render GitOps example
run: helm template termix charts/termix --values charts/termix/values-gitops-example.yaml --namespace termix
- name: Render Traefik example
run: helm template termix charts/termix --values charts/termix/values-traefik.yaml --namespace termix
publish:
needs: lint
if: github.event_name == 'workflow_dispatch' && inputs.publish
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout
uses: actions/checkout@v7
- name: Set up Helm
uses: azure/setup-helm@v5
with:
version: v3.15.4
- name: Log in to GHCR
run: echo "${{ secrets.GITHUB_TOKEN }}" | helm registry login ghcr.io --username "${{ github.actor }}" --password-stdin
- name: Package chart
run: helm package charts/termix --destination .helm-packages
- name: Push chart
run: |
OWNER="$(echo "$GITHUB_REPOSITORY_OWNER" | tr '[:upper:]' '[:lower:]')"
helm push .helm-packages/termix-*.tgz "oci://ghcr.io/$OWNER/charts"
-32
View File
@@ -1,32 +0,0 @@
name: Generate OpenAPI Specification
on:
workflow_dispatch:
jobs:
generate-openapi:
name: Generate OpenAPI JSON
runs-on: blacksmith-2vcpu-ubuntu-2404
steps:
- name: Checkout repository
uses: actions/checkout@v7
- name: Setup Node.js
uses: actions/setup-node@v7
with:
node-version-file: ".nvmrc"
cache: "npm"
- name: Install dependencies
run: npm ci
- name: Generate OpenAPI specification
run: npm run generate:openapi
- name: Upload OpenAPI artifact
uses: actions/upload-artifact@v7
with:
name: openapi-spec
path: openapi.json
retention-days: 90
-13
View File
@@ -28,8 +28,6 @@ jobs:
run: npm run build:sdk
- name: Lint
# npm run lint, not npx eslint — the script also checks that the
# generated dialect schemas match schema.ts, which eslint cannot see.
run: npm run lint
- name: Run Prettier check
@@ -44,16 +42,10 @@ jobs:
- name: Run unit tests
run: npm run test
- name: Run plugin tests
run: npm run test:plugins
database-dialects:
name: Postgres and MySQL
runs-on: blacksmith-2vcpu-ubuntu-2404
# The test suite only ever sees SQLite. Everything that differs per engine —
# the RETURNING replacements, the read-then-write transactions, the
# migrations themselves — is only covered here, against real servers.
services:
postgres:
image: postgres:16
@@ -100,17 +92,12 @@ jobs:
- name: Build plugin SDK
run: npm run build:sdk
# Each run applies the migrations to an empty database first, so a
# migration that does not apply cleanly fails the build.
- name: Verify Postgres
run: npm run verify:dialect -- postgres://termix:termix@127.0.0.1:5432/termix_test
- name: Verify MySQL
run: npm run verify:dialect -- mysql://termix:termix@127.0.0.1:3306/termix_test
# The same repository suite the SQLite run executes, pointed at each
# engine. This is where a dialect difference in a query shows up as a
# failing assertion rather than as a bug report.
- name: Repository tests on Postgres
env:
TEST_DIALECT: postgres
-23
View File
@@ -1,23 +0,0 @@
name: Redirect issues
on:
issues:
types: [opened]
permissions:
issues: write
jobs:
close:
if: github.event.issue.author_association != 'OWNER' && github.event.issue.author_association != 'COLLABORATOR'
runs-on: blacksmith-2vcpu-ubuntu-2404
steps:
- uses: actions/github-script@v9
with:
script: |
const n = context.issue.number;
await github.rest.issues.createComment({
...context.repo, issue_number: n,
body: "Do not create issues here. Use the dedicated support repo and issue template system: https://github.com/Termix-SSH/Support"
});
await github.rest.issues.update({ ...context.repo, issue_number: n, state: "closed", state_reason: "not_planned" });
await github.rest.issues.lock({ ...context.repo, issue_number: n, lock_reason: "off-topic" });
+227 -237
View File
@@ -1,10 +1,24 @@
name: Release
# Stable: run by hand from a dev-X.Y.Z branch. Beta: run by hand from a dev
# branch, or let the Tuesday and Friday schedule snapshot the newest dev
# branch when it moved since the last beta.
on:
schedule:
- cron: "0 15 * * 2,5"
workflow_dispatch:
inputs:
channel:
description: "Stable release or beta"
required: true
default: Stable
type: choice
options:
- Stable
- Beta
mode:
description: "Release mode"
description: "Release mode (Overwrite release is stable only)"
required: true
default: Everything
type: choice
@@ -17,23 +31,45 @@ on:
permissions:
contents: write
concurrency:
group: release
cancel-in-progress: false
jobs:
prep:
runs-on: blacksmith-2vcpu-ubuntu-2404
outputs:
version: ${{ steps.info.outputs.version }}
release_tag: ${{ steps.info.outputs.release_tag }}
channel: ${{ steps.mode.outputs.channel }}
mode: ${{ steps.mode.outputs.mode }}
skip: ${{ steps.beta.outputs.skip || 'false' }}
version: ${{ steps.info.outputs.version || steps.beta.outputs.version }}
release_tag: ${{ steps.info.outputs.release_tag || steps.beta.outputs.release_tag }}
mobile_version: ${{ steps.info.outputs.mobile_version }}
dev_branch: ${{ steps.info.outputs.dev_branch }}
mobile_tag: ${{ steps.info.outputs.mobile_tag }}
dev_branch: ${{ steps.info.outputs.dev_branch || steps.beta.outputs.dev_branch }}
sha: ${{ steps.beta.outputs.sha }}
prev_ref: ${{ steps.beta.outputs.prev_ref }}
steps:
- name: Resolve channel and mode
id: mode
run: |
CHANNEL="${{ inputs.channel || 'Beta' }}"
MODE="${{ inputs.mode || 'Everything' }}"
echo "channel=$CHANNEL" >> "$GITHUB_OUTPUT"
echo "mode=$MODE" >> "$GITHUB_OUTPUT"
if [ "$CHANNEL" = "Beta" ] && [ "$MODE" = "Overwrite release" ]; then
echo "Overwrite release is for stable releases only."
exit 1
fi
- name: Guard branch (release modes)
if: ${{ inputs.mode != 'Overwrite release' && !startsWith(github.ref, 'refs/heads/dev-') }}
if: ${{ github.event_name == 'workflow_dispatch' && steps.mode.outputs.mode != 'Overwrite release' && !startsWith(github.ref, 'refs/heads/dev-') }}
run: |
echo "This mode must be run from a dev branch (got ${{ github.ref }})."
exit 1
- name: Guard branch (overwrite mode)
if: ${{ inputs.mode == 'Overwrite release' && github.ref != 'refs/heads/main' }}
if: ${{ steps.mode.outputs.mode == 'Overwrite release' && github.ref != 'refs/heads/main' }}
run: |
echo "Overwrite release must be run from main (got ${{ github.ref }})."
exit 1
@@ -50,10 +86,11 @@ jobs:
- name: Resolve versions
id: info
if: ${{ steps.mode.outputs.channel == 'Stable' }}
env:
GH_TOKEN: ${{ github.token }}
run: |
if [ "${{ inputs.mode }}" = "Overwrite release" ]; then
if [ "${{ steps.mode.outputs.mode }}" = "Overwrite release" ]; then
DEV_BRANCH=""
VERSION=$(node -p "require('./package.json').version")
else
@@ -62,26 +99,106 @@ jobs:
fi
echo "dev_branch=$DEV_BRANCH" >> "$GITHUB_OUTPUT"
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
echo "release_tag=release-$VERSION-tag" >> "$GITHUB_OUTPUT"
echo "release_tag=v$VERSION" >> "$GITHUB_OUTPUT"
MOBILE_RAW=$(gh release view -R Termix-SSH/Mobile --json tagName -q .tagName)
MOBILE_VERSION=$(echo "$MOBILE_RAW" | sed -E 's/^release-//; s/-tag$//')
MOBILE_VERSION=$(echo "$MOBILE_RAW" | sed -E 's/^release-//; s/-tag$//; s/^v//')
if [ -z "$MOBILE_VERSION" ]; then
echo "Failed to resolve the latest Termix-SSH/Mobile release version."
exit 1
fi
echo "mobile_version=$MOBILE_VERSION" >> "$GITHUB_OUTPUT"
echo "mobile_tag=$MOBILE_RAW" >> "$GITHUB_OUTPUT"
- name: Validate release notes
- name: Check CHANGELOG.md has notes for this version
if: ${{ steps.mode.outputs.channel == 'Stable' }}
run: |
node scripts/generate-release-body.cjs \
--version "${{ steps.info.outputs.version }}" \
--mobile-version "${{ steps.info.outputs.mobile_version }}" \
--notes RELEASE_NOTES.md > /dev/null
--mobile-tag "${{ steps.info.outputs.mobile_tag }}" > /dev/null
- name: Resolve beta version
id: beta
if: ${{ steps.mode.outputs.channel == 'Beta' }}
env:
GH_TOKEN: ${{ secrets.TERMIX_PAT }}
run: |
if [ "${{ github.event_name }}" = "schedule" ]; then
REFS=$(gh api "repos/${{ github.repository }}/branches" --paginate -q '.[].name')
if ! DEV_BRANCH=$(printf '%s\n' "$REFS" | node scripts/latest-dev-branch.cjs 2>/dev/null); then
echo "No dev-X.Y.Z branch is open, so no beta today."
echo "skip=true" >> "$GITHUB_OUTPUT"
exit 0
fi
else
DEV_BRANCH="${GITHUB_REF#refs/heads/}"
fi
BASE=$(node scripts/parse-dev-branch.cjs "$DEV_BRANCH")
SHA=$(gh api "repos/${{ github.repository }}/branches/$DEV_BRANCH" -q .commit.sha)
TAGS=$(gh api "repos/${{ github.repository }}/git/matching-refs/tags/v$BASE-beta." -q '.[].ref' | sed 's#^refs/tags/##')
LATEST=$(printf '%s\n' "$TAGS" | node scripts/beta-version.cjs "$BASE" --latest)
VERSION=$(printf '%s\n' "$TAGS" | node scripts/beta-version.cjs "$BASE")
if [ -n "$LATEST" ]; then
LATEST_SHA=$(gh api "repos/${{ github.repository }}/commits/$LATEST" -q .sha)
if [ "$LATEST_SHA" = "$SHA" ] && [ "${{ github.event_name }}" = "schedule" ]; then
echo "$DEV_BRANCH has not moved since $LATEST, so no beta today."
echo "skip=true" >> "$GITHUB_OUTPUT"
exit 0
fi
PREV="$LATEST"
else
PREV=$(gh release view --repo ${{ github.repository }} --json tagName -q .tagName 2>/dev/null || true)
fi
echo "Beta $VERSION from $DEV_BRANCH @ $SHA, changes since ${PREV:-nothing}"
echo "skip=false" >> "$GITHUB_OUTPUT"
echo "dev_branch=$DEV_BRANCH" >> "$GITHUB_OUTPUT"
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
echo "release_tag=v$VERSION" >> "$GITHUB_OUTPUT"
echo "sha=$SHA" >> "$GITHUB_OUTPUT"
echo "prev_ref=$PREV" >> "$GITHUB_OUTPUT"
verify-beta:
needs: [prep]
if: ${{ needs.prep.outputs.channel == 'Beta' && needs.prep.outputs.skip != 'true' }}
runs-on: blacksmith-2vcpu-ubuntu-2404
steps:
- name: Checkout beta commit
uses: actions/checkout@v7
with:
ref: ${{ needs.prep.outputs.sha }}
fetch-depth: 1
- name: Setup Node.js
uses: actions/setup-node@v7
with:
node-version-file: ".nvmrc"
cache: "npm"
- name: Install dependencies
run: npm ci
- name: Build plugin SDK
run: npm run build:sdk
- name: Lint
run: npm run lint
- name: Type check
run: npm run type-check
- name: Build
run: npm run build
- name: Run unit tests
run: npm run test
normalize:
needs: [prep]
if: ${{ inputs.mode != 'Overwrite release' }}
if: ${{ needs.prep.outputs.channel == 'Stable' && needs.prep.outputs.mode != 'Overwrite release' }}
runs-on: blacksmith-2vcpu-ubuntu-2404
steps:
- name: Checkout dev branch
@@ -110,16 +227,9 @@ jobs:
npm run format
npm run lint
# The plugin boot tests load the built bundles.
- name: Build plugins
run: npm run build:plugins
- name: Run unit tests
run: npm run test
- name: Run plugin tests
run: npm run test:plugins
- name: Sync version
run: node scripts/sync-version.cjs --version "${{ needs.prep.outputs.version }}"
@@ -133,7 +243,7 @@ jobs:
exit 0
fi
if [ "${{ inputs.mode }}" = "Dry run" ]; then
if [ "${{ needs.prep.outputs.mode }}" = "Dry run" ]; then
echo "DRY RUN: would commit and push the following changes to ${{ needs.prep.outputs.dev_branch }}:"
git --no-pager diff --stat
exit 0
@@ -143,9 +253,9 @@ jobs:
git commit -m "chore: lint, format, and bump version to ${{ needs.prep.outputs.version }}"
git push origin HEAD:"${{ needs.prep.outputs.dev_branch }}"
crowdin:
sync-main:
needs: [prep, normalize]
if: ${{ inputs.mode != 'Dry run' && inputs.mode != 'Overwrite release' }}
if: ${{ needs.prep.outputs.channel == 'Stable' && needs.prep.outputs.mode != 'Dry run' && needs.prep.outputs.mode != 'Overwrite release' }}
runs-on: blacksmith-2vcpu-ubuntu-2404
steps:
- name: Checkout dev branch
@@ -155,79 +265,27 @@ jobs:
fetch-depth: 0
token: ${{ secrets.TERMIX_PAT }}
- name: Setup Node.js
uses: actions/setup-node@v7
with:
node-version-file: ".nvmrc"
- name: Merge main into dev branch
run: |
git config user.name "LukeGus"
git config user.email "bugattiguy527@gmail.com"
git fetch origin main
git merge origin/main -X ours --no-edit || true
git push origin HEAD:"${{ needs.prep.outputs.dev_branch }}"
- name: Clean up stale Crowdin git integration branch
continue-on-error: true
env:
GH_TOKEN: ${{ secrets.TERMIX_PAT }}
run: |
PR_NUMBER=$(gh pr list --repo ${{ github.repository }} --head i18n_translate --state open --json number -q '.[0].number' || true)
if [ -n "$PR_NUMBER" ]; then
gh pr close "$PR_NUMBER" --repo ${{ github.repository }} --delete-branch || true
# -X ours leaves modify/delete conflicts, so settle those to the dev side too
if ! git merge origin/main -X ours --no-edit; then
git diff --name-only --diff-filter=U | while read -r f; do
if git cat-file -e "HEAD:$f" 2>/dev/null; then
git checkout --ours -- "$f" && git add -- "$f"
else
git rm -q -- "$f"
fi
done
git commit --no-edit
fi
git push origin --delete i18n_translate || true
- name: Upload sources to Crowdin
uses: crowdin/github-action@v3
with:
upload_sources: true
upload_translations: false
download_translations: false
create_pull_request: false
push_translations: false
token: ${{ secrets.CROWDIN_API_KEY }}
project_id: "858252"
env:
CROWDIN_API_TOKEN: ${{ secrets.CROWDIN_API_KEY }}
# Off until the machine translation credits are topped up.
- name: Pre-translate untranslated strings
if: false
env:
CROWDIN_API_KEY: ${{ secrets.CROWDIN_API_KEY }}
run: node scripts/crowdin-pretranslate.cjs
- name: Download translations from Crowdin
uses: crowdin/github-action@v3
with:
upload_sources: false
upload_translations: false
download_translations: true
create_pull_request: false
push_translations: false
token: ${{ secrets.CROWDIN_API_KEY }}
project_id: "858252"
env:
CROWDIN_API_TOKEN: ${{ secrets.CROWDIN_API_KEY }}
- name: Commit translations to dev branch
run: |
git config user.name "LukeGus"
git config user.email "bugattiguy527@gmail.com"
git add src/ui/locales/translated plugins/*/locales/translated
if git diff --cached --quiet; then
echo "No translation changes to commit."
exit 0
fi
git commit -m "chore: sync Crowdin translations for ${{ needs.prep.outputs.version }}"
git push origin HEAD:"${{ needs.prep.outputs.dev_branch }}"
merge-to-main:
needs: [prep, normalize, crowdin]
if: ${{ always() && needs.prep.result == 'success' && (needs.normalize.result == 'success' || needs.normalize.result == 'skipped') && (needs.crowdin.result == 'success' || needs.crowdin.result == 'skipped') }}
needs: [prep, normalize, sync-main]
if: ${{ always() && needs.prep.result == 'success' && needs.prep.outputs.channel == 'Stable' && (needs.normalize.result == 'success' || needs.normalize.result == 'skipped') && (needs.sync-main.result == 'success' || needs.sync-main.result == 'skipped') }}
runs-on: blacksmith-2vcpu-ubuntu-2404
outputs:
main_sha: ${{ steps.merge.outputs.main_sha }}
@@ -249,7 +307,7 @@ jobs:
node scripts/generate-release-body.cjs \
--version "${{ needs.prep.outputs.version }}" \
--mobile-version "${{ needs.prep.outputs.mobile_version }}" \
--notes RELEASE_NOTES.md > PR_BODY.md
--mobile-tag "${{ needs.prep.outputs.mobile_tag }}" > PR_BODY.md
- name: Open and squash-merge PR to main
id: merge
@@ -257,16 +315,16 @@ jobs:
GH_TOKEN: ${{ secrets.TERMIX_PAT }}
run: |
DEV_BRANCH="${{ needs.prep.outputs.dev_branch }}"
TITLE="release-${{ needs.prep.outputs.version }}"
TITLE="v${{ needs.prep.outputs.version }}"
if [ "${{ inputs.mode }}" = "Overwrite release" ]; then
if [ "${{ needs.prep.outputs.mode }}" = "Overwrite release" ]; then
echo "OVERWRITE: no merge; building from main as-is."
echo "build_ref=main" >> "$GITHUB_OUTPUT"
echo "main_sha=$(gh api repos/${{ github.repository }}/commits/main -q .sha)" >> "$GITHUB_OUTPUT"
exit 0
fi
if [ "${{ inputs.mode }}" = "Dry run" ]; then
if [ "${{ needs.prep.outputs.mode }}" = "Dry run" ]; then
echo "DRY RUN: would open and squash-merge a PR from $DEV_BRANCH into main."
echo "DRY RUN: downstream jobs will build from $DEV_BRANCH instead of main."
echo "build_ref=$DEV_BRANCH" >> "$GITHUB_OUTPUT"
@@ -295,18 +353,19 @@ jobs:
echo "build_ref=$MAIN_SHA" >> "$GITHUB_OUTPUT"
docker:
needs: [prep, merge-to-main]
needs: [prep, verify-beta, merge-to-main]
if: ${{ always() && needs.prep.result == 'success' && needs.prep.outputs.skip != 'true' && (needs.merge-to-main.result == 'success' || needs.verify-beta.result == 'success') }}
uses: ./.github/workflows/docker.yml
with:
version: ${{ needs.prep.outputs.version }}
build_type: Production
dry_run: ${{ inputs.mode == 'Dry run' }}
source_ref: ${{ needs.merge-to-main.outputs.build_ref }}
build_type: ${{ needs.prep.outputs.channel == 'Beta' && 'Beta' || 'Production' }}
dry_run: ${{ needs.prep.outputs.mode == 'Dry run' }}
source_ref: ${{ needs.merge-to-main.outputs.build_ref || needs.prep.outputs.sha }}
secrets: inherit
create-release:
needs: [prep, merge-to-main, docker]
if: ${{ inputs.mode != 'Dry run' }}
if: ${{ always() && needs.prep.outputs.channel == 'Stable' && needs.docker.result == 'success' && needs.prep.outputs.mode != 'Dry run' }}
runs-on: blacksmith-2vcpu-ubuntu-2404
permissions:
contents: write
@@ -323,7 +382,7 @@ jobs:
node-version-file: ".nvmrc"
- name: Clear existing release artifacts (overwrite mode)
if: ${{ inputs.mode == 'Overwrite release' }}
if: ${{ needs.prep.outputs.mode == 'Overwrite release' }}
env:
GH_TOKEN: ${{ secrets.TERMIX_PAT }}
run: |
@@ -338,40 +397,83 @@ jobs:
done
- name: Generate release body
if: ${{ inputs.mode != 'Overwrite release' }}
if: ${{ needs.prep.outputs.mode != 'Overwrite release' }}
run: |
node scripts/generate-release-body.cjs \
--version "${{ needs.prep.outputs.version }}" \
--mobile-version "${{ needs.prep.outputs.mobile_version }}" \
--notes RELEASE_NOTES.md > RELEASE_BODY.md
--mobile-tag "${{ needs.prep.outputs.mobile_tag }}" > RELEASE_BODY.md
- name: Create or update GitHub release
if: ${{ inputs.mode != 'Overwrite release' }}
if: ${{ needs.prep.outputs.mode != 'Overwrite release' }}
env:
GH_TOKEN: ${{ secrets.TERMIX_PAT }}
run: |
TAG="${{ needs.prep.outputs.release_tag }}"
TITLE="release-${{ needs.prep.outputs.version }}"
TITLE="Termix v${{ needs.prep.outputs.version }}"
if gh release view "$TAG" --repo ${{ github.repository }} >/dev/null 2>&1; then
gh release edit "$TAG" --repo ${{ github.repository }} --title "$TITLE" --notes-file RELEASE_BODY.md
else
gh release create "$TAG" --repo ${{ github.repository }} --title "$TITLE" --notes-file RELEASE_BODY.md --target "${{ needs.merge-to-main.outputs.main_sha }}"
fi
create-beta-release:
needs: [prep, docker]
if: ${{ always() && needs.prep.outputs.channel == 'Beta' && needs.docker.result == 'success' && needs.prep.outputs.mode != 'Dry run' }}
runs-on: blacksmith-2vcpu-ubuntu-2404
permissions:
contents: write
steps:
- name: Checkout beta commit
uses: actions/checkout@v7
with:
ref: ${{ needs.prep.outputs.sha }}
fetch-depth: 0
- name: Setup Node.js
uses: actions/setup-node@v7
with:
node-version-file: ".nvmrc"
- name: Generate beta release body
run: |
node scripts/generate-beta-body.cjs \
--version "${{ needs.prep.outputs.version }}" \
--branch "${{ needs.prep.outputs.dev_branch }}" \
--sha "${{ needs.prep.outputs.sha }}" \
--prev "${{ needs.prep.outputs.prev_ref }}" > BETA_BODY.md
- name: Create beta release
env:
GH_TOKEN: ${{ secrets.TERMIX_PAT }}
run: |
VERSION="${{ needs.prep.outputs.version }}"
TITLE="Termix v${VERSION%-beta.*} Beta ${VERSION##*-beta.}"
TAG="${{ needs.prep.outputs.release_tag }}"
if gh release view "$TAG" --repo ${{ github.repository }} >/dev/null 2>&1; then
gh release edit "$TAG" --repo ${{ github.repository }} \
--title "$TITLE" --notes-file BETA_BODY.md --prerelease
else
gh release create "$TAG" --repo ${{ github.repository }} \
--title "$TITLE" --notes-file BETA_BODY.md \
--prerelease --target "${{ needs.prep.outputs.sha }}"
fi
electron-release:
needs: [prep, merge-to-main, create-release]
if: ${{ always() && needs.merge-to-main.result == 'success' && (needs.create-release.result == 'success' || needs.create-release.result == 'skipped') }}
needs: [prep, merge-to-main, docker, create-release, create-beta-release]
if: ${{ always() && needs.prep.outputs.skip != 'true' && needs.docker.result == 'success' && needs.create-release.result != 'failure' && needs.create-beta-release.result != 'failure' }}
uses: ./.github/workflows/electron.yml
with:
build_type: all
artifact_destination: ${{ inputs.mode == 'Dry run' && 'file' || 'release' }}
artifact_destination: ${{ needs.prep.outputs.mode == 'Dry run' && 'file' || 'release' }}
release_tag: ${{ needs.prep.outputs.release_tag }}
source_ref: ${{ needs.merge-to-main.outputs.build_ref }}
version_override: ${{ needs.prep.outputs.channel == 'Beta' && needs.prep.outputs.version || '' }}
source_ref: ${{ needs.merge-to-main.outputs.build_ref || needs.prep.outputs.sha }}
secrets: inherit
electron-submit:
needs: [prep, merge-to-main, electron-release]
if: ${{ inputs.mode != 'Dry run' && inputs.mode != 'Skip submit' }}
if: ${{ needs.prep.outputs.channel == 'Stable' && needs.prep.outputs.mode != 'Dry run' && needs.prep.outputs.mode != 'Skip submit' }}
uses: ./.github/workflows/electron.yml
with:
build_type: all
@@ -381,7 +483,7 @@ jobs:
cask-commit-back:
needs: [prep, electron-release]
if: ${{ inputs.mode != 'Dry run' }}
if: ${{ needs.prep.outputs.channel == 'Stable' && needs.prep.outputs.mode != 'Dry run' }}
runs-on: blacksmith-2vcpu-ubuntu-2404
permissions:
contents: write
@@ -421,142 +523,37 @@ jobs:
git commit -m "chore: bump Homebrew cask to $VERSION"
git push origin HEAD:main
docs:
needs: [prep, merge-to-main]
if: ${{ always() && needs.merge-to-main.result == 'success' && inputs.mode != 'Overwrite release' }}
prune-betas:
needs: [prep, create-release]
if: ${{ always() && needs.prep.outputs.channel == 'Stable' && needs.create-release.result == 'success' && needs.prep.outputs.mode != 'Overwrite release' }}
runs-on: blacksmith-2vcpu-ubuntu-2404
steps:
- name: Checkout Termix
uses: actions/checkout@v7
with:
ref: ${{ needs.merge-to-main.outputs.build_ref }}
fetch-depth: 1
path: termix
- name: Setup Node.js
uses: actions/setup-node@v7
with:
node-version-file: "termix/.nvmrc"
cache: "npm"
cache-dependency-path: termix/package-lock.json
- name: Generate OpenAPI spec
working-directory: termix
run: |
npm ci
npm run generate:openapi
- name: Checkout Docs repository
uses: actions/checkout@v7
with:
repository: Termix-SSH/Docs
ref: main
fetch-depth: 0
token: ${{ secrets.TERMIX_PAT }}
path: docs-repo
- name: Create docs release branch
working-directory: docs-repo
run: |
BRANCH="dev-${{ needs.prep.outputs.version }}"
if git ls-remote --exit-code origin "refs/heads/$BRANCH" >/dev/null 2>&1; then
echo "Reusing existing docs branch $BRANCH."
git checkout -B "$BRANCH" "origin/$BRANCH"
else
git checkout -B "$BRANCH"
fi
- name: Overwrite OpenAPI spec and regenerate API docs
working-directory: docs-repo
run: |
cp ../termix/openapi.json static/openapi.json
npm ci
npm run docusaurus clean-api-docs termix
npm run docusaurus gen-api-docs termix
- name: Commit and push docs branch
working-directory: docs-repo
run: |
git config user.name "LukeGus"
git config user.email "bugattiguy527@gmail.com"
if git diff --quiet; then
echo "No docs changes to commit."
exit 0
fi
if [ "${{ inputs.mode }}" = "Dry run" ]; then
echo "DRY RUN: would commit and push the following docs changes to dev-${{ needs.prep.outputs.version }}:"
git --no-pager diff --stat
exit 0
fi
git add -A
git commit -m "feat: update API docs for ${{ needs.prep.outputs.version }}"
git push origin "dev-${{ needs.prep.outputs.version }}"
- name: Open and squash-merge docs PR
if: ${{ inputs.mode != 'Dry run' }}
working-directory: docs-repo
- name: Delete this version's beta releases (tags stay)
env:
GH_TOKEN: ${{ secrets.TERMIX_PAT }}
run: |
BRANCH="dev-${{ needs.prep.outputs.version }}"
if ! git ls-remote --exit-code origin "refs/heads/$BRANCH" >/dev/null 2>&1; then
echo "No docs branch pushed (nothing changed); skipping PR."
exit 0
fi
PREFIX="v${{ needs.prep.outputs.version }}-beta."
gh release list --repo ${{ github.repository }} --limit 200 --json tagName -q '.[].tagName' \
| grep -F "$PREFIX" | while read -r TAG; do
echo "Deleting release $TAG"
gh release delete "$TAG" --repo ${{ github.repository }} --yes || true
done
PR_NUMBER=$(gh pr list --repo Termix-SSH/Docs --head "$BRANCH" --base main --state open --json number -q '.[0].number' || true)
if [ -z "$PR_NUMBER" ]; then
PR_URL=$(gh pr create --repo Termix-SSH/Docs \
--base main --head "$BRANCH" \
--title "release-${{ needs.prep.outputs.version }}" \
--body "API docs for ${{ needs.prep.outputs.version }}")
PR_NUMBER=$(echo "$PR_URL" | grep -oE '[0-9]+$')
fi
if [ -z "$PR_NUMBER" ]; then
echo "Failed to find or create a PR for $BRANCH."
exit 1
fi
gh pr merge "$PR_NUMBER" --repo Termix-SSH/Docs --squash --admin
publish-youtube:
# The docs site reads the env catalog, API spec and download list from the
# newest stable release.
docs-refresh:
needs: [prep, electron-release]
if: ${{ always() && inputs.mode != 'Dry run' && inputs.mode != 'Overwrite release' && needs.electron-release.result == 'success' }}
if: ${{ always() && needs.prep.outputs.channel == 'Stable' && needs.electron-release.result == 'success' && needs.prep.outputs.mode != 'Dry run' }}
runs-on: blacksmith-2vcpu-ubuntu-2404
steps:
- name: Checkout main
uses: actions/checkout@v7
with:
ref: main
fetch-depth: 1
- name: Setup Node.js
uses: actions/setup-node@v7
with:
node-version-file: ".nvmrc"
- name: Set release video to public
- name: Tell the docs site
env:
YOUTUBE_CLIENT_ID: ${{ secrets.YOUTUBE_CLIENT_ID }}
YOUTUBE_CLIENT_SECRET: ${{ secrets.YOUTUBE_CLIENT_SECRET }}
YOUTUBE_REFRESH_TOKEN: ${{ secrets.YOUTUBE_REFRESH_TOKEN }}
run: node scripts/publish-youtube.cjs
GH_TOKEN: ${{ secrets.TERMIX_PAT }}
run: gh api repos/Termix-SSH/Docs/dispatches -f event_type=core-released
cleanup:
needs:
[
prep,
merge-to-main,
electron-release,
cask-commit-back,
docs,
publish-youtube,
]
if: ${{ always() && (inputs.mode == 'Everything' || inputs.mode == 'Skip submit') && needs.merge-to-main.result == 'success' && needs.docs.result == 'success' }}
needs: [prep, merge-to-main, electron-release, cask-commit-back]
if: ${{ always() && needs.prep.outputs.channel == 'Stable' && (needs.prep.outputs.mode == 'Everything' || needs.prep.outputs.mode == 'Skip submit') && needs.merge-to-main.result == 'success' }}
runs-on: blacksmith-2vcpu-ubuntu-2404
steps:
- name: Checkout repository
@@ -571,10 +568,3 @@ jobs:
run: |
git push https://x-access-token:${{ secrets.TERMIX_PAT }}@github.com/${{ github.repository }}.git \
--delete "${{ needs.prep.outputs.dev_branch }}" || true
- name: Delete dev branch in Docs
env:
GH_TOKEN: ${{ secrets.TERMIX_PAT }}
run: |
git push https://x-access-token:${{ secrets.TERMIX_PAT }}@github.com/Termix-SSH/Docs.git \
--delete "dev-${{ needs.prep.outputs.version }}" || true