├── .gitignore ├── startup.sh ├── requirements.txt ├── indexer ├── src │ ├── security.py │ ├── channels.py │ ├── settings.py │ ├── directories.py │ ├── parsers.py │ ├── file_upload.py │ ├── repository.py │ └── models.py └── main.py ├── Dockerfile ├── Makefile ├── nginx ├── nginx-theme │ ├── js │ │ ├── breadcrumbs.js │ │ └── list.js │ ├── footer.html │ ├── theme.css │ └── header.html └── nginx.conf ├── .github └── workflows │ ├── lint.yml │ └── deploy.yml ├── README.md └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | *.swp 3 | venv 4 | -------------------------------------------------------------------------------- /startup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euo pipefail 4 | 5 | nginx & 6 | python3 ./main.py & 7 | 8 | wait -n 9 | exit $? 10 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | fastapi==0.115.12 2 | uvicorn==0.34.2 3 | python-multipart==0.0.20 4 | jsonschema==4.17.3 5 | PyGithub==1.57 6 | black==25.1.0 7 | pygelf==0.4.2 8 | -------------------------------------------------------------------------------- /indexer/src/security.py: -------------------------------------------------------------------------------- 1 | import os 2 | from fastapi import Request 3 | from .settings import settings 4 | 5 | 6 | def check_token(request: Request) -> bool: 7 | if os.path.basename(request.url.path) in settings.private_paths: 8 | return request.headers.get("Token") == settings.token 9 | return True 10 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.11-alpine3.17 2 | 3 | RUN apk update 4 | RUN apk add tzdata nginx-mod-http-fancyindex nginx-mod-http-headers-more bash 5 | 6 | ADD requirements.txt /app/ 7 | RUN python3 -m pip install -r /app/requirements.txt 8 | 9 | COPY nginx/nginx.conf /etc/nginx/nginx.conf 10 | COPY nginx/nginx-theme /var/lib/nginx/html/nginx-theme 11 | ADD indexer /app 12 | COPY startup.sh /app/ 13 | 14 | WORKDIR /app 15 | CMD ["/bin/bash", "/app/startup.sh"] 16 | 17 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | VERSION := 0.0.0 2 | 3 | .PHONY: run 4 | run: venv requirements 5 | ./venv/bin/python3 indexer/main.py 6 | 7 | venv: 8 | python3 -m venv venv 9 | 10 | .PHONY: requirements 11 | requirements: venv 12 | ./venv/bin/pip install -q -r requirements.txt 13 | 14 | .PHONY: clean 15 | clean: 16 | rm -rf venv 17 | 18 | .PHONY: lint 19 | lint: venv requirements 20 | ./venv/bin/black . --check 21 | 22 | .PHONY: format 23 | format: venv requirements 24 | ./venv/bin/black . 25 | -------------------------------------------------------------------------------- /indexer/src/channels.py: -------------------------------------------------------------------------------- 1 | from .models import Channel 2 | 3 | 4 | development_channel = Channel( 5 | id="development", 6 | title="Development Channel", 7 | description="Latest builds, not yet tested by Flipper QA, be careful", 8 | ) 9 | release_candidate_channel = Channel( 10 | id="release-candidate", 11 | title="Release Candidate Channel", 12 | description="This is going to be released soon, undergoing QA tests now", 13 | ) 14 | release_channel = Channel( 15 | id="release", 16 | title="Stable Release Channel", 17 | description="Stable releases, tested by Flipper QA", 18 | ) 19 | -------------------------------------------------------------------------------- /nginx/nginx-theme/js/breadcrumbs.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * 3 | * This file is part of the nginx-fancyindex-flat-theme, licensed under the GNU 4 | * General Public License. See the LICENSE file for details. 5 | * 6 | * Copyright (C) 7 | * 2018 Alexander Haase 8 | */ 9 | function generateBreadcrumbs(){for(var e,a,n,r=window.location.pathname.replace(/\/$/,"").split("/"),t="",c="",o=0;o'+(n?"":'')+e+(n?"":""));document.getElementById("breadcrumbs").innerHTML=t} 10 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: 'Lint Python code with Black' 2 | 3 | on: 4 | push: 5 | branches: 6 | - dev 7 | tags: 8 | - '*' 9 | pull_request: 10 | 11 | jobs: 12 | lint: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: 'Checkout code' 16 | uses: actions/checkout@v3 17 | with: 18 | fetch-depth: 0 19 | ref: ${{ github.event.pull_request.head.sha }} 20 | 21 | - name: 'Setup Python' 22 | uses: actions/setup-python@v4 23 | with: 24 | python-version: '3.11' 25 | 26 | - name: 'Lint Python code' 27 | run: | 28 | make lint 29 | -------------------------------------------------------------------------------- /nginx/nginx-theme/footer.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 16 | 17 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /nginx/nginx-theme/theme.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * This file is part of the nginx-fancyindex-flat-theme (licensed under the GPL 3 | * license) and uses Twitter Bootstrap (v4) (licensed under the MIT license). 4 | * 5 | * Copyright (C) 6 | * 2018 Alexander Haase 7 | * 8 | * See the LICENSE file for details. 9 | */body{background-color:#f8f9fa;padding-bottom:20px}@media (prefers-color-scheme:white){body{background-color:#343a40;color:#fff}}.breadcrumb{background-color:transparent;padding-left:35px}.breadcrumb .breadcrumb-item a{color:#4caf50}html{position:relative;min-height:100%}.footer{position:absolute;bottom:0;width:100%;margin-bottom:0}.footer a,.footer a:focus,.footer a:hover{color:#4caf50}.header{background-color:#4caf50;color:#fff;min-height:24px}.header .navbar-brand{padding:0 8px;font-size:16px;line-height:24px;height:24px}#list a,#list a:focus,#list a:hover{color:#000}#list colgroup{display:none}#list .filename{word-break:break-all;white-space:normal}@media (prefers-color-scheme:dark){a,a:focus,a:hover{color:#000}} 10 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: 'Deploy' 2 | 3 | on: 4 | push: 5 | tags: 6 | - '*' 7 | 8 | env: 9 | IMAGE_NAME: "flipperdevices/flipper-update-indexer" 10 | 11 | jobs: 12 | build: 13 | runs-on: [self-hosted,FlipperZeroShell] 14 | steps: 15 | - name: 'Checkout code' 16 | uses: actions/checkout@v3 17 | with: 18 | fetch-depth: 0 19 | 20 | - name: 'Set image tag' 21 | id: tag 22 | run: | 23 | REF=${{ github.ref }}; 24 | TAG_FULL=${REF#refs/*/}; 25 | IMAGE_TAG=${TAG_FULL//\//_}; 26 | echo "IMAGE_TAG=${IMAGE_TAG}" >> $GITHUB_ENV 27 | echo "image_tag=${IMAGE_TAG}" >> $GITHUB_OUTPUT 28 | 29 | - name: 'Login to Docker Hub' 30 | uses: docker/login-action@v2 31 | with: 32 | username: ${{ secrets.DOCKER_REGISTRY_LOGIN }} 33 | password: ${{ secrets.DOCKER_REGISTRY_PASSWORD }} 34 | 35 | - name: 'Build docker image' 36 | run: | 37 | docker build -t "${IMAGE_NAME}:${IMAGE_TAG}" . 38 | 39 | - name: 'Upload docker image' 40 | run: | 41 | docker push "$IMAGE_NAME:$IMAGE_TAG" 42 | -------------------------------------------------------------------------------- /nginx/nginx-theme/header.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Flipper Zero Firmware Update 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 28 |
29 | 30 |
31 | 34 | 35 |
36 |
37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Flipper Zero Update Indexer and Uploader 2 | 3 | ## Start locally 4 | ```bash 5 | INDEXER_FIRMWARE_GITHUB_TOKEN= \ 6 | INDEXER_QFLIPPER_GITHUB_TOKEN= \ 7 | INDEXER_GITHUB_ORGANIZATION= \ 8 | INDEXER_QFLIPPER_GITHUB_REPO= \ 9 | INDEXER_FIRMWARE_GITHUB_REPO= \ 10 | INDEXER_BLACKMAGIC_GITHUB_TOKEN= \ 11 | INDEXER_BLACKMAGIC_GITHUB_REPO= \ 12 | INDEXER_TOKEN= \ 13 | INDEXER_BASE_URL= \ 14 | INDEXER_FILES_DIR= \ 15 | make run 16 | ``` 17 | 18 | Clearing: 19 | ```bash 20 | make clean 21 | ``` 22 | 23 | ## Requests example 24 | Get index 25 | ```bash 26 | curl 127.0.0.1:8000/firmware/directory.json 27 | ``` 28 | 29 | Get latest release 30 | ```bash 31 | # format: 127.0.0.1:8000/{directory}/{channel}/{target}/{type} 32 | # if target contains '/' (slash) replace it by '-' dash symbol 33 | curl 127.0.0.1:8000/firmware/release/f7/updater_json 34 | curl 127.0.0.1:8000/qFlipper/release/windows-amd64/installer 35 | ``` 36 | 37 | Trigger reindex 38 | ```bash 39 | curl -H "Token: YOUR_TOKEN" 127.0.0.1:8000/firmware/reindex 40 | ``` 41 | 42 | Upload files 43 | ```bash 44 | curl -L -H "Token: YOUR_TOKEN" \ 45 | -F "branch=drunkbatya/test-spimemmanager" \ 46 | -F "files=@flipper-z-any-core2_firmware-0.73.1.tgz" \ 47 | -F "files=@flipper-z-f7-full-0.73.1.json" \ 48 | 127.0.0.1:8000/firmware/uploadfiles 49 | ``` 50 | 51 | Upload files without reindex 52 | ```bash 53 | curl -L -H "Token: YOUR_TOKEN" \ 54 | -F "files=@gcc-arm-none-eabi-12.3-arm64-darwin-flipper-24.tar.gz" \ 55 | 127.0.0.1:8000/toolchain/uploadfilesraw 56 | ``` 57 | -------------------------------------------------------------------------------- /nginx/nginx-theme/js/list.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * 3 | * This file is part of the nginx-fancyindex-flat-theme, licensed under the GNU 4 | * General Public License. See the LICENSE file for details. 5 | * 6 | * Copyright (C) 7 | * 2018 Alexander Haase 8 | */ 9 | function generateList(){var e=document.getElementById("list");e.removeAttribute("cellpadding"),e.removeAttribute("cellspacing"),e.classList.add("table","table-sm","table-hover","text-nowrap"),e.tHead.children[0].classList.add("d-none","d-md-table-row"),"/"!=window.location.pathname&&e.deleteRow(1);for(var a,s=0;a=e.rows[s];s++)filetype=function(e){if(e.endsWith("/"))return"folder";switch(e.split(".").pop().toLowerCase()){case"txt":return"text";case"pdf":return"pdf";case"bmp":case"gif":case"jpeg":case"jpg":case"png":case"tif":case"tiff":return"image";case"aac":case"aiff":case"m4a":case"mp3":case"ogg":case"opus":case"wav":return"audio";case"amv":case"avi":case"flv":case"m4v":case"mkv":case"mov":case"mp4":case"m4p":case"mpeg":case"mpg":case"ogv":case"vob":case"webm":case"wmv":return"video";case"7z":case"a":case"apk":case"ar":case"bin":case"bz2":case"cab":case"dmg":case"gz":case"iso":case"jar":case"lz":case"lzma":case"lzo":case"pak":case"partimg":case"rar":case"s7z":case"tar":case"tbz2":case"tgz":case"tlz":case"txz":case"xz":case"zip":return"archive";case"doc":case"docx":case"odt":case"rtf":return"word";case"csv":case"ods":case"xls":case"xlsx":return"excel";case"odp":case"ppt":case"pptx":return"powerpoint";case"c":case"class":case"cpp":case"cs":case"h":case"hpp":case"hxx":case"java":case"py":case"sh":case"swift":case"vb":return"code"}}(a.cells[0].children[0].innerHTML),a.insertCell(0).innerHTML=0