# Contributing to TuxMate
## Quick Navigation
1. [Project Overview](#project-overview)
2. [Development Workflow](#development-workflow)
* [Initial Setup](#initial-setup)
* [Running with Docker](#running-with-docker)
* [Quality Assurance](#quality-assurance)
* [Verifying Install Scripts](#verifying-install-scripts)
3. [Adding Applications](#adding-applications)
* [Research Protocol](#1-mandatory-research-protocol)
* [Entry Structure](#2-entry-structure)
* [Unavailable Reason](#3-unavailable-reason-guidelines)
* [Platform Rules](#4-platform-specific-rules)
* [Arch Linux](#arch-linux)
* [NixOS](#nixos)
* [Ubuntu/Debian](#ubuntudebian)
* [Flatpak](#flatpak)
* [Snap](#snap)
* [Homebrew](#homebrew)
* [Universal Targets](#universal-targets-npm--script)
* [Icon System](#5-icon-system)
* [Valid Categories](#6-valid-categories)
4. [Adding Distributions](#adding-distributions)
5. [Pull Request Checklist](#pull-request-checklist)
* [Core Principles](#core-principles)
* [Verification Steps](#verification-steps)
6. [Templates](#templates)
* [Pull Request](#pull-request-template)
* [Bug Report](#issue-template-bug-report)
---
## Project Overview
* `src/lib/apps/*.json`: Main registry for applications (split by category).
* `src/lib/data.ts`: Main registry for distributions, categories, and Typescript types.
* `src/lib/aur-packages.json`: Whitelist for AUR packages that lack standard suffixes.
* `src/lib/nix-unfree.json`: Registry for unfree Nix packages.
* `src/lib/verified-flatpaks.json`: Auto-generated list of verified Flathub apps. **Do not edit.**
* `src/lib/verified-snaps.json`: Manual whitelist of verified Snap publishers.
---
## Development Workflow
### Initial Setup
```bash
git clone https://github.com/abusoww/tuxmate.git
cd tuxmate
npm install
npm run dev
```
The app will be available at `http://localhost:3000`.
Running with Docker
You can run the full application using the official Docker image:
```bash
docker run -p 3000:80 ghcr.io/abusoww/tuxmate:latest
```
Quality Assurance
Always run these before pushing:
* `npm run lint`: Check for code style issues.
* `npm run test`: Run unit test suite.
* `npm run build`: Verify production build.
Verifying Install Scripts
To verify that the *scripts generated by TuxMate* work correctly, run them in a clean container environment. This prevents messing up your local system.
1. Generate a script in the TuxMate UI (Dev mode).
2. Copy the script.
3. Run the corresponding distro container commands below and paste the script.
```bash
# Arch Linux
docker run -it --rm archlinux:latest bash -c "pacman -Sy && bash"
# Ubuntu
docker run -it --rm ubuntu:latest bash -c "apt update && bash"
# Fedora
docker run -it --rm fedora:latest bash -c "dnf check-update; bash"
```
---
## Adding Applications
All applications are defined in category-specific JSON files within [`src/lib/apps/`](src/lib/apps/).
### 1. Mandatory Research Protocol
**You MUST verify every package on these official sources before submitting:**
| Source | Scope | URL |
| :--- | :--- | :--- |
| **Repology** | Global Index | [repology.org](https://repology.org/) |
| **Arch Linux** | Official Repos | [archlinux.org/packages](https://archlinux.org/packages/) |
| **AUR** | User Repo | [aur.archlinux.org](https://aur.archlinux.org/) |
| **Debian** | Official Repos | [packages.debian.org](https://packages.debian.org/) |
| **Ubuntu** | Official Repos | [packages.ubuntu.com](https://packages.ubuntu.com/) |
| **Fedora** | Official Repos | [packages.fedoraproject.org](https://packages.fedoraproject.org/) |
| **OpenSUSE** | Official Repos | [software.opensuse.org](https://software.opensuse.org/) |
| **Nix** | Nixpkgs | [search.nixos.org](https://search.nixos.org/packages) |
| **Flathub** | Flatpaks | [flathub.org](https://flathub.org/) |
| **Snapcraft** | Snaps | [snapcraft.io](https://snapcraft.io/) |
| **Homebrew** | CLI & Casks | [formulae.brew.sh](https://formulae.brew.sh/) |
### 2. Entry Structure
```json
{
"id": "app-id", // Unique, lowercase, kebab-case
"name": "App Name", // Official display name
"description": "Short description", // Max ~60 characters
"category": "Category", // Must match valid categories
"icon": { // See Icon System section
"type": "iconify",
"set": "simple-icons",
"name": "python",
"color": "#3776AB"
},
"targets": {
"ubuntu": "exact-package-name", // apt package (official repos ONLY)
"arch": "exact-package-name", // pacman OR AUR package name
"flatpak": "com.vendor.AppId", // FULL Flatpak App ID (reverse DNS)
"snap": "snap-name", // Add --classic if needed
"homebrew": "formula-name", // Formula (CLI) or '--cask name' (GUI)
"npm": "@scope/package-name", // Global npm install (universal fallback)
"script": "curl -fsSL ... | bash" // Custom install script (universal fallback)
},
"note": "Context for universal targets", // Shown on hover for universal-fallback apps
"unavailableReason": "Markdown install instructions"
}
```
> [!NOTE]
> **Priority system**: Native distro targets (e.g., `arch`, `ubuntu`) always take precedence over universal targets (`npm`, `script`). If an app has both `arch: "ollama"` and `script: "curl ..."`, the script is only used when the user selects a distro where no native package exists.
### 3. Unavailable Reason Guidelines
This field renders Markdown when a target is missing. It serves as the manual fallback instruction.
**Requirements:**
1. **Actionable**: Avoid "Not available". State the alternative (e.g., "Install via Flatpak").
2. **Linked**: Hard-link the solution (Flathub page, upstream .deb URL, or Wiki guide).
3. **Format**: Use standard Markdown for links `[text](url)` and code `` `cmd` ``.
**Examples:**
| Status | Message | Why? |
| :--- | :--- | :--- |
| ❌ **Bad** | `'Not available.'` | Dead end. No solution provided. |
| ❌ **Bad** | `'Download from website.'` | Dead end. No solution provided. |
| ✅ **Good** | `'Not in official repos. Use the [Flatpak version](https://flathub.org/apps/com.spotify.Client) instead.'` | Directs to the preferred supported alternative. |
| ✅ **Good** | ```'Arch requires [multilib](https://wiki.archlinux.org/title/Official_repositories#multilib) enabled: uncomment `[multilib]` in `/etc/pacman.conf`, run `sudo pacman -Syu`, then `sudo pacman -S steam`.'``` | Exact steps to enable the required repo. |
### 4. Platform Specific Rules
#### Arch Linux
* **Official Packages**: Use the package name directly if found in `core` or `extra` (e.g., `firefox`).
* **Automatic AUR Detection**: `src/lib/aur.ts` automatically detects suffixes `-bin`, `-git`, or `-appimage`. Use the name directly (e.g., `brave-bin`).
* **Manual AUR Detection**: For AUR packages without suffixes (e.g., `google-chrome`), **you must add the name to** `src/lib/aur-packages.json`.
* Prefer `-bin` suffix packages in AUR (pre-built, faster install)
#### NixOS
Nixpkgs requires explicit user consent for unfree software.
1. Check the license on [search.nixos.org](https://search.nixos.org/packages).
2. If the license is unfree, add it to `src/lib/nix-unfree.json` if missing.
3. Add the package to the appropriate JSON file in `src/lib/apps/` normally.
#### Ubuntu/Debian
**Strict Repository Policy**: The generation scripts do **not** enable extra repositories (like PPAs or `non-free` by default). Packages must be available in the standard enabled repositories.
* **Allowed**:
* **Ubuntu**: Main, Restricted
* **Debian**: Main
* **Prohibited**:
* PPAs (Personal Package Archives).
* External `.deb` URLs.
* Packages requiring manual `sources.list` modification (unless detailed in `unavailableReason`).
#### Flatpak
* **ID Format**: Always use the full **Application ID** (reverse-DNS style).
* ✅ Correct: `org.mozilla.firefox`
* ❌ Wrong: `firefox`
* **Verification**: Find the exact ID at the bottom of the app's [Flathub page](https://flathub.org/).
* **Note**: `verified-flatpaks.json` is auto-generated; do not edit it manually.
#### Snap
* **Classic Confinement**: If the snap requires classic confinement (access to host system files), append `--classic`.
* Example: `code --classic`
* Check the install command on [snapcraft.io](https://snapcraft.io/) to confirm.
* **Verification**: If the publisher has a **"Verified" badge** on [Snapcraft](https://snapcraft.io/):
* Add the **publisher name** to `src/lib/verified-snaps.json`.
* This enables the "Verified" badge in the TuxMate UI.
#### Homebrew
Homebrew (macOS/Linux) has two package types. Check [formulae.brew.sh](https://formulae.brew.sh/) to find the correct one.
* **Formula**: Standard CLI tools and libraries.
* Usage: Use the package name directly.
* Example: `'wget'`, `'node'`, `'python@3.12'`
* **Cask**: GUI applications and large binaries (macOS only).
* Usage: Prefix with `--cask ` (note the space).
* Example: `'--cask firefox'`, `'--cask visual-studio-code'`
* **Validation**:
* Run `brew search ` locally to confirm type.
* We skip `--cask` targets on Linux installs automatically.
#### Universal Targets (npm & script)
Universal targets provide cross-distro installation via package managers or custom scripts. They serve as **fallbacks** — only used when no native distro target exists for the selected distribution.
* **`npm`**: Install via `npm install -g`. Requires Node.js runtime on the system.
* **`script`**: Raw shell command (typically a `curl | bash` installer). Runs directly.
> [!IMPORTANT]
> **Native targets always take priority.** If an app defines `arch: "ollama"` alongside `script: "curl ..."`, the script target is completely ignored when Arch is selected. The fallback only activates for distros without a native package.
**When to use each:**
| Target | Use Case | Example |
| :--- | :--- | :--- |
| `npm` | CLI tools distributed via npmjs.com | `"npm": "@google/gemini-cli"` |
| `script` | Apps with official install scripts | `"script": "curl -fsSL https://ollama.com/install.sh \| sh"` |
**Real examples from the codebase:**
```json
// Ollama: native on arch/fedora/nix/homebrew, falls back to script on ubuntu/debian
{
"id": "ollama",
"targets": {
"fedora": "ollama",
"arch": "ollama",
"nix": "ollama",
"homebrew": "ollama",
"script": "curl -fsSL https://ollama.com/install.sh | sh"
},
"note": "Falls back to official installer (ollama.com/install.sh) on distros without a native package."
}
// Gemini CLI: npm-only (plus homebrew)
{
"id": "gemini-cli",
"targets": {
"npm": "@google/gemini-cli",
"homebrew": "gemini-cli"
},
"note": "Requires Node.js runtime. Installed globally via npm where native packages are unavailable."
}
```
**The `note` field:**
* Used to explain the universal target behavior to users (shown on hover).
* **Required** when using `npm` or `script` targets.
* Keep concise and professional. Examples:
* ✅ `"Falls back to official installer (ollama.com/install.sh) on distros without a native package."`
* ✅ `"Requires Node.js runtime. Installed globally via npm where native packages are unavailable."`
* ❌ `"Installed globally via npm."` *(too terse, doesn't explain when or why)*
**Script safety rules:**
1. Only use **official** installer scripts from the app's own domain.
2. Always use `curl -fsSL` flags (fail silently on errors, follow redirects, show errors).
3. Never combine multiple pipes or add `sudo` — the install script handles privileges itself.
4. Verify the script URL is stable and maintained by the upstream project.
### 5. Icon System
Every app needs an icon! Our JSON format makes it super simple to add icons using [Iconify](https://iconify.design/).
We store icons as structured objects in the JSON. You just need to provide the set, name, and color:
```json
"icon": {
"type": "iconify",
"set": "simple-icons",
"name": "python",
"color": "#3776AB"
}
```
#### The 3 Main Icon Sets We Use:
1. **Simple Icons** (`"set": "simple-icons"`)
* Used for major brands and single-color logos (like Discord or Python).
* *You must provide a hex `"color"` for these.*
2. **Logos** (`"set": "logos"`)
* Used when an app has a multi-colored, official logo.
* *You don't need a `"color"` for these.*
3. **Material Design** (`"set": "mdi"`)
* Used for generic utilities (like a terminal or a magnifying glass).
* *You must provide a hex `"color"` for these.*
#### Can't find it on Iconify?
If an app's icon isn't on Iconify, you can use a direct link to an image (SVG preferred, or a high-res PNG).
Just change the `"type"` to `"url"`:
```json
"icon": {
"type": "url",
"url": "https://raw.githubusercontent.com/..."
}
```
* ✅ **DO:** Use a stable link (like the app's official GitHub repo raw image, or Wikimedia).
* ❌ **DON'T:** Use temporary image hosts (like Imgur) or hotlink-protected websites.
### 6. Valid Categories
Use **exactly** one of these:
* Web Browsers • Communication • Media • Creative • Gaming • Office
* Dev: Languages • Dev: Editors • Dev: Tools
* Terminal • CLI Tools • AI Tools • VPN & Network • Security • File Sharing • System
---
## Adding Distributions
Adding a new distribution involves three main steps:
### 1. Register the Distribution
Edit [`src/lib/data.ts`](src/lib/data.ts):
1. Add the new ID to the `DistroId` type definition.
2. Add a new object to the `distros` array with:
* `id`: unique identifier.
* `name`: Display name.
* `iconUrl`: Icon for the distro selector.
* `color`: Brand color.
* `installPrefix`: The default command prefix (e.g., `sudo dnf install -y`).
### 2. Create the Generator Script
Create a new file `src/lib/scripts/.ts`. This file must export a function (e.g., `generateFedoraScript`) that takes `PackageInfo[]` and returns the generated shell script string.
* Use helpers from `shared.ts` like `generateAsciiHeader` and `PackageInfo`.
* Implement logic to check if a package is already installed.
* Implement the installation command loop using `with_retry` for robustness.
* See `src/lib/scripts/fedora.ts` or `ubuntu.ts` for clean examples.
### 3. Integrate the Generator
1. Export your new function in [`src/lib/scripts/index.ts`](src/lib/scripts/index.ts).
2. Import it in [`src/lib/generateInstallScript.ts`](src/lib/generateInstallScript.ts).
3. Add a case for your `distroId` in the `switch` statement inside `generateInstallScript`.
4. Also add the simple one-liner command logic in `generateSimpleCommand` within the same file.
---
## 🔀 Pull Request Checklist
### Core Principles
> [!IMPORTANT]
> **Your PR will be rejected if you violate these rules:**
>
> 1. **Verify Everything**: Submit only verified package names. Guessing is prohibited.
> 2. **Official First**: Use official repository packages over third-party options.
> 3. **No Unofficial Repos**: Do not include PPAs, COPRs, or unofficial repositories.
> 4. **Full IDs**: Use full IDs for Flatpaks (e.g., `org.mozilla.firefox`).
> 5. **Strict Casing**: Package names are case-sensitive.
> 6. **Link Integrity**: Ensure all links in `unavailableReason` are direct and working.
### Verification Steps
**Verify before submitting:**
- [ ] Package names verified on official search pages (Repology, Arch, etc).
- [ ] Case sensitivity checked (especially openSUSE).
- [ ] Arch packages verified (Official vs AUR).
- [ ] No PPAs used for Debian/Ubuntu; Main/Universe only.
- [ ] Flatpak IDs are full reverse-DNS style.
- [ ] Snap `--classic` flag verification.
- [ ] Nix unfree packages added to JSON.
- [ ] Homebrew Casks prefixed correctly.
- [ ] Universal targets: `npm`/`script` only used as fallbacks, `note` field provided.
- [ ] Script URLs verified as official, stable endpoints.
- [ ] `npm run lint` & `npm run test` passed.
---
## 📝 Templates
### Pull Request Template
```markdown
## Summary
Brief description of changes.
## Changes
| App Name | Category | Sources |
|----------|----------|---------|
| Example | Dev: Tool| apt, pacman |
## Verification
> Package names verified against official sources.
| Source | Link |
|--------|------|
| Repology | [Link](...) |
| Arch | [Link](...) |
| ... | ... |
## Testing
- [ ] `npm run dev` working
- [ ] `npm run build` passed
- [ ] `npm run test` passed
- [ ] `npm run lint` passed
## Screenshots (if applicable)
```
### Issue Template (Bug Report)
```markdown
## 🐛 Bug Report
**Environment**:
- OS: [e.g. Arch Linux]
- Browser: [e.g. Firefox 120]
**Steps to Reproduce**:
1. ...
2. ...
**Details**:
**Logs/Screenshots**:
[Paste console logs or attach screenshots]
```