From ba3eef434191d841945fe13f1384d742820a3295 Mon Sep 17 00:00:00 2001 From: Giacomo Leidi Date: Sun, 31 Oct 2021 01:55:25 +0200 Subject: [PATCH] Docker image (#84) * Add [guix](https://guix.gnu.org/) package. This enables: - [direnv](https://direnv.net/) integration to setup and tear down a suitable development environment; - if you're not a direnv user you can always `guix environment -l guix.scm` to spawn a shell with all the necessary dependencies; - Export of Mobilizon Reshare and its dependencies to one of the formats supported by `guix pack`. Right now they are: + tarball Self-contained tarball, ready to run on another machine + squashfs Squashfs image suitable for Singularity + docker Docker image ready for 'docker load' + deb Debian archive installable via dpkg/apt * Add docker image and docker-compose.yml. * Add Github CI workflow. --- .envrc | 35 ++ .github/workflows/main.yml | 67 ++++ .gitignore | 5 + channels-lock.scm | 11 + doc/development-environment-with-guix.md | 118 ++++++ docker-compose.yml | 10 + docker/image.scm | 60 +++ docker/mobilizon-reshare.scm | 401 +++++++++++++++++++ docker/service.scm | 52 +++ guix.scm | 3 + manifest.scm | 18 + patches/dynaconf-Unvendor-dependencies.patch | 180 +++++++++ scripts/build_docker_image.sh | 4 + scripts/release.sh | 8 +- 14 files changed, 970 insertions(+), 2 deletions(-) create mode 100644 .envrc create mode 100644 .github/workflows/main.yml create mode 100644 channels-lock.scm create mode 100644 doc/development-environment-with-guix.md create mode 100644 docker-compose.yml create mode 100644 docker/image.scm create mode 100644 docker/mobilizon-reshare.scm create mode 100644 docker/service.scm create mode 100644 guix.scm create mode 100644 manifest.scm create mode 100644 patches/dynaconf-Unvendor-dependencies.patch create mode 100755 scripts/build_docker_image.sh diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..0ad7aff --- /dev/null +++ b/.envrc @@ -0,0 +1,35 @@ +if command -v guix; then + + eval "$(guix time-machine -C channels-lock.scm -- shell -r .guix-root -D -f guix.scm -m manifest.scm --pure --search-paths -L . -L ./patches)" + + # Add development scripts to PATH + export PATH="$(pwd)/scripts:${PATH}" + + venv_dir=".venv" + + if [ ! -e "$venv_dir/bin/python" ] ; then + rm -rvf "$venv_dir" + pre-commit uninstall + fi + if [ ! -d "$venv_dir" ] ; then + virtualenv -p `which python3.9` "$venv_dir" + poetry install + pre-commit install + fi + + clear + git-cal --author="$(git config user.name)" + + run-tests () { + poetry run pytest + } + export_function run-tests + cat << EOF + +run-tests Runs pytest in the current directory + +The 'scripts' directory has been added to your PATH: you can now invoke scripts without typing the relative path. + +EOF + +fi diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..4382547 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,67 @@ +# This is a basic workflow to help you get started with Actions + +name: CI + +# Controls when the workflow will run +on: + push: + # Sequence of patterns matched against refs/tags + tags: + - v* + + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +# A workflow run is made up of one or more jobs that can run sequentially or in parallel +jobs: + # This workflow contains a single job called "build" + build: + # The type of runner that the job will run on + runs-on: ubuntu-latest + + # Steps represent a sequence of tasks that will be executed as part of the job + steps: + # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it + - uses: actions/checkout@v2 + + + # Runs a single command using the runners shell + - name: Install GNU Guix + uses: PromyLOPh/guix-install-action@v1 + + # Runs a set of commands using the runners shell + - name: Build image + run: scripts/build_docker_image.sh + - name: Upload pack (Docker) + uses: actions/upload-artifact@v2 + with: + name: mobilizon-reshare-docker + path: docker-image.tar.gz + publish: + # The type of runner that the job will run on + runs-on: ubuntu-latest + needs: build + # Steps represent a sequence of tasks that will be executed as part of the job + steps: + # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it + - uses: actions/checkout@v2 + + - name: Get release tag + id: vars + run: echo ::set-output name=tag::${GITHUB_REF#refs/*/} + + - name: Download image + uses: actions/download-artifact@v2 + with: + name: mobilizon-reshare-docker + + - name: Publish to Docker Hub + uses: fishinthecalculator/publish-docker-image-action@v0.1.3 + env: + IMAGE_TAG: ${{ steps.vars.outputs.tag }} + with: + name: fishinthecalculator/mobilizon-reshare + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + image: docker-image.tar.gz diff --git a/.gitignore b/.gitignore index f4eff58..e5043ef 100644 --- a/.gitignore +++ b/.gitignore @@ -176,3 +176,8 @@ crashlytics-build.properties fabric.properties .idea */local_testing.toml +.direnv/ +etc/ +var/ +docker-image.tar.gz +.guix-root diff --git a/channels-lock.scm b/channels-lock.scm new file mode 100644 index 0000000..6baed1d --- /dev/null +++ b/channels-lock.scm @@ -0,0 +1,11 @@ +(list + (channel + (name 'guix) + (url "https://git.savannah.gnu.org/git/guix.git") + (commit + "de3bf035b4e42474355af3a4b110b54835098ac4") + (introduction + (make-channel-introduction + "9edb3f66fd807b096b48283debdcddccfea34bad" + (openpgp-fingerprint + "BBB0 2DDF 2CEA F6A8 0D1D E643 A2A0 6DF2 A33A 54FA"))))) diff --git a/doc/development-environment-with-guix.md b/doc/development-environment-with-guix.md new file mode 100644 index 0000000..6ec2d5f --- /dev/null +++ b/doc/development-environment-with-guix.md @@ -0,0 +1,118 @@ +# Hacking with Guix on Mobilizon Reshare +To setup a development environment to hack on `mobilizon-reshare` you can use [Guix](https://guix.gnu.org/) and [direnv](https://direnv.net/). + +If you already have `guix` and `direnv` installed on your system, the development environment setup is as easy as: + +```shell +$ git clone https://github.com/Tech-Workers-Coalition-Italia/mobilizon-reshare +$ cd mobilizon-reshare/ +direnv: error .envrc is blocked. Run `direnv allow` to approve its content. +$ direnv allow +direnv: loading .envrc + +[...] + +direnv: export +CPLUS_INCLUDE_PATH +C_INCLUDE_PATH +LIBRARY_PATH ~GUIX_LOCPATH ~PATH ~PYTHONPATH +$ +``` + +Hurray 🎉 ! Now you can hack on `mobilizon-reshare` without worrying about dependencies. + +## Installing Guix + +*Caveat:* Guix currently runs only on Linux, if you run a different OS you're probably better off with something like [poetry](https://python-poetry.org/). Just beware that you may end up with slightly different behavior, since `poetry` only locks Python dependencies. + +### Debian Bullseye + +If you run Debian Bullseye (or one of its derivatives) installing Guix is achieved with: + +```shell +$ sudo apt install guix +``` + +If you want to find out if your distribution is a derivative of Debian Bullseye you can run: + +```shell +$ sudo cat /etc/debian_release +``` + +### Arch Linux + +The Arch Wiki has a very good [article](https://wiki.archlinux.org/title/Guix). + +### Other distributions + +For every other distributions you can install Guix with the installer script. It will guide you through the process of installing Guix. + +```shell +$ curl https://git.savannah.gnu.org/cgit/guix.git/plain/etc/guix-install.sh | sudo bash +``` + +Beware that piping to `sudo bash` is usually a *very* bad idea. Before running the above command please read the script and the Guix manual. + +## Configuring Guix + +To make Guix applications work out of the box you should add the following variables to your `.bash_profile` (or its equivalent for shells other than Bash): + +```shell +GUIX_PROFILE="${HOME}/.guix-profile" +. "$GUIX_PROFILE/etc/profile" + +export GUIX_LOCPATH="$GUIX_PROFILE/lib/locale" +export SSL_CERT_DIR="$GUIX_PROFILE/etc/ssl/certs" +export SSL_CERT_FILE="$GUIX_PROFILE/etc/ssl/certs/ca-certificates.crt" +export GIT_SSL_CAINFO="$SSL_CERT_FILE" +export CURL_CA_BUNDLE="$SSL_CERT_FILE" +export INFOPATH="$GUIX_PROFILE${INFOPATH:+:}${INFOPATH}" +export MANPATH="$GUIX_PROFILE${MANPATH:+:}${MANPATH}" + +GUIX_PROFILE="$XDG_CONFIG_HOME/guix/current" +. "$GUIX_PROFILE/etc/profile" +``` + +and then run **in a new shell** + +```shell +$ guix install nss-certs +$ sudo -i guix install glibc-locales +``` + +## Installing direnv + +Once you have Guix properly setup, you can install `direnv` with: + +```shell +$ guix install direnv +``` + +then you should [hook it](https://direnv.net/docs/hook.html) into your shell. + +## Troubleshooting + +Guix sometimes prints somewhat scary messages like: + +```shell +$ guix install hello +The following package will be installed: + hello 2.10 + +The following derivation will be built: + /gnu/store/15s9gs89i6bf16skwb1c03bm4wj9h30a-profile.drv + +building CA certificate bundle... +listing Emacs sub-directories... +building fonts directory... +building directory of Info manuals... +building database for manual pages... +building profile with 1 package... +hint: Consider setting the necessary environment variables by running: + + GUIX_PROFILE="~/.guix-profile/hello-profile" + . "$GUIX_PROFILE/etc/profile" + +Alternately, see `guix package --search-paths'. + +$ +``` + +when you see a message like that you can either run it to make the current shell work with the new packages installed by Guix or just close the current shell and spawn another, this way it'll put Guix packages in the right plache in your `PATH`. diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..960b553 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,10 @@ +version: "3.7" +services: + mobilizon-reshare: + image: fishinthecalculator/mobilizon-reshare:latest + environment: + SECRETS_FOR_DYNACONF: /etc/xdg/mobilizon-reshare/0.1.0/.secrets.toml + ENV_FOR_DYNACONF: production + volumes: + - ./etc:/etc/xdg/mobilizon-reshare/0.1.0 + - ./var:/var/lib/mobilizon-reshare diff --git a/docker/image.scm b/docker/image.scm new file mode 100644 index 0000000..a74c348 --- /dev/null +++ b/docker/image.scm @@ -0,0 +1,60 @@ +(define-module (docker image) + #:use-module (gnu) + #:use-module (gnu packages admin) ;; for shadow + #:use-module (gnu packages base) ;; for coreutils + #:use-module (gnu packages bash) ;; for bash + #:use-module (gnu packages gawk) ;; for gawk + #:use-module (gnu packages less) ;; for less + #:use-module (guix gexp) ;; for #$ and #~ + #:use-module (docker mobilizon-reshare) ;; for mobilizon-reshare.git + #:use-module (docker service) ;; for mobilizon-reshare-service-type + #:use-module (gnu services base) ;; for special-file-service-type + #:use-module (gnu services mcron)) ;; for mcron + +(define mobilizon-reshare-job + ;; Run mobilizon-reshare every 15th minute. + #~(job "*/15 * * * *" + (string-append #$mobilizon-reshare.git "/bin/mobilizon-reshare start") + "mobilizon-reshare-start" + #:user "mobilizon-reshare")) + +(define mobilizon-reshare-docker-image + (operating-system + (locale "it_IT.utf8") + (timezone "Europe/Rome") + (keyboard-layout + (keyboard-layout "it" "nodeadkeys")) + + (bootloader + (bootloader-configuration + (bootloader grub-bootloader))) + + (file-systems + (list + (file-system + (mount-point "/") + (device "/dev/fake") + (type "ext4")))) + + (host-name "mobilizon-reshare-scheduler") + + (packages + (list + coreutils + findutils + less + grep + gawk + sed)) + + (services + (list + (service mobilizon-reshare-service-type) + (service special-files-service-type + `(("/bin/sh" ,(file-append bash "/bin/bash")))) + (service mcron-service-type) + (simple-service 'mobilizon-reshare-cron-jobs + mcron-service-type + (list mobilizon-reshare-job)))))) + +mobilizon-reshare-docker-image diff --git a/docker/mobilizon-reshare.scm b/docker/mobilizon-reshare.scm new file mode 100644 index 0000000..4679ef3 --- /dev/null +++ b/docker/mobilizon-reshare.scm @@ -0,0 +1,401 @@ +(define-module (docker mobilizon-reshare) + #:use-module (guix download) + #:use-module (guix gexp) + #:use-module (guix git-download) + #:use-module (guix packages) + #:use-module (guix utils) + #:use-module ((guix licenses) #:prefix license:) + #:use-module (guix build-system python) + #:use-module (gnu packages) + #:use-module (gnu packages check) + #:use-module (gnu packages databases) + #:use-module (gnu packages django) + #:use-module (gnu packages python) + #:use-module (gnu packages python-build) + #:use-module (gnu packages python-check) + #:use-module (gnu packages python-web) + #:use-module (gnu packages python-xyz) + #:use-module (gnu packages serialization) + #:use-module (gnu packages time) + #:use-module (gnu packages web) + #:use-module (ice-9 popen) + #:use-module (ice-9 rdelim) + #:use-module (srfi srfi-1)) + +(define %source-dir (getcwd)) + +(define coopyleft + (let ((license (@@ (guix licenses) license))) + (license "Coopyleft" + "https://wiki.coopcycle.org/en:license" + "Coopyleft License"))) + +(define wrap-python3 + (@@ (gnu packages python) wrap-python3)) + +(define-public python-3.9-wrapper + (wrap-python3 python-3.9)) + +;; TODO: This should probably be upstreamed. +(define-public python-dotenv + (package + (name "python-dotenv") + (version "0.17.0") + (source + (origin + (method url-fetch) + (uri (pypi-uri "python-dotenv" version)) + (sha256 + (base32 + "0jjg7b8073gxsmh47ic152xdxym5zhw887ilh0ddl45gl0nph6s7")))) + (build-system python-build-system) + (propagated-inputs + `(("python-click" ,python-click))) + (native-inputs + `(("python-mock" ,python-mock) + ("python-pytest" ,python-pytest) + ("python-sh" ,python-sh))) + (home-page + "https://github.com/theskumar/python-dotenv") + (synopsis + "Setup environment variables according to .env files") + (description + "This package provides the @code{python-dotenv} Python module to +read key-value pairs from a .env file and set them as environment variables") + (license license:bsd-3))) + +;; TODO: This should probably be upstreamed. +;; This is only for python-dynaconf. +(define-public python-dotenv-0.13.0 + (package (inherit python-dotenv) + (name "python-dotenv") + (version "0.13.0") + (source + (origin + (method url-fetch) + (uri (pypi-uri "python-dotenv" version)) + (sha256 + (base32 + "0x5dagmfn31phrbxlwacw3s4w5vibv8fxqc62nqcdvdhjsy0k69v")))))) + +;; TODO: This should probably be upstreamed. +;; This is only for python-dynaconf. +(define-public python-ruamel.yaml-0.16.10 + (package (inherit python-ruamel.yaml) + (name "python-ruamel.yaml") + (version "0.16.10") + (source + (origin + (method url-fetch) + (uri (pypi-uri "ruamel.yaml" version)) + (sha256 + (base32 + "0m5rwlf3iwsb1w9l98qx9alvqxk41gfphksj03x2zxwbfx569709")))))) + +;; TODO: This should probably be upstreamed. +(define-public python-dynaconf + (package + (name "dynaconf") + (version "3.1.5") + (source + (origin + (method git-fetch) + (uri + (git-reference + (url "https://github.com/rochacbruno/dynaconf") + (commit version))) + (file-name (git-file-name name version)) + (sha256 + (base32 + "0hxp1iadwmva79l16frvc77jrisppb09z6k1asm0qfjjzwyaswg3")) + (patches (search-patches "dynaconf-Unvendor-dependencies.patch")) + (modules '((guix build utils))) + (snippet '(begin + ;; Remove vendored dependencies + (let ((unvendor '("click" "dotenv" "ruamel" "toml"))) + (with-directory-excursion "dynaconf/vendor" + (for-each delete-file-recursively unvendor)) + (with-directory-excursion "dynaconf/vendor_src" + (for-each delete-file-recursively unvendor))))))) + (build-system python-build-system) + (arguments + `(#:phases + (modify-phases %standard-phases + (replace 'check + (lambda* (#:key tests? outputs #:allow-other-keys) + (when tests? + (setenv "PATH" + (string-append (assoc-ref outputs "out") "/bin:" + (getenv "PATH"))) + ;; These tests depend on hvac and a + ;; live Vault process. + (delete-file "tests/test_vault.py") + (invoke "make" "test_only"))))))) + (propagated-inputs + `(("python-click" ,python-click) + ("python-configobj" ,python-configobj) + ("python-dotenv" ,python-dotenv-0.13.0) + ("python-ruamel.yaml" ,python-ruamel.yaml-0.16.10) + ("python-toml" ,python-toml))) + (native-inputs + `(("python-django" ,python-django) + ("python-flask" ,python-flask) + ("python-pytest" ,python-pytest-6) + ("python-pytest-cov" ,python-pytest-cov) + ("python-pytest-mock" ,python-pytest-mock))) + (home-page "https://www.dynaconf.com/") + (synopsis "The dynamic configurator for your Python project") + (description + "This package provides @code{dynaconf} the dynamic configurator manager for +your Python project. It provides features such as: + +@itemize +@item Inspired by the @url{https://12factor.net/config, 12-factor application guide}; +@item Settings management (default values, validation, parsing, templating); +@item Protection of sensitive information (passwords/tokens); +@item Multiple file formats @code{toml|yaml|json|ini|py} and also customizable +loaders; +@item Full support for environment variables to override existing settings +(dotenv support included); +@item Optional layered system for multiple environments @code{[default, +development, testing, production]}; +@item Built-in support for Hashicorp Vault and Redis as settings and secrets storage; +@item Built-in extensions for Django and Flask web frameworks; +@item CLI for common operations such as @code{init, list, write, validate, export}. +@end itemize") + (license license:expat))) + +;; This is only for mobilizon-bots.git. +(define-public python-arrow-1.1 + (package (inherit python-arrow) + (name "python-arrow") + (version "1.1.0") + (source + (origin + (method url-fetch) + (uri (pypi-uri "arrow" version)) + (sha256 + (base32 + "1n2vzyrirfj7fp0zn6iipm3i8bch0g4m14z02nrvlyjiyfmi7zmq")))))) + +;; This is only for mobilizon-bots.git. +(define-public python-tortoise-orm-0.17 + (package (inherit python-tortoise-orm) + (name "python-tortoise-orm") + (version "0.17.6") + (source + (origin + (method url-fetch) + (uri (pypi-uri "tortoise-orm" version)) + (sha256 + (base32 + "0viwmd8773b4bz8119d26wd3qxrdhmafrqd4m8bdz3439gcpq67l")))))) + +;; This is only for mobilizon-bots.git. +(define-public python-pytest-asyncio-0.15 + (package (inherit python-pytest-asyncio) + (name "python-pytest-asyncio") + (version "0.15.1") + (source + (origin + (method url-fetch) + (uri (pypi-uri "pytest-asyncio" version)) + (sha256 + (base32 + "0vrzsrg3j1cfd57m0b3r5xf87rslgcs42jya346mdg9bc6wwwr15")))) + (arguments + (substitute-keyword-arguments (package-arguments python-pytest-asyncio) + ((#:tests? _ #f) #f))))) + +(define-public python-markdownify + (package + (name "python-markdownify") + (version "0.9.2") + (source + (origin + (method url-fetch) + (uri (pypi-uri "markdownify" version)) + (sha256 + (base32 + "0zfpzdwkf34spmfr2iwkqch3fi0nnll2v5nghvgnrmazjn4rcxdr")))) + (build-system python-build-system) + (arguments + `(#:tests? #f)) + (native-inputs + `(("python-pytest" ,python-pytest-6))) + (propagated-inputs + `(("python-flake8" ,python-flake8) + ("python-beautifulsoup4" ,python-beautifulsoup4) + ("python-six" ,python-six))) + (home-page + "http://github.com/matthewwithanm/python-markdownify") + (synopsis "Convert HTML to markdown.") + (description "Convert HTML to markdown.") + (license license:expat))) + +(define-public python-ipaddress + (package + (name "python-ipaddress") + (version "1.0.23") + (source + (origin + (method url-fetch) + (uri (pypi-uri "ipaddress" version)) + (sha256 + (base32 "1qp743h30s04m3cg3yk3fycad930jv17q7dsslj4mfw0jlvf1y5p")))) + (build-system python-build-system) + (home-page "https://github.com/phihag/ipaddress") + (synopsis "IPv4/IPv6 manipulation library") + (description "IPv4/IPv6 manipulation library") + (license #f))) + +(define-public python-vcrpy + (package + (name "python-vcrpy") + (version "4.1.1") + (source + (origin + (method url-fetch) + (uri (pypi-uri "vcrpy" version)) + (sha256 + (base32 "16gmzxs3lzbgf1828n0q61vbmwyhpvzdlk37x6gdk8n05zr5n2ap")))) + (build-system python-build-system) + (arguments + `(#:phases + (modify-phases %standard-phases + (replace 'check + (lambda* (#:key tests? outputs #:allow-other-keys) + (when tests? + (substitute* "tox.ini" + (("AWS_ACCESS_KEY_ID") "PYTHONPATH")) + (setenv "PYTHONPATH" (string-append ".:" (getenv "PYTHONPATH"))) + ;; These tests require network access. + (delete-file "tests/unit/test_stubs.py") + (invoke "pytest" "tests/unit"))))))) + (native-inputs + `( + ("python-black" ,python-black) + ("python-coverage" ,python-coverage) + ("python-flake8" ,python-flake8) + ("python-flask" ,python-flask) + ("python-httplib2" ,python-httplib2) + ("python-ipaddress" ,python-ipaddress) + ("python-mock" ,python-mock) + ("python-pytest" ,python-pytest) + ("python-pytest-cov" ,python-pytest-cov) + ("python-pytest-httpbin" ,python-pytest-httpbin) + ("python-tox" ,python-tox) + ("python-urllib3" ,python-urllib3))) + + (propagated-inputs + `(("python-pyyaml" ,python-pyyaml) + ("python-six" ,python-six) + ("python-wrapt" ,python-wrapt) + ("python-yarl" ,python-yarl))) + (home-page "https://github.com/kevin1024/vcrpy") + (synopsis + "Automatically mock your HTTP interactions to simplify and speed up testing") + (description + "Automatically mock your HTTP interactions to simplify and speed up testing") + (license license:expat))) + +(define-public python-tweepy + (package + (name "python-tweepy") + (version "4.1.0") + (source + (origin + (method git-fetch) + (uri + (git-reference + (url "https://github.com/tweepy/tweepy") + (commit (string-append "v" version)))) + (file-name (git-file-name name version)) + (sha256 + (base32 + "1c0paxc38i5jq8i20f9xwv966sap4nnhgnbdxg3611pllnzg5wdv")))) + (build-system python-build-system) + (arguments + `(#:phases + (modify-phases %standard-phases + (replace 'check + (lambda* (#:key tests? inputs outputs #:allow-other-keys) + (when tests? + (invoke "python" "-m" "unittest"))))))) + (propagated-inputs + `(("python-aiohttp" ,python-aiohttp) + ("python-requests" ,python-requests) + ("python-requests-oauthlib" ,python-requests-oauthlib))) + (native-inputs + `(("python-coveralls" ,python-coveralls) + ("python-tox" ,python-tox) + ("python-vcrpy" ,python-vcrpy))) + (home-page "https://www.tweepy.org/") + (synopsis "Twitter library for Python") + (description "Twitter library for Python") + (license license:expat))) + +(define-public mobilizon-reshare.git + (let ((source-version (with-input-from-file + (string-append %source-dir + "/mobilizon_reshare/VERSION") + read-line)) + (revision "0") + (commit (read-line + (open-input-pipe "git show HEAD | head -1 | cut -d ' ' -f 2")))) + (package + (name "mobilizon-reshare.git") + (version (git-version source-version revision commit)) + (source (local-file %source-dir + #:recursive? #t + #:select? (git-predicate %source-dir))) + (build-system python-build-system) + (arguments + `(#:python ,python-3.9 + #:phases + (modify-phases %standard-phases + (add-after 'unpack 'generate-setup.py + (lambda* (#:key inputs outputs #:allow-other-keys) + ;; This is a hack needed to get poetry's + ;; setup.py. + (setenv "POETRY_VIRTUALENVS_CREATE" "false") + (invoke "poetry" "build" "-f" "sdist") + (invoke "bash" "-c" + "tar --wildcards -xvf dist/*-`poetry version -s`.tar.gz -O '*/setup.py' > setup.py") + (substitute* "setup.py" + (("'install_requires': install_requires,") "")))) + (replace 'check + (lambda* (#:key tests? inputs outputs #:allow-other-keys) + (when tests? + (invoke "python" "-m" "pytest" + ;; This test fails because of the unvendoring + ;; of toml from dynaconf. + "-k" "not test_get_settings_failure_invalid_toml"))))))) + (native-inputs + `(("python-asynctest" ,python-asynctest) + ("python-iniconfig" ,python-iniconfig) + ("poetry" ,poetry) + ("python-pytest" ,python-pytest-6) + ("python-pytest-asyncio" ,python-pytest-asyncio-0.15) + ("python-responses" ,python-responses) + ("python-wrapper" ,python-3.9-wrapper))) + (propagated-inputs + `(("python-aiosqlite" ,python-aiosqlite) + ("python-appdirs" ,python-appdirs) + ("python-arrow" ,python-arrow-1.1) + ("python-beautifulsoup4" ,python-beautifulsoup4) + ("python-click" ,python-click) + ("python-dynaconf" ,python-dynaconf) + ("python-jinja2" ,python-jinja2) + ("python-markdownify" ,python-markdownify) + ("python-requests" ,python-requests) + ("python-tweepy" ,python-tweepy) + ("python-tortoise-orm" ,python-tortoise-orm-0.17))) + (home-page + "https://github.com/Tech-Workers-Coalition-Italia/mobilizon-reshare") + (synopsis + "Publish Mobilizon events to your social networks") + (description + "This package provides a CLI application to publish your Mobilizon +events to your social media.") + (license coopyleft)))) diff --git a/docker/service.scm b/docker/service.scm new file mode 100644 index 0000000..9749532 --- /dev/null +++ b/docker/service.scm @@ -0,0 +1,52 @@ +(define-module (docker service) + #:use-module (gnu services) + #:use-module (gnu system shadow) + #:use-module (gnu packages admin) + #:use-module (guix records) + #:use-module (guix gexp) + #:use-module (docker mobilizon-reshare) + #:export (mobilizon-reshare-service-type + mobilizon-reshare-configuration + mobilizon-reshare-configuration?)) + +(define-record-type* + mobilizon-reshare-configuration make-mobilizon-reshare-configuration + mobilizon-reshare-configuration? + (mobilizon-reshare mobilizon-reshare-configuration-mobilizon-reshare (default mobilizon-reshare.git)) + (datadir mobilizon-reshare-datadir (default "/var/lib/mobilizon-reshare"))) + +(define %mobilizon-reshare-accounts + (list (user-group + (name "mobilizon-reshare") + (system? #t)) + (user-account + (name "mobilizon-reshare") + (comment "Mobilizon Reshare's Service Account") + (group "mobilizon-reshare") + (system? #t) + (home-directory "/var/empty") + (shell (file-append shadow "/sbin/nologin"))))) + +(define (%mobilizon-reshare-activation config) + "Return an activation gexp for Mobilizon Reshare." + (let ((datadir (mobilizon-reshare-datadir config))) + #~(begin + (use-modules (guix build utils)) + (let* ((user (getpwnam "mobilizon-reshare")) + (uid (passwd:uid user)) + (gid (passwd:gid user)) + (datadir #$datadir)) + (mkdir-p datadir) + (chown datadir uid gid))))) + +(define mobilizon-reshare-service-type + (service-type + (name 'mobilizon-reshare) + (extensions + (list (service-extension profile-service-type + (compose list mobilizon-reshare-configuration-mobilizon-reshare)) + (service-extension account-service-type + (const %mobilizon-reshare-accounts)) + (service-extension activation-service-type + %mobilizon-reshare-activation))) + (default-value (mobilizon-reshare-configuration)))) diff --git a/guix.scm b/guix.scm new file mode 100644 index 0000000..75b5293 --- /dev/null +++ b/guix.scm @@ -0,0 +1,3 @@ +(use-modules (docker mobilizon-reshare)) + +mobilizon-reshare.git diff --git a/manifest.scm b/manifest.scm new file mode 100644 index 0000000..6b360de --- /dev/null +++ b/manifest.scm @@ -0,0 +1,18 @@ +(define-module (manifest) + #:use-module (docker mobilizon-reshare) + #:use-module (gnu packages) + #:use-module (guix profiles)) + +(packages->manifest + (append + (list + python-3.9-wrapper) + (map specification->package+output + '("meld" "git-cal" "man-db" "texinfo" + "python-pre-commit" "poetry" "bzip2" + "guix" "grep" "sed" "unzip" "bash" "ncurses" + "findutils" "ripgrep" "python-semver" + "util-linux" "python-black" "gawk" "fd" + "coreutils" "less" "git" "git:credential-libsecret" + "gitg" "direnv" "which" "vim" "emacs" + "tar" "gzip" "openssh" "docker-cli" "docker-compose")))) diff --git a/patches/dynaconf-Unvendor-dependencies.patch b/patches/dynaconf-Unvendor-dependencies.patch new file mode 100644 index 0000000..a379b1f --- /dev/null +++ b/patches/dynaconf-Unvendor-dependencies.patch @@ -0,0 +1,180 @@ +From 3f7b48195500cbbbbecd3cac2f5308c64004479b Mon Sep 17 00:00:00 2001 +From: Giacomo Leidi +Date: Sun, 29 Aug 2021 23:39:27 +0200 +Subject: [PATCH] Use system site dependencies. + +Box was not unvendored because it appears to be heavily patched. +--- + dynaconf/cli.py | 4 ++-- + dynaconf/default_settings.py | 2 +- + dynaconf/loaders/env_loader.py | 2 +- + dynaconf/loaders/toml_loader.py | 2 +- + dynaconf/loaders/yaml_loader.py | 2 +- + dynaconf/utils/parse_conf.py | 2 +- + dynaconf/vendor/box/converters.py | 4 ++-- + dynaconf/vendor/box/from_file.py | 4 ++-- + dynaconf/vendor_src/box/converters.py | 4 ++-- + dynaconf/vendor_src/box/from_file.py | 4 ++-- + tests/test_cli.py | 2 +- + 11 files changed, 16 insertions(+), 16 deletions(-) + +diff --git a/dynaconf/cli.py b/dynaconf/cli.py +index 5bb8316..1341a95 100644 +--- a/dynaconf/cli.py ++++ b/dynaconf/cli.py +@@ -20,8 +20,8 @@ from dynaconf.utils.functional import empty + from dynaconf.utils.parse_conf import parse_conf_data + from dynaconf.validator import ValidationError + from dynaconf.validator import Validator +-from dynaconf.vendor import click +-from dynaconf.vendor import toml ++import click ++import toml + + + CWD = Path.cwd() +diff --git a/dynaconf/default_settings.py b/dynaconf/default_settings.py +index 66601b0..9605fc5 100644 +--- a/dynaconf/default_settings.py ++++ b/dynaconf/default_settings.py +@@ -8,7 +8,7 @@ from dynaconf.utils import upperfy + from dynaconf.utils import warn_deprecations + from dynaconf.utils.files import find_file + from dynaconf.utils.parse_conf import parse_conf_data +-from dynaconf.vendor.dotenv import load_dotenv ++from dotenv import load_dotenv + + + def try_renamed(key, value, older_key, current_key): +diff --git a/dynaconf/loaders/env_loader.py b/dynaconf/loaders/env_loader.py +index e7b13bd..b034c8a 100644 +--- a/dynaconf/loaders/env_loader.py ++++ b/dynaconf/loaders/env_loader.py +@@ -2,7 +2,7 @@ from os import environ + + from dynaconf.utils import upperfy + from dynaconf.utils.parse_conf import parse_conf_data +-from dynaconf.vendor.dotenv import cli as dotenv_cli ++from dotenv import cli as dotenv_cli + + + IDENTIFIER = "env" +diff --git a/dynaconf/loaders/toml_loader.py b/dynaconf/loaders/toml_loader.py +index 07b973f..d81d675 100644 +--- a/dynaconf/loaders/toml_loader.py ++++ b/dynaconf/loaders/toml_loader.py +@@ -5,7 +5,7 @@ from dynaconf import default_settings + from dynaconf.constants import TOML_EXTENSIONS + from dynaconf.loaders.base import BaseLoader + from dynaconf.utils import object_merge +-from dynaconf.vendor import toml ++import toml + + + def load(obj, env=None, silent=True, key=None, filename=None): +diff --git a/dynaconf/loaders/yaml_loader.py b/dynaconf/loaders/yaml_loader.py +index 33c6532..3ef419a 100644 +--- a/dynaconf/loaders/yaml_loader.py ++++ b/dynaconf/loaders/yaml_loader.py +@@ -7,7 +7,7 @@ from dynaconf.constants import YAML_EXTENSIONS + from dynaconf.loaders.base import BaseLoader + from dynaconf.utils import object_merge + from dynaconf.utils.parse_conf import try_to_encode +-from dynaconf.vendor.ruamel import yaml ++from ruamel import yaml + + # Add support for Dynaconf Lazy values to YAML dumper + yaml.SafeDumper.yaml_representers[ +diff --git a/dynaconf/utils/parse_conf.py b/dynaconf/utils/parse_conf.py +index c42b07a..01ccdae 100644 +--- a/dynaconf/utils/parse_conf.py ++++ b/dynaconf/utils/parse_conf.py +@@ -9,7 +9,7 @@ from dynaconf.utils import isnamedtupleinstance + from dynaconf.utils import multi_replace + from dynaconf.utils import recursively_evaluate_lazy_format + from dynaconf.utils.boxing import DynaBox +-from dynaconf.vendor import toml ++import toml + + try: + from jinja2 import Environment +diff --git a/dynaconf/vendor/box/converters.py b/dynaconf/vendor/box/converters.py +index 93cdcfb..e34c7dc 100644 +--- a/dynaconf/vendor/box/converters.py ++++ b/dynaconf/vendor/box/converters.py +@@ -7,9 +7,9 @@ _B='utf-8' + _A=None + import csv,json,sys,warnings + from pathlib import Path +-import dynaconf.vendor.ruamel.yaml as yaml ++import ruamel.yaml as yaml + from dynaconf.vendor.box.exceptions import BoxError,BoxWarning +-from dynaconf.vendor import toml ++import toml + BOX_PARAMETERS='default_box','default_box_attr','conversion_box','frozen_box','camel_killer_box','box_safe_prefix','box_duplicates','ordered_box','default_box_none_transform','box_dots','modify_tuples_box','box_intact_types','box_recast' + def _exists(filename,create=_E): + A=filename;B=Path(A) +diff --git a/dynaconf/vendor/box/from_file.py b/dynaconf/vendor/box/from_file.py +index daa1137..d75940b 100644 +--- a/dynaconf/vendor/box/from_file.py ++++ b/dynaconf/vendor/box/from_file.py +@@ -1,8 +1,8 @@ + from json import JSONDecodeError + from pathlib import Path + from typing import Union +-from dynaconf.vendor.toml import TomlDecodeError +-from dynaconf.vendor.ruamel.yaml import YAMLError ++from toml import TomlDecodeError ++from ruamel.yaml import YAMLError + from .exceptions import BoxError + from .box import Box + from .box_list import BoxList +diff --git a/dynaconf/vendor_src/box/converters.py b/dynaconf/vendor_src/box/converters.py +index c9a2293..ae42bf6 100644 +--- a/dynaconf/vendor_src/box/converters.py ++++ b/dynaconf/vendor_src/box/converters.py +@@ -9,9 +9,9 @@ import sys + import warnings + from pathlib import Path + +-import dynaconf.vendor.ruamel.yaml as yaml ++import ruamel.yaml as yaml + from dynaconf.vendor.box.exceptions import BoxError, BoxWarning +-from dynaconf.vendor import toml ++import toml + + + BOX_PARAMETERS = ('default_box', 'default_box_attr', 'conversion_box', +diff --git a/dynaconf/vendor_src/box/from_file.py b/dynaconf/vendor_src/box/from_file.py +index 2e2a6ad..3f76819 100644 +--- a/dynaconf/vendor_src/box/from_file.py ++++ b/dynaconf/vendor_src/box/from_file.py +@@ -3,8 +3,8 @@ + from json import JSONDecodeError + from pathlib import Path + from typing import Union +-from dynaconf.vendor.toml import TomlDecodeError +-from dynaconf.vendor.ruamel.yaml import YAMLError ++from toml import TomlDecodeError ++from ruamel.yaml import YAMLError + + + from .exceptions import BoxError +diff --git a/tests/test_cli.py b/tests/test_cli.py +index 6693701..df44409 100644 +--- a/tests/test_cli.py ++++ b/tests/test_cli.py +@@ -11,7 +11,7 @@ from dynaconf.cli import main + from dynaconf.cli import read_file_in_root_directory + from dynaconf.cli import WRITERS + from dynaconf.utils.files import read_file +-from dynaconf.vendor.click.testing import CliRunner ++from click.testing import CliRunner + + + runner = CliRunner() + +base-commit: ebf7b17cffd5e08b212948bd8036d580718d5bf8 +-- +2.32.0 + diff --git a/scripts/build_docker_image.sh b/scripts/build_docker_image.sh new file mode 100755 index 0000000..f4bdcd3 --- /dev/null +++ b/scripts/build_docker_image.sh @@ -0,0 +1,4 @@ +#!/bin/sh +set -eu + +guix time-machine -C channels-lock.scm -- system docker-image -L . -L ./patches --root=docker-image.tar.gz docker/image.scm diff --git a/scripts/release.sh b/scripts/release.sh index 5dc1507..26be1cc 100755 --- a/scripts/release.sh +++ b/scripts/release.sh @@ -5,6 +5,7 @@ set -eu myself="$(basename "$0")" version_file="$(pwd)/mobilizon_reshare/VERSION" pyproject_toml="$(pwd)/pyproject.toml" +docker_compose_yml="$(pwd)/docker-compose.yml" current_branch="$(git rev-parse --abbrev-ref HEAD)" current_commit="$(git log -1 --format='%H')" dryrun=0 @@ -81,8 +82,11 @@ release-new-version() { [ "$verbose" = "1" ] && echo "Updating $pyproject_toml" [ "$dryrun" = "0" ] && sed -i -E "s/version.*=.*\"${current}\"$/version = \"${next}\"/" "$pyproject_toml" - [ "$verbose" = "1" ] && echo "Committing ${pyproject_toml} and ${version_file}" - [ "$dryrun" = "0" ] && git add "${pyproject_toml}" "${version_file}" && git commit -m "Release v${next}." + [ "$verbose" = "1" ] && echo "Updating $docker_compose_yml" + [ "$dryrun" = "0" ] && sed -i "s/${current}/${next}/" "$docker_compose_yml" + + [ "$verbose" = "1" ] && echo "Committing ${pyproject_toml}, ${docker_compose_yml} and ${version_file}" + [ "$dryrun" = "0" ] && git add "$docker_compose_yml" "${pyproject_toml}" "${version_file}" && git commit -m "Release v${next}." [ "$verbose" = "1" ] && echo "Tagging Git HEAD with v${next}" [ "$dryrun" = "0" ] && git tag "v${next}"