├── .dockerignore ├── .gitignore ├── .gitlab-ci.yml ├── .idea └── codeStyleSettings.xml ├── .pylintrc ├── .weblate ├── CHANGELOG.md ├── Dockerfile ├── LICENSE ├── MANIFEST.in ├── README.md ├── Vagrantfile ├── debian ├── README.md ├── changelog ├── compat ├── control ├── data │ ├── org.fdroid.Repomaker.appdata.xml │ ├── org.fdroid.Repomaker.desktop │ └── org.fdroid.Repomaker.svg ├── repomaker.install ├── requirements.txt ├── rules └── source │ └── format ├── doc ├── README.md └── examples │ ├── repomaker.conf │ └── repomaker.service ├── docker ├── .env ├── README.md ├── apache.conf ├── docker-compose.yml ├── httpd-foreground ├── settings_docker.py ├── ssh_config └── wait-for ├── manage.py ├── package.json ├── pre-release.sh ├── purge.sh ├── repomaker ├── __init__.py ├── admin.py ├── apps.py ├── gui.py ├── locale │ ├── bo │ │ └── LC_MESSAGES │ │ │ ├── django.po │ │ │ └── djangojs.po │ ├── ca │ │ └── LC_MESSAGES │ │ │ ├── django.po │ │ │ └── djangojs.po │ ├── de │ │ └── LC_MESSAGES │ │ │ ├── django.po │ │ │ └── djangojs.po │ ├── django.pot │ ├── djangojs.pot │ ├── es │ │ └── LC_MESSAGES │ │ │ ├── django.po │ │ │ └── djangojs.po │ ├── es_MX │ │ └── LC_MESSAGES │ │ │ └── django.po │ ├── fa │ │ └── LC_MESSAGES │ │ │ ├── django.po │ │ │ └── djangojs.po │ ├── fr │ │ └── LC_MESSAGES │ │ │ ├── django.po │ │ │ └── djangojs.po │ ├── id │ │ └── LC_MESSAGES │ │ │ └── django.po │ ├── it │ │ └── LC_MESSAGES │ │ │ ├── django.po │ │ │ └── djangojs.po │ ├── ja │ │ └── LC_MESSAGES │ │ │ ├── django.po │ │ │ └── djangojs.po │ ├── ko │ │ └── LC_MESSAGES │ │ │ ├── django.po │ │ │ └── djangojs.po │ ├── nb_NO │ │ └── LC_MESSAGES │ │ │ ├── django.po │ │ │ └── djangojs.po │ ├── nl │ │ └── LC_MESSAGES │ │ │ ├── django.po │ │ │ └── djangojs.po │ ├── pl │ │ └── LC_MESSAGES │ │ │ ├── django.po │ │ │ └── djangojs.po │ ├── pt_BR │ │ └── LC_MESSAGES │ │ │ ├── django.po │ │ │ └── djangojs.po │ ├── ru │ │ └── LC_MESSAGES │ │ │ ├── django.po │ │ │ └── djangojs.po │ ├── sc │ │ └── LC_MESSAGES │ │ │ ├── django.po │ │ │ └── djangojs.po │ ├── sq │ │ └── LC_MESSAGES │ │ │ ├── django.po │ │ │ └── djangojs.po │ ├── tr │ │ └── LC_MESSAGES │ │ │ ├── django.po │ │ │ └── djangojs.po │ ├── ug │ │ └── LC_MESSAGES │ │ │ ├── django.po │ │ │ └── djangojs.po │ ├── uk │ │ └── LC_MESSAGES │ │ │ ├── django.po │ │ │ └── djangojs.po │ ├── zh_Hans │ │ └── LC_MESSAGES │ │ │ ├── django.po │ │ │ └── djangojs.po │ └── zh_Hant │ │ └── LC_MESSAGES │ │ ├── django.po │ │ └── djangojs.po ├── migrations │ ├── 0001_initial.py │ ├── __init__.py │ ├── default_categories.py │ ├── default_remote_repositories.py │ └── default_user.py ├── models │ ├── __init__.py │ ├── apk.py │ ├── apkpointer.py │ ├── app.py │ ├── category.py │ ├── remoteapp.py │ ├── remoterepository.py │ ├── repository.py │ ├── screenshot.py │ └── storage.py ├── settings.py ├── settings_desktop.py ├── settings_local.py ├── settings_test.py ├── settings_test_multi_user.py ├── static │ └── repomaker │ │ ├── css │ │ ├── app │ │ │ ├── add.scss │ │ │ ├── index.scss │ │ │ ├── styles.scss │ │ │ └── widget.scss │ │ ├── base.scss │ │ ├── forms.scss │ │ ├── login.scss │ │ ├── mixins.scss │ │ ├── mobile-detect.scss │ │ ├── repo │ │ │ ├── card.scss │ │ │ ├── index.scss │ │ │ ├── index_apps.scss │ │ │ ├── index_info.scss │ │ │ ├── index_share.scss │ │ │ ├── page.scss │ │ │ └── styles.scss │ │ ├── storage.scss │ │ ├── styles.scss │ │ └── variables.scss │ │ ├── images │ │ ├── default-app-icon.png │ │ ├── default-repo-icon.png │ │ ├── gitlab_url.png │ │ ├── list_item.png │ │ ├── list_item_active.png │ │ ├── repo_index_empty_state.png │ │ ├── repo_page │ │ │ ├── f-droid.png │ │ │ ├── facebook.png │ │ │ └── twitter.png │ │ ├── socialaccount_providers │ │ │ ├── facebook.png │ │ │ ├── github.png │ │ │ ├── gitlab.png │ │ │ ├── google.png │ │ │ ├── hyves.png │ │ │ ├── openid.png │ │ │ ├── twitter.png │ │ │ └── yahoo.png │ │ └── storage.png │ │ └── js │ │ ├── app │ │ ├── add.js │ │ ├── edit.js │ │ └── remote_add.js │ │ ├── drag-and-drop.js │ │ ├── endless-app-scroll.js │ │ ├── mdl-tinymce.js │ │ ├── no-invalid-by-default.js │ │ └── repo │ │ ├── add.js │ │ ├── apps.js │ │ └── share.js ├── storage.py ├── tasks.py ├── templates │ ├── account │ │ ├── base.html │ │ ├── index.html │ │ ├── login.html │ │ ├── login_form.html │ │ ├── logout.html │ │ ├── password_reset.html │ │ ├── password_reset_done.html │ │ ├── signup.html │ │ └── signup_form.html │ ├── django │ │ └── forms │ │ │ └── widgets │ │ │ ├── input.html │ │ │ ├── select.html │ │ │ └── textarea.html │ └── repomaker │ │ ├── app │ │ ├── apk_delete.html │ │ ├── delete.html │ │ ├── edit.html │ │ ├── edit_blocked.html │ │ ├── feature_graphic_delete.html │ │ ├── index.html │ │ ├── remote_add.html │ │ ├── screenshot_delete.html │ │ └── translation_add.html │ │ ├── base.html │ │ ├── base_modal.html │ │ ├── db_locked.html │ │ ├── error.html │ │ ├── form.html │ │ ├── index.html │ │ ├── repo │ │ ├── add.html │ │ ├── delete.html │ │ ├── edit.html │ │ ├── index.html │ │ ├── index_apps.html │ │ ├── index_info.html │ │ ├── index_share.html │ │ └── remotes.html │ │ ├── repo_page │ │ ├── base.html │ │ ├── fdroid.html │ │ ├── index.html │ │ └── qr_code.html │ │ ├── storage │ │ ├── delete.html │ │ ├── detail.html │ │ ├── detail_git.html │ │ ├── detail_s3.html │ │ ├── detail_ssh.html │ │ ├── detail_ssh_key.html │ │ ├── form.html │ │ ├── form_git.html │ │ └── index.html │ │ └── widgets │ │ ├── app.html │ │ ├── app_with_action.html │ │ ├── pagination.html │ │ └── site_notice.html ├── templatetags │ ├── __init__.py │ └── site_notice.py ├── tests │ ├── __init__.py │ ├── models │ │ ├── __init__.py │ │ ├── test_apk.py │ │ ├── test_apkpointer.py │ │ ├── test_app.py │ │ ├── test_category.py │ │ ├── test_remoteapp.py │ │ ├── test_remoterepository.py │ │ ├── test_repository.py │ │ ├── test_screenshot.py │ │ └── test_storage.py │ ├── test_tasks.py │ ├── test_urls.py │ ├── test_utils.py │ └── views │ │ ├── __init__.py │ │ ├── test_apk.py │ │ ├── test_app.py │ │ ├── test_misc.py │ │ ├── test_remoterepository.py │ │ ├── test_repository.py │ │ ├── test_screenshot.py │ │ └── test_storage.py ├── translation.py ├── urls.py ├── utils.py ├── views │ ├── __init__.py │ ├── apk.py │ ├── app.py │ ├── gitstorage.py │ ├── remoterepository.py │ ├── repository.py │ ├── s3storage.py │ ├── screenshot.py │ ├── sshstorage.py │ └── storage.py └── wsgi.py ├── requirements-dev.txt ├── requirements-gui.txt ├── requirements.txt ├── run-tests.sh ├── run.sh ├── setup.cfg ├── setup.py ├── setup.sh ├── tests ├── keystore.jks ├── test-pep8.sh ├── test-pylint.sh ├── test-units.sh ├── test.avi ├── test.docx ├── test.epub ├── test.flac ├── test.mp3 ├── test.mp4 ├── test.ods ├── test.odt ├── test.ogg ├── test.pdf ├── test.png ├── test_1.apk ├── test_2.apk ├── test_3.apk ├── test_invalid_signature.apk └── test_md5_signature.apk └── update-translations.sh /.dockerignore: -------------------------------------------------------------------------------- 1 | .git 2 | .gitignore 3 | .gitlab-ci.yml 4 | .idea 5 | .pylintrc 6 | .travis.yml 7 | MANIFEST.in 8 | README.md 9 | /debian 10 | !/debian/requirements.txt 11 | /doc 12 | /docker/data 13 | /docker/pgdata 14 | /node_modules 15 | /tests 16 | /setup.* 17 | /*.sh 18 | !/pre-release.sh 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .coverage 2 | .idea 3 | *.pyc 4 | credentials 5 | 6 | data/db.sqlite3 7 | data/media/packages/ 8 | data/media/remote_repo_* 9 | data/media/user_* 10 | data/private_repo/ 11 | 12 | debian/.debhelper 13 | debian/debhelper-build-stamp 14 | debian/files 15 | debian/repomaker* 16 | !debian/repomaker.links 17 | !debian/repomaker.install 18 | 19 | node_modules/ 20 | 21 | repomaker/static/repomaker/css/styles.css 22 | repomaker/static/repomaker/css/repo/page.css 23 | repomaker-static/ 24 | 25 | /.vagrant/ 26 | 27 | .idea/* 28 | !.idea/codeStyleSettings.xml 29 | 30 | *.mo 31 | 32 | /build/ 33 | /dist/ 34 | /repomaker.egg-info/ 35 | /docker/data 36 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | image: debian:bullseye 2 | 3 | stages: 4 | - test 5 | - deploy 6 | 7 | variables: 8 | LC_ALL: C.UTF-8 9 | DEBIAN_FRONTEND: noninteractive 10 | 11 | # This template needs to be in text block format since `gitlab-runner 12 | # exec` cannot handle templates in list format. 13 | .apt-template: &apt-template | 14 | set -e 15 | set -x 16 | echo Etc/UTC > /etc/timezone 17 | echo 'quiet "1";' \ 18 | 'APT::Install-Recommends "0";' \ 19 | 'APT::Install-Suggests "0";' \ 20 | 'APT::Acquire::Retries "20";' \ 21 | 'APT::Get::Assume-Yes "true";' \ 22 | 'Dpkg::Use-Pty "0";' \ 23 | > /etc/apt/apt.conf.d/99gitlab 24 | apt-get update 25 | apt-get dist-upgrade 26 | 27 | .setup-template: &setup-template 28 | - *apt-template 29 | - apt-get install 30 | curl 31 | fdroidserver 32 | git 33 | python3-babel 34 | python3-bleach 35 | python3-cryptography 36 | python3-django-allauth 37 | python3-django-compressor 38 | python3-django-modeltranslation 39 | python3-django-sass-processor 40 | python3-dockerpycreds 41 | python3-libcloud 42 | python3-libsass 43 | python3-magic 44 | python3-pip 45 | python3-psycopg2 46 | python3-qrcode 47 | python3-rcssmin 48 | python3-rjsmin 49 | python3-setuptools 50 | python3-websocket 51 | python3-webview 52 | python3-wheel 53 | rsync 54 | 55 | 56 | pycodestyle: 57 | stage: test 58 | script: 59 | - *apt-template 60 | - apt-get install pycodestyle 61 | - ./tests/test-pep8.sh 62 | 63 | pylint: 64 | stage: test 65 | before_script: 66 | - *setup-template 67 | - pip3 install -r requirements.txt 68 | - pip3 install -r requirements-dev.txt 69 | 70 | script: 71 | - ./tests/test-pylint.sh 72 | 73 | units: 74 | stage: test 75 | cache: 76 | paths: 77 | - /usr/local/lib/python*/dist-packages 78 | - node_modules 79 | 80 | before_script: 81 | - *setup-template 82 | - apt-get install npm 83 | - ./setup.sh 84 | - pip3 install -r requirements-dev.txt 85 | - npm install --user 86 | 87 | script: 88 | - ./tests/test-units.sh 89 | 90 | docker: 91 | stage: deploy 92 | image: docker:git 93 | services: 94 | - docker:dind 95 | 96 | before_script: 97 | - echo $CI_BUILD_TOKEN | docker login -u gitlab-ci-token --password-stdin registry.gitlab.com 98 | script: 99 | - docker build -t $CI_REGISTRY_IMAGE:latest . 100 | - docker tag $CI_REGISTRY_IMAGE:latest $CI_REGISTRY_IMAGE:bullseye 101 | - docker tag $CI_REGISTRY_IMAGE:latest $CI_REGISTRY_IMAGE:django-2 102 | - docker push $CI_REGISTRY_IMAGE:latest 103 | - docker push $CI_REGISTRY_IMAGE:bullseye 104 | - docker push $CI_REGISTRY_IMAGE:django-2 105 | 106 | when: on_success 107 | only: 108 | - master 109 | environment: 110 | name: docker 111 | -------------------------------------------------------------------------------- /.weblate: -------------------------------------------------------------------------------- 1 | [weblate] 2 | url = https://hosted.weblate.org/api/ 3 | translation = f-droid/repomaker 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog for F-Droid Repomaker 2 | 3 | This document lists changes for each version of 4 | [F-Droid Repomaker](https://f-droid.org/repomaker/). 5 | Signed [Git tags](https://gitlab.com/fdroid/repomaker/tags) can be found 6 | in the project's official repository. 7 | The Git tags can be verified with 8 | [the information from F-Droid's documentation](https://f-droid.org/docs/Release_Channels_and_Signing_Keys/). 9 | 10 | ### 1.0.0b2 (2020-10-07) 11 | 12 | * Port to _fdroidserver_ v1.1.10 13 | * Update translations 14 | * Fix dependency pinning to use most recent stable versions 15 | 16 | ### 1.0.0b1 (2018-01-29) 17 | 18 | * First beta release of 1.0.0. It is intended to be stable, but not 19 | guaranteed. 20 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM debian:bullseye 2 | MAINTAINER team@f-droid.org 3 | 4 | ENV PYTHONUNBUFFERED 1 5 | ENV DJANGO_SETTINGS_MODULE repomaker.settings_docker 6 | ENV REPOMAKER_SECRET_KEY "913d6#u8@-*#3l)spwzurd#fd77bey-6mfs5fc$a=yhnh!n4p9" 7 | 8 | WORKDIR /repomaker 9 | 10 | ADD . /repomaker 11 | 12 | COPY docker/settings_docker.py ./repomaker/ 13 | COPY docker/apache.conf /etc/apache2/sites-available/repomaker.conf 14 | COPY docker/wait-for ./ 15 | COPY docker/httpd-foreground ./ 16 | 17 | # Debian setup 18 | ENV LANG=C.UTF-8 \ 19 | DEBIAN_FRONTEND=noninteractive 20 | RUN echo Etc/UTC > /etc/timezone \ 21 | && echo 'APT::Install-Recommends "0";' \ 22 | 'APT::Install-Suggests "0";' \ 23 | 'APT::Acquire::Retries "20";' \ 24 | 'APT::Get::Assume-Yes "true";' \ 25 | 'Dpkg::Use-Pty "0";'\ 26 | > /etc/apt/apt.conf.d/99headless \ 27 | && printf "Package: apksigner libapksig-java\nPin: release a=bullseye-backports\nPin-Priority: 500\n" \ 28 | > /etc/apt/preferences.d/bullseye-backports.pref \ 29 | && echo "deb https://deb.debian.org/debian/ bullseye-backports main" \ 30 | > /etc/apt/sources.list.d/bullseye-backports.list 31 | 32 | # a version of the Debian package list is also in .gitlab-ci.yml 33 | RUN apt-get update && apt-get dist-upgrade && apt-get install \ 34 | apache2 \ 35 | apksigner \ 36 | fdroidserver \ 37 | gettext \ 38 | git \ 39 | gnupg \ 40 | libapache2-mod-wsgi-py3 \ 41 | netcat \ 42 | npm \ 43 | openssh-client \ 44 | python3-babel \ 45 | python3-bleach \ 46 | python3-cryptography \ 47 | python3-dev \ 48 | python3-django-allauth \ 49 | python3-django-compressor \ 50 | python3-django-modeltranslation \ 51 | python3-django-sass-processor \ 52 | python3-dockerpycreds \ 53 | python3-libcloud \ 54 | python3-libsass \ 55 | python3-magic \ 56 | python3-pip \ 57 | python3-psycopg2 \ 58 | python3-qrcode \ 59 | python3-rcssmin \ 60 | python3-rjsmin \ 61 | python3-setuptools \ 62 | python3-websocket \ 63 | python3-webview \ 64 | python3-wheel \ 65 | rsync \ 66 | s3cmd && \ 67 | apt-get autoremove --purge && \ 68 | apt-get clean && \ 69 | rm -rf /var/lib/apt/lists/* && \ 70 | cat docker/ssh_config >> /etc/ssh/ssh_config && \ 71 | a2dissite 000-default && \ 72 | a2ensite repomaker && \ 73 | pip3 install -r requirements.txt && \ 74 | npm install && \ 75 | ./pre-release.sh 76 | 77 | 78 | RUN find /repomaker/ -perm -o=w -exec chmod go-w {} \; 79 | RUN chmod 644 /etc/apache2/sites-available/repomaker.conf 80 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include LICENSE 2 | include README.md 3 | graft repomaker 4 | graft repomaker-static 5 | global-exclude __pycache__ 6 | global-exclude *.py[co] 7 | -------------------------------------------------------------------------------- /Vagrantfile: -------------------------------------------------------------------------------- 1 | require 'pathname' 2 | require 'tempfile' 3 | require 'yaml' 4 | 5 | srvpath = Pathname.new(File.dirname(__FILE__)).realpath 6 | configfile = YAML.load_file(File.join(srvpath, "/.gitlab-ci.yml")) 7 | # use the first URL for the remote called 'origin' 8 | remote_url = `git remote -v`.gsub(/.*\sorigin\s+(https:\S*).*/m, '\1').strip 9 | 10 | # set up essential environment variables 11 | env = configfile['variables'] 12 | env['CI_PROJECT_DIR'] = '/builds/fdroid/repomaker' 13 | env_file = Tempfile.new('env') 14 | File.chmod(0644, env_file.path) 15 | env.each do |k,v| 16 | env_file.write("export #{k}='#{v}'\n") 17 | end 18 | env_file.rewind 19 | 20 | sourcepath = '/etc/profile.d/env.sh' 21 | header = "#!/bin/bash -ex\nsource #{sourcepath}\ncd $CI_PROJECT_DIR\n" 22 | 23 | before_script_file = Tempfile.new('before_script') 24 | File.chmod(0755, before_script_file.path) 25 | before_script_file.write(header) 26 | configfile['units']['before_script'].flatten.each do |line| 27 | before_script_file.write(line) 28 | before_script_file.write("\n") 29 | end 30 | before_script_file.rewind 31 | 32 | Vagrant.configure("2") do |config| 33 | config.vm.box = "debian/bullseye64" 34 | config.vm.network "forwarded_port", guest: 8000, host: 8000 35 | config.vm.synced_folder '.', '/vagrant', disabled: true 36 | config.vm.provision "file", source: env_file.path, destination: 'env.sh' 37 | config.vm.provision :shell, inline: <<-SHELL 38 | set -ex 39 | 40 | apt-get update 41 | apt-get -qy install git 42 | 43 | mv ~vagrant/env.sh #{sourcepath} 44 | source #{sourcepath} 45 | mkdir -p $(dirname $CI_PROJECT_DIR) 46 | chown -R vagrant.vagrant $(dirname $CI_PROJECT_DIR) 47 | git clone #{remote_url} $CI_PROJECT_DIR 48 | chmod -R a+rX,u+w $CI_PROJECT_DIR 49 | chown -R vagrant.vagrant $CI_PROJECT_DIR 50 | SHELL 51 | config.vm.provision "file", source: before_script_file.path, destination: 'before_script.sh' 52 | config.vm.provision :shell, inline: '/home/vagrant/before_script.sh' 53 | config.vm.provision :shell, inline: 'chown -R vagrant.vagrant $CI_PROJECT_DIR' 54 | 55 | # remove this block or comment it out to use VirtualBox instead of libvirt 56 | config.vm.provider :libvirt do |libvirt| 57 | libvirt.memory = 512 58 | end 59 | end 60 | -------------------------------------------------------------------------------- /debian/README.md: -------------------------------------------------------------------------------- 1 | # Building Debian Package 2 | 3 | As of writing, not all dependencies are in Debian. 4 | That is why a virtualenv approach is used that is shipping various dependencies without the package. 5 | 6 | Before building the package, call the following command to prepare all non-code files for inclusion: 7 | 8 | ./pre-release.sh 9 | 10 | ## Using dpkg-buildpackage 11 | 12 | In the repository root execute the following command to build a Debian package. 13 | 14 | dpkg-buildpackage -b -us -uc 15 | 16 | ## Using pbuilder (recommended) 17 | 18 | It is advised to build packages in a minimal chroot instead, 19 | so dependencies can be detected and added properly. 20 | 21 | ### Setup 22 | 23 | Install pbuilder: 24 | 25 | sudo apt install pbuilder 26 | 27 | Enable network access during builds to download python dependencies: 28 | 29 | echo USENETWORK=yes >> ~/.pbuilderrc 30 | 31 | Create chroot environment image: 32 | 33 | sudo pbuilder create --distribution stable 34 | 35 | ### Building 36 | 37 | Build the package: 38 | 39 | pdebuild 40 | 41 | If everything goes well, you should find the Debian package in 42 | 43 | /var/cache/pbuilder/result/ 44 | -------------------------------------------------------------------------------- /debian/changelog: -------------------------------------------------------------------------------- 1 | repomaker (0.0.4) unstable; urgency=low 2 | 3 | * Initial public release 4 | 5 | -- Torsten Grote Thu, 21 Sep 2017 12:00:00 +0000 6 | -------------------------------------------------------------------------------- /debian/compat: -------------------------------------------------------------------------------- 1 | 9 2 | -------------------------------------------------------------------------------- /debian/control: -------------------------------------------------------------------------------- 1 | Source: repomaker 2 | Section: utils 3 | Priority: optional 4 | Maintainer: F-Droid Community 5 | Uploaders: Hans-Christoph Steiner 6 | Build-Depends: debhelper (>= 9), 7 | dh-python, 8 | python3-dev, python3-setuptools, python3-django-allauth, 9 | python3-django-background-tasks, python3-django-compressor, 10 | python3-django-hvad, python3-django-js-reverse, 11 | python3-django-sass-processor, python3-magic, 12 | python3-django-tinymce, fdroidserver, python3-bleach, 13 | Standards-Version: 4.1.0 14 | X-Python3-Version: >= 3.4 15 | Homepage: https://f-droid.org/repomaker 16 | 17 | Package: repomaker 18 | Architecture: all 19 | Depends: ${misc:Depends}, 20 | ${python3:Depends}, 21 | apksigner, 22 | fdroidserver, 23 | fonts-materialdesignicons-webfont, 24 | fonts-roboto, 25 | git (>= 2.3.0), 26 | libmagic1, 27 | python3-pyqt5.qtwebengine | python3-pyqt4, 28 | rsync, 29 | python3-pywebview, 30 | Description: Create F-Droid repositories with ease 31 | F-Droid.org is just a repo out of hundreds of repos created by 32 | individuals all around the globe. With the tools of F-Droid, everyone 33 | can create their own repo. So whether you are a musician who wants to 34 | publish their music or a developer who wants to serve nightly builds 35 | of their app, you are free to create your own repo and share it with 36 | other people independently of F-Droid.org. 37 | . 38 | In the past, creating a repo has been difficult because you had to 39 | have knowledge on the command line, needed to edit text files to edit 40 | your packages’ store details and had to paste screenshots in a 41 | special system of directories to have them served well inside the 42 | F-Droid app. 43 | . 44 | This all got easier now: with Repomaker, you are able to create your 45 | own repo and do not need to have any special knowledge to do so. 46 | -------------------------------------------------------------------------------- /debian/data/org.fdroid.Repomaker.appdata.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | org.fdroid.Repomaker 4 | CC-BY-SA-4.0 5 | AGPL-3.0 6 | Repomaker 7 | Create F-Droid repos with ease 8 | 9 | 10 |

11 | F-Droid is an installable catalogue of FOSS (Free and Open Source 12 | Software) applications for the Android platform. The client makes it 13 | easy to browse, install, and keep track of updates on your device. 14 |

15 |

16 | F-Droid.org is just a repo out of hundreds of repos created by 17 | individuals all around the globe. With the tools of F-Droid, 18 | everyone can create their own repo. So whether you are a musician 19 | who wants to publish their music or a developer who wants to serve 20 | nightly builds of their app, you are free to create your own repo 21 | and share it with other people independently of F-Droid.org. 22 |

23 |

24 | In the past, creating a repo has been difficult because you had to 25 | have knowledge on the command line, needed to edit text files to 26 | edit your packages’ store details and had to paste screenshots in a 27 | special system of directories to have them served well inside the 28 | F-Droid app. 29 |

30 |

31 | This all got easier now: with Repomaker, you are able to create your 32 | own repo and do not need to have any special knowledge to do so. 33 |

34 |
35 | 36 | 37 | Development 38 | Utility 39 | 40 | 41 | org.fdroid.Repomaker.desktop 42 | 43 | 44 | 45 | https://f-droid.org/assets/repomaker-screenshots/repo-details.png 46 | Your repo can serve all types of media 47 | 48 | 49 | https://f-droid.org/assets/repomaker-screenshots/package-details.png 50 | Editing your package store details never was easier 51 | 52 | 53 | https://f-droid.org/assets/repomaker-screenshots/create-repo.png 54 | Creating a repo just takes you two clicks 55 | 56 | 57 | https://f-droid.org/assets/repomaker-screenshots/add-storage.png 58 | You have many possibilities where to upload your repo 59 | 60 | 61 | 62 | 63 | https://f-droid.org 64 | https://gitlab.com/fdroid/repomaker/issues 65 | https://f-droid.org/docs/FAQ_-_General/ 66 | https://f-droid.org/about/ 67 | https://f-droid.org/docs/Translation_and_Localization/ 68 | 69 | team@f-droid.org 70 | F-Droid Team 71 | 72 | 73 | 74 | repomaker 75 | repomaker-server 76 | repomaker-tasks 77 | 78 | 79 | 80 | 81 | 82 |

Port to _fdroidserver_ v1.1.10

83 |

Update translations

84 |

Fix dependency pinning to use most recent stable versions

85 |
86 |
87 | 88 | 89 |

First beta release of 1.0.0. It is intended to be stable, but not guaranteed.

90 |
91 |
92 | 93 | 94 |

Initial Release

95 |
96 |
97 |
98 | 99 | 100 |
101 | -------------------------------------------------------------------------------- /debian/data/org.fdroid.Repomaker.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Name=Repomaker 3 | Comment=Create F-Droid repos with ease 4 | Keywords=F-Droid;FDroid;Repository;Repositories;Repos; 5 | Exec=repomaker 6 | Terminal=false 7 | Type=Application 8 | Icon=org.fdroid.Repomaker 9 | Categories=Development;Utility; 10 | StartupNotify=true 11 | -------------------------------------------------------------------------------- /debian/repomaker.install: -------------------------------------------------------------------------------- 1 | debian/data/org.fdroid.Repomaker.appdata.xml usr/share/metainfo/ 2 | debian/data/org.fdroid.Repomaker.desktop usr/share/applications/ 3 | debian/data/org.fdroid.Repomaker.svg usr/share/icons/hicolor/scalable/apps/ 4 | -------------------------------------------------------------------------------- /debian/requirements.txt: -------------------------------------------------------------------------------- 1 | # These requirements are not yet in Debian 2 | django-background-tasks==1.1.13 3 | django-tinymce >=2.6.0, <3 4 | -------------------------------------------------------------------------------- /debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | # -*- mode: makefile; coding: utf-8 -*- 3 | 4 | %: 5 | dh $@ --with python3 --buildsystem=pybuild 6 | 7 | override_dh_auto_test: 8 | -------------------------------------------------------------------------------- /debian/source/format: -------------------------------------------------------------------------------- 1 | 3.0 (native) 2 | -------------------------------------------------------------------------------- /doc/README.md: -------------------------------------------------------------------------------- 1 | # Hosted 2 | 3 | Repomaker can be run on a server in hosted mode. 4 | Set `SINGLE_USER_MODE` to `False` in the settings. 5 | 6 | ## Enabling Remote Storage 7 | 8 | Publishing to remote storages uses SSH. 9 | For this to work, the user running the background tasks 10 | needs to have the remote server's key in its `.ssh/known_hosts` file. 11 | 12 | -------------------------------------------------------------------------------- /doc/examples/repomaker.conf: -------------------------------------------------------------------------------- 1 | 2 | ServerName repomaker.example.org 3 | ServerAdmin webmaster@example.org 4 | 5 | Alias /static /var/local/repomaker/static 6 | 7 | Require all granted 8 | 9 | 10 | Alias /repos /var/local/repomaker/repos 11 | 12 | Require all granted 13 | 14 | 15 | WSGIScriptAlias / /var/local/repomaker/repomaker/wsgi.py 16 | WSGIDaemonProcess repomaker python-path=/var/local/repomaker:/usr/local/lib/python3.5/dist-packages 17 | WSGIProcessGroup repomaker 18 | WSGIPassAuthorization On 19 | 20 | 21 | 22 | Require all granted 23 | 24 | 25 | 26 | ErrorLog ${APACHE_LOG_DIR}/error.log 27 | CustomLog ${APACHE_LOG_DIR}/access.log combined 28 | 29 | 30 | # vim: syntax=apache ts=4 sw=4 sts=4 sr noet 31 | -------------------------------------------------------------------------------- /doc/examples/repomaker.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Repomaker Background Tasks 3 | After=network.target 4 | Wants=mysqld.service 5 | 6 | [Service] 7 | Type=simple 8 | User=www-data 9 | WorkingDirectory=/var/local/repomaker 10 | Environment=PYTHONPATH="/usr/local/lib/python3.5/dist-packages/" 11 | ExecStart=/usr/bin/python3 manage.py process_tasks 12 | Restart=on-failure 13 | 14 | [Install] 15 | WantedBy=multi-user.target 16 | 17 | -------------------------------------------------------------------------------- /docker/.env: -------------------------------------------------------------------------------- 1 | # Please enter your hostname here 2 | REPOMAKER_HOSTNAME=abc-repomaker-test.org 3 | 4 | # Here all repomaker related files (including the database) will be stored 5 | REPOMAKER_PATH=/var/lib/repomaker 6 | 7 | # The local port repomaker will listen on the host machine 8 | REPOMAKER_PORT=80 9 | 10 | # Warning: Changing this later will change all repo locations in default storage 11 | REPOMAKER_SECRET_KEY=913d6#u8@-*#3l)spwzurd#fd77bey-6mfs5fc$a=yhnh!n4p9 12 | 13 | # You usually do not need to change this 14 | DJANGO_SETTINGS_MODULE=repomaker.settings_docker 15 | 16 | # Uncomment to allow password-less connections to the database 17 | # https://djangoforprofessionals.com/postgresql/#postgresql 18 | #POSTGRES_HOST_AUTH_METHOD=trust 19 | -------------------------------------------------------------------------------- /docker/README.md: -------------------------------------------------------------------------------- 1 | # Docker Compose Deployment 2 | 3 | First, copy the files `docker-compose.yml` and `.env` from this directory: 4 | 5 | ``` bash 6 | cp docker/docker-compose.yml docker/.env . 7 | ``` 8 | 9 | Decide on which domain name your repomaker instance will be running. 10 | Then edit your `.env` file: 11 | 12 | - Set up the database authentication, or disable it using [`POSTGRES_HOST_AUTH_METHOD=trust`](https://djangoforprofessionals.com/postgresql/#postgresql). 13 | - `REPOMAKER_HOSTNAME` Set your hostname, i.e `localhost`, `repomaker.domain.tld`, ... 14 | - `REPOMAKER_PORT` Leave it as default (80), or set another port if you get a port conflict. 15 | - `REPOMAKER_SECRET_KEY` Generate a new secret key for your deployment, for example like so: 16 | ``` bash 17 | echo "REPOMAKER_SECRET_KEY=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 64 | head -n 1)" 18 | ``` 19 | 20 | 21 | 22 | Then start the docker containers: 23 | 24 | ``` 25 | docker-compose up 26 | ``` 27 | 28 | If everything worked as it should, 29 | there should now be a repomaker instance running on your domain name. 30 | -------------------------------------------------------------------------------- /docker/apache.conf: -------------------------------------------------------------------------------- 1 | 2 | Alias /static /repomaker/repomaker-static 3 | 4 | 5 | Require all granted 6 | 7 | 8 | Alias /repos /repomaker/data/repos 9 | 10 | 11 | Require all granted 12 | 13 | 14 | WSGIScriptAlias / /repomaker/repomaker/wsgi.py 15 | WSGIDaemonProcess repomaker python-path=/repomaker:/usr/local/lib/python3.5/dist-packages 16 | WSGIProcessGroup repomaker 17 | WSGIPassAuthorization On 18 | 19 | 20 | 21 | Require all granted 22 | 23 | 24 | 25 | 26 | # Available loglevels: trace8, ..., trace1, debug, info, notice, warn, 27 | # error, crit, alert, emerg. 28 | # It is also possible to configure the loglevel for particular 29 | # modules, e.g. 30 | #LogLevel info ssl:warn 31 | LogLevel info 32 | 33 | ErrorLog /repomaker/data/logs/error.log 34 | CustomLog /repomaker/data/logs/access.log combined 35 | 36 | 37 | # vim: syntax=apache ts=4 sw=4 sts=4 sr noet 38 | -------------------------------------------------------------------------------- /docker/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | 3 | services: 4 | db: 5 | image: postgres 6 | env_file: 7 | - .env 8 | volumes: 9 | - ${REPOMAKER_PATH}/pgdata:/var/lib/postgresql/data 10 | restart: unless-stopped 11 | 12 | web: 13 | image: registry.gitlab.com/fdroid/repomaker:latest 14 | hostname: ${REPOMAKER_HOSTNAME} 15 | domainname: ${REPOMAKER_HOSTNAME} 16 | env_file: 17 | - .env 18 | command: bash -c './wait-for db:5432 -- python3 manage.py migrate && ./httpd-foreground' 19 | volumes: 20 | - ${REPOMAKER_PATH}/data:/repomaker/data 21 | ports: 22 | - "${REPOMAKER_PORT}:80" 23 | depends_on: 24 | - db 25 | restart: unless-stopped 26 | 27 | tasks: 28 | image: registry.gitlab.com/fdroid/repomaker:latest 29 | env_file: 30 | - .env 31 | command: bash -c './wait-for web:80 -- su www-data -p -s /bin/bash -c "cd /repomaker && python3 manage.py process_tasks"' 32 | volumes: 33 | - ${REPOMAKER_PATH}/data:/repomaker/data 34 | depends_on: 35 | - db 36 | - web 37 | restart: unless-stopped 38 | 39 | # vim: set tabstop=2:softtabstop=2:shiftwidth=2 40 | -------------------------------------------------------------------------------- /docker/httpd-foreground: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | # Apache gets grumpy about PID files pre-existing 5 | rm -f /var/run/apache2/apache2.pid 6 | 7 | # Make sure log directory exists and has proper permissions 8 | chown www-data /repomaker/data 9 | mkdir -p /repomaker/data/logs 10 | chown www-data /repomaker/data/logs 11 | 12 | exec apache2ctl -DFOREGROUND 13 | -------------------------------------------------------------------------------- /docker/settings_docker.py: -------------------------------------------------------------------------------- 1 | import os 2 | from pkg_resources import Requirement, resource_filename 3 | from repomaker.settings import * 4 | 5 | 6 | DATABASES = { 7 | 'default': { 8 | 'ENGINE': 'django.db.backends.postgresql', 9 | 'NAME': 'postgres', 10 | 'USER': 'postgres', 11 | 'HOST': 'db', 12 | 'PORT': 5432, 13 | } 14 | } 15 | 16 | SINGLE_USER_MODE = False 17 | 18 | ALLOWED_HOSTS = [os.getenv('REPOMAKER_HOSTNAME')] 19 | SECRET_KEY = os.getenv('REPOMAKER_SECRET_KEY') 20 | DEBUG = False 21 | 22 | DEFAULT_REPO_STORAGE = [ 23 | (os.path.join(DATA_DIR, 'repos'), 'https://%s/repos/' % os.getenv('REPOMAKER_HOSTNAME')), 24 | ] 25 | 26 | LOGIN_REDIRECT_URL = "/" 27 | # http://django-allauth.readthedocs.io/en/latest/installation.html 28 | INSTALLED_APPS += [ 29 | 'allauth', 30 | 'allauth.socialaccount', 31 | # 'allauth.socialaccount.providers.amazon', 32 | # 'allauth.socialaccount.providers.baidu', 33 | # 'allauth.socialaccount.providers.bitbucket_oauth2', 34 | # 'allauth.socialaccount.providers.dropbox_oauth2', 35 | # 'allauth.socialaccount.providers.facebook', 36 | # 'allauth.socialaccount.providers.github', 37 | # 'allauth.socialaccount.providers.gitlab', 38 | # 'allauth.socialaccount.providers.google', 39 | # 'allauth.socialaccount.providers.linkedin_oauth2', 40 | 'allauth.socialaccount.providers.openid', 41 | # 'allauth.socialaccount.providers.reddit', 42 | # 'allauth.socialaccount.providers.slack', 43 | # 'allauth.socialaccount.providers.stackexchange', 44 | # 'allauth.socialaccount.providers.twitter', 45 | # 'allauth.socialaccount.providers.weibo', 46 | ] 47 | AUTHENTICATION_BACKENDS = ( 48 | 'django.contrib.auth.backends.ModelBackend', 49 | 'allauth.account.auth_backends.AuthenticationBackend', 50 | ) 51 | ACCOUNT_FORMS = { 52 | 'login': 'repomaker.views.RmLoginForm', 53 | 'reset_password': 'repomaker.views.RmResetPasswordForm', 54 | 'signup': 'repomaker.views.RmSignupForm', 55 | } 56 | ACCOUNT_EMAIL_VERIFICATION = "none" 57 | 58 | if DEBUG: 59 | SASS_PROCESSOR_ENABLED = False 60 | 61 | -------------------------------------------------------------------------------- /docker/ssh_config: -------------------------------------------------------------------------------- 1 | 2 | # Automatically add new host keys to the user known hosts files. 3 | # This is required for pushing to remote servers in the background. 4 | 5 | Host * 6 | StrictHostKeyChecking no 7 | 8 | -------------------------------------------------------------------------------- /docker/wait-for: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | TIMEOUT=15 4 | QUIET=0 5 | 6 | echoerr() { 7 | if [ "$QUIET" -ne 1 ]; then printf "%s\n" "$*" 1>&2; fi 8 | } 9 | 10 | usage() { 11 | exitcode="$1" 12 | cat << USAGE >&2 13 | Usage: 14 | $cmdname host:port [-t timeout] [-- command args] 15 | -q | --quiet Do not output any status messages 16 | -t TIMEOUT | --timeout=timeout Timeout in seconds, zero for no timeout 17 | -- COMMAND ARGS Execute command with args after the test finishes 18 | USAGE 19 | exit "$exitcode" 20 | } 21 | 22 | wait_for() { 23 | for i in `seq $TIMEOUT` ; do 24 | nc -z "$HOST" "$PORT" > /dev/null 2>&1 25 | 26 | result=$? 27 | if [ $result -eq 0 ] ; then 28 | if [ $# -gt 0 ] ; then 29 | exec "$@" 30 | fi 31 | exit 0 32 | fi 33 | sleep 1 34 | done 35 | echo "Operation timed out" >&2 36 | exit 1 37 | } 38 | 39 | while [ $# -gt 0 ] 40 | do 41 | case "$1" in 42 | *:* ) 43 | HOST=$(printf "%s\n" "$1"| cut -d : -f 1) 44 | PORT=$(printf "%s\n" "$1"| cut -d : -f 2) 45 | shift 1 46 | ;; 47 | -q | --quiet) 48 | QUIET=1 49 | shift 1 50 | ;; 51 | -t) 52 | TIMEOUT="$2" 53 | if [ "$TIMEOUT" = "" ]; then break; fi 54 | shift 2 55 | ;; 56 | --timeout=*) 57 | TIMEOUT="${1#*=}" 58 | shift 1 59 | ;; 60 | --) 61 | shift 62 | break 63 | ;; 64 | --help) 65 | usage 0 66 | ;; 67 | *) 68 | echoerr "Unknown argument: $1" 69 | usage 1 70 | ;; 71 | esac 72 | done 73 | 74 | if [ "$HOST" = "" -o "$PORT" = "" ]; then 75 | echoerr "Error: you need to provide a host and port to test." 76 | usage 2 77 | fi 78 | 79 | wait_for "$@" 80 | -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "repomaker.settings") 7 | try: 8 | from django.core.management import execute_from_command_line 9 | except ImportError: 10 | # The above import may fail for some other reason. Ensure that the 11 | # issue is really that Django is missing to avoid masking other 12 | # exceptions on Python 2. 13 | try: 14 | import django 15 | except ImportError: 16 | raise ImportError( 17 | "Couldn't import Django. Are you sure it's installed and " 18 | "available on your PYTHONPATH environment variable? Did you " 19 | "forget to activate a virtual environment?" 20 | ) 21 | raise 22 | execute_from_command_line(sys.argv) 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "repomaker", 3 | "version": "0.0.1", 4 | "license": "AGPL-3.0", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://gitlab.com/fdroid/repomaker.git" 8 | }, 9 | "dependencies": { 10 | "dialog-polyfill": "~0.4.8", 11 | "material-design-icons-iconfont": "4.0.2", 12 | "material-design-lite": "^1.3.0", 13 | "roboto-fontface": ">=0.8.0" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /pre-release.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | shopt -s extglob 4 | 5 | NODE=node_modules 6 | 7 | set -x 8 | 9 | # compile translations 10 | python3 manage.py compilemessages 11 | 12 | # update npm packages 13 | npm install 14 | 15 | # dialog-polyfill remove unused files 16 | rm -rf ${NODE}/dialog-polyfill/!(dialog-polyfill.*) 17 | 18 | # material-design-icons-iconfont remove unused files 19 | rm -rf ${NODE}/material-design-icons-iconfont/!(src|dist) 20 | rm -rf ${NODE}/material-design-icons-iconfont/dist/!(fonts) 21 | rm -rf ${NODE}/material-design-icons-iconfont/src/!(material-design-icons.scss) 22 | rm -rf ${NODE}/material-design-icons-iconfont/.idea 23 | 24 | # material-design-lite remove unused files 25 | rm -rf ${NODE}/material-design-lite/!(dist|src|material*.js) 26 | rm -rf ${NODE}/material-design-lite/dist/!(images) 27 | rm -rf ${NODE}/material-design-lite/.[tj]* 28 | 29 | # roboto-fontface remove unused files 30 | rm -rf ${NODE}/roboto-fontface/!(css|fonts) 31 | rm -rf ${NODE}/roboto-fontface/.npmignore 32 | 33 | # tinymce remove unused files 34 | rm -rf ${NODE}/tinymce/!(plugins|skins|themes|tinymce*.js) 35 | rm -rf ${NODE}/tinymce/themes/!(modern) 36 | rm -rf ${NODE}/tinymce/plugins/!(autolink|link|lists) 37 | 38 | # compile stylesheets 39 | python3 manage.py compilescss 40 | 41 | # rename folders so they can be ignored 42 | mv ${NODE}/material-design-lite/src ${NODE}/material-design-lite/src-ignore 43 | mv ${NODE}/roboto-fontface/css ${NODE}/roboto-fontface/css-ignore 44 | 45 | # copy static files into STATIC_DIR 46 | python3 manage.py collectstatic --no-input \ 47 | -i *.scss -i *.less \ 48 | -i tiny_mce \ 49 | -i src-ignore \ 50 | -i css-ignore \ 51 | --clear 52 | 53 | # rename folders back 54 | mv ${NODE}/material-design-lite/src-ignore ${NODE}/material-design-lite/src 55 | mv ${NODE}/roboto-fontface/css-ignore ${NODE}/roboto-fontface/css 56 | 57 | # compress stylesheets and javascripts 58 | python3 manage.py compress --force 59 | 60 | echo "Ready to ship release!" 61 | -------------------------------------------------------------------------------- /purge.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # remove local files 4 | rm -rf data/private_repo/user_* 5 | rm -rf data/media/user_* 6 | rm -rf data/media/remote_repo_* 7 | rm -rf data/media/packages 8 | 9 | # remove database and move custom migrations out of the way 10 | rm repomaker/migrations/0* 11 | mv repomaker/migrations/default_user.py repomaker/migrations/default_user.py.bak 12 | mv repomaker/migrations/default_categories.py repomaker/migrations/default_categories.py.bak 13 | mv repomaker/migrations/default_remote_repositories.py repomaker/migrations/default_remote_repositories.py.bak 14 | rm data/db.sqlite3 15 | 16 | # initialize database and re-add custom migrations 17 | python3 manage.py makemigrations repomaker 18 | mv repomaker/migrations/default_user.py.bak repomaker/migrations/default_user.py 19 | mv repomaker/migrations/default_categories.py.bak repomaker/migrations/default_categories.py 20 | mv repomaker/migrations/default_remote_repositories.py.bak repomaker/migrations/default_remote_repositories.py 21 | python3 manage.py migrate 22 | -------------------------------------------------------------------------------- /repomaker/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | from django.core.checks import Error, register 5 | from fdroidserver import common 6 | from fdroidserver.exception import FDroidException 7 | 8 | VERSION = '1.0.0b2' 9 | 10 | # The name of the default user. Please DO NOT CHANGE 11 | DEFAULT_USER_NAME = 'user' 12 | 13 | 14 | def runserver(): 15 | execute([sys.argv[0], 'migrate']) # TODO move into package hook? 16 | if len(sys.argv) <= 1 or sys.argv[1] != 'runserver': 17 | sys.argv = sys.argv[:1] + ['runserver'] + sys.argv[1:] 18 | sys.argv.append('--noreload') 19 | execute(sys.argv) 20 | 21 | 22 | def process_tasks(): 23 | if len(sys.argv) <= 1 or sys.argv[1] != 'process_tasks': 24 | sys.argv = sys.argv[:1] + ['process_tasks'] + sys.argv[1:] 25 | execute(sys.argv) 26 | 27 | 28 | def execute(params): 29 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "repomaker.settings_desktop") 30 | try: 31 | from django.core.management import execute_from_command_line 32 | except ImportError: 33 | # The above import may fail for some other reason. Ensure that the 34 | # issue is really that Django is missing to avoid masking other 35 | # exceptions on Python 2. 36 | try: 37 | import django # pylint: disable=unused-import 38 | except ImportError: 39 | raise ImportError( 40 | "Couldn't import Django. Are you sure it's installed and " 41 | "available on your PYTHONPATH environment variable? Did you " 42 | "forget to activate a virtual environment?" 43 | ) 44 | raise 45 | 46 | # create DATA_DIR if it doesn't exist 47 | from django.conf import settings 48 | if not os.path.isdir(settings.DATA_DIR): 49 | os.makedirs(settings.DATA_DIR) 50 | 51 | if len(sys.argv) > 1 and sys.argv[1] == 'process_tasks': 52 | non_atomic_background_tasks() 53 | 54 | # execute pending command 55 | execute_from_command_line(params) 56 | 57 | 58 | def non_atomic_background_tasks(): 59 | from django import setup 60 | setup() 61 | import background_task.tasks 62 | from repomaker.tasks import DesktopRunner 63 | background_task.tasks.tasks._runner = DesktopRunner() # pylint: disable=protected-access 64 | 65 | 66 | @register() 67 | def requirements_check(app_configs, **kwargs): # pylint: disable=unused-argument 68 | errors = [] 69 | config = {} 70 | common.fill_config_defaults(config) 71 | common.config = config 72 | if 'keytool' not in config: 73 | errors.append( 74 | Error( 75 | 'Could not find `keytool` program.', 76 | hint='This program usually comes with Java. Try to install JRE. ' 77 | 'On Debian-based system you can try to run ' 78 | '`apt install default-jre-headless`.', 79 | ) 80 | ) 81 | if 'jarsigner' not in config and not common.set_command_in_config('apksigner'): 82 | errors.append( 83 | Error( 84 | 'Could not find `jarsigner` or `apksigner`. At least one of them is required.', 85 | hint='Please install the missing tool. On Debian-based systems you can try to run ' 86 | '`apt install apksigner`.', 87 | ) 88 | ) 89 | # rsync 90 | if common.find_command('rsync') is None: 91 | errors.append( 92 | Error( 93 | 'Could not find `rsync` program.', 94 | hint='Please install it before continuing. On Debian-based systems you can run ' 95 | '`apt install rsync`.', 96 | ) 97 | ) 98 | # git 99 | if common.find_command('git') is None: 100 | errors.append( 101 | Error( 102 | 'Could not find `git` program.', 103 | hint='Please install it before continuing. On Debian-based systems you can run ' 104 | '`apt install git`.', 105 | ) 106 | ) 107 | return errors 108 | -------------------------------------------------------------------------------- /repomaker/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from modeltranslation.admin import TranslationAdmin 3 | 4 | from .models import Repository, RemoteRepository, App, RemoteApp, Apk, ApkPointer, \ 5 | RemoteApkPointer, Category, Screenshot, RemoteScreenshot 6 | from .models.storage import StorageManager 7 | 8 | admin.site.register(Repository) 9 | admin.site.register(RemoteRepository) 10 | admin.site.register(App, TranslationAdmin) # hides untranslated apps which should not exist 11 | admin.site.register(RemoteApp, TranslationAdmin) # hides untranslated apps which should not exist 12 | admin.site.register(Apk) 13 | admin.site.register(ApkPointer) 14 | admin.site.register(RemoteApkPointer) 15 | admin.site.register(Category) 16 | admin.site.register(Screenshot) 17 | admin.site.register(RemoteScreenshot) 18 | 19 | for storage in StorageManager.storage_models: 20 | admin.site.register(storage) 21 | -------------------------------------------------------------------------------- /repomaker/apps.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | 3 | from django.apps import AppConfig 4 | 5 | 6 | class RepoMakerConfig(AppConfig): 7 | name = 'repomaker' 8 | -------------------------------------------------------------------------------- /repomaker/gui.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import subprocess 3 | import time 4 | from threading import Thread 5 | 6 | import repomaker 7 | import requests 8 | import webview 9 | 10 | URL = 'http://127.0.0.1:8000/' 11 | WAIT_BEFORE_TASKS = 30 # number of seconds to wait before starting background tasks 12 | logger = logging.getLogger(__name__) 13 | 14 | # multi-thread access 15 | task_process = None 16 | terminate = False 17 | 18 | 19 | def main(): 20 | # start stuff in thread 21 | t_window = Thread(target=start) 22 | t_window.start() 23 | 24 | create_window() 25 | 26 | 27 | def create_window(): 28 | global terminate # pylint: disable=global-statement 29 | try: 30 | webview.config["USE_QT"] = True # use Qt instead of Gtk for webview 31 | webview.create_window("Repomaker", confirm_close=True) 32 | terminate = True 33 | finally: 34 | # halt background tasks 35 | if task_process is not None: 36 | task_process.terminate() 37 | 38 | 39 | def start(): 40 | global task_process # pylint: disable=global-statement 41 | # show loading screen 42 | webview.load_html(get_loading_screen()) 43 | 44 | double_instance = server_started() 45 | if not double_instance: 46 | # start web server 47 | logger.debug("Starting server") 48 | t = Thread(target=repomaker.runserver) 49 | t.daemon = True 50 | t.start() 51 | 52 | # wait for server to start 53 | while not server_started(): 54 | if not t.is_alive(): 55 | logging.error('Repomaker webserver could not be started.') 56 | return 57 | time.sleep(0.1) 58 | 59 | # load repomaker into webview 60 | webview.load_url(URL) 61 | 62 | if not double_instance: 63 | # wait and then start the background tasks 64 | for i in range(0, WAIT_BEFORE_TASKS): 65 | if terminate: 66 | return 67 | time.sleep(1) 68 | if not terminate: 69 | # this needs to run as its own process 70 | task_process = subprocess.Popen(['repomaker-tasks']) 71 | 72 | 73 | def get_loading_screen(): 74 | return """ 75 | 76 |
77 |

Loading...

78 |
79 | 80 | """ 81 | 82 | 83 | def server_started(): 84 | try: 85 | return requests.head(URL, timeout=60).status_code == requests.codes.OK 86 | except Exception: 87 | return False 88 | -------------------------------------------------------------------------------- /repomaker/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/f-droid/repomaker/2d9e6a17eb7cca6fe3f7405a98109be5320f7f00/repomaker/migrations/__init__.py -------------------------------------------------------------------------------- /repomaker/migrations/default_categories.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | 3 | from django.db import migrations 4 | 5 | DEFAULT_CATEGORIES = [ 6 | 'Connectivity', 'Development', 'Games', 'Graphics', 'Internet', 'Money', 'Multimedia', 7 | 'Navigation', 'Phone & SMS', 'Reading', 'Science & Education', 'Security', 'Sports & Health', 8 | 'System', 'Theming', 'Time', 'Writing' 9 | ] 10 | 11 | 12 | def forwards_func(apps, schema_editor): 13 | # noinspection PyPep8Naming 14 | Category = apps.get_model("repomaker", "Category") 15 | db_alias = schema_editor.connection.alias 16 | Category.objects.using(db_alias).bulk_create( 17 | [Category(user=None, name=name) for name in DEFAULT_CATEGORIES]) 18 | 19 | 20 | def reverse_func(apps, schema_editor): 21 | # noinspection PyPep8Naming 22 | Category = apps.get_model("repomaker", "Category") 23 | db_alias = schema_editor.connection.alias 24 | for name in DEFAULT_CATEGORIES: 25 | Category.objects.using(db_alias).filter(user=None, name=name).delete() 26 | 27 | 28 | class Migration(migrations.Migration): 29 | 30 | dependencies = [ 31 | ('repomaker', 'default_user'), 32 | ] 33 | 34 | operations = [ 35 | migrations.RunPython(forwards_func, reverse_func), 36 | ] 37 | -------------------------------------------------------------------------------- /repomaker/migrations/default_user.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | 3 | from django.conf import settings 4 | from django.db import migrations 5 | 6 | from repomaker import DEFAULT_USER_NAME 7 | 8 | 9 | def forwards_func(apps, schema_editor): 10 | if not settings.SINGLE_USER_MODE: 11 | return 12 | # noinspection PyPep8Naming 13 | User = apps.get_model("auth", "User") 14 | db_alias = schema_editor.connection.alias 15 | User.objects.using(db_alias).create(username=DEFAULT_USER_NAME, is_staff=True, 16 | is_superuser=True) 17 | 18 | 19 | def reverse_func(apps, schema_editor): 20 | if not settings.SINGLE_USER_MODE: 21 | return 22 | # noinspection PyPep8Naming 23 | User = apps.get_model("auth", "User") 24 | db_alias = schema_editor.connection.alias 25 | User.objects.using(db_alias).filter(username=DEFAULT_USER_NAME).delete() 26 | 27 | 28 | class Migration(migrations.Migration): 29 | 30 | dependencies = [ 31 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 32 | ('repomaker', '0001_initial'), 33 | ] 34 | 35 | operations = [ 36 | migrations.RunPython(forwards_func, reverse_func), 37 | ] 38 | -------------------------------------------------------------------------------- /repomaker/models/__init__.py: -------------------------------------------------------------------------------- 1 | from .apk import Apk 2 | from .apkpointer import ApkPointer, RemoteApkPointer 3 | from .app import App 4 | from .category import Category 5 | from .remoteapp import RemoteApp 6 | from .remoterepository import RemoteRepository 7 | from .repository import Repository 8 | from .screenshot import Screenshot, RemoteScreenshot 9 | from .storage import S3Storage, SshStorage, GitStorage 10 | -------------------------------------------------------------------------------- /repomaker/models/category.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.models import User 2 | from django.db import models 3 | 4 | 5 | class Category(models.Model): 6 | user = models.ForeignKey(User, on_delete=models.CASCADE, blank=True, null=True) 7 | name = models.CharField(max_length=64) 8 | 9 | def __str__(self): 10 | return str(self.name) 11 | 12 | class Meta: 13 | verbose_name_plural = "Categories" 14 | unique_together = (("user", "name"),) 15 | -------------------------------------------------------------------------------- /repomaker/models/screenshot.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import os 3 | from io import BytesIO 4 | 5 | import requests 6 | from django.db import models 7 | from django.db.models.signals import post_delete 8 | from django.dispatch import receiver 9 | from django.utils.translation import ugettext_lazy as _ 10 | 11 | from repomaker import tasks 12 | from repomaker.storage import get_screenshot_file_path, RepoStorage 13 | from repomaker.utils import to_universal_language_code 14 | from .app import App 15 | from .remoteapp import RemoteApp 16 | 17 | PHONE = 'phoneScreenshots' 18 | SEVEN_INCH = 'sevenInchScreenshots' 19 | TEN_INCH = 'tenInchScreenshots' 20 | TV = 'tvScreenshots' 21 | WEAR = 'wearScreenshots' 22 | TYPE_CHOICES = ( 23 | (PHONE, _('Phone')), 24 | (SEVEN_INCH, _("7'' Tablet")), 25 | (TEN_INCH, _("10'' Tablet")), 26 | (TV, _('TV')), 27 | (WEAR, _('Wearable')), 28 | ) 29 | 30 | 31 | class AbstractScreenshot(models.Model): 32 | language_code = models.CharField(max_length=32, default='en') 33 | type = models.CharField(max_length=32, choices=TYPE_CHOICES, default=PHONE) 34 | 35 | def __str__(self): 36 | return self.app.__str__() + " - " + self.language_code + " " + self.type 37 | 38 | def get_url(self): 39 | raise NotImplementedError() 40 | 41 | class Meta: 42 | abstract = True 43 | 44 | 45 | class Screenshot(AbstractScreenshot): 46 | app = models.ForeignKey(App, on_delete=models.CASCADE) 47 | file = models.ImageField(upload_to=get_screenshot_file_path, storage=RepoStorage(), 48 | max_length=1024) 49 | # TODO add a thumbnail to be automatically generated from file 50 | 51 | def __str__(self): 52 | return super(Screenshot, self).__str__() + " " + self.file.name 53 | 54 | def get_relative_path(self): 55 | language_code = to_universal_language_code(self.language_code) 56 | return os.path.join(self.app.package_id, language_code, self.type) 57 | 58 | def get_url(self): 59 | return self.file.url 60 | 61 | 62 | class RemoteScreenshot(AbstractScreenshot): 63 | app = models.ForeignKey(RemoteApp, on_delete=models.CASCADE) 64 | url = models.URLField(max_length=2048) 65 | 66 | def __str__(self): 67 | return super(RemoteScreenshot, self).__str__() + " " + os.path.basename(self.url) 68 | 69 | def get_url(self): 70 | return self.url 71 | 72 | @staticmethod 73 | def add(language_code, s_type, app, base_url, files): 74 | """ 75 | Creates and saves one or more new RemoteScreenshots if the given s_type is supported. 76 | """ 77 | if not is_supported_type(s_type): 78 | return 79 | for file in files: 80 | url = base_url + file 81 | if not RemoteScreenshot.objects.filter(language_code=language_code, type=s_type, 82 | app=app, url=url).exists(): 83 | screenshot = RemoteScreenshot(language_code=language_code, type=s_type, app=app, 84 | url=url) 85 | screenshot.save() 86 | 87 | def download_async(self, app): 88 | """ 89 | Downloads this RemoteScreenshot asynchronously and creates a local Screenshot if successful. 90 | """ 91 | tasks.download_remote_screenshot(self.pk, app.pk) 92 | 93 | def download(self, app_id): 94 | """ 95 | Does a blocking download of this RemoteScreenshot 96 | and creates a local Screenshot if successful. 97 | """ 98 | screenshot = Screenshot(language_code=self.language_code, type=self.type, app_id=app_id) 99 | r = requests.get(self.url, timeout=60) 100 | if r.status_code == requests.codes.ok: 101 | screenshot.file.save(os.path.basename(self.url), BytesIO(r.content), save=True) 102 | 103 | 104 | def is_supported_type(s_type): 105 | for t in TYPE_CHOICES: 106 | if s_type == t[0]: 107 | return True 108 | return False 109 | 110 | 111 | @receiver(post_delete, sender=Screenshot) 112 | def screenshot_post_delete_handler(**kwargs): 113 | apk = kwargs['instance'] 114 | logging.info("Deleting Screenshot: %s", apk.file.name) 115 | apk.file.delete(save=False) 116 | -------------------------------------------------------------------------------- /repomaker/settings_desktop.py: -------------------------------------------------------------------------------- 1 | from pkg_resources import Requirement, resource_filename 2 | from repomaker.settings import * # pylint: disable=wildcard-import,unused-wildcard-import 3 | 4 | 5 | # Where user data such as repositories will be stored 6 | DATA_DIR = os.path.join(os.path.expanduser('~'), '.local', 'share', 'repomaker') 7 | 8 | DATABASES = { 9 | 'default': { 10 | 'ENGINE': 'django.db.backends.sqlite3', 11 | 'NAME': os.path.join(DATA_DIR, 'db.sqlite3'), 12 | } 13 | } 14 | 15 | # Location for media accessible via the web-server such as repo icons, screenshots, etc. 16 | MEDIA_ROOT = os.path.join(DATA_DIR, 'media') 17 | 18 | # Location for private data such as the repo signing key 19 | PRIVATE_REPO_ROOT = os.path.join(DATA_DIR, 'private_repo') 20 | 21 | # Static repomaker files (CSS, JavaScript, Images) 22 | STATIC_ROOT = resource_filename(Requirement.parse("repomaker"), "repomaker-static") 23 | NODE_MODULES_ROOT = os.path.join(STATIC_ROOT, 'node_modules') 24 | 25 | STATICFILES_DIRS = [ 26 | ('node_modules', NODE_MODULES_ROOT), 27 | ] 28 | 29 | # Do not try to compile SASS files 30 | SASS_PROCESSOR_ENABLED = False 31 | 32 | JS_REVERSE_JS_MINIFY = True 33 | COMPRESS_OFFLINE = True 34 | -------------------------------------------------------------------------------- /repomaker/settings_local.py: -------------------------------------------------------------------------------- 1 | LOGGING = { 2 | 'version': 1, 3 | 'disable_existing_loggers': False, 4 | 'formatters': { 5 | 'verbose': { 6 | 'format': '{levelname} {asctime} {module} {process:d} {thread:d} {message}', 7 | 'style': '{', 8 | }, 9 | 'simple': { 10 | 'format': '{levelname} {message}', 11 | 'style': '{', 12 | }, 13 | }, 14 | 'handlers': { 15 | 'console': { 16 | 'class': 'logging.StreamHandler', 17 | }, 18 | }, 19 | 'loggers': { 20 | 'root': { 21 | 'handlers': ['console'], 22 | 'formatter': 'verbose', 23 | 'level': 'INFO', 24 | }, 25 | # 'django': { 26 | # 'handlers': ['console'], 27 | # 'level': 'DEBUG', 28 | # 'propagate': True, 29 | # }, 30 | 'repomaker': { 31 | 'level': 'DEBUG', 32 | 'propagate': True, 33 | }, 34 | }, 35 | } 36 | -------------------------------------------------------------------------------- /repomaker/settings_test.py: -------------------------------------------------------------------------------- 1 | import tempfile 2 | from repomaker.settings import * # pylint: disable=wildcard-import,unused-wildcard-import 3 | 4 | 5 | TEST_FILES_DIR = os.path.join(BASE_DIR, 'tests') 6 | 7 | TEST_DIR = os.path.join(tempfile.gettempdir(), 'test_dir') 8 | MEDIA_ROOT = os.path.join(TEST_DIR, 'media') 9 | PRIVATE_REPO_ROOT = os.path.join(TEST_DIR, 'private_repo') 10 | STATIC_ROOT = os.path.join(TEST_DIR, 'static') 11 | 12 | SINGLE_USER_MODE = True 13 | 14 | COMPRESS_ENABLED = False 15 | -------------------------------------------------------------------------------- /repomaker/settings_test_multi_user.py: -------------------------------------------------------------------------------- 1 | from repomaker.settings_test import * # pylint: disable=wildcard-import,unused-wildcard-import 2 | 3 | 4 | SINGLE_USER_MODE = False 5 | 6 | INSTALLED_APPS.append('allauth') 7 | INSTALLED_APPS.append('allauth.socialaccount') 8 | -------------------------------------------------------------------------------- /repomaker/static/repomaker/css/app/add.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * Repomaker Add App Styles 3 | */ 4 | .rm-repo-add-toolbar-title { 5 | color: black; 6 | @include font-size(20px); 7 | } 8 | 9 | .rm-repo-add-toolbar-count { 10 | @extend .rm-toolbar-item; 11 | color: $rm-blue; 12 | font-weight: 500; 13 | @include font-size(11px); 14 | 15 | a { 16 | @extend .rm-no-underline; 17 | color: $rm-blue; 18 | display: flex; 19 | align-items: center; 20 | padding-left: 3px; 21 | } 22 | 23 | i { 24 | @include font-size(16px); 25 | padding-right: 3px; 26 | } 27 | } 28 | 29 | #rm-repo-add-toolbar-count-span { 30 | padding-right: 3px; 31 | } 32 | 33 | .rm-repo-add-toolbar-done { 34 | color: black !important; 35 | text-decoration: none; 36 | align-items: flex-start; 37 | display: flex; 38 | flex-flow: column; 39 | padding-right: 8px; 40 | } 41 | 42 | .rm-app-add { 43 | @extend .mdl-grid, .rm-grid--center; 44 | } 45 | 46 | .rm-app-add-filters { 47 | @extend .mdl-cell, .mdl-cell--10-col; 48 | } 49 | 50 | .rm-app-add-filters-search { 51 | @extend .mdl-textfield; 52 | align-items: center; 53 | display: flex; 54 | justify-content: center; 55 | padding: 0 6px; 56 | width: 100%; 57 | 58 | i { 59 | padding-right: 3px; 60 | } 61 | } 62 | 63 | .rm-app-add-filters-search-input { 64 | @extend .mdl-textfield__input; 65 | border: none; 66 | color: black; 67 | } 68 | 69 | .rm-app-add-filters-search-clear { 70 | color: black; 71 | } 72 | 73 | .rm-app-add-divider { 74 | border-color: #ECECEC; 75 | margin: 0; 76 | } 77 | 78 | .rm-app-add-filters-second-row { 79 | @extend .mdl-grid, .rm-grid--full-width; 80 | } 81 | 82 | .rm-app-add-filters-category-container, 83 | .rm-app-add-filters-source-container, 84 | .rm-app-clear-filters-container { 85 | @extend .mdl-cell, .mdl-cell--4-col; 86 | } 87 | 88 | .rm-app-add-filters-category, 89 | .rm-app-add-filters-source { 90 | align-items: center; 91 | display: flex; 92 | } 93 | 94 | .rm-app-add-filters-category-title, 95 | .rm-app-add-filters-source-title { 96 | color: #828282; 97 | font-weight: 500; 98 | @include font-size(13px); 99 | text-transform: uppercase; 100 | 101 | } 102 | 103 | .rm-app-add-filters-category-chip, 104 | .rm-app-add-filters-category-chip--all, 105 | .rm-app-add-filters-source-chip, 106 | .rm-app-add-filters-source-chip--all { 107 | @extend .mdl-chip, .mdl-chip--deletable; 108 | background: #f4f4f4; 109 | margin-left: 12px; 110 | 111 | .material-icons { 112 | @include font-size(16px); 113 | line-height: 24px; 114 | } 115 | } 116 | 117 | .rm-app-add-filters-category-chip--all, 118 | .rm-app-add-filters-source-chip--all { 119 | padding: 0 50px; 120 | } 121 | 122 | .rm-app-add-filters-category-chip-title, 123 | .rm-app-add-filters-source-chip-title { 124 | @extend .mdl-chip__text; 125 | color: black; 126 | @include font-size(13px); 127 | } 128 | 129 | .rm-app-add-filters-source-menu, 130 | .rm-app-add-filters-category-menu { 131 | @extend .mdl-menu; 132 | padding: 8px; 133 | min-width: 175px; 134 | max-height: 500px; 135 | overflow: auto; 136 | 137 | a { 138 | @extend .rm-no-underline; 139 | } 140 | 141 | button { 142 | display: block; 143 | background-color: Transparent; 144 | background-repeat:no-repeat; 145 | border: none; 146 | outline:none; 147 | width: 100%; 148 | @include font-size(14px); 149 | text-align: left; 150 | padding: 5px; 151 | } 152 | 153 | button:hover:enabled { 154 | background-color: #f4f4f4; 155 | } 156 | 157 | hr { 158 | border-color: #ECECEC; 159 | margin: 3px; 160 | } 161 | } 162 | 163 | .rm-app-add-apps { 164 | @extend .mdl-cell, .mdl-cell--6-col, .mdl-grid, .rm-grid--center; 165 | align-items: flex-start; 166 | align-content: flex-start; 167 | } 168 | 169 | .rm-app-add-remote-repository-information { 170 | @extend .mdl-cell, .mdl-cell--12-col; 171 | } 172 | 173 | .rm-app-add-remote-repository-information-title { 174 | color: #000000; 175 | font-family: "Roboto-Black"; 176 | @include font-size(30px); 177 | margin-bottom: 20px; 178 | margin-top: 0; 179 | } 180 | 181 | .rm-app-add-remote-repository-information-description { 182 | color: #000000; 183 | @include font-size(17px); 184 | } 185 | 186 | .rm-app-add-remote-repository-information-divider { 187 | border-color: #ECECEC; 188 | } 189 | 190 | .rm-app-add-no-results { 191 | width: 100%; 192 | @include font-size(18px); 193 | line-height: 24px; 194 | text-align: center; 195 | color: gray; 196 | } 197 | 198 | #rm-app-add-errors { 199 | @extend .error; 200 | } 201 | -------------------------------------------------------------------------------- /repomaker/static/repomaker/css/app/styles.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * Repomaker App Styles 3 | */ 4 | 5 | @import "add.scss"; 6 | @import "index.scss"; 7 | @import "widget.scss"; 8 | -------------------------------------------------------------------------------- /repomaker/static/repomaker/css/app/widget.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * App card 3 | */ 4 | 5 | .rm-app-card { 6 | border-radius: 8px; 7 | margin: 8px auto 8px auto; 8 | flex-direction: row; 9 | } 10 | 11 | .rm-app-card:not(.rm-app-card--no-hover) { 12 | transition: background-color 0.2s ease; 13 | 14 | &:hover { 15 | background: #f2f2f2; 16 | } 17 | } 18 | 19 | .rm-app-card-left { 20 | display: flex; 21 | flex-direction: column; 22 | padding: 25px; 23 | min-width: 80px; 24 | background-size: 80px 80px; 25 | background-repeat: no-repeat; 26 | background-origin: content-box; 27 | } 28 | 29 | .rm-app-card-right { 30 | padding-bottom: 25px; 31 | padding-right: 25px; 32 | padding-top: 25px; 33 | padding-left: 5px; 34 | display: flex; 35 | flex-direction: column; 36 | flex-grow: 1; 37 | overflow: auto; 38 | } 39 | 40 | .rm-app-card-title { 41 | font-weight: 500; 42 | @include font-size(17px); 43 | padding-bottom: 3px; 44 | } 45 | 46 | .rm-app-card-summary { 47 | color: #575757; 48 | @include font-size(14px); 49 | overflow: hidden; 50 | padding-bottom: 6px; 51 | text-overflow: ellipsis; 52 | white-space: nowrap; 53 | } 54 | 55 | .rm-app-card-description { 56 | @include font-size(14px); 57 | flex-grow: 1; 58 | overflow-wrap: break-word; 59 | } 60 | 61 | .rm-app-card-updated { 62 | color: #575757; 63 | @include font-size(14px); 64 | text-align: right; 65 | padding-top: 6px; 66 | } 67 | 68 | .rm-app-card-footer { 69 | display: flex; 70 | align-items: center; 71 | margin-top: 4px; 72 | } 73 | 74 | .rm-app-card-categories { 75 | align-items: flex-start; 76 | display: flex; 77 | flex-grow: 2; 78 | flex-flow: row; 79 | flex-wrap: wrap; 80 | } 81 | 82 | .rm-app-card-category-chip { 83 | background: #f4f4f4; 84 | margin: 4px; 85 | } 86 | 87 | .rm-app-card-category-text { 88 | color: #4a4a4a; 89 | font-size: 13px; 90 | } 91 | 92 | .rm-app-card-footer-action { 93 | @extend .rm-no-underline; 94 | align-self: center; 95 | button, button:focus:not(:active) { 96 | // TODO: Extend .rm-button from style.css 97 | @extend .mdl-button; 98 | background: none; 99 | border: thin solid #4a4a4a; 100 | border-radius: 25px; 101 | @include font-size(11px); 102 | margin: 10px; 103 | padding: 0 40px; 104 | } 105 | } 106 | 107 | .rm-app-card-footer-action--successful { 108 | button, button:focus:not(:active) { 109 | background: #0062cf; 110 | border-color: #0062cf; 111 | color: white; 112 | display: inline-flex; 113 | vertical-align: middle; 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /repomaker/static/repomaker/css/forms.scss: -------------------------------------------------------------------------------- 1 | .rm-form { 2 | 3 | input[type="submit"] { 4 | @extend .mdl-button; 5 | display: block; 6 | margin-left: auto; 7 | margin-right: auto; 8 | margin-top: 15px; 9 | background: none; 10 | color: #5583b6; 11 | border: thin solid #5583b6; 12 | border-radius: 25px; 13 | @include font-size(11px); 14 | padding: 0 60px; 15 | } 16 | 17 | input[type="file"] { 18 | display: none; 19 | } 20 | 21 | .mdl-spinner--single-color .mdl-spinner__layer { 22 | border-color: $rm-blue; 23 | } 24 | 25 | select { 26 | // TODO: There is no "mdc-multi-select" 27 | @extend .mdl-list; 28 | 29 | option { 30 | @extend .mdl-list__item; 31 | } 32 | } 33 | 34 | label.rm-file-upload { 35 | @extend .mdl-button, .mdl-button--accent; 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /repomaker/static/repomaker/css/login.scss: -------------------------------------------------------------------------------- 1 | 2 | .rm-login-tab-bar { 3 | @extend .mdl-tabs__tab-bar; 4 | margin-bottom: 25px; 5 | 6 | .rm-login-tab { 7 | @extend .mdl-tabs__tab; 8 | width: 50%; 9 | @include font-size(15px); 10 | text-transform: unset; 11 | } 12 | } 13 | 14 | .mdl-tabs.is-upgraded .rm-login-tab-bar .is-active.rm-login-tab { 15 | color: black; 16 | font-weight: bold; 17 | } 18 | 19 | .rm-login-form { 20 | .mdl-grid { 21 | padding: 0px; 22 | } 23 | .mdl-cell { 24 | margin: 0px; 25 | } 26 | .mdl-cell--12-col { 27 | width: 100%; 28 | } 29 | .mdl-cell--6-col { 30 | width: 50%; 31 | } 32 | .mdl-checkbox__label { 33 | @include font-size(12px); 34 | } 35 | input[type="submit"] { 36 | width: 80%; 37 | margin-top: 10px; 38 | } 39 | a { 40 | text-decoration: none; 41 | color: rgb(63,81,181); 42 | text-transform: uppercase; 43 | @include font-size(12px); 44 | float: right; 45 | } 46 | } 47 | 48 | p.or-login-with { 49 | @include font-size(15px); 50 | font-family: "Roboto-Medium"; 51 | } 52 | 53 | ul.socialaccount_providers { 54 | padding: 0; 55 | } 56 | 57 | .socialaccount_providers { 58 | 59 | a { 60 | text-decoration: none; 61 | color: unset; 62 | } 63 | 64 | li { 65 | display: inline-block; 66 | margin-right: 15px; 67 | margin-bottom: 10px; 68 | } 69 | 70 | a.socialaccount_provider { 71 | text-align: center; 72 | display: block; 73 | @include font-size(12px); 74 | } 75 | 76 | a.github:before { 77 | content:url($socialaccount_provider_url + 'github.png'); 78 | display: grid; 79 | } 80 | a.gitlab:before { 81 | content:url($socialaccount_provider_url + 'gitlab.png'); 82 | display: grid; 83 | } 84 | a.openid:before { 85 | content:url($socialaccount_provider_url + 'openid.png'); 86 | display: grid; 87 | } 88 | a.yahoo:before { 89 | content:url($socialaccount_provider_url + 'yahoo.png'); 90 | display: grid; 91 | } 92 | a.hyves:before { 93 | content:url($socialaccount_provider_url + 'hyves.png'); 94 | display: grid; 95 | } 96 | a.google:before { 97 | content:url($socialaccount_provider_url + 'google.png'); 98 | display: grid; 99 | } 100 | a.twitter:before { 101 | content:url($socialaccount_provider_url + 'twitter.png'); 102 | display: grid; 103 | } 104 | a.facebook:before { 105 | content:url($socialaccount_provider_url + 'facebook.png'); 106 | display: grid; 107 | } 108 | 109 | } 110 | -------------------------------------------------------------------------------- /repomaker/static/repomaker/css/mixins.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * Rem function with fallback from Z63: 3 | * http://zerosixthree.se/8-sass-mixins-you-must-have-in-your-toolbox/ 4 | */ 5 | @function calculateRem($size) { 6 | $remSize: $size / 14px; 7 | @return $remSize * 1rem; 8 | } 9 | 10 | @mixin font-size($size) { 11 | font-size: $size; 12 | font-size: calculateRem($size); 13 | } 14 | -------------------------------------------------------------------------------- /repomaker/static/repomaker/css/mobile-detect.scss: -------------------------------------------------------------------------------- 1 | @media screen and (max-width: $grid-desktop-breakpoint) { 2 | 3 | .only-desktop { 4 | display: none; 5 | } 6 | 7 | .only-mobile { 8 | display: unset; 9 | } 10 | 11 | } 12 | 13 | @media screen and (min-width: $grid-desktop-breakpoint - 1) { 14 | 15 | .only-desktop { 16 | display: unset; 17 | } 18 | 19 | .only-mobile { 20 | display: none; 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /repomaker/static/repomaker/css/repo/card.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * Repo card 3 | */ 4 | 5 | .rm-repo-card { 6 | @extend .mdl-cell, .mdl-cell--4-col, .mdl-card, .mdl-shadow--2dp; 7 | border-radius: 8px; 8 | transition: background-color 0.2s ease; 9 | 10 | .rm-app-number { 11 | font-family: "Roboto-Black"; 12 | @include font-size(52px); 13 | } 14 | 15 | .rm-repo-title { 16 | font-family: "Roboto-Medium"; 17 | @include font-size(22px); 18 | } 19 | 20 | .mdl-card__title { 21 | padding-bottom: 0px; 22 | } 23 | 24 | .mdl-card__supporting-text { 25 | @include font-size(14px); 26 | color: #000000; 27 | } 28 | 29 | .rm-repo-date { 30 | padding-bottom: 0px; 31 | color: #575757; 32 | } 33 | } 34 | 35 | .rm-repo-card:hover { 36 | @extend .rm-app-card:hover; 37 | } 38 | -------------------------------------------------------------------------------- /repomaker/static/repomaker/css/repo/index.scss: -------------------------------------------------------------------------------- 1 | @import "index_apps.scss"; 2 | @import "index_share.scss"; 3 | @import "index_info.scss"; 4 | 5 | .rm-repo-tabs__tab-bar { 6 | background: #39414a; 7 | } 8 | 9 | .rm-repo-tabs__tab { 10 | color: #abafb3; 11 | @include font-size(13px); 12 | padding: 0 50px; 13 | outline: 0; 14 | } 15 | 16 | .rm-repo-tabs__tab:first-child { 17 | margin-left: 100px; 18 | } 19 | 20 | // On devices smaller than desktops tabs are handled differently 21 | @media (max-width: $grid-desktop-breakpoint - 1) { 22 | .rm-repo-tabs__tab-bar { 23 | justify-content: center; 24 | } 25 | .rm-repo-tabs__tab { 26 | padding: 0 24px; 27 | } 28 | .rm-repo-tabs__tab:first-child { 29 | margin-left: 0; 30 | } 31 | } 32 | 33 | /* 34 | * Repo Empty State 35 | */ 36 | 37 | .rm-repo-empty-box { 38 | @extend .mdl-cell, .mdl-cell--8-col; 39 | background-color: #F6F8FC; 40 | margin: 25px auto; 41 | padding: 25px; 42 | text-align: center; 43 | 44 | img { 45 | max-width: 100%; 46 | } 47 | 48 | p { 49 | @include font-size(22px); 50 | } 51 | 52 | button { 53 | @extend .rm-button--blue; 54 | @include font-size(14px); 55 | height: 52px; 56 | padding: 0 52px; 57 | white-space: nowrap; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /repomaker/static/repomaker/css/repo/index_apps.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * Styles for Apps section 3 | */ 4 | 5 | .rm-repo-apps { 6 | @extend .rm-grid--center, .mdl-grid, .mdl-cell--9-col; 7 | } 8 | 9 | /* 10 | * Search 11 | */ 12 | 13 | .rm-repo-apps-header { 14 | display: flex; 15 | align-items: center; 16 | margin: 0; 17 | } 18 | 19 | .rm-repo-apps-header-search { 20 | align-items: center; 21 | display: flex; 22 | flex-direction: row; 23 | flex-grow: 2; 24 | flex-flow: row; 25 | margin-left: 16px; 26 | font-style: italic; 27 | font-size: 17px; 28 | color: #000; 29 | 30 | .mdl-textfield { 31 | margin-left: 16px; 32 | } 33 | } 34 | 35 | .rm-repo-apps-header-search-icon { 36 | color: unset; 37 | text-decoration: none; 38 | transition: color 0.2s ease; 39 | padding: 20px 0; 40 | } 41 | 42 | .rm-repo-apps-header-search-icon:hover { 43 | color: #808080; 44 | } 45 | 46 | .rm-no-search-results { 47 | margin-top: 25px; 48 | @include font-size(17px); 49 | color: #808080; 50 | } 51 | 52 | /* 53 | * Add apps 54 | */ 55 | 56 | .rm-repo-apps-add-container { 57 | @extend .mdl-cell, .mdl-cell--10-col, .center, .rm-form; 58 | padding: 20px; 59 | } 60 | 61 | .rm-repo-apps-add-remote { 62 | @extend .rm-no-underline; 63 | 64 | button { 65 | @extend .rm-button--blue; 66 | @include font-size(14px); 67 | height: 42px; 68 | padding: 0 42px; 69 | white-space: nowrap; 70 | } 71 | } 72 | 73 | .rm-repo-apps-add { 74 | background: #fafafa; 75 | background: linear-gradient(#ffffff, #fafafa); 76 | margin: 15px; 77 | padding: 20px; 78 | } 79 | 80 | .rm-repo-apps-add-title { 81 | font-weight: 600; 82 | @include font-size(15px); 83 | } 84 | 85 | .rm-repo-apps-add-description { 86 | color: #575757; 87 | @include font-size(14px); 88 | } 89 | 90 | .rm-repo-apps-add-choose { 91 | text-decoration: underline; 92 | cursor: pointer; 93 | } 94 | 95 | .rm-repo-apps-add-button { 96 | @extend .rm-button--blue; 97 | } 98 | 99 | /* 100 | * Add apps - loading 101 | */ 102 | .rm-repo-apps-add--loading { 103 | @extend .rm-repo-apps-add, .rm-dnd--loading; 104 | height: calc(70%); 105 | } 106 | 107 | .rm-repo-apps-add--loading-title { 108 | @extend .rm-dnd--loading-title; 109 | } 110 | -------------------------------------------------------------------------------- /repomaker/static/repomaker/css/repo/index_info.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * Styles for Info section 3 | */ 4 | 5 | .rm-repo-info-header { 6 | display: flex; 7 | } 8 | 9 | .rm-repo-info-name-container { 10 | flex-grow: 1; 11 | } 12 | 13 | .rm-repo-info-storage-header { 14 | display: flex; 15 | flex-flow: row; 16 | align-items: center; 17 | } 18 | 19 | .rm-repo-info-storage-header-title { 20 | flex-grow: 1; 21 | } 22 | 23 | .rm-repo-info-storage-header-add { 24 | align-items: center; 25 | color: $rm-blue; 26 | display: inline-flex; 27 | text-decoration: none; 28 | vertical-align: middle; 29 | } 30 | 31 | .rm-repo-info-name-header, .rm-repo-info-description-header, 32 | .rm-repo-info-storage-header, .rm-repo-info-fingerprint-header { 33 | color: #575757; 34 | @include font-size(14px); 35 | padding: 15px 0; 36 | text-transform: uppercase; 37 | } 38 | 39 | .rm-repo-info-name { 40 | @include font-size(18px); 41 | } 42 | 43 | .rm-repo-info-edit { 44 | @extend .rm-no-underline; 45 | button { 46 | @extend .rm-button, .mdl-button; 47 | padding: 0 35px; 48 | } 49 | } 50 | 51 | .rm-repo-info-description-header { 52 | padding-top: 45px; 53 | } 54 | 55 | .rm-repo-info-description { 56 | color: #000000; 57 | padding-bottom: 25px; 58 | } 59 | 60 | .rm-repo-info-divider { 61 | border-color: #ECECEC; 62 | } 63 | 64 | .rm-repo-info-storage-header { 65 | padding-top: 3px; 66 | } 67 | 68 | .rm-repo-info-storages { 69 | @extend .mdl-list; 70 | } 71 | 72 | .rm-repo-info-storages-item { 73 | @extend .mdl-list__item, .mdl-list__item--two-line; 74 | transition: background-color 0.2s ease; 75 | } 76 | 77 | .rm-repo-info-storages-item:hover { 78 | background: #f2f2f2; 79 | } 80 | 81 | .rm-repo-info-storages-item--default { 82 | @extend .mdl-list__item, .mdl-list__item--two-line; 83 | } 84 | 85 | .rm-repo-info-storage-add-ssh, .rm-repo-info-storage-add-git, .rm-repo-info-storage-add-as3 { 86 | margin-right: 3px; 87 | } 88 | 89 | .rm-repo-info-fingerprint { 90 | font-family: monospace; 91 | @include font-size(18px); 92 | word-spacing: 10px; 93 | } 94 | -------------------------------------------------------------------------------- /repomaker/static/repomaker/css/repo/index_share.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * Styles for share section 3 | */ 4 | .rm-repo-share-container { 5 | @extend .mdl-grid, .rm-grid--center; 6 | } 7 | .rm-repo-share { 8 | @extend .mdl-cell, .mdl-cell--6-col; 9 | } 10 | 11 | .rm-repo-share-empty { 12 | @extend .center; 13 | } 14 | 15 | .rm-repo-share-general { 16 | display: flex; 17 | margin: 16px; 18 | } 19 | 20 | .rm-repo-share-general-text { 21 | flex-grow: 1; 22 | font-weight: 500; 23 | @include font-size(17px); 24 | span { 25 | color: #0167cd; 26 | } 27 | } 28 | 29 | .rm-repo-share-general-view { 30 | @extend .rm-repo-info-edit; 31 | } 32 | 33 | .rm-repo-share-share-copy { 34 | @extend .rm-button, .mdl-button; 35 | border-color: #0167cd; 36 | color: #0167cd; 37 | display: none; 38 | padding: 0 35px; 39 | } 40 | 41 | .rm-repo-share-share-copy--successful, .rm-repo-share-share-copy--successful:focus:not(:active), 42 | .rm-repo-share-share-copy--successful:hover{ 43 | @extend .rm-button, .mdl-button; 44 | background: #0062cf; 45 | border: thin solid #0062cf; 46 | border-radius: 25px; 47 | color: white; 48 | @include font-size(11px); 49 | padding: 0 35px; 50 | vertical-align: middle; 51 | } 52 | 53 | .rm-repo-share-add-scan { 54 | @extend .rm-no-underline; 55 | button { 56 | @extend .rm-repo-share-share-copy; 57 | display: inline-block; 58 | } 59 | } 60 | 61 | .rm-repo-share-share-header, .rm-repo-share-add-header { 62 | @extend .rm-repo-info-name-header; 63 | } 64 | 65 | .rm-repo-share-share, .rm-repo-share-add { 66 | display: flex; 67 | } 68 | 69 | .rm-repo-share-share-url, .rm-repo-share-add-text { 70 | flex-grow: 1; 71 | } 72 | 73 | .rm-repo-share-divider { 74 | @extend .rm-repo-info-divider; 75 | } 76 | 77 | .rm-repo-share-social { 78 | @extend .center; 79 | a { 80 | @extend .rm-no-underline; 81 | padding: 15px; 82 | } 83 | img { 84 | height: 30px; 85 | width: 30px; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /repomaker/static/repomaker/css/repo/page.scss: -------------------------------------------------------------------------------- 1 | @import "../variables"; 2 | $roboto-font-path: unquote("roboto-fonts"); 3 | @import "roboto-fontface/css/roboto/sass/roboto-fontface"; 4 | 5 | @import "material-design-lite/src/button/button"; 6 | @import "material-design-lite/src/card/card"; 7 | @import "material-design-lite/src/grid/grid"; 8 | @import "material-design-lite/src/layout/layout"; 9 | @import "material-design-lite/src/mixins"; 10 | @import "material-design-lite/src/resets/resets"; 11 | @import "material-design-lite/src/shadow/shadow"; 12 | @import "material-design-lite/src/typography/typography"; 13 | 14 | @import "../mixins"; 15 | @import "../mobile-detect"; 16 | @import "../app/widget"; 17 | 18 | a { 19 | color: $rm-blue; 20 | } 21 | 22 | a.rm-no-underline { 23 | text-decoration: none; 24 | color: unset; 25 | } 26 | 27 | body { 28 | font-family: "Roboto", "Helvetica", "Arial", sans-serif; 29 | } 30 | 31 | button { 32 | @include font-size(11px); 33 | font-weight: medium; 34 | } 35 | 36 | h1 { 37 | @include font-size(18px); 38 | font-weight: bold; 39 | text-align: left; 40 | } 41 | 42 | p { 43 | @include font-size(14px); 44 | } 45 | 46 | .mdl-layout__header-row { 47 | padding: 0 16px 0 16px; 48 | } 49 | 50 | .mdl-button { 51 | border-radius: 25px; 52 | @include font-size(12px); 53 | margin: 10px; 54 | } 55 | 56 | #repo-buttons-add { 57 | button { 58 | background: $rm-blue; 59 | } 60 | display: none; 61 | } 62 | 63 | #repo-buttons-install { 64 | white-space: nowrap; 65 | } 66 | 67 | #repo-buttons-install-need { 68 | background: none; 69 | border: thin solid #58595B; 70 | color: #58595B; 71 | } 72 | 73 | .repo-buttons-install-have { 74 | button { 75 | background: none; 76 | border: thin solid $rm-blue; 77 | color: $rm-blue; 78 | } 79 | } 80 | 81 | .repo-header { 82 | color: #000000; 83 | background-color: #F8F8F8; 84 | 85 | .mdl-layout-title { 86 | @include font-size(14px); 87 | flex-grow: 1; 88 | text-align: center; 89 | } 90 | 91 | .mdl-navigation img { 92 | margin-left: 14px; 93 | height: 26px; 94 | width: 26px; 95 | } 96 | } 97 | 98 | .qr-header { 99 | background-color: unset; 100 | color: black; 101 | 102 | .mdl-layout__header-row { 103 | align-self: flex-end; 104 | } 105 | } 106 | 107 | .repo-content { 108 | text-align: center; 109 | } 110 | 111 | .qr-content { 112 | h3 { 113 | font-family: "Roboto-Bold"; 114 | a { 115 | color: $rm-blue; 116 | } 117 | } 118 | 119 | .qr-code { 120 | text-align: center; 121 | 122 | a { 123 | @extend a.rm-no-underline; 124 | @include font-size(12px); 125 | color: #575757; 126 | } 127 | } 128 | } 129 | 130 | .fdroid-text { 131 | text-align: left; 132 | 133 | p:first-child { 134 | @include font-size(17px); 135 | font-weight: bold; 136 | } 137 | } 138 | 139 | footer { 140 | margin-top: 50px; 141 | 142 | a { 143 | @extend a.rm-no-underline; 144 | font-weight: bold; 145 | } 146 | 147 | .mdl-mega-footer__top-section { 148 | background-color: #F8F8F8; 149 | padding: 25px; 150 | 151 | .fdroid-icon { 152 | align-self: center; 153 | text-align: right; 154 | padding: 16px; 155 | } 156 | 157 | .fdroid-text { 158 | border-left: darkgrey solid 2px; 159 | padding-left: 16px; 160 | } 161 | 162 | @media (max-width: 839px) and (min-width: 480px) { 163 | .fdroid-icon { 164 | text-align: center; 165 | margin: auto; 166 | } 167 | .fdroid-text { 168 | border-left: none; 169 | margin: auto; 170 | } 171 | } 172 | 173 | @media (max-width: 479px) { 174 | .fdroid-icon { 175 | text-align: center; 176 | margin: auto; 177 | } 178 | .fdroid-text { 179 | border-left: none; 180 | margin: auto; 181 | } 182 | } 183 | } 184 | 185 | .mdl-mega-footer__bottom-section { 186 | color: #FFFFFF; 187 | background-color: #39414A; 188 | @include font-size(12px); 189 | padding: 16px; 190 | text-align: center; 191 | 192 | .repo-made-with { 193 | text-transform: uppercase; 194 | } 195 | } 196 | 197 | } 198 | -------------------------------------------------------------------------------- /repomaker/static/repomaker/css/repo/styles.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * Repomaker Repo Styles 3 | */ 4 | 5 | @import "card.scss"; 6 | @import "index.scss"; 7 | -------------------------------------------------------------------------------- /repomaker/static/repomaker/css/storage.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Storage 3 | * 4 | * Toolbar 5 | */ 6 | .rm-storage-toolbar-container { 7 | @extend .rm-grid--full-width, .rm-grid--center, .mdl-grid; 8 | } 9 | 10 | .rm-storage-toolbar { 11 | @extend .mdl-cell, .mdl-cell--8-col; 12 | display: flex; 13 | align-items: center; 14 | } 15 | 16 | .rm-storage-toolbar-back { 17 | @extend .rm-toolbar-action-left; 18 | flex-grow: 0; 19 | padding-right: 10px; 20 | } 21 | 22 | .rm-storage-toolbar-title { 23 | @extend .rm-toolbar-action-left; 24 | font-weight: 500; 25 | @include font-size(18px); 26 | } 27 | 28 | /** 29 | * Body 30 | */ 31 | .rm-storage-container { 32 | @extend .rm-grid--center, .mdl-grid; 33 | } 34 | 35 | .rm-storage { 36 | @extend .rm-grid--center, .mdl-cell, .mdl-cell--4-col; 37 | } 38 | 39 | .rm-storage-header-icon { 40 | @extend .center; 41 | } 42 | 43 | .rm-storage-header-title { 44 | @extend .center; 45 | } 46 | 47 | .rm-storage-git-title, 48 | .rm-storage-ssh-title, 49 | .rm-storage-s3-title { 50 | margin-bottom: 3px; 51 | } 52 | 53 | .rm-storage-git-subtitle, 54 | .rm-storage-ssh-subtitle, 55 | .rm-storage-s3-subtitle { 56 | color: $rm-blue; 57 | margin-bottom: 16px; 58 | } 59 | 60 | .rm-storage-action { 61 | display: inline-block; 62 | width: 100%; 63 | 64 | a { 65 | float: right; 66 | } 67 | } 68 | 69 | .rm-storage-actions { 70 | display: flex; 71 | flex-flow: row; 72 | a, input { 73 | padding: 0 6px; 74 | } 75 | } 76 | 77 | .rm-storage-setup, 78 | .rm-storage-cancel { 79 | @extend .rm-no-underline; 80 | margin-top: 15px; 81 | button { 82 | @extend .rm-button; 83 | padding: 0 40px; 84 | } 85 | } 86 | 87 | /** 88 | * Details 89 | */ 90 | .rm-storage-detail-container { 91 | @extend .rm-grid--center, .mdl-grid; 92 | } 93 | 94 | .rm-storage-detail { 95 | @extend .rm-grid--center, .mdl-cell, .mdl-cell--5-col; 96 | } 97 | 98 | .rm-storage-detail-header { 99 | @extend .center; 100 | } 101 | 102 | .rm-storage-detail-help { 103 | a { 104 | color: $rm-blue; 105 | } 106 | } 107 | 108 | .rm-storage-detail-form { 109 | @extend .center; 110 | } 111 | 112 | .rm-storage-detail-form-enable { 113 | @extend .rm-no-underline; 114 | button { 115 | @extend .rm-button--blue; 116 | } 117 | } 118 | 119 | .rm-storage-detail-form-disable { 120 | @extend .rm-no-underline; 121 | button { 122 | @extend .rm-button--red; 123 | } 124 | } 125 | 126 | .rm-storage-detail-info { 127 | @extend .mdl-list; 128 | } 129 | 130 | .rm-storage-detail-info-item-container { 131 | @extend .mdl-list__item, .mdl-list__item--two-line; 132 | } 133 | 134 | .rm-storage-detail-info-item { 135 | @extend .mdl-list__item-primary-content; 136 | } 137 | 138 | .rm-storage-detail-info-item-icon { 139 | @extend .material-icons, .mdl-list__item-icon; 140 | } 141 | 142 | .rm-storage-detail-info-item-subtitle { 143 | @extend .mdl-list__item-sub-title; 144 | } 145 | 146 | .rm-storage-detail-action { 147 | @extend .center; 148 | 149 | a { 150 | @extend .rm-no-underline; 151 | padding: 0 6px; 152 | 153 | button { 154 | @extend .rm-button; 155 | } 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /repomaker/static/repomaker/css/styles.scss: -------------------------------------------------------------------------------- 1 | // Project-wide variables 2 | @import "variables.scss"; 3 | 4 | // Material Design Icons 5 | @import "material-design-icons-iconfont/src/material-design-icons.scss"; 6 | 7 | // Roboto Font 8 | @import "roboto-fontface/css/roboto/sass/roboto-fontface"; 9 | 10 | // Material Design Lite 11 | @import "material-design-lite/src/animation/animation"; 12 | @import "material-design-lite/src/button/button"; 13 | @import "material-design-lite/src/card/card"; 14 | @import "material-design-lite/src/checkbox/checkbox"; 15 | @import "material-design-lite/src/chip/chip"; 16 | @import "material-design-lite/src/icon-toggle/icon-toggle"; 17 | @import "material-design-lite/src/layout/layout"; 18 | @import "material-design-lite/src/list/list"; 19 | @import "material-design-lite/src/menu/menu"; 20 | @import "material-design-lite/src/mixins"; 21 | @import "material-design-lite/src/progress/progress"; 22 | @import "material-design-lite/src/resets/resets"; 23 | @import "material-design-lite/src/ripple/ripple"; 24 | @import "material-design-lite/src/shadow/shadow"; 25 | @import "material-design-lite/src/spinner/spinner"; 26 | @import "material-design-lite/src/tabs/tabs"; 27 | @import "material-design-lite/src/textfield/textfield"; 28 | @import "material-design-lite/src/typography/typography"; 29 | @import "material-design-lite/src/grid/grid"; // last 30 | 31 | @import "mixins"; 32 | 33 | @import "base.scss"; 34 | @import "login.scss"; 35 | @import "forms.scss"; 36 | @import "repo/styles.scss"; 37 | @import "app/styles.scss"; 38 | @import "storage.scss"; 39 | -------------------------------------------------------------------------------- /repomaker/static/repomaker/css/variables.scss: -------------------------------------------------------------------------------- 1 | // Paths 2 | $material-design-icons-font-path: unquote(get-setting(NODE_MODULES_URL) + 'material-design-icons-iconfont/dist/fonts/'); 3 | $roboto-font-path: unquote(get-setting(NODE_MODULES_URL) + 'roboto-fontface/fonts'); 4 | $image_path: unquote(get-setting(NODE_MODULES_URL) + 'material-design-lite/dist/images/'); 5 | $socialaccount_provider_url: unquote(get-setting(STATIC_URL) + 'repomaker/images/socialaccount_providers/'); 6 | 7 | // Colors 8 | $rm-blue: #0066CC; 9 | $rm-red: #FF3E16; 10 | $tab-active-text-color: white; 11 | 12 | // Sizes 13 | $rm-icon-size: 80px; 14 | $rm-icon-size-large: 120px; 15 | $rm-app-navigation-padding: 16px; -------------------------------------------------------------------------------- /repomaker/static/repomaker/images/default-app-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/f-droid/repomaker/2d9e6a17eb7cca6fe3f7405a98109be5320f7f00/repomaker/static/repomaker/images/default-app-icon.png -------------------------------------------------------------------------------- /repomaker/static/repomaker/images/default-repo-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/f-droid/repomaker/2d9e6a17eb7cca6fe3f7405a98109be5320f7f00/repomaker/static/repomaker/images/default-repo-icon.png -------------------------------------------------------------------------------- /repomaker/static/repomaker/images/gitlab_url.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/f-droid/repomaker/2d9e6a17eb7cca6fe3f7405a98109be5320f7f00/repomaker/static/repomaker/images/gitlab_url.png -------------------------------------------------------------------------------- /repomaker/static/repomaker/images/list_item.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/f-droid/repomaker/2d9e6a17eb7cca6fe3f7405a98109be5320f7f00/repomaker/static/repomaker/images/list_item.png -------------------------------------------------------------------------------- /repomaker/static/repomaker/images/list_item_active.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/f-droid/repomaker/2d9e6a17eb7cca6fe3f7405a98109be5320f7f00/repomaker/static/repomaker/images/list_item_active.png -------------------------------------------------------------------------------- /repomaker/static/repomaker/images/repo_index_empty_state.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/f-droid/repomaker/2d9e6a17eb7cca6fe3f7405a98109be5320f7f00/repomaker/static/repomaker/images/repo_index_empty_state.png -------------------------------------------------------------------------------- /repomaker/static/repomaker/images/repo_page/f-droid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/f-droid/repomaker/2d9e6a17eb7cca6fe3f7405a98109be5320f7f00/repomaker/static/repomaker/images/repo_page/f-droid.png -------------------------------------------------------------------------------- /repomaker/static/repomaker/images/repo_page/facebook.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/f-droid/repomaker/2d9e6a17eb7cca6fe3f7405a98109be5320f7f00/repomaker/static/repomaker/images/repo_page/facebook.png -------------------------------------------------------------------------------- /repomaker/static/repomaker/images/repo_page/twitter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/f-droid/repomaker/2d9e6a17eb7cca6fe3f7405a98109be5320f7f00/repomaker/static/repomaker/images/repo_page/twitter.png -------------------------------------------------------------------------------- /repomaker/static/repomaker/images/socialaccount_providers/facebook.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/f-droid/repomaker/2d9e6a17eb7cca6fe3f7405a98109be5320f7f00/repomaker/static/repomaker/images/socialaccount_providers/facebook.png -------------------------------------------------------------------------------- /repomaker/static/repomaker/images/socialaccount_providers/github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/f-droid/repomaker/2d9e6a17eb7cca6fe3f7405a98109be5320f7f00/repomaker/static/repomaker/images/socialaccount_providers/github.png -------------------------------------------------------------------------------- /repomaker/static/repomaker/images/socialaccount_providers/gitlab.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/f-droid/repomaker/2d9e6a17eb7cca6fe3f7405a98109be5320f7f00/repomaker/static/repomaker/images/socialaccount_providers/gitlab.png -------------------------------------------------------------------------------- /repomaker/static/repomaker/images/socialaccount_providers/google.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/f-droid/repomaker/2d9e6a17eb7cca6fe3f7405a98109be5320f7f00/repomaker/static/repomaker/images/socialaccount_providers/google.png -------------------------------------------------------------------------------- /repomaker/static/repomaker/images/socialaccount_providers/hyves.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/f-droid/repomaker/2d9e6a17eb7cca6fe3f7405a98109be5320f7f00/repomaker/static/repomaker/images/socialaccount_providers/hyves.png -------------------------------------------------------------------------------- /repomaker/static/repomaker/images/socialaccount_providers/openid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/f-droid/repomaker/2d9e6a17eb7cca6fe3f7405a98109be5320f7f00/repomaker/static/repomaker/images/socialaccount_providers/openid.png -------------------------------------------------------------------------------- /repomaker/static/repomaker/images/socialaccount_providers/twitter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/f-droid/repomaker/2d9e6a17eb7cca6fe3f7405a98109be5320f7f00/repomaker/static/repomaker/images/socialaccount_providers/twitter.png -------------------------------------------------------------------------------- /repomaker/static/repomaker/images/socialaccount_providers/yahoo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/f-droid/repomaker/2d9e6a17eb7cca6fe3f7405a98109be5320f7f00/repomaker/static/repomaker/images/socialaccount_providers/yahoo.png -------------------------------------------------------------------------------- /repomaker/static/repomaker/images/storage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/f-droid/repomaker/2d9e6a17eb7cca6fe3f7405a98109be5320f7f00/repomaker/static/repomaker/images/storage.png -------------------------------------------------------------------------------- /repomaker/static/repomaker/js/app/remote_add.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Remote App Add 3 | */ 4 | 5 | var showScreenshotsButtonContainer = document.querySelector('.rm-remote-app-privacy-button-container') 6 | if (showScreenshotsButtonContainer !== null) { 7 | var showScreenshotsAnchor = showScreenshotsButtonContainer.querySelector('a') 8 | showScreenshotsAnchor.addEventListener('click', function (event) { 9 | event.preventDefault() 10 | 11 | var screenshotsContainer = document.querySelector('.rm-app-screenshots') 12 | 13 | // Screenshot URLs are stored in a datalist element 14 | var screenshotsData = document.getElementById('rm-app-screenshots-data').options 15 | for (var i = 0; i < screenshotsData.length; i++) { 16 | addScreenshot(screenshotsContainer, screenshotsData[i].value) 17 | } 18 | 19 | // Hide privacy text and button 20 | var showScreenshotsText = document.querySelector('.rm-remote-app-privacy-text') 21 | showScreenshotsText.hidden = true 22 | showScreenshotsButtonContainer.hidden = true 23 | 24 | // Show screenshots 25 | screenshotsContainer.hidden = false 26 | }) 27 | } 28 | 29 | function addScreenshot(screenshotsContainer, url) { 30 | var container = document.createElement('div') 31 | container.classList.add('rm-app-screenshot') 32 | 33 | var image = document.createElement('img') 34 | image.src = url 35 | 36 | container.appendChild(image) 37 | screenshotsContainer.append(container) 38 | } 39 | -------------------------------------------------------------------------------- /repomaker/static/repomaker/js/mdl-tinymce.js: -------------------------------------------------------------------------------- 1 | document.addEventListener('mdl-componentupgraded', function(e) { 2 | if (typeof e.target.MaterialLayout !== 'undefined') { 3 | var head = document.getElementsByTagName('head')[0]; 4 | var script = document.createElement('script'); 5 | script.type = 'text/javascript'; 6 | script.src = '/static/django_tinymce/init_tinymce.js'; 7 | head.appendChild(script); 8 | } 9 | }); 10 | -------------------------------------------------------------------------------- /repomaker/static/repomaker/js/no-invalid-by-default.js: -------------------------------------------------------------------------------- 1 | // Source: https://gist.github.com/roshangautam/caefd856f9eb9e26033c0f71eebca837 2 | (function() { 3 | 'use strict'; 4 | 5 | MaterialTextfield = window['MaterialTextfield']; 6 | 7 | /** 8 | * Handle lost focus. 9 | * 10 | * @private 11 | */ 12 | MaterialTextfield.prototype.onBlur_ = function() { 13 | this.element_.classList.remove(this.CssClasses_.IS_FOCUSED); 14 | this.checkValidity(); 15 | }; 16 | /** 17 | * Handle change. 18 | * 19 | * @private 20 | */ 21 | MaterialTextfield.prototype.onChange_ = function() { 22 | this.checkValidity(); 23 | }; 24 | 25 | /** 26 | * Handle class updates. 27 | * 28 | * @private 29 | */ 30 | MaterialTextfield.prototype.updateClasses_ = function() { 31 | this.checkDisabled(); 32 | this.checkDirty(); 33 | var dirty = this.element_.classList.contains(this.CssClasses_.IS_DIRTY); 34 | var required = this.input_.required; 35 | if (!required || required && dirty) { 36 | this.checkValidity(); 37 | } 38 | this.checkFocus(); 39 | }; 40 | /** 41 | * Enable text field. 42 | * 43 | * @public 44 | */ 45 | MaterialTextfield.prototype.enable = function() { 46 | this.input_.disabled = false; 47 | this.updateClasses_(); 48 | this.checkValidity(); 49 | }; 50 | MaterialTextfield.prototype['enable'] = MaterialTextfield.prototype.enable; 51 | 52 | /** 53 | * Initialize element. 54 | */ 55 | MaterialTextfield.prototype.init = function() { 56 | if (this.element_) { 57 | this.label_ = this.element_.querySelector('.' + this.CssClasses_.LABEL); 58 | this.input_ = this.element_.querySelector('.' + this.CssClasses_.INPUT); 59 | if (this.input_) { 60 | if (this.input_.hasAttribute( 61 | /** @type {string} */ 62 | (this.Constant_.MAX_ROWS_ATTRIBUTE))) { 63 | this.maxRows = parseInt(this.input_.getAttribute( 64 | /** @type {string} */ 65 | (this.Constant_.MAX_ROWS_ATTRIBUTE)), 10); 66 | if (isNaN(this.maxRows)) { 67 | this.maxRows = this.Constant_.NO_MAX_ROWS; 68 | } 69 | } 70 | if (this.input_.hasAttribute('placeholder')) { 71 | this.element_.classList.add(this.CssClasses_.HAS_PLACEHOLDER); 72 | } 73 | this.boundUpdateClassesHandler = this.updateClasses_.bind(this); 74 | this.boundFocusHandler = this.onFocus_.bind(this); 75 | this.boundBlurHandler = this.onBlur_.bind(this); 76 | this.boundResetHandler = this.onReset_.bind(this); 77 | this.boundChangeHandler = this.onChange_.bind(this); 78 | this.input_.addEventListener('input', this.boundUpdateClassesHandler); 79 | this.input_.addEventListener('focus', this.boundFocusHandler); 80 | this.input_.addEventListener('blur', this.boundBlurHandler); 81 | this.input_.addEventListener('reset', this.boundResetHandler); 82 | this.input_.addEventListener('change', this.boundChangeHandler); 83 | if (this.maxRows !== this.Constant_.NO_MAX_ROWS) { 84 | // TODO: This should handle pasting multi line text. 85 | // Currently doesn't. 86 | this.boundKeyDownHandler = this.onKeyDown_.bind(this); 87 | this.input_.addEventListener('keydown', this.boundKeyDownHandler); 88 | } 89 | var invalid = this.element_.classList.contains(this.CssClasses_.IS_INVALID); 90 | this.updateClasses_(); 91 | this.element_.classList.add(this.CssClasses_.IS_UPGRADED); 92 | if (invalid) { 93 | this.element_.classList.add(this.CssClasses_.IS_INVALID); 94 | } 95 | if (this.input_.hasAttribute('autofocus')) { 96 | this.element_.focus(); 97 | this.checkFocus(); 98 | } 99 | } 100 | } 101 | }; 102 | // The component registers itself. It can assume componentHandler is available 103 | // in the global scope. 104 | componentHandler.registerUpgradedCallback(MaterialTextfield, function(textfield){}); 105 | })(); 106 | -------------------------------------------------------------------------------- /repomaker/static/repomaker/js/repo/add.js: -------------------------------------------------------------------------------- 1 | var form = document.querySelector('form') 2 | var spinner = document.querySelector('.mdl-spinner') 3 | 4 | form.addEventListener("submit", function() { 5 | form.querySelector('input[type=submit]').hidden = true 6 | spinner.hidden = false 7 | }) 8 | -------------------------------------------------------------------------------- /repomaker/static/repomaker/js/repo/apps.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Search 3 | */ 4 | var rmRepoAppsHeaderSearch = document.getElementById('id_search') 5 | var rmRepoAppsHeaderSearchInput = document.getElementById('rm-repo-apps-header-search-input') 6 | if (rmRepoAppsHeaderSearch !== null && rmRepoAppsHeaderSearch.value == '') { 7 | rmRepoAppsHeaderSearchInput.style.display = 'none' 8 | } 9 | function toggleSearch() { 10 | if (rmRepoAppsHeaderSearchInput.style.display == 'none') { 11 | rmRepoAppsHeaderSearchInput.style.display = 'block' 12 | return; 13 | } 14 | rmRepoAppsHeaderSearchInput.style.display = 'none' 15 | } 16 | 17 | /** 18 | * Upload files 19 | */ 20 | form = document.querySelector('.rm-repo-apps-add form') 21 | if (form !== null) { 22 | // Hide add button 23 | form.querySelector('button[type=submit]').hidden = true 24 | } 25 | 26 | /** 27 | * Pagination 28 | */ 29 | var mdlBody = document.querySelector('.mdl-layout__content') 30 | mdlBody.addEventListener("scroll", function () { 31 | if (!document.getElementById('rm-repo-panel-apps').classList.contains('is-active')) { 32 | return 33 | } 34 | if (mdlBody.scrollHeight - window.innerHeight - 35 | mdlBody.scrollTop <= 800) { 36 | var pagination = document.querySelector('.rm-pagination') 37 | if (pagination !== null) { 38 | handlePagination(jsonHtmlRelation, '.rm-repo-apps') 39 | } 40 | } 41 | }, false) 42 | 43 | document.addEventListener('mdl-componentupgraded', function () { 44 | var pagination = document.querySelector('.rm-pagination') 45 | // Check if pagination is already visible at first page load 46 | if (pagination !== null && isVisible(pagination)) { 47 | handlePagination(jsonHtmlRelation, '.rm-repo-apps') 48 | } 49 | }) 50 | 51 | var jsonHtmlRelation = { 52 | 'rm-app-card-description': 'description', 53 | 'rm-app-card-left': 'icon', 54 | 'rm-app-card-summary': 'summary', 55 | 'rm-app-card-title': 'name', 56 | 'rm-app-card-updated': 'updated', 57 | 'rm-app-card--repo-apps': 'id', 58 | } 59 | -------------------------------------------------------------------------------- /repomaker/static/repomaker/js/repo/share.js: -------------------------------------------------------------------------------- 1 | var button = document.getElementsByClassName('rm-repo-share-share-copy')[0]; 2 | if (button !== undefined) { 3 | button.style.display = 'inline-block'; 4 | } 5 | function copyLink(link) { 6 | var textArea = document.createElement("textarea"); 7 | textArea.value = link; 8 | document.body.appendChild(textArea); 9 | textArea.select(); 10 | 11 | try { 12 | var successful = document.execCommand('copy'); 13 | var msg = successful ? 'successful' : 'unsuccessful'; 14 | console.log('Copying link command was ' + msg); 15 | buttonSetSuccessful(); 16 | } catch (err) { 17 | console.log('Copying link command was ' + msg + ': ' + err); 18 | } 19 | } 20 | 21 | function buttonSetSuccessful() { 22 | button.className = 'rm-repo-share-share-copy--successful' 23 | button.innerHTML = 'done' 24 | } 25 | -------------------------------------------------------------------------------- /repomaker/storage.py: -------------------------------------------------------------------------------- 1 | import os 2 | import re 3 | 4 | from django.conf import settings 5 | from django.core.files.storage import FileSystemStorage 6 | from modeltranslation.utils import get_language 7 | from repomaker.utils import to_universal_language_code 8 | 9 | REPO_DIR = 'repo' 10 | 11 | USER_RE = re.compile('^user_([0-9]+)$') 12 | REMOTE_REPO_RE = re.compile('^remote_repo_([0-9]+)$') 13 | 14 | 15 | def get_repo_file_path(repo, filename): 16 | if hasattr(repo, 'user'): 17 | return os.path.join(get_repo_path(repo), filename) 18 | else: 19 | return os.path.join(get_remote_repo_path(repo), filename) 20 | 21 | 22 | def get_repo_root_path(repo): 23 | return os.path.join('user_{0}'.format(repo.user.pk), 'repo_{0}'.format(repo.pk)) 24 | 25 | 26 | def get_repo_path(repo): 27 | return os.path.join(get_repo_root_path(repo), REPO_DIR) 28 | 29 | 30 | def get_remote_repo_path(repo): 31 | return os.path.join('remote_repo_{0}'.format(repo.pk)) 32 | 33 | 34 | def get_apk_file_path(apk, filename): 35 | if hasattr(apk, 'repo'): 36 | return os.path.join(get_repo_path(apk.repo), filename) 37 | else: 38 | return os.path.join('packages', filename) 39 | 40 | 41 | def get_graphic_asset_file_path(app, filename): 42 | language_code = to_universal_language_code(get_language()) 43 | path = os.path.join(get_repo_path(app.repo), app.package_id, language_code) 44 | return os.path.join(path, filename) 45 | 46 | 47 | def get_screenshot_file_path(screenshot, filename): 48 | path = os.path.join(get_repo_path(screenshot.app.repo), screenshot.get_relative_path()) 49 | return os.path.join(path, filename) 50 | 51 | 52 | def get_icon_file_path(repo, filename): 53 | if hasattr(repo, 'user'): 54 | icon_path = os.path.join('icons', filename) 55 | return os.path.join(get_repo_path(repo), icon_path) 56 | else: 57 | return os.path.join(get_remote_repo_path(repo), filename) 58 | 59 | 60 | def get_icon_file_path_for_app(app, filename): 61 | if hasattr(app.repo, 'user'): 62 | icon_path = os.path.join('icons-640', filename) 63 | return os.path.join(get_repo_path(app.repo), icon_path) 64 | else: 65 | return os.path.join(get_remote_repo_path(app.repo), filename) 66 | 67 | 68 | def get_identity_file_path(storage, filename): 69 | return os.path.join(get_repo_root_path(storage.repo), filename) 70 | 71 | 72 | class RepoStorage(FileSystemStorage): 73 | 74 | def link(self, source, target): 75 | """ 76 | Links or copies the source file to the target file. 77 | :param source: path to source file relative to self.location 78 | :param target: path to target file relative to self.location 79 | :return: The final relative path to the target file, can be different from :param target 80 | """ 81 | target_dir = os.path.dirname(target) 82 | abs_source = os.path.join(self.location, source) 83 | abs_target = os.path.join(self.location, target) 84 | abs_target = self.get_available_name(abs_target) 85 | target_path = os.path.dirname(abs_target) 86 | 87 | if not os.path.exists(target_path): 88 | os.makedirs(target_path) 89 | # TODO support operating systems without support for os.link() 90 | os.link(abs_source, abs_target) 91 | 92 | rel_target = os.path.join(target_dir, os.path.basename(abs_target)) 93 | return rel_target 94 | 95 | 96 | class PrivateStorage(FileSystemStorage): 97 | 98 | def __init__(self, file_permissions_mode=0o600, directory_permissions_mode=0o700): 99 | super(PrivateStorage, self).__init__(settings.PRIVATE_REPO_ROOT, None, 100 | file_permissions_mode, 101 | directory_permissions_mode) 102 | -------------------------------------------------------------------------------- /repomaker/templates/account/base.html: -------------------------------------------------------------------------------- 1 | {% extends "repomaker/base.html" %} 2 | {% load i18n %} 3 | {% load sass_tags %} 4 | 5 | {% block title %} 6 | {% block head_title %}{% endblock %} 7 | {% endblock %} 8 | 9 | {% block head %} 10 | {% block extra_head %}{% endblock %} 11 | {% endblock %} 12 | 13 | 14 | {% block rm-content %} 15 | {% block body %} 16 | 17 | {% if messages %} 18 |
19 |
    20 | {% for message in messages %} 21 |
  • {{message}}
  • 22 | {% endfor %} 23 |
24 |
25 | {% endif %} 26 | 27 |
28 | {% if user.is_authenticated %} 29 | {% trans 'Change E-mail' %} | 30 | {% trans 'Sign Out' %} 31 | {% else %} 32 | {% trans 'Sign In' %} | 33 | {% trans 'Sign Up' %} 34 | {% endif %} 35 |
36 | 37 | {% block content %}{% endblock content %} 38 | 39 | {% endblock body %} 40 | 41 | {% block extra_body %}{% endblock extra_body %} 42 | 43 | {% endblock rm-content %} 44 | -------------------------------------------------------------------------------- /repomaker/templates/account/index.html: -------------------------------------------------------------------------------- 1 | {% extends "repomaker/base_modal.html" %} 2 | 3 | {% load i18n %} 4 | {% load sass_tags %} 5 | 6 | {% block title %}{% trans "Sign In" %}{% endblock %} 7 | 8 | {% block toolbar %}{% endblock toolbar %} 9 | 10 | {% block rm-content %} 11 |
12 | 13 |
14 |

{% trans "Distribute apps." %}
{% trans "Spread the love." %}

15 | 16 | {% blocktrans trimmed %} 17 | Repomaker is a free web app built for creating collections 18 | of apps, music, books, videos and pictures to share with your peers. 19 | It uses F-Droid as the mobile distribution platform. 20 | Available on Android only. 21 | {% endblocktrans %} 22 | 23 |
24 | 25 |
26 | 27 |
28 | 29 |
30 | 31 | 39 | 40 | 43 | 44 | 47 | 48 |
49 |
50 | 51 |
52 | {% endblock rm-content %} 53 | -------------------------------------------------------------------------------- /repomaker/templates/account/login.html: -------------------------------------------------------------------------------- 1 | {% extends 'account/index.html' %} 2 | 3 | {% block login-active %}is-active{% endblock login-active %} 4 | {% block login-tab-active %}is-active{% endblock login-tab-active %} 5 | 6 | {% block login-form %} 7 | {% include 'account/login_form.html' %} 8 | {% endblock login-form %} 9 | -------------------------------------------------------------------------------- /repomaker/templates/account/login_form.html: -------------------------------------------------------------------------------- 1 | {% load i18n %} 2 | {% load account socialaccount %} 3 | 4 | 42 | 43 |

44 |

45 | 46 | {% get_providers as socialaccount_providers %} 47 | 48 | {% if socialaccount_providers %} 49 | 50 | 51 |
52 | 53 |
    54 | {% include "socialaccount/snippets/provider_list.html" with process="login" %} 55 |
56 | 57 |
58 | {% include "socialaccount/snippets/login_extra.html" %} 59 | {% endif %} 60 | -------------------------------------------------------------------------------- /repomaker/templates/account/logout.html: -------------------------------------------------------------------------------- 1 | {% extends "repomaker/base_modal.html" %} 2 | {% load i18n %} 3 | 4 | {% block title %}{% trans 'Logout' %}{% endblock title %} 5 | 6 | 7 | {% block rm-content %} 8 |
9 |
10 | exit_to_app 11 |

{% trans 'See you later' %}

12 |

{% trans 'Your security is important to us. Please confirm that you want to logout.' %}

13 | 14 |
15 | {% csrf_token %} 16 | {% if redirect_field_value %} 17 | 18 | {% endif %} 19 | 20 |
21 |
22 |
23 | {% endblock rm-content %} 24 | -------------------------------------------------------------------------------- /repomaker/templates/account/password_reset.html: -------------------------------------------------------------------------------- 1 | {% extends "repomaker/base_modal.html" %} 2 | {% load i18n %} 3 | 4 | {% block title %}{% trans 'Forgot Password' %}{% endblock title %} 5 | 6 | {% block toolbar %} 7 |
8 | 9 | close 10 | 11 | {% endblock toolbar %} 12 | 13 | {% block rm-content %} 14 |
15 |
16 | lock_outline 17 |

{% trans 'Forgot Password' %}

18 | 19 |
20 | {% csrf_token %} 21 | {% include 'repomaker/form.html' %} 22 | 23 |
24 |
25 |
26 | {% endblock rm-content %} 27 | 28 | -------------------------------------------------------------------------------- /repomaker/templates/account/password_reset_done.html: -------------------------------------------------------------------------------- 1 | {% extends "repomaker/base_modal.html" %} 2 | {% load i18n %} 3 | 4 | {% block title %}{% trans 'Forgot Password' %}{% endblock title %} 5 | 6 | {% block toolbar %} 7 |
8 | 9 | close 10 | 11 | {% endblock toolbar %} 12 | 13 | {% block rm-content %} 14 |
15 |
16 | lock_outline 17 |

{% trans 'Forgot Password' %}

18 |

{% trans 'We have sent you an email with a link to reset your password.' %}

19 |
20 |
21 | {% endblock rm-content %} 22 | 23 | -------------------------------------------------------------------------------- /repomaker/templates/account/signup.html: -------------------------------------------------------------------------------- 1 | {% extends 'account/index.html' %} 2 | {% load i18n %} 3 | 4 | {% block title %}{% trans "Sign Up" %}{% endblock %} 5 | 6 | {% block signup-active %}is-active{% endblock signup-active %} 7 | {% block signup-tab-active %}is-active{% endblock signup-tab-active %} 8 | 9 | {% block signup-form %} 10 | {% include 'account/signup_form.html' %} 11 | {% endblock signup-form %} 12 | -------------------------------------------------------------------------------- /repomaker/templates/account/signup_form.html: -------------------------------------------------------------------------------- 1 | {% load i18n %} 2 | 3 | 13 | 14 |

15 |

16 | -------------------------------------------------------------------------------- /repomaker/templates/django/forms/widgets/input.html: -------------------------------------------------------------------------------- 1 | 12 | -------------------------------------------------------------------------------- /repomaker/templates/django/forms/widgets/select.html: -------------------------------------------------------------------------------- 1 | 16 | -------------------------------------------------------------------------------- /repomaker/templates/django/forms/widgets/textarea.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /repomaker/templates/repomaker/app/apk_delete.html: -------------------------------------------------------------------------------- 1 | {% extends "repomaker/base_modal.html" %} 2 | {% load i18n %} 3 | 4 | {% block title %}{% trans 'Delete App Version' %}{% endblock %} 5 | 6 | {% block rm-content %} 7 |
8 |
9 |

10 | {% blocktrans with app=object.app version=object.apk.version_name code=object.apk.version_code trimmed %} 11 | Are you sure you want to delete this version {{ version }} ({{ code }}) 12 | from your app {{ app }}? 13 | {% endblocktrans %} 14 |

15 |
16 | {% csrf_token %} 17 | 18 |
19 |
20 |
21 | {% endblock rm-content %} 22 | -------------------------------------------------------------------------------- /repomaker/templates/repomaker/app/delete.html: -------------------------------------------------------------------------------- 1 | {% extends "repomaker/base_modal.html" %} 2 | {% load i18n %} 3 | 4 | {% block title %}{% trans 'Delete App' %}{% endblock %} 5 | 6 | {% block rm-content %} 7 |
8 |
9 |

10 | {% blocktrans with app=object.app trimmed %} 11 | Are you sure you want to delete your app {{ object }}? 12 | {% endblocktrans %} 13 |

14 |
15 | {% csrf_token %} 16 | 17 |
18 |
19 |
20 | {% endblock rm-content %} 21 | -------------------------------------------------------------------------------- /repomaker/templates/repomaker/app/edit_blocked.html: -------------------------------------------------------------------------------- 1 | {% extends "repomaker/app/index.html" %} 2 | {% load i18n %} 3 | {% load static %} 4 | {% load compress %} 5 | 6 | {% block title %}{% blocktrans with app=app.name %}Edit {{ app }}{% endblocktrans %}{% endblock %} 7 | 8 | {% block back-button %}
{% endblock back-button %} 9 | 10 | {% block top-buttons %} 11 | 12 | 15 | 16 | 17 | 20 | 21 | {% endblock top-buttons %} 22 | 23 | {% block app-lang-url %}{% url 'app_edit' app.repo.id app.id lang.code %}{% endblock app-lang-url %} 24 | 25 | {% block app-info %} 26 |
27 |
28 |

{% trans 'Editing Disabled' %}

29 |

30 | {% blocktrans trimmed %} 31 | This app gets updated automatically from the remote repo. 32 | If you want to edit it yourself, you need to disable automatic updates first. 33 | Please note that without automatic updates, you will need to add new versions manually. 34 | {% endblocktrans %} 35 |

36 |
37 | {% csrf_token %} 38 | 39 | 42 |
43 |
44 |
45 | {% endblock app-info %} 46 | -------------------------------------------------------------------------------- /repomaker/templates/repomaker/app/feature_graphic_delete.html: -------------------------------------------------------------------------------- 1 | {% extends "repomaker/base_modal.html" %} 2 | {% load i18n %} 3 | 4 | {% block title %}{% trans 'Delete Feature Graphic' %}{% endblock %} 5 | 6 | 7 | {% block rm-content %} 8 |
9 | 10 |
11 | 12 |

13 | {% blocktrans trimmed %} 14 | Are you sure you want to delete the feature graphic from your app {{ app }}? 15 | {% endblocktrans %} 16 |

17 | 18 | 19 | 20 |

21 | 22 |
23 | {% csrf_token %} 24 | 25 |
26 |
27 | 28 |
29 | {% endblock rm-content %} 30 | -------------------------------------------------------------------------------- /repomaker/templates/repomaker/app/remote_add.html: -------------------------------------------------------------------------------- 1 | {% extends "repomaker/app/index.html" %} 2 | {% load i18n %} 3 | {% load static %} 4 | {% load compress %} 5 | 6 | {% block back-button %} 7 | 8 | arrow_back 9 | 10 | {% endblock back-button %} 11 | 12 | {% block top-buttons %} 13 |
14 | {% csrf_token %} 15 | 17 |
18 | {% endblock top-buttons %} 19 | 20 | {% block app-lang-url %}{% url 'add_remote_app' repo.id app.repo.id app.id lang.code %}{% endblock app-lang-url %} 21 | {% block add-translation-button %}{% endblock add-translation-button %} 22 | 23 | {% block screenshots %} 24 | 25 | {% if screenshots.exists %} 26 |
27 | {% if show_screenshots %} 28 |
29 | {% for screenshot in screenshots %} 30 |
31 | 32 |
33 | {% endfor %} 34 |
35 | {% else %} 36 | 37 |
38 | {% trans 'There are currently no screenshots shown because this gives the repo owner the ability to track you.' %} 39 |
40 | 45 | 46 | {% for screenshot in screenshots %} 47 | 50 | {% endif %} 51 |
52 | {% endif %} 53 | {% endblock screenshots %} 54 | 55 | {% block apks %}{% endblock apks %} 56 | 57 | {% block page-footer %} 58 | {% compress js file remote_add %} 59 | 60 | 61 | {% endcompress %} 62 | {% endblock page-footer %} 63 | -------------------------------------------------------------------------------- /repomaker/templates/repomaker/app/screenshot_delete.html: -------------------------------------------------------------------------------- 1 | {% extends "repomaker/base_modal.html" %} 2 | {% load i18n %} 3 | 4 | {% block title %}{% trans 'Delete Screenshot' %}{% endblock %} 5 | 6 | 7 | {% block rm-content %} 8 |
9 | 10 |
11 | 12 |

13 | {% blocktrans with app=object.app trimmed %} 14 | Are you sure you want to delete this screenshot from your app {{ app }}? 15 | {% endblocktrans %} 16 |

17 | 18 | 19 | 20 |

21 | 22 |
23 | {% csrf_token %} 24 | 25 |
26 |
27 | 28 |
29 | {% endblock rm-content %} 30 | -------------------------------------------------------------------------------- /repomaker/templates/repomaker/app/translation_add.html: -------------------------------------------------------------------------------- 1 | {% extends "repomaker/app/edit.html" %} 2 | {% load i18n %} 3 | 4 | {% block back-button %} 5 | 6 | arrow_back 7 | 8 | {% endblock back-button %} 9 | 10 | {% block top-buttons %} 11 |
12 | {% csrf_token %} 13 | 16 | {% endblock top-buttons %} 17 | 18 | {% block author %}{% endblock author %} 19 | {% block info %}{% endblock info %} 20 | {% block categories %}{% endblock categories %} 21 | 22 | {% block before-summary %} 23 |
24 | 25 | {{ form.lang }} 26 |
27 | {% if form.lang.errors %} 28 |
{{ form.lang.errors }}
29 | {% endif %} 30 | {% endblock before-summary %} 31 | 32 | 33 | {% block screenshots %}{% endblock screenshots %} 34 | {% block feature-graphic %}{% endblock feature-graphic %} 35 | 36 | {% block apks %}{% endblock apks %} 37 | {% block bottom-navigation %}{% endblock bottom-navigation %} 38 | -------------------------------------------------------------------------------- /repomaker/templates/repomaker/base.html: -------------------------------------------------------------------------------- 1 | 2 | {% load static %} 3 | {% load sass_tags %} 4 | {% load i18n %} 5 | {% load compress %} 6 | 7 | 8 | 9 | 10 | Repo Maker - {% block title %}{% endblock %} 11 | {% compress css file styles %} 12 | 13 | {% endcompress %} 14 | 15 | 16 | {% block head %}{% endblock head %} 17 | 18 | 19 | 20 | 21 |
22 |
23 |
24 | {% block toolbar %} 25 | 26 | {% block toolbar-items-left %}{% endblock toolbar-items-left %} 27 | 28 | 29 | 30 | {% block toolbar-title %}Repo Maker{% endblock toolbar-title %} 31 | 32 | 33 |
34 | 35 | {% block toolbar-items-right %}{% endblock toolbar-items-right %} 36 | 37 | 52 | {% endblock toolbar %} 53 |
54 |
55 | 56 |
57 | {% block rm-content--without-margin %} 58 | {% include 'repomaker/widgets/site_notice.html' %} 59 | {% endblock rm-content--without-margin %} 60 |
{% block rm-content %}{% endblock %}
61 |
62 |
63 | {% compress js file material %} 64 | 65 | {% endcompress %} 66 | 67 | {% if form %} 68 | 72 | {% compress js file no-invalid-by-default %} 73 | 74 | {% endcompress %} 75 | {% endif %} 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /repomaker/templates/repomaker/base_modal.html: -------------------------------------------------------------------------------- 1 | {% extends "repomaker/base.html" %} 2 | 3 | {% block rm-header-classes %}rm-header--light{% endblock rm-header-classes %} 4 | 5 | {% block toolbar %} 6 |
7 | 8 | close 9 | 10 | {% endblock toolbar %} 11 | -------------------------------------------------------------------------------- /repomaker/templates/repomaker/db_locked.html: -------------------------------------------------------------------------------- 1 | {% extends "repomaker/base_modal.html" %} 2 | {% load i18n %} 3 | 4 | {% block title %}{% trans 'Please Wait!' %}{% endblock title %} 5 | 6 | {% block toolbar %}{% endblock toolbar %} 7 | 8 | {% block rm-content %} 9 |
10 |
11 |

{% trans 'Please Wait!' %}

12 |
13 |

{% trans 'Background Operation in Progress' %}

14 |

15 | {% blocktrans trimmed %} 16 | Repomaker might be updating or publishing your repo. Or it might be updating a remote repo. 17 | Depending on the type and size of the background operation, this might take awhile. 18 | {% endblocktrans %} 19 |

20 |

21 | 22 |

23 | 24 |
25 |
26 | {% endblock rm-content %} 27 | -------------------------------------------------------------------------------- /repomaker/templates/repomaker/error.html: -------------------------------------------------------------------------------- 1 | {% extends "repomaker/base_modal.html" %} 2 | {% load i18n %} 3 | 4 | {% block title %}{% trans 'Error' %}{% endblock title %} 5 | 6 | 7 | {% block rm-content %} 8 |
9 |
10 |

{% trans 'Error' %}

11 |
12 | {{ error }} 13 | {% for field in form %} 14 | {{ field.errors }} 15 | {% endfor %} 16 |
17 |
18 |
19 | {% endblock rm-content %} 20 | -------------------------------------------------------------------------------- /repomaker/templates/repomaker/form.html: -------------------------------------------------------------------------------- 1 | {% for field in form %} 2 |
16 | {% if field.field.widget.input_type == 'file' %} 17 | 18 | {% elif field.field.widget.input_type == 'checkbox' %} 19 | 20 | {% else %} 21 | 22 | {% endif %} 23 | {{ field }} 24 |
25 | {{ field.help_text }} 26 | {% if field.errors %} 27 | {{ field.errors }} 28 | {% endif %} 29 | {% endfor %} 30 | -------------------------------------------------------------------------------- /repomaker/templates/repomaker/index.html: -------------------------------------------------------------------------------- 1 | {% extends "repomaker/base.html" %} 2 | {% load i18n %} 3 | {% load static %} 4 | {% load humanize %} 5 | 6 | {% block title %}{% trans 'My Repos' %}{% endblock %} 7 | 8 | {% block toolbar-items-left %} 9 | 10 | home 11 | 12 | {% endblock toolbar-items-left %} 13 | 14 | {% block toolbar-title %}{% trans 'My Repos' %}{% endblock toolbar-title %} 15 | 16 | {% block navigation %} 17 | 18 | add 19 | 20 | {% endblock navigation %} 21 | 22 | {% block rm-content %} 23 | 61 | {% endblock rm-content %} 62 | -------------------------------------------------------------------------------- /repomaker/templates/repomaker/repo/add.html: -------------------------------------------------------------------------------- 1 | {% extends "repomaker/base_modal.html" %} 2 | {% load i18n %} 3 | {% load static %} 4 | {% load compress %} 5 | 6 | {% block title %}{% trans 'New Repo' %}{% endblock title %} 7 | 8 | 9 | {% block rm-content %} 10 |
11 |
12 |

{% trans 'New Repo' %}

13 | 14 | 15 | {% csrf_token %} 16 | {% include '../form.html' %} 17 | 18 | 19 | 20 |
21 |
22 | {% compress js file add %} 23 | 24 | {% endcompress %} 25 | {% endblock rm-content %} 26 | -------------------------------------------------------------------------------- /repomaker/templates/repomaker/repo/delete.html: -------------------------------------------------------------------------------- 1 | {% extends "repomaker/base_modal.html" %} 2 | {% load i18n %} 3 | 4 | {% block title %}{% trans 'Delete Repo' %}{% endblock %} 5 | 6 | {% block rm-content %} 7 |
8 |
9 |

10 | {% blocktrans with repo=object trimmed %} 11 | Are you sure you want to delete the repo {{ repo }}? 12 | {% endblocktrans %} 13 |

14 |

15 | {% blocktrans trimmed %} 16 | This will also delete all apps and other files from your repo. 17 | You will not be able to get them back, 18 | users of your repo will not be able to use it anymore and 19 | not receive updates for the apps they received from you. 20 | There is no way to recover this repo! 21 | {% endblocktrans %} 22 |

23 |

24 | 25 | {% blocktrans trimmed %} 26 | Please only proceed if you know what you are doing! 27 | {% endblocktrans %} 28 | 29 |

30 |
31 | {% csrf_token %} 32 | 33 |
34 |
35 |
36 | {% endblock rm-content %} 37 | -------------------------------------------------------------------------------- /repomaker/templates/repomaker/repo/edit.html: -------------------------------------------------------------------------------- 1 | {% extends "repomaker/base_modal.html" %} 2 | {% load i18n %} 3 | 4 | {% block title %}{% trans 'Edit Repo' %}{% endblock %} 5 | 6 | 7 | {% block rm-content %} 8 |
9 |
10 |

{% trans 'Edit Repo' %}

11 | 12 |
13 |
14 | {% csrf_token %} 15 | {% include '../form.html' %} 16 | 17 |
18 |
19 |
20 |
21 | {% endblock rm-content %} 22 | -------------------------------------------------------------------------------- /repomaker/templates/repomaker/repo/index.html: -------------------------------------------------------------------------------- 1 | {% extends "repomaker/base.html" %} 2 | {% load i18n %} 3 | {% load humanize %} 4 | 5 | {% block title %}{{ repo.name }}{% endblock %} 6 | 7 | {% block toolbar-items-left %} 8 | 9 | home 10 | 11 | {% endblock toolbar-items-left %} 12 | 13 | {% block toolbar-title %} 14 | {{ repo.name }} 15 | {% endblock toolbar-title %} 16 | 17 | {% block toolbar-items-right %} 18 | 19 | add 20 | 21 | {% endblock toolbar-items-right %} 22 | 23 | {% block rm-content--without-margin %} 24 |
25 | 30 | 31 | {% include 'repomaker/widgets/site_notice.html' %} 32 | 33 |
34 | {% include "repomaker/repo/index_apps.html" %} 35 |
36 |
37 | {% include "repomaker/repo/index_share.html" %} 38 |
39 |
40 | {% include "repomaker/repo/index_info.html" %} 41 |
42 |
43 | 44 | {% endblock rm-content--without-margin %} 45 | -------------------------------------------------------------------------------- /repomaker/templates/repomaker/repo/index_apps.html: -------------------------------------------------------------------------------- 1 | {% load i18n %} 2 | {% load static %} 3 | {% load compress %} 4 | 5 |
6 | {% if request.GET.search or is_paginated %} 7 |
8 | 22 |
23 | {% endif %} 24 |
25 | 26 | 29 | 30 |
31 |
32 |
33 | {% blocktrans with label_start='' trimmed %} 34 | Alternatively, drag and drop or {{ label_start }}choose files{{ label_end }} 35 | {% endblocktrans %} 36 |
37 |
38 | {% trans 'Accepted files include Android apps, documents, eBooks, audio and video.' %} 39 |
40 |

{{ form.apks }}

41 | 44 | {% csrf_token %} 45 |
46 |
47 | 52 |
53 | {% for app in apps %} 54 | {% url 'app' repo.id app.id as url %} 55 | {% include "repomaker/widgets/app.html" with clickable=True url=url width=6 %} 56 | {% empty %} 57 | {% if request.GET.search %} 58 |
59 | {% trans 'Your search did not return any results.' %} 60 |
61 | {% endif %} 62 | {% endfor %} 63 |
64 | 65 | {% compress js file endless-app-scroll %} 66 | 67 | {% endcompress %} 68 | 69 |
70 | 71 | {% compress js file apps %} 72 | 73 | {% endcompress %} 74 | 75 | {% compress js file drag-and-drop %} 76 | 77 | {% endcompress %} 78 | 79 | {% include "repomaker/widgets/pagination.html" %} 80 | -------------------------------------------------------------------------------- /repomaker/templates/repomaker/repo/index_info.html: -------------------------------------------------------------------------------- 1 | {% load i18n %} 2 |
3 |
4 |
5 | 6 |
{% trans 'Name' %}
7 |
{{ repo.name }}
8 |
9 | 10 | 13 | 14 |
15 | 16 |
{% trans 'Description' %}
17 |

{{ repo.description }}

18 |
19 | 20 |
{% trans 'Storage' %}
21 | {% if storage %} 22 | 47 | {% endif %} 48 | 49 | add 50 | {% trans 'Add Storage' %} 51 | 52 | 53 |
54 | 55 |
{% trans 'Fingerprint' %}
56 |
57 | 58 |
{{ repo.get_fingerprint_with_spaces }}
59 |
60 |
61 | 68 |
69 |
70 | -------------------------------------------------------------------------------- /repomaker/templates/repomaker/repo/index_share.html: -------------------------------------------------------------------------------- 1 | {% load i18n %} 2 | {% load static %} 3 | {% load compress %} 4 | 5 |
6 |
7 | {% if not repo.last_publication_date %} 8 |
9 | cloud_upload 10 |

11 | {% trans 'Add storage to publish' %} 12 |

13 |

14 | {% blocktrans trimmed %} 15 | Your repo will be automatically published when you add storage. 16 | Repomaker does not publically promote your stuff. 17 | Only people who receive the direct link to your repo 18 | will be able to see it. 19 | {% endblocktrans %} 20 |

21 | 22 | 25 | 26 |
27 | {% else %} 28 |
29 |
30 | {% blocktrans count app_count=repo.app_set.all.count trimmed %} 31 | You have {{ app_count }} app in your repo. Share it! 32 | {% plural %} 33 | You have {{ app_count }} apps in your repo. Share it! 34 | {% endblocktrans %} 35 |
36 | 37 | 40 | 41 |
42 | 43 |
{% trans 'share public link' %}
44 |
45 | {{ repo.url }} 46 | 49 |
50 |
51 | 52 |
{% trans 'add to an android phone' %}
53 |
54 | 55 | {% trans 'To install these apps on your Android phone, add them to F-Droid.' %}
56 | {% trans 'F-Droid ensures access to secure app updates.' %} 57 |
58 | 59 | 62 | 63 |
64 |
65 |
66 | {% blocktrans with name=repo.name asvar text trimmed %} 67 | Check out this F-Droid repo: {{ name }} 68 | {% endblocktrans %} 69 | 71 | 72 | 73 | 75 | 76 | 77 |
78 | {% endif %} 79 |
80 |
81 | {% compress js file share %} 82 | 83 | {% endcompress %} 84 | -------------------------------------------------------------------------------- /repomaker/templates/repomaker/repo_page/base.html: -------------------------------------------------------------------------------- 1 | 2 | {% load sass_tags %} 3 | 4 | 5 | 6 | 7 | {{ repo.name }} 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 |
16 |
17 | {% block header %}{% endblock header %} 18 |
19 |
20 | 21 |
22 | {% block content %}{% endblock content %} 23 |
24 |
25 | 26 | {% block footer %}{% endblock footer %} 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /repomaker/templates/repomaker/repo_page/fdroid.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 |
5 |

Use F-Droid to install apps and ensure access to app updates.

6 | 7 |

8 | F-Droid is an independent, community-sourced app store. 9 | Adding a repo to F-Droid makes the apps within it available for download 10 | and ensures that you have access to secure app updates. 11 |

12 |

13 | Download F-Droid at f-droid.org. 14 |

15 |
16 | -------------------------------------------------------------------------------- /repomaker/templates/repomaker/repo_page/index.html: -------------------------------------------------------------------------------- 1 | {% extends 'repomaker/repo_page/base.html' %} 2 | 3 | {% block header-classes %}repo-header{% endblock header-classes %} 4 | 5 | {% block header %} 6 |
7 | 8 | To install these apps on your Android phone, add them to F-Droid. 9 | Get F-Droid 10 | 11 | 12 |
13 | 17 |
18 | 19 | 22 | 23 | 37 |
38 | 39 | 51 | {% endblock header %} 52 | 53 | {% block content %} 54 |
55 |
56 |

{{ repo.name }}

57 |

{{ repo.description }}

58 |
59 | {% for app in repo.app_set.all %} 60 | {% include "repomaker/widgets/app.html" with repo_page=True width=6 no_hover=True %} 61 | {% endfor %} 62 |
63 |
64 | 67 | 72 |
73 | {% endblock content %} 74 | 75 | {% block footer %} 76 | 77 | 91 | {% endblock footer %} 92 | -------------------------------------------------------------------------------- /repomaker/templates/repomaker/repo_page/qr_code.html: -------------------------------------------------------------------------------- 1 | {% extends 'repomaker/repo_page/base.html' %} 2 | 3 | {% block header-classes %}qr-header{% endblock header-classes %} 4 | 5 | {% block header %} 6 | 11 | {% endblock header %} 12 | 13 | {% block content %} 14 |
15 | 16 |
17 |

18 | Scan the QR code to add {{ repo.name }} 19 | to F-Droid. 20 |

21 |
22 | 23 | 30 | 31 |
32 | {% include 'repomaker/repo_page/fdroid.html' %} 33 |
34 | 35 |
36 | {% endblock content %} 37 | -------------------------------------------------------------------------------- /repomaker/templates/repomaker/storage/delete.html: -------------------------------------------------------------------------------- 1 | {% extends "repomaker/base_modal.html" %} 2 | {% load i18n %} 3 | 4 | {% block title %}{% trans 'Delete Storage' %}{% endblock %} 5 | 6 | {% block rm-content %} 7 |
8 |
9 |

10 | {% blocktrans with repo=object trimmed %} 11 | Are you sure you want to remove the storage at {{ object }} from your repo? 12 | {% endblocktrans %} 13 |

14 |

15 | {% blocktrans trimmed %} 16 | This will not remove the content from that storage 17 | but will stop to publish future versions of your repo to it. 18 | {% endblocktrans %} 19 |

20 | 21 |
22 | {% csrf_token %} 23 | 24 |
25 |
26 |
27 | {% endblock rm-content %} 28 | -------------------------------------------------------------------------------- /repomaker/templates/repomaker/storage/detail.html: -------------------------------------------------------------------------------- 1 | {% extends "repomaker/base_modal.html" %} 2 | {% load i18n %} 3 | {% load static %} 4 | 5 | {% block title %}{{ storage.get_name }}{% endblock title %} 6 | 7 | {% block toolbar %} 8 |
9 |
10 | 11 | arrow_back 12 | 13 | 14 | {% trans 'Storage' %} 15 | 16 |
17 |
18 | {% endblock toolbar %} 19 | 20 | {% block rm-content %} 21 |
22 |
23 |
24 | 25 |

{{ storage.get_name }}

26 |
27 | 28 |
29 | {% block help_text %}{% endblock help_text %} 30 |
31 | 32 |
33 | {% csrf_token %} 34 | {% if storage.disabled %} 35 | 36 | 37 | 40 | 41 | {% else %} 42 | 43 | 44 | 47 | 48 | {% endif %} 49 |
50 | 51 |
    52 |
  • 53 | 54 | insert_link 55 | {% trans 'Repo Address' %} 56 | 57 | {{ storage.get_repo_url }} 58 | 59 | 60 |
  • 61 | {% block storage_details %}{% endblock storage_details %} 62 |
63 | 64 | 65 | 66 | 78 |
79 |
80 | {% endblock rm-content %} 81 | -------------------------------------------------------------------------------- /repomaker/templates/repomaker/storage/detail_git.html: -------------------------------------------------------------------------------- 1 | {% extends "repomaker/storage/detail_ssh_key.html" %} 2 | {% load i18n %} 3 | 4 | {% block ssh_help_text %} 5 |

6 | {% blocktrans trimmed %} 7 | Please make sure to add this public SSH key to your Git account for the upload to work. 8 | There are instructions for 9 | Gitlab 10 | and 11 | GitHub. 12 | {% endblocktrans %} 13 |

14 |

15 | {% blocktrans trimmed %} 16 | Also, please make sure that the master branch of your repository is not protected from force pushing. 17 | {% endblocktrans %} 18 |

19 | {% endblock ssh_help_text %} 20 | 21 | {% block storage_details %} 22 |
  • 23 | 24 | public 25 | {% trans 'Git Repository SSH Address' %} 26 | 27 | {{ storage.get_remote_url }} 28 | 29 | 30 |
  • 31 | {% endblock storage_details %} 32 | -------------------------------------------------------------------------------- /repomaker/templates/repomaker/storage/detail_s3.html: -------------------------------------------------------------------------------- 1 | {% extends "repomaker/storage/detail.html" %} 2 | {% load i18n %} 3 | 4 | {% block storage_details %} 5 |
  • 6 | 7 | public 8 | {% trans 'Region' %} 9 | 10 | {{ storage.get_region_display }} 11 | 12 | 13 |
  • 14 |
  • 15 | 16 | dns 17 | {% trans 'Bucket' %} 18 | 19 | {{ storage.bucket }} 20 | 21 | 22 |
  • 23 |
  • 24 | 25 | vpn_key 26 | {% trans 'Access Key ID' %} 27 | 28 | {{ storage.accesskeyid }} 29 | 30 | 31 |
  • 32 | {% endblock storage_details %} 33 | -------------------------------------------------------------------------------- /repomaker/templates/repomaker/storage/detail_ssh.html: -------------------------------------------------------------------------------- 1 | {% extends "repomaker/storage/detail_ssh_key.html" %} 2 | {% load i18n %} 3 | 4 | {% block ssh_help_text %} 5 |

    6 | {% blocktrans trimmed %} 7 | Please make sure to add this public SSH key to your SSH server's 8 | 9 | ~/.ssh/authorized_keys 10 | file, if you have not done so already. 11 | {% endblocktrans %} 12 |

    13 |

    14 | {% trans "Security Warning: This gives repomaker write access to that storage. Please limit this user's access as much as possible." %} 15 |

    16 | {% endblock ssh_help_text %} 17 | 18 | {% block storage_details %} 19 |
  • 20 | 21 | face 22 | {% trans 'Username' %} 23 | 24 | {{ storage.username }} 25 | 26 | 27 |
  • 28 |
  • 29 | 30 | cloud_circle 31 | {% trans 'Host' %} 32 | 33 | {{ storage.host }} 34 | 35 | 36 |
  • 37 |
  • 38 | 39 | folder 40 | {% trans 'Path' %} 41 | 42 | {{ storage.path }} 43 | 44 | 45 |
  • 46 | {% endblock storage_details %} 47 | -------------------------------------------------------------------------------- /repomaker/templates/repomaker/storage/detail_ssh_key.html: -------------------------------------------------------------------------------- 1 | {% extends "repomaker/storage/detail.html" %} 2 | {% load i18n %} 3 | 4 | {% block help_text %} 5 | 6 | {% if storage.disabled %} 7 | {% if object.public_key %} 8 |

    A fresh SSH key has been created for you:

    9 |
    {{ object.public_key }}
    10 | {% else %} 11 |

    12 | {% trans "Your local default SSH key will be used to connect to the storage." %} 13 |

    14 | {% endif %} 15 | {% block ssh_help_text %} 16 |

    17 | {% trans "Please make sure to authorize this public SSH key for your remote storage, if you have not done so already." %} 18 |

    19 | {% endblock ssh_help_text %} 20 |

    21 | {% trans "Activate this storage only once you have added the SSH key." %} 22 |

    23 | {% else %} 24 |

    25 | {% trans "If this storage is experiencing problems, please check that the SSH key works properly." %} 26 |

    27 | {% endif %} 28 | 29 | {% endblock help_text %} 30 | -------------------------------------------------------------------------------- /repomaker/templates/repomaker/storage/form.html: -------------------------------------------------------------------------------- 1 | {% extends "repomaker/base_modal.html" %} 2 | {% load i18n %} 3 | {% load static %} 4 | 5 | {% block title %} 6 | {% if object %} 7 | {% blocktrans %}Update {{ storage_name }}{% endblocktrans %} 8 | {% else %} 9 | {% blocktrans %}Setup {{ storage_name }}{% endblocktrans %} 10 | {% endif %} 11 | {% endblock title %} 12 | 13 | {% block toolbar %} 14 |
    15 |
    16 | {% if object %} 17 | 19 | arrow_back 20 | 21 | {% else %} 22 | 24 | arrow_back 25 | 26 | {% endif %} 27 | 28 | {% trans 'Storage' %} 29 | 30 |
    31 |
    32 | {% endblock toolbar %} 33 | 34 | {% block rm-content %} 35 |
    36 |
    37 |
    38 | {% block header %} 39 |

    40 | {% if object %} 41 | {% blocktrans %}Update {{ storage_name }}{% endblocktrans %} 42 | {% else %} 43 | {% blocktrans %}Setup {{ storage_name }}{% endblocktrans %} 44 | {% endif %} 45 |

    46 | {% endblock header %} 47 | 48 | 49 |
    50 | {% csrf_token %} 51 | {% block form %} 52 | {% include '../form.html' %} 53 | {% endblock form %} 54 |
    55 | {% if object %} 56 | 58 | 59 | 60 | {% else %} 61 | 63 | 64 | 65 | {% endif %} 66 | 67 |
    68 | 69 |
    70 |
    71 |
    72 | {% endblock rm-content %} 73 | -------------------------------------------------------------------------------- /repomaker/templates/repomaker/storage/form_git.html: -------------------------------------------------------------------------------- 1 | {% extends "repomaker/storage/form.html" %} 2 | {% load i18n %} 3 | {% load static %} 4 | 5 | {% block form %} 6 |
    8 | 9 | {{ form.ssh_url }} 10 |
    11 | {% if form.ssh_url.errors %} 12 | {{ form.ssh_url.errors }} 13 | {% endif %} 14 |
    15 | {% blocktrans trimmed %} 16 | Here's how to copy the SSH Address from GitLab: 17 | {% endblocktrans %} 18 | 19 | 20 | 21 |
    22 | 23 |
    25 | 26 | {{ form.url }} 27 |
    28 | {{ form.url.help_text }} 29 | {% if form.url.errors %} 30 | {{ form.url.errors }} 31 | {% endif %} 32 | 33 | {% if form.main %} 34 |
    36 | 39 | {{ form.main }} 40 |
    41 | {{ form.main.help_text }} 42 | {% if form.main.errors %} 43 | {{ form.main.errors }} 44 | {% endif %} 45 | {% endif %} 46 | 47 | {% if form.ignore_identity_file %} 48 |
    50 | 53 | {{ form.ignore_identity_file }} 54 |
    55 | {{ form.ignore_identity_file.help_text }} 56 | {% if form.ignore_identity_file.errors %} 57 | {{ form.ignore_identity_file.errors }} 58 | {% endif %} 59 | {% endif %} 60 | {% endblock form %} 61 | -------------------------------------------------------------------------------- /repomaker/templates/repomaker/storage/index.html: -------------------------------------------------------------------------------- 1 | {% extends "repomaker/base_modal.html" %} 2 | {% load i18n %} 3 | {% load static %} 4 | 5 | {% block title %}{% trans 'Add Storage' %}{% endblock title %} 6 | 7 | {% block toolbar %} 8 |
    9 |
    10 | 11 | arrow_back 12 | 13 | 14 | {% trans 'Storage' %} 15 | 16 |
    17 |
    18 | {% endblock toolbar %} 19 | 20 | {% block rm-content %} 21 |
    22 |
    23 |
    24 |
    25 |

    26 | {% trans 'Setup a place to store your repo' %} 27 |

    28 |
    29 | 30 |
    31 |

    {% trans 'Git' %}

    32 |
    {% trans 'Easy, free, requires an account' %}
    33 |

    34 | {% blocktrans trimmed %} 35 | Git is a popular tool to version control things. 36 | You can securely publish repos to GitLab, GitHub or any other Git service. 37 | Git services use SSH to securely copy files from your computer to remote computers. 38 | {% endblocktrans %} 39 |

    40 | 45 |

    46 | 47 | 66 |
    67 | 68 |
    69 |

    {% trans 'SSH' %}

    70 |
    {% trans 'Requires a personal web server or host that supports SSH' %}
    71 |

    72 | {% blocktrans trimmed %} 73 | SSH is a technology that can be used to copy files securely to remote computers. 74 | When using Git as storage, the files will also be copied via SSH. 75 | But the pure SSH option directly copies the files without using any other technology. 76 | {% endblocktrans %} 77 |

    78 | 83 |
    84 | 85 |
    86 |

    {% trans 'Amazon S3' %}

    87 |
    {% trans 'Requires an Amazon account, Pricing varies' %}
    88 |

    89 | {% blocktrans trimmed %} 90 | Amazon S3 is a web service offered by Amazon Web Services. 91 | Amazon S3 provides storages through web service interfaces. 92 | Pricing varies. 93 | Visit the Amazon S3 website for details. 94 | {% endblocktrans %} 95 |

    96 | 101 |
    102 |
    103 |
    104 | {% endblock rm-content %} 105 | -------------------------------------------------------------------------------- /repomaker/templates/repomaker/widgets/app.html: -------------------------------------------------------------------------------- 1 | {% load i18n %} 2 | {% if not clickable or repo_page %} 3 | 30 | {% endif %} 31 | -------------------------------------------------------------------------------- /repomaker/templates/repomaker/widgets/app_with_action.html: -------------------------------------------------------------------------------- 1 | {% extends "repomaker/widgets/app.html" %} 2 | {% load i18n %} 3 | 4 | {% block rm-app-card-attributes %} 5 | {% if not app.added %} 6 | onclick="window.location.href = '{{ url }}'" 7 | {% endif %} 8 | {% endblock rm-app-card-attributes %} 9 | 10 | {% block rm-app-card-right %} 11 | 30 | {% endblock rm-app-card-right %} 31 | -------------------------------------------------------------------------------- /repomaker/templates/repomaker/widgets/pagination.html: -------------------------------------------------------------------------------- 1 | {% if is_paginated %} 2 |
    3 | {% if page_obj.has_previous %} 4 | 5 | arrow_back 6 | 7 | {% endif %} 8 | 9 | {% for page in paginator.page_range %} 10 | {% if page == page_obj.number %} 11 | {{ page }} 12 | {% else %} 13 | {{ page }} 14 | {% endif %} 15 | {% endfor %} 16 | 17 | {% if page_obj.has_next %} 18 | 19 | arrow_forward 20 | 21 | {% endif %} 22 |
    23 | 26 | {% endif %} 27 | -------------------------------------------------------------------------------- /repomaker/templates/repomaker/widgets/site_notice.html: -------------------------------------------------------------------------------- 1 | {% load site_notice %} 2 | 3 | {% notice as site_notice %} 4 | {% if site_notice %} 5 |
    6 | {% autoescape off %}{{ site_notice }}{% endautoescape %} 7 |
    8 | {% endif %} 9 | -------------------------------------------------------------------------------- /repomaker/templatetags/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/f-droid/repomaker/2d9e6a17eb7cca6fe3f7405a98109be5320f7f00/repomaker/templatetags/__init__.py -------------------------------------------------------------------------------- /repomaker/templatetags/site_notice.py: -------------------------------------------------------------------------------- 1 | from django import template 2 | from django.conf import settings 3 | 4 | register = template.Library() 5 | 6 | 7 | @register.simple_tag 8 | # noinspection PyUnusedLocal 9 | def notice(): 10 | """ 11 | Returns a site notice if SITE_NOTICE is defined in the settings. 12 | """ 13 | if hasattr(settings, 'SITE_NOTICE'): 14 | return settings.SITE_NOTICE 15 | return None 16 | -------------------------------------------------------------------------------- /repomaker/tests/__init__.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import shutil 3 | from shutil import copyfile 4 | 5 | import os 6 | from django.conf import settings 7 | from django.contrib.auth.models import User 8 | from django.test import TestCase 9 | from fdroidserver import update 10 | 11 | from repomaker import DEFAULT_USER_NAME 12 | from repomaker.models import Repository 13 | 14 | 15 | def datetime_is_recent(dt, seconds=10 * 60): 16 | now = datetime.datetime.utcnow().timestamp() 17 | return now - seconds < dt.timestamp() < now 18 | 19 | 20 | def fake_repo_create(repo): 21 | # copy existing keystore 22 | src = os.path.join(settings.TEST_FILES_DIR, 'keystore.jks') 23 | dest = os.path.join(repo.get_private_path(), 'keystore.jks') 24 | if not os.path.isdir(repo.get_private_path()): 25 | os.makedirs(repo.get_private_path()) 26 | copyfile(src, dest) 27 | 28 | repo.key_store_pass = 'uGrqvkPLiGptUScrAHsVAyNSQqyJq4OQJSiN1YZWxes=' 29 | repo.key_pass = 'uGrqvkPLiGptUScrAHsVAyNSQqyJq4OQJSiN1YZWxes=' 30 | 31 | # make sure that icon directories exist 32 | for icon_dir in update.get_all_icon_dirs(repo.get_repo_path()): 33 | if not os.path.exists(icon_dir): 34 | os.makedirs(icon_dir) 35 | 36 | 37 | class RmTestCase(TestCase): 38 | user = None 39 | repo = None 40 | 41 | def setUp(self): 42 | if not settings.SINGLE_USER_MODE: 43 | self.user = User.objects.create(username=DEFAULT_USER_NAME) 44 | self.client.force_login(user=self.user) 45 | else: 46 | self.user = User.objects.get() 47 | 48 | self.repo = Repository.objects.create( 49 | name="Test Name", 50 | description="Test Description", 51 | url="https://example.org", 52 | fingerprint="foongerprint", 53 | user=self.user, 54 | ) 55 | self.repo.chdir() 56 | 57 | def tearDown(self): 58 | if os.path.isdir(settings.TEST_DIR): 59 | shutil.rmtree(settings.TEST_DIR) 60 | -------------------------------------------------------------------------------- /repomaker/tests/models/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/f-droid/repomaker/2d9e6a17eb7cca6fe3f7405a98109be5320f7f00/repomaker/tests/models/__init__.py -------------------------------------------------------------------------------- /repomaker/tests/models/test_category.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | from repomaker.migrations.default_categories import DEFAULT_CATEGORIES 4 | from repomaker.models import Category 5 | 6 | 7 | class CategoryTestCase(TestCase): 8 | 9 | def test_pre_install(self): 10 | categories = Category.objects.all() 11 | self.assertEqual(len(DEFAULT_CATEGORIES), len(categories)) 12 | for c in categories: 13 | self.assertTrue(c.name in DEFAULT_CATEGORIES) 14 | 15 | def test_str(self): 16 | category = Category.objects.get(name=DEFAULT_CATEGORIES[0]) 17 | self.assertEqual(category.name, str(category)) 18 | -------------------------------------------------------------------------------- /repomaker/tests/test_urls.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.models import User 2 | from django.urls import reverse 3 | 4 | import repomaker.storage 5 | from repomaker.models import Repository, RemoteRepository, App, Category, Screenshot, Apk, \ 6 | ApkPointer, GitStorage 7 | from repomaker.tests import RmTestCase 8 | from repomaker.urls import urlpatterns 9 | 10 | IGNORE = ['js_reverse', 'javascript-catalog'] 11 | 12 | 13 | class UrlsTest(RmTestCase): 14 | test_user = None 15 | test_repo = None 16 | remote_repo = None 17 | category = None 18 | app = None 19 | screenshot = None 20 | apk = None 21 | apk_pointer = None 22 | storage = None 23 | 24 | def setUp(self): 25 | super().setUp() 26 | self.test_user = User.objects.create(username="testuser") 27 | self.test_repo = Repository.objects.create( 28 | name="Test Repo", 29 | description="This repo belongs to testuser, but another user is logged in.", 30 | url="https://example.org", 31 | user=self.test_user, 32 | ) 33 | self.remote_repo = RemoteRepository.objects.get(pk=1) 34 | self.remote_repo.users.set([self.test_user]) 35 | self.remote_repo.save() 36 | self.category = Category.objects.create(user=self.test_user, name="TestCat") 37 | self.app = App.objects.create(repo=self.test_repo, package_id='org.example', name="App") 38 | self.screenshot = Screenshot.objects.create(app=self.app) 39 | self.apk = Apk.objects.create(package_id='org.example') 40 | self.apk_pointer = ApkPointer.objects.create(apk=self.apk, repo=self.test_repo, 41 | app=self.app) 42 | self.storage = GitStorage.objects.create(repo=self.test_repo) 43 | 44 | def test_authentication(self): 45 | for url in urlpatterns: 46 | if not hasattr(url, 'name'): 47 | continue 48 | if url.name in IGNORE: 49 | continue 50 | 51 | keys = url.pattern.regex.groupindex.keys() 52 | params = {} 53 | expectation = 403 54 | 55 | # Set URL parameters 56 | if 'repo_id' in keys: 57 | params['repo_id'] = self.test_repo.pk 58 | if 'remote_repo_id' in keys: 59 | params['remote_repo_id'] = self.remote_repo.pk 60 | if 'lang' in keys: 61 | params['lang'] = 'en-US' 62 | if 'category_id' in keys: 63 | params['category_id'] = self.category.pk 64 | if 'app_id' in keys: 65 | params['app_id'] = self.app.pk 66 | if 's_id' in keys: 67 | params['s_id'] = self.screenshot.pk 68 | if 'pk' in keys: 69 | params['pk'] = self.apk_pointer.pk 70 | if 'path' in keys: 71 | params['path'] = repomaker.storage.get_repo_root_path(self.test_repo) 72 | 73 | # Adapt HTTP status code expectations different from 403 Forbidden 74 | if 'index' == url.name: 75 | expectation = 200 # showing an index is always possible 76 | elif 'add_repo' == url.name or 'add_remote_repo' == url.name: 77 | expectation = 200 # adding a new (remote) repo is always possible 78 | elif 'app' == url.name or 'app_edit' == url.name: 79 | # expectation = 404 # apps are bound to repo and return 404 when not found there 80 | # XXX unless I'm reading this wrong, it's requesting an app that is in the repo, 81 | # so it shouldn't 404? 82 | expectation = 403 83 | 84 | resolved_url = reverse(url.name, kwargs=params) 85 | print("%(url)s should return %(status_code)d" % {'url': resolved_url, 86 | 'status_code': expectation}) 87 | 88 | response = self.client.get(resolved_url) 89 | self.assertEqual(expectation, response.status_code) 90 | -------------------------------------------------------------------------------- /repomaker/tests/test_utils.py: -------------------------------------------------------------------------------- 1 | import bleach 2 | from unittest import TestCase 3 | 4 | from repomaker.utils import clean 5 | 6 | 7 | class UtilsTest(TestCase): 8 | 9 | def test_clean_empty_link(self): 10 | if bleach.__version__ == '3.2.1': 11 | self.skipTest('skipping since bleach v3.2.1 does not clean this properly') 12 | string = 'Link Orbot not supported' 13 | self.assertEqual('Link Orbot not supported', clean(string)) 14 | 15 | def test_clean_only_empty_link(self): 16 | string = 'Link Orbot is supported' 17 | self.assertEqual(string, clean(string)) 18 | -------------------------------------------------------------------------------- /repomaker/tests/views/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/f-droid/repomaker/2d9e6a17eb7cca6fe3f7405a98109be5320f7f00/repomaker/tests/views/__init__.py -------------------------------------------------------------------------------- /repomaker/tests/views/test_misc.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.contrib.auth.models import User 3 | from django.test import TestCase, override_settings 4 | from repomaker import DEFAULT_USER_NAME 5 | from repomaker.tests import RmTestCase 6 | 7 | 8 | class LoginSingleUserTest(TestCase): 9 | 10 | def test_login(self): 11 | if not settings.SINGLE_USER_MODE: 12 | return 13 | 14 | response = self.client.get('/') 15 | self.assertEqual(200, response.status_code) 16 | self.assertTemplateUsed(response, 'repomaker/index.html') 17 | 18 | # verify that default user was logged in automatically 19 | user = User.objects.get(username=DEFAULT_USER_NAME) 20 | self.assertEqual(str(user.pk), self.client.session['_auth_user_id']) 21 | self.assertEqual(user, response.context['request'].user) 22 | 23 | 24 | class LoginMultiUserTest(TestCase): 25 | 26 | def test_login_redirect(self): 27 | if settings.SINGLE_USER_MODE: 28 | return 29 | 30 | response = self.client.get('/') 31 | self.assertRedirects(response, '/accounts/login/?next=/') 32 | 33 | def test_login_get(self): 34 | if settings.SINGLE_USER_MODE: 35 | return 36 | 37 | response = self.client.get('/accounts/login/?next=/') 38 | self.assertEqual(200, response.status_code) 39 | self.assertTemplateUsed(response, 'account/login.html') 40 | self.assertTemplateUsed(response, 'account/login_form.html') 41 | 42 | def test_login(self): 43 | if settings.SINGLE_USER_MODE: 44 | return 45 | 46 | # create a new user to log in with 47 | User.objects.create_user('user2', 'user2@example.org', 'pass') 48 | 49 | # post user credentials into login form 50 | query = {'login': 'user2', 'password': 'pass', 'next': '/'} 51 | response = self.client.post('/accounts/login/', query) 52 | self.assertRedirects(response, '/') 53 | 54 | # assert that user was logged in properly 55 | user = User.objects.get(username='user2') 56 | self.assertEqual(str(user.pk), self.client.session['_auth_user_id']) 57 | 58 | 59 | class MiscTest(RmTestCase): 60 | 61 | @override_settings(SITE_NOTICE='test site notice') 62 | def test_site_notice(self): 63 | # Issue a GET request 64 | response = self.client.get('/') 65 | self.assertEqual(200, response.status_code) 66 | 67 | # Check that the rendered context contains the site_notice 68 | self.assertTrue('site_notice' in response.context) 69 | self.assertEqual(settings.SITE_NOTICE, response.context['site_notice']) 70 | self.assertContains(response, settings.SITE_NOTICE) 71 | 72 | def test_site_notice_only_when_set(self): 73 | # Issue a GET request 74 | response = self.client.get('/') 75 | self.assertEqual(200, response.status_code) 76 | 77 | # Check that the rendered context contains a None site_notice 78 | self.assertTrue('site_notice' in response.context) 79 | self.assertIsNone(response.context['site_notice']) 80 | -------------------------------------------------------------------------------- /repomaker/tests/views/test_screenshot.py: -------------------------------------------------------------------------------- 1 | from django.urls import reverse 2 | from repomaker.models import App, Screenshot 3 | 4 | from .. import RmTestCase 5 | 6 | 7 | class ScreenshotViewTestCase(RmTestCase): 8 | 9 | def setUp(self): 10 | super().setUp() 11 | 12 | self.app = App.objects.create( 13 | repo=self.repo, 14 | package_id='org.example', 15 | name='AppName', 16 | ) 17 | self.app.default_translate() 18 | self.app.save() 19 | 20 | def test_delete_screenshot(self): 21 | screenshot = Screenshot.objects.create(app=self.app, file='test.png') 22 | 23 | # request screenshot deletion confirmation page 24 | kwargs = {'repo_id': self.repo.id, 'app_id': self.app.id, 's_id': screenshot.id} 25 | response = self.client.get(reverse('screenshot_delete', kwargs=kwargs)) 26 | 27 | # assert that it contains the relevant information 28 | self.assertContains(response, self.app.name) 29 | self.assertContains(response, screenshot.file.url) 30 | 31 | # request the screenshot to be deleted 32 | response = self.client.post(reverse('screenshot_delete', kwargs=kwargs)) 33 | self.assertRedirects(response, self.app.get_edit_url()) 34 | 35 | # assert that the pointer got deleted 36 | self.assertEqual(0, Screenshot.objects.all().count()) 37 | -------------------------------------------------------------------------------- /repomaker/translation.py: -------------------------------------------------------------------------------- 1 | from modeltranslation.translator import register, TranslationOptions 2 | 3 | from repomaker.models.app import AbstractApp, App 4 | from repomaker.models.remoteapp import RemoteApp 5 | 6 | 7 | @register(AbstractApp) 8 | class AbstractAppTranslationOptions(TranslationOptions): 9 | fields = ('summary', 'description') 10 | 11 | 12 | @register(App) 13 | class AppTranslationOptions(TranslationOptions): 14 | fields = ('feature_graphic', 'high_res_icon', 'tv_banner') 15 | 16 | 17 | @register(RemoteApp) 18 | class RemoteAppTranslationOptions(TranslationOptions): 19 | fields = ( 20 | 'feature_graphic_url', 'feature_graphic_etag', 'high_res_icon_url', 21 | 'high_res_icon_etag', 'tv_banner_url', 'tv_banner_etag') 22 | -------------------------------------------------------------------------------- /repomaker/utils.py: -------------------------------------------------------------------------------- 1 | import bleach 2 | from bleach.sanitizer import Cleaner 3 | from html5lib.filters.base import Filter 4 | 5 | 6 | class EmptyLinkFilter(Filter): 7 | def __iter__(self): 8 | remove_end_tag = False 9 | for token in Filter.__iter__(self): 10 | # only check anchor tags 11 | if 'name' in token and token['name'] == 'a' and token['type'] in ['StartTag', 'EndTag']: 12 | if token['type'] == 'StartTag' and token['data'] == {}: 13 | remove_end_tag = True 14 | continue 15 | elif token['type'] == 'EndTag' and remove_end_tag: 16 | remove_end_tag = False 17 | continue 18 | yield token 19 | 20 | 21 | def clean(text): 22 | cleaner = Cleaner( 23 | tags=bleach.sanitizer.ALLOWED_TAGS + ['p', 'br'], 24 | attributes=bleach.sanitizer.ALLOWED_ATTRIBUTES, 25 | styles=bleach.sanitizer.ALLOWED_STYLES, 26 | protocols=bleach.sanitizer.ALLOWED_PROTOCOLS, 27 | strip=True, 28 | strip_comments=True, 29 | filters=[EmptyLinkFilter], 30 | ) 31 | return cleaner.clean(text) 32 | 33 | 34 | def to_universal_language_code(language): 35 | """ 36 | Turns a lower-cased Django language name (en-us) into a universal language code 37 | with the region part upper-cased (en-US). 38 | """ 39 | p = language.find('-') 40 | if p >= 0: 41 | if len(language[p + 1:]) > 2: 42 | return language[:p].lower() + '-' + language[p + 1].upper() + language[p + 2:].lower() 43 | return language[:p].lower() + '-' + language[p + 1:].upper() 44 | else: 45 | return language 46 | -------------------------------------------------------------------------------- /repomaker/views/apk.py: -------------------------------------------------------------------------------- 1 | from django.forms import FileField, ClearableFileInput 2 | from django.http import HttpResponseRedirect, HttpResponseNotFound 3 | from django.urls import reverse_lazy 4 | from django.views.generic.edit import UpdateView, DeleteView 5 | 6 | from repomaker.models import Apk, ApkPointer 7 | from . import BaseModelForm 8 | from .repository import RepositoryAuthorizationMixin, ApkUploadMixin 9 | 10 | 11 | class ApkForm(BaseModelForm): 12 | apks = FileField(required=False, widget=ClearableFileInput(attrs={'multiple': True})) 13 | 14 | class Meta: 15 | model = Apk 16 | fields = ['apks'] 17 | 18 | 19 | class ApkUploadView(ApkUploadMixin, UpdateView): 20 | object = None 21 | form_class = ApkForm 22 | template_name = "repomaker/error.html" 23 | 24 | def get(self, request, *args, **kwargs): 25 | # don't answer GET requests 26 | return HttpResponseNotFound() 27 | 28 | def post(self, request, *args, **kwargs): 29 | form = self.get_form() 30 | if not form.is_valid(): 31 | return self.form_invalid(form) 32 | 33 | # add posted APKs 34 | added_apks = self.add_apks() 35 | if len(added_apks['failed']) > 0: 36 | form.add_error('apks', self.get_error_msg(added_apks['failed'])) 37 | return super(ApkUploadView, self).form_invalid(form) 38 | 39 | # don't let the View create anything as we already did 40 | return HttpResponseRedirect(self.get_success_url()) 41 | 42 | def get_success_url(self): 43 | return reverse_lazy('repo', args=[self.get_repo().pk]) 44 | 45 | 46 | class ApkPointerDeleteView(RepositoryAuthorizationMixin, DeleteView): 47 | model = ApkPointer 48 | template_name = 'repomaker/app/apk_delete.html' 49 | pk_url_kwarg = 'pk' 50 | 51 | def get_repo(self): 52 | return self.get_object().app.repo 53 | 54 | def get_success_url(self): 55 | self.get_repo().update_async() 56 | return self.get_object().app.get_edit_url() 57 | -------------------------------------------------------------------------------- /repomaker/views/gitstorage.py: -------------------------------------------------------------------------------- 1 | import fdroidserver.index 2 | from django.core.exceptions import ValidationError 3 | from django.core.validators import URLValidator 4 | from django.forms import CharField, URLField, TextInput 5 | from django.utils.translation import ugettext_lazy as _ 6 | from repomaker.models.storage import GitStorage, HostnameValidator, PathValidator 7 | 8 | from .sshstorage import SshStorageForm, SshKeyMixin 9 | from .storage import StorageCreateView, StorageUpdateView, StorageDeleteView, StorageDetailView 10 | 11 | 12 | class GitStorageForm(SshStorageForm): 13 | ssh_url = CharField(required=True, label=_('SSH'), 14 | widget=TextInput(attrs={'placeholder': _('Enter SSH URL')})) 15 | url = URLField(required=False, label=_('Raw Git URL'), help_text=_( 16 | 'This is the location where the uploaded files can be accessed from' + 17 | ' in raw form.' + 18 | ' Leave empty for GitHub or GitLab.com repositories.')) 19 | 20 | def get_initial_for_field(self, field, field_name): 21 | if field_name == 'ssh_url' and self.instance.pk: 22 | return self.instance.get_remote_url() 23 | return super(GitStorageForm, self).get_initial_for_field(field, field_name) 24 | 25 | class Meta(SshStorageForm.Meta): 26 | model = GitStorage 27 | fields = ['ssh_url', 'url'] 28 | 29 | 30 | class GitUrlValidationMixin(SshKeyMixin): 31 | 32 | def form_valid(self, form): 33 | # check that ssh_url starts with git@ 34 | if not form.cleaned_data['ssh_url'].startswith('git@'): 35 | form.add_error('ssh_url', _("This URL must start with 'git@'.")) 36 | return self.form_invalid(form) 37 | 38 | # strip the standard git user name from the URL 39 | ssh_url = form.cleaned_data['ssh_url'].replace('git@', '') 40 | 41 | # check that ssh_url ends with .git 42 | if not ssh_url.endswith('.git'): 43 | form.add_error('ssh_url', _("This URL must end with '.git'.")) 44 | return self.form_invalid(form) 45 | 46 | # strip the .git ending from the URL 47 | ssh_url = ssh_url.replace('.git', '') 48 | 49 | # check that ssh_url includes a path 50 | url_error = _("This URL is invalid. Please copy the exact SSH URL of your git repository.") 51 | if ':' not in ssh_url: 52 | form.add_error('ssh_url', url_error) 53 | return self.form_invalid(form) 54 | 55 | # extract hostname and path from URL 56 | (hostname, path) = ssh_url.split(':', 1) 57 | 58 | # validate hostname and path 59 | try: 60 | HostnameValidator().__call__(hostname) 61 | PathValidator().__call__('/' + path) 62 | except ValidationError: 63 | form.add_error('ssh_url', url_error) 64 | return self.form_invalid(form) 65 | 66 | # assign hostname and path to the GitStorage object 67 | form.instance.host = hostname 68 | form.instance.path = path 69 | 70 | # try to generate the F-Droid repo URL from the git repo URL 71 | mirror_urls = fdroidserver.index.get_mirror_service_urls(form.cleaned_data['ssh_url']) 72 | if len(mirror_urls) > 0: 73 | form.instance.url = mirror_urls[0] 74 | # URL generation failed, so the user needs to provide a valid URL 75 | else: 76 | try: 77 | URLValidator().__call__(form.cleaned_data['ssh_url']) 78 | except ValidationError: 79 | form.add_error('url', _('Please add a valid URL' + 80 | 'for the raw content of this git repository.')) 81 | return self.form_invalid(form) 82 | 83 | return super(GitUrlValidationMixin, self).form_valid(form) 84 | 85 | 86 | class GitStorageCreate(GitUrlValidationMixin, StorageCreateView): 87 | model = GitStorage 88 | form_class = GitStorageForm 89 | template_name = 'repomaker/storage/form_git.html' 90 | 91 | 92 | class GitStorageUpdate(GitUrlValidationMixin, StorageUpdateView): 93 | model = GitStorage 94 | form_class = GitStorageForm 95 | 96 | 97 | class GitStorageDetail(StorageDetailView): 98 | model = GitStorage 99 | template_name = 'repomaker/storage/detail_git.html' 100 | 101 | 102 | class GitStorageDelete(StorageDeleteView): 103 | model = GitStorage 104 | -------------------------------------------------------------------------------- /repomaker/views/s3storage.py: -------------------------------------------------------------------------------- 1 | from django.forms import PasswordInput 2 | from django.utils.translation import ugettext_lazy as _ 3 | 4 | from repomaker.models import S3Storage 5 | from .storage import StorageForm, StorageCreateView, StorageDetailView, StorageUpdateView, \ 6 | StorageDeleteView 7 | 8 | 9 | class S3StorageForm(StorageForm): 10 | 11 | class Meta(StorageForm.Meta): 12 | model = S3Storage 13 | fields = ['region', 'bucket', 'accesskeyid', 'secretkey'] 14 | labels = { 15 | 'bucket': _('Bucket Name'), 16 | 'accesskeyid': _('Access Key ID'), 17 | 'secretkey': _('Secret Access Key'), 18 | } 19 | help_texts = { 20 | 'region': _('Other regions are currently not supported.'), 21 | } 22 | widgets = { 23 | 'secretkey': PasswordInput(), 24 | } 25 | 26 | 27 | class S3StorageCreate(StorageCreateView): 28 | model = S3Storage 29 | form_class = S3StorageForm 30 | 31 | 32 | class S3StorageUpdate(StorageUpdateView): 33 | model = S3Storage 34 | form_class = S3StorageForm 35 | 36 | 37 | class S3StorageDetail(StorageDetailView): 38 | model = S3Storage 39 | template_name = 'repomaker/storage/detail_s3.html' 40 | 41 | 42 | class S3StorageDelete(StorageDeleteView): 43 | model = S3Storage 44 | -------------------------------------------------------------------------------- /repomaker/views/screenshot.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.forms import Select 3 | from django.utils.translation import ugettext_lazy as _ 4 | from django.views.generic import DeleteView 5 | 6 | from repomaker.models import Screenshot 7 | from repomaker.views.repository import RepositoryAuthorizationMixin 8 | from . import BaseModelForm 9 | 10 | 11 | class ScreenshotForm(BaseModelForm): 12 | class Meta: 13 | model = Screenshot 14 | fields = ['language_code', 'type', 'file'] 15 | labels = { 16 | 'language_code': _('Language'), 17 | 'file': _('Select Screenshot for upload'), 18 | } 19 | widgets = { 20 | 'language_code': Select(choices=settings.LANGUAGES) 21 | } 22 | 23 | 24 | class ScreenshotDeleteView(RepositoryAuthorizationMixin, DeleteView): 25 | model = Screenshot 26 | pk_url_kwarg = 's_id' 27 | template_name = 'repomaker/app/screenshot_delete.html' 28 | 29 | def get_repo(self): 30 | return self.get_object().app.repo 31 | 32 | def get_success_url(self): 33 | self.get_repo().update_async() 34 | return self.get_object().app.get_edit_url() 35 | -------------------------------------------------------------------------------- /repomaker/views/sshstorage.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.forms import BooleanField 3 | from django.utils.translation import ugettext_lazy as _ 4 | from repomaker.models import SshStorage 5 | 6 | from .storage import StorageForm, MainStorageMixin, StorageCreateView, StorageDetailView, \ 7 | StorageUpdateView, StorageDeleteView 8 | 9 | 10 | class SshStorageForm(StorageForm): 11 | if settings.SINGLE_USER_MODE: 12 | ignore_identity_file = BooleanField(required=False, initial=True, 13 | label=_('Use local default SSH Key')) 14 | 15 | def __init__(self, *args, **kwargs): 16 | super().__init__(*args, **kwargs) 17 | # don't show identity_file option when updating 18 | if settings.SINGLE_USER_MODE and self.instance.pk: 19 | del self.fields['ignore_identity_file'] 20 | 21 | def get_initial_for_field(self, field, field_name): 22 | if field_name == 'ignore_identity_file' and self.instance.pk: 23 | return not bool(self.instance.identity_file) # True if no identity file exists 24 | return super().get_initial_for_field(field, field_name) 25 | 26 | class Meta(StorageForm.Meta): 27 | model = SshStorage 28 | fields = ['username', 'host', 'path', 'url'] 29 | if settings.SINGLE_USER_MODE: 30 | fields += ['ignore_identity_file'] 31 | labels = { 32 | 'username': _('User Name'), 33 | 'url': _('URL'), 34 | } 35 | help_texts = { 36 | 'url': _('This is the location where the uploaded files can be accessed from.'), 37 | } 38 | 39 | 40 | class SshKeyMixin(MainStorageMixin): 41 | def form_valid(self, form): 42 | create_key = not settings.SINGLE_USER_MODE or ( # multi-user mode or 43 | 'ignore_identity_file' in form.cleaned_data and # in the form, 44 | not form.cleaned_data['ignore_identity_file']) # but wants a key created (file ignored) 45 | if create_key and not form.instance.identity_file: 46 | result = super(SshKeyMixin, self).form_valid(form) # validate rest of the form and save 47 | form.instance.create_identity_file() 48 | return result 49 | return super(SshKeyMixin, self).form_valid(form) 50 | 51 | 52 | class SshStorageCreate(SshKeyMixin, StorageCreateView): 53 | model = SshStorage 54 | form_class = SshStorageForm 55 | 56 | 57 | class SshStorageUpdate(SshKeyMixin, StorageUpdateView): 58 | model = SshStorage 59 | form_class = SshStorageForm 60 | 61 | 62 | class SshStorageDetail(StorageDetailView): 63 | model = SshStorage 64 | template_name = 'repomaker/storage/detail_ssh.html' 65 | 66 | 67 | class SshStorageDelete(StorageDeleteView): 68 | model = SshStorage 69 | -------------------------------------------------------------------------------- /repomaker/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for repomaker project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.10/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "repomaker.settings") 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /requirements-dev.txt: -------------------------------------------------------------------------------- 1 | pycodestyle 2 | coverage 3 | pylint 4 | pylint-django 5 | lxml 6 | cssselect 7 | -------------------------------------------------------------------------------- /requirements-gui.txt: -------------------------------------------------------------------------------- 1 | -r requirements.txt 2 | pywebview[qt5] < 3 3 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | -r debian/requirements.txt 2 | apache-libcloud>=2.0.0 3 | bleach>=2.1.4, <5.0 4 | cryptography>=1.4.0 5 | django >=2.0, <3.0 6 | django-allauth 7 | django-compressor 8 | django-modeltranslation >=0.15 9 | django-js-reverse >=0.9.0 10 | django-sass-processor 11 | fdroidserver >=2.0 12 | html5lib >= 1.1, <1.2 13 | libsass 14 | python-magic 15 | qrcode 16 | -------------------------------------------------------------------------------- /run-tests.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ./tests/test-pep8.sh && 4 | ./tests/test-pylint.sh && 5 | ./tests/test-units.sh && 6 | echo "All tests ran successfully! \o/" 7 | -------------------------------------------------------------------------------- /run.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | python3 manage.py runserver & 4 | PID=$! 5 | sleep 10 6 | python3 manage.py process_tasks 7 | pkill -P $PID 8 | kill $PID 9 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | author = F-Droid Community 3 | author_email = team@f-droid.org 4 | keywords = android, app store, repository 5 | 6 | # See https://pypi.python.org/pypi?%3Aaction=list_classifiers 7 | classifiers = 8 | Development Status :: 5 - Production/Stable 9 | Intended Audience :: Developers 10 | Intended Audience :: End Users/Desktop 11 | Environment :: Web Environment 12 | Framework :: Django 13 | License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+) 14 | Programming Language :: Python :: 3.4 15 | Programming Language :: Python :: 3.5 16 | Programming Language :: Python :: 3.6 17 | Programming Language :: Python :: 3.7 18 | Topic :: Utilities 19 | Topic :: System :: Software Distribution 20 | 21 | long_description = file: README.md 22 | description-file = README.md 23 | license_file = LICENSE 24 | 25 | 26 | # post release files with: twine upload --sign dist/repomaker-*.tar.gz 27 | [aliases] 28 | release = version_check repomaker_static_check compile_catalog sdist 29 | 30 | 31 | # All this below is for Babel config. Ideally we would only use 32 | # Babel, but it is still missing some key features that gettext gives 33 | # us. So for now, this Babel setup is just to make it easy for Python 34 | # people who are used to it. Babel is missing: 35 | # 36 | # * properly tagging various Python formats in the comments 37 | # * --add-location=file 38 | # * --join-existing 39 | # * --sort-output on update 40 | # 41 | # So for now the canonical way to update the template and translation 42 | # files is: `make -C locale` 43 | 44 | [extract_messages] 45 | keywords = _ 46 | charset = UTF-8 47 | sort_output = true 48 | no_location = true 49 | add-comments = true 50 | output_file = repomaker/locale/django.pot 51 | msgid-bugs-address = https://gitlab.com/fdroid/repomaker/issues 52 | 53 | [update_catalog] 54 | input_file = repomaker/locale/django.pot 55 | output_dir = repomaker/locale 56 | 57 | [init_catalog] 58 | input_file = repomaker/locale/django.pot 59 | output_dir = repomaker/locale 60 | 61 | [compile_catalog] 62 | domain = django 63 | directory = repomaker/locale 64 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import os 4 | import subprocess 5 | import sys 6 | 7 | from setuptools import setup, find_packages, Command 8 | from repomaker import VERSION 9 | 10 | 11 | class RepomakerStaticCheckCommand(Command): 12 | """Make sure git tag and version match before uploading""" 13 | 14 | user_options = [] 15 | 16 | def initialize_options(self): 17 | """Abstract method that is required to be overwritten""" 18 | 19 | def finalize_options(self): 20 | """Abstract method that is required to be overwritten""" 21 | 22 | def run(self): 23 | if not os.path.isdir('repomaker-static'): 24 | print('ERROR: repomaker-static is missing, run ./pre-release.sh') 25 | sys.exit(1) 26 | 27 | 28 | class VersionCheckCommand(Command): 29 | """Make sure git tag and version match before uploading""" 30 | 31 | user_options = [] 32 | 33 | def initialize_options(self): 34 | """Abstract method that is required to be overwritten""" 35 | 36 | def finalize_options(self): 37 | """Abstract method that is required to be overwritten""" 38 | 39 | def run(self): 40 | version = self.distribution.get_version() 41 | version_git = subprocess.check_output(['git', 'describe', '--tags', '--always']).rstrip().decode('utf-8') 42 | if version != version_git: 43 | print('ERROR: Release version mismatch! setup.py (%s) does not match git (%s)' 44 | % (version, version_git)) 45 | sys.exit(1) 46 | print('Upload using: twine upload --sign dist/repomaker-%s.tar.gz' % version) 47 | 48 | 49 | DATA_PREFIX = os.path.join('share', 'repomaker') 50 | 51 | packages = find_packages() 52 | print("Packages: %s" % str(packages)) 53 | 54 | with open(os.path.join(os.path.abspath(os.path.dirname(__file__)), 'README.md')) as f: 55 | long_description = f.read() 56 | 57 | setup( 58 | name='repomaker', 59 | version=VERSION, 60 | packages=packages + ['repomaker-static'], 61 | description='Create F-Droid repositories with ease', 62 | long_description=long_description, 63 | long_description_content_type='text/markdown', 64 | license='AGPL-3.0', 65 | url='https://f-droid.org/repomaker/', 66 | python_requires='>=3', 67 | cmdclass={ 68 | 'repomaker_static_check': RepomakerStaticCheckCommand, 69 | 'version_check': VersionCheckCommand, 70 | }, 71 | setup_requires=[ 72 | 'babel', 73 | ], 74 | # List run-time dependencies here. These will be installed by pip when 75 | # your project is installed. For an analysis of "install_requires" vs pip's 76 | # requirements files see: 77 | # https://packaging.python.org/en/latest/requirements.html 78 | install_requires=[ 79 | 'django >=2.0, <3.0', 80 | 'django-allauth', 81 | 'django-tinymce >=2.6.0, <3', 82 | 'django-js-reverse>=0.9.0', 83 | 'django-compressor', 84 | 'django-modeltranslation <0.17', 85 | 'django-sass-processor', 86 | 'django-background-tasks >=1.1.13, <1.2', 87 | 'bleach >=2.1.4, <5.0', 88 | 'html5lib >= 1.1, <1.2', 89 | 'python-magic', 90 | 'qrcode', 91 | 'cryptography>=1.4.0', 92 | 'fdroidserver>=2.0', 93 | ], 94 | # List additional groups of dependencies here (e.g. development dependencies). 95 | # You can install these using the following syntax, for example: 96 | # $ pip install -e .[dev,test] 97 | extras_require={ 98 | 'gui': [ 99 | 'PyQt5==5.10.0', 100 | 'pywebview[qt5] <3', 101 | ], 102 | 'test': [ 103 | 'pycodestyle', 104 | 'coverage', 105 | 'pylint-django', 106 | ], 107 | }, 108 | include_package_data=True, 109 | package_data={}, 110 | # To provide executable scripts, use entry points in preference to the 111 | # "scripts" keyword. Entry points provide cross-platform support and allow 112 | # pip to create the appropriate form of executable for the target platform. 113 | entry_points={ 114 | 'console_scripts': [ 115 | 'repomaker-server = repomaker:runserver', 116 | 'repomaker-tasks = repomaker:process_tasks', 117 | ], 118 | 'gui_scripts': [ 119 | 'repomaker = repomaker.gui:main', 120 | ], 121 | }, 122 | ) 123 | -------------------------------------------------------------------------------- /setup.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -x 3 | set -e 4 | pip3 install -r requirements.txt 5 | mkdir -p data 6 | python3 manage.py makemigrations repomaker 7 | python3 manage.py migrate 8 | npm install 9 | echo "All set up, now execute run.sh" 10 | -------------------------------------------------------------------------------- /tests/keystore.jks: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/f-droid/repomaker/2d9e6a17eb7cca6fe3f7405a98109be5320f7f00/tests/keystore.jks -------------------------------------------------------------------------------- /tests/test-pep8.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | pycodestyle --show-pep8 --max-line-length=100 --exclude=setup.py,.git,build,migrations,docker,bin,lib . 4 | -------------------------------------------------------------------------------- /tests/test-pylint.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | export DJANGO_SETTINGS_MODULE=repomaker.settings_test 4 | pylint --load-plugins=pylint_django --disable=C,R,fixme repomaker 5 | -------------------------------------------------------------------------------- /tests/test-units.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | coverage=coverage 4 | if which python3-coverage; then 5 | coverage=python3-coverage 6 | elif which coverage3; then 7 | coverage=coverage3 8 | fi 9 | 10 | $coverage run --source='repomaker' --omit="*/wsgi.py","repomaker/__init__.py" manage.py test --settings repomaker.settings_test && 11 | $coverage run --append --source='repomaker' --omit="*/wsgi.py","repomaker/__init__.py" manage.py test --settings repomaker.settings_test_multi_user && 12 | $coverage report 13 | -------------------------------------------------------------------------------- /tests/test.avi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/f-droid/repomaker/2d9e6a17eb7cca6fe3f7405a98109be5320f7f00/tests/test.avi -------------------------------------------------------------------------------- /tests/test.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/f-droid/repomaker/2d9e6a17eb7cca6fe3f7405a98109be5320f7f00/tests/test.docx -------------------------------------------------------------------------------- /tests/test.epub: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/f-droid/repomaker/2d9e6a17eb7cca6fe3f7405a98109be5320f7f00/tests/test.epub -------------------------------------------------------------------------------- /tests/test.flac: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/f-droid/repomaker/2d9e6a17eb7cca6fe3f7405a98109be5320f7f00/tests/test.flac -------------------------------------------------------------------------------- /tests/test.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/f-droid/repomaker/2d9e6a17eb7cca6fe3f7405a98109be5320f7f00/tests/test.mp3 -------------------------------------------------------------------------------- /tests/test.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/f-droid/repomaker/2d9e6a17eb7cca6fe3f7405a98109be5320f7f00/tests/test.mp4 -------------------------------------------------------------------------------- /tests/test.ods: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/f-droid/repomaker/2d9e6a17eb7cca6fe3f7405a98109be5320f7f00/tests/test.ods -------------------------------------------------------------------------------- /tests/test.odt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/f-droid/repomaker/2d9e6a17eb7cca6fe3f7405a98109be5320f7f00/tests/test.odt -------------------------------------------------------------------------------- /tests/test.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/f-droid/repomaker/2d9e6a17eb7cca6fe3f7405a98109be5320f7f00/tests/test.ogg -------------------------------------------------------------------------------- /tests/test.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/f-droid/repomaker/2d9e6a17eb7cca6fe3f7405a98109be5320f7f00/tests/test.pdf -------------------------------------------------------------------------------- /tests/test.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/f-droid/repomaker/2d9e6a17eb7cca6fe3f7405a98109be5320f7f00/tests/test.png -------------------------------------------------------------------------------- /tests/test_1.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/f-droid/repomaker/2d9e6a17eb7cca6fe3f7405a98109be5320f7f00/tests/test_1.apk -------------------------------------------------------------------------------- /tests/test_2.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/f-droid/repomaker/2d9e6a17eb7cca6fe3f7405a98109be5320f7f00/tests/test_2.apk -------------------------------------------------------------------------------- /tests/test_3.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/f-droid/repomaker/2d9e6a17eb7cca6fe3f7405a98109be5320f7f00/tests/test_3.apk -------------------------------------------------------------------------------- /tests/test_invalid_signature.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/f-droid/repomaker/2d9e6a17eb7cca6fe3f7405a98109be5320f7f00/tests/test_invalid_signature.apk -------------------------------------------------------------------------------- /tests/test_md5_signature.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/f-droid/repomaker/2d9e6a17eb7cca6fe3f7405a98109be5320f7f00/tests/test_md5_signature.apk -------------------------------------------------------------------------------- /update-translations.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | python3 manage.py makemessages --keep-pot --no-wrap --no-location --no-obsolete --ignore node_modules -v 3 4 | python3 manage.py makemessages --keep-pot --no-wrap --no-location --no-obsolete --ignore node_modules --ignore data -v 3 -d djangojs 5 | 6 | sed -i -e '/^"POT-Creation-Date: /d' repomaker/locale/*/LC_MESSAGES/django.po 7 | sed -i -e '/^"POT-Creation-Date: /d' repomaker/locale/*/LC_MESSAGES/djangojs.po 8 | 9 | --------------------------------------------------------------------------------