Skip to content

Latest commit

Β 

History

38 Commits

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

🐱 install.cat

Install any CLI tool with one command. No package managers, no setup β€” just curl and go.

curl -fsSL install.cat/user/repo | sh

That's it. Your users don't need to remember raw GitHub URLs, add https://, or type .sh. It just works.


Usage

Linux & macOS

curl -fsSL install.cat/verseles/xpm | sh

Or with wget:

wget -O- install.cat/verseles/xpm | sh

Windows (PowerShell)

irm install.cat/verseles/xpm | iex

Android

Just open install.cat/user/repo in your Android browser β€” the APK from the latest GitHub release is downloaded automatically, matched to your device architecture.

iOS

Open install.cat/user/repo on an iPhone or iPad β€” if an IPA is found in the latest release, you'll see download instructions with sideload info (AltStore/Sideloadly). You can also use ios.txt to redirect to the App Store or TestFlight.

For your own project

  1. Add an install.sh to the root of your repo (for Linux/macOS)
  2. Add an install.ps1 to the root of your repo (for Windows)
  3. For Android: publish APKs in your GitHub releases (naming convention: {repo}-android-{arch}.apk, or any .apk). Priority chain: android.html β†’ android.txt β†’ APK in releases β†’ GitHub repo page.
  4. For iOS: publish IPAs in your GitHub releases, or add an ios.txt with an App Store/TestFlight URL. Priority chain: ios.html β†’ ios.txt β†’ IPA in releases β†’ GitHub repo page.
  5. Tell your users:
curl -fsSL install.cat/your-username/your-repo | sh

Done. No registration, no config, no tokens.

Custom redirect files (android.txt / ios.txt)

Add a plain text file to your repo root containing a single URL on the first line. The user will be redirected there automatically:

# android.txt β€” redirect to Google Play
https://play.google.com/store/apps/details?id=com.example.app

# ios.txt β€” redirect to App Store
https://apps.apple.com/app/example/id123456789

Works with any URL: Google Play, F-Droid, App Store, TestFlight, direct downloads, etc.


How it works

install.cat runs entirely on Cloudflare Redirect Rules β€” there is no server, no backend, no application code processing requests. The domain uses Cloudflare's edge network with a set of dynamic redirect rules that route requests based on the User-Agent header:

User-Agent Redirect target
curl raw.githubusercontent.com/{user}/{repo}/HEAD/install.sh
wget raw.githubusercontent.com/{user}/{repo}/HEAD/install.sh
PowerShell raw.githubusercontent.com/{user}/{repo}/HEAD/install.ps1
Android install.cat/android.html?gh={user}/{repo} (detects arch, finds APK)
iPhone / iPad install.cat/ios.html?gh={user}/{repo} (App Store / IPA sideload)
Browser (default) github.com/{user}/{repo}/

The full ruleset:

  1. HTTP β†’ HTTPS β€” 301 redirect, ensures all traffic is encrypted
  2. www β†’ root β€” 301 redirect, canonical domain normalization
  3. curl β†’ install.sh β€” 302 redirect to raw script on GitHub
  4. wget β†’ install.sh β€” 302 redirect to raw script on GitHub
  5. PowerShell β†’ install.ps1 β€” 302 redirect to raw script on GitHub
  6. iOS β†’ ios.html β€” 302 redirect to iOS app finder page
  7. Android β†’ android.html β€” 302 redirect to APK finder page
  8. Browser β†’ GitHub β€” 302 redirect to the repo page

Self-hosting: create the rules yourself

Want to run your own install domain? You just need a Cloudflare zone. Set your env vars:

export CLOUDFLARE_EMAIL="you@example.com"
export CLOUDFLARE_API_KEY="your-global-api-key"
export ZONE_ID="your-zone-id"

First, create the ruleset:

curl -s -X POST "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/rulesets" \
  -H "X-Auth-Email: $CLOUDFLARE_EMAIL" \
  -H "X-Auth-Key: $CLOUDFLARE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "name": "default",
  "kind": "zone",
  "phase": "http_request_dynamic_redirect",
  "rules": [
    {
      "action": "redirect",
      "expression": "(http.request.full_uri wildcard r\"http://*\")",
      "description": "HTTP to HTTPS",
      "enabled": true,
      "action_parameters": {
        "from_value": {
          "preserve_query_string": true,
          "status_code": 301,
          "target_url": {
            "expression": "wildcard_replace(http.request.full_uri, r\"http://*\", r\"https://${1}\")"
          }
        }
      }
    },
    {
      "action": "redirect",
      "expression": "(http.request.full_uri wildcard r\"https://www.*\")",
      "description": "WWW to root",
      "enabled": true,
      "action_parameters": {
        "from_value": {
          "preserve_query_string": true,
          "status_code": 301,
          "target_url": {
            "expression": "wildcard_replace(http.request.full_uri, r\"https://www.*\", r\"https://${1}\")"
          }
        }
      }
    },
    {
      "action": "redirect",
      "expression": "(http.user_agent contains \"curl\" and http.request.uri.path wildcard r\"/*/*\")",
      "description": "curl β†’ install.sh",
      "enabled": true,
      "action_parameters": {
        "from_value": {
          "preserve_query_string": true,
          "status_code": 302,
          "target_url": {
            "expression": "wildcard_replace(http.request.full_uri, r\"https://YOURDOMAIN.COM/*/*\", r\"https://raw.githubusercontent.com/${1}/${2}/HEAD/install.sh\")"
          }
        }
      }
    },
    {
      "action": "redirect",
      "expression": "(http.user_agent contains \"Wget\" and http.request.uri.path wildcard r\"/*/*\")",
      "description": "wget β†’ install.sh",
      "enabled": true,
      "action_parameters": {
        "from_value": {
          "preserve_query_string": true,
          "status_code": 302,
          "target_url": {
            "expression": "wildcard_replace(http.request.full_uri, r\"https://YOURDOMAIN.COM/*/*\", r\"https://raw.githubusercontent.com/${1}/${2}/HEAD/install.sh\")"
          }
        }
      }
    },
    {
      "action": "redirect",
      "expression": "(http.user_agent contains \"PowerShell\" and http.request.uri.path wildcard r\"/*/*\")",
      "description": "PowerShell β†’ install.ps1",
      "enabled": true,
      "action_parameters": {
        "from_value": {
          "preserve_query_string": true,
          "status_code": 302,
          "target_url": {
            "expression": "wildcard_replace(http.request.full_uri, r\"https://YOURDOMAIN.COM/*/*\", r\"https://raw.githubusercontent.com/${1}/${2}/HEAD/install.ps1\")"
          }
        }
      }
    },
    {
      "action": "redirect",
      "expression": "(http.user_agent contains \"iPhone\" or http.user_agent contains \"iPad\") and http.request.uri.path wildcard r\"/*/*\"",
      "description": "iOS β†’ ios.html",
      "enabled": true,
      "action_parameters": {
        "from_value": {
          "preserve_query_string": false,
          "status_code": 302,
          "target_url": {
            "expression": "wildcard_replace(http.request.full_uri, r\"https://YOURDOMAIN.COM/*/*\", r\"https://YOURDOMAIN.COM/ios.html?gh=${1}/${2}\")"
          }
        }
      }
    },
    {
      "action": "redirect",
      "expression": "(http.request.uri.path wildcard r\"/*/*\")",
      "description": "Browser β†’ GitHub repo",
      "enabled": true,
      "action_parameters": {
        "from_value": {
          "preserve_query_string": true,
          "status_code": 302,
          "target_url": {
            "expression": "wildcard_replace(http.request.full_uri, r\"https://YOURDOMAIN.COM/*/*\", r\"https://github.com/${1}/${2}/\")"
          }
        }
      }
    }
  ]
}'

Replace YOURDOMAIN.COM with your actual domain in all three places.

To list existing rules:

# First, find your ruleset ID
curl -s "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/rulesets" \
  -H "X-Auth-Email: $CLOUDFLARE_EMAIL" \
  -H "X-Auth-Key: $CLOUDFLARE_API_KEY" | jq '.result[] | select(.phase == "http_request_dynamic_redirect") | .id'

# Then inspect the rules
RULESET_ID="the-id-from-above"
curl -s "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/rulesets/$RULESET_ID" \
  -H "X-Auth-Email: $CLOUDFLARE_EMAIL" \
  -H "X-Auth-Key: $CLOUDFLARE_API_KEY" | jq '.result.rules[] | {description, expression}'

Why this matters

  • Zero downtime β€” There is no server to crash. Cloudflare's edge network handles everything. As long as Cloudflare is up, install.cat is up.
  • Zero maintenance β€” No dependencies, no runtime, no database, no containers. Nothing to update or patch.
  • Global performance β€” Requests are processed at Cloudflare's nearest edge node, not routed to a single origin server.
  • Zero cost β€” Redirect Rules are included in Cloudflare's free plan.

The landing page (index.html) is a static file served via Cloudflare Pages / GitHub Pages. It's purely informational β€” the actual install routing is handled entirely at the DNS/edge layer.


Landing page

The landing page at install.cat includes:

  • Live example with syntax-highlighted commands
  • curl/wget toggle to switch between tools
  • Command generator β€” paste any GitHub URL or user/repo and get ready-to-copy install commands
  • Auto-detected language (17 languages supported, including RTL for Arabic and Urdu)

Development

Project structure

index.src.html     # Main page source β€” edit this
index.html         # Minified output β€” auto-generated, do not edit
android.src.html   # Android APK redirect page source β€” edit this
android.html       # Minified output β€” auto-generated, do not edit
ios.src.html       # iOS IPA/App Store redirect page source β€” edit this
ios.html           # Minified output β€” auto-generated, do not edit
Makefile           # Dev commands

Prerequisites

Install minhtml (Rust):

cargo install minhtml

For file watching (Linux):

# Arch
sudo pacman -S inotify-tools

# Debian/Ubuntu
sudo apt install inotify-tools

Commands

make dev     # Start local server + watch + open browser
make build   # Minify index.src.html β†’ index.html
make watch   # Watch for changes and rebuild (no server)
make open    # Open in browser

Workflow

  1. Edit index.src.html
  2. Run make dev to preview locally
  3. Commit β€” a git pre-commit hook automatically runs minhtml and stages index.html

Never edit index.html directly. It is regenerated on every commit.

Alternative minifier (Node.js, no install)

npx html-minifier-terser --collapse-whitespace --remove-comments --minify-css --minify-js -o index.html index.src.html

License

MIT


Made with ❀️ by @insign

About

A kitty redirector for installers

Resources

Stars

5 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages