├── .dockerignore ├── .github ├── renovate.json └── workflows │ └── docker-publish.yml ├── .gitignore ├── Dockerfile ├── README.md └── root ├── app └── www │ └── public │ ├── ajax │ ├── logs.php │ ├── notification.php │ ├── settings.php │ ├── starr.php │ └── templates.php │ ├── api │ └── index.php │ ├── classes │ ├── Cache.php │ ├── Database.php │ ├── Notifications.php │ ├── Shell.php │ ├── Starr.php │ ├── interfaces │ │ ├── Notifications.php │ │ └── Starr.php │ └── traits │ │ ├── Database │ │ ├── Apps.php │ │ ├── NotificationLink.php │ │ ├── NotificationPlatform.php │ │ ├── NotificationTrigger.php │ │ ├── Settings.php │ │ ├── Starrs.php │ │ └── Usage.php │ │ ├── Notifications │ │ ├── Notifiarr.php │ │ ├── Telegram.php │ │ ├── Templates.php │ │ └── Tests.php │ │ └── Starr │ │ └── Overrides.php │ ├── crons │ ├── backup.php │ └── cleanup.php │ ├── css │ └── style.css │ ├── functions │ ├── api.php │ ├── common.php │ ├── curl.php │ ├── file.php │ ├── git.php │ ├── helpers │ │ ├── arrays.php │ │ ├── sizes.php │ │ └── strings.php │ ├── logger.php │ ├── stats.php │ └── templates.php │ ├── health.html │ ├── images │ ├── favicon.ico │ ├── logo-16.png │ ├── logo-32.png │ ├── logo-512.png │ ├── logo-64.png │ ├── logos │ │ ├── lidarr.png │ │ ├── prowlarr.png │ │ ├── radarr.png │ │ ├── readarr.png │ │ ├── sonarr.png │ │ └── whisparr.png │ └── screenshots │ │ ├── apps.png │ │ ├── endpointUsage.png │ │ └── stats.png │ ├── includes │ ├── constants.php │ ├── footer.php │ └── header.php │ ├── index.php │ ├── js │ ├── functions.js │ ├── logs.js │ ├── notification.js │ ├── settings.js │ ├── starr.js │ └── templates.js │ ├── libraries │ ├── bootstrap │ │ ├── bootstrap-icons.css │ │ ├── bootstrap.bundle.min.js │ │ ├── bootstrap.min.css │ │ └── fonts │ │ │ ├── bootstrap-icons.woff │ │ │ └── bootstrap-icons.woff2 │ ├── fontawesome │ │ ├── all.min.css │ │ └── webfonts │ │ │ ├── fa-brands-400.eot │ │ │ ├── fa-brands-400.svg │ │ │ ├── fa-brands-400.ttf │ │ │ ├── fa-brands-400.woff │ │ │ ├── fa-brands-400.woff2 │ │ │ ├── fa-regular-400.eot │ │ │ ├── fa-regular-400.svg │ │ │ ├── fa-regular-400.ttf │ │ │ ├── fa-regular-400.woff │ │ │ ├── fa-regular-400.woff2 │ │ │ ├── fa-solid-900.eot │ │ │ ├── fa-solid-900.svg │ │ │ ├── fa-solid-900.ttf │ │ │ ├── fa-solid-900.woff │ │ │ └── fa-solid-900.woff2 │ ├── jquery │ │ ├── jquery-3.4.1.min.js │ │ └── jquery-ui-1.13.2.min.js │ └── select2 │ │ ├── select2-bootstrap-5-theme.min.css │ │ ├── select2.min.css │ │ └── select2.min.js │ ├── loader.php │ ├── migrations │ ├── 001_initial_setup.php │ ├── 002_settings.php │ ├── 003_notifications.php │ ├── 004_save_app_template.php │ ├── 005_ui_link_settings.php │ └── 006_ui_template_order.php │ ├── pages │ ├── help.php │ ├── home.php │ ├── logs.php │ ├── notifications.php │ ├── settings.php │ ├── starr.php │ └── templates.php │ ├── startup.php │ └── templates │ ├── lidarr │ ├── notifiarr.json │ ├── organizr.json │ └── unpackerr.json │ ├── prowlarr │ ├── homepage.json │ ├── notifiarr.json │ ├── nzb360.json │ └── organizr.json │ ├── radarr │ ├── autobrr.json │ ├── bazarr.json │ ├── daps.json │ ├── homepage.json │ ├── jellyseerr.json │ ├── kometa.json │ ├── lunasea.json │ ├── nabarr.json │ ├── notifiarr.json │ ├── nzb360.json │ ├── omegabrr.json │ ├── organizr.json │ ├── overseerr.json │ ├── prowlarr.json │ ├── recyclarr.json │ └── unpackerr.json │ ├── readarr │ ├── notifiarr.json │ └── unpackerr.json │ ├── sonarr │ ├── autobrr.json │ ├── bazarr.json │ ├── daps.json │ ├── homepage.json │ ├── jellyseerr.json │ ├── kometa.json │ ├── lunasea.json │ ├── nabarr.json │ ├── notifiarr.json │ ├── nzb360.json │ ├── omegabrr.json │ ├── organizr.json │ ├── overseerr.json │ ├── prowlarr.json │ ├── recyclarr.json │ ├── titlecardmaker.json │ └── unpackerr.json │ └── whisparr │ └── unpackerr.json └── etc ├── crontabs └── abc ├── nginx └── nginx.conf └── s6-overlay └── s6-rc.d ├── init-config-end └── dependencies.d │ └── init-starrproxy-config ├── init-starrproxy-config ├── dependencies.d │ └── init-nginx-end ├── run ├── type └── up └── user └── contents.d └── init-starrproxy-config /.dockerignore: -------------------------------------------------------------------------------- 1 | /Dockerfile 2 | /LICENSE 3 | /README.md 4 | /.github 5 | -------------------------------------------------------------------------------- /.github/renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:base", 5 | "docker:enableMajor", 6 | "default:automergeDigest" 7 | ], 8 | "packageRules": [ 9 | { 10 | "matchDatasources": ["docker"], 11 | "matchPackageNames": ["ubuntu"], 12 | "matchUpdateTypes": ["minor", "patch", "pin", "digest"], 13 | "automerge": true 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /.github/workflows/docker-publish.yml: -------------------------------------------------------------------------------- 1 | name: Docker 2 | 3 | # This all came from github. I wrote none of it, but I did remove some bits that didn't work. 4 | 5 | on: 6 | push: 7 | branches: [ "main", "develop" ] 8 | # Publish semver tags as releases. 9 | tags: [ 'v*.*.*' ] 10 | pull_request: 11 | branches: [ "main", "develop" ] 12 | 13 | env: 14 | # Use docker.io for Docker Hub if empty 15 | REGISTRY: ghcr.io 16 | # github.repository as / 17 | IMAGE_NAME: ${{ github.repository }} 18 | 19 | jobs: 20 | build: 21 | 22 | runs-on: ubuntu-latest 23 | permissions: 24 | contents: read 25 | packages: write 26 | # This is used to complete the identity challenge 27 | # with sigstore/fulcio when running outside of PRs. 28 | id-token: write 29 | 30 | steps: 31 | - name: Checkout repository 32 | uses: actions/checkout@v4 33 | with: 34 | # we need the whole thing so we can count commits. 35 | fetch-depth: '0' 36 | 37 | - name: Set up QEMU 38 | uses: docker/setup-qemu-action@v3 39 | with: 40 | platforms: 'arm64' 41 | 42 | # Workaround: https://github.com/docker/build-push-action/issues/461 43 | - name: Setup Docker buildx 44 | uses: docker/setup-buildx-action@v3 45 | 46 | # Login against a Docker registry except on PR 47 | # https://github.com/docker/login-action 48 | - name: Log into registry ${{ env.REGISTRY }} 49 | if: github.event_name != 'pull_request' 50 | uses: docker/login-action@v3 51 | with: 52 | registry: ${{ env.REGISTRY }} 53 | username: ${{ github.actor }} 54 | password: ${{ secrets.GITHUB_TOKEN }} 55 | 56 | # Extract metadata (tags, labels) for Docker 57 | # https://github.com/docker/metadata-action 58 | - name: Extract Docker metadata 59 | id: meta 60 | uses: docker/metadata-action@v5 61 | with: 62 | images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} 63 | 64 | - name: More Docker metadata 65 | run: | 66 | echo BUILD_DATE=$(date -u +%Y-%m-%dT%H:%M:00Z) >> $GITHUB_ENV 67 | echo COMMITS=$(git rev-list --count --all || echo 0) >> $GITHUB_ENV 68 | 69 | # Build and push Docker image with Buildx (don't push on PR) 70 | # https://github.com/docker/build-push-action 71 | - name: Build and push Docker image 72 | id: build-and-push 73 | uses: docker/build-push-action@v5 74 | with: 75 | context: . 76 | platforms: linux/amd64,linux/arm64 77 | push: ${{ github.event_name != 'pull_request' }} 78 | tags: ${{ steps.meta.outputs.tags }} 79 | labels: ${{ steps.meta.outputs.labels }} 80 | cache-from: type=gha 81 | cache-to: type=gha,mode=max 82 | build-args: | 83 | "BUILD_DATE=${{ env.BUILD_DATE }}" 84 | "COMMITS=${{ env.COMMITS }}" 85 | "BRANCH=${{ github.ref_name }}" 86 | "COMMIT=${{ github.sha }}" 87 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.vscode/* 2 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # syntax=docker/dockerfile:1 2 | 3 | FROM ghcr.io/linuxserver/baseimage-alpine-nginx:3.21 4 | 5 | # install packages 6 | RUN \ 7 | if [ -z ${NGINX_VERSION+x} ]; then \ 8 | NGINX_VERSION=$(curl -sL "http://dl-cdn.alpinelinux.org/alpine/v3.18/main/x86_64/APKINDEX.tar.gz" | tar -xz -C /tmp \ 9 | && awk '/^P:nginx$/,/V:/' /tmp/APKINDEX | sed -n 2p | sed 's/^V://'); \ 10 | fi && \ 11 | apk add --no-cache --repository=http://dl-cdn.alpinelinux.org/alpine/edge/community \ 12 | php83-pecl-mcrypt && \ 13 | echo "**** configure php-fpm to pass env vars ****" && \ 14 | sed -E -i 's/^;?clear_env ?=.*$/clear_env = no/g' /etc/php83/php-fpm.d/www.conf && \ 15 | grep -qxF 'clear_env = no' /etc/php83/php-fpm.d/www.conf || echo 'clear_env = no' >> /etc/php83/php-fpm.d/www.conf && \ 16 | echo "env[PATH] = /usr/local/bin:/usr/bin:/bin" >> /etc/php83/php-fpm.conf 17 | 18 | RUN apk --no-cache add \ 19 | # Database 20 | php83-sqlite3 \ 21 | # Memcache 22 | memcached \ 23 | php83-pecl-memcached 24 | 25 | # healthchecks 26 | HEALTHCHECK --interval=60s --timeout=30s --start-period=180s --start-interval=10s --retries=5 \ 27 | CMD curl -f http://localhost/health.html > /dev/null || exit 1 28 | 29 | # add local files 30 | COPY root/ / 31 | 32 | ARG COMMIT=unknown 33 | ARG COMMITS=0 34 | ARG BRANCH=unknown 35 | ARG COMMIT_MSG=unknown 36 | RUN echo -e "\n//-- DOCKERFILE DEFINES" >> /app/www/public/includes/constants.php \ 37 | && echo "define('DOCKERFILE_BUILD_DATE', '${BUILD_DATE}');" >> /app/www/public/includes/constants.php \ 38 | && echo "define('DOCKERFILE_COMMIT', '${COMMIT}');" >> /app/www/public/includes/constants.php \ 39 | && echo "define('DOCKERFILE_COMMITS', '${COMMITS}');" >> /app/www/public/includes/constants.php \ 40 | && echo "define('DOCKERFILE_BRANCH', '${BRANCH}');" >> /app/www/public/includes/constants.php 41 | 42 | # ports and volumes 43 | EXPOSE 80 443 44 | 45 | VOLUME /config 46 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Starr Proxy
3 | Starr Proxy 4 |

5 | 6 | ## Note 7 | 8 | This is still being tested and generating templates. Feel free to join the Notifiarr discord and ask questions in the `#starrproxy` channel or help out with templates 9 | 10 | ## Navigation 11 | 12 | - [Purpose](#purpose) 13 | - [Templates](#app-templates) 14 | - [Current templates](#current-templates) 15 | - [Automation](#automation) 16 | - [Installation](#installation) 17 | - [Compose example](#compose-example) 18 | - [Run example](#run-example) 19 | - [Environment variables](#environment-variables) 20 | - [Screenshots](#screenshots) 21 | 22 | ## Purpose 23 | 24 | Provide access scoped apikeys & stop letting every 3rd party app and script have full access to your starr instance(s)! 25 | 26 | Some apps only need one or two endpoints but have full access/control over everything, needlessly. 27 | 28 | Access logs per app are generated so you can see everytime the app hits the proxy, allowed and rejected requests, etc 29 | 30 | ## App templates 31 | 32 | There are some pre-built templates that enable just the api access the app actually needs so they are quick and easy to setup. More will be added in time for the common 3rd party apps. 33 | 34 | ### Current templates 35 | 36 | - Autobrr 37 | - Bazarr 38 | - DAPS 39 | - Homepage 40 | - Jellyseerr 41 | - Kometa 42 | - LunaSea 43 | - Nabarr 44 | - Notifiarr 45 | - Nzb360 46 | - Omegabrr 47 | - Overseerr 48 | - Recyclarr\* 49 | - TitleCardMaker 50 | - Unpackerr 51 | 52 | \* Has a requirement of unique URLs for each app, this means you will need to add a fake base to the url. Example: `http://10.1.0.100:9090/radarr1` 53 | 54 | ## Automation 55 | 56 | When the app is first opened, it checks for a `key` file in `/config` and if it is not present, it creates it with a 32 char apikey. Since automation will not open the UI this file will need to be created automatically as well. Create `/config/key` and add a 32 character key to it. 57 | 58 | All internal api requests will authenticate with either: 59 | 60 | ``` 61 | Header: "X-Api-Key: " 62 | Parameter: "?apikey=" 63 | ``` 64 | 65 | If you need to auto add starr apps and 3rd party apps you can do that via the api endpoint `/api/addstarr`. Send a curl `post` request to the starr proxy url with the json header and the payload below 66 | 67 | ``` json 68 | { 69 | "name": "notifiarr", 70 | "starr": "radarr", 71 | "url": "http://:", 72 | "apikey": "", 73 | "template": "notifiarr" 74 | } 75 | ``` 76 | 77 | The `template` variable is not required but if you do not use an existing template then the app will have no starr api access initially. 78 | 79 | An example curl would be: 80 | 81 | ``` bash 82 | curl -i -H "Content-Type:application/json" -d "{\"name\":\"notifiarr\",\"starr\":\"radarr\",\"url\":\"http://:\",\"apikey\":\"\",\"template\":\"notifiarr\"}" "http://10.1.0.128:9090/api/addstarr?apikey=" 83 | ``` 84 | 85 | Responses will be `json` 86 | 87 | Success: 88 | 89 | ``` json 90 | { 91 | "proxied-scope": "notifiarr's template access (25 endpoints)", 92 | "proxied-url": "http://10.1.0.128:9090", 93 | "proxied-key": "c54696c9a238336712454dc7aa088190" 94 | } 95 | ``` 96 | 97 | Errors: 98 | 99 | ``` json 100 | { 101 | "error": "Starr Proxy: no apikey provided" 102 | "error": "Starr Proxy: provided apikey is not valid for internal api access" 103 | "error": "Starr Proxy: missing required fields for addstarr endpoint. Optional: template | Required: name, starr, url, apikey" 104 | "error": "Starr Proxy: invalid internal api route" 105 | "error": "Starr Proxy: provided apikey is not valid or has no access" 106 | "error": "Starr Proxy: name field is required, should be the name of the 3rd party app/script" 107 | "error": "Starr Proxy: url field is required, should be the local url to the starr app" 108 | "error": "Starr Proxy: apikey field is required, should be the apikey to the starr app" 109 | "error": "Starr Proxy: starr field is required, should be one of: lidarr, radarr, readarr, sonarr, whisparr" 110 | "error": "Starr Proxy: starr field is not valid, should be one of: lidarr, radarr, readarr, sonarr, whisparr" 111 | "error": "Starr Proxy: could not connect to the starr app (radarr)" 112 | "error": "Starr Proxy: requested template (fake-template) does not exist for radarr, provide a valid template or leave it blank" 113 | } 114 | ``` 115 | 116 | ## Installation 117 | 118 | ### Compose example 119 | 120 | ``` yaml 121 | services: 122 | starrproxy: 123 | container_name: starrproxy 124 | image: ghcr.io/notifiarr/starrproxy:main 125 | restart: unless-stopped 126 | ports: 127 | - 9090:80/tcp 128 | environment: 129 | - TZ=America/New_York 130 | volumes: 131 | - /volume1/data/docker/starrproxy/config:/config 132 | 133 | ``` 134 | 135 | ### Run example 136 | 137 | ``` bash 138 | docker run \ 139 | -d \ 140 | --name "/starrproxy" \ 141 | --hostname "/starrproxy" \ 142 | --volume "/volume1/data/docker/starrproxy/config:/config:rw" \ 143 | --restart "unless-stopped" \ 144 | --publish "9090:80/tcp" \ 145 | --network "bridge" \ 146 | --env "TZ=America/New_York" \ 147 | "ghcr.io/notifiarr/starrproxy:main" 148 | ``` 149 | 150 | ### Environment variables 151 | 152 | #### Volumes 153 | 154 | Name: `App config`, Host: `/volume1/data/docker/starrproxy/config`, Container: `/config` 155 | 156 | #### Ports 157 | 158 | Inside: `80`, Outside: `9090` 159 | 160 | #### Variables 161 | 162 | Name: `TZ`, Key: `TZ`, Value: `America/New_York` 163 | 164 | ## Screenshots 165 | 166 | When viewing the access log for an allowed app, an endpoints tab contains all the endpoints referenced in the log and if the app has access or not. Clicking the red x allows access. 167 | 168 | ![Usage](root/app/www/public/images/screenshots/endpointUsage.png) 169 | 170 | Easily view apps, what they access, etc 171 | 172 | ![Apps](root/app/www/public/images/screenshots/apps.png) 173 | 174 | Some basic stats 175 | 176 | ![Apps](root/app/www/public/images/screenshots/stats.png) 177 | -------------------------------------------------------------------------------- /root/app/www/public/ajax/logs.php: -------------------------------------------------------------------------------- 1 | $val) { 25 | if (str_equals_any($key, ['m', 'apikey'])) { 26 | continue; 27 | } 28 | 29 | $newSettings[$key] = $val; 30 | } 31 | 32 | $proxyDb->setSettings($newSettings, $settingsTable); 33 | 34 | if ($_POST['apikey'] && $_POST['apikey'] != APP_APIKEY) { 35 | file_put_contents(APP_APIKEY_FILE, $_POST['apikey']); 36 | } 37 | } 38 | 39 | if ($_POST['m'] == 'bustCache') { 40 | $cache->bust($_POST['key']); 41 | } 42 | -------------------------------------------------------------------------------- /root/app/www/public/ajax/templates.php: -------------------------------------------------------------------------------- 1 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 |
Path/*.json
Name
[a-zA-Z0-9 _-]
37 | Notes:
38 |
    39 |
  • Using an existing template name will overwrite it
  • 40 |
41 | getAppFromId($_POST['id'], $appsTable); 46 | $endpoints = $existing['endpoints'] ? json_decode($existing['endpoints'], true) : []; 47 | $name = strtolower(preg_replace('/[^a-zA-Z0-9 _-]/', '', $_POST['name'])); 48 | file_put_contents(APP_USER_TEMPLATES_PATH . $app . '/' . $name . '.json', json_encode($endpoints, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)); 49 | } 50 | 51 | if ($_POST['m'] == 'viewTemplate') { 52 | $template = file_get_contents(str_replace('./', '../', $_POST['template'])); 53 | ?>
init(); 20 | } 21 | 22 | public function __tostring() 23 | { 24 | return 'Class loaded: Cache'; 25 | } 26 | 27 | public function init() 28 | { 29 | $this->cache = new Memcached(); 30 | $this->cache->addServer('127.0.0.1', '11211') or die('Cache connection failure'); 31 | } 32 | 33 | public function set($key, $data, $seconds) 34 | { 35 | if (!$this->cache) { 36 | $this->init(); 37 | } 38 | 39 | if ($key && $data && $seconds) { 40 | $this->cache->set($key, $data, $seconds); 41 | } 42 | } 43 | 44 | public function get($key) 45 | { 46 | if (!$this->cache) { 47 | $this->init(); 48 | } 49 | 50 | if (!$key) { 51 | return; 52 | } 53 | 54 | return $this->cache->get($key); 55 | } 56 | 57 | public function bust($key) 58 | { 59 | if (!$this->cache) { 60 | $this->init(); 61 | } 62 | 63 | if (!$key) { 64 | return; 65 | } 66 | 67 | $this->cache->delete($key); 68 | } 69 | 70 | public function stats() 71 | { 72 | if (!$this->cache) { 73 | $this->init(); 74 | } 75 | 76 | return $this->cache->getStats(); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /root/app/www/public/classes/Database.php: -------------------------------------------------------------------------------- 1 | connect(DATABASE_PATH . $dbName); 37 | $this->dbName = $dbName; 38 | $this->starr = $starr ?: new Starr(); 39 | $this->shell = $shell ?: new Shell(); 40 | $this->cache = $cache ?: new Cache(); 41 | } 42 | 43 | public function connect($dbFile) 44 | { 45 | $db = new SQLite3($dbFile, SQLITE3_OPEN_CREATE | SQLITE3_OPEN_READWRITE); 46 | $db->exec('PRAGMA journal_mode = WAL;'); 47 | $this->db = $db; 48 | } 49 | 50 | public function query($query) 51 | { 52 | if (str_equals_any(substr($query, 0, 5), ['UPDATE', 'INSERT', 'DELETE'])) { 53 | $transaction[] = 'BEGIN TRANSACTION;'; 54 | $transaction[] = $query . (substr($query, -1) != ';' ? ';' : ''); 55 | $transaction[] = 'COMMIT;'; 56 | 57 | $transaction = implode("\n", $transaction); 58 | } else { 59 | $transaction = $query; 60 | } 61 | 62 | return $this->db->query($transaction); 63 | } 64 | 65 | public function fetchAssoc($res) 66 | { 67 | return !$res ? [] : $res->fetchArray(SQLITE3_ASSOC); 68 | } 69 | 70 | public function affectedRows() 71 | { 72 | return $this->db->changes(); 73 | } 74 | 75 | public function insertId() 76 | { 77 | return $this->db->lastInsertRowID(); 78 | } 79 | 80 | public function error() 81 | { 82 | return $this->db->lastErrorMsg(); 83 | } 84 | 85 | public function prepare($in) 86 | { 87 | if (!$in) { 88 | return; 89 | } 90 | 91 | $out = addslashes(stripslashes($in)); 92 | return $out; 93 | } 94 | 95 | public function backup() 96 | { 97 | $this->db->query("VACUUM INTO '" . BACKUP_PATH . date('Y-m-d') . '/' . $this->dbName . "'"); 98 | 99 | if ($this->error() != 'not an error') { 100 | return $this->error(); 101 | } 102 | } 103 | 104 | public function getBackups() 105 | { 106 | if (!is_dir(BACKUP_PATH)) { 107 | return []; 108 | } 109 | 110 | $dir = opendir(BACKUP_PATH); 111 | while ($backup = readdir($dir)) { 112 | if ($backup[0] == '.' || !is_dir(BACKUP_PATH . $backup)) { 113 | continue; 114 | } 115 | 116 | $proxyDatabaseSize = filesize(BACKUP_PATH . $backup . '/' . PROXY_DATABASE_NAME); 117 | $usageDatabaseSize = filesize(BACKUP_PATH . $backup . '/' . USAGE_DATABASE_NAME); 118 | $backups[$backup] = [PROXY_DATABASE_NAME => byteConversion($proxyDatabaseSize), USAGE_DATABASE_NAME => byteConversion($usageDatabaseSize)]; 119 | } 120 | closedir($dir); 121 | krsort($backups); 122 | 123 | return $backups; 124 | } 125 | 126 | public function getNewestMigration() 127 | { 128 | $newestMigration = '001'; 129 | $dir = opendir(MIGRATIONS_PATH); 130 | while ($migration = readdir($dir)) { 131 | if (intval(substr($migration, 0, 3)) > intval($newestMigration) && str_contains($migration, '.php')) { 132 | $newestMigration = substr($migration, 0, 3); 133 | } 134 | } 135 | closedir($dir); 136 | 137 | return $newestMigration; 138 | } 139 | 140 | public function migrations() 141 | { 142 | $proxyDb = $this; 143 | $usageDb = new Database(USAGE_DATABASE_NAME); 144 | $starr = new Starr(); 145 | 146 | //-- DONT RUN MIGRATIONS IF IT IS ALREADY RUNNING 147 | if (file_exists(MIGRATION_FILE)) { 148 | return; 149 | } 150 | 151 | setFile(MIGRATION_FILE, ['started' => date('c')]); 152 | 153 | if (filesize(DATABASE_PATH . $this->dbName) == 0) { //-- INITIAL SETUP 154 | logger(SYSTEM_LOG, ['text' => 'Creating database and applying migration 001_initial_setup']); 155 | logger(MIGRATION_LOG, ['text' => '====================|']); 156 | logger(MIGRATION_LOG, ['text' => '====================| migrations']); 157 | logger(MIGRATION_LOG, ['text' => '====================|']); 158 | logger(MIGRATION_LOG, ['text' => 'migration 001 ->']); 159 | logger(MIGRATION_LOG, ['text' => 'migration 001 <-']); 160 | $q = []; 161 | require MIGRATIONS_PATH . '001_initial_setup.php'; 162 | logger(MIGRATION_LOG, ['text' => 'migration 001 <-']); 163 | 164 | $neededMigrations = []; 165 | $dir = opendir(MIGRATIONS_PATH); 166 | while ($migration = readdir($dir)) { 167 | if (substr($migration, 0, 3) > '001' && substr($migration, 0, 3) > $this->getSetting('migration') && str_contains($migration, '.php')) { 168 | $neededMigrations[substr($migration, 0, 3)] = $migration; 169 | } 170 | } 171 | closedir($dir); 172 | 173 | if ($neededMigrations) { 174 | ksort($neededMigrations); 175 | 176 | foreach ($neededMigrations as $migrationNumber => $neededMigration) { 177 | logger(MIGRATION_LOG, ['text' => 'migration ' . $migrationNumber . ' ->']); 178 | $q = []; 179 | require MIGRATIONS_PATH . $neededMigration; 180 | logger(MIGRATION_LOG, ['text' => 'migration ' . $migrationNumber . ' <-']); 181 | } 182 | } 183 | } else { //-- GET CURRENT MIGRATION & CHECK FOR NEEDED MIGRATIONS 184 | $neededMigrations = []; 185 | $dir = opendir(MIGRATIONS_PATH); 186 | while ($migration = readdir($dir)) { 187 | if (substr($migration, 0, 3) > $this->getSetting('migration') && str_contains($migration, '.php')) { 188 | $neededMigrations[substr($migration, 0, 3)] = $migration; 189 | } 190 | } 191 | closedir($dir); 192 | 193 | if ($neededMigrations) { 194 | ksort($neededMigrations); 195 | 196 | logger(SYSTEM_LOG, ['text' => 'Applying migrations: ' . implode(', ', array_keys($neededMigrations))]); 197 | logger(MIGRATION_LOG, ['text' => '====================|']); 198 | logger(MIGRATION_LOG, ['text' => '====================| migrations']); 199 | logger(MIGRATION_LOG, ['text' => '====================|']); 200 | 201 | foreach ($neededMigrations as $migrationNumber => $neededMigration) { 202 | logger(MIGRATION_LOG, ['text' => 'migration ' . $migrationNumber . ' ->']); 203 | $q = []; 204 | require MIGRATIONS_PATH . $neededMigration; 205 | logger(MIGRATION_LOG, ['text' => 'migration ' . $migrationNumber . ' <-']); 206 | } 207 | } 208 | } 209 | 210 | deleteFile(MIGRATION_FILE); 211 | } 212 | } 213 | -------------------------------------------------------------------------------- /root/app/www/public/classes/Notifications.php: -------------------------------------------------------------------------------- 1 | database = $proxyDb ?? new Database(PROXY_DATABASE_NAME); 30 | $this->logpath = LOGS_PATH . 'notifications/'; 31 | $this->serverName = is_array($settingsTable) ? $settingsTable['serverName'] : ''; 32 | } 33 | 34 | public function __toString() 35 | { 36 | return 'Class loaded: Notifications'; 37 | } 38 | 39 | public function sendTestNotification($linkId, $name) 40 | { 41 | $linkIds = []; 42 | $return = $notificationLinkData = ''; 43 | $tests = $this->getTestPayloads(); 44 | $notificationPlatformTable = $this->database->getNotificationPlatforms(); 45 | $notificationLinkTable = $this->database->getNotificationLinks(); 46 | 47 | foreach ($notificationLinkTable as $notificationLink) { 48 | if ($notificationLink['id'] == $linkId) { 49 | $notificationLinkData = $notificationLink; 50 | break; 51 | } 52 | } 53 | $notificationPlatform = $notificationPlatformTable[$notificationLinkData['platform_id']]; 54 | 55 | $logfile = $this->logpath . $notificationPlatform['platform'] . '.log'; 56 | logger($logfile, ['text' => 'test notification request to ' . $notificationPlatform['platform']]); 57 | logger($logfile, ['text' => 'test=' . $name]); 58 | logger($logfile, ['text' => 'tests=' . json_encode($tests)]); 59 | logger($logfile, ['text' => 'test payload=' . json_encode($tests[$name])]); 60 | 61 | $result = $this->notify($linkId, $name, $tests[$name], true); 62 | 63 | if ($result['code'] != 200) { 64 | $return = 'Code ' . $result['code'] . ', ' . $result['error']; 65 | } 66 | 67 | return ['code' => $result['code'], 'result' => $return]; 68 | } 69 | 70 | public function notify($linkId, $trigger, $payload, $test = false) 71 | { 72 | $linkIds = []; 73 | $notificationPlatformTable = $this->database->getNotificationPlatforms(); 74 | $notificationTriggersTable = $this->database->getNotificationTriggers(); 75 | $notificationLinkTable = $this->database->getNotificationLinks(); 76 | $triggerFields = $this->getTemplate($trigger); 77 | 78 | //-- MAKE IT MATCH THE TEMPLATE 79 | foreach ($payload as $payloadField => $payloadVal) { 80 | if (!array_key_exists($payloadField, $triggerFields) || !$payloadVal) { 81 | unset($payload[$payloadField]); 82 | } 83 | } 84 | 85 | if ($this->serverName) { 86 | $payload['server']['name'] = $this->serverName; 87 | } 88 | 89 | if ($linkId) { 90 | foreach ($notificationLinkTable as $notificationLink) { 91 | if ($notificationLink['id'] == $linkId) { 92 | $linkIds[] = $notificationLink; 93 | } 94 | } 95 | 96 | $notificationLink = $notificationLinkTable[$linkId]; 97 | $notificationPlatform = $notificationPlatformTable[$notificationLink['platform_id']]; 98 | } else { 99 | foreach ($notificationTriggersTable as $notificationTrigger) { 100 | if ($notificationTrigger['name'] == $trigger) { 101 | foreach ($notificationLinkTable as $notificationLink) { 102 | $triggers = makeArray(json_decode($notificationLink['trigger_ids'], true)); 103 | 104 | foreach ($triggers as $trigger) { 105 | if ($trigger == $notificationTrigger['id']) { 106 | $linkIds[] = $notificationLink; 107 | } 108 | } 109 | } 110 | break; 111 | } 112 | } 113 | } 114 | 115 | foreach ($linkIds as $linkId) { 116 | $platformId = $linkId['platform_id']; 117 | $platformParameters = json_decode($linkId['platform_parameters'], true); 118 | $platformName = ''; 119 | 120 | foreach ($notificationPlatformTable as $notificationPlatform) { 121 | if ($notificationPlatform['id'] == $platformId) { 122 | $platformName = $notificationPlatform['platform']; 123 | break; 124 | } 125 | } 126 | 127 | $logfile = $this->logpath . $platformName . '.log'; 128 | logger($logfile, ['text' => 'notification request to ' . $platformName]); 129 | logger($logfile, ['text' => 'notification payload: ' . json_encode($payload)]); 130 | 131 | switch ($platformId) { 132 | case NotificationPlatforms::NOTIFIARR: 133 | return $this->notifiarr($logfile, $platformParameters['apikey'], $payload, $test); 134 | case NotificationPlatforms::TELEGRAM: 135 | return $this->telegram($logfile, $platformParameters['botToken'], $platformParameters['chatId'], $payload, $test); 136 | } 137 | } 138 | } 139 | 140 | public function getNotificationPlatformNameFromId($id, $platforms) 141 | { 142 | foreach ($platforms as $platform) { 143 | if ($id == $platform['id']) { 144 | return $platform['platform']; 145 | } 146 | } 147 | } 148 | 149 | public function getNotificationTriggerNameFromId($id, $triggers) 150 | { 151 | foreach ($triggers as $trigger) { 152 | if ($id == $trigger['id']) { 153 | return $trigger['label']; 154 | } 155 | } 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /root/app/www/public/classes/Shell.php: -------------------------------------------------------------------------------- 1 | cache->get(APPS_TABLE_CACHE_KEY); 17 | 18 | if ($appsTableCache) { 19 | $apps = json_decode($appsTableCache, true); 20 | } 21 | 22 | if (empty($apps)) { 23 | $q = "SELECT * 24 | FROM " . APPS_TABLE . " 25 | ORDER BY name ASC"; 26 | $r = $this->query($q); 27 | while ($row = $this->fetchAssoc($r)) { 28 | $apps[] = $row; 29 | } 30 | 31 | $this->cache->set(APPS_TABLE_CACHE_KEY, json_encode($apps), APPS_TABLE_CACHE_TIME); 32 | } 33 | 34 | return $apps; 35 | } 36 | 37 | public function getAppFromId($appId, $appsTable) 38 | { 39 | $appsTable = $appsTable ?: $this->getAppsTable(); 40 | 41 | foreach ($appsTable as $app) { 42 | if ($app['id'] == $appId) { 43 | return $app; 44 | } 45 | } 46 | 47 | return []; 48 | } 49 | 50 | public function addApp($fields = []) 51 | { 52 | $fieldList = $valueList = ''; 53 | 54 | foreach ($fields as $field => $val) { 55 | $val = str_equals_any($field, ['endpoints']) ? $val : $this->prepare($val); 56 | 57 | $fieldList .= ($fieldList ? ', ' : '') . "`" . $field . "`"; 58 | $valueList .= ($valueList ? ', ' : '') . "'" . $val . "'"; 59 | } 60 | 61 | $q = "INSERT INTO " . APPS_TABLE . " 62 | (" . $fieldList . ") 63 | VALUES 64 | (" . $valueList . ")"; 65 | $this->query($q); 66 | 67 | if ($this->error() != 'not an error') { 68 | return $this->error(); 69 | } 70 | 71 | $this->cache->bust(APPS_TABLE_CACHE_KEY); 72 | 73 | return; 74 | } 75 | 76 | public function updateApp($appId, $fields = []) 77 | { 78 | $fieldList = ''; 79 | 80 | foreach ($fields as $field => $val) { 81 | $val = str_equals_any($field, ['endpoints']) ? $val : $this->prepare($val); 82 | 83 | $fieldList .= ($fieldList ? ', ' : '') . "`" . $field . "` = '" . $val . "'"; 84 | $fieldList .= ($fieldList ? ', ' : '') . "`" . $field . "` = '" . $val . "'"; 85 | } 86 | 87 | $q = "UPDATE " . APPS_TABLE . " 88 | SET " . $fieldList . " 89 | WHERE id = " . intval($appId); 90 | $this->query($q); 91 | 92 | if ($this->error() != 'not an error') { 93 | return $this->error(); 94 | } 95 | 96 | $this->cache->bust(APPS_TABLE_CACHE_KEY); 97 | 98 | return; 99 | } 100 | 101 | public function deleteApp($appId) 102 | { 103 | $q = "DELETE FROM " . APPS_TABLE . " 104 | WHERE id = " . intval($appId); 105 | $this->query($q); 106 | 107 | if ($this->error() != 'not an error') { 108 | return $this->error(); 109 | } 110 | 111 | $this->cache->bust(APPS_TABLE_CACHE_KEY); 112 | 113 | return; 114 | } 115 | 116 | public function adjustAppUsage($appId, $code) 117 | { 118 | $field = str_equals_any($code, [401, 405]) ? 'rejected' : 'allowed'; 119 | 120 | $q = "UPDATE " . USAGE_TABLE . " 121 | SET " . $field . " = " . $field . " + 1 122 | WHERE app_id = " . $appId; 123 | $this->query($q); 124 | 125 | if ($this->error() != 'not an error' || $this->affectedRows() == 0) { 126 | $q = "INSERT INTO " . USAGE_TABLE . " 127 | ('app_id', 'allowed', 'rejected') 128 | VALUES 129 | ('" . $appId . "', " . ($field == 'allowed' ? 1 : 0) . ", " . ($field == 'rejected' ? 1 : 0) . ")"; 130 | $this->query($q); 131 | } 132 | } 133 | 134 | public function getAppUsage($appId) 135 | { 136 | $q = "SELECT * 137 | FROM " . USAGE_TABLE . " 138 | WHERE app_id = " . intval($appId); 139 | $r = $this->query($q); 140 | $row = $this->fetchAssoc($r); 141 | 142 | return $row ?: []; 143 | } 144 | 145 | public function resetAppUsage($appId) 146 | { 147 | $q = "UPDATE " . USAGE_TABLE . " 148 | SET allowed = 0, rejected = 0 149 | WHERE app_id = " . intval($appId); 150 | $this->query($q); 151 | 152 | if ($this->error() != 'not an error') { 153 | return $this->error(); 154 | } 155 | 156 | return; 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /root/app/www/public/classes/traits/Database/NotificationLink.php: -------------------------------------------------------------------------------- 1 | notificationLinkTable) { 15 | return $this->notificationLinkTable; 16 | } 17 | 18 | $notificationLinkTable = []; 19 | 20 | $q = "SELECT * 21 | FROM " . NOTIFICATION_LINK_TABLE; 22 | $r = $this->query($q); 23 | while ($row = $this->fetchAssoc($r)) { 24 | $notificationLinkTable[$row['id']] = $row; 25 | } 26 | 27 | $this->notificationLinkTable = $notificationLinkTable; 28 | return $notificationLinkTable ?: []; 29 | } 30 | 31 | public function updateNotificationLink($linkId, $triggerIds, $platformParameters, $senderName) 32 | { 33 | $q = "UPDATE " . NOTIFICATION_LINK_TABLE . " 34 | SET name = '" . $this->prepare($senderName) . "', platform_parameters = '" . json_encode($platformParameters) . "', trigger_ids = '" . json_encode($triggerIds) . "' 35 | WHERE id = '" . intval($linkId) . "'"; 36 | $this->query($q); 37 | 38 | $this->notificationLinkTable = ''; 39 | return $this->getNotificationLinks(); 40 | } 41 | 42 | public function addNotificationLink($platformId, $triggerIds, $platformParameters, $senderName) 43 | { 44 | $q = "INSERT INTO " . NOTIFICATION_LINK_TABLE . " 45 | (`name`, `platform_id`, `platform_parameters`, `trigger_ids`) 46 | VALUES 47 | ('" . $this->prepare($senderName) . "', '" . intval($platformId) . "', '" . json_encode($platformParameters) . "', '" . json_encode($triggerIds) . "')"; 48 | $this->query($q); 49 | 50 | $this->notificationLinkTable = ''; 51 | return $this->getNotificationLinks(); 52 | } 53 | 54 | function deleteNotificationLink($linkId) 55 | { 56 | $q = "DELETE FROM " . NOTIFICATION_LINK_TABLE . " 57 | WHERE id = " . intval($linkId); 58 | $this->query($q); 59 | 60 | $this->notificationLinkTable = ''; 61 | return $this->getNotificationLinks(); 62 | } 63 | 64 | public function getNotificationLinkPlatformFromName($name) 65 | { 66 | $notificationLinks = $this->getNotificationLinks(); 67 | $notificationTrigger = $this->getNotificationTriggerFromName($name); 68 | 69 | foreach ($notificationLinks as $notificationLink) { 70 | if ($notificationLink['name'] == $name) { 71 | $triggers = makeArray(json_decode($notificationLink['trigger_ids'], true)); 72 | 73 | foreach ($triggers as $trigger) { 74 | if ($trigger == $notificationTrigger['id']) { 75 | return $notificationLink['platform_id']; 76 | } 77 | } 78 | } 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /root/app/www/public/classes/traits/Database/NotificationPlatform.php: -------------------------------------------------------------------------------- 1 | notificationPlatformTable) { 15 | return $this->notificationPlatformTable; 16 | } 17 | 18 | $notificationPlatformTable = []; 19 | 20 | $q = "SELECT * 21 | FROM " . NOTIFICATION_PLATFORM_TABLE; 22 | $r = $this->query($q); 23 | while ($row = $this->fetchAssoc($r)) { 24 | $notificationPlatformTable[$row['id']] = $row; 25 | } 26 | 27 | $this->notificationPlatformTable = $notificationPlatformTable; 28 | return $notificationPlatformTable ?: []; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /root/app/www/public/classes/traits/Database/NotificationTrigger.php: -------------------------------------------------------------------------------- 1 | notificationTriggersTable) { 15 | return $this->notificationTriggersTable; 16 | } 17 | 18 | $notificationTriggersTable = []; 19 | 20 | $q = "SELECT * 21 | FROM " . NOTIFICATION_TRIGGER_TABLE; 22 | $r = $this->query($q); 23 | while ($row = $this->fetchAssoc($r)) { 24 | $notificationTriggersTable[$row['id']] = $row; 25 | } 26 | 27 | $this->notificationTriggersTable = $notificationTriggersTable; 28 | return $notificationTriggersTable ?: []; 29 | } 30 | 31 | public function getNotificationTriggerFromName($name) 32 | { 33 | $triggers = $this->getNotificationTriggers(); 34 | 35 | foreach ($triggers as $trigger) { 36 | if (str_compare($name, $trigger['name'])) { 37 | return $trigger; 38 | } 39 | } 40 | 41 | return []; 42 | } 43 | 44 | public function isNotificationTriggerEnabled($name) 45 | { 46 | $notificationLinks = $this->getNotificationLinks(); 47 | $notificationTrigger = $this->getNotificationTriggerFromName($name); 48 | 49 | foreach ($notificationLinks as $notificationLink) { 50 | $triggers = makeArray(json_decode($notificationLink['trigger_ids'], true)); 51 | 52 | foreach ($triggers as $trigger) { 53 | if ($trigger == $notificationTrigger['id']) { 54 | return true; 55 | } 56 | } 57 | } 58 | 59 | return false; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /root/app/www/public/classes/traits/Database/Settings.php: -------------------------------------------------------------------------------- 1 | query($q); 18 | $row = $this->fetchAssoc($r); 19 | 20 | return $row['value']; 21 | } 22 | 23 | public function setSetting($field, $value) 24 | { 25 | $q = "UPDATE " . SETTINGS_TABLE . " 26 | SET value = '" . $this->prepare($value) . "' 27 | WHERE name = '" . $field . "'"; 28 | $this->query($q); 29 | 30 | return $this->getSettings(); 31 | } 32 | 33 | public function setSettings($newSettings = [], $currentSettings = []) 34 | { 35 | if (!$newSettings) { 36 | return; 37 | } 38 | 39 | if (!$currentSettings) { 40 | $currentSettings = $this->getSettings(); 41 | } 42 | 43 | foreach ($newSettings as $field => $value) { 44 | if ($currentSettings[$field] != $value) { 45 | $q = "UPDATE " . SETTINGS_TABLE . " 46 | SET value = '" . $this->prepare($value) . "' 47 | WHERE name = '" . $field . "'"; 48 | $this->query($q); 49 | } 50 | } 51 | 52 | return $this->getSettings(); 53 | } 54 | 55 | public function getSettings() 56 | { 57 | $settingsTable = []; 58 | 59 | $q = "SELECT * 60 | FROM " . SETTINGS_TABLE ; 61 | $r = $this->query($q); 62 | while ($row = $this->fetchAssoc($r)) { 63 | $settingsTable[$row['name']] = $row['value']; 64 | } 65 | 66 | $this->settingsTable = $settingsTable; 67 | return $settingsTable; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /root/app/www/public/classes/traits/Database/Starrs.php: -------------------------------------------------------------------------------- 1 | cache->get(STARRS_TABLE_CACHE_KEY); 17 | 18 | if ($starrsTableCache) { 19 | $starrs = json_decode($starrsTableCache, true); 20 | } 21 | 22 | if (empty($starrs)) { 23 | $q = "SELECT * 24 | FROM " . STARRS_TABLE . " 25 | ORDER BY name ASC"; 26 | $r = $this->query($q); 27 | while ($row = $this->fetchAssoc($r)) { 28 | $starrs[] = $row; 29 | } 30 | 31 | $this->cache->set(STARRS_TABLE_CACHE_KEY, json_encode($starrs), STARRS_TABLE_CACHE_TIME); 32 | } 33 | 34 | return $starrs; 35 | } 36 | 37 | public function getStarrAppFromId($id, $starrsTable) 38 | { 39 | $starrsTable = $starrsTable ?: $this->getStarrsTable(); 40 | 41 | foreach ($starrsTable as $starrApp) { 42 | if ($starrApp['id'] == $id) { 43 | return $starrApp; 44 | } 45 | } 46 | 47 | return []; 48 | } 49 | 50 | public function deleteStarrApp($starrId) 51 | { 52 | $q = "DELETE FROM " . STARRS_TABLE . " 53 | WHERE id = " . intval($starrId); 54 | $this->query($q); 55 | 56 | if ($this->error() != 'not an error') { 57 | return $this->error(); 58 | } 59 | 60 | $this->cache->bust(STARRS_TABLE_CACHE_KEY); 61 | 62 | return; 63 | } 64 | 65 | public function addStarrApp($starrApp, $fields = []) 66 | { 67 | $q = "INSERT INTO " . STARRS_TABLE . " 68 | (`starr`, `name`, `url`, `apikey`, `username`, `password`) 69 | VALUES 70 | ('". $this->starr->getStarrInterfaceIdFromName($starrApp) ."', '" . $this->prepare($fields['name']) . "', '" . $this->prepare($fields['url']) . "', '" . $this->prepare($fields['apikey']) . "', '" . $this->prepare($fields['username']) . "', '" . $this->prepare($fields['password']) . "')"; 71 | $this->query($q); 72 | 73 | if ($this->error() != 'not an error') { 74 | return 'addStarrApp() :: ' . $this->error(); 75 | } 76 | 77 | $this->cache->bust(STARRS_TABLE_CACHE_KEY); 78 | 79 | return; 80 | } 81 | 82 | public function updateStarrApp($id, $fields = []) 83 | { 84 | $q = "UPDATE " . STARRS_TABLE . " 85 | SET name = '" . $this->prepare($fields['name']) . "', url = '" . $this->prepare($fields['url']) . "', apikey = '" . $this->prepare($fields['apikey']) . "', username = '" . $this->prepare($fields['username']) . "', password = '" . $this->prepare($fields['password']) . "' 86 | WHERE id = " . intval($id); 87 | $this->query($q); 88 | 89 | if ($this->error() != 'not an error') { 90 | return 'updateStarrApp() :: ' . $this->error(); 91 | } 92 | 93 | $this->cache->bust(STARRS_TABLE_CACHE_KEY); 94 | 95 | return; 96 | } 97 | 98 | public function updateStarrAppSetting($id, $field, $value) 99 | { 100 | $value = $field != 'endpoints' ? $this->prepare($value) : $value; 101 | 102 | $q = "UPDATE " . STARRS_TABLE . " 103 | SET `" . $field . "` = '" . $value . "' 104 | WHERE id = " . intval($id); 105 | $this->query($q); 106 | 107 | if ($this->error() != 'not an error') { 108 | return $this->error(); 109 | } 110 | 111 | $this->cache->bust(STARRS_TABLE_CACHE_KEY); 112 | 113 | return; 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /root/app/www/public/classes/traits/Database/Usage.php: -------------------------------------------------------------------------------- 1 | query($q); 19 | while ($row = $this->fetchAssoc($r)) { 20 | $usage[] = $row; 21 | } 22 | 23 | return $usage; 24 | } 25 | 26 | public function deleteStarrAppUsage($starrId) 27 | { 28 | $q = "DELETE FROM " . USAGE_TABLE . " 29 | WHERE id = " . intval($starrId); 30 | $this->query($q); 31 | } 32 | 33 | public function getStarrAppUsage($appId) 34 | { 35 | $q = "SELECT * 36 | FROM " . USAGE_TABLE . " 37 | WHERE app_id = " . intval($appId); 38 | $r = $this->query($q); 39 | $row = $this->fetchAssoc($r); 40 | 41 | return $row ?: []; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /root/app/www/public/classes/traits/Notifications/Notifiarr.php: -------------------------------------------------------------------------------- 1 | 'notification response:' . json_encode($curl)]); 19 | 20 | $return = ['code' => 200]; 21 | 22 | if ($curl['code'] != 200) { 23 | $error = is_array($curl['response']) && $curl['response']['details'] && $curl['response']['details']['response'] ? $curl['response']['details']['response'] : 'Unknown error'; 24 | $return = ['code' => $curl['code'], 'error' => $error]; 25 | } 26 | 27 | if (!str_equals_any($curl['code'], [200, 201, 400, 401])) { 28 | logger($logfile, ['text' => 'sending a retry in 5s...']); 29 | sleep(5); 30 | 31 | $curl = curl($url, $headers, 'POST', json_encode($payload)); 32 | logger($logfile, ['text' => 'notification response:' . json_encode($curl)]); 33 | 34 | if ($curl['code'] != 200) { 35 | $error = is_array($curl['response']) && $curl['response']['details'] && $curl['response']['details']['response'] ? $curl['response']['details']['response'] : 'Unknown error'; 36 | $return = ['code' => $curl['code'], 'error' => $error]; 37 | } 38 | } 39 | 40 | return $return; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /root/app/www/public/classes/traits/Notifications/Telegram.php: -------------------------------------------------------------------------------- 1 | 400, 'error' => 'Missing bot token']; 16 | } 17 | if (!$chatId) { 18 | return ['code' => 400, 'error' => 'Missing chat id']; 19 | } 20 | 21 | $message = $this->buildTelegramMessage($payload, $test); 22 | $url = 'https://api.telegram.org/bot%s/sendMessage'; 23 | $payload = ['chat_id' => $chatId, 'text' => $message, 'parse_mode' => 'MarkdownV2', 'disable_web_page_preview' => true]; 24 | $url = sprintf($url, $botToken); 25 | $curl = curl($url, [], 'POST', json_encode($payload)); 26 | 27 | logger($logfile, ['text' => 'notification response:' . json_encode($curl), 'notificationCode' => $curl['code']]); 28 | 29 | $return = ['code' => 200]; 30 | if (!str_equals_any($curl['code'], [200, 201, 400, 401])) { 31 | logger($logfile, ['text' => 'sending a retry in 5s...']); 32 | sleep(5); 33 | 34 | $curl = curl($url, [], 'POST', json_encode($payload)); 35 | logger($logfile, ['text' => 'notification response:' . json_encode($curl), 'notificationCode' => $curl['code']]); 36 | 37 | if ($curl['code'] != 200) { 38 | $return = ['code' => $curl['code'], 'error' => $curl['response']['description']]; 39 | } 40 | } 41 | 42 | return $return; 43 | } 44 | 45 | public function buildTelegramMessage($payload, $test = false) 46 | { 47 | $message = ''; 48 | 49 | switch ($payload['event']) { 50 | case 'test': 51 | $message .= APP_NAME . ': Test' . "\n\n"; 52 | $message .= $payload['message']; 53 | $message .= "\n\n"; 54 | break; 55 | case 'blocked': 56 | $message .= APP_NAME . ': Blocked API request' . "\n\n"; 57 | $message .= 'Proxied app: ' . $payload['proxyApp'] . "\n"; 58 | $message .= 'Starr app: ' . $payload['starrApp'] . "\n"; 59 | $message .= 'Endpoint: ' . ($payload['method'] ? '[' . strtoupper($payload['method']) . '] ' : '') . $payload['endpoint'] . "\n"; 60 | $message .= "\n\n"; 61 | break; 62 | } 63 | 64 | $message = $test ? $message .= '`[TEST NOTIFICATION]`' : $message; 65 | 66 | return $this->escapeTelegramNotification($message); 67 | } 68 | 69 | public function escapeTelegramNotification($message) 70 | { 71 | $chars = ['-', '.', '(', ')', '<', '>', '=', '[', ']']; 72 | foreach ($chars as $char) { 73 | $message = str_replace($char, '\\' . $char, $message); 74 | } 75 | 76 | return $message; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /root/app/www/public/classes/traits/Notifications/Templates.php: -------------------------------------------------------------------------------- 1 | '', 18 | 'proxyApp' => '', 19 | 'starrApp' => '', 20 | 'endpoint' => '', 21 | 'method' => '' 22 | ]; 23 | case 'test': 24 | return [ 25 | 'event' => '', 26 | 'title' => '', 27 | 'message' => '' 28 | ]; 29 | default: 30 | return []; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /root/app/www/public/classes/traits/Notifications/Tests.php: -------------------------------------------------------------------------------- 1 | ['event' => 'blocked', 'proxyApp' => 'Notifiarr', 'starrApp' => 'Radarr', 'endpoint' => '/api/v3/system', 'method' => 'GET'], 16 | 'test' => ['event' => 'test', 'title' => APP_NAME . ' test', 'message' => 'This is a test message sent from ' . APP_NAME] 17 | ]; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /root/app/www/public/classes/traits/Starr/Overrides.php: -------------------------------------------------------------------------------- 1 | apiVersion($starr); 18 | 19 | $overrides['lidarr'] = []; 20 | $overrides['prowlarr'] = []; 21 | $overrides['radarr'] = [ 22 | '/api/' . $version . '/movie' => ['put'], 23 | '/api/' . $version . '/config/naming' => ['put'] 24 | ]; 25 | $overrides['readarr'] = []; 26 | $overrides['sonarr'] = [ 27 | '/api/' . $version . '/series' => ['put'], 28 | '/api/' . $version . '/config/naming' => ['put'] 29 | ]; 30 | $overrides['whisparr'] = []; 31 | 32 | return $overrides[$starr]; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /root/app/www/public/crons/backup.php: -------------------------------------------------------------------------------- 1 | exec('mkdir -p ' . $backupFolder); 28 | 29 | $proxyBackupError = $proxyDb->backup(); 30 | if ($proxyBackupError) { 31 | echo date('c') . '[ERROR] Backup of main database failed: ' . $proxyBackupError . "\n"; 32 | } 33 | $usageBackupError = $usageDb->backup(); 34 | if ($usageBackupError) { 35 | echo date('c') . '[ERROR] Backup of usage database failed: ' . $usageBackupError . "\n"; 36 | } 37 | 38 | copy(APP_APIKEY_FILE, $backupFolder . basename(APP_APIKEY_FILE)); 39 | -------------------------------------------------------------------------------- /root/app/www/public/crons/cleanup.php: -------------------------------------------------------------------------------- 1 | exec('rm ' . $logfile); 46 | } 47 | } 48 | closedir($dir); 49 | } 50 | } 51 | 52 | //-- OLD BACKUPS 53 | if (is_dir(BACKUP_PATH)) { 54 | $dir = opendir(BACKUP_PATH); 55 | while ($folder = readdir($dir)) { 56 | $backupFolder = BACKUP_PATH . $folder; 57 | 58 | //-- NOTIFIARR CORRUPTION CHECKS 59 | if (str_contains($backupFolder, '.zip')) { 60 | if (filemtime($backupFolder) <= (time() - (86400 * STARR_BACKUP_AGE))) { 61 | echo date('c') . ' removing old starr backup \'' . $backupFolder . '\''."\n"; 62 | $shell->exec('rm ' . $backupFolder); 63 | } 64 | } 65 | 66 | if (!is_dir($backupFolder) || $folder[0] == '.') { 67 | continue; 68 | } 69 | 70 | if (filemtime($backupFolder) <= (time() - (86400 * BACKUP_AGE))) { 71 | echo date('c') . ' removing old backup \'' . $backupFolder . '\''."\n"; 72 | $shell->exec('rm -r ' . $backupFolder); 73 | } 74 | } 75 | closedir($dir); 76 | } 77 | -------------------------------------------------------------------------------- /root/app/www/public/css/style.css: -------------------------------------------------------------------------------- 1 | .text-small { 2 | font-size: small; 3 | } 4 | 5 | pre { 6 | padding: 1em; 7 | width: 100%; 8 | overflow: auto; 9 | overflow-y: hidden; 10 | font-size: 12px; 11 | line-height: 20px; 12 | background-color: #000; 13 | color: #fff; 14 | border: 1px solid #777; 15 | } 16 | 17 | .modal-xxl { 18 | --bs-modal-width:95%; 19 | } 20 | 21 | .select2-container--open { 22 | z-index: 10001; 23 | } 24 | -------------------------------------------------------------------------------- /root/app/www/public/functions/api.php: -------------------------------------------------------------------------------- 1 | $rhVal) { 26 | if ($code[0] != '3' && $rhKey == 'Location') { 27 | continue; 28 | } 29 | 30 | header($rhKey . ': ' . $rhVal[0]); 31 | } 32 | } else { 33 | header('Content-Length: ' . strlen($response)); 34 | header('Access-Control-Allow-Origin: *'); 35 | 36 | if ($response) { 37 | header('Content-Type: application/json'); 38 | } 39 | } 40 | 41 | if ($response) { 42 | echo $response; 43 | } 44 | 45 | die(); 46 | } 47 | -------------------------------------------------------------------------------- /root/app/www/public/functions/common.php: -------------------------------------------------------------------------------- 1 | exec('mkdir -p ' . $tree); 58 | } 59 | 60 | function extractCookies($string) { 61 | $lines = explode(PHP_EOL, $string); 62 | 63 | foreach ($lines as $line) { 64 | if (substr($line, 0, 10) == '#HttpOnly_') { 65 | $line = substr($line, 10); 66 | $cookie['httponly'] = true; 67 | } else { 68 | $cookie['httponly'] = false; 69 | } 70 | 71 | // we only care for valid cookie def lines 72 | if (strlen( $line ) > 0 && $line[0] != '#' && substr_count($line, "\t") == 6) { 73 | $tokens = explode("\t", $line); 74 | $tokens = array_map('trim', $tokens); 75 | $cookie = [ 76 | 'domain' => $tokens[0], 77 | 'flag' => $tokens[1], 78 | 'path' => $tokens[2], 79 | 'secure' => $tokens[3], 80 | 'expiration-epoch' => $tokens[4], 81 | 'name' => urldecode($tokens[5]), 82 | 'value' => urldecode($tokens[6]), 83 | 'expiration' => date('Y-m-d h:i:s', $tokens[4]) 84 | ]; 85 | 86 | $cookies[] = $cookie; 87 | } 88 | } 89 | 90 | return $cookies ?: []; 91 | } 92 | -------------------------------------------------------------------------------- /root/app/www/public/functions/curl.php: -------------------------------------------------------------------------------- 1 | $url, 85 | 'method' => $method, 86 | 'payload' => $payload, 87 | 'responseHeaders' => $responseHeaders, 88 | 'response' => $response, 89 | 'error' => $error, 90 | 'code' => $code 91 | ]; 92 | } 93 | -------------------------------------------------------------------------------- /root/app/www/public/functions/file.php: -------------------------------------------------------------------------------- 1 | 'getFile() ' . $file]); 13 | 14 | if (!$file) { 15 | logger(SYSTEM_LOG, ['text' => '$file is empty']); 16 | return []; 17 | } 18 | 19 | if (!file_exists($file)) { 20 | file_put_contents($file, '[]'); 21 | } 22 | 23 | return json_decode(file_get_contents($file), true); 24 | } 25 | 26 | function setFile($file, $contents) 27 | { 28 | logger(SYSTEM_LOG, ['text' => 'setFile() ' . $file]); 29 | 30 | if (is_array($contents)) { 31 | $contents = json_encode($contents, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); 32 | } else { 33 | $contents = json_encode(json_decode($contents, true), JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); 34 | } 35 | 36 | if (!empty(json_decode($contents, true))) { 37 | file_put_contents($file, $contents); 38 | } 39 | } 40 | 41 | function deleteFile($file) 42 | { 43 | logger(SYSTEM_LOG, ['text' => 'deleteFile() ' . $file]); 44 | unlink($file); 45 | } 46 | -------------------------------------------------------------------------------- /root/app/www/public/functions/git.php: -------------------------------------------------------------------------------- 1 | v0.0.0'; 41 | } 42 | 43 | return 'v' . APP_X . '.' . APP_Y . '.' . DOCKERFILE_COMMITS . ''; 44 | } 45 | -------------------------------------------------------------------------------- /root/app/www/public/functions/helpers/arrays.php: -------------------------------------------------------------------------------- 1 | $value) { 15 | $return[$key] = $value; 16 | } 17 | 18 | return $return; 19 | } 20 | 21 | function makeArray($array) 22 | { 23 | return is_array($array) ? $array : []; 24 | } 25 | -------------------------------------------------------------------------------- /root/app/www/public/functions/helpers/sizes.php: -------------------------------------------------------------------------------- 1 | $a || str_contains($haystack, $n), false); 26 | } 27 | 28 | function str_contains_all(string|null $haystack, array $needles): bool 29 | { 30 | if (!$haystack) { 31 | return false; 32 | } 33 | 34 | return array_reduce($needles, fn($a, $n) => $a && str_contains($haystack, $n), true); 35 | } 36 | 37 | function str_compare($str1, $str2, $case = false): bool 38 | { 39 | if ($case) { 40 | return $str1 == $str2; 41 | } else { 42 | return strtolower($str1) == strtolower($str2); 43 | } 44 | } 45 | 46 | function truncateEnd($str, $max, $minLength = false) 47 | { 48 | if (!is_string($str)) { 49 | return $str; 50 | } 51 | 52 | if (!$minLength && strlen($str) <= $max) { 53 | return $str; 54 | } 55 | 56 | if (strlen($str) > $max) { 57 | $str = substr($str, 0, $max - 3) . '...'; 58 | } 59 | 60 | if ($minLength && strlen($str) < $max) { 61 | $str = str_pad($str, $max, ' ', STR_PAD_RIGHT); 62 | } 63 | 64 | return $str; 65 | } 66 | 67 | function truncateMiddle($str, $max) 68 | { 69 | if (strlen($str) <= $max) { 70 | return $str; 71 | } 72 | 73 | $padMax = $max - 5; 74 | 75 | return substr($str, 0, floor($padMax / 2)) . '..........' . substr($str, (floor($padMax / 2) * -1)); 76 | } 77 | -------------------------------------------------------------------------------- /root/app/www/public/functions/stats.php: -------------------------------------------------------------------------------- 1 | getStarrInterfaceNameFromId($starrApp['starr']); 19 | 20 | $stats[$app]++; 21 | } 22 | 23 | ksort($stats); 24 | } 25 | 26 | return $stats; 27 | } 28 | 29 | function getTotalEndpointStats($starrsTable, $appsTable) 30 | { 31 | global $starr; 32 | 33 | $starr = $starr ?: new Starr(); 34 | $stats = $endpointList = []; 35 | 36 | if ($starrsTable) { 37 | foreach ($starrsTable as $starrApp) { 38 | $app = $starr->getStarrInterfaceNameFromId($starrApp['starr']); 39 | $allowed = $total = $apps = 0; 40 | 41 | if (!$endpointList[$app]) { 42 | $endpointList[$app] = $starr->getEndpoints($app); 43 | } 44 | 45 | foreach ($appsTable as $proxiedApp) { 46 | if ($proxiedApp['starr_id'] == $starrApp['id']) { 47 | $endpoints = json_decode($proxiedApp['endpoints'], true); 48 | $allowed += count($endpoints); 49 | $total += count($endpointList[$app]); 50 | $apps++; 51 | } 52 | } 53 | 54 | $stats[$app] = [ 55 | 'apps' => ($stats[$app]['apps'] + $apps), 56 | 'total' => ($stats[$app]['total'] + $total), 57 | 'allowed' => ($stats[$app]['allowed'] + $allowed) 58 | ]; 59 | } 60 | 61 | if ($stats) { 62 | ksort($stats); 63 | } 64 | } 65 | 66 | return $stats; 67 | } 68 | 69 | function getTotalUsageStats($starrsTable, $appsTable, $usageTable) 70 | { 71 | global $starr; 72 | 73 | $stats = []; 74 | if ($starrsTable && $appsTable && $usageTable) { 75 | foreach ($starrsTable as $starrApp) { 76 | $app = $starr->getStarrInterfaceNameFromId($starrApp['starr']); 77 | $allowed = 0; 78 | $rejected = 0; 79 | 80 | foreach ($appsTable as $proxiedApp) { 81 | if ($proxiedApp['starr_id'] == $starrApp['id']) { 82 | foreach ($usageTable as $usage) { 83 | if ($usage['app_id'] == $proxiedApp['id']) { 84 | $allowed += $usage['allowed']; 85 | $rejected += $usage['rejected']; 86 | break; 87 | } 88 | } 89 | } 90 | } 91 | 92 | $stats[$app] = [ 93 | 'allowed' => ($stats[$app]['allowed'] + $allowed), 94 | 'rejected' => ($stats[$app]['rejected'] + $rejected) 95 | ]; 96 | } 97 | 98 | if ($stats) { 99 | ksort($stats); 100 | } 101 | } 102 | 103 | return $stats; 104 | } 105 | -------------------------------------------------------------------------------- /root/app/www/public/functions/templates.php: -------------------------------------------------------------------------------- 1 | $appTemplates) { 16 | $templateOptions .= ''; 17 | 18 | foreach ($appTemplates as $appTemplate) { 19 | $custom = str_contains($appTemplate['location'], APP_USER_TEMPLATES_PATH); 20 | 21 | if (TEMPLATE_ORDER == 1) { 22 | $templateOptions .= ''; 23 | } elseif (TEMPLATE_ORDER == 2) { 24 | $templateOptions .= ''; 25 | } 26 | } 27 | $templateOptions .= ''; 28 | } 29 | 30 | return '' . $templateOptions; 31 | } 32 | 33 | function getTemplateList() 34 | { 35 | $templateLocations = [ABSOLUTE_PATH . 'templates/', APP_USER_TEMPLATES_PATH]; 36 | $list = []; 37 | foreach (StarrApps::LIST as $starrApp) { 38 | foreach ($templateLocations as $templateLocation) { 39 | if (is_dir($templateLocation . $starrApp)) { 40 | $dir = opendir($templateLocation . $starrApp); 41 | while ($template = readdir($dir)) { 42 | if (!str_contains($template, '.json')) { 43 | continue; 44 | } 45 | 46 | $template = str_replace('.json', '', $template); 47 | if (TEMPLATE_ORDER == 1) { 48 | $list[$template][] = ['location' => $templateLocation, 'item' => $starrApp]; 49 | } elseif (TEMPLATE_ORDER == 2) { 50 | $list[$starrApp][] = ['location' => $templateLocation, 'item' => $template]; 51 | } 52 | } 53 | closedir($dir); 54 | ksort($list); 55 | } 56 | } 57 | } 58 | 59 | return $list; 60 | } 61 | -------------------------------------------------------------------------------- /root/app/www/public/health.html: -------------------------------------------------------------------------------- 1 | healthy -------------------------------------------------------------------------------- /root/app/www/public/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Notifiarr/starrproxy/73511ed7dc7dc646b798dde4030700564d499be5/root/app/www/public/images/favicon.ico -------------------------------------------------------------------------------- /root/app/www/public/images/logo-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Notifiarr/starrproxy/73511ed7dc7dc646b798dde4030700564d499be5/root/app/www/public/images/logo-16.png -------------------------------------------------------------------------------- /root/app/www/public/images/logo-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Notifiarr/starrproxy/73511ed7dc7dc646b798dde4030700564d499be5/root/app/www/public/images/logo-32.png -------------------------------------------------------------------------------- /root/app/www/public/images/logo-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Notifiarr/starrproxy/73511ed7dc7dc646b798dde4030700564d499be5/root/app/www/public/images/logo-512.png -------------------------------------------------------------------------------- /root/app/www/public/images/logo-64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Notifiarr/starrproxy/73511ed7dc7dc646b798dde4030700564d499be5/root/app/www/public/images/logo-64.png -------------------------------------------------------------------------------- /root/app/www/public/images/logos/lidarr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Notifiarr/starrproxy/73511ed7dc7dc646b798dde4030700564d499be5/root/app/www/public/images/logos/lidarr.png -------------------------------------------------------------------------------- /root/app/www/public/images/logos/prowlarr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Notifiarr/starrproxy/73511ed7dc7dc646b798dde4030700564d499be5/root/app/www/public/images/logos/prowlarr.png -------------------------------------------------------------------------------- /root/app/www/public/images/logos/radarr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Notifiarr/starrproxy/73511ed7dc7dc646b798dde4030700564d499be5/root/app/www/public/images/logos/radarr.png -------------------------------------------------------------------------------- /root/app/www/public/images/logos/readarr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Notifiarr/starrproxy/73511ed7dc7dc646b798dde4030700564d499be5/root/app/www/public/images/logos/readarr.png -------------------------------------------------------------------------------- /root/app/www/public/images/logos/sonarr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Notifiarr/starrproxy/73511ed7dc7dc646b798dde4030700564d499be5/root/app/www/public/images/logos/sonarr.png -------------------------------------------------------------------------------- /root/app/www/public/images/logos/whisparr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Notifiarr/starrproxy/73511ed7dc7dc646b798dde4030700564d499be5/root/app/www/public/images/logos/whisparr.png -------------------------------------------------------------------------------- /root/app/www/public/images/screenshots/apps.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Notifiarr/starrproxy/73511ed7dc7dc646b798dde4030700564d499be5/root/app/www/public/images/screenshots/apps.png -------------------------------------------------------------------------------- /root/app/www/public/images/screenshots/endpointUsage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Notifiarr/starrproxy/73511ed7dc7dc646b798dde4030700564d499be5/root/app/www/public/images/screenshots/endpointUsage.png -------------------------------------------------------------------------------- /root/app/www/public/images/screenshots/stats.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Notifiarr/starrproxy/73511ed7dc7dc646b798dde4030700564d499be5/root/app/www/public/images/screenshots/stats.png -------------------------------------------------------------------------------- /root/app/www/public/includes/constants.php: -------------------------------------------------------------------------------- 1 | 19 | 20 | 21 | 22 |
23 | 24 | 25 |
26 | 40 |
41 | 42 | 43 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /root/app/www/public/includes/header.php: -------------------------------------------------------------------------------- 1 | 19 | 20 | 21 | 22 | 23 | <?= APP_NAME ?> 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 101 |
102 | -------------------------------------------------------------------------------- /root/app/www/public/index.php: -------------------------------------------------------------------------------- 1 | 22 |
23 | 43 | 44 |
45 | '; 16 | toast += '
'; 17 | toast += ' '; 18 | toast += ' ' + title + ''; 19 | toast += ' ' + type + ''; 20 | toast += ' '; 21 | toast += '
'; 22 | toast += '
' + message + '
'; 23 | toast += '
'; 24 | 25 | $('.toast-container').append(toast); 26 | $('#toast-' + uniqueId).toast('show'); 27 | 28 | setTimeout(function () { 29 | $('#toast-' + uniqueId).remove(); 30 | }, 10000); 31 | 32 | } 33 | // ------------------------------------------------------------------------------------------- 34 | function dialogOpen(p) 35 | { 36 | const id = p.id; 37 | const title = p.title ? p.title : ' '; 38 | const body = p.body ? p.body : ' '; 39 | const footer = p.footer ? p.footer : ' '; 40 | const close = typeof p.close === 'undefined' ? true : p.close; 41 | const size = p.size ? p.size : ''; //-- sm, lg, xl, xxl 42 | const escape = typeof p.escape === 'undefined' ? false : p.escape; 43 | const minimize = typeof p.minimize === 'undefined' ? false : p.minimize; 44 | 45 | if (typeof id === 'undefined') { 46 | console.log('Error: Called dialogOpen with no id parameter'); 47 | return; 48 | } 49 | 50 | if ($('#' + id).length) { 51 | $('#' + id).remove(); 52 | } 53 | 54 | //-- CLONE IT 55 | $('#dialog-modal').clone().appendTo('#dialog-modal-container').prop('id', id); 56 | 57 | //-- USE THE CLONE 58 | $('#' + id).modal({ 59 | keyboard: false, 60 | backdrop: 'static' 61 | }); 62 | 63 | if (escape) { 64 | $('#' + id).attr('data-escape-close', 'true'); 65 | } 66 | 67 | $('#' + id + ' .modal-title').html(title); 68 | $('#' + id + ' .modal-body').html(body); 69 | $('#' + id + ' .modal-footer').html(footer); 70 | 71 | if (!close) { 72 | $('#' + id + ' .btn-close').hide(); 73 | 74 | $('#' + id + ' .modal-header').dblclick(function () { 75 | $('#' + id + ' .btn-close').show(); 76 | }); 77 | } 78 | 79 | if (minimize) { 80 | const closeBtn = $('#' + id + ' .btn-close').clone(); 81 | 82 | $('#' + id + ' .btn-close').remove(); 83 | $('#' + id + ' .modal-header').append('
'); 84 | $('#' + id + ' .modal-header .dialog-btn-container').append('').append(closeBtn); 85 | 86 | let minimizeDiv = ''; 95 | 96 | $('body').append(minimizeDiv); 97 | } 98 | 99 | $('#' + id + ' .modal-dialog').draggable({ 100 | handle: '.modal-header' 101 | }); 102 | 103 | $('#' + id + ' .modal-header').css('cursor', 'grab'); 104 | 105 | $('#' + id).modal('show'); 106 | 107 | if (size) { 108 | $('#' + id + ' .modal-dialog').addClass('modal-' + size); 109 | } 110 | 111 | if (typeof p.onOpen !== 'undefined') { 112 | const onOpenFunction = p.onOpen; 113 | function onOpenCallback(callback) 114 | { 115 | callback(); 116 | } 117 | onOpenCallback(onOpenFunction); 118 | } 119 | 120 | if (typeof p.onClose !== 'undefined') { 121 | const onCloseFunction = p.onClose; 122 | function onCloseCallback(callback) 123 | { 124 | callback(); 125 | } 126 | 127 | $('#' + id + ' .btn-close').attr('onclick', ''); 128 | $('#' + id + ' .btn-close').bind('click', function () { 129 | onCloseCallback(onCloseFunction); 130 | dialogClose(id); 131 | }); 132 | } 133 | 134 | } 135 | // ------------------------------------------------------------------------------------------- 136 | function dialogClose(elm) 137 | { 138 | if (!elm) { 139 | console.error('Error: Called dialogClose on no elm'); 140 | return; 141 | } 142 | 143 | if (!$('#' + elm).length) { 144 | console.error('Error: Could not locate dialog with id \'' + elm + '\''); 145 | return; 146 | } 147 | 148 | $('#' + elm).modal('hide'); 149 | 150 | } 151 | // ------------------------------------------------------------------------------------------- 152 | function clipboard(elm, elmType) 153 | { 154 | let txt = ''; 155 | 156 | switch (elmType) { 157 | case 'html': 158 | txt = $('#' + elm).html(); 159 | break; 160 | case 'raw': 161 | txt = elm; 162 | break; 163 | case 'val': 164 | txt = $('#' + elm).val(); 165 | break; 166 | } 167 | 168 | if (!txt) { 169 | toast('Copy Failed', 'Nothing found to copy with element "' + elm + '"', 'error'); 170 | return; 171 | } 172 | 173 | if (navigator.clipboard) { 174 | navigator.clipboard.writeText(txt).then(function () { 175 | toast('Copied', 'Contents copied to clipboard', 'success'); 176 | }, function () { 177 | toast('Copy Failed', 'Contents failed to copy to clipboard', 'error'); 178 | }); 179 | } else { 180 | try { 181 | clipboardText = document.createElement('textarea'); 182 | clipboardText.id = 'copyText'; 183 | clipboardText.value = txt; 184 | document.body.appendChild(clipboardText); 185 | clipboardText.select(); 186 | 187 | document.execCommand('copy'); 188 | document.body.removeChild(clipboardText); 189 | toast('Copied', 'Contents copied to clipboard', 'success'); 190 | } catch (err) { 191 | toast('Copy Failed', 'Contents failed to copy to clipboard', 'error'); 192 | } 193 | } 194 | } 195 | // --------------------------------------------------------------------------------------------- 196 | function reload() 197 | { 198 | window.location.href = window.location.href; 199 | } 200 | // --------------------------------------------------------------------------------------------- 201 | function loadingStart() 202 | { 203 | if ($('#loading-modal .btn-close').is(':visible')) { 204 | loadingStop(); 205 | } 206 | 207 | $('#loading-modal .btn-close').hide(); 208 | $('#loading-modal').modal('show'); 209 | 210 | $('#loading-modal .modal-header').dblclick(function () { 211 | $('#loading-modal .btn-close').show(); 212 | }); 213 | 214 | } 215 | // ------------------------------------------------------------------------------------------- 216 | function loadingStop() 217 | { 218 | setTimeout(function () { 219 | $('#loading-modal').modal('hide'); 220 | }, 500); 221 | 222 | } 223 | // ------------------------------------------------------------------------------------------- 224 | -------------------------------------------------------------------------------- /root/app/www/public/js/logs.js: -------------------------------------------------------------------------------- 1 | function viewLog(log, index, page = 1) 2 | { 3 | loadingStart(); 4 | 5 | $('#log-viewer').html('Loading log...'); 6 | $('[id^=app-log-]').removeClass('text-info'); 7 | $('#app-log-' + index).addClass('text-info'); 8 | 9 | $.ajax({ 10 | type: 'POST', 11 | url: 'ajax/logs.php', 12 | data: '&m=viewLog&log=' + log + '&page=' + page + '&index=' + index, 13 | success: function (resultData) { 14 | $('#log-viewer').html(resultData); 15 | loadingStop(); 16 | } 17 | }); 18 | } 19 | // ------------------------------------------------------------------------------------------- 20 | function viewAppLog(log, key, appName) 21 | { 22 | loadingStart(); 23 | 24 | $.ajax({ 25 | type: 'POST', 26 | url: 'ajax/logs.php', 27 | data: '&m=viewAppLog&key=' + key + '&log=' + log, 28 | success: function (resultData) { 29 | dialogOpen({ 30 | id: 'viewAppLog', 31 | title: 'Access log viewer: ' + appName + ' (filter: ' + key + ')', 32 | size: 'xxl', 33 | body: resultData, 34 | onOpen: function() { 35 | loadingStop(); 36 | } 37 | }); 38 | } 39 | }); 40 | } 41 | // ------------------------------------------------------------------------------------------- 42 | function deleteLog(log) 43 | { 44 | if (confirm('Are you sure you want to delete the log: ' + log + '?')) { 45 | $.ajax({ 46 | type: 'POST', 47 | url: 'ajax/logs.php', 48 | data: '&m=deleteLog&log=' + log, 49 | success: function () { 50 | reload(); 51 | } 52 | }); 53 | } 54 | } 55 | // ------------------------------------------------------------------------------------------- 56 | -------------------------------------------------------------------------------- /root/app/www/public/js/notification.js: -------------------------------------------------------------------------------- 1 | function saveNotification(platformId, linkId) 2 | { 3 | let requiredError = false; 4 | let params = ''; 5 | $.each($('[id^=notificationTrigger-]'), function () { 6 | let val = ''; 7 | if ($(this).is(':checkbox') || $(this).is(':radio')) { 8 | val = $(this).prop('checked') ? 1 : 0; 9 | } else { 10 | val = $(this).val(); 11 | } 12 | 13 | params += '&' + $(this).attr('id') + '=' + val; 14 | }); 15 | 16 | $.each($('[id^=notificationPlatformParameter-]'), function () { 17 | const required = $(this).attr('data-required'); 18 | let val = ''; 19 | if ($(this).is(':checkbox') || $(this).is(':radio')) { 20 | val = $(this).prop('checked') ? 1 : 0; 21 | } else { 22 | val = $(this).val(); 23 | } 24 | 25 | if (required && val == '') { 26 | requiredError = true; 27 | } 28 | 29 | params += '&' + $(this).attr('id') + '=' + val; 30 | }); 31 | 32 | if (requiredError) { 33 | toast('Notifications', 'Required fields can not be empty', 'error'); 34 | return; 35 | } 36 | 37 | loadingStart(); 38 | $.ajax({ 39 | type: 'POST', 40 | url: '../ajax/notification.php', 41 | data: '&m=saveNotification&platformId=' + platformId + '&linkId=' + linkId + params, 42 | dataType: 'json', 43 | success: function (resultData) { 44 | loadingStop(); 45 | if (resultData.error) { 46 | toast('Notifications', resultData.error, 'error'); 47 | return; 48 | } 49 | 50 | dialogClose('openNotificationTriggers'); 51 | toast('Notifications', 'Notification changes have been saved', 'success'); 52 | // refreesh(); 53 | } 54 | }); 55 | 56 | } 57 | // --------------------------------------------------------------------------------------------- 58 | function addNotification(platformId) 59 | { 60 | let requiredError = false; 61 | let params = ''; 62 | $.each($('[id^=notificationTrigger-]'), function () { 63 | let val = ''; 64 | if ($(this).is(':checkbox') || $(this).is(':radio')) { 65 | val = $(this).prop('checked') ? 1 : 0; 66 | } else { 67 | val = $(this).val(); 68 | } 69 | 70 | params += '&' + $(this).attr('id') + '=' + val; 71 | }); 72 | 73 | $.each($('[id^=notificationPlatformParameter-]'), function () { 74 | const required = $(this).attr('data-required'); 75 | let val = ''; 76 | if ($(this).is(':checkbox') || $(this).is(':radio')) { 77 | val = $(this).prop('checked') ? 1 : 0; 78 | } else { 79 | val = $(this).val(); 80 | } 81 | 82 | if (required && val == '') { 83 | requiredError = true; 84 | } 85 | 86 | params += '&' + $(this).attr('id') + '=' + val; 87 | }); 88 | 89 | if (requiredError) { 90 | toast('Notifications', 'Required fields can not be empty', 'error'); 91 | return; 92 | } 93 | 94 | loadingStart(); 95 | $.ajax({ 96 | type: 'POST', 97 | url: '../ajax/notification.php', 98 | data: '&m=addNotification&platformId=' + platformId + params, 99 | dataType: 'json', 100 | success: function (resultData) { 101 | loadingStop(); 102 | if (resultData.error) { 103 | toast('Notifications', resultData.error, 'error'); 104 | return; 105 | } 106 | 107 | dialogClose('openNotificationTriggers'); 108 | toast('Notifications', 'Notification has been added', 'success'); 109 | reload(); 110 | } 111 | }); 112 | 113 | } 114 | // --------------------------------------------------------------------------------------------- 115 | function deleteNotification(linkId) 116 | { 117 | if (confirm('Are you sure you want to delete this notification?')) { 118 | loadingStart(); 119 | $.ajax({ 120 | type: 'POST', 121 | url: '../ajax/notification.php', 122 | data: '&m=deleteNotification&linkId=' + linkId, 123 | success: function (resultData) { 124 | loadingStop(); 125 | 126 | dialogClose('openNotificationTriggers'); 127 | toast('Notifications', 'Notification changes have been saved', 'success'); 128 | reload(); 129 | } 130 | }); 131 | } 132 | } 133 | // --------------------------------------------------------------------------------------------- 134 | function openNotificationTriggers(platformId, linkId = 0) 135 | { 136 | loadingStart(); 137 | 138 | $.ajax({ 139 | type: 'POST', 140 | url: '../ajax/notification.php', 141 | data: '&m=openNotificationTriggers&platformId=' + platformId + '&linkId=' + linkId, 142 | success: function (resultData) { 143 | dialogOpen({ 144 | id: 'openNotificationTriggers', 145 | title: 'Notification triggers - ' + (linkId ? 'Edit' : 'Add'), 146 | size: 'lg', 147 | body: resultData, 148 | onOpen: function() { 149 | loadingStop(); 150 | } 151 | }); 152 | } 153 | }); 154 | } 155 | // --------------------------------------------------------------------------------------------- 156 | function testNotify(linkId, name) 157 | { 158 | loadingStart(); 159 | 160 | $.ajax({ 161 | type: 'POST', 162 | url: '../ajax/notification.php', 163 | data: '&m=testNotify&linkId=' + linkId + '&name=' + name, 164 | dataType: 'json', 165 | success: function (resultData) { 166 | if (resultData.error) { 167 | toast('Notifications', resultData.error, 'error'); 168 | } else { 169 | toast('Notifications', resultData.result, 'success'); 170 | } 171 | loadingStop(); 172 | } 173 | }); 174 | } 175 | // --------------------------------------------------------------------------------------------- 176 | -------------------------------------------------------------------------------- /root/app/www/public/js/settings.js: -------------------------------------------------------------------------------- 1 | function saveSettings() 2 | { 3 | let params = []; 4 | 5 | $.each($('[id^=setting-]'), function() { 6 | let val = ''; 7 | if ($(this).is(':checkbox') || $(this).is(':radio')) { 8 | val = $(this).prop('checked') ? 1 : 0; 9 | } else { 10 | val = $(this).val(); 11 | } 12 | 13 | params += '&' + $(this).attr('id').replace('setting-', '') + '=' + val; 14 | }); 15 | 16 | $.ajax({ 17 | type: 'POST', 18 | url: 'ajax/settings.php', 19 | data: '&m=saveSettings' + params, 20 | success: function (resultData) { 21 | toast('Settings', 'The settings have been updated', 'success'); 22 | } 23 | }); 24 | } 25 | // ------------------------------------------------------------------------------------------- 26 | function bustCache(key) 27 | { 28 | loadingStart(); 29 | 30 | $.ajax({ 31 | type: 'POST', 32 | url: 'ajax/settings.php', 33 | data: '&m=bustCache&key=' + key, 34 | success: function (resultData) { 35 | toast('Cache', 'The cache has been busted for the key: ' + key, 'success'); 36 | loadingStop(); 37 | } 38 | }); 39 | } 40 | // ------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------- /root/app/www/public/js/starr.js: -------------------------------------------------------------------------------- 1 | function testStarr(starrId, app) 2 | { 3 | if (!$('#instance-url-' + starrId).val()) { 4 | toast('Starr test', 'The url is required before testing', 'error'); 5 | return; 6 | } 7 | 8 | if (!$('#instance-apikey-' + starrId).val()) { 9 | toast('Starr test', 'The apikey is required before testing', 'error'); 10 | return; 11 | } 12 | 13 | let apikey = $('#instance-apikey-' + starrId).val(); 14 | if ($('#instance-apikey-' + starrId).val().includes('..')) { 15 | apikey = $('#instance-apikey-' + starrId).data('apikey'); 16 | } 17 | 18 | $.ajax({ 19 | type: 'POST', 20 | url: 'ajax/starr.php', 21 | data: '&m=testStarr&starrId=' + starrId + '&url=' + $('#instance-url-' + starrId).val() + '&apikey=' + apikey + '&app=' + app, 22 | dataType: 'json', 23 | success: function (resultData) { 24 | if (resultData.error) { 25 | toast('Starr test', resultData.error, 'error'); 26 | return; 27 | } 28 | 29 | toast('Starr test', resultData.result, 'success'); 30 | } 31 | }); 32 | } 33 | // ------------------------------------------------------------------------------------------- 34 | function saveStarr(starrId, app) 35 | { 36 | const apikey = $('#instance-apikey-' + starrId).val(); 37 | if (apikey.includes('..')) { 38 | $('#instance-apikey-' + starrId).val($('#instance-apikey-' + starrId).data('apikey')); 39 | } 40 | 41 | $.ajax({ 42 | type: 'POST', 43 | url: 'ajax/starr.php', 44 | data: '&m=saveStarr&starrId=' + starrId + '&app=' + app + '&url=' + $('#instance-url-' + starrId).val() + '&apikey=' + $('#instance-apikey-' + starrId).val() + '&username=' + encodeURIComponent($('#instance-username-' + starrId).val()) + '&password=' + encodeURIComponent($('#instance-password-' + starrId).val()), 45 | success: function (resultData) { 46 | if (resultData) { 47 | toast('Starr apps', resultData, 'error'); 48 | return; 49 | } 50 | 51 | reload(); 52 | } 53 | }); 54 | } 55 | // ------------------------------------------------------------------------------------------- 56 | function deleteStarr(starrId, app) 57 | { 58 | if (confirm('Are you sure you want to delete this instance?')) { 59 | $.ajax({ 60 | type: 'POST', 61 | url: 'ajax/starr.php', 62 | data: '&m=deleteStarr&starrId=' + starrId + '&app=' + app, 63 | success: function (resultData) { 64 | if (resultData) { 65 | toast('Starr apps', resultData, 'error'); 66 | return; 67 | } 68 | 69 | reload(); 70 | } 71 | }); 72 | } 73 | } 74 | // ------------------------------------------------------------------------------------------- 75 | function openAppStarrAccess(app, id, clone = '') 76 | { 77 | loadingStart(); 78 | 79 | $.ajax({ 80 | type: 'POST', 81 | url: 'ajax/starr.php', 82 | data: '&m=openAppStarrAccess&app=' + app + '&id=' + id + '&clone=' + clone, 83 | success: function (resultData) { 84 | dialogOpen({ 85 | id: 'openAppStarrAccess', 86 | title: 'Grant starr API access', 87 | size: 'lg', 88 | body: resultData, 89 | onOpen: function() { 90 | $('#access-template').select2({ 91 | theme: 'bootstrap-5' 92 | }); 93 | 94 | loadingStop(); 95 | } 96 | }); 97 | } 98 | }); 99 | } 100 | // ------------------------------------------------------------------------------------------- 101 | function saveAppStarrAccess(app, id) 102 | { 103 | let error = ''; 104 | if (!$('#access-name').val()) { 105 | error = 'App name is required'; 106 | } 107 | if (!$('#access-apikey').val()) { 108 | error = 'App apikey is required'; 109 | } 110 | if (!$('#access-instance').val()) { 111 | error = 'App instance is required'; 112 | } 113 | 114 | if (error) { 115 | toast('API access', error, 'error'); 116 | return; 117 | } 118 | 119 | loadingStart(); 120 | 121 | let params = '&app=' + app; 122 | params += '&name=' + $('#access-name').val(); 123 | params += '&apikey=' + $('#access-apikey').val(); 124 | params += '&id=' + id; 125 | params += '&starr_id=' + $('#access-instance').val(); 126 | params += '&template=' + $('#access-template').val(); 127 | 128 | $.each($('[id^=endpoint-counter-]'), function() { 129 | const counter = $(this).attr('id').replace('endpoint-counter-', ''); 130 | params += '&endpoint-' + counter + '=' + $(this).data('endpoint'); 131 | params += '&method-' + counter + '=' + $(this).data('method'); 132 | params += '&enabled-' + counter + '=' + ($(this).prop('checked') ? 1 : 0); 133 | }); 134 | 135 | $.ajax({ 136 | type: 'POST', 137 | url: 'ajax/starr.php', 138 | data: '&m=saveAppStarrAccess' + params, 139 | success: function (resultData) { 140 | loadingStop(); 141 | 142 | if (resultData) { 143 | toast('App access', resultData, 'error'); 144 | return; 145 | } 146 | 147 | reload(); 148 | } 149 | }); 150 | } 151 | // ------------------------------------------------------------------------------------------- 152 | function deleteAppStarrAccess(app, id) 153 | { 154 | if (confirm('Are you sure you want to delete this apps access to ' + app + '?')) { 155 | $.ajax({ 156 | type: 'POST', 157 | url: 'ajax/starr.php', 158 | data: '&m=deleteAppStarrAccess&app=' + app + '&id=' + id, 159 | success: function (resultData) { 160 | if (resultData) { 161 | toast('App access', resultData, 'error'); 162 | return; 163 | } 164 | 165 | reload(); 166 | } 167 | }); 168 | } 169 | } 170 | // ------------------------------------------------------------------------------------------- 171 | function resetUsage(app, id) 172 | { 173 | if (confirm('Are you sure you want to reset the usage counter?')) { 174 | $.ajax({ 175 | type: 'POST', 176 | url: 'ajax/starr.php', 177 | data: '&m=resetUsage&app=' + app + '&id=' + id, 178 | success: function (resultData) { 179 | if (resultData) { 180 | toast('Usage', resultData, 'error'); 181 | return; 182 | } 183 | 184 | reload(); 185 | } 186 | }); 187 | } 188 | } 189 | // ------------------------------------------------------------------------------------------- 190 | function addEndpointAccess(app, id, endpoint, method, endpointHash) 191 | { 192 | $.ajax({ 193 | type: 'POST', 194 | url: 'ajax/starr.php', 195 | data: '&m=addEndpointAccess&app=' + app + '&id=' + id + '&endpoint=' + endpoint + '&method=' + method, 196 | success: function (resultData) { 197 | if (resultData) { 198 | toast('App access', resultData, 'error'); 199 | return; 200 | } 201 | 202 | $('#disallowed-endpoint-' + endpointHash + ', #allowed-endpoint-' + endpointHash).toggle(); 203 | toast('Endpoint access', 'The ' + endpoint + ' endpoint has been allowed for this app', 'success'); 204 | } 205 | }); 206 | } 207 | // ------------------------------------------------------------------------------------------- 208 | function removeEndpointAccess(app, id, endpoint, method, endpointHash) 209 | { 210 | $.ajax({ 211 | type: 'POST', 212 | url: 'ajax/starr.php', 213 | data: '&m=removeEndpointAccess&app=' + app + '&id=' + id + '&endpoint=' + endpoint + '&method=' + method, 214 | success: function (resultData) { 215 | if (resultData) { 216 | toast('App access', resultData, 'error'); 217 | return; 218 | } 219 | 220 | $('#disallowed-endpoint-' + endpointHash + ', #allowed-endpoint-' + endpointHash).toggle(); 221 | toast('Endpoint access', 'The ' + endpoint + ' endpoint has been blocked for this app', 'success'); 222 | } 223 | }); 224 | } 225 | // ------------------------------------------------------------------------------------------- 226 | function viewAppEndpointDiff(appId) 227 | { 228 | loadingStart(); 229 | 230 | $.ajax({ 231 | type: 'POST', 232 | url: 'ajax/starr.php', 233 | data: '&m=viewAppEndpointDiff&appId=' + appId, 234 | success: function (resultData) { 235 | dialogOpen({ 236 | id: 'viewAppEndpointDiff', 237 | title: 'Template endpoint differences', 238 | size: 'lg', 239 | body: resultData, 240 | onOpen: function() { 241 | loadingStop(); 242 | } 243 | }); 244 | } 245 | }); 246 | } 247 | // ------------------------------------------------------------------------------------------- 248 | function autoAdjustAppEndpoints(appId) 249 | { 250 | loadingStart(); 251 | 252 | $.ajax({ 253 | type: 'POST', 254 | url: 'ajax/starr.php', 255 | data: '&m=autoAdjustAppEndpoints&appId=' + appId, 256 | success: function () { 257 | reload(); 258 | } 259 | }); 260 | } 261 | // ------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------- /root/app/www/public/js/templates.js: -------------------------------------------------------------------------------- 1 | function viewTemplate(template, index) 2 | { 3 | loadingStart(); 4 | 5 | $('[class^=app-index-]').removeClass('text-info'); 6 | $('.app-index-' + index).addClass('text-info'); 7 | 8 | $.ajax({ 9 | type: 'POST', 10 | url: 'ajax/templates.php', 11 | data: '&m=viewTemplate&template=' + template, 12 | success: function (resultData) { 13 | $('#template-viewer').html(resultData) 14 | loadingStop(); 15 | } 16 | }); 17 | } 18 | // --------------------------------------------------------------------------------------------- 19 | function applyTemplateOptions() 20 | { 21 | if ($('#access-template').val() == '0') { 22 | return; 23 | } 24 | 25 | $.each($('[id^=endpoint-counter-]'), function() { 26 | $(this).prop('checked', false); 27 | }); 28 | 29 | $.ajax({ 30 | type: 'POST', 31 | url: 'ajax/templates.php', 32 | data: '&m=applyTemplateOptions&template=' + $('#access-template').val(), 33 | dataType: 'json', 34 | success: function (resultData) { 35 | $.each($('[id^=endpoint-counter-]'), function() { 36 | const loopEndpoint = $(this).data('endpoint'); 37 | const loopMethod = $(this).data('method'); 38 | const loopId = $(this).prop('id'); 39 | 40 | $.each(resultData, function(endpoint, methods) { 41 | if (loopEndpoint == endpoint && methods.includes(loopMethod)) { 42 | $('#' + loopId).prop('checked', true); 43 | } 44 | }); 45 | }); 46 | 47 | toast('Templates', 'The selected template access has been applied', 'info'); 48 | } 49 | }); 50 | } 51 | // --------------------------------------------------------------------------------------------- 52 | function deleteCustomTemplate(app, item) 53 | { 54 | if (confirm('Are you sure you want to delete this template?')) { 55 | $.ajax({ 56 | type: 'POST', 57 | url: 'ajax/templates.php', 58 | data: '&m=deleteCustomTemplate&app=' + app + '&item=' + item, 59 | success: function () { 60 | reload(); 61 | } 62 | }); 63 | } 64 | } 65 | // --------------------------------------------------------------------------------------------- 66 | function openTemplateStarrAccess(app, id) 67 | { 68 | $.ajax({ 69 | type: 'POST', 70 | url: 'ajax/templates.php', 71 | data: '&m=openTemplateStarrAccess&app=' + app + '&id=' + id, 72 | success: function (resultData) { 73 | dialogOpen({ 74 | id: 'openTemplateStarrAccess', 75 | title: 'Create new template for ' + app, 76 | size: 'lg', 77 | body: resultData 78 | }); 79 | } 80 | }); 81 | } 82 | // ------------------------------------------------------------------------------------------- 83 | function saveTemplateStarrAccess(app, id) 84 | { 85 | if (!$('#new-template-name').val()) { 86 | toast('Templates', 'Template name is required', 'error'); 87 | return; 88 | } 89 | 90 | $.ajax({ 91 | type: 'POST', 92 | url: 'ajax/templates.php', 93 | data: '&m=saveTemplateStarrAccess&app=' + app + '&id=' + id + '&name=' + encodeURIComponent($('#new-template-name').val()), 94 | success: function () { 95 | dialogClose('openTemplateStarrAccess'); 96 | toast('Templates', 'The template has been added', 'info'); 97 | } 98 | }); 99 | } 100 | // ------------------------------------------------------------------------------------------- 101 | -------------------------------------------------------------------------------- /root/app/www/public/libraries/bootstrap/fonts/bootstrap-icons.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Notifiarr/starrproxy/73511ed7dc7dc646b798dde4030700564d499be5/root/app/www/public/libraries/bootstrap/fonts/bootstrap-icons.woff -------------------------------------------------------------------------------- /root/app/www/public/libraries/bootstrap/fonts/bootstrap-icons.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Notifiarr/starrproxy/73511ed7dc7dc646b798dde4030700564d499be5/root/app/www/public/libraries/bootstrap/fonts/bootstrap-icons.woff2 -------------------------------------------------------------------------------- /root/app/www/public/libraries/fontawesome/webfonts/fa-brands-400.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Notifiarr/starrproxy/73511ed7dc7dc646b798dde4030700564d499be5/root/app/www/public/libraries/fontawesome/webfonts/fa-brands-400.eot -------------------------------------------------------------------------------- /root/app/www/public/libraries/fontawesome/webfonts/fa-brands-400.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Notifiarr/starrproxy/73511ed7dc7dc646b798dde4030700564d499be5/root/app/www/public/libraries/fontawesome/webfonts/fa-brands-400.ttf -------------------------------------------------------------------------------- /root/app/www/public/libraries/fontawesome/webfonts/fa-brands-400.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Notifiarr/starrproxy/73511ed7dc7dc646b798dde4030700564d499be5/root/app/www/public/libraries/fontawesome/webfonts/fa-brands-400.woff -------------------------------------------------------------------------------- /root/app/www/public/libraries/fontawesome/webfonts/fa-brands-400.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Notifiarr/starrproxy/73511ed7dc7dc646b798dde4030700564d499be5/root/app/www/public/libraries/fontawesome/webfonts/fa-brands-400.woff2 -------------------------------------------------------------------------------- /root/app/www/public/libraries/fontawesome/webfonts/fa-regular-400.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Notifiarr/starrproxy/73511ed7dc7dc646b798dde4030700564d499be5/root/app/www/public/libraries/fontawesome/webfonts/fa-regular-400.eot -------------------------------------------------------------------------------- /root/app/www/public/libraries/fontawesome/webfonts/fa-regular-400.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Notifiarr/starrproxy/73511ed7dc7dc646b798dde4030700564d499be5/root/app/www/public/libraries/fontawesome/webfonts/fa-regular-400.ttf -------------------------------------------------------------------------------- /root/app/www/public/libraries/fontawesome/webfonts/fa-regular-400.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Notifiarr/starrproxy/73511ed7dc7dc646b798dde4030700564d499be5/root/app/www/public/libraries/fontawesome/webfonts/fa-regular-400.woff -------------------------------------------------------------------------------- /root/app/www/public/libraries/fontawesome/webfonts/fa-regular-400.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Notifiarr/starrproxy/73511ed7dc7dc646b798dde4030700564d499be5/root/app/www/public/libraries/fontawesome/webfonts/fa-regular-400.woff2 -------------------------------------------------------------------------------- /root/app/www/public/libraries/fontawesome/webfonts/fa-solid-900.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Notifiarr/starrproxy/73511ed7dc7dc646b798dde4030700564d499be5/root/app/www/public/libraries/fontawesome/webfonts/fa-solid-900.eot -------------------------------------------------------------------------------- /root/app/www/public/libraries/fontawesome/webfonts/fa-solid-900.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Notifiarr/starrproxy/73511ed7dc7dc646b798dde4030700564d499be5/root/app/www/public/libraries/fontawesome/webfonts/fa-solid-900.ttf -------------------------------------------------------------------------------- /root/app/www/public/libraries/fontawesome/webfonts/fa-solid-900.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Notifiarr/starrproxy/73511ed7dc7dc646b798dde4030700564d499be5/root/app/www/public/libraries/fontawesome/webfonts/fa-solid-900.woff -------------------------------------------------------------------------------- /root/app/www/public/libraries/fontawesome/webfonts/fa-solid-900.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Notifiarr/starrproxy/73511ed7dc7dc646b798dde4030700564d499be5/root/app/www/public/libraries/fontawesome/webfonts/fa-solid-900.woff2 -------------------------------------------------------------------------------- /root/app/www/public/loader.php: -------------------------------------------------------------------------------- 1 | migrations(); 68 | } 69 | $usageDb = new Database(USAGE_DATABASE_NAME); 70 | 71 | //-- INITIALIZE THE NOTIFICATION CLASS 72 | $notifications = new Notifications(); 73 | 74 | //-- CREATE APIKEY IF MISSING 75 | if (!file_exists(APP_APIKEY_FILE)) { 76 | $key = generateApikey(); 77 | file_put_contents(APP_APIKEY_FILE, $key); 78 | } 79 | define('APP_APIKEY', file_get_contents(APP_APIKEY_FILE)); 80 | 81 | //-- LOAD THE TABLES 82 | $starrsTable = $proxyDb->getStarrsTable(); 83 | $appsTable = $proxyDb->getAppsTable(); 84 | $settingsTable = $proxyDb->getSettings(); 85 | $usageTable = $usageDb->getUsageTable(); 86 | 87 | //-- SOMETIMES THE TABLE IS BUSY, RETRY 88 | if (!$usageTable && ($app || !$page || $page == 'home')) { 89 | sleep(1); 90 | $usageTable = $usageDb->getUsageTable(); 91 | } 92 | 93 | //-- CONSTANTS BASED ON DATABASE SETTINGS 94 | define('LOG_ROTATE_SIZE', $settingsTable['logRotationSize'] ?: $LOG_ROTATE_SIZE); 95 | define('LOG_AGE', $settingsTable['logRetentionLength'] ?: $LOG_AGE); 96 | define('BACKUP_AGE', $settingsTable['backupRetentionLength'] ?: $BACKUP_AGE); 97 | 98 | define('TEMPLATE_ORDER', $settingsTable['templateOrder'] ?: 1); 99 | -------------------------------------------------------------------------------- /root/app/www/public/migrations/001_initial_setup.php: -------------------------------------------------------------------------------- 1 | '[Q] ' . preg_replace('!\s+!', ' ', $query)]); 76 | 77 | $proxyDb->query($query); 78 | 79 | if ($proxyDb->error() != 'not an error') { 80 | logger(MIGRATION_LOG, ['text' => '[R] ' . $proxyDb->error()]); 81 | } else { 82 | logger(MIGRATION_LOG, ['text' => '[R] query applied!']); 83 | } 84 | } 85 | 86 | $q = []; 87 | $q[] = "CREATE TABLE " . USAGE_TABLE . " ( 88 | id INTEGER PRIMARY KEY, 89 | app_id INTEGER NOT NULL UNIQUE, 90 | allowed INTEGER, 91 | rejected INTEGER 92 | )"; 93 | 94 | foreach ($q as $query) { 95 | logger(MIGRATION_LOG, ['text' => '[Q] ' . preg_replace('!\s+!', ' ', $query)]); 96 | 97 | $usageDb->query($query); 98 | 99 | if ($usageDb->error() != 'not an error') { 100 | logger(MIGRATION_LOG, ['text' => '[R] ' . $usageDb->error()]); 101 | } else { 102 | logger(MIGRATION_LOG, ['text' => '[R] query applied!']); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /root/app/www/public/migrations/002_settings.php: -------------------------------------------------------------------------------- 1 | $BACKUP_AGE, 20 | 'logRotationSize' => $LOG_ROTATE_SIZE, 21 | 'logRetentionLength' => $LOG_AGE, 22 | ]; 23 | 24 | $settingRows = []; 25 | foreach ($settings as $key => $val) { 26 | $settingRows[] = "('" . $key . "', '" . $val . "')"; 27 | } 28 | 29 | $q[] = "INSERT INTO " . SETTINGS_TABLE . " 30 | (`name`, `value`) 31 | VALUES " . implode(', ', $settingRows); 32 | 33 | foreach ($q as $query) { 34 | logger(MIGRATION_LOG, ['text' => '[Q] ' . preg_replace('!\s+!', ' ', $query)]); 35 | 36 | $proxyDb->query($query); 37 | 38 | if ($proxyDb->error() != 'not an error') { 39 | logger(MIGRATION_LOG, ['text' => '[R] ' . $proxyDb->error()]); 40 | } else { 41 | logger(MIGRATION_LOG, ['text' => '[R] query applied!']); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /root/app/www/public/migrations/003_notifications.php: -------------------------------------------------------------------------------- 1 | '[Q] ' . preg_replace('!\s+!', ' ', $query)]); 24 | 25 | $proxyDb->query($query); 26 | 27 | if ($proxyDb->error() != 'not an error') { 28 | logger(MIGRATION_LOG, ['text' => '[R] ' . $proxyDb->error()]); 29 | } else { 30 | logger(MIGRATION_LOG, ['text' => '[R] query applied!']); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /root/app/www/public/migrations/004_save_app_template.php: -------------------------------------------------------------------------------- 1 | '[Q] ' . preg_replace('!\s+!', ' ', $query)]); 22 | 23 | $proxyDb->query($query); 24 | 25 | if ($proxyDb->error() != 'not an error') { 26 | logger(MIGRATION_LOG, ['text' => '[R] ' . $proxyDb->error()]); 27 | } else { 28 | logger(MIGRATION_LOG, ['text' => '[R] query applied!']); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /root/app/www/public/migrations/005_ui_link_settings.php: -------------------------------------------------------------------------------- 1 | true, 20 | 'uiHeaderProwlarr' => true, 21 | 'uiHeaderRadarr' => true, 22 | 'uiHeaderReadarr' => true, 23 | 'uiHeaderSonarr' => true, 24 | 'uiHeaderWhisparr' => true, 25 | 'uiHeaderNotifications' => true, 26 | 'uiHeaderHelp' => true 27 | ]; 28 | 29 | $settingRows = []; 30 | foreach ($settings as $key => $val) { 31 | $settingRows[] = "('" . $key . "', '" . $val . "')"; 32 | } 33 | 34 | $q[] = "INSERT INTO " . SETTINGS_TABLE . " 35 | (`name`, `value`) 36 | VALUES " . implode(', ', $settingRows); 37 | 38 | foreach ($q as $query) { 39 | logger(MIGRATION_LOG, ['text' => '[Q] ' . preg_replace('!\s+!', ' ', $query)]); 40 | 41 | $proxyDb->query($query); 42 | 43 | if ($proxyDb->error() != 'not an error') { 44 | logger(MIGRATION_LOG, ['text' => '[R] ' . $proxyDb->error()]); 45 | } else { 46 | logger(MIGRATION_LOG, ['text' => '[R] query applied!']); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /root/app/www/public/migrations/006_ui_template_order.php: -------------------------------------------------------------------------------- 1 | 1 20 | ]; 21 | 22 | $settingRows = []; 23 | foreach ($settings as $key => $val) { 24 | $settingRows[] = "('" . $key . "', '" . $val . "')"; 25 | } 26 | 27 | $q[] = "INSERT INTO " . SETTINGS_TABLE . " 28 | (`name`, `value`) 29 | VALUES " . implode(', ', $settingRows); 30 | 31 | foreach ($q as $query) { 32 | logger(MIGRATION_LOG, ['text' => '[Q] ' . preg_replace('!\s+!', ' ', $query)]); 33 | 34 | $proxyDb->query($query); 35 | 36 | if ($proxyDb->error() != 'not an error') { 37 | logger(MIGRATION_LOG, ['text' => '[R] ' . $proxyDb->error()]); 38 | } else { 39 | logger(MIGRATION_LOG, ['text' => '[R] query applied!']); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /root/app/www/public/pages/help.php: -------------------------------------------------------------------------------- 1 | 18 | 19 |
20 |

Help

21 |
Usage
22 | Add your starr apps to the system, then create proxy access for the starr app by adding a 3rd party app/script and settings its access permissions
23 | 24 |
App permissions and templates
25 | There are applied settings, templates & custom templates. Templates are not "saved" with the app but instead "applied" to the app as a way to quickly setup basic access for it. 26 |
    27 |
  • Settings: This is what the system uses to allow/reject access to an app. It is the list of endpoints when you modify an app.
  • 28 |
  • Native template: This is a pre-built list of what an app uses that can be used as a starting point. It will disable/enable the endpoints needed to allow that app to function and then you can customize the selection after.
  • 29 |
  • Custom template: This has the same use case as the native ones but allow for custom apps or scripts to have a pre-built list of endpoints in case you add it multiple times.
  • 30 |
31 | 32 |
Giving a new app access
33 | Existing templates:
34 |
    35 |
  • Select the template from the dropdown to automaticlally enable all needed endpoints/methods
  • 36 |
37 | No template:
38 |
    39 |
  • The inclusion method is suggested here. Add the app with no access and watch the logs for errors and enable the needed endpoints and methods
  • 40 |
  • The bottom of the log viewer will show all endpoints requested by the app
  • 41 |
42 | 43 |
3rd party implementation
44 | You are going to use the URL and apikey from the proxy in all apps instead of the real starr url and apikey 45 |
    46 |
  • App: notifiarr
  • 47 |
  • Instance: Radarr
  • 48 |
  • URL:
  • 49 |
  • Apikey: The one generated in the Radarr section for the app
  • 50 |
  • Open the notifiarr client, click Starr apps, use the proxy info
  • 51 |
52 | 53 |
Instance names
54 | These names are pulled from the starr apps themself, rename them there and then click "Save Instance" and it will update them here. 55 | 56 |
API Methods
57 |
    58 |
  • get: Asking the api for data
  • 59 |
  • post: Asking the api to add something
  • 60 |
  • put: Asking the api to update something
  • 61 |
  • delete: Asking the api to remove something
  • 62 |
63 |
-------------------------------------------------------------------------------- /root/app/www/public/pages/home.php: -------------------------------------------------------------------------------- 1 | 22 | 23 |
24 |
Purpose
25 |
26 |

27 | The list of 3rd party apps that utilize the starr app API's is ever growing but their is no limitation to what they can do and access! Most apps need very 28 | little access to function so why expose every method available and full access to your database needlessly?

29 | 30 | Permission scopes for apikeys is something that would be better served native in the apps but as it stands that request was denied years ago so it was time to simply make it 31 | possible with another solution. 32 |

33 |
34 |
35 | 36 |
37 |
Protection
38 |
39 |
40 |
41 |
42 |
Starr instances
43 |
44 | 45 | 46 | 47 | 48 | 49 | 50 | $instances) { 53 | ?> 54 | 55 | 56 | 57 | 58 | 59 | 65 |
StarrInstances
Nothing protected! What are you waiting for?
66 |
67 |
68 |
69 |
70 |
71 |
Starr app endpoints
72 |
73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | $endpointStats) { 84 | ?> 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 98 |
StarrAppsEnabledDisabled
Nothing protected! What are you waiting for?
99 |
100 |
101 |
102 |
103 |
104 |
Starr api enforcement
105 |
106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | $usageStats) { 116 | ?> 117 | 118 | 119 | 120 | 121 | 122 | 123 | 129 |
StarrAllowedRejected
Nothing protected! What are you waiting for?
130 |
131 |
132 |
133 |
134 |
135 |
Template problems
136 |
137 | $app['id'], 'app' => $app['name'], 'template' => count($appTemplate, COUNT_RECURSIVE), 'endpoints' => count($appAccess, COUNT_RECURSIVE)]; 156 | break; 157 | } 158 | } 159 | } 160 | } 161 | } 162 | 163 | if ($notMatching) { 164 | ?> 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | $starrAppApps) { 175 | foreach ($starrAppApps as $starrAppApp) { 176 | ?> 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 188 |
StarrAppApp accessTemplate access
189 | All apps with a template assigned match their template
Apps with no template assigned: 198 |
199 |
200 |
201 |
202 |
203 |
204 | -------------------------------------------------------------------------------- /root/app/www/public/pages/logs.php: -------------------------------------------------------------------------------- 1 | 20 | 21 |
22 |
23 | $groupLogs) { 26 | ?>

30 |
    31 |
  • 32 | 33 | 34 |
    35 |
  • 36 |
37 | $logs) { 42 | if ($app == 'system') { 43 | continue; 44 | } 45 | 46 | ?>

50 |
    51 |
  • 52 | 53 | 54 |
    55 |
  • 56 |
57 | 61 |
62 |
63 |
64 |
65 |
66 | -------------------------------------------------------------------------------- /root/app/www/public/pages/notifications.php: -------------------------------------------------------------------------------- 1 | getNotificationPlatforms(); 19 | $notificationTriggersTable = $proxyDb->getNotificationTriggers(); 20 | $notificationLinkTable = $proxyDb->getNotificationLinks(); 21 | 22 | ?> 23 |
24 |
25 |
26 |
Platforms
27 |
28 |
29 | ' : 'Coming soon!'; 32 | 33 | ?> 34 |
35 |
36 |
37 |

38 |
39 |
40 |
41 | 44 |
45 |
46 |
47 |
48 |
49 |
50 |
Configured senders
51 |
52 |
53 | 54 |
55 |
56 | Notifications have not been setup yet, click the plus icon above to set them up. 57 |
58 |
59 | 60 | 63 |
64 |
65 |
66 |

67 | 68 | 69 | 70 |

71 |
72 | You have not configured any triggers for this notificationgetNotificationTriggerNameFromId($triggerId, $notificationTriggersTable); 80 | $enabledTriggers[] = $trigger; 81 | } 82 | 83 | echo '
Enabled: ' . ($enabledTriggers ? implode(', ', $enabledTriggers) : 'No triggers enabled') . '
'; 84 | } 85 | ?> 86 |
87 |
88 |
89 |
90 | 93 | 94 |
95 |
96 |
97 |
98 |
99 | 20 | 21 |
22 |
23 | $appTemplates) { 26 | ?>

31 |
    32 |
  • 33 | 34 | 35 | 36 | 37 | 38 | ' : '' ?> 39 |
  • 40 |
41 | 45 |
46 |
47 |
48 |
49 |
50 | -------------------------------------------------------------------------------- /root/app/www/public/startup.php: -------------------------------------------------------------------------------- 1 | ' . "\n"; 11 | 12 | error_reporting(E_ERROR | E_PARSE); 13 | 14 | if (!defined('ABSOLUTE_PATH')) { 15 | define('ABSOLUTE_PATH', __DIR__ . '/'); 16 | } 17 | 18 | echo 'require_once ' . ABSOLUTE_PATH . 'loader.php' . "\n"; 19 | require_once ABSOLUTE_PATH . 'loader.php'; 20 | 21 | $command = 'memcached -u abc > /dev/null 2>&1 &'; 22 | 23 | echo date('c') . ' starting memcached \'' . $command . '\'' . "\n"; 24 | $shell->exec($command); 25 | 26 | echo date('c') . ' startup.php <-' . "\n"; 27 | -------------------------------------------------------------------------------- /root/app/www/public/templates/lidarr/notifiarr.json: -------------------------------------------------------------------------------- 1 | { 2 | "/api/v1/album": [ 3 | "get", 4 | "post" 5 | ], 6 | "/api/v1/album/{id}": [ 7 | "get" 8 | ], 9 | "/api/v1/system/backup": [ 10 | "get" 11 | ], 12 | "/api/v1/command": [ 13 | "post", 14 | "get" 15 | ], 16 | "/api/v1/history": [ 17 | "get" 18 | ], 19 | "/api/v1/metadataprofile": [ 20 | "get" 21 | ], 22 | "/api/v1/notification/{id}": [ 23 | "get", 24 | "put" 25 | ], 26 | "/api/v1/notification": [ 27 | "get", 28 | "post" 29 | ], 30 | "/api/v1/qualityprofile": [ 31 | "get" 32 | ], 33 | "/api/v1/queue/{id}": [ 34 | "delete" 35 | ], 36 | "/api/v1/queue": [ 37 | "get" 38 | ], 39 | "/api/v1/rootfolder/{id}": [ 40 | "get" 41 | ], 42 | "/api/v1/rootfolder": [ 43 | "get" 44 | ], 45 | "/api/v1/system/status": [ 46 | "get" 47 | ], 48 | "/api/v1/tag/{id}": [ 49 | "get", 50 | "put", 51 | "delete" 52 | ], 53 | "/api/v1/tag": [ 54 | "get", 55 | "post" 56 | ] 57 | } -------------------------------------------------------------------------------- /root/app/www/public/templates/lidarr/organizr.json: -------------------------------------------------------------------------------- 1 | { 2 | "/api/v3/rootfolder": [ 3 | "get" 4 | ], 5 | "/api/v3/calendar": [ 6 | "get" 7 | ], 8 | "/api/v3/queue": [ 9 | "get" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /root/app/www/public/templates/lidarr/unpackerr.json: -------------------------------------------------------------------------------- 1 | { 2 | "/api/v1/queue": [ 3 | "get" 4 | ] 5 | } -------------------------------------------------------------------------------- /root/app/www/public/templates/prowlarr/homepage.json: -------------------------------------------------------------------------------- 1 | { 2 | "/api/v1/indexerstats": [ 3 | "get" 4 | ] 5 | } -------------------------------------------------------------------------------- /root/app/www/public/templates/prowlarr/notifiarr.json: -------------------------------------------------------------------------------- 1 | { 2 | "/api/v1/system/status": [ 3 | "get" 4 | ], 5 | "/api/v1/system/backup": [ 6 | "get" 7 | ] 8 | } -------------------------------------------------------------------------------- /root/app/www/public/templates/prowlarr/nzb360.json: -------------------------------------------------------------------------------- 1 | { 2 | "/api/v1/indexer/categories": [ 3 | "get" 4 | ], 5 | "/api/v1/search": [ 6 | "get" 7 | ] 8 | } -------------------------------------------------------------------------------- /root/app/www/public/templates/prowlarr/organizr.json: -------------------------------------------------------------------------------- 1 | { 2 | "/api/v1/search": [ 3 | "get" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /root/app/www/public/templates/radarr/autobrr.json: -------------------------------------------------------------------------------- 1 | { 2 | "/api/v3/movie": [ 3 | "get" 4 | ], 5 | "/api/v3/release/push": [ 6 | "post" 7 | ], 8 | "/api/v3/system/status": [ 9 | "get" 10 | ], 11 | "/api/v3/tag": [ 12 | "get" 13 | ] 14 | } -------------------------------------------------------------------------------- /root/app/www/public/templates/radarr/bazarr.json: -------------------------------------------------------------------------------- 1 | { 2 | "/api/v3/command": [ 3 | "post" 4 | ], 5 | "/api/v3/mediacover/{movieId}/{filename}": [ 6 | "get" 7 | ], 8 | "/api/v3/movie": [ 9 | "get" 10 | ], 11 | "/api/v3/qualityprofile": [ 12 | "get" 13 | ], 14 | "/api/v3/rootfolder": [ 15 | "get" 16 | ], 17 | "/api/v3/system/status": [ 18 | "get" 19 | ], 20 | "/api/v3/tag": [ 21 | "get" 22 | ], 23 | "/api/system/status": [ 24 | "get" 25 | ] 26 | } -------------------------------------------------------------------------------- /root/app/www/public/templates/radarr/daps.json: -------------------------------------------------------------------------------- 1 | { 2 | "/api/v3/command": [ 3 | "post" 4 | ], 5 | "/api/v3/command/{id}": [ 6 | "get" 7 | ], 8 | "/api/v3/movie": [ 9 | "get" 10 | ], 11 | "/api/v3/movie/editor": [ 12 | "put" 13 | ], 14 | "/api/v3/queue": [ 15 | "get" 16 | ], 17 | "/api/v3/system/status": [ 18 | "get" 19 | ], 20 | "/api/v3/tag": [ 21 | "get", 22 | "post" 23 | ], 24 | "/api/v3/qualityprofile": [ 25 | "get" 26 | ], 27 | "/api/v3/health": [ 28 | "get" 29 | ] 30 | } -------------------------------------------------------------------------------- /root/app/www/public/templates/radarr/homepage.json: -------------------------------------------------------------------------------- 1 | { 2 | "/api/v3/calendar": [ 3 | "get" 4 | ], 5 | "/api/v3/movie": [ 6 | "get" 7 | ], 8 | "/api/v3/movie/{id}": [ 9 | "get" 10 | ], 11 | "/api/v3/queue/details": [ 12 | "get" 13 | ], 14 | "/api/v3/queue/status": [ 15 | "get" 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /root/app/www/public/templates/radarr/jellyseerr.json: -------------------------------------------------------------------------------- 1 | { 2 | "/api/v3/qualityprofile": [ 3 | "get" 4 | ], 5 | "/api/v3/system/status": [ 6 | "get" 7 | ], 8 | "/api/v3/rootfolder": [ 9 | "get" 10 | ], 11 | "/api/v3/tag": [ 12 | "get", 13 | "post" 14 | ], 15 | "/api/v3/command": [ 16 | "post" 17 | ], 18 | "/api/v3/queue": [ 19 | "get" 20 | ], 21 | "/api/v3/movie": [ 22 | "get", 23 | "post" 24 | ], 25 | "/api/v3/movie/{id}": [ 26 | "get" 27 | ], 28 | "/api/v3/movie/lookup": [ 29 | "get" 30 | ] 31 | } 32 | -------------------------------------------------------------------------------- /root/app/www/public/templates/radarr/kometa.json: -------------------------------------------------------------------------------- 1 | { 2 | "/api/v3/exclusions": [ 3 | "get" 4 | ], 5 | "/api/v3/movie": [ 6 | "get" 7 | ], 8 | "/api/v3/movie/lookup": [ 9 | "get" 10 | ], 11 | "/api/v3/qualityprofile": [ 12 | "get" 13 | ], 14 | "/api/v3/rootfolder": [ 15 | "get" 16 | ], 17 | "/api/v3/rootfolder/{id}": [ 18 | "get" 19 | ], 20 | "/api/v3/system/status": [ 21 | "get" 22 | ], 23 | "/api/v3/tag": [ 24 | "get" 25 | ], 26 | "/api/v3/movie/import": [ 27 | "post" 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /root/app/www/public/templates/radarr/lunasea.json: -------------------------------------------------------------------------------- 1 | { 2 | "/api/v3/calendar": [ 3 | "get" 4 | ], 5 | "/api/v3/command": [ 6 | "post" 7 | ], 8 | "/api/v3/credit": [ 9 | "get" 10 | ], 11 | "/api/v3/diskspace": [ 12 | "get" 13 | ], 14 | "/api/v3/extrafile": [ 15 | "get" 16 | ], 17 | "/api/v3/filesystem": [ 18 | "get" 19 | ], 20 | "/api/v3/history": [ 21 | "get" 22 | ], 23 | "/api/v3/history/movie": [ 24 | "get" 25 | ], 26 | "/api/v3/exclusions": [ 27 | "get" 28 | ], 29 | "/api/v3/importlist/movie": [ 30 | "get" 31 | ], 32 | "/api/v3/language": [ 33 | "get" 34 | ], 35 | "/api/v3/manualimport": [ 36 | "get" 37 | ], 38 | "/api/v3/mediacover/{movieId}/{filename}": [ 39 | "get" 40 | ], 41 | "/api/v3/movie": [ 42 | "get", 43 | "post", 44 | "put" 45 | ], 46 | "/api/v3/movie/{id}": [ 47 | "get" 48 | ], 49 | "/api/v3/moviefile": [ 50 | "get" 51 | ], 52 | "/api/v3/moviefile/{id}": [ 53 | "delete" 54 | ], 55 | "/api/v3/qualityprofile": [ 56 | "get" 57 | ], 58 | "/api/v3/queue/{id}": [ 59 | "delete" 60 | ], 61 | "/api/v3/queue": [ 62 | "get" 63 | ], 64 | "/api/v3/release": [ 65 | "get", 66 | "post" 67 | ], 68 | "/api/v3/rootfolder": [ 69 | "get" 70 | ], 71 | "/api/v3/system/status": [ 72 | "get" 73 | ], 74 | "/api/v3/tag": [ 75 | "get", 76 | "post" 77 | ], 78 | "/api/v3/tag/{id}": [ 79 | "delete" 80 | ], 81 | "/api/v3/qualitydefinition": [ 82 | "get" 83 | ], 84 | "/api/v3/movie/lookup": [ 85 | "get" 86 | ] 87 | } 88 | -------------------------------------------------------------------------------- /root/app/www/public/templates/radarr/nabarr.json: -------------------------------------------------------------------------------- 1 | { 2 | "/api/v3/exclusions": [ 3 | "get", 4 | "post" 5 | ], 6 | "/api/v3/exclusions/{id}": [ 7 | "put", 8 | "delete", 9 | "get" 10 | ], 11 | "/api/v3/importlistexclusion": [ 12 | "get", 13 | "post" 14 | ], 15 | "/api/v3/importlistexclusion/{id}": [ 16 | "put" 17 | ], 18 | "/api/v3/movie": [ 19 | "get", 20 | "post" 21 | ], 22 | "/api/v3/movie/{id}": [ 23 | "put", 24 | "delete", 25 | "get" 26 | ], 27 | "/api/v3/qualityprofile": [ 28 | "post", 29 | "get" 30 | ], 31 | "/api/v3/qualityprofile/{id}": [ 32 | "delete", 33 | "put", 34 | "get" 35 | ], 36 | "/api/v3/system/status": [ 37 | "get" 38 | ] 39 | } -------------------------------------------------------------------------------- /root/app/www/public/templates/radarr/notifiarr.json: -------------------------------------------------------------------------------- 1 | { 2 | "/api/v3/system/backup": [ 3 | "get" 4 | ], 5 | "/api/v3/blocklist": [ 6 | "get" 7 | ], 8 | "/api/v3/blocklist/movie": [ 9 | "get" 10 | ], 11 | "/api/v3/blocklist/{id}": [ 12 | "delete" 13 | ], 14 | "/api/v3/command": [ 15 | "post", 16 | "get" 17 | ], 18 | "/api/v3/command/{id}": [ 19 | "get" 20 | ], 21 | "/api/v3/customformat": [ 22 | "get", 23 | "post" 24 | ], 25 | "/api/v3/customformat/{id}": [ 26 | "put", 27 | "delete", 28 | "get" 29 | ], 30 | "/api/v3/history": [ 31 | "get" 32 | ], 33 | "/api/v3/importlist": [ 34 | "get", 35 | "post" 36 | ], 37 | "/api/v3/importlist/{id}": [ 38 | "put" 39 | ], 40 | "/api/v3/exclusions": [ 41 | "get", 42 | "post" 43 | ], 44 | "/api/v3/exclusions/{id}": [ 45 | "put", 46 | "delete", 47 | "get" 48 | ], 49 | "/api/v3/movie": [ 50 | "get", 51 | "post" 52 | ], 53 | "/api/v3/movie/{id}": [ 54 | "put", 55 | "delete", 56 | "get" 57 | ], 58 | "/api/v3/moviefile/{id}": [ 59 | "put", 60 | "delete", 61 | "get" 62 | ], 63 | "/api/v3/config/naming": [ 64 | "get", 65 | "put" 66 | ], 67 | "/api/v3/config/naming/{id}": [ 68 | "put", 69 | "get" 70 | ], 71 | "/api/v3/notification": [ 72 | "get", 73 | "post" 74 | ], 75 | "/api/v3/notification/{id}": [ 76 | "put", 77 | "get" 78 | ], 79 | "/api/v3/qualitydefinition/{id}": [ 80 | "put", 81 | "get" 82 | ], 83 | "/api/v3/qualitydefinition": [ 84 | "get" 85 | ], 86 | "/api/v3/qualitydefinition/update": [ 87 | "put" 88 | ], 89 | "/api/v3/qualityprofile": [ 90 | "post", 91 | "get" 92 | ], 93 | "/api/v3/qualityprofile/{id}": [ 94 | "delete", 95 | "put", 96 | "get" 97 | ], 98 | "/api/v3/queue/{id}": [ 99 | "delete" 100 | ], 101 | "/api/v3/queue": [ 102 | "get" 103 | ], 104 | "/api/v3/rootfolder": [ 105 | "get" 106 | ], 107 | "/api/v3/rootfolder/{id}": [ 108 | "get" 109 | ], 110 | "/api/v3/system/status": [ 111 | "get" 112 | ], 113 | "/api/v3/tag": [ 114 | "get", 115 | "post" 116 | ], 117 | "/api/v3/tag/{id}": [ 118 | "put", 119 | "get" 120 | ] 121 | } -------------------------------------------------------------------------------- /root/app/www/public/templates/radarr/nzb360.json: -------------------------------------------------------------------------------- 1 | { 2 | "/api/v3/history": [ 3 | "get" 4 | ], 5 | "/api/v3/mediacover/{movieId}/{filename}": [ 6 | "get" 7 | ], 8 | "/api/v3/movie": [ 9 | "get", 10 | "put" 11 | ], 12 | "/api/v3/qualityprofile": [ 13 | "get" 14 | ], 15 | "/api/v3/queue/status": [ 16 | "get" 17 | ], 18 | "/api/v3/system/status": [ 19 | "get" 20 | ], 21 | "/api/v3/credit": [ 22 | "get" 23 | ], 24 | "/api/v3/rootfolder": [ 25 | "get" 26 | ], 27 | "/api/v3/command": [ 28 | "post" 29 | ], 30 | "/api/v3/release": [ 31 | "get" 32 | ], 33 | "/api/v3/movie/{id}": [ 34 | "get", 35 | "put" 36 | ], 37 | "/api/v3/tag": [ 38 | "get" 39 | ] 40 | } -------------------------------------------------------------------------------- /root/app/www/public/templates/radarr/omegabrr.json: -------------------------------------------------------------------------------- 1 | { 2 | "/api/v3/movie": [ 3 | "get" 4 | ] 5 | } -------------------------------------------------------------------------------- /root/app/www/public/templates/radarr/organizr.json: -------------------------------------------------------------------------------- 1 | { 2 | "/api/v3/rootfolder": [ 3 | "get" 4 | ], 5 | "/api/v3/calendar": [ 6 | "get" 7 | ], 8 | "/api/v3/queue": [ 9 | "get" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /root/app/www/public/templates/radarr/overseerr.json: -------------------------------------------------------------------------------- 1 | { 2 | "/api/v3/movie": [ 3 | "get", 4 | "post" 5 | ], 6 | "/api/v3/movie/{id}": [ 7 | "get" 8 | ], 9 | "/api/v3/qualityprofile": [ 10 | "get" 11 | ], 12 | "/api/v3/queue": [ 13 | "get" 14 | ], 15 | "/api/v3/rootfolder": [ 16 | "get" 17 | ], 18 | "/api/v3/system/status": [ 19 | "get" 20 | ], 21 | "/api/v3/tag": [ 22 | "get", 23 | "post" 24 | ], 25 | "/api/v3/movie/lookup": [ 26 | "get" 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /root/app/www/public/templates/radarr/prowlarr.json: -------------------------------------------------------------------------------- 1 | { 2 | "/api/v3/indexer": [ 3 | "get", 4 | "post" 5 | ], 6 | "/api/v3/indexer/{id}": [ 7 | "put", 8 | "get", 9 | "post" 10 | ], 11 | "/api/v3/indexer/schema": [ 12 | "get" 13 | ], 14 | "/api/v3/indexer/test": [ 15 | "post" 16 | ], 17 | "/api/v3/indexer/testall": [ 18 | "post" 19 | ], 20 | "/api/v3/indexer/action/{name}": [ 21 | "post" 22 | ], 23 | "/api/v3/config/indexer": [ 24 | "get" 25 | ], 26 | "/api/v3/config/indexer/{id}": [ 27 | "put", 28 | "get" 29 | ], 30 | "/api/v3/indexerflag": [ 31 | "get" 32 | ] 33 | } -------------------------------------------------------------------------------- /root/app/www/public/templates/radarr/recyclarr.json: -------------------------------------------------------------------------------- 1 | { 2 | "/api/v3/command/{id}": [ 3 | "get" 4 | ], 5 | "/api/v3/customformat": [ 6 | "get", 7 | "post" 8 | ], 9 | "/api/v3/customformat/{id}": [ 10 | "put", 11 | "delete", 12 | "get" 13 | ], 14 | "/api/v3/config/naming": [ 15 | "get" 16 | ], 17 | "/api/v3/config/naming/{id}": [ 18 | "put", 19 | "get" 20 | ], 21 | "/api/v3/qualitydefinition/{id}": [ 22 | "put", 23 | "get" 24 | ], 25 | "/api/v3/qualitydefinition": [ 26 | "get" 27 | ], 28 | "/api/v3/qualitydefinition/update": [ 29 | "put" 30 | ], 31 | "/api/v3/qualityprofile": [ 32 | "post", 33 | "get" 34 | ], 35 | "/api/v3/qualityprofile/{id}": [ 36 | "delete", 37 | "put", 38 | "get" 39 | ], 40 | "/api/v3/system/status": [ 41 | "get" 42 | ] 43 | } -------------------------------------------------------------------------------- /root/app/www/public/templates/radarr/unpackerr.json: -------------------------------------------------------------------------------- 1 | { 2 | "/api/v3/queue": [ 3 | "get" 4 | ] 5 | } -------------------------------------------------------------------------------- /root/app/www/public/templates/readarr/notifiarr.json: -------------------------------------------------------------------------------- 1 | { 2 | "/api/v1/author": [ 3 | "get", 4 | "post" 5 | ], 6 | "/api/v1/author/{id}": [ 7 | "get" 8 | ], 9 | "/api/v1/system/backup": [ 10 | "get" 11 | ], 12 | "/api/v1/book": [ 13 | "get" 14 | ], 15 | "/api/v1/book/{id}": [ 16 | "put", 17 | "delete", 18 | "get" 19 | ], 20 | "/api/v1/command": [ 21 | "post" 22 | ], 23 | "/api/v1/history": [ 24 | "get" 25 | ], 26 | "/api/v1/metadataprofile": [ 27 | "get" 28 | ], 29 | "/api/v1/notification": [ 30 | "get", 31 | "post" 32 | ], 33 | "/api/v1/notification/{id}": [ 34 | "put", 35 | "get" 36 | ], 37 | "/api/v1/qualityprofile": [ 38 | "get" 39 | ], 40 | "/api/v1/queue/{id}": [ 41 | "delete" 42 | ], 43 | "/api/v1/queue": [ 44 | "get" 45 | ], 46 | "/api/v1/rootfolder": [ 47 | "get" 48 | ], 49 | "/api/v1/rootfolder/{id}": [ 50 | "get" 51 | ], 52 | "/api/v1/system/status": [ 53 | "get" 54 | ], 55 | "/api/v1/tag": [ 56 | "get", 57 | "post" 58 | ] 59 | } -------------------------------------------------------------------------------- /root/app/www/public/templates/readarr/unpackerr.json: -------------------------------------------------------------------------------- 1 | { 2 | "/api/v1/queue": [ 3 | "get" 4 | ] 5 | } -------------------------------------------------------------------------------- /root/app/www/public/templates/sonarr/autobrr.json: -------------------------------------------------------------------------------- 1 | { 2 | "/api/v3/release/push": [ 3 | "post" 4 | ], 5 | "/api/v3/system/status": [ 6 | "get" 7 | ], 8 | "/api/v3/tag": [ 9 | "get" 10 | ], 11 | "/api/v3/series": [ 12 | "get" 13 | ] 14 | } -------------------------------------------------------------------------------- /root/app/www/public/templates/sonarr/bazarr.json: -------------------------------------------------------------------------------- 1 | { 2 | "/api/v3/command": [ 3 | "post" 4 | ], 5 | "/api/v3/episode": [ 6 | "get" 7 | ], 8 | "/api/v3/episode/{id}": [ 9 | "get" 10 | ], 11 | "/api/v3/episodefile": [ 12 | "get" 13 | ], 14 | "/api/v3/episodefile/{id}": [ 15 | "get" 16 | ], 17 | "/api/v3/filesystem": [ 18 | "get" 19 | ], 20 | "/api/v3/mediacover/{seriesId}/{filename}": [ 21 | "get" 22 | ], 23 | "/api/v3/rootfolder": [ 24 | "get" 25 | ], 26 | "/api/v3/series": [ 27 | "get" 28 | ], 29 | "/api/v3/series/{id}": [ 30 | "get" 31 | ], 32 | "/api/v3/system/status": [ 33 | "get" 34 | ], 35 | "/api/v3/tag": [ 36 | "get" 37 | ], 38 | "/api/system/status": [ 39 | "get" 40 | ] 41 | } 42 | -------------------------------------------------------------------------------- /root/app/www/public/templates/sonarr/daps.json: -------------------------------------------------------------------------------- 1 | { 2 | "/api/v3/system/status": [ 3 | "get" 4 | ], 5 | "/api/v3/series": [ 6 | "get" 7 | ], 8 | "/api/v3/episode": [ 9 | "get" 10 | ], 11 | "/api/v3/health": [ 12 | "get" 13 | ], 14 | "/api/v3/qualityprofile": [ 15 | "get" 16 | ] 17 | } -------------------------------------------------------------------------------- /root/app/www/public/templates/sonarr/homepage.json: -------------------------------------------------------------------------------- 1 | { 2 | "/api/v3/calendar": [ 3 | "get" 4 | ], 5 | "/api/v3/wanted/missing": [ 6 | "get" 7 | ], 8 | "/api/v3/queue": [ 9 | "get" 10 | ], 11 | "/api/v3/queue/details": [ 12 | "get" 13 | ], 14 | "/api/v3/series": [ 15 | "get" 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /root/app/www/public/templates/sonarr/jellyseerr.json: -------------------------------------------------------------------------------- 1 | { 2 | "/api/v3/system/status": [ 3 | "get" 4 | ], 5 | "/api/v3/qualityprofile": [ 6 | "get" 7 | ], 8 | "/api/v3/rootfolder": [ 9 | "get" 10 | ], 11 | "/api/v3/languageprofile": [ 12 | "get" 13 | ], 14 | "/api/v3/tag": [ 15 | "get" 16 | ], 17 | "/api/v3/command": [ 18 | "post" 19 | ], 20 | "/api/v3/queue": [ 21 | "get" 22 | ], 23 | "/api/v3/series/{id}": [ 24 | "get" 25 | ], 26 | "/api/v3/series": [ 27 | "get", 28 | "post", 29 | "put" 30 | ], 31 | "/api/v3/series/lookup": [ 32 | "get" 33 | ] 34 | } 35 | -------------------------------------------------------------------------------- /root/app/www/public/templates/sonarr/kometa.json: -------------------------------------------------------------------------------- 1 | { 2 | "/api/v3/exclusions": [ 3 | "get" 4 | ], 5 | "/api/v3/rootfolder": [ 6 | "get" 7 | ], 8 | "/api/v3/rootfolder/{id}": [ 9 | "get" 10 | ], 11 | "/api/v3/system/status": [ 12 | "get" 13 | ], 14 | "/api/v3/qualityprofile": [ 15 | "get" 16 | ], 17 | "/api/v3/movie": [ 18 | "get" 19 | ], 20 | "/api/v3/movie/lookup": [ 21 | "get" 22 | ], 23 | "/api/v3/tag": [ 24 | "get" 25 | ], 26 | "/api/v3/importlistexclusion": [ 27 | "get" 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /root/app/www/public/templates/sonarr/lunasea.json: -------------------------------------------------------------------------------- 1 | { 2 | "/api/v3/calendar": [ 3 | "get" 4 | ], 5 | "/api/v3/command": [ 6 | "post" 7 | ], 8 | "/api/v3/episode": [ 9 | "get" 10 | ], 11 | "/api/v3/episode/monitor": [ 12 | "put" 13 | ], 14 | "/api/v3/episodefile": [ 15 | "get" 16 | ], 17 | "/api/v3/episodefile/{id}": [ 18 | "delete" 19 | ], 20 | "/api/v3/history": [ 21 | "get" 22 | ], 23 | "/api/v3/history/series": [ 24 | "get" 25 | ], 26 | "/api/v3/importlistexclusion": [ 27 | "get" 28 | ], 29 | "/api/v3/languageprofile": [ 30 | "get" 31 | ], 32 | "/api/v3/mediacover/{seriesId}/{filename}": [ 33 | "get" 34 | ], 35 | "/api/v3/wanted/missing": [ 36 | "get" 37 | ], 38 | "/api/v3/qualityprofile": [ 39 | "get" 40 | ], 41 | "/api/v3/queue/{id}": [ 42 | "delete" 43 | ], 44 | "/api/v3/queue": [ 45 | "get" 46 | ], 47 | "/api/v3/queue/details": [ 48 | "get" 49 | ], 50 | "/api/v3/release": [ 51 | "get", 52 | "post" 53 | ], 54 | "/api/v3/rootfolder": [ 55 | "get" 56 | ], 57 | "/api/v3/series": [ 58 | "get", 59 | "post", 60 | "put" 61 | ], 62 | "/api/v3/series/{id}": [ 63 | "delete", 64 | "get" 65 | ], 66 | "/api/v3/system/status": [ 67 | "get" 68 | ], 69 | "/api/v3/tag": [ 70 | "get", 71 | "post" 72 | ], 73 | "/api/v3/tag/{id}": [ 74 | "delete" 75 | ], 76 | "/api/v3/series/lookup": [ 77 | "get" 78 | ] 79 | } 80 | -------------------------------------------------------------------------------- /root/app/www/public/templates/sonarr/nabarr.json: -------------------------------------------------------------------------------- 1 | { 2 | "/api/v3/importlistexclusion": [ 3 | "get", 4 | "post" 5 | ], 6 | "/api/v3/importlistexclusion/{id}": [ 7 | "put" 8 | ], 9 | "/api/v3/qualityprofile": [ 10 | "post", 11 | "get" 12 | ], 13 | "/api/v3/qualityprofile/{id}": [ 14 | "delete", 15 | "put", 16 | "get" 17 | ], 18 | "/api/v3/series": [ 19 | "get", 20 | "post" 21 | ], 22 | "/api/v3/series/{id}": [ 23 | "get", 24 | "put", 25 | "delete" 26 | ], 27 | "/api/v3/series/lookup": [ 28 | "get" 29 | ], 30 | "/api/v3/system/status": [ 31 | "get" 32 | ] 33 | } -------------------------------------------------------------------------------- /root/app/www/public/templates/sonarr/notifiarr.json: -------------------------------------------------------------------------------- 1 | { 2 | "/api/v3/system/backup": [ 3 | "get" 4 | ], 5 | "/api/v3/command": [ 6 | "post", 7 | "get" 8 | ], 9 | "/api/v3/command/{id}": [ 10 | "get" 11 | ], 12 | "/api/v3/customformat": [ 13 | "get", 14 | "post" 15 | ], 16 | "/api/v3/customformat/{id}": [ 17 | "put", 18 | "delete", 19 | "get" 20 | ], 21 | "/api/v3/episode": [ 22 | "get" 23 | ], 24 | "/api/v3/episode/{id}": [ 25 | "get" 26 | ], 27 | "/api/v3/episode/monitor": [ 28 | "put" 29 | ], 30 | "/api/v3/episodefile": [ 31 | "get" 32 | ], 33 | "/api/v3/episodefile/{id}": [ 34 | "delete", 35 | "get" 36 | ], 37 | "/api/v3/importlist": [ 38 | "get", 39 | "post" 40 | ], 41 | "/api/v3/importlist/{id}": [ 42 | "put" 43 | ], 44 | "/api/v3/config/naming": [ 45 | "get", 46 | "put" 47 | ], 48 | "/api/v3/config/naming/{id}": [ 49 | "put", 50 | "get" 51 | ], 52 | "/api/v3/notification": [ 53 | "get", 54 | "post" 55 | ], 56 | "/api/v3/notification/{id}": [ 57 | "put", 58 | "get" 59 | ], 60 | "/api/v3/qualitydefinition/{id}": [ 61 | "put", 62 | "get" 63 | ], 64 | "/api/v3/qualitydefinition": [ 65 | "get" 66 | ], 67 | "/api/v3/qualitydefinition/update": [ 68 | "put" 69 | ], 70 | "/api/v3/qualityprofile": [ 71 | "post", 72 | "get" 73 | ], 74 | "/api/v3/qualityprofile/{id}": [ 75 | "delete", 76 | "put", 77 | "get" 78 | ], 79 | "/api/v3/queue/{id}": [ 80 | "delete" 81 | ], 82 | "/api/v3/queue": [ 83 | "get" 84 | ], 85 | "/api/v3/rootfolder": [ 86 | "get" 87 | ], 88 | "/api/v3/rootfolder/{id}": [ 89 | "get" 90 | ], 91 | "/api/v3/seasonpass": [ 92 | "post" 93 | ], 94 | "/api/v3/series": [ 95 | "get", 96 | "post" 97 | ], 98 | "/api/v3/series/{id}": [ 99 | "get", 100 | "put", 101 | "delete" 102 | ], 103 | "/api/v3/series/editor": [ 104 | "put" 105 | ], 106 | "/api/v3/series/lookup": [ 107 | "get" 108 | ], 109 | "/api/v3/system/status": [ 110 | "get" 111 | ], 112 | "/api/v3/tag": [ 113 | "get", 114 | "post" 115 | ], 116 | "/api/v3/tag/{id}": [ 117 | "put", 118 | "get" 119 | ], 120 | "/api/v3/releaseprofile": [ 121 | "get" 122 | ], 123 | "/api/v3/history": [ 124 | "get" 125 | ] 126 | } -------------------------------------------------------------------------------- /root/app/www/public/templates/sonarr/nzb360.json: -------------------------------------------------------------------------------- 1 | { 2 | "/api/v3/calendar": [ 3 | "get" 4 | ], 5 | "/api/v3/mediacover/{seriesId}/{filename}": [ 6 | "get" 7 | ], 8 | "/api/v3/wanted/missing": [ 9 | "get" 10 | ], 11 | "/api/v3/queue/status": [ 12 | "get" 13 | ], 14 | "/api/v3/series": [ 15 | "get", 16 | "put" 17 | ], 18 | "/api/v3/system/status": [ 19 | "get" 20 | ], 21 | "/api/v3/series/{id}": [ 22 | "get", 23 | "put" 24 | ], 25 | "/api/v3/qualityprofile": [ 26 | "get" 27 | ], 28 | "/api/v3/episode": [ 29 | "get" 30 | ], 31 | "/api/v3/rootfolder": [ 32 | "get" 33 | ], 34 | "/api/v3/queue": [ 35 | "get" 36 | ], 37 | "/api/v3/episodefile": [ 38 | "get" 39 | ], 40 | "/api/v3/tag": [ 41 | "get" 42 | ], 43 | "/api/v3/languageprofile": [ 44 | "get" 45 | ], 46 | "/api/v3/command": [ 47 | "post" 48 | ], 49 | "/api/v3/episode/{id}": [ 50 | "put" 51 | ] 52 | } -------------------------------------------------------------------------------- /root/app/www/public/templates/sonarr/omegabrr.json: -------------------------------------------------------------------------------- 1 | { 2 | "/api/v3/series": [ 3 | "get" 4 | ] 5 | } -------------------------------------------------------------------------------- /root/app/www/public/templates/sonarr/organizr.json: -------------------------------------------------------------------------------- 1 | { 2 | "/api/v3/rootfolder": [ 3 | "get" 4 | ], 5 | "/api/v3/queue": [ 6 | "get" 7 | ], 8 | "/api/v3/calendar": [ 9 | "get" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /root/app/www/public/templates/sonarr/overseerr.json: -------------------------------------------------------------------------------- 1 | { 2 | "/api/v3/languageprofile": [ 3 | "get" 4 | ], 5 | "/api/v3/qualityprofile": [ 6 | "get" 7 | ], 8 | "/api/v3/queue": [ 9 | "get" 10 | ], 11 | "/api/v3/rootfolder": [ 12 | "get" 13 | ], 14 | "/api/v3/series": [ 15 | "get", 16 | "post" 17 | ], 18 | "/api/v3/series/{id}": [ 19 | "get" 20 | ], 21 | "/api/v3/series/lookup": [ 22 | "get" 23 | ], 24 | "/api/v3/system/status": [ 25 | "get" 26 | ], 27 | "/api/v3/tag": [ 28 | "get", 29 | "post" 30 | ] 31 | } 32 | -------------------------------------------------------------------------------- /root/app/www/public/templates/sonarr/prowlarr.json: -------------------------------------------------------------------------------- 1 | { 2 | "/api/v3/indexer": [ 3 | "get", 4 | "post" 5 | ], 6 | "/api/v3/indexer/{id}": [ 7 | "put", 8 | "get" 9 | ], 10 | "/api/v3/indexer/schema": [ 11 | "get" 12 | ], 13 | "/api/v3/indexer/test": [ 14 | "post" 15 | ], 16 | "/api/v3/indexer/testall": [ 17 | "post" 18 | ], 19 | "/api/v3/config/indexer": [ 20 | "get" 21 | ], 22 | "/api/v3/config/indexer/{id}": [ 23 | "put", 24 | "get" 25 | ], 26 | "/api/v3/indexerflag": [ 27 | "get" 28 | ] 29 | } -------------------------------------------------------------------------------- /root/app/www/public/templates/sonarr/recyclarr.json: -------------------------------------------------------------------------------- 1 | { 2 | "/api/v3/command/{id}": [ 3 | "get" 4 | ], 5 | "/api/v3/customformat": [ 6 | "get", 7 | "post" 8 | ], 9 | "/api/v3/customformat/{id}": [ 10 | "put", 11 | "delete", 12 | "get" 13 | ], 14 | "/api/v3/config/naming": [ 15 | "get" 16 | ], 17 | "/api/v3/config/naming/{id}": [ 18 | "put", 19 | "get" 20 | ], 21 | "/api/v3/qualitydefinition/{id}": [ 22 | "put", 23 | "get" 24 | ], 25 | "/api/v3/qualitydefinition": [ 26 | "get" 27 | ], 28 | "/api/v3/qualitydefinition/update": [ 29 | "put" 30 | ], 31 | "/api/v3/qualityprofile": [ 32 | "post", 33 | "get" 34 | ], 35 | "/api/v3/qualityprofile/{id}": [ 36 | "delete", 37 | "put", 38 | "get" 39 | ], 40 | "/api/v3/system/status": [ 41 | "get" 42 | ] 43 | } -------------------------------------------------------------------------------- /root/app/www/public/templates/sonarr/titlecardmaker.json: -------------------------------------------------------------------------------- 1 | { 2 | "/api/v3/series": [ 3 | "get" 4 | ], 5 | "/api/v3/system/status": [ 6 | "get" 7 | ], 8 | "/api/v3/tag": [ 9 | "get" 10 | ], 11 | "/api/v3/series/lookup": [ 12 | "get" 13 | ], 14 | "/api/v3/episode": [ 15 | "get" 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /root/app/www/public/templates/sonarr/unpackerr.json: -------------------------------------------------------------------------------- 1 | { 2 | "/api/v3/queue": [ 3 | "get" 4 | ] 5 | } -------------------------------------------------------------------------------- /root/app/www/public/templates/whisparr/unpackerr.json: -------------------------------------------------------------------------------- 1 | { 2 | "/api/v3/queue": [ 3 | "get" 4 | ] 5 | } -------------------------------------------------------------------------------- /root/etc/crontabs/abc: -------------------------------------------------------------------------------- 1 | # min hour day month weekday command 2 | 0 * * * * /usr/bin/php /app/www/public/crons/cleanup.php 3 | 30 23 * * * /usr/bin/php /app/www/public/crons/backup.php 4 | -------------------------------------------------------------------------------- /root/etc/nginx/nginx.conf: -------------------------------------------------------------------------------- 1 | ## Version 2024/05/27 - Changelog: https://github.com/linuxserver/docker-baseimage-alpine-nginx/commits/master/root/defaults/nginx/nginx.conf.sample 2 | 3 | ### Based on alpine defaults 4 | # https://git.alpinelinux.org/aports/tree/main/nginx/nginx.conf?h=3.20-stable 5 | 6 | user abc; 7 | 8 | # Set number of worker processes automatically based on number of CPU cores. 9 | include /config/nginx/worker_processes.conf; 10 | 11 | # Enables the use of JIT for regular expressions to speed-up their processing. 12 | pcre_jit on; 13 | 14 | # Configures default error logger. 15 | error_log /config/log/nginx/error.log; 16 | 17 | # Includes files with directives to load dynamic modules. 18 | include /etc/nginx/modules/*.conf; 19 | 20 | # Include files with config snippets into the root context. 21 | include /etc/nginx/conf.d/*.conf; 22 | 23 | events { 24 | # The maximum number of simultaneous connections that can be opened by 25 | # a worker process. 26 | worker_connections 1024; 27 | } 28 | 29 | http { 30 | # Includes mapping of file name extensions to MIME types of responses 31 | # and defines the default type. 32 | include /etc/nginx/mime.types; 33 | default_type application/octet-stream; 34 | 35 | # Name servers used to resolve names of upstream servers into addresses. 36 | # It's also needed when using tcpsocket and udpsocket in Lua modules. 37 | #resolver 1.1.1.1 1.0.0.1 2606:4700:4700::1111 2606:4700:4700::1001; 38 | include /config/nginx/resolver.conf; 39 | 40 | # Don't tell nginx version to the clients. Default is 'on'. 41 | server_tokens off; 42 | 43 | # Specifies the maximum accepted body size of a client request, as 44 | # indicated by the request header Content-Length. If the stated content 45 | # length is greater than this size, then the client receives the HTTP 46 | # error code 413. Set to 0 to disable. Default is '1m'. 47 | client_max_body_size 0; 48 | 49 | # Sendfile copies data between one FD and other from within the kernel, 50 | # which is more efficient than read() + write(). Default is off. 51 | sendfile on; 52 | 53 | # Causes nginx to attempt to send its HTTP response head in one packet, 54 | # instead of using partial frames. Default is 'off'. 55 | tcp_nopush on; 56 | 57 | # all ssl related config moved to ssl.conf 58 | # included in server blocks where listen 443 is defined 59 | 60 | # Enable gzipping of responses. 61 | #gzip on; 62 | 63 | # Set the Vary HTTP header as defined in the RFC 2616. Default is 'off'. 64 | gzip_vary on; 65 | 66 | # Helper variable for proxying websockets. 67 | map $http_upgrade $connection_upgrade { 68 | default upgrade; 69 | '' close; 70 | } 71 | 72 | # Sets the path, format, and configuration for a buffered log write. 73 | access_log /config/log/nginx/access.log; 74 | 75 | # Includes virtual hosts configs. 76 | include /etc/nginx/http.d/*.conf; 77 | 78 | #include /config/nginx/site-confs/*.conf; 79 | server { 80 | listen 80 default_server; 81 | listen [::]:80 default_server; 82 | 83 | listen 443 ssl http2 default_server; 84 | listen [::]:443 ssl http2 default_server; 85 | 86 | server_name _; 87 | 88 | include /config/nginx/ssl.conf; 89 | 90 | set $root /app/www/public; 91 | if (!-d /app/www/public) { 92 | set $root /config/www; 93 | } 94 | root $root; 95 | index index.html index.htm index.php; 96 | 97 | location / { 98 | # enable for basic auth 99 | #auth_basic "Restricted"; 100 | #auth_basic_user_file /config/nginx/.htpasswd; 101 | 102 | try_files $uri $uri/ /index.html /index.php$is_args$args; 103 | } 104 | 105 | location ~ ^(.+\.php)(.*)$ { 106 | try_files $fastcgi_script_name =404; 107 | fastcgi_split_path_info ^(.+\.php)(.*)$; 108 | fastcgi_pass 127.0.0.1:9000; 109 | fastcgi_index index.php; 110 | include /etc/nginx/fastcgi_params; 111 | } 112 | 113 | location ~ ^(.*)/api/(.*)$ { 114 | try_files $uri /api/index.php?endpoint=/api/$2&$args; 115 | } 116 | 117 | location ~ ^(.*)/backup/(.*)$ { 118 | try_files $uri /api/index.php?backup=/backup/$2&$args; 119 | } 120 | 121 | # deny access to .htaccess/.htpasswd files 122 | location ~ /\.ht { 123 | deny all; 124 | } 125 | } 126 | } 127 | 128 | daemon off; 129 | pid /run/nginx.pid; -------------------------------------------------------------------------------- /root/etc/s6-overlay/s6-rc.d/init-config-end/dependencies.d/init-starrproxy-config: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Notifiarr/starrproxy/73511ed7dc7dc646b798dde4030700564d499be5/root/etc/s6-overlay/s6-rc.d/init-config-end/dependencies.d/init-starrproxy-config -------------------------------------------------------------------------------- /root/etc/s6-overlay/s6-rc.d/init-starrproxy-config/dependencies.d/init-nginx-end: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Notifiarr/starrproxy/73511ed7dc7dc646b798dde4030700564d499be5/root/etc/s6-overlay/s6-rc.d/init-starrproxy-config/dependencies.d/init-nginx-end -------------------------------------------------------------------------------- /root/etc/s6-overlay/s6-rc.d/init-starrproxy-config/run: -------------------------------------------------------------------------------- 1 | #!/usr/bin/with-contenv bash 2 | # shellcheck shell=bash 3 | 4 | echo 's6-overlay init require(/app/www/public/startup.php) ->'; 5 | s6-setuidgid abc php -r "require '/app/www/public/startup.php';" 6 | echo 's6-overlay init require(/app/www/public/startup.php) <-'; 7 | -------------------------------------------------------------------------------- /root/etc/s6-overlay/s6-rc.d/init-starrproxy-config/type: -------------------------------------------------------------------------------- 1 | oneshot 2 | -------------------------------------------------------------------------------- /root/etc/s6-overlay/s6-rc.d/init-starrproxy-config/up: -------------------------------------------------------------------------------- 1 | /etc/s6-overlay/s6-rc.d/init-starrproxy-config/run 2 | -------------------------------------------------------------------------------- /root/etc/s6-overlay/s6-rc.d/user/contents.d/init-starrproxy-config: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Notifiarr/starrproxy/73511ed7dc7dc646b798dde4030700564d499be5/root/etc/s6-overlay/s6-rc.d/user/contents.d/init-starrproxy-config --------------------------------------------------------------------------------