From 5a3d0d4d9e0a54d975a61cb1446c83c3a9c04293 Mon Sep 17 00:00:00 2001 From: Eduardo Robles Elvira Date: Sat, 22 Jul 2023 16:05:39 +0200 Subject: [PATCH 01/26] adding devenv.sh, initial devcontainer support and starting work on container generation --- .devcontainer.json | 12 +++ .envrc | 3 + .gitignore | 7 ++ devenv.lock | 241 +++++++++++++++++++++++++++++++++++++++++++++ devenv.nix | 38 +++++++ devenv.yaml | 5 + requirements.txt | 3 +- 7 files changed, 307 insertions(+), 2 deletions(-) create mode 100644 .devcontainer.json create mode 100644 .envrc create mode 100644 devenv.lock create mode 100644 devenv.nix create mode 100644 devenv.yaml diff --git a/.devcontainer.json b/.devcontainer.json new file mode 100644 index 0000000..75b8759 --- /dev/null +++ b/.devcontainer.json @@ -0,0 +1,12 @@ +{ + "customizations": { + "vscode": { + "extensions": [ + "mkhl.direnv" + ] + } + }, + "image": "ghcr.io/cachix/devenv:latest", + "overrideCommand": false, + "updateContentCommand": "devenv ci" +} diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..6de8a8a --- /dev/null +++ b/.envrc @@ -0,0 +1,3 @@ +source_url "https://raw.githubusercontent.com/cachix/devenv/d1f7b48e35e6dee421cfd0f51481d17f77586997/direnvrc" "sha256-YBzqskFZxmNb3kYVoKD9ZixoPXJh1C9ZvTLGFRkauZ0=" + +use devenv \ No newline at end of file diff --git a/.gitignore b/.gitignore index f158fa9..e6d63ee 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,10 @@ custom_settings.py *.pyc *.sqlite + +# Devenv +.devenv* +devenv.local.nix + +# Direnv +.direnv/ diff --git a/devenv.lock b/devenv.lock new file mode 100644 index 0000000..7472bfb --- /dev/null +++ b/devenv.lock @@ -0,0 +1,241 @@ +{ + "nodes": { + "devenv": { + "locked": { + "dir": "src/modules", + "lastModified": 1690015987, + "narHash": "sha256-CS8Dz7HK4Ya8wWDd+dqcQbpTKeuqqBLma2LwJMKsozg=", + "owner": "cachix", + "repo": "devenv", + "rev": "016081e7895b36e4392bb4ee28a3c477ec030f30", + "type": "github" + }, + "original": { + "dir": "src/modules", + "owner": "cachix", + "repo": "devenv", + "type": "github" + } + }, + "flake-compat": { + "flake": false, + "locked": { + "lastModified": 1673956053, + "narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=", + "owner": "edolstra", + "repo": "flake-compat", + "rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9", + "type": "github" + }, + "original": { + "owner": "edolstra", + "repo": "flake-compat", + "type": "github" + } + }, + "flake-compat_2": { + "flake": false, + "locked": { + "lastModified": 1673956053, + "narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=", + "owner": "edolstra", + "repo": "flake-compat", + "rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9", + "type": "github" + }, + "original": { + "owner": "edolstra", + "repo": "flake-compat", + "type": "github" + } + }, + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1689068808, + "narHash": "sha256-6ixXo3wt24N/melDWjq70UuHQLxGV8jZvooRanIHXw0=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "919d646de7be200f3bf08cb76ae1f09402b6f9b4", + "type": "github" + }, + "original": { + "id": "flake-utils", + "type": "indirect" + } + }, + "flake-utils_2": { + "inputs": { + "systems": "systems_2" + }, + "locked": { + "lastModified": 1685518550, + "narHash": "sha256-o2d0KcvaXzTrPRIo0kOLV0/QXHhDQ5DTi+OxcjO8xqY=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "a1720a10a6cfe8234c0e93907ffe81be440f4cef", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "gitignore": { + "inputs": { + "nixpkgs": [ + "pre-commit-hooks", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1660459072, + "narHash": "sha256-8DFJjXG8zqoONA1vXtgeKXy68KdJL5UaXR8NtVMUbx8=", + "owner": "hercules-ci", + "repo": "gitignore.nix", + "rev": "a20de23b925fd8264fd7fad6454652e142fd7f73", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "gitignore.nix", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1689935543, + "narHash": "sha256-6GQ9ib4dA/r1leC5VUpsBo0BmDvNxLjKrX1iyL+h8mc=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "e43e2448161c0a2c4928abec4e16eae1516571bc", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs-python": { + "inputs": { + "flake-compat": "flake-compat", + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs_2" + }, + "locked": { + "lastModified": 1690020017, + "narHash": "sha256-fa0Ax9FikRGF8iJOOh/IAiLT6yP1WKQ+Cm6IHLVJtdY=", + "owner": "cachix", + "repo": "nixpkgs-python", + "rev": "d621fade6965c202463ee4bd3d0c1011b3eb741a", + "type": "github" + }, + "original": { + "owner": "cachix", + "repo": "nixpkgs-python", + "type": "github" + } + }, + "nixpkgs-stable": { + "locked": { + "lastModified": 1685801374, + "narHash": "sha256-otaSUoFEMM+LjBI1XL/xGB5ao6IwnZOXc47qhIgJe8U=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "c37ca420157f4abc31e26f436c1145f8951ff373", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-23.05", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_2": { + "locked": { + "lastModified": 1685974512, + "narHash": "sha256-WLPHpe96RbPRO9iDtCxgsYkadTheRq7wqXWdGpR6g7w=", + "owner": "domenkozar", + "repo": "nixpkgs", + "rev": "1102477695918daba466123cc2ef694ed3a49939", + "type": "github" + }, + "original": { + "owner": "domenkozar", + "ref": "cpython-moduralize", + "repo": "nixpkgs", + "type": "github" + } + }, + "pre-commit-hooks": { + "inputs": { + "flake-compat": "flake-compat_2", + "flake-utils": "flake-utils_2", + "gitignore": "gitignore", + "nixpkgs": [ + "nixpkgs" + ], + "nixpkgs-stable": "nixpkgs-stable" + }, + "locked": { + "lastModified": 1689668210, + "narHash": "sha256-XAATwDkaUxH958yXLs1lcEOmU6pSEIkatY3qjqk8X0E=", + "owner": "cachix", + "repo": "pre-commit-hooks.nix", + "rev": "eb433bff05b285258be76513add6f6c57b441775", + "type": "github" + }, + "original": { + "owner": "cachix", + "repo": "pre-commit-hooks.nix", + "type": "github" + } + }, + "root": { + "inputs": { + "devenv": "devenv", + "nixpkgs": "nixpkgs", + "nixpkgs-python": "nixpkgs-python", + "pre-commit-hooks": "pre-commit-hooks" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, + "systems_2": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/devenv.nix b/devenv.nix new file mode 100644 index 0000000..fa585b5 --- /dev/null +++ b/devenv.nix @@ -0,0 +1,38 @@ +{ pkgs, config, lib, ... }: + +{ + # https://devenv.sh/basics/ + env.GREET = "devenv"; + + # https://devenv.sh/packages/ + packages = lib.optionals (!config.container.isBuilding) [ + pkgs.git + ]; + + # https://devenv.sh/processes/ + processes.election-orchestra.exec = """ + export FRESTQ_SETTINGS=base_settings.py + python -m flask + """; + + enterShell = '' + git --version + ''; + + # https://devenv.sh/integrations/codespaces-devcontainer/ + devcontainer.enable = true; + + # https://devenv.sh/languages/ + languages.nix.enable = true; + languages.python = { + enable = true; + venv.enable = true; + venv.requirements = builtins.readFile ./requirements.txt; + }; + + services.postgres = { + enable = true; + package = pkgs.postgresql_15; + initialDatabases = [{ name = "election-orchestra"; }]; + }; +} diff --git a/devenv.yaml b/devenv.yaml new file mode 100644 index 0000000..a32e623 --- /dev/null +++ b/devenv.yaml @@ -0,0 +1,5 @@ +inputs: + nixpkgs: + url: github:NixOS/nixpkgs/nixpkgs-unstable + nixpkgs-python: + url: github:cachix/nixpkgs-python diff --git a/requirements.txt b/requirements.txt index bb867df..34608fa 100644 --- a/requirements.txt +++ b/requirements.txt @@ -15,5 +15,4 @@ ipython==8.10.0 itsdangerous==2.1.2 prettytable==0.7.2 psycopg2-binary==2.8.6 -pycparser==2.10 -uwsgi==2.0.18 +pycparser==2.10 \ No newline at end of file From d513602c2badd41ded0b10910b2b4182445afafd Mon Sep 17 00:00:00 2001 From: Eduardo Robles Elvira Date: Sat, 22 Jul 2023 16:06:12 +0200 Subject: [PATCH 02/26] WIP --- requirements.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 34608fa..bb867df 100644 --- a/requirements.txt +++ b/requirements.txt @@ -15,4 +15,5 @@ ipython==8.10.0 itsdangerous==2.1.2 prettytable==0.7.2 psycopg2-binary==2.8.6 -pycparser==2.10 \ No newline at end of file +pycparser==2.10 +uwsgi==2.0.18 From 2981be180827bf57f9e778bb1f647fa4f6c3d1f3 Mon Sep 17 00:00:00 2001 From: Eduardo Robles Date: Sat, 22 Jul 2023 16:57:14 +0000 Subject: [PATCH 03/26] fixing dependencies --- devenv.nix | 16 +++++++++++++--- requirements.txt | 4 ++-- setup.py | 4 ++-- 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/devenv.nix b/devenv.nix index fa585b5..b0264b5 100644 --- a/devenv.nix +++ b/devenv.nix @@ -7,13 +7,17 @@ # https://devenv.sh/packages/ packages = lib.optionals (!config.container.isBuilding) [ pkgs.git + + # used for building uwsgi: + pkgs.gcc + pkgs.libffi ]; # https://devenv.sh/processes/ - processes.election-orchestra.exec = """ + processes.election-orchestra.exec = '' export FRESTQ_SETTINGS=base_settings.py python -m flask - """; + ''; enterShell = '' git --version @@ -27,7 +31,13 @@ languages.python = { enable = true; venv.enable = true; - venv.requirements = builtins.readFile ./requirements.txt; + venv.requirements = ( + builtins.readFile ./requirements.txt + + '' + colorama==0.4.6 + PyYAML==6.0.1 + ZODB==5.8.1 + ''); }; services.postgres = { diff --git a/requirements.txt b/requirements.txt index bb867df..9e67129 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -git+https://github.com/sequentech/frestq.git@master +git+https://github.com/sequentech/frestq.git@feat/master/k8s requests==2.31.0 Flask==2.3.2 Flask-SQLAlchemy==2.5.1 @@ -16,4 +16,4 @@ itsdangerous==2.1.2 prettytable==0.7.2 psycopg2-binary==2.8.6 pycparser==2.10 -uwsgi==2.0.18 +uwsgi==2.0.21 diff --git a/setup.py b/setup.py index 1beb7a2..83a574a 100644 --- a/setup.py +++ b/setup.py @@ -17,7 +17,7 @@ description='election orchestrator', long_description=open('README.md').read(), install_requires=[ - 'frestq @ git+https://github.com/sequentech/frestq.git@master', + 'frestq @ git+https://github.com/sequentech/frestq.git@feat/master/k8s', 'requests==2.31.0', 'Flask==2.3.2', 'Flask-SQLAlchemy==2.5.1', @@ -35,7 +35,7 @@ 'prettytable==0.7.2', 'psycopg2-binary==2.8.6', 'pycparser==2.10', - 'uwsgi==2.0.18', + 'uwsgi==2.0.21', ], classifiers=[ "Programming Language :: Python :: 3", From 4c96ce5c3ffbb94ce83fcd843a8b7e9cbe3c9846 Mon Sep 17 00:00:00 2001 From: Eduardo Robles Date: Sun, 23 Jul 2023 04:46:47 +0000 Subject: [PATCH 04/26] WIP --- devenv.nix | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/devenv.nix b/devenv.nix index b0264b5..93d8b73 100644 --- a/devenv.nix +++ b/devenv.nix @@ -7,6 +7,7 @@ # https://devenv.sh/packages/ packages = lib.optionals (!config.container.isBuilding) [ pkgs.git + pkgs.ack # used for building uwsgi: pkgs.gcc @@ -16,7 +17,7 @@ # https://devenv.sh/processes/ processes.election-orchestra.exec = '' export FRESTQ_SETTINGS=base_settings.py - python -m flask + devenv shell python app.py --createdb && python app.py ''; enterShell = '' @@ -30,6 +31,7 @@ languages.nix.enable = true; languages.python = { enable = true; + package = pkgs.python39; venv.enable = true; venv.requirements = ( builtins.readFile ./requirements.txt + From 53f56627351c6ffe486cfda6063ecf3bc95fa40b Mon Sep 17 00:00:00 2001 From: Eduardo Robles Date: Sun, 23 Jul 2023 05:12:05 +0000 Subject: [PATCH 05/26] fixing devenv up --- devenv.nix | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/devenv.nix b/devenv.nix index 93d8b73..cada930 100644 --- a/devenv.nix +++ b/devenv.nix @@ -16,8 +16,10 @@ # https://devenv.sh/processes/ processes.election-orchestra.exec = '' - export FRESTQ_SETTINGS=base_settings.py - devenv shell python app.py --createdb && python app.py + devenv shell bash -c \ + "export FRESTQ_SETTINGS=base_settings.py &&\ + python app.py --createdb \ + && python app.py" ''; enterShell = '' From d34c45ed5159e9ee85fc71ceed4f4bdc8c96dcd0 Mon Sep 17 00:00:00 2001 From: Eduardo Robles Date: Sun, 23 Jul 2023 05:12:45 +0000 Subject: [PATCH 06/26] WIP --- devenv.nix | 1 + 1 file changed, 1 insertion(+) diff --git a/devenv.nix b/devenv.nix index cada930..e896151 100644 --- a/devenv.nix +++ b/devenv.nix @@ -33,6 +33,7 @@ languages.nix.enable = true; languages.python = { enable = true; + # using python39 as default (python310) seems to have some glibc glitch package = pkgs.python39; venv.enable = true; venv.requirements = ( From a4ae6d9832f2b9689706221004d30a864d12da3b Mon Sep 17 00:00:00 2001 From: Eduardo Robles Date: Sun, 23 Jul 2023 06:33:42 +0000 Subject: [PATCH 07/26] WIP --- .devcontainer.json | 12 ------- .devcontainer/devcontainer.json | 40 ++++++++++++++++++++++ .devcontainer/docker-compose.yml | 12 +++++++ devenv.lock | 59 ++++++++++++++++++++++++++++++-- devenv.nix | 3 ++ devenv.yaml | 7 ++++ 6 files changed, 118 insertions(+), 15 deletions(-) delete mode 100644 .devcontainer.json create mode 100644 .devcontainer/devcontainer.json create mode 100644 .devcontainer/docker-compose.yml diff --git a/.devcontainer.json b/.devcontainer.json deleted file mode 100644 index 75b8759..0000000 --- a/.devcontainer.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "customizations": { - "vscode": { - "extensions": [ - "mkhl.direnv" - ] - } - }, - "image": "ghcr.io/cachix/devenv:latest", - "overrideCommand": false, - "updateContentCommand": "devenv ci" -} diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..1f4b8b1 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,40 @@ +{ + "name": "backend-services", + "dockerComposeFile": "docker-compose.yml", + "service": "devcontainer", + "forwardPorts": [], + "workspaceFolder": "/workspace", + + "userEnvProbe": "loginShell", + "updateRemoteUserUID": false, + + "updateContentCommand": "devenv ci", + + "customizations": { + "vscode": { + "extensions": [ + // run commands on save + // https://marketplace.visualstudio.com/items?itemName=fsevenm.run-it-on + "fsevenm.run-it-on", + + // Docker for Visual Studio Code + "ms-azuretools.vscode-docker", + + // direnv support + "mkhl.direnv", + + // Nix language support + "jnoortheen.nix-ide", + + // Markdown support and previsualization + "yzhang.markdown-all-in-one", + + // Debugging + "vadimcn.vscode-lldb", + + // Allows to use Alt+Q (or Option+Q in mac) to rewrap lines + "stkb.rewrap" + ] + } + } +} diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml new file mode 100644 index 0000000..ddb6a3e --- /dev/null +++ b/.devcontainer/docker-compose.yml @@ -0,0 +1,12 @@ +version: "3.6" +services: + dev: + image: ghcr.io/cachix/devenv:latest + volumes: + - ../../:/workspaces:cached + - /var/run/docker.sock:/var/run/docker-host.sock + # Required for ptrace-based debuggers like C++, Go, and Rust + cap_add: + - SYS_PTRACE + security_opt: + - label:disable diff --git a/devenv.lock b/devenv.lock index 7472bfb..00db773 100644 --- a/devenv.lock +++ b/devenv.lock @@ -50,6 +50,21 @@ } }, "flake-utils": { + "locked": { + "lastModified": 1653893745, + "narHash": "sha256-0jntwV3Z8//YwuOjzhV2sgJJPt+HY6KhU7VZUL0fKZQ=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "1ed9fb1935d260de5fe1c2f7ee0ebaae17ed2fa1", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "flake-utils_2": { "inputs": { "systems": "systems" }, @@ -66,7 +81,7 @@ "type": "indirect" } }, - "flake-utils_2": { + "flake-utils_3": { "inputs": { "systems": "systems_2" }, @@ -105,6 +120,42 @@ "type": "github" } }, + "mk-shell-bin": { + "locked": { + "lastModified": 1677004959, + "narHash": "sha256-/uEkr1UkJrh11vD02aqufCxtbF5YnhRTIKlx5kyvf+I=", + "owner": "rrbutani", + "repo": "nix-mk-shell-bin", + "rev": "ff5d8bd4d68a347be5042e2f16caee391cd75887", + "type": "github" + }, + "original": { + "owner": "rrbutani", + "repo": "nix-mk-shell-bin", + "type": "github" + } + }, + "nix2container": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1688922987, + "narHash": "sha256-RnQwrCD5anqWfyDAVbfFIeU+Ha6cwt5QcIwIkaGRzQw=", + "owner": "nlewo", + "repo": "nix2container", + "rev": "ab381a7d714ebf96a83882264245dbd34f0a7ec8", + "type": "github" + }, + "original": { + "owner": "nlewo", + "repo": "nix2container", + "type": "github" + } + }, "nixpkgs": { "locked": { "lastModified": 1689935543, @@ -124,7 +175,7 @@ "nixpkgs-python": { "inputs": { "flake-compat": "flake-compat", - "flake-utils": "flake-utils", + "flake-utils": "flake-utils_2", "nixpkgs": "nixpkgs_2" }, "locked": { @@ -176,7 +227,7 @@ "pre-commit-hooks": { "inputs": { "flake-compat": "flake-compat_2", - "flake-utils": "flake-utils_2", + "flake-utils": "flake-utils_3", "gitignore": "gitignore", "nixpkgs": [ "nixpkgs" @@ -200,6 +251,8 @@ "root": { "inputs": { "devenv": "devenv", + "mk-shell-bin": "mk-shell-bin", + "nix2container": "nix2container", "nixpkgs": "nixpkgs", "nixpkgs-python": "nixpkgs-python", "pre-commit-hooks": "pre-commit-hooks" diff --git a/devenv.nix b/devenv.nix index e896151..14dc88b 100644 --- a/devenv.nix +++ b/devenv.nix @@ -9,6 +9,9 @@ pkgs.git pkgs.ack + # to create containers + pkgs.docker + # used for building uwsgi: pkgs.gcc pkgs.libffi diff --git a/devenv.yaml b/devenv.yaml index a32e623..ae10034 100644 --- a/devenv.yaml +++ b/devenv.yaml @@ -3,3 +3,10 @@ inputs: url: github:NixOS/nixpkgs/nixpkgs-unstable nixpkgs-python: url: github:cachix/nixpkgs-python + nix2container: + url: github:nlewo/nix2container + inputs: + nixpkgs: + follows: nixpkgs + mk-shell-bin: + url: github:rrbutani/nix-mk-shell-bin \ No newline at end of file From 756845e278c6de7d83ed59bb35418cda23e21952 Mon Sep 17 00:00:00 2001 From: Eduardo Robles Date: Sun, 23 Jul 2023 06:40:06 +0000 Subject: [PATCH 08/26] WIP --- .devcontainer/devcontainer.json | 8 +------- .devcontainer/docker-compose.yml | 2 +- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 1f4b8b1..8b60bff 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,13 +1,7 @@ { - "name": "backend-services", + "name": "election-orchestra", "dockerComposeFile": "docker-compose.yml", "service": "devcontainer", - "forwardPorts": [], - "workspaceFolder": "/workspace", - - "userEnvProbe": "loginShell", - "updateRemoteUserUID": false, - "updateContentCommand": "devenv ci", "customizations": { diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml index ddb6a3e..3848514 100644 --- a/.devcontainer/docker-compose.yml +++ b/.devcontainer/docker-compose.yml @@ -1,6 +1,6 @@ version: "3.6" services: - dev: + devcontainer: image: ghcr.io/cachix/devenv:latest volumes: - ../../:/workspaces:cached From 3797d4eef9a66ec1811f4697846977e346e7f5d0 Mon Sep 17 00:00:00 2001 From: Eduardo Robles Date: Sun, 23 Jul 2023 06:45:42 +0000 Subject: [PATCH 09/26] WIP --- .devcontainer/devcontainer.json | 1 + .devcontainer/docker-compose.yml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 8b60bff..79be5fa 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -3,6 +3,7 @@ "dockerComposeFile": "docker-compose.yml", "service": "devcontainer", "updateContentCommand": "devenv ci", + "workspaceFolder": "/workspace", "customizations": { "vscode": { diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml index 3848514..7c2b094 100644 --- a/.devcontainer/docker-compose.yml +++ b/.devcontainer/docker-compose.yml @@ -3,7 +3,7 @@ services: devcontainer: image: ghcr.io/cachix/devenv:latest volumes: - - ../../:/workspaces:cached + - ../:/workspace:cached - /var/run/docker.sock:/var/run/docker-host.sock # Required for ptrace-based debuggers like C++, Go, and Rust cap_add: From 2247445d5851ade0f6b10aa61a8086c546dbff1d Mon Sep 17 00:00:00 2001 From: Eduardo Robles Date: Sun, 23 Jul 2023 07:52:55 +0000 Subject: [PATCH 10/26] WIP --- .devcontainer/Dockerfile | 36 ++ .devcontainer/docker-compose.yml | 16 +- .../library-scripts/common-debian.sh | 454 ++++++++++++++++++ .../docker-in-docker-debian.sh | 405 ++++++++++++++++ 4 files changed, 908 insertions(+), 3 deletions(-) create mode 100644 .devcontainer/Dockerfile create mode 100644 .devcontainer/library-scripts/common-debian.sh create mode 100644 .devcontainer/library-scripts/docker-in-docker-debian.sh diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 0000000..24fb998 --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,36 @@ +FROM ghcr.io/cachix/devenv:latest + +# [Option] Install zsh +ARG INSTALL_ZSH="true" +# [Option] Upgrade OS packages to their latest versions +ARG UPGRADE_PACKAGES="false" +# [Option] Enable non-root Docker access in container +ARG ENABLE_NONROOT_DOCKER="true" +# [Option] Use the OSS Moby Engine instead of the licensed Docker Engine +ARG USE_MOBY="true" +# [Option] Engine/CLI Version +ARG DOCKER_VERSION="latest" + +# Enable new "BUILDKIT" mode for Docker CLI +ENV DOCKER_BUILDKIT=1 + +# Install needed packages and setup non-root user. Use a separate RUN statement to add your +# own dependencies. A user of "automatic" attempts to reuse an user ID if one already exists. +ARG USERNAME=vscode +ARG USER_UID=1000 +ARG USER_GID=$USER_UID +COPY library-scripts/*.sh /tmp/library-scripts/ +RUN apt-get update +RUN /bin/bash /tmp/library-scripts/common-debian.sh "${INSTALL_ZSH}" "${USERNAME}" "${USER_UID}" "${USER_GID}" "${UPGRADE_PACKAGES}" "true" "true" + +# Use Docker script from script library to set things up +RUN /bin/bash /tmp/library-scripts/docker-in-docker-debian.sh "${ENABLE_NONROOT_DOCKER}" "${USERNAME}" "${USE_MOBY}" "${DOCKER_VERSION}" +RUN apt-get autoremove -y && apt-get clean -y && rm -rf /var/lib/apt/lists/* /tmp/library-scripts/ + +VOLUME [ "/var/lib/docker" ] + +# Setting the ENTRYPOINT to docker-init.sh will start up the Docker Engine +# inside the container "overrideCommand": false is set in devcontainer.json. +# The script will also execute CMD if you need to alter startup behaviors. +ENTRYPOINT [ "/usr/local/share/docker-init.sh" ] +CMD [ "sleep", "infinity" ] diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml index 7c2b094..4d5d15b 100644 --- a/.devcontainer/docker-compose.yml +++ b/.devcontainer/docker-compose.yml @@ -1,10 +1,20 @@ version: "3.6" services: devcontainer: - image: ghcr.io/cachix/devenv:latest + build: + context: . + dockerfile: Dockerfile + volumes: - - ../:/workspace:cached - - /var/run/docker.sock:/var/run/docker-host.sock + # Forwards the local Docker socket to the container. + - /var/run/docker.sock:/var/run/docker-host.sock + # Update this to wherever you want VS Code to mount the folder of your project + - ..:/workspace:cached + + # Overrides default command so things don't shut down after the process ends. + entrypoint: /usr/local/share/docker-init.sh + command: sleep infinity + # Required for ptrace-based debuggers like C++, Go, and Rust cap_add: - SYS_PTRACE diff --git a/.devcontainer/library-scripts/common-debian.sh b/.devcontainer/library-scripts/common-debian.sh new file mode 100644 index 0000000..bf1f9e2 --- /dev/null +++ b/.devcontainer/library-scripts/common-debian.sh @@ -0,0 +1,454 @@ +#!/usr/bin/env bash +#------------------------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information. +#------------------------------------------------------------------------------------------------------------- +# +# Docs: https://github.com/microsoft/vscode-dev-containers/blob/main/script-library/docs/common.md +# Maintainer: The VS Code and Codespaces Teams +# +# Syntax: ./common-debian.sh [install zsh flag] [username] [user UID] [user GID] [upgrade packages flag] [install Oh My Zsh! flag] [Add non-free packages] + +set -e + +INSTALL_ZSH=${1:-"true"} +USERNAME=${2:-"automatic"} +USER_UID=${3:-"automatic"} +USER_GID=${4:-"automatic"} +UPGRADE_PACKAGES=${5:-"true"} +INSTALL_OH_MYS=${6:-"true"} +ADD_NON_FREE_PACKAGES=${7:-"false"} +SCRIPT_DIR="$(cd $(dirname "${BASH_SOURCE[0]}") && pwd)" +MARKER_FILE="/usr/local/etc/vscode-dev-containers/common" + +if [ "$(id -u)" -ne 0 ]; then + echo -e 'Script must be run as root. Use sudo, su, or add "USER root" to your Dockerfile before running this script.' + exit 1 +fi + +# Ensure that login shells get the correct path if the user updated the PATH using ENV. +rm -f /etc/profile.d/00-restore-env.sh +echo "export PATH=${PATH//$(sh -lc 'echo $PATH')/\$PATH}" > /etc/profile.d/00-restore-env.sh +chmod +x /etc/profile.d/00-restore-env.sh + +# If in automatic mode, determine if a user already exists, if not use vscode +if [ "${USERNAME}" = "auto" ] || [ "${USERNAME}" = "automatic" ]; then + USERNAME="" + POSSIBLE_USERS=("vscode" "node" "codespace" "$(awk -v val=1000 -F ":" '$3==val{print $1}' /etc/passwd)") + for CURRENT_USER in ${POSSIBLE_USERS[@]}; do + if id -u ${CURRENT_USER} > /dev/null 2>&1; then + USERNAME=${CURRENT_USER} + break + fi + done + if [ "${USERNAME}" = "" ]; then + USERNAME=vscode + fi +elif [ "${USERNAME}" = "none" ]; then + USERNAME=root + USER_UID=0 + USER_GID=0 +fi + +# Load markers to see which steps have already run +if [ -f "${MARKER_FILE}" ]; then + echo "Marker file found:" + cat "${MARKER_FILE}" + source "${MARKER_FILE}" +fi + +# Ensure apt is in non-interactive to avoid prompts +export DEBIAN_FRONTEND=noninteractive + +# Function to call apt-get if needed +apt_get_update_if_needed() +{ + if [ ! -d "/var/lib/apt/lists" ] || [ "$(ls /var/lib/apt/lists/ | wc -l)" = "0" ]; then + echo "Running apt-get update..." + apt-get update + else + echo "Skipping apt-get update." + fi +} + +# Run install apt-utils to avoid debconf warning then verify presence of other common developer tools and dependencies +if [ "${PACKAGES_ALREADY_INSTALLED}" != "true" ]; then + + package_list="apt-utils \ + openssh-client \ + gnupg2 \ + dirmngr \ + iproute2 \ + procps \ + lsof \ + htop \ + net-tools \ + psmisc \ + curl \ + wget \ + rsync \ + ca-certificates \ + unzip \ + zip \ + nano \ + vim-tiny \ + less \ + jq \ + lsb-release \ + apt-transport-https \ + dialog \ + libc6 \ + libgcc1 \ + libkrb5-3 \ + libgssapi-krb5-2 \ + libicu[0-9][0-9] \ + liblttng-ust[0-9] \ + libstdc++6 \ + zlib1g \ + locales \ + sudo \ + ncdu \ + man-db \ + strace \ + manpages \ + manpages-dev \ + init-system-helpers" + + # Needed for adding manpages-posix and manpages-posix-dev which are non-free packages in Debian + if [ "${ADD_NON_FREE_PACKAGES}" = "true" ]; then + # Bring in variables from /etc/os-release like VERSION_CODENAME + . /etc/os-release + sed -i -E "s/deb http:\/\/(deb|httpredir)\.debian\.org\/debian ${VERSION_CODENAME} main/deb http:\/\/\1\.debian\.org\/debian ${VERSION_CODENAME} main contrib non-free/" /etc/apt/sources.list + sed -i -E "s/deb-src http:\/\/(deb|httredir)\.debian\.org\/debian ${VERSION_CODENAME} main/deb http:\/\/\1\.debian\.org\/debian ${VERSION_CODENAME} main contrib non-free/" /etc/apt/sources.list + sed -i -E "s/deb http:\/\/(deb|httpredir)\.debian\.org\/debian ${VERSION_CODENAME}-updates main/deb http:\/\/\1\.debian\.org\/debian ${VERSION_CODENAME}-updates main contrib non-free/" /etc/apt/sources.list + sed -i -E "s/deb-src http:\/\/(deb|httpredir)\.debian\.org\/debian ${VERSION_CODENAME}-updates main/deb http:\/\/\1\.debian\.org\/debian ${VERSION_CODENAME}-updates main contrib non-free/" /etc/apt/sources.list + sed -i "s/deb http:\/\/security\.debian\.org\/debian-security ${VERSION_CODENAME}\/updates main/deb http:\/\/security\.debian\.org\/debian-security ${VERSION_CODENAME}\/updates main contrib non-free/" /etc/apt/sources.list + sed -i "s/deb-src http:\/\/security\.debian\.org\/debian-security ${VERSION_CODENAME}\/updates main/deb http:\/\/security\.debian\.org\/debian-security ${VERSION_CODENAME}\/updates main contrib non-free/" /etc/apt/sources.list + sed -i "s/deb http:\/\/deb\.debian\.org\/debian ${VERSION_CODENAME}-backports main/deb http:\/\/deb\.debian\.org\/debian ${VERSION_CODENAME}-backports main contrib non-free/" /etc/apt/sources.list + sed -i "s/deb-src http:\/\/deb\.debian\.org\/debian ${VERSION_CODENAME}-backports main/deb http:\/\/deb\.debian\.org\/debian ${VERSION_CODENAME}-backports main contrib non-free/" /etc/apt/sources.list + # Handle bullseye location for security https://www.debian.org/releases/bullseye/amd64/release-notes/ch-information.en.html + sed -i "s/deb http:\/\/security\.debian\.org\/debian-security ${VERSION_CODENAME}-security main/deb http:\/\/security\.debian\.org\/debian-security ${VERSION_CODENAME}-security main contrib non-free/" /etc/apt/sources.list + sed -i "s/deb-src http:\/\/security\.debian\.org\/debian-security ${VERSION_CODENAME}-security main/deb http:\/\/security\.debian\.org\/debian-security ${VERSION_CODENAME}-security main contrib non-free/" /etc/apt/sources.list + echo "Running apt-get update..." + apt-get update + package_list="${package_list} manpages-posix manpages-posix-dev" + else + apt_get_update_if_needed + fi + + # Install libssl1.1 if available + if [[ ! -z $(apt-cache --names-only search ^libssl1.1$) ]]; then + package_list="${package_list} libssl1.1" + fi + + # Install appropriate version of libssl1.0.x if available + libssl_package=$(dpkg-query -f '${db:Status-Abbrev}\t${binary:Package}\n' -W 'libssl1\.0\.?' 2>&1 || echo '') + if [ "$(echo "$LIlibssl_packageBSSL" | grep -o 'libssl1\.0\.[0-9]:' | uniq | sort | wc -l)" -eq 0 ]; then + if [[ ! -z $(apt-cache --names-only search ^libssl1.0.2$) ]]; then + # Debian 9 + package_list="${package_list} libssl1.0.2" + elif [[ ! -z $(apt-cache --names-only search ^libssl1.0.0$) ]]; then + # Ubuntu 18.04, 16.04, earlier + package_list="${package_list} libssl1.0.0" + fi + fi + + echo "Packages to verify are installed: ${package_list}" + apt-get -y install --no-install-recommends ${package_list} 2> >( grep -v 'debconf: delaying package configuration, since apt-utils is not installed' >&2 ) + + # Install git if not already installed (may be more recent than distro version) + if ! type git > /dev/null 2>&1; then + apt-get -y install --no-install-recommends git + fi + + PACKAGES_ALREADY_INSTALLED="true" +fi + +# Get to latest versions of all packages +if [ "${UPGRADE_PACKAGES}" = "true" ]; then + apt_get_update_if_needed + apt-get -y upgrade --no-install-recommends + apt-get autoremove -y +fi + +# Ensure at least the en_US.UTF-8 UTF-8 locale is available. +# Common need for both applications and things like the agnoster ZSH theme. +if [ "${LOCALE_ALREADY_SET}" != "true" ] && ! grep -o -E '^\s*en_US.UTF-8\s+UTF-8' /etc/locale.gen > /dev/null; then + echo "en_US.UTF-8 UTF-8" >> /etc/locale.gen + locale-gen + LOCALE_ALREADY_SET="true" +fi + +# Create or update a non-root user to match UID/GID. +group_name="${USERNAME}" +if id -u ${USERNAME} > /dev/null 2>&1; then + # User exists, update if needed + if [ "${USER_GID}" != "automatic" ] && [ "$USER_GID" != "$(id -g $USERNAME)" ]; then + group_name="$(id -gn $USERNAME)" + groupmod --gid $USER_GID ${group_name} + usermod --gid $USER_GID $USERNAME + fi + if [ "${USER_UID}" != "automatic" ] && [ "$USER_UID" != "$(id -u $USERNAME)" ]; then + usermod --uid $USER_UID $USERNAME + fi +else + # Create user + if [ "${USER_GID}" = "automatic" ]; then + groupadd $USERNAME + else + groupadd --gid $USER_GID $USERNAME + fi + if [ "${USER_UID}" = "automatic" ]; then + useradd -s /bin/bash --gid $USERNAME -m $USERNAME + else + useradd -s /bin/bash --uid $USER_UID --gid $USERNAME -m $USERNAME + fi +fi + +# Add sudo support for non-root user +if [ "${USERNAME}" != "root" ] && [ "${EXISTING_NON_ROOT_USER}" != "${USERNAME}" ]; then + echo $USERNAME ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/$USERNAME + chmod 0440 /etc/sudoers.d/$USERNAME + EXISTING_NON_ROOT_USER="${USERNAME}" +fi + +# ** Shell customization section ** +if [ "${USERNAME}" = "root" ]; then + user_rc_path="/root" +else + user_rc_path="/home/${USERNAME}" +fi + +# Restore user .bashrc defaults from skeleton file if it doesn't exist or is empty +if [ ! -f "${user_rc_path}/.bashrc" ] || [ ! -s "${user_rc_path}/.bashrc" ] ; then + cp /etc/skel/.bashrc "${user_rc_path}/.bashrc" +fi + +# Restore user .profile defaults from skeleton file if it doesn't exist or is empty +if [ ! -f "${user_rc_path}/.profile" ] || [ ! -s "${user_rc_path}/.profile" ] ; then + cp /etc/skel/.profile "${user_rc_path}/.profile" +fi + +# .bashrc/.zshrc snippet +rc_snippet="$(cat << 'EOF' + +if [ -z "${USER}" ]; then export USER=$(whoami); fi +if [[ "${PATH}" != *"$HOME/.local/bin"* ]]; then export PATH="${PATH}:$HOME/.local/bin"; fi + +# Display optional first run image specific notice if configured and terminal is interactive +if [ -t 1 ] && [[ "${TERM_PROGRAM}" = "vscode" || "${TERM_PROGRAM}" = "codespaces" ]] && [ ! -f "$HOME/.config/vscode-dev-containers/first-run-notice-already-displayed" ]; then + if [ -f "/usr/local/etc/vscode-dev-containers/first-run-notice.txt" ]; then + cat "/usr/local/etc/vscode-dev-containers/first-run-notice.txt" + elif [ -f "/workspaces/.codespaces/shared/first-run-notice.txt" ]; then + cat "/workspaces/.codespaces/shared/first-run-notice.txt" + fi + mkdir -p "$HOME/.config/vscode-dev-containers" + # Mark first run notice as displayed after 10s to avoid problems with fast terminal refreshes hiding it + ((sleep 10s; touch "$HOME/.config/vscode-dev-containers/first-run-notice-already-displayed") &) +fi + +# Set the default git editor if not already set +if [ -z "$(git config --get core.editor)" ] && [ -z "${GIT_EDITOR}" ]; then + if [ "${TERM_PROGRAM}" = "vscode" ]; then + if [[ -n $(command -v code-insiders) && -z $(command -v code) ]]; then + export GIT_EDITOR="code-insiders --wait" + else + export GIT_EDITOR="code --wait" + fi + fi +fi + +EOF +)" + +# code shim, it fallbacks to code-insiders if code is not available +cat << 'EOF' > /usr/local/bin/code +#!/bin/sh + +get_in_path_except_current() { + which -a "$1" | grep -A1 "$0" | grep -v "$0" +} + +code="$(get_in_path_except_current code)" + +if [ -n "$code" ]; then + exec "$code" "$@" +elif [ "$(command -v code-insiders)" ]; then + exec code-insiders "$@" +else + echo "code or code-insiders is not installed" >&2 + exit 127 +fi +EOF +chmod +x /usr/local/bin/code + +# systemctl shim - tells people to use 'service' if systemd is not running +cat << 'EOF' > /usr/local/bin/systemctl +#!/bin/sh +set -e +if [ -d "/run/systemd/system" ]; then + exec /bin/systemctl "$@" +else + echo '\n"systemd" is not running in this container due to its overhead.\nUse the "service" command to start services instead. e.g.: \n\nservice --status-all' +fi +EOF +chmod +x /usr/local/bin/systemctl + +# Codespaces bash and OMZ themes - partly inspired by https://github.com/ohmyzsh/ohmyzsh/blob/master/themes/robbyrussell.zsh-theme +codespaces_bash="$(cat \ +<<'EOF' + +# Codespaces bash prompt theme +__bash_prompt() { + local userpart='`export XIT=$? \ + && [ ! -z "${GITHUB_USER}" ] && echo -n "\[\033[0;32m\]@${GITHUB_USER} " || echo -n "\[\033[0;32m\]\u " \ + && [ "$XIT" -ne "0" ] && echo -n "\[\033[1;31m\]➜" || echo -n "\[\033[0m\]➜"`' + local gitbranch='`\ + if [ "$(git config --get codespaces-theme.hide-status 2>/dev/null)" != 1 ]; then \ + export BRANCH=$(git symbolic-ref --short HEAD 2>/dev/null || git rev-parse --short HEAD 2>/dev/null); \ + if [ "${BRANCH}" != "" ]; then \ + echo -n "\[\033[0;36m\](\[\033[1;31m\]${BRANCH}" \ + && if git ls-files --error-unmatch -m --directory --no-empty-directory -o --exclude-standard ":/*" > /dev/null 2>&1; then \ + echo -n " \[\033[1;33m\]✗"; \ + fi \ + && echo -n "\[\033[0;36m\]) "; \ + fi; \ + fi`' + local lightblue='\[\033[1;34m\]' + local removecolor='\[\033[0m\]' + PS1="${userpart} ${lightblue}\w ${gitbranch}${removecolor}\$ " + unset -f __bash_prompt +} +__bash_prompt + +EOF +)" + +codespaces_zsh="$(cat \ +<<'EOF' +# Codespaces zsh prompt theme +__zsh_prompt() { + local prompt_username + if [ ! -z "${GITHUB_USER}" ]; then + prompt_username="@${GITHUB_USER}" + else + prompt_username="%n" + fi + PROMPT="%{$fg[green]%}${prompt_username} %(?:%{$reset_color%}➜ :%{$fg_bold[red]%}➜ )" # User/exit code arrow + PROMPT+='%{$fg_bold[blue]%}%(5~|%-1~/…/%3~|%4~)%{$reset_color%} ' # cwd + PROMPT+='$([ "$(git config --get codespaces-theme.hide-status 2>/dev/null)" != 1 ] && git_prompt_info)' # Git status + PROMPT+='%{$fg[white]%}$ %{$reset_color%}' + unset -f __zsh_prompt +} +ZSH_THEME_GIT_PROMPT_PREFIX="%{$fg_bold[cyan]%}(%{$fg_bold[red]%}" +ZSH_THEME_GIT_PROMPT_SUFFIX="%{$reset_color%} " +ZSH_THEME_GIT_PROMPT_DIRTY=" %{$fg_bold[yellow]%}✗%{$fg_bold[cyan]%})" +ZSH_THEME_GIT_PROMPT_CLEAN="%{$fg_bold[cyan]%})" +__zsh_prompt + +EOF +)" + +# Add RC snippet and custom bash prompt +if [ "${RC_SNIPPET_ALREADY_ADDED}" != "true" ]; then + echo "${rc_snippet}" >> /etc/bash.bashrc + echo "${codespaces_bash}" >> "${user_rc_path}/.bashrc" + echo 'export PROMPT_DIRTRIM=4' >> "${user_rc_path}/.bashrc" + if [ "${USERNAME}" != "root" ]; then + echo "${codespaces_bash}" >> "/root/.bashrc" + echo 'export PROMPT_DIRTRIM=4' >> "/root/.bashrc" + fi + chown ${USERNAME}:${group_name} "${user_rc_path}/.bashrc" + RC_SNIPPET_ALREADY_ADDED="true" +fi + +# Optionally install and configure zsh and Oh My Zsh! +if [ "${INSTALL_ZSH}" = "true" ]; then + if ! type zsh > /dev/null 2>&1; then + apt_get_update_if_needed + apt-get install -y zsh + fi + if [ "${ZSH_ALREADY_INSTALLED}" != "true" ]; then + echo "${rc_snippet}" >> /etc/zsh/zshrc + ZSH_ALREADY_INSTALLED="true" + fi + + # Adapted, simplified inline Oh My Zsh! install steps that adds, defaults to a codespaces theme. + # See https://github.com/ohmyzsh/ohmyzsh/blob/master/tools/install.sh for official script. + oh_my_install_dir="${user_rc_path}/.oh-my-zsh" + if [ ! -d "${oh_my_install_dir}" ] && [ "${INSTALL_OH_MYS}" = "true" ]; then + template_path="${oh_my_install_dir}/templates/zshrc.zsh-template" + user_rc_file="${user_rc_path}/.zshrc" + umask g-w,o-w + mkdir -p ${oh_my_install_dir} + git clone --depth=1 \ + -c core.eol=lf \ + -c core.autocrlf=false \ + -c fsck.zeroPaddedFilemode=ignore \ + -c fetch.fsck.zeroPaddedFilemode=ignore \ + -c receive.fsck.zeroPaddedFilemode=ignore \ + "https://github.com/ohmyzsh/ohmyzsh" "${oh_my_install_dir}" 2>&1 + echo -e "$(cat "${template_path}")\nDISABLE_AUTO_UPDATE=true\nDISABLE_UPDATE_PROMPT=true" > ${user_rc_file} + sed -i -e 's/ZSH_THEME=.*/ZSH_THEME="codespaces"/g' ${user_rc_file} + + mkdir -p ${oh_my_install_dir}/custom/themes + echo "${codespaces_zsh}" > "${oh_my_install_dir}/custom/themes/codespaces.zsh-theme" + # Shrink git while still enabling updates + cd "${oh_my_install_dir}" + git repack -a -d -f --depth=1 --window=1 + # Copy to non-root user if one is specified + if [ "${USERNAME}" != "root" ]; then + cp -rf "${user_rc_file}" "${oh_my_install_dir}" /root + chown -R ${USERNAME}:${group_name} "${user_rc_path}" + fi + fi +fi + +# Persist image metadata info, script if meta.env found in same directory +meta_info_script="$(cat << 'EOF' +#!/bin/sh +. /usr/local/etc/vscode-dev-containers/meta.env + +# Minimal output +if [ "$1" = "version" ] || [ "$1" = "image-version" ]; then + echo "${VERSION}" + exit 0 +elif [ "$1" = "release" ]; then + echo "${GIT_REPOSITORY_RELEASE}" + exit 0 +elif [ "$1" = "content" ] || [ "$1" = "content-url" ] || [ "$1" = "contents" ] || [ "$1" = "contents-url" ]; then + echo "${CONTENTS_URL}" + exit 0 +fi + +#Full output +echo +echo "Development container image information" +echo +if [ ! -z "${VERSION}" ]; then echo "- Image version: ${VERSION}"; fi +if [ ! -z "${DEFINITION_ID}" ]; then echo "- Definition ID: ${DEFINITION_ID}"; fi +if [ ! -z "${VARIANT}" ]; then echo "- Variant: ${VARIANT}"; fi +if [ ! -z "${GIT_REPOSITORY}" ]; then echo "- Source code repository: ${GIT_REPOSITORY}"; fi +if [ ! -z "${GIT_REPOSITORY_RELEASE}" ]; then echo "- Source code release/branch: ${GIT_REPOSITORY_RELEASE}"; fi +if [ ! -z "${BUILD_TIMESTAMP}" ]; then echo "- Timestamp: ${BUILD_TIMESTAMP}"; fi +if [ ! -z "${CONTENTS_URL}" ]; then echo && echo "More info: ${CONTENTS_URL}"; fi +echo +EOF +)" +if [ -f "${SCRIPT_DIR}/meta.env" ]; then + mkdir -p /usr/local/etc/vscode-dev-containers/ + cp -f "${SCRIPT_DIR}/meta.env" /usr/local/etc/vscode-dev-containers/meta.env + echo "${meta_info_script}" > /usr/local/bin/devcontainer-info + chmod +x /usr/local/bin/devcontainer-info +fi + +# Write marker file +mkdir -p "$(dirname "${MARKER_FILE}")" +echo -e "\ + PACKAGES_ALREADY_INSTALLED=${PACKAGES_ALREADY_INSTALLED}\n\ + LOCALE_ALREADY_SET=${LOCALE_ALREADY_SET}\n\ + EXISTING_NON_ROOT_USER=${EXISTING_NON_ROOT_USER}\n\ + RC_SNIPPET_ALREADY_ADDED=${RC_SNIPPET_ALREADY_ADDED}\n\ + ZSH_ALREADY_INSTALLED=${ZSH_ALREADY_INSTALLED}" > "${MARKER_FILE}" + +echo "Done!" \ No newline at end of file diff --git a/.devcontainer/library-scripts/docker-in-docker-debian.sh b/.devcontainer/library-scripts/docker-in-docker-debian.sh new file mode 100644 index 0000000..88603a9 --- /dev/null +++ b/.devcontainer/library-scripts/docker-in-docker-debian.sh @@ -0,0 +1,405 @@ +#!/usr/bin/env bash +#------------------------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information. +#------------------------------------------------------------------------------------------------------------- +# +# Docs: https://github.com/microsoft/vscode-dev-containers/blob/main/script-library/docs/docker-in-docker.md +# Maintainer: The VS Code and Codespaces Teams +# +# Syntax: ./docker-in-docker-debian.sh [enable non-root docker access flag] [non-root user] [use moby] [Engine/CLI Version] [Major version for docker-compose] [azure DNS auto detection flag] + +ENABLE_NONROOT_DOCKER=${1:-"true"} +USERNAME=${2:-"automatic"} +USE_MOBY=${3:-"true"} +DOCKER_VERSION=${4:-"latest"} # The Docker/Moby Engine + CLI should match in version +DOCKER_DASH_COMPOSE_VERSION=${5:-"v1"} # v1 or v2 +AZURE_DNS_AUTO_DETECTION=${6:-"true"} +MICROSOFT_GPG_KEYS_URI="https://packages.microsoft.com/keys/microsoft.asc" +DOCKER_MOBY_ARCHIVE_VERSION_CODENAMES="buster bullseye bionic focal jammy" +DOCKER_LICENSED_ARCHIVE_VERSION_CODENAMES="buster bullseye bionic focal hirsute impish jammy" + +# Default: Exit on any failure. +set -e + +# Setup STDERR. +err() { + echo "(!) $*" >&2 +} + +if [ "$(id -u)" -ne 0 ]; then + err 'Script must be run as root. Use sudo, su, or add "USER root" to your Dockerfile before running this script.' + exit 1 +fi + +################### +# Helper Functions +# See: https://github.com/microsoft/vscode-dev-containers/blob/main/script-library/shared/utils.sh +################### + +# Determine the appropriate non-root user +if [ "${USERNAME}" = "auto" ] || [ "${USERNAME}" = "automatic" ]; then + USERNAME="" + POSSIBLE_USERS=("vscode" "node" "codespace" "$(awk -v val=1000 -F ":" '$3==val{print $1}' /etc/passwd)") + for CURRENT_USER in ${POSSIBLE_USERS[@]}; do + if id -u ${CURRENT_USER} > /dev/null 2>&1; then + USERNAME=${CURRENT_USER} + break + fi + done + if [ "${USERNAME}" = "" ]; then + USERNAME=root + fi +elif [ "${USERNAME}" = "none" ] || ! id -u ${USERNAME} > /dev/null 2>&1; then + USERNAME=root +fi + +# Get central common setting +get_common_setting() { + if [ "${common_settings_file_loaded}" != "true" ]; then + curl -sfL "https://aka.ms/vscode-dev-containers/script-library/settings.env" 2>/dev/null -o /tmp/vsdc-settings.env || echo "Could not download settings file. Skipping." + common_settings_file_loaded=true + fi + if [ -f "/tmp/vsdc-settings.env" ]; then + local multi_line="" + if [ "$2" = "true" ]; then multi_line="-z"; fi + local result="$(grep ${multi_line} -oP "$1=\"?\K[^\"]+" /tmp/vsdc-settings.env | tr -d '\0')" + if [ ! -z "${result}" ]; then declare -g $1="${result}"; fi + fi + echo "$1=${!1}" +} + +# Function to run apt-get if needed +apt_get_update_if_needed() +{ + if [ ! -d "/var/lib/apt/lists" ] || [ "$(ls /var/lib/apt/lists/ | wc -l)" = "0" ]; then + echo "Running apt-get update..." + apt-get update + else + echo "Skipping apt-get update." + fi +} + +# Checks if packages are installed and installs them if not +check_packages() { + if ! dpkg -s "$@" > /dev/null 2>&1; then + apt_get_update_if_needed + apt-get -y install --no-install-recommends "$@" + fi +} + +# Figure out correct version of a three part version number is not passed +find_version_from_git_tags() { + local variable_name=$1 + local requested_version=${!variable_name} + if [ "${requested_version}" = "none" ]; then return; fi + local repository=$2 + local prefix=${3:-"tags/v"} + local separator=${4:-"."} + local last_part_optional=${5:-"false"} + if [ "$(echo "${requested_version}" | grep -o "." | wc -l)" != "2" ]; then + local escaped_separator=${separator//./\\.} + local last_part + if [ "${last_part_optional}" = "true" ]; then + last_part="(${escaped_separator}[0-9]+)?" + else + last_part="${escaped_separator}[0-9]+" + fi + local regex="${prefix}\\K[0-9]+${escaped_separator}[0-9]+${last_part}$" + local version_list="$(git ls-remote --tags ${repository} | grep -oP "${regex}" | tr -d ' ' | tr "${separator}" "." | sort -rV)" + if [ "${requested_version}" = "latest" ] || [ "${requested_version}" = "current" ] || [ "${requested_version}" = "lts" ]; then + declare -g ${variable_name}="$(echo "${version_list}" | head -n 1)" + else + set +e + declare -g ${variable_name}="$(echo "${version_list}" | grep -E -m 1 "^${requested_version//./\\.}([\\.\\s]|$)")" + set -e + fi + fi + if [ -z "${!variable_name}" ] || ! echo "${version_list}" | grep "^${!variable_name//./\\.}$" > /dev/null 2>&1; then + err "Invalid ${variable_name} value: ${requested_version}\nValid values:\n${version_list}" >&2 + exit 1 + fi + echo "${variable_name}=${!variable_name}" +} + +########################################### +# Start docker-in-docker installation +########################################### + +# Ensure apt is in non-interactive to avoid prompts +export DEBIAN_FRONTEND=noninteractive + + +# Source /etc/os-release to get OS info +. /etc/os-release +# Fetch host/container arch. +architecture="$(dpkg --print-architecture)" + +# Check if distro is suppported +if [ "${USE_MOBY}" = "true" ]; then + # 'get_common_setting' allows attribute to be updated remotely + get_common_setting DOCKER_MOBY_ARCHIVE_VERSION_CODENAMES + if [[ "${DOCKER_MOBY_ARCHIVE_VERSION_CODENAMES}" != *"${VERSION_CODENAME}"* ]]; then + err "Unsupported distribution version '${VERSION_CODENAME}'. To resolve, either: (1) set feature option '\"moby\": false' , or (2) choose a compatible OS distribution" + err "Support distributions include: ${DOCKER_MOBY_ARCHIVE_VERSION_CODENAMES}" + exit 1 + fi + echo "Distro codename '${VERSION_CODENAME}' matched filter '${DOCKER_MOBY_ARCHIVE_VERSION_CODENAMES}'" +else + get_common_setting DOCKER_LICENSED_ARCHIVE_VERSION_CODENAMES + if [[ "${DOCKER_LICENSED_ARCHIVE_VERSION_CODENAMES}" != *"${VERSION_CODENAME}"* ]]; then + err "Unsupported distribution version '${VERSION_CODENAME}'. To resolve, please choose a compatible OS distribution" + err "Support distributions include: ${DOCKER_LICENSED_ARCHIVE_VERSION_CODENAMES}" + exit 1 + fi + echo "Distro codename '${VERSION_CODENAME}' matched filter '${DOCKER_LICENSED_ARCHIVE_VERSION_CODENAMES}'" +fi + +# Install dependencies +check_packages apt-transport-https curl ca-certificates pigz iptables gnupg2 dirmngr +if ! type git > /dev/null 2>&1; then + apt_get_update_if_needed + apt-get -y install git +fi + +# Swap to legacy iptables for compatibility +if type iptables-legacy > /dev/null 2>&1; then + update-alternatives --set iptables /usr/sbin/iptables-legacy + update-alternatives --set ip6tables /usr/sbin/ip6tables-legacy +fi + + + +# Set up the necessary apt repos (either Microsoft's or Docker's) +if [ "${USE_MOBY}" = "true" ]; then + + # Name of open source engine/cli + engine_package_name="moby-engine" + cli_package_name="moby-cli" + + # Import key safely and import Microsoft apt repo + get_common_setting MICROSOFT_GPG_KEYS_URI + curl -sSL ${MICROSOFT_GPG_KEYS_URI} | gpg --dearmor > /usr/share/keyrings/microsoft-archive-keyring.gpg + echo "deb [arch=${architecture} signed-by=/usr/share/keyrings/microsoft-archive-keyring.gpg] https://packages.microsoft.com/repos/microsoft-${ID}-${VERSION_CODENAME}-prod ${VERSION_CODENAME} main" > /etc/apt/sources.list.d/microsoft.list +else + # Name of licensed engine/cli + engine_package_name="docker-ce" + cli_package_name="docker-ce-cli" + + # Import key safely and import Docker apt repo + curl -fsSL https://download.docker.com/linux/${ID}/gpg | gpg --dearmor > /usr/share/keyrings/docker-archive-keyring.gpg + echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/${ID} ${VERSION_CODENAME} stable" > /etc/apt/sources.list.d/docker.list +fi + +# Refresh apt lists +apt-get update + +# Soft version matching +if [ "${DOCKER_VERSION}" = "latest" ] || [ "${DOCKER_VERSION}" = "lts" ] || [ "${DOCKER_VERSION}" = "stable" ]; then + # Empty, meaning grab whatever "latest" is in apt repo + engine_version_suffix="" + cli_version_suffix="" +else + # Fetch a valid version from the apt-cache (eg: the Microsoft repo appends +azure, breakfix, etc...) + docker_version_dot_escaped="${DOCKER_VERSION//./\\.}" + docker_version_dot_plus_escaped="${docker_version_dot_escaped//+/\\+}" + # Regex needs to handle debian package version number format: https://www.systutorials.com/docs/linux/man/5-deb-version/ + docker_version_regex="^(.+:)?${docker_version_dot_plus_escaped}([\\.\\+ ~:-]|$)" + set +e # Don't exit if finding version fails - will handle gracefully + cli_version_suffix="=$(apt-cache madison ${cli_package_name} | awk -F"|" '{print $2}' | sed -e 's/^[ \t]*//' | grep -E -m 1 "${docker_version_regex}")" + engine_version_suffix="=$(apt-cache madison ${engine_package_name} | awk -F"|" '{print $2}' | sed -e 's/^[ \t]*//' | grep -E -m 1 "${docker_version_regex}")" + set -e + if [ -z "${engine_version_suffix}" ] || [ "${engine_version_suffix}" = "=" ] || [ -z "${cli_version_suffix}" ] || [ "${cli_version_suffix}" = "=" ] ; then + err "No full or partial Docker / Moby version match found for \"${DOCKER_VERSION}\" on OS ${ID} ${VERSION_CODENAME} (${architecture}). Available versions:" + apt-cache madison ${cli_package_name} | awk -F"|" '{print $2}' | grep -oP '^(.+:)?\K.+' + exit 1 + fi + echo "engine_version_suffix ${engine_version_suffix}" + echo "cli_version_suffix ${cli_version_suffix}" +fi + +# Install Docker / Moby CLI if not already installed +if type docker > /dev/null 2>&1 && type dockerd > /dev/null 2>&1; then + echo "Docker / Moby CLI and Engine already installed." +else + if [ "${USE_MOBY}" = "true" ]; then + # Install engine + set +e # Handle error gracefully + apt-get -y install --no-install-recommends moby-cli${cli_version_suffix} moby-buildx moby-engine${engine_version_suffix} + if [ $? -ne 0 ]; then + err "Packages for moby not available in OS ${ID} ${VERSION_CODENAME} (${architecture}). To resolve, either: (1) set feature option '\"moby\": false' , or (2) choose a compatible OS version (eg: 'ubuntu-20.04')." + exit 1 + fi + set -e + + # Install compose + apt-get -y install --no-install-recommends moby-compose || err "Package moby-compose (Docker Compose v2) not available for OS ${ID} ${VERSION_CODENAME} (${architecture}). Skipping." + else + apt-get -y install --no-install-recommends docker-ce-cli${cli_version_suffix} docker-ce${engine_version_suffix} + # Install compose + apt-get -y install --no-install-recommends docker-compose-plugin || echo "(*) Package docker-compose-plugin (Docker Compose v2) not available for OS ${ID} ${VERSION_CODENAME} (${architecture}). Skipping." + fi +fi + +echo "Finished installing docker / moby!" + +# Install Docker Compose if not already installed and is on a supported architecture +if type docker-compose > /dev/null 2>&1; then + echo "Docker Compose v1 already installed." +else + target_compose_arch="${architecture}" + if [ "${target_compose_arch}" = "amd64" ]; then + target_compose_arch="x86_64" + fi + if [ "${target_compose_arch}" != "x86_64" ]; then + # Use pip to get a version that runs on this architecture + if ! dpkg -s python3-minimal python3-pip libffi-dev python3-venv > /dev/null 2>&1; then + apt_get_update_if_needed + apt-get -y install python3-minimal python3-pip libffi-dev python3-venv + fi + export PIPX_HOME=/usr/local/pipx + mkdir -p ${PIPX_HOME} + export PIPX_BIN_DIR=/usr/local/bin + export PYTHONUSERBASE=/tmp/pip-tmp + export PIP_CACHE_DIR=/tmp/pip-tmp/cache + pipx_bin=pipx + if ! type pipx > /dev/null 2>&1; then + pip3 install --disable-pip-version-check --no-cache-dir --user pipx + pipx_bin=/tmp/pip-tmp/bin/pipx + fi + ${pipx_bin} install --pip-args '--no-cache-dir --force-reinstall' docker-compose + rm -rf /tmp/pip-tmp + else + compose_v1_version="1" + find_version_from_git_tags compose_v1_version "https://github.com/docker/compose" "tags/" + echo "(*) Installing docker-compose ${compose_v1_version}..." + curl -fsSL "https://github.com/docker/compose/releases/download/${compose_v1_version}/docker-compose-Linux-x86_64" -o /usr/local/bin/docker-compose + chmod +x /usr/local/bin/docker-compose + fi +fi + +# Install docker-compose switch if not already installed - https://github.com/docker/compose-switch#manual-installation +current_v1_compose_path="$(which docker-compose)" +target_v1_compose_path="$(dirname "${current_v1_compose_path}")/docker-compose-v1" +if ! type compose-switch > /dev/null 2>&1; then + echo "(*) Installing compose-switch..." + compose_switch_version="latest" + find_version_from_git_tags compose_switch_version "https://github.com/docker/compose-switch" + curl -fsSL "https://github.com/docker/compose-switch/releases/download/v${compose_switch_version}/docker-compose-linux-${architecture}" -o /usr/local/bin/compose-switch + chmod +x /usr/local/bin/compose-switch + # TODO: Verify checksum once available: https://github.com/docker/compose-switch/issues/11 + + # Setup v1 CLI as alternative in addition to compose-switch (which maps to v2) + mv "${current_v1_compose_path}" "${target_v1_compose_path}" + update-alternatives --install /usr/local/bin/docker-compose docker-compose /usr/local/bin/compose-switch 99 + update-alternatives --install /usr/local/bin/docker-compose docker-compose "${target_v1_compose_path}" 1 +fi +if [ "${DOCKER_DASH_COMPOSE_VERSION}" = "v1" ]; then + update-alternatives --set docker-compose "${target_v1_compose_path}" +else + update-alternatives --set docker-compose /usr/local/bin/compose-switch +fi + +# If init file already exists, exit +if [ -f "/usr/local/share/docker-init.sh" ]; then + echo "/usr/local/share/docker-init.sh already exists, so exiting." + exit 0 +fi +echo "docker-init doesnt exist, adding..." + +# Add user to the docker group +if [ "${ENABLE_NONROOT_DOCKER}" = "true" ]; then + if ! getent group docker > /dev/null 2>&1; then + groupadd docker + fi + + usermod -aG docker ${USERNAME} +fi + +tee /usr/local/share/docker-init.sh > /dev/null \ +<< EOF +#!/bin/sh +#------------------------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information. +#------------------------------------------------------------------------------------------------------------- + +set -e + +AZURE_DNS_AUTO_DETECTION=$AZURE_DNS_AUTO_DETECTION +EOF + +tee -a /usr/local/share/docker-init.sh > /dev/null \ +<< 'EOF' +dockerd_start="$(cat << 'INNEREOF' + # explicitly remove dockerd and containerd PID file to ensure that it can start properly if it was stopped uncleanly + # ie: docker kill + find /run /var/run -iname 'docker*.pid' -delete || : + find /run /var/run -iname 'container*.pid' -delete || : + + ## Dind wrapper script from docker team, adapted to a function + # Maintained: https://github.com/moby/moby/blob/master/hack/dind + + export container=docker + + if [ -d /sys/kernel/security ] && ! mountpoint -q /sys/kernel/security; then + mount -t securityfs none /sys/kernel/security || { + echo >&2 'Could not mount /sys/kernel/security.' + echo >&2 'AppArmor detection and --privileged mode might break.' + } + fi + + # Mount /tmp (conditionally) + if ! mountpoint -q /tmp; then + mount -t tmpfs none /tmp + fi + + # cgroup v2: enable nesting + if [ -f /sys/fs/cgroup/cgroup.controllers ]; then + # move the processes from the root group to the /init group, + # otherwise writing subtree_control fails with EBUSY. + # An error during moving non-existent process (i.e., "cat") is ignored. + mkdir -p /sys/fs/cgroup/init + xargs -rn1 < /sys/fs/cgroup/cgroup.procs > /sys/fs/cgroup/init/cgroup.procs || : + # enable controllers + sed -e 's/ / +/g' -e 's/^/+/' < /sys/fs/cgroup/cgroup.controllers \ + > /sys/fs/cgroup/cgroup.subtree_control + fi + ## Dind wrapper over. + + # Handle DNS + set +e + cat /etc/resolv.conf | grep -i 'internal.cloudapp.net' + if [ $? -eq 0 ] && [ ${AZURE_DNS_AUTO_DETECTION} = "true" ] + then + echo "Setting dockerd Azure DNS." + CUSTOMDNS="--dns 168.63.129.16" + else + echo "Not setting dockerd DNS manually." + CUSTOMDNS="" + fi + set -e + + # Start docker/moby engine + ( dockerd $CUSTOMDNS > /tmp/dockerd.log 2>&1 ) & +INNEREOF +)" + +# Start using sudo if not invoked as root +if [ "$(id -u)" -ne 0 ]; then + sudo /bin/sh -c "${dockerd_start}" +else + eval "${dockerd_start}" +fi + +set +e + +# Execute whatever commands were passed in (if any). This allows us +# to set this script to ENTRYPOINT while still executing the default CMD. +exec "$@" +EOF + +chmod +x /usr/local/share/docker-init.sh +chown ${USERNAME}:root /usr/local/share/docker-init.sh + +echo 'docker-in-docker-debian script has completed!' \ No newline at end of file From a57ba43d7a9c686982415bca58ddbf4984bbe0e5 Mon Sep 17 00:00:00 2001 From: Eduardo Robles Date: Sun, 23 Jul 2023 09:14:17 +0000 Subject: [PATCH 11/26] docker from docker works now --- .devcontainer/Dockerfile | 36 -- .devcontainer/devcontainer.json | 4 +- .devcontainer/docker-compose.yml | 12 +- .../library-scripts/common-debian.sh | 454 ------------------ .../docker-in-docker-debian.sh | 405 ---------------- devenv.nix | 3 - 6 files changed, 4 insertions(+), 910 deletions(-) delete mode 100644 .devcontainer/Dockerfile delete mode 100644 .devcontainer/library-scripts/common-debian.sh delete mode 100644 .devcontainer/library-scripts/docker-in-docker-debian.sh diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile deleted file mode 100644 index 24fb998..0000000 --- a/.devcontainer/Dockerfile +++ /dev/null @@ -1,36 +0,0 @@ -FROM ghcr.io/cachix/devenv:latest - -# [Option] Install zsh -ARG INSTALL_ZSH="true" -# [Option] Upgrade OS packages to their latest versions -ARG UPGRADE_PACKAGES="false" -# [Option] Enable non-root Docker access in container -ARG ENABLE_NONROOT_DOCKER="true" -# [Option] Use the OSS Moby Engine instead of the licensed Docker Engine -ARG USE_MOBY="true" -# [Option] Engine/CLI Version -ARG DOCKER_VERSION="latest" - -# Enable new "BUILDKIT" mode for Docker CLI -ENV DOCKER_BUILDKIT=1 - -# Install needed packages and setup non-root user. Use a separate RUN statement to add your -# own dependencies. A user of "automatic" attempts to reuse an user ID if one already exists. -ARG USERNAME=vscode -ARG USER_UID=1000 -ARG USER_GID=$USER_UID -COPY library-scripts/*.sh /tmp/library-scripts/ -RUN apt-get update -RUN /bin/bash /tmp/library-scripts/common-debian.sh "${INSTALL_ZSH}" "${USERNAME}" "${USER_UID}" "${USER_GID}" "${UPGRADE_PACKAGES}" "true" "true" - -# Use Docker script from script library to set things up -RUN /bin/bash /tmp/library-scripts/docker-in-docker-debian.sh "${ENABLE_NONROOT_DOCKER}" "${USERNAME}" "${USE_MOBY}" "${DOCKER_VERSION}" -RUN apt-get autoremove -y && apt-get clean -y && rm -rf /var/lib/apt/lists/* /tmp/library-scripts/ - -VOLUME [ "/var/lib/docker" ] - -# Setting the ENTRYPOINT to docker-init.sh will start up the Docker Engine -# inside the container "overrideCommand": false is set in devcontainer.json. -# The script will also execute CMD if you need to alter startup behaviors. -ENTRYPOINT [ "/usr/local/share/docker-init.sh" ] -CMD [ "sleep", "infinity" ] diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 79be5fa..a6080d3 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -4,7 +4,9 @@ "service": "devcontainer", "updateContentCommand": "devenv ci", "workspaceFolder": "/workspace", - + "features": { + "ghcr.io/devcontainers/features/docker-outside-of-docker:1": {} + }, "customizations": { "vscode": { "extensions": [ diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml index 4d5d15b..0f2a300 100644 --- a/.devcontainer/docker-compose.yml +++ b/.devcontainer/docker-compose.yml @@ -1,20 +1,10 @@ version: "3.6" services: devcontainer: - build: - context: . - dockerfile: Dockerfile - + image: ghcr.io/cachix/devenv:latest volumes: - # Forwards the local Docker socket to the container. - - /var/run/docker.sock:/var/run/docker-host.sock # Update this to wherever you want VS Code to mount the folder of your project - ..:/workspace:cached - - # Overrides default command so things don't shut down after the process ends. - entrypoint: /usr/local/share/docker-init.sh - command: sleep infinity - # Required for ptrace-based debuggers like C++, Go, and Rust cap_add: - SYS_PTRACE diff --git a/.devcontainer/library-scripts/common-debian.sh b/.devcontainer/library-scripts/common-debian.sh deleted file mode 100644 index bf1f9e2..0000000 --- a/.devcontainer/library-scripts/common-debian.sh +++ /dev/null @@ -1,454 +0,0 @@ -#!/usr/bin/env bash -#------------------------------------------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information. -#------------------------------------------------------------------------------------------------------------- -# -# Docs: https://github.com/microsoft/vscode-dev-containers/blob/main/script-library/docs/common.md -# Maintainer: The VS Code and Codespaces Teams -# -# Syntax: ./common-debian.sh [install zsh flag] [username] [user UID] [user GID] [upgrade packages flag] [install Oh My Zsh! flag] [Add non-free packages] - -set -e - -INSTALL_ZSH=${1:-"true"} -USERNAME=${2:-"automatic"} -USER_UID=${3:-"automatic"} -USER_GID=${4:-"automatic"} -UPGRADE_PACKAGES=${5:-"true"} -INSTALL_OH_MYS=${6:-"true"} -ADD_NON_FREE_PACKAGES=${7:-"false"} -SCRIPT_DIR="$(cd $(dirname "${BASH_SOURCE[0]}") && pwd)" -MARKER_FILE="/usr/local/etc/vscode-dev-containers/common" - -if [ "$(id -u)" -ne 0 ]; then - echo -e 'Script must be run as root. Use sudo, su, or add "USER root" to your Dockerfile before running this script.' - exit 1 -fi - -# Ensure that login shells get the correct path if the user updated the PATH using ENV. -rm -f /etc/profile.d/00-restore-env.sh -echo "export PATH=${PATH//$(sh -lc 'echo $PATH')/\$PATH}" > /etc/profile.d/00-restore-env.sh -chmod +x /etc/profile.d/00-restore-env.sh - -# If in automatic mode, determine if a user already exists, if not use vscode -if [ "${USERNAME}" = "auto" ] || [ "${USERNAME}" = "automatic" ]; then - USERNAME="" - POSSIBLE_USERS=("vscode" "node" "codespace" "$(awk -v val=1000 -F ":" '$3==val{print $1}' /etc/passwd)") - for CURRENT_USER in ${POSSIBLE_USERS[@]}; do - if id -u ${CURRENT_USER} > /dev/null 2>&1; then - USERNAME=${CURRENT_USER} - break - fi - done - if [ "${USERNAME}" = "" ]; then - USERNAME=vscode - fi -elif [ "${USERNAME}" = "none" ]; then - USERNAME=root - USER_UID=0 - USER_GID=0 -fi - -# Load markers to see which steps have already run -if [ -f "${MARKER_FILE}" ]; then - echo "Marker file found:" - cat "${MARKER_FILE}" - source "${MARKER_FILE}" -fi - -# Ensure apt is in non-interactive to avoid prompts -export DEBIAN_FRONTEND=noninteractive - -# Function to call apt-get if needed -apt_get_update_if_needed() -{ - if [ ! -d "/var/lib/apt/lists" ] || [ "$(ls /var/lib/apt/lists/ | wc -l)" = "0" ]; then - echo "Running apt-get update..." - apt-get update - else - echo "Skipping apt-get update." - fi -} - -# Run install apt-utils to avoid debconf warning then verify presence of other common developer tools and dependencies -if [ "${PACKAGES_ALREADY_INSTALLED}" != "true" ]; then - - package_list="apt-utils \ - openssh-client \ - gnupg2 \ - dirmngr \ - iproute2 \ - procps \ - lsof \ - htop \ - net-tools \ - psmisc \ - curl \ - wget \ - rsync \ - ca-certificates \ - unzip \ - zip \ - nano \ - vim-tiny \ - less \ - jq \ - lsb-release \ - apt-transport-https \ - dialog \ - libc6 \ - libgcc1 \ - libkrb5-3 \ - libgssapi-krb5-2 \ - libicu[0-9][0-9] \ - liblttng-ust[0-9] \ - libstdc++6 \ - zlib1g \ - locales \ - sudo \ - ncdu \ - man-db \ - strace \ - manpages \ - manpages-dev \ - init-system-helpers" - - # Needed for adding manpages-posix and manpages-posix-dev which are non-free packages in Debian - if [ "${ADD_NON_FREE_PACKAGES}" = "true" ]; then - # Bring in variables from /etc/os-release like VERSION_CODENAME - . /etc/os-release - sed -i -E "s/deb http:\/\/(deb|httpredir)\.debian\.org\/debian ${VERSION_CODENAME} main/deb http:\/\/\1\.debian\.org\/debian ${VERSION_CODENAME} main contrib non-free/" /etc/apt/sources.list - sed -i -E "s/deb-src http:\/\/(deb|httredir)\.debian\.org\/debian ${VERSION_CODENAME} main/deb http:\/\/\1\.debian\.org\/debian ${VERSION_CODENAME} main contrib non-free/" /etc/apt/sources.list - sed -i -E "s/deb http:\/\/(deb|httpredir)\.debian\.org\/debian ${VERSION_CODENAME}-updates main/deb http:\/\/\1\.debian\.org\/debian ${VERSION_CODENAME}-updates main contrib non-free/" /etc/apt/sources.list - sed -i -E "s/deb-src http:\/\/(deb|httpredir)\.debian\.org\/debian ${VERSION_CODENAME}-updates main/deb http:\/\/\1\.debian\.org\/debian ${VERSION_CODENAME}-updates main contrib non-free/" /etc/apt/sources.list - sed -i "s/deb http:\/\/security\.debian\.org\/debian-security ${VERSION_CODENAME}\/updates main/deb http:\/\/security\.debian\.org\/debian-security ${VERSION_CODENAME}\/updates main contrib non-free/" /etc/apt/sources.list - sed -i "s/deb-src http:\/\/security\.debian\.org\/debian-security ${VERSION_CODENAME}\/updates main/deb http:\/\/security\.debian\.org\/debian-security ${VERSION_CODENAME}\/updates main contrib non-free/" /etc/apt/sources.list - sed -i "s/deb http:\/\/deb\.debian\.org\/debian ${VERSION_CODENAME}-backports main/deb http:\/\/deb\.debian\.org\/debian ${VERSION_CODENAME}-backports main contrib non-free/" /etc/apt/sources.list - sed -i "s/deb-src http:\/\/deb\.debian\.org\/debian ${VERSION_CODENAME}-backports main/deb http:\/\/deb\.debian\.org\/debian ${VERSION_CODENAME}-backports main contrib non-free/" /etc/apt/sources.list - # Handle bullseye location for security https://www.debian.org/releases/bullseye/amd64/release-notes/ch-information.en.html - sed -i "s/deb http:\/\/security\.debian\.org\/debian-security ${VERSION_CODENAME}-security main/deb http:\/\/security\.debian\.org\/debian-security ${VERSION_CODENAME}-security main contrib non-free/" /etc/apt/sources.list - sed -i "s/deb-src http:\/\/security\.debian\.org\/debian-security ${VERSION_CODENAME}-security main/deb http:\/\/security\.debian\.org\/debian-security ${VERSION_CODENAME}-security main contrib non-free/" /etc/apt/sources.list - echo "Running apt-get update..." - apt-get update - package_list="${package_list} manpages-posix manpages-posix-dev" - else - apt_get_update_if_needed - fi - - # Install libssl1.1 if available - if [[ ! -z $(apt-cache --names-only search ^libssl1.1$) ]]; then - package_list="${package_list} libssl1.1" - fi - - # Install appropriate version of libssl1.0.x if available - libssl_package=$(dpkg-query -f '${db:Status-Abbrev}\t${binary:Package}\n' -W 'libssl1\.0\.?' 2>&1 || echo '') - if [ "$(echo "$LIlibssl_packageBSSL" | grep -o 'libssl1\.0\.[0-9]:' | uniq | sort | wc -l)" -eq 0 ]; then - if [[ ! -z $(apt-cache --names-only search ^libssl1.0.2$) ]]; then - # Debian 9 - package_list="${package_list} libssl1.0.2" - elif [[ ! -z $(apt-cache --names-only search ^libssl1.0.0$) ]]; then - # Ubuntu 18.04, 16.04, earlier - package_list="${package_list} libssl1.0.0" - fi - fi - - echo "Packages to verify are installed: ${package_list}" - apt-get -y install --no-install-recommends ${package_list} 2> >( grep -v 'debconf: delaying package configuration, since apt-utils is not installed' >&2 ) - - # Install git if not already installed (may be more recent than distro version) - if ! type git > /dev/null 2>&1; then - apt-get -y install --no-install-recommends git - fi - - PACKAGES_ALREADY_INSTALLED="true" -fi - -# Get to latest versions of all packages -if [ "${UPGRADE_PACKAGES}" = "true" ]; then - apt_get_update_if_needed - apt-get -y upgrade --no-install-recommends - apt-get autoremove -y -fi - -# Ensure at least the en_US.UTF-8 UTF-8 locale is available. -# Common need for both applications and things like the agnoster ZSH theme. -if [ "${LOCALE_ALREADY_SET}" != "true" ] && ! grep -o -E '^\s*en_US.UTF-8\s+UTF-8' /etc/locale.gen > /dev/null; then - echo "en_US.UTF-8 UTF-8" >> /etc/locale.gen - locale-gen - LOCALE_ALREADY_SET="true" -fi - -# Create or update a non-root user to match UID/GID. -group_name="${USERNAME}" -if id -u ${USERNAME} > /dev/null 2>&1; then - # User exists, update if needed - if [ "${USER_GID}" != "automatic" ] && [ "$USER_GID" != "$(id -g $USERNAME)" ]; then - group_name="$(id -gn $USERNAME)" - groupmod --gid $USER_GID ${group_name} - usermod --gid $USER_GID $USERNAME - fi - if [ "${USER_UID}" != "automatic" ] && [ "$USER_UID" != "$(id -u $USERNAME)" ]; then - usermod --uid $USER_UID $USERNAME - fi -else - # Create user - if [ "${USER_GID}" = "automatic" ]; then - groupadd $USERNAME - else - groupadd --gid $USER_GID $USERNAME - fi - if [ "${USER_UID}" = "automatic" ]; then - useradd -s /bin/bash --gid $USERNAME -m $USERNAME - else - useradd -s /bin/bash --uid $USER_UID --gid $USERNAME -m $USERNAME - fi -fi - -# Add sudo support for non-root user -if [ "${USERNAME}" != "root" ] && [ "${EXISTING_NON_ROOT_USER}" != "${USERNAME}" ]; then - echo $USERNAME ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/$USERNAME - chmod 0440 /etc/sudoers.d/$USERNAME - EXISTING_NON_ROOT_USER="${USERNAME}" -fi - -# ** Shell customization section ** -if [ "${USERNAME}" = "root" ]; then - user_rc_path="/root" -else - user_rc_path="/home/${USERNAME}" -fi - -# Restore user .bashrc defaults from skeleton file if it doesn't exist or is empty -if [ ! -f "${user_rc_path}/.bashrc" ] || [ ! -s "${user_rc_path}/.bashrc" ] ; then - cp /etc/skel/.bashrc "${user_rc_path}/.bashrc" -fi - -# Restore user .profile defaults from skeleton file if it doesn't exist or is empty -if [ ! -f "${user_rc_path}/.profile" ] || [ ! -s "${user_rc_path}/.profile" ] ; then - cp /etc/skel/.profile "${user_rc_path}/.profile" -fi - -# .bashrc/.zshrc snippet -rc_snippet="$(cat << 'EOF' - -if [ -z "${USER}" ]; then export USER=$(whoami); fi -if [[ "${PATH}" != *"$HOME/.local/bin"* ]]; then export PATH="${PATH}:$HOME/.local/bin"; fi - -# Display optional first run image specific notice if configured and terminal is interactive -if [ -t 1 ] && [[ "${TERM_PROGRAM}" = "vscode" || "${TERM_PROGRAM}" = "codespaces" ]] && [ ! -f "$HOME/.config/vscode-dev-containers/first-run-notice-already-displayed" ]; then - if [ -f "/usr/local/etc/vscode-dev-containers/first-run-notice.txt" ]; then - cat "/usr/local/etc/vscode-dev-containers/first-run-notice.txt" - elif [ -f "/workspaces/.codespaces/shared/first-run-notice.txt" ]; then - cat "/workspaces/.codespaces/shared/first-run-notice.txt" - fi - mkdir -p "$HOME/.config/vscode-dev-containers" - # Mark first run notice as displayed after 10s to avoid problems with fast terminal refreshes hiding it - ((sleep 10s; touch "$HOME/.config/vscode-dev-containers/first-run-notice-already-displayed") &) -fi - -# Set the default git editor if not already set -if [ -z "$(git config --get core.editor)" ] && [ -z "${GIT_EDITOR}" ]; then - if [ "${TERM_PROGRAM}" = "vscode" ]; then - if [[ -n $(command -v code-insiders) && -z $(command -v code) ]]; then - export GIT_EDITOR="code-insiders --wait" - else - export GIT_EDITOR="code --wait" - fi - fi -fi - -EOF -)" - -# code shim, it fallbacks to code-insiders if code is not available -cat << 'EOF' > /usr/local/bin/code -#!/bin/sh - -get_in_path_except_current() { - which -a "$1" | grep -A1 "$0" | grep -v "$0" -} - -code="$(get_in_path_except_current code)" - -if [ -n "$code" ]; then - exec "$code" "$@" -elif [ "$(command -v code-insiders)" ]; then - exec code-insiders "$@" -else - echo "code or code-insiders is not installed" >&2 - exit 127 -fi -EOF -chmod +x /usr/local/bin/code - -# systemctl shim - tells people to use 'service' if systemd is not running -cat << 'EOF' > /usr/local/bin/systemctl -#!/bin/sh -set -e -if [ -d "/run/systemd/system" ]; then - exec /bin/systemctl "$@" -else - echo '\n"systemd" is not running in this container due to its overhead.\nUse the "service" command to start services instead. e.g.: \n\nservice --status-all' -fi -EOF -chmod +x /usr/local/bin/systemctl - -# Codespaces bash and OMZ themes - partly inspired by https://github.com/ohmyzsh/ohmyzsh/blob/master/themes/robbyrussell.zsh-theme -codespaces_bash="$(cat \ -<<'EOF' - -# Codespaces bash prompt theme -__bash_prompt() { - local userpart='`export XIT=$? \ - && [ ! -z "${GITHUB_USER}" ] && echo -n "\[\033[0;32m\]@${GITHUB_USER} " || echo -n "\[\033[0;32m\]\u " \ - && [ "$XIT" -ne "0" ] && echo -n "\[\033[1;31m\]➜" || echo -n "\[\033[0m\]➜"`' - local gitbranch='`\ - if [ "$(git config --get codespaces-theme.hide-status 2>/dev/null)" != 1 ]; then \ - export BRANCH=$(git symbolic-ref --short HEAD 2>/dev/null || git rev-parse --short HEAD 2>/dev/null); \ - if [ "${BRANCH}" != "" ]; then \ - echo -n "\[\033[0;36m\](\[\033[1;31m\]${BRANCH}" \ - && if git ls-files --error-unmatch -m --directory --no-empty-directory -o --exclude-standard ":/*" > /dev/null 2>&1; then \ - echo -n " \[\033[1;33m\]✗"; \ - fi \ - && echo -n "\[\033[0;36m\]) "; \ - fi; \ - fi`' - local lightblue='\[\033[1;34m\]' - local removecolor='\[\033[0m\]' - PS1="${userpart} ${lightblue}\w ${gitbranch}${removecolor}\$ " - unset -f __bash_prompt -} -__bash_prompt - -EOF -)" - -codespaces_zsh="$(cat \ -<<'EOF' -# Codespaces zsh prompt theme -__zsh_prompt() { - local prompt_username - if [ ! -z "${GITHUB_USER}" ]; then - prompt_username="@${GITHUB_USER}" - else - prompt_username="%n" - fi - PROMPT="%{$fg[green]%}${prompt_username} %(?:%{$reset_color%}➜ :%{$fg_bold[red]%}➜ )" # User/exit code arrow - PROMPT+='%{$fg_bold[blue]%}%(5~|%-1~/…/%3~|%4~)%{$reset_color%} ' # cwd - PROMPT+='$([ "$(git config --get codespaces-theme.hide-status 2>/dev/null)" != 1 ] && git_prompt_info)' # Git status - PROMPT+='%{$fg[white]%}$ %{$reset_color%}' - unset -f __zsh_prompt -} -ZSH_THEME_GIT_PROMPT_PREFIX="%{$fg_bold[cyan]%}(%{$fg_bold[red]%}" -ZSH_THEME_GIT_PROMPT_SUFFIX="%{$reset_color%} " -ZSH_THEME_GIT_PROMPT_DIRTY=" %{$fg_bold[yellow]%}✗%{$fg_bold[cyan]%})" -ZSH_THEME_GIT_PROMPT_CLEAN="%{$fg_bold[cyan]%})" -__zsh_prompt - -EOF -)" - -# Add RC snippet and custom bash prompt -if [ "${RC_SNIPPET_ALREADY_ADDED}" != "true" ]; then - echo "${rc_snippet}" >> /etc/bash.bashrc - echo "${codespaces_bash}" >> "${user_rc_path}/.bashrc" - echo 'export PROMPT_DIRTRIM=4' >> "${user_rc_path}/.bashrc" - if [ "${USERNAME}" != "root" ]; then - echo "${codespaces_bash}" >> "/root/.bashrc" - echo 'export PROMPT_DIRTRIM=4' >> "/root/.bashrc" - fi - chown ${USERNAME}:${group_name} "${user_rc_path}/.bashrc" - RC_SNIPPET_ALREADY_ADDED="true" -fi - -# Optionally install and configure zsh and Oh My Zsh! -if [ "${INSTALL_ZSH}" = "true" ]; then - if ! type zsh > /dev/null 2>&1; then - apt_get_update_if_needed - apt-get install -y zsh - fi - if [ "${ZSH_ALREADY_INSTALLED}" != "true" ]; then - echo "${rc_snippet}" >> /etc/zsh/zshrc - ZSH_ALREADY_INSTALLED="true" - fi - - # Adapted, simplified inline Oh My Zsh! install steps that adds, defaults to a codespaces theme. - # See https://github.com/ohmyzsh/ohmyzsh/blob/master/tools/install.sh for official script. - oh_my_install_dir="${user_rc_path}/.oh-my-zsh" - if [ ! -d "${oh_my_install_dir}" ] && [ "${INSTALL_OH_MYS}" = "true" ]; then - template_path="${oh_my_install_dir}/templates/zshrc.zsh-template" - user_rc_file="${user_rc_path}/.zshrc" - umask g-w,o-w - mkdir -p ${oh_my_install_dir} - git clone --depth=1 \ - -c core.eol=lf \ - -c core.autocrlf=false \ - -c fsck.zeroPaddedFilemode=ignore \ - -c fetch.fsck.zeroPaddedFilemode=ignore \ - -c receive.fsck.zeroPaddedFilemode=ignore \ - "https://github.com/ohmyzsh/ohmyzsh" "${oh_my_install_dir}" 2>&1 - echo -e "$(cat "${template_path}")\nDISABLE_AUTO_UPDATE=true\nDISABLE_UPDATE_PROMPT=true" > ${user_rc_file} - sed -i -e 's/ZSH_THEME=.*/ZSH_THEME="codespaces"/g' ${user_rc_file} - - mkdir -p ${oh_my_install_dir}/custom/themes - echo "${codespaces_zsh}" > "${oh_my_install_dir}/custom/themes/codespaces.zsh-theme" - # Shrink git while still enabling updates - cd "${oh_my_install_dir}" - git repack -a -d -f --depth=1 --window=1 - # Copy to non-root user if one is specified - if [ "${USERNAME}" != "root" ]; then - cp -rf "${user_rc_file}" "${oh_my_install_dir}" /root - chown -R ${USERNAME}:${group_name} "${user_rc_path}" - fi - fi -fi - -# Persist image metadata info, script if meta.env found in same directory -meta_info_script="$(cat << 'EOF' -#!/bin/sh -. /usr/local/etc/vscode-dev-containers/meta.env - -# Minimal output -if [ "$1" = "version" ] || [ "$1" = "image-version" ]; then - echo "${VERSION}" - exit 0 -elif [ "$1" = "release" ]; then - echo "${GIT_REPOSITORY_RELEASE}" - exit 0 -elif [ "$1" = "content" ] || [ "$1" = "content-url" ] || [ "$1" = "contents" ] || [ "$1" = "contents-url" ]; then - echo "${CONTENTS_URL}" - exit 0 -fi - -#Full output -echo -echo "Development container image information" -echo -if [ ! -z "${VERSION}" ]; then echo "- Image version: ${VERSION}"; fi -if [ ! -z "${DEFINITION_ID}" ]; then echo "- Definition ID: ${DEFINITION_ID}"; fi -if [ ! -z "${VARIANT}" ]; then echo "- Variant: ${VARIANT}"; fi -if [ ! -z "${GIT_REPOSITORY}" ]; then echo "- Source code repository: ${GIT_REPOSITORY}"; fi -if [ ! -z "${GIT_REPOSITORY_RELEASE}" ]; then echo "- Source code release/branch: ${GIT_REPOSITORY_RELEASE}"; fi -if [ ! -z "${BUILD_TIMESTAMP}" ]; then echo "- Timestamp: ${BUILD_TIMESTAMP}"; fi -if [ ! -z "${CONTENTS_URL}" ]; then echo && echo "More info: ${CONTENTS_URL}"; fi -echo -EOF -)" -if [ -f "${SCRIPT_DIR}/meta.env" ]; then - mkdir -p /usr/local/etc/vscode-dev-containers/ - cp -f "${SCRIPT_DIR}/meta.env" /usr/local/etc/vscode-dev-containers/meta.env - echo "${meta_info_script}" > /usr/local/bin/devcontainer-info - chmod +x /usr/local/bin/devcontainer-info -fi - -# Write marker file -mkdir -p "$(dirname "${MARKER_FILE}")" -echo -e "\ - PACKAGES_ALREADY_INSTALLED=${PACKAGES_ALREADY_INSTALLED}\n\ - LOCALE_ALREADY_SET=${LOCALE_ALREADY_SET}\n\ - EXISTING_NON_ROOT_USER=${EXISTING_NON_ROOT_USER}\n\ - RC_SNIPPET_ALREADY_ADDED=${RC_SNIPPET_ALREADY_ADDED}\n\ - ZSH_ALREADY_INSTALLED=${ZSH_ALREADY_INSTALLED}" > "${MARKER_FILE}" - -echo "Done!" \ No newline at end of file diff --git a/.devcontainer/library-scripts/docker-in-docker-debian.sh b/.devcontainer/library-scripts/docker-in-docker-debian.sh deleted file mode 100644 index 88603a9..0000000 --- a/.devcontainer/library-scripts/docker-in-docker-debian.sh +++ /dev/null @@ -1,405 +0,0 @@ -#!/usr/bin/env bash -#------------------------------------------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information. -#------------------------------------------------------------------------------------------------------------- -# -# Docs: https://github.com/microsoft/vscode-dev-containers/blob/main/script-library/docs/docker-in-docker.md -# Maintainer: The VS Code and Codespaces Teams -# -# Syntax: ./docker-in-docker-debian.sh [enable non-root docker access flag] [non-root user] [use moby] [Engine/CLI Version] [Major version for docker-compose] [azure DNS auto detection flag] - -ENABLE_NONROOT_DOCKER=${1:-"true"} -USERNAME=${2:-"automatic"} -USE_MOBY=${3:-"true"} -DOCKER_VERSION=${4:-"latest"} # The Docker/Moby Engine + CLI should match in version -DOCKER_DASH_COMPOSE_VERSION=${5:-"v1"} # v1 or v2 -AZURE_DNS_AUTO_DETECTION=${6:-"true"} -MICROSOFT_GPG_KEYS_URI="https://packages.microsoft.com/keys/microsoft.asc" -DOCKER_MOBY_ARCHIVE_VERSION_CODENAMES="buster bullseye bionic focal jammy" -DOCKER_LICENSED_ARCHIVE_VERSION_CODENAMES="buster bullseye bionic focal hirsute impish jammy" - -# Default: Exit on any failure. -set -e - -# Setup STDERR. -err() { - echo "(!) $*" >&2 -} - -if [ "$(id -u)" -ne 0 ]; then - err 'Script must be run as root. Use sudo, su, or add "USER root" to your Dockerfile before running this script.' - exit 1 -fi - -################### -# Helper Functions -# See: https://github.com/microsoft/vscode-dev-containers/blob/main/script-library/shared/utils.sh -################### - -# Determine the appropriate non-root user -if [ "${USERNAME}" = "auto" ] || [ "${USERNAME}" = "automatic" ]; then - USERNAME="" - POSSIBLE_USERS=("vscode" "node" "codespace" "$(awk -v val=1000 -F ":" '$3==val{print $1}' /etc/passwd)") - for CURRENT_USER in ${POSSIBLE_USERS[@]}; do - if id -u ${CURRENT_USER} > /dev/null 2>&1; then - USERNAME=${CURRENT_USER} - break - fi - done - if [ "${USERNAME}" = "" ]; then - USERNAME=root - fi -elif [ "${USERNAME}" = "none" ] || ! id -u ${USERNAME} > /dev/null 2>&1; then - USERNAME=root -fi - -# Get central common setting -get_common_setting() { - if [ "${common_settings_file_loaded}" != "true" ]; then - curl -sfL "https://aka.ms/vscode-dev-containers/script-library/settings.env" 2>/dev/null -o /tmp/vsdc-settings.env || echo "Could not download settings file. Skipping." - common_settings_file_loaded=true - fi - if [ -f "/tmp/vsdc-settings.env" ]; then - local multi_line="" - if [ "$2" = "true" ]; then multi_line="-z"; fi - local result="$(grep ${multi_line} -oP "$1=\"?\K[^\"]+" /tmp/vsdc-settings.env | tr -d '\0')" - if [ ! -z "${result}" ]; then declare -g $1="${result}"; fi - fi - echo "$1=${!1}" -} - -# Function to run apt-get if needed -apt_get_update_if_needed() -{ - if [ ! -d "/var/lib/apt/lists" ] || [ "$(ls /var/lib/apt/lists/ | wc -l)" = "0" ]; then - echo "Running apt-get update..." - apt-get update - else - echo "Skipping apt-get update." - fi -} - -# Checks if packages are installed and installs them if not -check_packages() { - if ! dpkg -s "$@" > /dev/null 2>&1; then - apt_get_update_if_needed - apt-get -y install --no-install-recommends "$@" - fi -} - -# Figure out correct version of a three part version number is not passed -find_version_from_git_tags() { - local variable_name=$1 - local requested_version=${!variable_name} - if [ "${requested_version}" = "none" ]; then return; fi - local repository=$2 - local prefix=${3:-"tags/v"} - local separator=${4:-"."} - local last_part_optional=${5:-"false"} - if [ "$(echo "${requested_version}" | grep -o "." | wc -l)" != "2" ]; then - local escaped_separator=${separator//./\\.} - local last_part - if [ "${last_part_optional}" = "true" ]; then - last_part="(${escaped_separator}[0-9]+)?" - else - last_part="${escaped_separator}[0-9]+" - fi - local regex="${prefix}\\K[0-9]+${escaped_separator}[0-9]+${last_part}$" - local version_list="$(git ls-remote --tags ${repository} | grep -oP "${regex}" | tr -d ' ' | tr "${separator}" "." | sort -rV)" - if [ "${requested_version}" = "latest" ] || [ "${requested_version}" = "current" ] || [ "${requested_version}" = "lts" ]; then - declare -g ${variable_name}="$(echo "${version_list}" | head -n 1)" - else - set +e - declare -g ${variable_name}="$(echo "${version_list}" | grep -E -m 1 "^${requested_version//./\\.}([\\.\\s]|$)")" - set -e - fi - fi - if [ -z "${!variable_name}" ] || ! echo "${version_list}" | grep "^${!variable_name//./\\.}$" > /dev/null 2>&1; then - err "Invalid ${variable_name} value: ${requested_version}\nValid values:\n${version_list}" >&2 - exit 1 - fi - echo "${variable_name}=${!variable_name}" -} - -########################################### -# Start docker-in-docker installation -########################################### - -# Ensure apt is in non-interactive to avoid prompts -export DEBIAN_FRONTEND=noninteractive - - -# Source /etc/os-release to get OS info -. /etc/os-release -# Fetch host/container arch. -architecture="$(dpkg --print-architecture)" - -# Check if distro is suppported -if [ "${USE_MOBY}" = "true" ]; then - # 'get_common_setting' allows attribute to be updated remotely - get_common_setting DOCKER_MOBY_ARCHIVE_VERSION_CODENAMES - if [[ "${DOCKER_MOBY_ARCHIVE_VERSION_CODENAMES}" != *"${VERSION_CODENAME}"* ]]; then - err "Unsupported distribution version '${VERSION_CODENAME}'. To resolve, either: (1) set feature option '\"moby\": false' , or (2) choose a compatible OS distribution" - err "Support distributions include: ${DOCKER_MOBY_ARCHIVE_VERSION_CODENAMES}" - exit 1 - fi - echo "Distro codename '${VERSION_CODENAME}' matched filter '${DOCKER_MOBY_ARCHIVE_VERSION_CODENAMES}'" -else - get_common_setting DOCKER_LICENSED_ARCHIVE_VERSION_CODENAMES - if [[ "${DOCKER_LICENSED_ARCHIVE_VERSION_CODENAMES}" != *"${VERSION_CODENAME}"* ]]; then - err "Unsupported distribution version '${VERSION_CODENAME}'. To resolve, please choose a compatible OS distribution" - err "Support distributions include: ${DOCKER_LICENSED_ARCHIVE_VERSION_CODENAMES}" - exit 1 - fi - echo "Distro codename '${VERSION_CODENAME}' matched filter '${DOCKER_LICENSED_ARCHIVE_VERSION_CODENAMES}'" -fi - -# Install dependencies -check_packages apt-transport-https curl ca-certificates pigz iptables gnupg2 dirmngr -if ! type git > /dev/null 2>&1; then - apt_get_update_if_needed - apt-get -y install git -fi - -# Swap to legacy iptables for compatibility -if type iptables-legacy > /dev/null 2>&1; then - update-alternatives --set iptables /usr/sbin/iptables-legacy - update-alternatives --set ip6tables /usr/sbin/ip6tables-legacy -fi - - - -# Set up the necessary apt repos (either Microsoft's or Docker's) -if [ "${USE_MOBY}" = "true" ]; then - - # Name of open source engine/cli - engine_package_name="moby-engine" - cli_package_name="moby-cli" - - # Import key safely and import Microsoft apt repo - get_common_setting MICROSOFT_GPG_KEYS_URI - curl -sSL ${MICROSOFT_GPG_KEYS_URI} | gpg --dearmor > /usr/share/keyrings/microsoft-archive-keyring.gpg - echo "deb [arch=${architecture} signed-by=/usr/share/keyrings/microsoft-archive-keyring.gpg] https://packages.microsoft.com/repos/microsoft-${ID}-${VERSION_CODENAME}-prod ${VERSION_CODENAME} main" > /etc/apt/sources.list.d/microsoft.list -else - # Name of licensed engine/cli - engine_package_name="docker-ce" - cli_package_name="docker-ce-cli" - - # Import key safely and import Docker apt repo - curl -fsSL https://download.docker.com/linux/${ID}/gpg | gpg --dearmor > /usr/share/keyrings/docker-archive-keyring.gpg - echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/${ID} ${VERSION_CODENAME} stable" > /etc/apt/sources.list.d/docker.list -fi - -# Refresh apt lists -apt-get update - -# Soft version matching -if [ "${DOCKER_VERSION}" = "latest" ] || [ "${DOCKER_VERSION}" = "lts" ] || [ "${DOCKER_VERSION}" = "stable" ]; then - # Empty, meaning grab whatever "latest" is in apt repo - engine_version_suffix="" - cli_version_suffix="" -else - # Fetch a valid version from the apt-cache (eg: the Microsoft repo appends +azure, breakfix, etc...) - docker_version_dot_escaped="${DOCKER_VERSION//./\\.}" - docker_version_dot_plus_escaped="${docker_version_dot_escaped//+/\\+}" - # Regex needs to handle debian package version number format: https://www.systutorials.com/docs/linux/man/5-deb-version/ - docker_version_regex="^(.+:)?${docker_version_dot_plus_escaped}([\\.\\+ ~:-]|$)" - set +e # Don't exit if finding version fails - will handle gracefully - cli_version_suffix="=$(apt-cache madison ${cli_package_name} | awk -F"|" '{print $2}' | sed -e 's/^[ \t]*//' | grep -E -m 1 "${docker_version_regex}")" - engine_version_suffix="=$(apt-cache madison ${engine_package_name} | awk -F"|" '{print $2}' | sed -e 's/^[ \t]*//' | grep -E -m 1 "${docker_version_regex}")" - set -e - if [ -z "${engine_version_suffix}" ] || [ "${engine_version_suffix}" = "=" ] || [ -z "${cli_version_suffix}" ] || [ "${cli_version_suffix}" = "=" ] ; then - err "No full or partial Docker / Moby version match found for \"${DOCKER_VERSION}\" on OS ${ID} ${VERSION_CODENAME} (${architecture}). Available versions:" - apt-cache madison ${cli_package_name} | awk -F"|" '{print $2}' | grep -oP '^(.+:)?\K.+' - exit 1 - fi - echo "engine_version_suffix ${engine_version_suffix}" - echo "cli_version_suffix ${cli_version_suffix}" -fi - -# Install Docker / Moby CLI if not already installed -if type docker > /dev/null 2>&1 && type dockerd > /dev/null 2>&1; then - echo "Docker / Moby CLI and Engine already installed." -else - if [ "${USE_MOBY}" = "true" ]; then - # Install engine - set +e # Handle error gracefully - apt-get -y install --no-install-recommends moby-cli${cli_version_suffix} moby-buildx moby-engine${engine_version_suffix} - if [ $? -ne 0 ]; then - err "Packages for moby not available in OS ${ID} ${VERSION_CODENAME} (${architecture}). To resolve, either: (1) set feature option '\"moby\": false' , or (2) choose a compatible OS version (eg: 'ubuntu-20.04')." - exit 1 - fi - set -e - - # Install compose - apt-get -y install --no-install-recommends moby-compose || err "Package moby-compose (Docker Compose v2) not available for OS ${ID} ${VERSION_CODENAME} (${architecture}). Skipping." - else - apt-get -y install --no-install-recommends docker-ce-cli${cli_version_suffix} docker-ce${engine_version_suffix} - # Install compose - apt-get -y install --no-install-recommends docker-compose-plugin || echo "(*) Package docker-compose-plugin (Docker Compose v2) not available for OS ${ID} ${VERSION_CODENAME} (${architecture}). Skipping." - fi -fi - -echo "Finished installing docker / moby!" - -# Install Docker Compose if not already installed and is on a supported architecture -if type docker-compose > /dev/null 2>&1; then - echo "Docker Compose v1 already installed." -else - target_compose_arch="${architecture}" - if [ "${target_compose_arch}" = "amd64" ]; then - target_compose_arch="x86_64" - fi - if [ "${target_compose_arch}" != "x86_64" ]; then - # Use pip to get a version that runs on this architecture - if ! dpkg -s python3-minimal python3-pip libffi-dev python3-venv > /dev/null 2>&1; then - apt_get_update_if_needed - apt-get -y install python3-minimal python3-pip libffi-dev python3-venv - fi - export PIPX_HOME=/usr/local/pipx - mkdir -p ${PIPX_HOME} - export PIPX_BIN_DIR=/usr/local/bin - export PYTHONUSERBASE=/tmp/pip-tmp - export PIP_CACHE_DIR=/tmp/pip-tmp/cache - pipx_bin=pipx - if ! type pipx > /dev/null 2>&1; then - pip3 install --disable-pip-version-check --no-cache-dir --user pipx - pipx_bin=/tmp/pip-tmp/bin/pipx - fi - ${pipx_bin} install --pip-args '--no-cache-dir --force-reinstall' docker-compose - rm -rf /tmp/pip-tmp - else - compose_v1_version="1" - find_version_from_git_tags compose_v1_version "https://github.com/docker/compose" "tags/" - echo "(*) Installing docker-compose ${compose_v1_version}..." - curl -fsSL "https://github.com/docker/compose/releases/download/${compose_v1_version}/docker-compose-Linux-x86_64" -o /usr/local/bin/docker-compose - chmod +x /usr/local/bin/docker-compose - fi -fi - -# Install docker-compose switch if not already installed - https://github.com/docker/compose-switch#manual-installation -current_v1_compose_path="$(which docker-compose)" -target_v1_compose_path="$(dirname "${current_v1_compose_path}")/docker-compose-v1" -if ! type compose-switch > /dev/null 2>&1; then - echo "(*) Installing compose-switch..." - compose_switch_version="latest" - find_version_from_git_tags compose_switch_version "https://github.com/docker/compose-switch" - curl -fsSL "https://github.com/docker/compose-switch/releases/download/v${compose_switch_version}/docker-compose-linux-${architecture}" -o /usr/local/bin/compose-switch - chmod +x /usr/local/bin/compose-switch - # TODO: Verify checksum once available: https://github.com/docker/compose-switch/issues/11 - - # Setup v1 CLI as alternative in addition to compose-switch (which maps to v2) - mv "${current_v1_compose_path}" "${target_v1_compose_path}" - update-alternatives --install /usr/local/bin/docker-compose docker-compose /usr/local/bin/compose-switch 99 - update-alternatives --install /usr/local/bin/docker-compose docker-compose "${target_v1_compose_path}" 1 -fi -if [ "${DOCKER_DASH_COMPOSE_VERSION}" = "v1" ]; then - update-alternatives --set docker-compose "${target_v1_compose_path}" -else - update-alternatives --set docker-compose /usr/local/bin/compose-switch -fi - -# If init file already exists, exit -if [ -f "/usr/local/share/docker-init.sh" ]; then - echo "/usr/local/share/docker-init.sh already exists, so exiting." - exit 0 -fi -echo "docker-init doesnt exist, adding..." - -# Add user to the docker group -if [ "${ENABLE_NONROOT_DOCKER}" = "true" ]; then - if ! getent group docker > /dev/null 2>&1; then - groupadd docker - fi - - usermod -aG docker ${USERNAME} -fi - -tee /usr/local/share/docker-init.sh > /dev/null \ -<< EOF -#!/bin/sh -#------------------------------------------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information. -#------------------------------------------------------------------------------------------------------------- - -set -e - -AZURE_DNS_AUTO_DETECTION=$AZURE_DNS_AUTO_DETECTION -EOF - -tee -a /usr/local/share/docker-init.sh > /dev/null \ -<< 'EOF' -dockerd_start="$(cat << 'INNEREOF' - # explicitly remove dockerd and containerd PID file to ensure that it can start properly if it was stopped uncleanly - # ie: docker kill - find /run /var/run -iname 'docker*.pid' -delete || : - find /run /var/run -iname 'container*.pid' -delete || : - - ## Dind wrapper script from docker team, adapted to a function - # Maintained: https://github.com/moby/moby/blob/master/hack/dind - - export container=docker - - if [ -d /sys/kernel/security ] && ! mountpoint -q /sys/kernel/security; then - mount -t securityfs none /sys/kernel/security || { - echo >&2 'Could not mount /sys/kernel/security.' - echo >&2 'AppArmor detection and --privileged mode might break.' - } - fi - - # Mount /tmp (conditionally) - if ! mountpoint -q /tmp; then - mount -t tmpfs none /tmp - fi - - # cgroup v2: enable nesting - if [ -f /sys/fs/cgroup/cgroup.controllers ]; then - # move the processes from the root group to the /init group, - # otherwise writing subtree_control fails with EBUSY. - # An error during moving non-existent process (i.e., "cat") is ignored. - mkdir -p /sys/fs/cgroup/init - xargs -rn1 < /sys/fs/cgroup/cgroup.procs > /sys/fs/cgroup/init/cgroup.procs || : - # enable controllers - sed -e 's/ / +/g' -e 's/^/+/' < /sys/fs/cgroup/cgroup.controllers \ - > /sys/fs/cgroup/cgroup.subtree_control - fi - ## Dind wrapper over. - - # Handle DNS - set +e - cat /etc/resolv.conf | grep -i 'internal.cloudapp.net' - if [ $? -eq 0 ] && [ ${AZURE_DNS_AUTO_DETECTION} = "true" ] - then - echo "Setting dockerd Azure DNS." - CUSTOMDNS="--dns 168.63.129.16" - else - echo "Not setting dockerd DNS manually." - CUSTOMDNS="" - fi - set -e - - # Start docker/moby engine - ( dockerd $CUSTOMDNS > /tmp/dockerd.log 2>&1 ) & -INNEREOF -)" - -# Start using sudo if not invoked as root -if [ "$(id -u)" -ne 0 ]; then - sudo /bin/sh -c "${dockerd_start}" -else - eval "${dockerd_start}" -fi - -set +e - -# Execute whatever commands were passed in (if any). This allows us -# to set this script to ENTRYPOINT while still executing the default CMD. -exec "$@" -EOF - -chmod +x /usr/local/share/docker-init.sh -chown ${USERNAME}:root /usr/local/share/docker-init.sh - -echo 'docker-in-docker-debian script has completed!' \ No newline at end of file diff --git a/devenv.nix b/devenv.nix index 14dc88b..73d5687 100644 --- a/devenv.nix +++ b/devenv.nix @@ -29,9 +29,6 @@ git --version ''; - # https://devenv.sh/integrations/codespaces-devcontainer/ - devcontainer.enable = true; - # https://devenv.sh/languages/ languages.nix.enable = true; languages.python = { From 2824a9eff87296dc781f0dad9a09f3bd704de4e3 Mon Sep 17 00:00:00 2001 From: Eduardo Robles Date: Wed, 26 Jul 2023 13:12:00 +0000 Subject: [PATCH 12/26] WIP --- .ackrc | 2 + .devcontainer/devcontainer.json | 8 +- devenv.lock | 18 +- devenv.nix | 61 +- app.py => election_orchestra/app.py | 1 + .../asyncproc.py | 0 .../base_settings.py | 0 .../create_election}/__init__.py | 0 .../create_election}/director_jobs.py | 0 .../create_election}/performer_jobs.py | 0 .../demociphs.py | 0 .../keys_management.py | 0 models.py => election_orchestra/models.py | 0 .../public_api.py | 0 .../reject_adapter.py | 0 .../second_settings.py | 0 sha256.py => election_orchestra/sha256.py | 0 .../tally_election}/__init__.py | 0 .../tally_election}/director_jobs.py | 0 .../tally_election}/performer_jobs.py | 0 .../taskqueue.py | 0 .../tools}/__init__.py | 0 .../tools}/create_tarball.py | 0 utils.py => election_orchestra/utils.py | 0 vmn.py => election_orchestra/vmn.py | 0 flake.lock | 326 +++++++ flake.nix | 88 ++ poetry.lock | 820 ++++++++++++++++++ pyproject.toml | 23 + requirements.txt | 19 - requirements.txt.license | 3 - setup.py | 46 - 32 files changed, 1309 insertions(+), 106 deletions(-) create mode 100644 .ackrc rename app.py => election_orchestra/app.py (99%) rename asyncproc.py => election_orchestra/asyncproc.py (100%) rename base_settings.py => election_orchestra/base_settings.py (100%) rename {create_election => election_orchestra/create_election}/__init__.py (100%) rename {create_election => election_orchestra/create_election}/director_jobs.py (100%) rename {create_election => election_orchestra/create_election}/performer_jobs.py (100%) rename demociphs.py => election_orchestra/demociphs.py (100%) rename keys_management.py => election_orchestra/keys_management.py (100%) rename models.py => election_orchestra/models.py (100%) rename public_api.py => election_orchestra/public_api.py (100%) rename reject_adapter.py => election_orchestra/reject_adapter.py (100%) rename second_settings.py => election_orchestra/second_settings.py (100%) rename sha256.py => election_orchestra/sha256.py (100%) rename {tally_election => election_orchestra/tally_election}/__init__.py (100%) rename {tally_election => election_orchestra/tally_election}/director_jobs.py (100%) rename {tally_election => election_orchestra/tally_election}/performer_jobs.py (100%) rename taskqueue.py => election_orchestra/taskqueue.py (100%) rename {tools => election_orchestra/tools}/__init__.py (100%) rename {tools => election_orchestra/tools}/create_tarball.py (100%) rename utils.py => election_orchestra/utils.py (100%) rename vmn.py => election_orchestra/vmn.py (100%) create mode 100644 flake.lock create mode 100644 flake.nix create mode 100644 poetry.lock create mode 100644 pyproject.toml delete mode 100644 requirements.txt delete mode 100644 requirements.txt.license delete mode 100644 setup.py diff --git a/.ackrc b/.ackrc new file mode 100644 index 0000000..9cf362b --- /dev/null +++ b/.ackrc @@ -0,0 +1,2 @@ +--ignore-dir=.devenv +--ignore-dir=.venv diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index a6080d3..b192fdc 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -14,6 +14,9 @@ // https://marketplace.visualstudio.com/items?itemName=fsevenm.run-it-on "fsevenm.run-it-on", + // Python IDE support + "ms-python.python", + // Docker for Visual Studio Code "ms-azuretools.vscode-docker", @@ -30,7 +33,10 @@ "vadimcn.vscode-lldb", // Allows to use Alt+Q (or Option+Q in mac) to rewrap lines - "stkb.rewrap" + "stkb.rewrap", + + // TOML support, for poetry + "tamasfe.even-better-toml" ] } } diff --git a/devenv.lock b/devenv.lock index 00db773..1e51bee 100644 --- a/devenv.lock +++ b/devenv.lock @@ -3,11 +3,11 @@ "devenv": { "locked": { "dir": "src/modules", - "lastModified": 1690015987, - "narHash": "sha256-CS8Dz7HK4Ya8wWDd+dqcQbpTKeuqqBLma2LwJMKsozg=", + "lastModified": 1690298817, + "narHash": "sha256-nf3FK1Nqc2EVMflVRVNOGwT0OXr+jcAXniSm9SPM4sY=", "owner": "cachix", "repo": "devenv", - "rev": "016081e7895b36e4392bb4ee28a3c477ec030f30", + "rev": "9b21ab9147b0d1d601f4a6e13c19aaba2858a6ad", "type": "github" }, "original": { @@ -158,11 +158,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1689935543, - "narHash": "sha256-6GQ9ib4dA/r1leC5VUpsBo0BmDvNxLjKrX1iyL+h8mc=", + "lastModified": 1690327932, + "narHash": "sha256-Fv7PYZxN4eo0K6zXhHG/vOc+e2iuqQ5ywDrh0yeRjP0=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "e43e2448161c0a2c4928abec4e16eae1516571bc", + "rev": "a9b47d85504bdd199e90846622c76aa0bfeabfac", "type": "github" }, "original": { @@ -179,11 +179,11 @@ "nixpkgs": "nixpkgs_2" }, "locked": { - "lastModified": 1690020017, - "narHash": "sha256-fa0Ax9FikRGF8iJOOh/IAiLT6yP1WKQ+Cm6IHLVJtdY=", + "lastModified": 1690216544, + "narHash": "sha256-fWZVYvyoGTNzRnr6MDd0O2FLA2GwLCkSOlZbOxEBuk0=", "owner": "cachix", "repo": "nixpkgs-python", - "rev": "d621fade6965c202463ee4bd3d0c1011b3eb741a", + "rev": "64b32e82cba00c8a1fdc53618ae8b5502866fe96", "type": "github" }, "original": { diff --git a/devenv.nix b/devenv.nix index 73d5687..5e9d4c9 100644 --- a/devenv.nix +++ b/devenv.nix @@ -1,48 +1,53 @@ { pkgs, config, lib, ... }: { - # https://devenv.sh/basics/ - env.GREET = "devenv"; - - # https://devenv.sh/packages/ - packages = lib.optionals (!config.container.isBuilding) [ + packages = [ pkgs.git pkgs.ack # to create containers pkgs.docker - # used for building uwsgi: + # to inspect containers + pkgs.dive + + # used for building uwsgi (TODO: deprecated): pkgs.gcc pkgs.libffi ]; - # https://devenv.sh/processes/ - processes.election-orchestra.exec = '' - devenv shell bash -c \ - "export FRESTQ_SETTINGS=base_settings.py &&\ - python app.py --createdb \ - && python app.py" - ''; - - enterShell = '' - git --version - ''; + # HACK: I had to use `python $DEVENV_STATE/venv/bin/flask` instead of simply + # `flask` because otherwise it fails for some reason. + processes.serve.exec = ( + '' + export FLASK_ENV=development + export FLASK_APP=election_orchestra.app:app + export PYTHONPATH="$DEVENV_STATE/venv/lib/python3.10/site-packages:$PYTHONPATH" + export FRESTQ_SETTINGS=base_settings.py + python election_orchestra/app.py --createdb + '' + # + (if (config.container.isBuilding) + # then "python $DEVENV_STATE/venv/bin/flask run" + # else "flask run" + # ) + ); + + # HACK: The venv is missing from PYTHONPATH and PATH so I add them manually + enterShell = ( + ''git --version;'' + + ( + lib.optionalString (config.container.isBuilding) + ''export PYTHONPATH="$DEVENV_STATE/venv/lib/python3.10/site-packages:$PYTHONPATH"'' + ) + ); # https://devenv.sh/languages/ languages.nix.enable = true; languages.python = { enable = true; - # using python39 as default (python310) seems to have some glibc glitch - package = pkgs.python39; - venv.enable = true; - venv.requirements = ( - builtins.readFile ./requirements.txt + - '' - colorama==0.4.6 - PyYAML==6.0.1 - ZODB==5.8.1 - ''); + poetry = { + enable = true; + }; }; services.postgres = { @@ -50,4 +55,4 @@ package = pkgs.postgresql_15; initialDatabases = [{ name = "election-orchestra"; }]; }; -} +} \ No newline at end of file diff --git a/app.py b/election_orchestra/app.py similarity index 99% rename from app.py rename to election_orchestra/app.py index 74e2a30..ebd6eac 100755 --- a/app.py +++ b/election_orchestra/app.py @@ -6,6 +6,7 @@ # # SPDX-License-Identifier: AGPL-3.0-only # + import logging import os import sys diff --git a/asyncproc.py b/election_orchestra/asyncproc.py similarity index 100% rename from asyncproc.py rename to election_orchestra/asyncproc.py diff --git a/base_settings.py b/election_orchestra/base_settings.py similarity index 100% rename from base_settings.py rename to election_orchestra/base_settings.py diff --git a/create_election/__init__.py b/election_orchestra/create_election/__init__.py similarity index 100% rename from create_election/__init__.py rename to election_orchestra/create_election/__init__.py diff --git a/create_election/director_jobs.py b/election_orchestra/create_election/director_jobs.py similarity index 100% rename from create_election/director_jobs.py rename to election_orchestra/create_election/director_jobs.py diff --git a/create_election/performer_jobs.py b/election_orchestra/create_election/performer_jobs.py similarity index 100% rename from create_election/performer_jobs.py rename to election_orchestra/create_election/performer_jobs.py diff --git a/demociphs.py b/election_orchestra/demociphs.py similarity index 100% rename from demociphs.py rename to election_orchestra/demociphs.py diff --git a/keys_management.py b/election_orchestra/keys_management.py similarity index 100% rename from keys_management.py rename to election_orchestra/keys_management.py diff --git a/models.py b/election_orchestra/models.py similarity index 100% rename from models.py rename to election_orchestra/models.py diff --git a/public_api.py b/election_orchestra/public_api.py similarity index 100% rename from public_api.py rename to election_orchestra/public_api.py diff --git a/reject_adapter.py b/election_orchestra/reject_adapter.py similarity index 100% rename from reject_adapter.py rename to election_orchestra/reject_adapter.py diff --git a/second_settings.py b/election_orchestra/second_settings.py similarity index 100% rename from second_settings.py rename to election_orchestra/second_settings.py diff --git a/sha256.py b/election_orchestra/sha256.py similarity index 100% rename from sha256.py rename to election_orchestra/sha256.py diff --git a/tally_election/__init__.py b/election_orchestra/tally_election/__init__.py similarity index 100% rename from tally_election/__init__.py rename to election_orchestra/tally_election/__init__.py diff --git a/tally_election/director_jobs.py b/election_orchestra/tally_election/director_jobs.py similarity index 100% rename from tally_election/director_jobs.py rename to election_orchestra/tally_election/director_jobs.py diff --git a/tally_election/performer_jobs.py b/election_orchestra/tally_election/performer_jobs.py similarity index 100% rename from tally_election/performer_jobs.py rename to election_orchestra/tally_election/performer_jobs.py diff --git a/taskqueue.py b/election_orchestra/taskqueue.py similarity index 100% rename from taskqueue.py rename to election_orchestra/taskqueue.py diff --git a/tools/__init__.py b/election_orchestra/tools/__init__.py similarity index 100% rename from tools/__init__.py rename to election_orchestra/tools/__init__.py diff --git a/tools/create_tarball.py b/election_orchestra/tools/create_tarball.py similarity index 100% rename from tools/create_tarball.py rename to election_orchestra/tools/create_tarball.py diff --git a/utils.py b/election_orchestra/utils.py similarity index 100% rename from utils.py rename to election_orchestra/utils.py diff --git a/vmn.py b/election_orchestra/vmn.py similarity index 100% rename from vmn.py rename to election_orchestra/vmn.py diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..29235fa --- /dev/null +++ b/flake.lock @@ -0,0 +1,326 @@ +{ + "nodes": { + "devenv": { + "inputs": { + "flake-compat": "flake-compat", + "nix": "nix", + "nixpkgs": "nixpkgs", + "pre-commit-hooks": "pre-commit-hooks" + }, + "locked": { + "lastModified": 1690298817, + "narHash": "sha256-nf3FK1Nqc2EVMflVRVNOGwT0OXr+jcAXniSm9SPM4sY=", + "owner": "cachix", + "repo": "devenv", + "rev": "9b21ab9147b0d1d601f4a6e13c19aaba2858a6ad", + "type": "github" + }, + "original": { + "owner": "cachix", + "repo": "devenv", + "type": "github" + } + }, + "flake-compat": { + "flake": false, + "locked": { + "lastModified": 1673956053, + "narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=", + "owner": "edolstra", + "repo": "flake-compat", + "rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9", + "type": "github" + }, + "original": { + "owner": "edolstra", + "repo": "flake-compat", + "type": "github" + } + }, + "flake-parts": { + "inputs": { + "nixpkgs-lib": "nixpkgs-lib" + }, + "locked": { + "lastModified": 1688466019, + "narHash": "sha256-VeM2akYrBYMsb4W/MmBo1zmaMfgbL4cH3Pu8PGyIwJ0=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "8e8d955c22df93dbe24f19ea04f47a74adbdc5ec", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "flake-parts", + "type": "github" + } + }, + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1685518550, + "narHash": "sha256-o2d0KcvaXzTrPRIo0kOLV0/QXHhDQ5DTi+OxcjO8xqY=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "a1720a10a6cfe8234c0e93907ffe81be440f4cef", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "flake-utils_2": { + "locked": { + "lastModified": 1653893745, + "narHash": "sha256-0jntwV3Z8//YwuOjzhV2sgJJPt+HY6KhU7VZUL0fKZQ=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "1ed9fb1935d260de5fe1c2f7ee0ebaae17ed2fa1", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "gitignore": { + "inputs": { + "nixpkgs": [ + "devenv", + "pre-commit-hooks", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1660459072, + "narHash": "sha256-8DFJjXG8zqoONA1vXtgeKXy68KdJL5UaXR8NtVMUbx8=", + "owner": "hercules-ci", + "repo": "gitignore.nix", + "rev": "a20de23b925fd8264fd7fad6454652e142fd7f73", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "gitignore.nix", + "type": "github" + } + }, + "lowdown-src": { + "flake": false, + "locked": { + "lastModified": 1633514407, + "narHash": "sha256-Dw32tiMjdK9t3ETl5fzGrutQTzh2rufgZV4A/BbxuD4=", + "owner": "kristapsdz", + "repo": "lowdown", + "rev": "d2c2b44ff6c27b936ec27358a2653caaef8f73b8", + "type": "github" + }, + "original": { + "owner": "kristapsdz", + "repo": "lowdown", + "type": "github" + } + }, + "mk-shell-bin": { + "locked": { + "lastModified": 1677004959, + "narHash": "sha256-/uEkr1UkJrh11vD02aqufCxtbF5YnhRTIKlx5kyvf+I=", + "owner": "rrbutani", + "repo": "nix-mk-shell-bin", + "rev": "ff5d8bd4d68a347be5042e2f16caee391cd75887", + "type": "github" + }, + "original": { + "owner": "rrbutani", + "repo": "nix-mk-shell-bin", + "type": "github" + } + }, + "nix": { + "inputs": { + "lowdown-src": "lowdown-src", + "nixpkgs": [ + "devenv", + "nixpkgs" + ], + "nixpkgs-regression": "nixpkgs-regression" + }, + "locked": { + "lastModified": 1676545802, + "narHash": "sha256-EK4rZ+Hd5hsvXnzSzk2ikhStJnD63odF7SzsQ8CuSPU=", + "owner": "domenkozar", + "repo": "nix", + "rev": "7c91803598ffbcfe4a55c44ac6d49b2cf07a527f", + "type": "github" + }, + "original": { + "owner": "domenkozar", + "ref": "relaxed-flakes", + "repo": "nix", + "type": "github" + } + }, + "nix2container": { + "inputs": { + "flake-utils": "flake-utils_2", + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1688922987, + "narHash": "sha256-RnQwrCD5anqWfyDAVbfFIeU+Ha6cwt5QcIwIkaGRzQw=", + "owner": "nlewo", + "repo": "nix2container", + "rev": "ab381a7d714ebf96a83882264245dbd34f0a7ec8", + "type": "github" + }, + "original": { + "owner": "nlewo", + "repo": "nix2container", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1678875422, + "narHash": "sha256-T3o6NcQPwXjxJMn2shz86Chch4ljXgZn746c2caGxd8=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "126f49a01de5b7e35a43fd43f891ecf6d3a51459", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs-lib": { + "locked": { + "dir": "lib", + "lastModified": 1688049487, + "narHash": "sha256-100g4iaKC9MalDjUW9iN6Jl/OocTDtXdeAj7pEGIRh4=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "4bc72cae107788bf3f24f30db2e2f685c9298dc9", + "type": "github" + }, + "original": { + "dir": "lib", + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs-regression": { + "locked": { + "lastModified": 1643052045, + "narHash": "sha256-uGJ0VXIhWKGXxkeNnq4TvV3CIOkUJ3PAoLZ3HMzNVMw=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2", + "type": "github" + }, + "original": { + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2", + "type": "github" + } + }, + "nixpkgs-stable": { + "locked": { + "lastModified": 1685801374, + "narHash": "sha256-otaSUoFEMM+LjBI1XL/xGB5ao6IwnZOXc47qhIgJe8U=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "c37ca420157f4abc31e26f436c1145f8951ff373", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-23.05", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_2": { + "locked": { + "lastModified": 1690179384, + "narHash": "sha256-+arbgqFTAtoeKtepW9wCnA0njCOyoiDFyl0Q0SBSOtE=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "b12803b6d90e2e583429bb79b859ca53c348b39a", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "pre-commit-hooks": { + "inputs": { + "flake-compat": [ + "devenv", + "flake-compat" + ], + "flake-utils": "flake-utils", + "gitignore": "gitignore", + "nixpkgs": [ + "devenv", + "nixpkgs" + ], + "nixpkgs-stable": "nixpkgs-stable" + }, + "locked": { + "lastModified": 1688056373, + "narHash": "sha256-2+SDlNRTKsgo3LBRiMUcoEUb6sDViRNQhzJquZ4koOI=", + "owner": "cachix", + "repo": "pre-commit-hooks.nix", + "rev": "5843cf069272d92b60c3ed9e55b7a8989c01d4c7", + "type": "github" + }, + "original": { + "owner": "cachix", + "repo": "pre-commit-hooks.nix", + "type": "github" + } + }, + "root": { + "inputs": { + "devenv": "devenv", + "flake-parts": "flake-parts", + "mk-shell-bin": "mk-shell-bin", + "nix2container": "nix2container", + "nixpkgs": "nixpkgs_2" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..1200ca4 --- /dev/null +++ b/flake.nix @@ -0,0 +1,88 @@ +# SPDX-FileCopyrightText: 2013-2021 Sequent Tech Inc +# SPDX-License-Identifier: AGPL-3.0-only + +# Usage: +# time nix run .#dockerImage.copyToDockerDaemon && docker images election_orchestra:latest && docker run -it --network election-orchestra_devcontainer_default election_orchestra:latest +# Then: +# curl http://172.18.0.3:9090 +{ + description = "test-devenv test project"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + devenv.url = "github:cachix/devenv"; + nix2container.url = "github:nlewo/nix2container"; + nix2container.inputs.nixpkgs.follows = "nixpkgs"; + mk-shell-bin.url = "github:rrbutani/nix-mk-shell-bin"; + flake-parts.url = "github:hercules-ci/flake-parts"; + }; + + nixConfig = { + extra-trusted-public-keys = "devenv.cachix.org-1:w1cLUi8dv3hnoSPGAuibQv+f9TZLr6cv/Hm9XgU50cw="; + extra-substituters = "https://devenv.cachix.org"; + }; + outputs = inputs@{ flake-parts, ... }: + flake-parts.lib.mkFlake { inherit inputs; } { + imports = [ + inputs.devenv.flakeModule + ]; + systems = [ "x86_64-linux" ]; + + # Per-system attributes can be defined here. The self' and inputs' + # module parameters provide easy access to attributes of the same + # system. + perSystem = { config, self', inputs', pkgs, system, lib, ... }: + let + python = pkgs.python3; + nix2containerInput = inputs.nix2container; + nix2container = nix2containerInput.packages.${pkgs.stdenv.system}; + election_orchestra = pkgs.poetry2nix.mkPoetryApplication { + projectDir = ./.; + python = python; + }; + servicePort = "9090"; + dockerImage = nix2container.nix2container.buildImage { + name = "election_orchestra"; + tag = "latest"; + config = { + entrypoint = [ + #"${election_orchestra.dependencyEnv}/bin/flask" "run" + "${python.pkgs.gunicorn}/bin/gunicorn" + "-b" "0.0.0.0:${servicePort}" + "--log-level" "debug" + "election_orchestra.app:app" + ]; + Env = lib.mapAttrsToList (name: value: "${name}=${value}") { + FLASK_APP = "election_orchestra.app:app"; + FLASK_RUN_PORT = "${servicePort}"; + FLASK_RUN_HOST = "0.0.0.0"; + PYTHONPATH = "${election_orchestra.dependencyEnv}/lib/python3.10/site-packages"; + }; + ExposedPorts = { + "${servicePort}/tcp" = {}; + }; + + }; + # This is to not rebuild/push uwsgi and pythonEnv closures on a + # hello.py change. + layers = [ + (nix2container.nix2container.buildLayer { + deps = [ + election_orchestra.dependencyEnv + python.pkgs.gunicorn + ]; + }) + ]; + }; + in { + packages.election_orchestra = election_orchestra; + packages.dockerImage = dockerImage; + packages.default = election_orchestra; + }; + flake = { + # The usual flake attributes can be defined here, including system- + # agnostic ones like nixosModule and system-enumerating ones, although + # those are more easily expressed in perSystem. + }; + }; +} diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 0000000..f0f4c91 --- /dev/null +++ b/poetry.lock @@ -0,0 +1,820 @@ +# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand. + +[[package]] +name = "apscheduler" +version = "3.7.0" +description = "In-process task scheduler with Cron-like capabilities" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" +files = [ + {file = "APScheduler-3.7.0-py2.py3-none-any.whl", hash = "sha256:c06cc796d5bb9eb3c4f77727f6223476eb67749e7eea074d1587550702a7fbe3"}, + {file = "APScheduler-3.7.0.tar.gz", hash = "sha256:1cab7f2521e107d07127b042155b632b7a1cd5e02c34be5a28ff62f77c900c6a"}, +] + +[package.dependencies] +pytz = "*" +setuptools = ">=0.7" +six = ">=1.4.0" +tzlocal = ">=2.0,<3.0" + +[package.extras] +asyncio = ["trollius"] +doc = ["sphinx", "sphinx-rtd-theme"] +gevent = ["gevent"] +mongodb = ["pymongo (>=3.0)"] +redis = ["redis (>=3.0)"] +rethinkdb = ["rethinkdb (>=2.4.0)"] +sqlalchemy = ["sqlalchemy (>=0.8)"] +testing = ["mock", "pytest (<6)", "pytest-asyncio", "pytest-asyncio (<0.6)", "pytest-cov", "pytest-tornado5"] +tornado = ["tornado (>=4.3)"] +twisted = ["twisted"] +zookeeper = ["kazoo"] + +[[package]] +name = "blinker" +version = "1.6.2" +description = "Fast, simple object-to-object and broadcast signaling" +optional = false +python-versions = ">=3.7" +files = [ + {file = "blinker-1.6.2-py3-none-any.whl", hash = "sha256:c3d739772abb7bc2860abf5f2ec284223d9ad5c76da018234f6f50d6f31ab1f0"}, + {file = "blinker-1.6.2.tar.gz", hash = "sha256:4afd3de66ef3a9f8067559fb7a1cbe555c17dcbe15971b05d1b625c3e7abe213"}, +] + +[[package]] +name = "certifi" +version = "2023.7.22" +description = "Python package for providing Mozilla's CA Bundle." +optional = false +python-versions = ">=3.6" +files = [ + {file = "certifi-2023.7.22-py3-none-any.whl", hash = "sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9"}, + {file = "certifi-2023.7.22.tar.gz", hash = "sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082"}, +] + +[[package]] +name = "cffi" +version = "1.15.1" +description = "Foreign Function Interface for Python calling C code." +optional = false +python-versions = "*" +files = [ + {file = "cffi-1.15.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2"}, + {file = "cffi-1.15.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:470c103ae716238bbe698d67ad020e1db9d9dba34fa5a899b5e21577e6d52ed2"}, + {file = "cffi-1.15.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:9ad5db27f9cabae298d151c85cf2bad1d359a1b9c686a275df03385758e2f914"}, + {file = "cffi-1.15.1-cp27-cp27m-win32.whl", hash = "sha256:b3bbeb01c2b273cca1e1e0c5df57f12dce9a4dd331b4fa1635b8bec26350bde3"}, + {file = "cffi-1.15.1-cp27-cp27m-win_amd64.whl", hash = "sha256:e00b098126fd45523dd056d2efba6c5a63b71ffe9f2bbe1a4fe1716e1d0c331e"}, + {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:d61f4695e6c866a23a21acab0509af1cdfd2c013cf256bbf5b6b5e2695827162"}, + {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:ed9cb427ba5504c1dc15ede7d516b84757c3e3d7868ccc85121d9310d27eed0b"}, + {file = "cffi-1.15.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:39d39875251ca8f612b6f33e6b1195af86d1b3e60086068be9cc053aa4376e21"}, + {file = "cffi-1.15.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:285d29981935eb726a4399badae8f0ffdff4f5050eaa6d0cfc3f64b857b77185"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3eb6971dcff08619f8d91607cfc726518b6fa2a9eba42856be181c6d0d9515fd"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21157295583fe8943475029ed5abdcf71eb3911894724e360acff1d61c1d54bc"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5635bd9cb9731e6d4a1132a498dd34f764034a8ce60cef4f5319c0541159392f"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2012c72d854c2d03e45d06ae57f40d78e5770d252f195b93f581acf3ba44496e"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd86c085fae2efd48ac91dd7ccffcfc0571387fe1193d33b6394db7ef31fe2a4"}, + {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:fa6693661a4c91757f4412306191b6dc88c1703f780c8234035eac011922bc01"}, + {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:59c0b02d0a6c384d453fece7566d1c7e6b7bae4fc5874ef2ef46d56776d61c9e"}, + {file = "cffi-1.15.1-cp310-cp310-win32.whl", hash = "sha256:cba9d6b9a7d64d4bd46167096fc9d2f835e25d7e4c121fb2ddfc6528fb0413b2"}, + {file = "cffi-1.15.1-cp310-cp310-win_amd64.whl", hash = "sha256:ce4bcc037df4fc5e3d184794f27bdaab018943698f4ca31630bc7f84a7b69c6d"}, + {file = "cffi-1.15.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3d08afd128ddaa624a48cf2b859afef385b720bb4b43df214f85616922e6a5ac"}, + {file = "cffi-1.15.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3799aecf2e17cf585d977b780ce79ff0dc9b78d799fc694221ce814c2c19db83"}, + {file = "cffi-1.15.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a591fe9e525846e4d154205572a029f653ada1a78b93697f3b5a8f1f2bc055b9"}, + {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3548db281cd7d2561c9ad9984681c95f7b0e38881201e157833a2342c30d5e8c"}, + {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91fc98adde3d7881af9b59ed0294046f3806221863722ba7d8d120c575314325"}, + {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94411f22c3985acaec6f83c6df553f2dbe17b698cc7f8ae751ff2237d96b9e3c"}, + {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:03425bdae262c76aad70202debd780501fabeaca237cdfddc008987c0e0f59ef"}, + {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cc4d65aeeaa04136a12677d3dd0b1c0c94dc43abac5860ab33cceb42b801c1e8"}, + {file = "cffi-1.15.1-cp311-cp311-win32.whl", hash = "sha256:a0f100c8912c114ff53e1202d0078b425bee3649ae34d7b070e9697f93c5d52d"}, + {file = "cffi-1.15.1-cp311-cp311-win_amd64.whl", hash = "sha256:04ed324bda3cda42b9b695d51bb7d54b680b9719cfab04227cdd1e04e5de3104"}, + {file = "cffi-1.15.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50a74364d85fd319352182ef59c5c790484a336f6db772c1a9231f1c3ed0cbd7"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e263d77ee3dd201c3a142934a086a4450861778baaeeb45db4591ef65550b0a6"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cec7d9412a9102bdc577382c3929b337320c4c4c4849f2c5cdd14d7368c5562d"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4289fc34b2f5316fbb762d75362931e351941fa95fa18789191b33fc4cf9504a"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:173379135477dc8cac4bc58f45db08ab45d228b3363adb7af79436135d028405"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6975a3fac6bc83c4a65c9f9fcab9e47019a11d3d2cf7f3c0d03431bf145a941e"}, + {file = "cffi-1.15.1-cp36-cp36m-win32.whl", hash = "sha256:2470043b93ff09bf8fb1d46d1cb756ce6132c54826661a32d4e4d132e1977adf"}, + {file = "cffi-1.15.1-cp36-cp36m-win_amd64.whl", hash = "sha256:30d78fbc8ebf9c92c9b7823ee18eb92f2e6ef79b45ac84db507f52fbe3ec4497"}, + {file = "cffi-1.15.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:198caafb44239b60e252492445da556afafc7d1e3ab7a1fb3f0584ef6d742375"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5ef34d190326c3b1f822a5b7a45f6c4535e2f47ed06fec77d3d799c450b2651e"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8102eaf27e1e448db915d08afa8b41d6c7ca7a04b7d73af6514df10a3e74bd82"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5df2768244d19ab7f60546d0c7c63ce1581f7af8b5de3eb3004b9b6fc8a9f84b"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8c4917bd7ad33e8eb21e9a5bbba979b49d9a97acb3a803092cbc1133e20343c"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2642fe3142e4cc4af0799748233ad6da94c62a8bec3a6648bf8ee68b1c7426"}, + {file = "cffi-1.15.1-cp37-cp37m-win32.whl", hash = "sha256:e229a521186c75c8ad9490854fd8bbdd9a0c9aa3a524326b55be83b54d4e0ad9"}, + {file = "cffi-1.15.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a0b71b1b8fbf2b96e41c4d990244165e2c9be83d54962a9a1d118fd8657d2045"}, + {file = "cffi-1.15.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:320dab6e7cb2eacdf0e658569d2575c4dad258c0fcc794f46215e1e39f90f2c3"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e74c6b51a9ed6589199c787bf5f9875612ca4a8a0785fb2d4a84429badaf22a"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5c84c68147988265e60416b57fc83425a78058853509c1b0629c180094904a5"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b926aa83d1edb5aa5b427b4053dc420ec295a08e40911296b9eb1b6170f6cca"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:87c450779d0914f2861b8526e035c5e6da0a3199d8f1add1a665e1cbc6fc6d02"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f2c9f67e9821cad2e5f480bc8d83b8742896f1242dba247911072d4fa94c192"}, + {file = "cffi-1.15.1-cp38-cp38-win32.whl", hash = "sha256:8b7ee99e510d7b66cdb6c593f21c043c248537a32e0bedf02e01e9553a172314"}, + {file = "cffi-1.15.1-cp38-cp38-win_amd64.whl", hash = "sha256:00a9ed42e88df81ffae7a8ab6d9356b371399b91dbdf0c3cb1e84c03a13aceb5"}, + {file = "cffi-1.15.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:54a2db7b78338edd780e7ef7f9f6c442500fb0d41a5a4ea24fff1c929d5af585"}, + {file = "cffi-1.15.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fcd131dd944808b5bdb38e6f5b53013c5aa4f334c5cad0c72742f6eba4b73db0"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7473e861101c9e72452f9bf8acb984947aa1661a7704553a9f6e4baa5ba64415"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c9a799e985904922a4d207a94eae35c78ebae90e128f0c4e521ce339396be9d"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3bcde07039e586f91b45c88f8583ea7cf7a0770df3a1649627bf598332cb6984"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33ab79603146aace82c2427da5ca6e58f2b3f2fb5da893ceac0c42218a40be35"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d598b938678ebf3c67377cdd45e09d431369c3b1a5b331058c338e201f12b27"}, + {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:db0fbb9c62743ce59a9ff687eb5f4afbe77e5e8403d6697f7446e5f609976f76"}, + {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:98d85c6a2bef81588d9227dde12db8a7f47f639f4a17c9ae08e773aa9c697bf3"}, + {file = "cffi-1.15.1-cp39-cp39-win32.whl", hash = "sha256:40f4774f5a9d4f5e344f31a32b5096977b5d48560c5592e2f3d2c4374bd543ee"}, + {file = "cffi-1.15.1-cp39-cp39-win_amd64.whl", hash = "sha256:70df4e3b545a17496c9b3f41f5115e69a4f2e77e94e1d2a8e1070bc0c38c8a3c"}, + {file = "cffi-1.15.1.tar.gz", hash = "sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9"}, +] + +[package.dependencies] +pycparser = "*" + +[[package]] +name = "charset-normalizer" +version = "3.2.0" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "charset-normalizer-3.2.0.tar.gz", hash = "sha256:3bb3d25a8e6c0aedd251753a79ae98a093c7e7b471faa3aa9a93a81431987ace"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0b87549028f680ca955556e3bd57013ab47474c3124dc069faa0b6545b6c9710"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7c70087bfee18a42b4040bb9ec1ca15a08242cf5867c58726530bdf3945672ed"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a103b3a7069b62f5d4890ae1b8f0597618f628b286b03d4bc9195230b154bfa9"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94aea8eff76ee6d1cdacb07dd2123a68283cb5569e0250feab1240058f53b623"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:db901e2ac34c931d73054d9797383d0f8009991e723dab15109740a63e7f902a"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b0dac0ff919ba34d4df1b6131f59ce95b08b9065233446be7e459f95554c0dc8"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:193cbc708ea3aca45e7221ae58f0fd63f933753a9bfb498a3b474878f12caaad"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09393e1b2a9461950b1c9a45d5fd251dc7c6f228acab64da1c9c0165d9c7765c"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:baacc6aee0b2ef6f3d308e197b5d7a81c0e70b06beae1f1fcacffdbd124fe0e3"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:bf420121d4c8dce6b889f0e8e4ec0ca34b7f40186203f06a946fa0276ba54029"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:c04a46716adde8d927adb9457bbe39cf473e1e2c2f5d0a16ceb837e5d841ad4f"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:aaf63899c94de41fe3cf934601b0f7ccb6b428c6e4eeb80da72c58eab077b19a"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d62e51710986674142526ab9f78663ca2b0726066ae26b78b22e0f5e571238dd"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-win32.whl", hash = "sha256:04e57ab9fbf9607b77f7d057974694b4f6b142da9ed4a199859d9d4d5c63fe96"}, + {file = "charset_normalizer-3.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:48021783bdf96e3d6de03a6e39a1171ed5bd7e8bb93fc84cc649d11490f87cea"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4957669ef390f0e6719db3613ab3a7631e68424604a7b448f079bee145da6e09"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:46fb8c61d794b78ec7134a715a3e564aafc8f6b5e338417cb19fe9f57a5a9bf2"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f779d3ad205f108d14e99bb3859aa7dd8e9c68874617c72354d7ecaec2a054ac"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f25c229a6ba38a35ae6e25ca1264621cc25d4d38dca2942a7fce0b67a4efe918"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2efb1bd13885392adfda4614c33d3b68dee4921fd0ac1d3988f8cbb7d589e72a"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f30b48dd7fa1474554b0b0f3fdfdd4c13b5c737a3c6284d3cdc424ec0ffff3a"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:246de67b99b6851627d945db38147d1b209a899311b1305dd84916f2b88526c6"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bd9b3b31adcb054116447ea22caa61a285d92e94d710aa5ec97992ff5eb7cf3"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:8c2f5e83493748286002f9369f3e6607c565a6a90425a3a1fef5ae32a36d749d"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:3170c9399da12c9dc66366e9d14da8bf7147e1e9d9ea566067bbce7bb74bd9c2"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:7a4826ad2bd6b07ca615c74ab91f32f6c96d08f6fcc3902ceeedaec8cdc3bcd6"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:3b1613dd5aee995ec6d4c69f00378bbd07614702a315a2cf6c1d21461fe17c23"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9e608aafdb55eb9f255034709e20d5a83b6d60c054df0802fa9c9883d0a937aa"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-win32.whl", hash = "sha256:f2a1d0fd4242bd8643ce6f98927cf9c04540af6efa92323e9d3124f57727bfc1"}, + {file = "charset_normalizer-3.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:681eb3d7e02e3c3655d1b16059fbfb605ac464c834a0c629048a30fad2b27489"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c57921cda3a80d0f2b8aec7e25c8aa14479ea92b5b51b6876d975d925a2ea346"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41b25eaa7d15909cf3ac4c96088c1f266a9a93ec44f87f1d13d4a0e86c81b982"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f058f6963fd82eb143c692cecdc89e075fa0828db2e5b291070485390b2f1c9c"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a7647ebdfb9682b7bb97e2a5e7cb6ae735b1c25008a70b906aecca294ee96cf4"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eef9df1eefada2c09a5e7a40991b9fc6ac6ef20b1372abd48d2794a316dc0449"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e03b8895a6990c9ab2cdcd0f2fe44088ca1c65ae592b8f795c3294af00a461c3"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:ee4006268ed33370957f55bf2e6f4d263eaf4dc3cfc473d1d90baff6ed36ce4a"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c4983bf937209c57240cff65906b18bb35e64ae872da6a0db937d7b4af845dd7"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:3bb7fda7260735efe66d5107fb7e6af6a7c04c7fce9b2514e04b7a74b06bf5dd"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:72814c01533f51d68702802d74f77ea026b5ec52793c791e2da806a3844a46c3"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:70c610f6cbe4b9fce272c407dd9d07e33e6bf7b4aa1b7ffb6f6ded8e634e3592"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-win32.whl", hash = "sha256:a401b4598e5d3f4a9a811f3daf42ee2291790c7f9d74b18d75d6e21dda98a1a1"}, + {file = "charset_normalizer-3.2.0-cp37-cp37m-win_amd64.whl", hash = "sha256:c0b21078a4b56965e2b12f247467b234734491897e99c1d51cee628da9786959"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:95eb302ff792e12aba9a8b8f8474ab229a83c103d74a750ec0bd1c1eea32e669"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1a100c6d595a7f316f1b6f01d20815d916e75ff98c27a01ae817439ea7726329"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6339d047dab2780cc6220f46306628e04d9750f02f983ddb37439ca47ced7149"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4b749b9cc6ee664a3300bb3a273c1ca8068c46be705b6c31cf5d276f8628a94"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a38856a971c602f98472050165cea2cdc97709240373041b69030be15047691f"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f87f746ee241d30d6ed93969de31e5ffd09a2961a051e60ae6bddde9ec3583aa"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89f1b185a01fe560bc8ae5f619e924407efca2191b56ce749ec84982fc59a32a"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e1c8a2f4c69e08e89632defbfabec2feb8a8d99edc9f89ce33c4b9e36ab63037"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2f4ac36d8e2b4cc1aa71df3dd84ff8efbe3bfb97ac41242fbcfc053c67434f46"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a386ebe437176aab38c041de1260cd3ea459c6ce5263594399880bbc398225b2"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:ccd16eb18a849fd8dcb23e23380e2f0a354e8daa0c984b8a732d9cfaba3a776d"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:e6a5bf2cba5ae1bb80b154ed68a3cfa2fa00fde979a7f50d6598d3e17d9ac20c"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:45de3f87179c1823e6d9e32156fb14c1927fcc9aba21433f088fdfb555b77c10"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-win32.whl", hash = "sha256:1000fba1057b92a65daec275aec30586c3de2401ccdcd41f8a5c1e2c87078706"}, + {file = "charset_normalizer-3.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:8b2c760cfc7042b27ebdb4a43a4453bd829a5742503599144d54a032c5dc7e9e"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:855eafa5d5a2034b4621c74925d89c5efef61418570e5ef9b37717d9c796419c"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:203f0c8871d5a7987be20c72442488a0b8cfd0f43b7973771640fc593f56321f"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e857a2232ba53ae940d3456f7533ce6ca98b81917d47adc3c7fd55dad8fab858"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e86d77b090dbddbe78867a0275cb4df08ea195e660f1f7f13435a4649e954e5"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c4fb39a81950ec280984b3a44f5bd12819953dc5fa3a7e6fa7a80db5ee853952"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2dee8e57f052ef5353cf608e0b4c871aee320dd1b87d351c28764fc0ca55f9f4"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8700f06d0ce6f128de3ccdbc1acaea1ee264d2caa9ca05daaf492fde7c2a7200"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1920d4ff15ce893210c1f0c0e9d19bfbecb7983c76b33f046c13a8ffbd570252"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:c1c76a1743432b4b60ab3358c937a3fe1341c828ae6194108a94c69028247f22"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f7560358a6811e52e9c4d142d497f1a6e10103d3a6881f18d04dbce3729c0e2c"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:c8063cf17b19661471ecbdb3df1c84f24ad2e389e326ccaf89e3fb2484d8dd7e"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:cd6dbe0238f7743d0efe563ab46294f54f9bc8f4b9bcf57c3c666cc5bc9d1299"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:1249cbbf3d3b04902ff081ffbb33ce3377fa6e4c7356f759f3cd076cc138d020"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-win32.whl", hash = "sha256:6c409c0deba34f147f77efaa67b8e4bb83d2f11c8806405f76397ae5b8c0d1c9"}, + {file = "charset_normalizer-3.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:7095f6fbfaa55defb6b733cfeb14efaae7a29f0b59d8cf213be4e7ca0b857b80"}, + {file = "charset_normalizer-3.2.0-py3-none-any.whl", hash = "sha256:8e098148dd37b4ce3baca71fb394c81dc5d9c7728c95df695d2dca218edf40e6"}, +] + +[[package]] +name = "click" +version = "8.1.6" +description = "Composable command line interface toolkit" +optional = false +python-versions = ">=3.7" +files = [ + {file = "click-8.1.6-py3-none-any.whl", hash = "sha256:fa244bb30b3b5ee2cae3da8f55c9e5e0c0e86093306301fb418eb9dc40fbded5"}, + {file = "click-8.1.6.tar.gz", hash = "sha256:48ee849951919527a045bfe3bf7baa8a959c423134e1a5b98c05c20ba75a1cbd"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "cryptography" +version = "41.0.2" +description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." +optional = false +python-versions = ">=3.7" +files = [ + {file = "cryptography-41.0.2-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:01f1d9e537f9a15b037d5d9ee442b8c22e3ae11ce65ea1f3316a41c78756b711"}, + {file = "cryptography-41.0.2-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:079347de771f9282fbfe0e0236c716686950c19dee1b76240ab09ce1624d76d7"}, + {file = "cryptography-41.0.2-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:439c3cc4c0d42fa999b83ded80a9a1fb54d53c58d6e59234cfe97f241e6c781d"}, + {file = "cryptography-41.0.2-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f14ad275364c8b4e525d018f6716537ae7b6d369c094805cae45300847e0894f"}, + {file = "cryptography-41.0.2-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:84609ade00a6ec59a89729e87a503c6e36af98ddcd566d5f3be52e29ba993182"}, + {file = "cryptography-41.0.2-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:49c3222bb8f8e800aead2e376cbef687bc9e3cb9b58b29a261210456a7783d83"}, + {file = "cryptography-41.0.2-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:d73f419a56d74fef257955f51b18d046f3506270a5fd2ac5febbfa259d6c0fa5"}, + {file = "cryptography-41.0.2-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:2a034bf7d9ca894720f2ec1d8b7b5832d7e363571828037f9e0c4f18c1b58a58"}, + {file = "cryptography-41.0.2-cp37-abi3-win32.whl", hash = "sha256:d124682c7a23c9764e54ca9ab5b308b14b18eba02722b8659fb238546de83a76"}, + {file = "cryptography-41.0.2-cp37-abi3-win_amd64.whl", hash = "sha256:9c3fe6534d59d071ee82081ca3d71eed3210f76ebd0361798c74abc2bcf347d4"}, + {file = "cryptography-41.0.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a719399b99377b218dac6cf547b6ec54e6ef20207b6165126a280b0ce97e0d2a"}, + {file = "cryptography-41.0.2-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:182be4171f9332b6741ee818ec27daff9fb00349f706629f5cbf417bd50e66fd"}, + {file = "cryptography-41.0.2-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:7a9a3bced53b7f09da251685224d6a260c3cb291768f54954e28f03ef14e3766"}, + {file = "cryptography-41.0.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:f0dc40e6f7aa37af01aba07277d3d64d5a03dc66d682097541ec4da03cc140ee"}, + {file = "cryptography-41.0.2-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:674b669d5daa64206c38e507808aae49904c988fa0a71c935e7006a3e1e83831"}, + {file = "cryptography-41.0.2-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:7af244b012711a26196450d34f483357e42aeddb04128885d95a69bd8b14b69b"}, + {file = "cryptography-41.0.2-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:9b6d717393dbae53d4e52684ef4f022444fc1cce3c48c38cb74fca29e1f08eaa"}, + {file = "cryptography-41.0.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:192255f539d7a89f2102d07d7375b1e0a81f7478925b3bc2e0549ebf739dae0e"}, + {file = "cryptography-41.0.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f772610fe364372de33d76edcd313636a25684edb94cee53fd790195f5989d14"}, + {file = "cryptography-41.0.2-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:b332cba64d99a70c1e0836902720887fb4529ea49ea7f5462cf6640e095e11d2"}, + {file = "cryptography-41.0.2-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:9a6673c1828db6270b76b22cc696f40cde9043eb90373da5c2f8f2158957f42f"}, + {file = "cryptography-41.0.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:342f3767e25876751e14f8459ad85e77e660537ca0a066e10e75df9c9e9099f0"}, + {file = "cryptography-41.0.2.tar.gz", hash = "sha256:7d230bf856164de164ecb615ccc14c7fc6de6906ddd5b491f3af90d3514c925c"}, +] + +[package.dependencies] +cffi = ">=1.12" + +[package.extras] +docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.1.1)"] +docstest = ["pyenchant (>=1.6.11)", "sphinxcontrib-spelling (>=4.0.1)", "twine (>=1.12.0)"] +nox = ["nox"] +pep8test = ["black", "check-sdist", "mypy", "ruff"] +sdist = ["build"] +ssh = ["bcrypt (>=3.1.5)"] +test = ["pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] +test-randomorder = ["pytest-randomly"] + +[[package]] +name = "flask" +version = "2.3.2" +description = "A simple framework for building complex web applications." +optional = false +python-versions = ">=3.8" +files = [ + {file = "Flask-2.3.2-py3-none-any.whl", hash = "sha256:77fd4e1249d8c9923de34907236b747ced06e5467ecac1a7bb7115ae0e9670b0"}, + {file = "Flask-2.3.2.tar.gz", hash = "sha256:8c2f9abd47a9e8df7f0c3f091ce9497d011dc3b31effcf4c85a6e2b50f4114ef"}, +] + +[package.dependencies] +blinker = ">=1.6.2" +click = ">=8.1.3" +importlib-metadata = {version = ">=3.6.0", markers = "python_version < \"3.10\""} +itsdangerous = ">=2.1.2" +Jinja2 = ">=3.1.2" +Werkzeug = ">=2.3.3" + +[package.extras] +async = ["asgiref (>=3.2)"] +dotenv = ["python-dotenv"] + +[[package]] +name = "flask-sqlalchemy" +version = "3.0.5" +description = "Add SQLAlchemy support to your Flask application." +optional = false +python-versions = ">=3.7" +files = [ + {file = "flask_sqlalchemy-3.0.5-py3-none-any.whl", hash = "sha256:cabb6600ddd819a9f859f36515bb1bd8e7dbf30206cc679d2b081dff9e383283"}, + {file = "flask_sqlalchemy-3.0.5.tar.gz", hash = "sha256:c5765e58ca145401b52106c0f46178569243c5da25556be2c231ecc60867c5b1"}, +] + +[package.dependencies] +flask = ">=2.2.5" +sqlalchemy = ">=1.4.18" + +[[package]] +name = "frestq" +version = "4.0.0" +description = "" +optional = false +python-versions = "^3.8" +files = [] +develop = false + +[package.dependencies] +apscheduler = "3.7.0" +cryptography = "^41.0" +Flask = "^2.3" +flask-sqlalchemy = "^3.0" +ipaddress = "^1.0" +prettytable = "^3.8.0" +pyOpenSSL = "23.2.0" +requests = "^2.31" + +[package.source] +type = "git" +url = "https://github.com/sequentech/frestq.git" +reference = "feat/master/k8s" +resolved_reference = "0d911d96d89406518a3083a8221dca78bde145d0" + +[[package]] +name = "greenlet" +version = "2.0.2" +description = "Lightweight in-process concurrent programming" +optional = false +python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*" +files = [ + {file = "greenlet-2.0.2-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:bdfea8c661e80d3c1c99ad7c3ff74e6e87184895bbaca6ee8cc61209f8b9b85d"}, + {file = "greenlet-2.0.2-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:9d14b83fab60d5e8abe587d51c75b252bcc21683f24699ada8fb275d7712f5a9"}, + {file = "greenlet-2.0.2-cp27-cp27m-win32.whl", hash = "sha256:6c3acb79b0bfd4fe733dff8bc62695283b57949ebcca05ae5c129eb606ff2d74"}, + {file = "greenlet-2.0.2-cp27-cp27m-win_amd64.whl", hash = "sha256:283737e0da3f08bd637b5ad058507e578dd462db259f7f6e4c5c365ba4ee9343"}, + {file = "greenlet-2.0.2-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:d27ec7509b9c18b6d73f2f5ede2622441de812e7b1a80bbd446cb0633bd3d5ae"}, + {file = "greenlet-2.0.2-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:30bcf80dda7f15ac77ba5af2b961bdd9dbc77fd4ac6105cee85b0d0a5fcf74df"}, + {file = "greenlet-2.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26fbfce90728d82bc9e6c38ea4d038cba20b7faf8a0ca53a9c07b67318d46088"}, + {file = "greenlet-2.0.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9190f09060ea4debddd24665d6804b995a9c122ef5917ab26e1566dcc712ceeb"}, + {file = "greenlet-2.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d75209eed723105f9596807495d58d10b3470fa6732dd6756595e89925ce2470"}, + {file = "greenlet-2.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:3a51c9751078733d88e013587b108f1b7a1fb106d402fb390740f002b6f6551a"}, + {file = "greenlet-2.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:76ae285c8104046b3a7f06b42f29c7b73f77683df18c49ab5af7983994c2dd91"}, + {file = "greenlet-2.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:2d4686f195e32d36b4d7cf2d166857dbd0ee9f3d20ae349b6bf8afc8485b3645"}, + {file = "greenlet-2.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c4302695ad8027363e96311df24ee28978162cdcdd2006476c43970b384a244c"}, + {file = "greenlet-2.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c48f54ef8e05f04d6eff74b8233f6063cb1ed960243eacc474ee73a2ea8573ca"}, + {file = "greenlet-2.0.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a1846f1b999e78e13837c93c778dcfc3365902cfb8d1bdb7dd73ead37059f0d0"}, + {file = "greenlet-2.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a06ad5312349fec0ab944664b01d26f8d1f05009566339ac6f63f56589bc1a2"}, + {file = "greenlet-2.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:eff4eb9b7eb3e4d0cae3d28c283dc16d9bed6b193c2e1ace3ed86ce48ea8df19"}, + {file = "greenlet-2.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5454276c07d27a740c5892f4907c86327b632127dd9abec42ee62e12427ff7e3"}, + {file = "greenlet-2.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:7cafd1208fdbe93b67c7086876f061f660cfddc44f404279c1585bbf3cdc64c5"}, + {file = "greenlet-2.0.2-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:910841381caba4f744a44bf81bfd573c94e10b3045ee00de0cbf436fe50673a6"}, + {file = "greenlet-2.0.2-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:18a7f18b82b52ee85322d7a7874e676f34ab319b9f8cce5de06067384aa8ff43"}, + {file = "greenlet-2.0.2-cp35-cp35m-win32.whl", hash = "sha256:03a8f4f3430c3b3ff8d10a2a86028c660355ab637cee9333d63d66b56f09d52a"}, + {file = "greenlet-2.0.2-cp35-cp35m-win_amd64.whl", hash = "sha256:4b58adb399c4d61d912c4c331984d60eb66565175cdf4a34792cd9600f21b394"}, + {file = "greenlet-2.0.2-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:703f18f3fda276b9a916f0934d2fb6d989bf0b4fb5a64825260eb9bfd52d78f0"}, + {file = "greenlet-2.0.2-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:32e5b64b148966d9cccc2c8d35a671409e45f195864560829f395a54226408d3"}, + {file = "greenlet-2.0.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2dd11f291565a81d71dab10b7033395b7a3a5456e637cf997a6f33ebdf06f8db"}, + {file = "greenlet-2.0.2-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e0f72c9ddb8cd28532185f54cc1453f2c16fb417a08b53a855c4e6a418edd099"}, + {file = "greenlet-2.0.2-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cd021c754b162c0fb55ad5d6b9d960db667faad0fa2ff25bb6e1301b0b6e6a75"}, + {file = "greenlet-2.0.2-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:3c9b12575734155d0c09d6c3e10dbd81665d5c18e1a7c6597df72fd05990c8cf"}, + {file = "greenlet-2.0.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:b9ec052b06a0524f0e35bd8790686a1da006bd911dd1ef7d50b77bfbad74e292"}, + {file = "greenlet-2.0.2-cp36-cp36m-win32.whl", hash = "sha256:dbfcfc0218093a19c252ca8eb9aee3d29cfdcb586df21049b9d777fd32c14fd9"}, + {file = "greenlet-2.0.2-cp36-cp36m-win_amd64.whl", hash = "sha256:9f35ec95538f50292f6d8f2c9c9f8a3c6540bbfec21c9e5b4b751e0a7c20864f"}, + {file = "greenlet-2.0.2-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:d5508f0b173e6aa47273bdc0a0b5ba055b59662ba7c7ee5119528f466585526b"}, + {file = "greenlet-2.0.2-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:f82d4d717d8ef19188687aa32b8363e96062911e63ba22a0cff7802a8e58e5f1"}, + {file = "greenlet-2.0.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9c59a2120b55788e800d82dfa99b9e156ff8f2227f07c5e3012a45a399620b7"}, + {file = "greenlet-2.0.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2780572ec463d44c1d3ae850239508dbeb9fed38e294c68d19a24d925d9223ca"}, + {file = "greenlet-2.0.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:937e9020b514ceedb9c830c55d5c9872abc90f4b5862f89c0887033ae33c6f73"}, + {file = "greenlet-2.0.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:36abbf031e1c0f79dd5d596bfaf8e921c41df2bdf54ee1eed921ce1f52999a86"}, + {file = "greenlet-2.0.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:18e98fb3de7dba1c0a852731c3070cf022d14f0d68b4c87a19cc1016f3bb8b33"}, + {file = "greenlet-2.0.2-cp37-cp37m-win32.whl", hash = "sha256:3f6ea9bd35eb450837a3d80e77b517ea5bc56b4647f5502cd28de13675ee12f7"}, + {file = "greenlet-2.0.2-cp37-cp37m-win_amd64.whl", hash = "sha256:7492e2b7bd7c9b9916388d9df23fa49d9b88ac0640db0a5b4ecc2b653bf451e3"}, + {file = "greenlet-2.0.2-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:b864ba53912b6c3ab6bcb2beb19f19edd01a6bfcbdfe1f37ddd1778abfe75a30"}, + {file = "greenlet-2.0.2-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:ba2956617f1c42598a308a84c6cf021a90ff3862eddafd20c3333d50f0edb45b"}, + {file = "greenlet-2.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc3a569657468b6f3fb60587e48356fe512c1754ca05a564f11366ac9e306526"}, + {file = "greenlet-2.0.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8eab883b3b2a38cc1e050819ef06a7e6344d4a990d24d45bc6f2cf959045a45b"}, + {file = "greenlet-2.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:acd2162a36d3de67ee896c43effcd5ee3de247eb00354db411feb025aa319857"}, + {file = "greenlet-2.0.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:0bf60faf0bc2468089bdc5edd10555bab6e85152191df713e2ab1fcc86382b5a"}, + {file = "greenlet-2.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b0ef99cdbe2b682b9ccbb964743a6aca37905fda5e0452e5ee239b1654d37f2a"}, + {file = "greenlet-2.0.2-cp38-cp38-win32.whl", hash = "sha256:b80f600eddddce72320dbbc8e3784d16bd3fb7b517e82476d8da921f27d4b249"}, + {file = "greenlet-2.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:4d2e11331fc0c02b6e84b0d28ece3a36e0548ee1a1ce9ddde03752d9b79bba40"}, + {file = "greenlet-2.0.2-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:88d9ab96491d38a5ab7c56dd7a3cc37d83336ecc564e4e8816dbed12e5aaefc8"}, + {file = "greenlet-2.0.2-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:561091a7be172ab497a3527602d467e2b3fbe75f9e783d8b8ce403fa414f71a6"}, + {file = "greenlet-2.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:971ce5e14dc5e73715755d0ca2975ac88cfdaefcaab078a284fea6cfabf866df"}, + {file = "greenlet-2.0.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:be4ed120b52ae4d974aa40215fcdfde9194d63541c7ded40ee12eb4dda57b76b"}, + {file = "greenlet-2.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94c817e84245513926588caf1152e3b559ff794d505555211ca041f032abbb6b"}, + {file = "greenlet-2.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:1a819eef4b0e0b96bb0d98d797bef17dc1b4a10e8d7446be32d1da33e095dbb8"}, + {file = "greenlet-2.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7efde645ca1cc441d6dc4b48c0f7101e8d86b54c8530141b09fd31cef5149ec9"}, + {file = "greenlet-2.0.2-cp39-cp39-win32.whl", hash = "sha256:ea9872c80c132f4663822dd2a08d404073a5a9b5ba6155bea72fb2a79d1093b5"}, + {file = "greenlet-2.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:db1a39669102a1d8d12b57de2bb7e2ec9066a6f2b3da35ae511ff93b01b5d564"}, + {file = "greenlet-2.0.2.tar.gz", hash = "sha256:e7c8dc13af7db097bed64a051d2dd49e9f0af495c26995c00a9ee842690d34c0"}, +] + +[package.extras] +docs = ["Sphinx", "docutils (<0.18)"] +test = ["objgraph", "psutil"] + +[[package]] +name = "idna" +version = "3.4" +description = "Internationalized Domain Names in Applications (IDNA)" +optional = false +python-versions = ">=3.5" +files = [ + {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, + {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, +] + +[[package]] +name = "importlib-metadata" +version = "6.8.0" +description = "Read metadata from Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "importlib_metadata-6.8.0-py3-none-any.whl", hash = "sha256:3ebb78df84a805d7698245025b975d9d67053cd94c79245ba4b3eb694abe68bb"}, + {file = "importlib_metadata-6.8.0.tar.gz", hash = "sha256:dbace7892d8c0c4ac1ad096662232f831d4e64f4c4545bd53016a3e9d4654743"}, +] + +[package.dependencies] +zipp = ">=0.5" + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +perf = ["ipython"] +testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)", "pytest-ruff"] + +[[package]] +name = "ipaddress" +version = "1.0.23" +description = "IPv4/IPv6 manipulation library" +optional = false +python-versions = "*" +files = [ + {file = "ipaddress-1.0.23-py2.py3-none-any.whl", hash = "sha256:6e0f4a39e66cb5bb9a137b00276a2eff74f93b71dcbdad6f10ff7df9d3557fcc"}, + {file = "ipaddress-1.0.23.tar.gz", hash = "sha256:b7f8e0369580bb4a24d5ba1d7cc29660a4a6987763faf1d8a8046830e020e7e2"}, +] + +[[package]] +name = "itsdangerous" +version = "2.1.2" +description = "Safely pass data to untrusted environments and back." +optional = false +python-versions = ">=3.7" +files = [ + {file = "itsdangerous-2.1.2-py3-none-any.whl", hash = "sha256:2c2349112351b88699d8d4b6b075022c0808887cb7ad10069318a8b0bc88db44"}, + {file = "itsdangerous-2.1.2.tar.gz", hash = "sha256:5dbbc68b317e5e42f327f9021763545dc3fc3bfe22e6deb96aaf1fc38874156a"}, +] + +[[package]] +name = "jinja2" +version = "3.1.2" +description = "A very fast and expressive template engine." +optional = false +python-versions = ">=3.7" +files = [ + {file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"}, + {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"}, +] + +[package.dependencies] +MarkupSafe = ">=2.0" + +[package.extras] +i18n = ["Babel (>=2.7)"] + +[[package]] +name = "markupsafe" +version = "2.1.3" +description = "Safely add untrusted strings to HTML/XML markup." +optional = false +python-versions = ">=3.7" +files = [ + {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-win32.whl", hash = "sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-win_amd64.whl", hash = "sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca379055a47383d02a5400cb0d110cef0a776fc644cda797db0c5696cfd7e18e"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b7ff0f54cb4ff66dd38bebd335a38e2c22c41a8ee45aa608efc890ac3e3931bc"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c011a4149cfbcf9f03994ec2edffcb8b1dc2d2aede7ca243746df97a5d41ce48"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-win32.whl", hash = "sha256:8758846a7e80910096950b67071243da3e5a20ed2546e6392603c096778d48e0"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-win_amd64.whl", hash = "sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d080e0a5eb2529460b30190fcfcc4199bd7f827663f858a226a81bc27beaa97e"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-win32.whl", hash = "sha256:ceb01949af7121f9fc39f7d27f91be8546f3fb112c608bc4029aef0bab86a2a5"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-win_amd64.whl", hash = "sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05fb21170423db021895e1ea1e1f3ab3adb85d1c2333cbc2310f2a26bc77272e"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-win32.whl", hash = "sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-win_amd64.whl", hash = "sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba"}, + {file = "MarkupSafe-2.1.3.tar.gz", hash = "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad"}, +] + +[[package]] +name = "prettytable" +version = "3.8.0" +description = "A simple Python library for easily displaying tabular data in a visually appealing ASCII table format" +optional = false +python-versions = ">=3.8" +files = [ + {file = "prettytable-3.8.0-py3-none-any.whl", hash = "sha256:03481bca25ae0c28958c8cd6ac5165c159ce89f7ccde04d5c899b24b68bb13b7"}, + {file = "prettytable-3.8.0.tar.gz", hash = "sha256:031eae6a9102017e8c7c7906460d150b7ed78b20fd1d8c8be4edaf88556c07ce"}, +] + +[package.dependencies] +wcwidth = "*" + +[package.extras] +tests = ["pytest", "pytest-cov", "pytest-lazy-fixture"] + +[[package]] +name = "pycparser" +version = "2.21" +description = "C parser in Python" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"}, + {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"}, +] + +[[package]] +name = "pyopenssl" +version = "23.2.0" +description = "Python wrapper module around the OpenSSL library" +optional = false +python-versions = ">=3.6" +files = [ + {file = "pyOpenSSL-23.2.0-py3-none-any.whl", hash = "sha256:24f0dc5227396b3e831f4c7f602b950a5e9833d292c8e4a2e06b709292806ae2"}, + {file = "pyOpenSSL-23.2.0.tar.gz", hash = "sha256:276f931f55a452e7dea69c7173e984eb2a4407ce413c918aa34b55f82f9b8bac"}, +] + +[package.dependencies] +cryptography = ">=38.0.0,<40.0.0 || >40.0.0,<40.0.1 || >40.0.1,<42" + +[package.extras] +docs = ["sphinx (!=5.2.0,!=5.2.0.post0)", "sphinx-rtd-theme"] +test = ["flaky", "pretend", "pytest (>=3.0.1)"] + +[[package]] +name = "pytz" +version = "2023.3" +description = "World timezone definitions, modern and historical" +optional = false +python-versions = "*" +files = [ + {file = "pytz-2023.3-py2.py3-none-any.whl", hash = "sha256:a151b3abb88eda1d4e34a9814df37de2a80e301e68ba0fd856fb9b46bfbbbffb"}, + {file = "pytz-2023.3.tar.gz", hash = "sha256:1d8ce29db189191fb55338ee6d0387d82ab59f3d00eac103412d64e0ebd0c588"}, +] + +[[package]] +name = "requests" +version = "2.31.0" +description = "Python HTTP for Humans." +optional = false +python-versions = ">=3.7" +files = [ + {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, + {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, +] + +[package.dependencies] +certifi = ">=2017.4.17" +charset-normalizer = ">=2,<4" +idna = ">=2.5,<4" +urllib3 = ">=1.21.1,<3" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] + +[[package]] +name = "setuptools" +version = "68.0.0" +description = "Easily download, build, install, upgrade, and uninstall Python packages" +optional = false +python-versions = ">=3.7" +files = [ + {file = "setuptools-68.0.0-py3-none-any.whl", hash = "sha256:11e52c67415a381d10d6b462ced9cfb97066179f0e871399e006c4ab101fc85f"}, + {file = "setuptools-68.0.0.tar.gz", hash = "sha256:baf1fdb41c6da4cd2eae722e135500da913332ab3f2f5c7d33af9b492acb5235"}, +] + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] + +[[package]] +name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] + +[[package]] +name = "sqlalchemy" +version = "2.0.19" +description = "Database Abstraction Library" +optional = false +python-versions = ">=3.7" +files = [ + {file = "SQLAlchemy-2.0.19-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9deaae357edc2091a9ed5d25e9ee8bba98bcfae454b3911adeaf159c2e9ca9e3"}, + {file = "SQLAlchemy-2.0.19-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0bf0fd65b50a330261ec7fe3d091dfc1c577483c96a9fa1e4323e932961aa1b5"}, + {file = "SQLAlchemy-2.0.19-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d90ccc15ba1baa345796a8fb1965223ca7ded2d235ccbef80a47b85cea2d71a"}, + {file = "SQLAlchemy-2.0.19-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb4e688f6784427e5f9479d1a13617f573de8f7d4aa713ba82813bcd16e259d1"}, + {file = "SQLAlchemy-2.0.19-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:584f66e5e1979a7a00f4935015840be627e31ca29ad13f49a6e51e97a3fb8cae"}, + {file = "SQLAlchemy-2.0.19-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2c69ce70047b801d2aba3e5ff3cba32014558966109fecab0c39d16c18510f15"}, + {file = "SQLAlchemy-2.0.19-cp310-cp310-win32.whl", hash = "sha256:96f0463573469579d32ad0c91929548d78314ef95c210a8115346271beeeaaa2"}, + {file = "SQLAlchemy-2.0.19-cp310-cp310-win_amd64.whl", hash = "sha256:22bafb1da60c24514c141a7ff852b52f9f573fb933b1e6b5263f0daa28ce6db9"}, + {file = "SQLAlchemy-2.0.19-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d6894708eeb81f6d8193e996257223b6bb4041cb05a17cd5cf373ed836ef87a2"}, + {file = "SQLAlchemy-2.0.19-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d8f2afd1aafded7362b397581772c670f20ea84d0a780b93a1a1529da7c3d369"}, + {file = "SQLAlchemy-2.0.19-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b15afbf5aa76f2241184c1d3b61af1a72ba31ce4161013d7cb5c4c2fca04fd6e"}, + {file = "SQLAlchemy-2.0.19-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8fc05b59142445a4efb9c1fd75c334b431d35c304b0e33f4fa0ff1ea4890f92e"}, + {file = "SQLAlchemy-2.0.19-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:5831138f0cc06b43edf5f99541c64adf0ab0d41f9a4471fd63b54ae18399e4de"}, + {file = "SQLAlchemy-2.0.19-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3afa8a21a9046917b3a12ffe016ba7ebe7a55a6fc0c7d950beb303c735c3c3ad"}, + {file = "SQLAlchemy-2.0.19-cp311-cp311-win32.whl", hash = "sha256:c896d4e6ab2eba2afa1d56be3d0b936c56d4666e789bfc59d6ae76e9fcf46145"}, + {file = "SQLAlchemy-2.0.19-cp311-cp311-win_amd64.whl", hash = "sha256:024d2f67fb3ec697555e48caeb7147cfe2c08065a4f1a52d93c3d44fc8e6ad1c"}, + {file = "SQLAlchemy-2.0.19-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:89bc2b374ebee1a02fd2eae6fd0570b5ad897ee514e0f84c5c137c942772aa0c"}, + {file = "SQLAlchemy-2.0.19-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd4d410a76c3762511ae075d50f379ae09551d92525aa5bb307f8343bf7c2c12"}, + {file = "SQLAlchemy-2.0.19-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f469f15068cd8351826df4080ffe4cc6377c5bf7d29b5a07b0e717dddb4c7ea2"}, + {file = "SQLAlchemy-2.0.19-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:cda283700c984e699e8ef0fcc5c61f00c9d14b6f65a4f2767c97242513fcdd84"}, + {file = "SQLAlchemy-2.0.19-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:43699eb3f80920cc39a380c159ae21c8a8924fe071bccb68fc509e099420b148"}, + {file = "SQLAlchemy-2.0.19-cp37-cp37m-win32.whl", hash = "sha256:61ada5831db36d897e28eb95f0f81814525e0d7927fb51145526c4e63174920b"}, + {file = "SQLAlchemy-2.0.19-cp37-cp37m-win_amd64.whl", hash = "sha256:57d100a421d9ab4874f51285c059003292433c648df6abe6c9c904e5bd5b0828"}, + {file = "SQLAlchemy-2.0.19-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:16a310f5bc75a5b2ce7cb656d0e76eb13440b8354f927ff15cbaddd2523ee2d1"}, + {file = "SQLAlchemy-2.0.19-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cf7b5e3856cbf1876da4e9d9715546fa26b6e0ba1a682d5ed2fc3ca4c7c3ec5b"}, + {file = "SQLAlchemy-2.0.19-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e7b69d9ced4b53310a87117824b23c509c6fc1f692aa7272d47561347e133b6"}, + {file = "SQLAlchemy-2.0.19-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f9eb4575bfa5afc4b066528302bf12083da3175f71b64a43a7c0badda2be365"}, + {file = "SQLAlchemy-2.0.19-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6b54d1ad7a162857bb7c8ef689049c7cd9eae2f38864fc096d62ae10bc100c7d"}, + {file = "SQLAlchemy-2.0.19-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5d6afc41ca0ecf373366fd8e10aee2797128d3ae45eb8467b19da4899bcd1ee0"}, + {file = "SQLAlchemy-2.0.19-cp38-cp38-win32.whl", hash = "sha256:430614f18443b58ceb9dedec323ecddc0abb2b34e79d03503b5a7579cd73a531"}, + {file = "SQLAlchemy-2.0.19-cp38-cp38-win_amd64.whl", hash = "sha256:eb60699de43ba1a1f77363f563bb2c652f7748127ba3a774f7cf2c7804aa0d3d"}, + {file = "SQLAlchemy-2.0.19-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a752b7a9aceb0ba173955d4f780c64ee15a1a991f1c52d307d6215c6c73b3a4c"}, + {file = "SQLAlchemy-2.0.19-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7351c05db355da112e056a7b731253cbeffab9dfdb3be1e895368513c7d70106"}, + {file = "SQLAlchemy-2.0.19-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fa51ce4aea583b0c6b426f4b0563d3535c1c75986c4373a0987d84d22376585b"}, + {file = "SQLAlchemy-2.0.19-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ae7473a67cd82a41decfea58c0eac581209a0aa30f8bc9190926fbf628bb17f7"}, + {file = "SQLAlchemy-2.0.19-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:851a37898a8a39783aab603c7348eb5b20d83c76a14766a43f56e6ad422d1ec8"}, + {file = "SQLAlchemy-2.0.19-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:539010665c90e60c4a1650afe4ab49ca100c74e6aef882466f1de6471d414be7"}, + {file = "SQLAlchemy-2.0.19-cp39-cp39-win32.whl", hash = "sha256:f82c310ddf97b04e1392c33cf9a70909e0ae10a7e2ddc1d64495e3abdc5d19fb"}, + {file = "SQLAlchemy-2.0.19-cp39-cp39-win_amd64.whl", hash = "sha256:8e712cfd2e07b801bc6b60fdf64853bc2bd0af33ca8fa46166a23fe11ce0dbb0"}, + {file = "SQLAlchemy-2.0.19-py3-none-any.whl", hash = "sha256:314145c1389b021a9ad5aa3a18bac6f5d939f9087d7fc5443be28cba19d2c972"}, + {file = "SQLAlchemy-2.0.19.tar.gz", hash = "sha256:77a14fa20264af73ddcdb1e2b9c5a829b8cc6b8304d0f093271980e36c200a3f"}, +] + +[package.dependencies] +greenlet = {version = "!=0.4.17", markers = "platform_machine == \"win32\" or platform_machine == \"WIN32\" or platform_machine == \"AMD64\" or platform_machine == \"amd64\" or platform_machine == \"x86_64\" or platform_machine == \"ppc64le\" or platform_machine == \"aarch64\""} +typing-extensions = ">=4.2.0" + +[package.extras] +aiomysql = ["aiomysql", "greenlet (!=0.4.17)"] +aiosqlite = ["aiosqlite", "greenlet (!=0.4.17)", "typing-extensions (!=3.10.0.1)"] +asyncio = ["greenlet (!=0.4.17)"] +asyncmy = ["asyncmy (>=0.2.3,!=0.2.4,!=0.2.6)", "greenlet (!=0.4.17)"] +mariadb-connector = ["mariadb (>=1.0.1,!=1.1.2,!=1.1.5)"] +mssql = ["pyodbc"] +mssql-pymssql = ["pymssql"] +mssql-pyodbc = ["pyodbc"] +mypy = ["mypy (>=0.910)"] +mysql = ["mysqlclient (>=1.4.0)"] +mysql-connector = ["mysql-connector-python"] +oracle = ["cx-oracle (>=7)"] +oracle-oracledb = ["oracledb (>=1.0.1)"] +postgresql = ["psycopg2 (>=2.7)"] +postgresql-asyncpg = ["asyncpg", "greenlet (!=0.4.17)"] +postgresql-pg8000 = ["pg8000 (>=1.29.1)"] +postgresql-psycopg = ["psycopg (>=3.0.7)"] +postgresql-psycopg2binary = ["psycopg2-binary"] +postgresql-psycopg2cffi = ["psycopg2cffi"] +postgresql-psycopgbinary = ["psycopg[binary] (>=3.0.7)"] +pymysql = ["pymysql"] +sqlcipher = ["sqlcipher3-binary"] + +[[package]] +name = "typing-extensions" +version = "4.7.1" +description = "Backported and Experimental Type Hints for Python 3.7+" +optional = false +python-versions = ">=3.7" +files = [ + {file = "typing_extensions-4.7.1-py3-none-any.whl", hash = "sha256:440d5dd3af93b060174bf433bccd69b0babc3b15b1a8dca43789fd7f61514b36"}, + {file = "typing_extensions-4.7.1.tar.gz", hash = "sha256:b75ddc264f0ba5615db7ba217daeb99701ad295353c45f9e95963337ceeeffb2"}, +] + +[[package]] +name = "tzlocal" +version = "2.1" +description = "tzinfo object for the local timezone" +optional = false +python-versions = "*" +files = [ + {file = "tzlocal-2.1-py2.py3-none-any.whl", hash = "sha256:e2cb6c6b5b604af38597403e9852872d7f534962ae2954c7f35efcb1ccacf4a4"}, + {file = "tzlocal-2.1.tar.gz", hash = "sha256:643c97c5294aedc737780a49d9df30889321cbe1204eac2c2ec6134035a92e44"}, +] + +[package.dependencies] +pytz = "*" + +[[package]] +name = "urllib3" +version = "2.0.4" +description = "HTTP library with thread-safe connection pooling, file post, and more." +optional = false +python-versions = ">=3.7" +files = [ + {file = "urllib3-2.0.4-py3-none-any.whl", hash = "sha256:de7df1803967d2c2a98e4b11bb7d6bd9210474c46e8a0401514e3a42a75ebde4"}, + {file = "urllib3-2.0.4.tar.gz", hash = "sha256:8d22f86aae8ef5e410d4f539fde9ce6b2113a001bb4d189e0aed70642d602b11"}, +] + +[package.extras] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +secure = ["certifi", "cryptography (>=1.9)", "idna (>=2.0.0)", "pyopenssl (>=17.1.0)", "urllib3-secure-extra"] +socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] +zstd = ["zstandard (>=0.18.0)"] + +[[package]] +name = "wcwidth" +version = "0.2.6" +description = "Measures the displayed width of unicode strings in a terminal" +optional = false +python-versions = "*" +files = [ + {file = "wcwidth-0.2.6-py2.py3-none-any.whl", hash = "sha256:795b138f6875577cd91bba52baf9e445cd5118fd32723b460e30a0af30ea230e"}, + {file = "wcwidth-0.2.6.tar.gz", hash = "sha256:a5220780a404dbe3353789870978e472cfe477761f06ee55077256e509b156d0"}, +] + +[[package]] +name = "werkzeug" +version = "2.3.6" +description = "The comprehensive WSGI web application library." +optional = false +python-versions = ">=3.8" +files = [ + {file = "Werkzeug-2.3.6-py3-none-any.whl", hash = "sha256:935539fa1413afbb9195b24880778422ed620c0fc09670945185cce4d91a8890"}, + {file = "Werkzeug-2.3.6.tar.gz", hash = "sha256:98c774df2f91b05550078891dee5f0eb0cb797a522c757a2452b9cee5b202330"}, +] + +[package.dependencies] +MarkupSafe = ">=2.1.1" + +[package.extras] +watchdog = ["watchdog (>=2.3)"] + +[[package]] +name = "zipp" +version = "3.16.2" +description = "Backport of pathlib-compatible object wrapper for zip files" +optional = false +python-versions = ">=3.8" +files = [ + {file = "zipp-3.16.2-py3-none-any.whl", hash = "sha256:679e51dd4403591b2d6838a48de3d283f3d188412a9782faadf845f298736ba0"}, + {file = "zipp-3.16.2.tar.gz", hash = "sha256:ebc15946aa78bd63458992fc81ec3b6f7b1e92d51c35e6de1c3804e73b799147"}, +] + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy (>=0.9.1)", "pytest-ruff"] + +[metadata] +lock-version = "2.0" +python-versions = "^3.8" +content-hash = "acacef90b58165739cce2b821d73ec032c78246c3a88a7cb5c6b5cb75b6d7884" diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..df3c16c --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,23 @@ +# SPDX-FileCopyrightText: 2013-2021 Sequent Tech Inc +# SPDX-License-Identifier: AGPL-3.0-only + +[tool.poetry] +name = "election-orchestra" +version = "0.1.0" +description = "" +authors = ["Eduardo Robles "] +license = "AGPL-3.0-only" +readme = "README.md" +packages = [{include = "election_orchestra"}] + +[tool.poetry.dependencies] +frestq = { git = "https://github.com/sequentech/frestq.git", branch = "feat/master/k8s" } +python = "^3.8" +requests = "^2.31" +Flask = "^2.3" +flask-sqlalchemy = "^3.0" +cryptography = "^41.0" + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 9e67129..0000000 --- a/requirements.txt +++ /dev/null @@ -1,19 +0,0 @@ -git+https://github.com/sequentech/frestq.git@feat/master/k8s -requests==2.31.0 -Flask==2.3.2 -Flask-SQLAlchemy==2.5.1 -Jinja2==3.1.2 -MarkupSafe==2.1.1 -SQLAlchemy==1.3.23 -Werkzeug==2.3.3 -argparse==1.2.1 -cffi==1.14.4 -cryptography==41.0.0 -pyOpenSSL==23.2.0 -ipdb==0.13.9 -ipython==8.10.0 -itsdangerous==2.1.2 -prettytable==0.7.2 -psycopg2-binary==2.8.6 -pycparser==2.10 -uwsgi==2.0.21 diff --git a/requirements.txt.license b/requirements.txt.license deleted file mode 100644 index 99cf9ad..0000000 --- a/requirements.txt.license +++ /dev/null @@ -1,3 +0,0 @@ -SPDX-FileCopyrightText: 2021 Sequent Tech Inc - -SPDX-License-Identifier: AGPL-3.0-only \ No newline at end of file diff --git a/setup.py b/setup.py deleted file mode 100644 index 83a574a..0000000 --- a/setup.py +++ /dev/null @@ -1,46 +0,0 @@ -# -# SPDX-FileCopyrightText: 2013-2021 Sequent Tech Inc -# -# SPDX-License-Identifier: AGPL-3.0-only -# -from setuptools import setup, find_packages - -setup( - name='election-orchestra', - version='master', - author='Sequent Team', - author_email='legal@sequentech.io', - packages=find_packages(), - scripts=[], - url='http://github.com/sequentech/election-orchestra', - license='AGPL-3.0', - description='election orchestrator', - long_description=open('README.md').read(), - install_requires=[ - 'frestq @ git+https://github.com/sequentech/frestq.git@feat/master/k8s', - 'requests==2.31.0', - 'Flask==2.3.2', - 'Flask-SQLAlchemy==2.5.1', - 'Jinja2==3.1.2', - 'MarkupSafe==2.1.1', - 'SQLAlchemy==1.3.23', - 'Werkzeug==2.3.3', - 'argparse==1.2.1', - 'cffi==1.14.4', - 'cryptography==41.0.0', - 'pyOpenSSL==23.2.0', - 'ipdb==0.13.9', - 'ipython==8.10.0', - 'itsdangerous==2.1.2', - 'prettytable==0.7.2', - 'psycopg2-binary==2.8.6', - 'pycparser==2.10', - 'uwsgi==2.0.21', - ], - classifiers=[ - "Programming Language :: Python :: 3", - "OSI Approved :: GNU Affero General Public License v3" - ], - python_requires='>=3.5', - dependency_links = [] -) From 21ac989a6c130c624e65b7a6771e69cc8f8dd7dd Mon Sep 17 00:00:00 2001 From: Eduardo Robles Date: Wed, 26 Jul 2023 14:03:02 +0000 Subject: [PATCH 13/26] allow setting config from env vars, like it's typical in docker environments --- devenv.nix | 3 +- election_orchestra/app.py | 87 +++++++++++++++++++++------------------ 2 files changed, 49 insertions(+), 41 deletions(-) diff --git a/devenv.nix b/devenv.nix index 5e9d4c9..8be3e4b 100644 --- a/devenv.nix +++ b/devenv.nix @@ -20,9 +20,10 @@ # `flask` because otherwise it fails for some reason. processes.serve.exec = ( '' + set -eux export FLASK_ENV=development export FLASK_APP=election_orchestra.app:app - export PYTHONPATH="$DEVENV_STATE/venv/lib/python3.10/site-packages:$PYTHONPATH" + export PYTHONPATH="$VIRTUAL_ENV/lib/python3.10/site-packages/:$PYTHONPATH" export FRESTQ_SETTINGS=base_settings.py python election_orchestra/app.py --createdb '' diff --git a/election_orchestra/app.py b/election_orchestra/app.py index ebd6eac..7eeeca5 100755 --- a/election_orchestra/app.py +++ b/election_orchestra/app.py @@ -11,75 +11,82 @@ import os import sys -from flask import Flask -from flask_sqlalchemy import SQLAlchemy - # Note: we need to import app before decorators or it won't work from frestq.app import app -from frestq import decorators + +import tally_election.performer_jobs +from public_api import public_api +from taskqueue import start_queue logging.basicConfig(level=logging.DEBUG) ### configuration -# debug, set to false on production deployment -DEBUG = True - -# see https://stackoverflow.com/questions/33738467/how-do-i-know-if-i-can-disable-sqlalchemy-track-modifications/33790196#33790196 -SQLALCHEMY_TRACK_MODIFICATIONS = False +class DefaultConfig(object): + # debug, set to false on production deployment + DEBUG = True -# URL to our HTTP server -VFORK_SERVER_URL = 'http://127.0.0.1' + # see https://stackoverflow.com/questions/33738467/how-do-i-know-if-i-can-disable-sqlalchemy-track-modifications/33790196#33790196 + SQLALCHEMY_TRACK_MODIFICATIONS = False -VFORK_SERVER_PORT_RANGE = [4081, 4083] + # URL to our HTTP server + VFORK_SERVER_URL = 'http://127.0.0.1' -# Socket address given as : to our hint server. -# A hint server is a simple UDP server that reduces latency and -# traffic on the HTTP servers. -VFORK_HINT_SERVER_SOCKET = '127.0.0.1' + VFORK_SERVER_PORT_RANGE = [4081, 4083] -VFORK_HINT_SERVER_PORT_RANGE = [8081, 8083] + # Socket address given as : to our hint server. + # A hint server is a simple UDP server that reduces latency and + # traffic on the HTTP servers. + VFORK_HINT_SERVER_SOCKET = '127.0.0.1' -ROOT_PATH = os.path.split(os.path.abspath(__file__))[0] + VFORK_HINT_SERVER_PORT_RANGE = [8081, 8083] -SQLALCHEMY_DATABASE_URI = 'sqlite:///%s/db.sqlite' % ROOT_PATH + ROOT_PATH = os.path.split(os.path.abspath(__file__))[0] -PRIVATE_DATA_PATH = os.path.join(ROOT_PATH, 'datastore/private') -PUBLIC_DATA_PATH = os.path.join(ROOT_PATH, 'datastore/public') - -import models -import reject_adapter -import create_election.director_jobs -import create_election.performer_jobs -import tally_election.director_jobs -import tally_election.performer_jobs -from public_api import public_api -from taskqueue import start_queue + SQLALCHEMY_DATABASE_URI = 'sqlite:///%s/db.sqlite' % ROOT_PATH + PRIVATE_DATA_PATH = os.path.join(ROOT_PATH, 'datastore/private') + PUBLIC_DATA_PATH = os.path.join(ROOT_PATH, 'datastore/public') def extra_parse_args(self, parser): - parser.add_argument("--reset-tally", help="Enable making a second tally for :election_id", - type=int) + parser.add_argument( + "--reset-tally", + help="Enable making a second tally for :election_id", + type=int + ) def extra_run(self): - if self.pargs.reset_tally and isinstance(self.pargs.reset_tally,(int,long)): + if self.pargs.reset_tally and isinstance(self.pargs.reset_tally, int): election_id = self.pargs.reset_tally tally_election.performer_jobs.reset_tally(election_id) return True return False +def update_config(app): + ''' + override config from environment variables, using defaults from the class + DefaultConfig. + ''' + app.configure_app(scheduler=False, config_object=DefaultConfig()) + config_var_prefix = "ELECTION_ORCHESTRA_" + for variable, value in os.environ.items(): + if variable.startswith(config_var_prefix): + env_name = variable.split(config_var_prefix)[1] + app.config[env_name] = value + +update_config(app) +app.register_blueprint(public_api, url_prefix='/public_api') + if __name__ == "__main__": - app.configure_app(scheduler=False, config_object=__name__) - app.register_blueprint(public_api, url_prefix='/public_api') if len(sys.argv) == 3 and sys.argv[1] == "create-tarball": from tools import create_tarball create_tarball.create(sys.argv[2]) exit(0) - app.run(parse_args=True, extra_parse_func=extra_parse_args, - extra_run=extra_run) + app.run( + parse_args=True, + extra_parse_func=extra_parse_args, + extra_run=extra_run + ) else: - app.configure_app(config_object=__name__) - app.register_blueprint(public_api, url_prefix='/public_api') start_queue() - From 98b75710f98e7de364aafd9031e51c6394719a36 Mon Sep 17 00:00:00 2001 From: Eduardo Robles Date: Wed, 26 Jul 2023 19:16:42 +0000 Subject: [PATCH 14/26] WIP --- devenv.nix | 31 ++-- election_orchestra/__init__.py | 0 election_orchestra/app.py | 38 +++-- .../create_election/director_jobs.py | 10 +- .../create_election/performer_jobs.py | 6 +- election_orchestra/keys_management.py | 5 +- election_orchestra/models.py | 1 - election_orchestra/public_api.py | 8 +- .../tally_election/director_jobs.py | 6 +- .../tally_election/performer_jobs.py | 10 +- election_orchestra/taskqueue.py | 4 +- election_orchestra/tools/create_tarball.py | 6 +- election_orchestra/utils.py | 2 +- election_orchestra/vmn.py | 2 +- poetry.lock | 157 +++++++++--------- pyproject.toml | 5 +- 16 files changed, 146 insertions(+), 145 deletions(-) create mode 100644 election_orchestra/__init__.py diff --git a/devenv.nix b/devenv.nix index 8be3e4b..303c3bf 100644 --- a/devenv.nix +++ b/devenv.nix @@ -10,10 +10,6 @@ # to inspect containers pkgs.dive - - # used for building uwsgi (TODO: deprecated): - pkgs.gcc - pkgs.libffi ]; # HACK: I had to use `python $DEVENV_STATE/venv/bin/flask` instead of simply @@ -23,24 +19,18 @@ set -eux export FLASK_ENV=development export FLASK_APP=election_orchestra.app:app - export PYTHONPATH="$VIRTUAL_ENV/lib/python3.10/site-packages/:$PYTHONPATH" - export FRESTQ_SETTINGS=base_settings.py - python election_orchestra/app.py --createdb + export PYTHONPATH="/workspace:$VIRTUAL_ENV/lib/python3.10/site-packages/:$PYTHONPATH" + export EO_SQLALCHEMY_DATABASE_URI="postgresql+psycopg2:///dev" + timeout 20 bash -c 'until psql -c "SELECT 1" dev; do sleep 0.5; done' + python -m election_orchestra.app --createdb + flask run '' - # + (if (config.container.isBuilding) - # then "python $DEVENV_STATE/venv/bin/flask run" - # else "flask run" - # ) + # flask run + # '' ); # HACK: The venv is missing from PYTHONPATH and PATH so I add them manually - enterShell = ( - ''git --version;'' - + ( - lib.optionalString (config.container.isBuilding) - ''export PYTHONPATH="$DEVENV_STATE/venv/lib/python3.10/site-packages:$PYTHONPATH"'' - ) - ); + enterShell = ''git --version;''; # https://devenv.sh/languages/ languages.nix.enable = true; @@ -54,6 +44,9 @@ services.postgres = { enable = true; package = pkgs.postgresql_15; - initialDatabases = [{ name = "election-orchestra"; }]; + initialScript = '' + CREATE USER dev WITH PASSWORD 'dev' SUPERUSER; + ''; + initialDatabases = [{ name = "dev"; }]; }; } \ No newline at end of file diff --git a/election_orchestra/__init__.py b/election_orchestra/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/election_orchestra/app.py b/election_orchestra/app.py index 7eeeca5..87f3188 100755 --- a/election_orchestra/app.py +++ b/election_orchestra/app.py @@ -2,7 +2,7 @@ # -*- coding: utf-8 -*- # -# SPDX-FileCopyrightText: 2013-2021 Sequent Tech Inc +# SPDX-FileCopyrightText: 2013-2023 Sequent Tech Inc # # SPDX-License-Identifier: AGPL-3.0-only # @@ -11,18 +11,15 @@ import os import sys -# Note: we need to import app before decorators or it won't work -from frestq.app import app +from frestq.app import app, DefaultConfig as FrestqDefaultConfig -import tally_election.performer_jobs -from public_api import public_api -from taskqueue import start_queue +from .models import * +from .tally_election import performer_jobs +from .public_api import public_api +from .taskqueue import start_queue -logging.basicConfig(level=logging.DEBUG) -### configuration - -class DefaultConfig(object): +class DefaultConfig(FrestqDefaultConfig): # debug, set to false on production deployment DEBUG = True @@ -48,6 +45,7 @@ class DefaultConfig(object): PRIVATE_DATA_PATH = os.path.join(ROOT_PATH, 'datastore/private') PUBLIC_DATA_PATH = os.path.join(ROOT_PATH, 'datastore/public') + def extra_parse_args(self, parser): parser.add_argument( "--reset-tally", @@ -58,25 +56,27 @@ def extra_parse_args(self, parser): def extra_run(self): if self.pargs.reset_tally and isinstance(self.pargs.reset_tally, int): election_id = self.pargs.reset_tally - tally_election.performer_jobs.reset_tally(election_id) + performer_jobs.reset_tally(election_id) return True return False -def update_config(app): +def configure_app(app): ''' override config from environment variables, using defaults from the class DefaultConfig. ''' - app.configure_app(scheduler=False, config_object=DefaultConfig()) - config_var_prefix = "ELECTION_ORCHESTRA_" + config_object = DefaultConfig() + config_var_prefix = "EO_" for variable, value in os.environ.items(): if variable.startswith(config_var_prefix): env_name = variable.split(config_var_prefix)[1] - app.config[env_name] = value + logging.debug(f"SET:from-env-var config.${env_name} = ${value}") + setattr(config_object, env_name, value) + app.configure_app(scheduler=False, config_object=config_object) + app.register_blueprint(public_api, url_prefix='/public_api') -update_config(app) -app.register_blueprint(public_api, url_prefix='/public_api') +configure_app(app) if __name__ == "__main__": if len(sys.argv) == 3 and sys.argv[1] == "create-tarball": @@ -89,4 +89,6 @@ def update_config(app): extra_run=extra_run ) else: - start_queue() + # used when run using uwsgi or similar + with app.app_context(): + start_queue() diff --git a/election_orchestra/create_election/director_jobs.py b/election_orchestra/create_election/director_jobs.py index f8e7cea..3c984be 100644 --- a/election_orchestra/create_election/director_jobs.py +++ b/election_orchestra/create_election/director_jobs.py @@ -21,12 +21,12 @@ from frestq.action_handlers import TaskHandler from frestq.app import app, db -from models import Election, Authority, Session -from reject_adapter import RejectAdapter -from utils import mkdir_recursive -from vmn import * +from ..models import Election, Authority, Session +from ..reject_adapter import RejectAdapter +from ..utils import mkdir_recursive +from ..vmn import * -from taskqueue import end_task +from ..taskqueue import end_task @decorators.local_task @decorators.task(action="create_election", queue="launch_task") diff --git a/election_orchestra/create_election/performer_jobs.py b/election_orchestra/create_election/performer_jobs.py index 180bf83..37bfbd4 100644 --- a/election_orchestra/create_election/performer_jobs.py +++ b/election_orchestra/create_election/performer_jobs.py @@ -20,9 +20,9 @@ from frestq.protocol import certs_differ from frestq.app import app, db -from models import Election, Authority, Session -from utils import * -from vmn import * +from ..models import Election, Authority, Session +from ..utils import * +from ..vmn import * def check_pipe(requirements, l): for req in requirements: diff --git a/election_orchestra/keys_management.py b/election_orchestra/keys_management.py index 7708ac1..17ffc6f 100644 --- a/election_orchestra/keys_management.py +++ b/election_orchestra/keys_management.py @@ -3,14 +3,13 @@ # SPDX-License-Identifier: AGPL-3.0-only from frestq.app import app, db -from models import Election +from .models import Election, Session import tempfile -from models import Session import os import shutil -from tools.create_tarball import hash_file, hash_bytes, create_deterministic_tar_file, extract_tar_file from flask import request, make_response import base64 +from .tools.create_tarball import hash_file, hash_bytes, create_deterministic_tar_file, extract_tar_file def get_election_by_id(election_id): return db.session.query(Election)\ diff --git a/election_orchestra/models.py b/election_orchestra/models.py index 1f78060..71f3fb0 100644 --- a/election_orchestra/models.py +++ b/election_orchestra/models.py @@ -11,7 +11,6 @@ from flask import Flask, jsonify from flask_sqlalchemy import SQLAlchemy from sqlalchemy.types import TypeDecorator, VARCHAR - from frestq.app import db class Election(db.Model): diff --git a/election_orchestra/public_api.py b/election_orchestra/public_api.py index fefaace..7acf02d 100644 --- a/election_orchestra/public_api.py +++ b/election_orchestra/public_api.py @@ -17,12 +17,12 @@ from frestq.tasks import SimpleTask, TaskError from frestq.app import app, db -from models import Election, Authority, QueryQueue -from create_election.performer_jobs import check_election_data -import keys_management +from .models import Election, Authority, QueryQueue +from .create_election.performer_jobs import check_election_data +from election_orchestra import keys_management -from taskqueue import queue_task, apply_task, dequeue_task +from .taskqueue import queue_task, apply_task, dequeue_task public_api = Blueprint('public_api', __name__) diff --git a/election_orchestra/tally_election/director_jobs.py b/election_orchestra/tally_election/director_jobs.py index 1017ded..fb6026f 100644 --- a/election_orchestra/tally_election/director_jobs.py +++ b/election_orchestra/tally_election/director_jobs.py @@ -17,9 +17,9 @@ from frestq.action_handlers import TaskHandler, SynchronizedTaskHandler from frestq.app import app, db -from models import Election, Authority, Session -from reject_adapter import RejectAdapter -from utils import mkdir_recursive +from ..models import Election, Authority, Session +from ..reject_adapter import RejectAdapter +from ..utils import mkdir_recursive from taskqueue import end_task diff --git a/election_orchestra/tally_election/performer_jobs.py b/election_orchestra/tally_election/performer_jobs.py index 086fa0e..eee5e3c 100644 --- a/election_orchestra/tally_election/performer_jobs.py +++ b/election_orchestra/tally_election/performer_jobs.py @@ -24,11 +24,11 @@ from frestq.protocol import certs_differ from frestq.action_handlers import TaskHandler -from models import Election, Authority, Session, Ballot -from reject_adapter import RejectAdapter -from utils import * -from vmn import * -from sha256 import hash_file, hash_data +from ..models import Election, Authority, Session, Ballot +from ..reject_adapter import RejectAdapter +from ..utils import * +from ..vmn import * +from ..sha256 import hash_file, hash_data # we just use always the same timestamp for the files for creating # deterministic tars diff --git a/election_orchestra/taskqueue.py b/election_orchestra/taskqueue.py index 2bb9141..2eec2ec 100644 --- a/election_orchestra/taskqueue.py +++ b/election_orchestra/taskqueue.py @@ -9,8 +9,8 @@ from frestq.app import app, db from frestq.utils import loads, dumps from frestq.tasks import SimpleTask, TaskError -from models import Election, Authority, QueryQueue -from create_election.performer_jobs import check_election_data +from .models import Election, Authority, QueryQueue +from .create_election.performer_jobs import check_election_data import threading diff --git a/election_orchestra/tools/create_tarball.py b/election_orchestra/tools/create_tarball.py index 407dd18..11a2544 100644 --- a/election_orchestra/tools/create_tarball.py +++ b/election_orchestra/tools/create_tarball.py @@ -24,9 +24,9 @@ from frestq.protocol import certs_differ from frestq.action_handlers import TaskHandler -from models import Election, Authority, Session -from utils import * -from vmn import * +from ..models import Election, Authority, Session +from ..utils import * +from ..vmn import * BUF_SIZE = 10*1024 diff --git a/election_orchestra/utils.py b/election_orchestra/utils.py index 03ed174..e3bd42e 100644 --- a/election_orchestra/utils.py +++ b/election_orchestra/utils.py @@ -12,7 +12,7 @@ import hashlib from frestq.app import app -from asyncproc import Process +from .asyncproc import Process def mkdir_recursive(path): if not os.path.exists(path): diff --git a/election_orchestra/vmn.py b/election_orchestra/vmn.py index 8b2cd02..f3b5288 100644 --- a/election_orchestra/vmn.py +++ b/election_orchestra/vmn.py @@ -4,7 +4,7 @@ # SPDX-License-Identifier: AGPL-3.0-only # import subprocess -from utils import * +from .utils import * from frestq.app import app # diff --git a/poetry.lock b/poetry.lock index f0f4c91..18788fb 100644 --- a/poetry.lock +++ b/poetry.lock @@ -307,18 +307,18 @@ dotenv = ["python-dotenv"] [[package]] name = "flask-sqlalchemy" -version = "3.0.5" -description = "Add SQLAlchemy support to your Flask application." +version = "2.5.1" +description = "Adds SQLAlchemy support to your Flask application." optional = false -python-versions = ">=3.7" +python-versions = ">= 2.7, != 3.0.*, != 3.1.*, != 3.2.*, != 3.3.*" files = [ - {file = "flask_sqlalchemy-3.0.5-py3-none-any.whl", hash = "sha256:cabb6600ddd819a9f859f36515bb1bd8e7dbf30206cc679d2b081dff9e383283"}, - {file = "flask_sqlalchemy-3.0.5.tar.gz", hash = "sha256:c5765e58ca145401b52106c0f46178569243c5da25556be2c231ecc60867c5b1"}, + {file = "Flask-SQLAlchemy-2.5.1.tar.gz", hash = "sha256:2bda44b43e7cacb15d4e05ff3cc1f8bc97936cc464623424102bfc2c35e95912"}, + {file = "Flask_SQLAlchemy-2.5.1-py2.py3-none-any.whl", hash = "sha256:f12c3d4cc5cc7fdcc148b9527ea05671718c3ea45d50c7e732cceb33f574b390"}, ] [package.dependencies] -flask = ">=2.2.5" -sqlalchemy = ">=1.4.18" +Flask = ">=0.10" +SQLAlchemy = ">=0.8.0" [[package]] name = "frestq" @@ -333,17 +333,18 @@ develop = false apscheduler = "3.7.0" cryptography = "^41.0" Flask = "^2.3" -flask-sqlalchemy = "^3.0" +flask-sqlalchemy = "^2.5" ipaddress = "^1.0" prettytable = "^3.8.0" pyOpenSSL = "23.2.0" requests = "^2.31" +sqlalchemy = "^1.4" [package.source] type = "git" url = "https://github.com/sequentech/frestq.git" reference = "feat/master/k8s" -resolved_reference = "0d911d96d89406518a3083a8221dca78bde145d0" +resolved_reference = "2a588c17b142d8c514902805cf52caf2926dcb54" [[package]] name = "greenlet" @@ -563,6 +564,28 @@ wcwidth = "*" [package.extras] tests = ["pytest", "pytest-cov", "pytest-lazy-fixture"] +[[package]] +name = "psycopg2" +version = "2.9.6" +description = "psycopg2 - Python-PostgreSQL Database Adapter" +optional = false +python-versions = ">=3.6" +files = [ + {file = "psycopg2-2.9.6-cp310-cp310-win32.whl", hash = "sha256:f7a7a5ee78ba7dc74265ba69e010ae89dae635eea0e97b055fb641a01a31d2b1"}, + {file = "psycopg2-2.9.6-cp310-cp310-win_amd64.whl", hash = "sha256:f75001a1cbbe523e00b0ef896a5a1ada2da93ccd752b7636db5a99bc57c44494"}, + {file = "psycopg2-2.9.6-cp311-cp311-win32.whl", hash = "sha256:53f4ad0a3988f983e9b49a5d9765d663bbe84f508ed655affdb810af9d0972ad"}, + {file = "psycopg2-2.9.6-cp311-cp311-win_amd64.whl", hash = "sha256:b81fcb9ecfc584f661b71c889edeae70bae30d3ef74fa0ca388ecda50b1222b7"}, + {file = "psycopg2-2.9.6-cp36-cp36m-win32.whl", hash = "sha256:11aca705ec888e4f4cea97289a0bf0f22a067a32614f6ef64fcf7b8bfbc53744"}, + {file = "psycopg2-2.9.6-cp36-cp36m-win_amd64.whl", hash = "sha256:36c941a767341d11549c0fbdbb2bf5be2eda4caf87f65dfcd7d146828bd27f39"}, + {file = "psycopg2-2.9.6-cp37-cp37m-win32.whl", hash = "sha256:869776630c04f335d4124f120b7fb377fe44b0a7645ab3c34b4ba42516951889"}, + {file = "psycopg2-2.9.6-cp37-cp37m-win_amd64.whl", hash = "sha256:a8ad4a47f42aa6aec8d061fdae21eaed8d864d4bb0f0cade5ad32ca16fcd6258"}, + {file = "psycopg2-2.9.6-cp38-cp38-win32.whl", hash = "sha256:2362ee4d07ac85ff0ad93e22c693d0f37ff63e28f0615a16b6635a645f4b9214"}, + {file = "psycopg2-2.9.6-cp38-cp38-win_amd64.whl", hash = "sha256:d24ead3716a7d093b90b27b3d73459fe8cd90fd7065cf43b3c40966221d8c394"}, + {file = "psycopg2-2.9.6-cp39-cp39-win32.whl", hash = "sha256:1861a53a6a0fd248e42ea37c957d36950da00266378746588eab4f4b5649e95f"}, + {file = "psycopg2-2.9.6-cp39-cp39-win_amd64.whl", hash = "sha256:ded2faa2e6dfb430af7713d87ab4abbfc764d8d7fb73eafe96a24155f906ebf5"}, + {file = "psycopg2-2.9.6.tar.gz", hash = "sha256:f15158418fd826831b28585e2ab48ed8df2d0d98f502a2b4fe619e7d5ca29011"}, +] + [[package]] name = "pycparser" version = "2.21" @@ -653,93 +676,75 @@ files = [ [[package]] name = "sqlalchemy" -version = "2.0.19" +version = "1.4.49" description = "Database Abstraction Library" optional = false -python-versions = ">=3.7" +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" files = [ - {file = "SQLAlchemy-2.0.19-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9deaae357edc2091a9ed5d25e9ee8bba98bcfae454b3911adeaf159c2e9ca9e3"}, - {file = "SQLAlchemy-2.0.19-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0bf0fd65b50a330261ec7fe3d091dfc1c577483c96a9fa1e4323e932961aa1b5"}, - {file = "SQLAlchemy-2.0.19-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d90ccc15ba1baa345796a8fb1965223ca7ded2d235ccbef80a47b85cea2d71a"}, - {file = "SQLAlchemy-2.0.19-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb4e688f6784427e5f9479d1a13617f573de8f7d4aa713ba82813bcd16e259d1"}, - {file = "SQLAlchemy-2.0.19-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:584f66e5e1979a7a00f4935015840be627e31ca29ad13f49a6e51e97a3fb8cae"}, - {file = "SQLAlchemy-2.0.19-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2c69ce70047b801d2aba3e5ff3cba32014558966109fecab0c39d16c18510f15"}, - {file = "SQLAlchemy-2.0.19-cp310-cp310-win32.whl", hash = "sha256:96f0463573469579d32ad0c91929548d78314ef95c210a8115346271beeeaaa2"}, - {file = "SQLAlchemy-2.0.19-cp310-cp310-win_amd64.whl", hash = "sha256:22bafb1da60c24514c141a7ff852b52f9f573fb933b1e6b5263f0daa28ce6db9"}, - {file = "SQLAlchemy-2.0.19-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d6894708eeb81f6d8193e996257223b6bb4041cb05a17cd5cf373ed836ef87a2"}, - {file = "SQLAlchemy-2.0.19-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d8f2afd1aafded7362b397581772c670f20ea84d0a780b93a1a1529da7c3d369"}, - {file = "SQLAlchemy-2.0.19-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b15afbf5aa76f2241184c1d3b61af1a72ba31ce4161013d7cb5c4c2fca04fd6e"}, - {file = "SQLAlchemy-2.0.19-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8fc05b59142445a4efb9c1fd75c334b431d35c304b0e33f4fa0ff1ea4890f92e"}, - {file = "SQLAlchemy-2.0.19-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:5831138f0cc06b43edf5f99541c64adf0ab0d41f9a4471fd63b54ae18399e4de"}, - {file = "SQLAlchemy-2.0.19-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3afa8a21a9046917b3a12ffe016ba7ebe7a55a6fc0c7d950beb303c735c3c3ad"}, - {file = "SQLAlchemy-2.0.19-cp311-cp311-win32.whl", hash = "sha256:c896d4e6ab2eba2afa1d56be3d0b936c56d4666e789bfc59d6ae76e9fcf46145"}, - {file = "SQLAlchemy-2.0.19-cp311-cp311-win_amd64.whl", hash = "sha256:024d2f67fb3ec697555e48caeb7147cfe2c08065a4f1a52d93c3d44fc8e6ad1c"}, - {file = "SQLAlchemy-2.0.19-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:89bc2b374ebee1a02fd2eae6fd0570b5ad897ee514e0f84c5c137c942772aa0c"}, - {file = "SQLAlchemy-2.0.19-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd4d410a76c3762511ae075d50f379ae09551d92525aa5bb307f8343bf7c2c12"}, - {file = "SQLAlchemy-2.0.19-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f469f15068cd8351826df4080ffe4cc6377c5bf7d29b5a07b0e717dddb4c7ea2"}, - {file = "SQLAlchemy-2.0.19-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:cda283700c984e699e8ef0fcc5c61f00c9d14b6f65a4f2767c97242513fcdd84"}, - {file = "SQLAlchemy-2.0.19-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:43699eb3f80920cc39a380c159ae21c8a8924fe071bccb68fc509e099420b148"}, - {file = "SQLAlchemy-2.0.19-cp37-cp37m-win32.whl", hash = "sha256:61ada5831db36d897e28eb95f0f81814525e0d7927fb51145526c4e63174920b"}, - {file = "SQLAlchemy-2.0.19-cp37-cp37m-win_amd64.whl", hash = "sha256:57d100a421d9ab4874f51285c059003292433c648df6abe6c9c904e5bd5b0828"}, - {file = "SQLAlchemy-2.0.19-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:16a310f5bc75a5b2ce7cb656d0e76eb13440b8354f927ff15cbaddd2523ee2d1"}, - {file = "SQLAlchemy-2.0.19-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cf7b5e3856cbf1876da4e9d9715546fa26b6e0ba1a682d5ed2fc3ca4c7c3ec5b"}, - {file = "SQLAlchemy-2.0.19-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e7b69d9ced4b53310a87117824b23c509c6fc1f692aa7272d47561347e133b6"}, - {file = "SQLAlchemy-2.0.19-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f9eb4575bfa5afc4b066528302bf12083da3175f71b64a43a7c0badda2be365"}, - {file = "SQLAlchemy-2.0.19-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6b54d1ad7a162857bb7c8ef689049c7cd9eae2f38864fc096d62ae10bc100c7d"}, - {file = "SQLAlchemy-2.0.19-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5d6afc41ca0ecf373366fd8e10aee2797128d3ae45eb8467b19da4899bcd1ee0"}, - {file = "SQLAlchemy-2.0.19-cp38-cp38-win32.whl", hash = "sha256:430614f18443b58ceb9dedec323ecddc0abb2b34e79d03503b5a7579cd73a531"}, - {file = "SQLAlchemy-2.0.19-cp38-cp38-win_amd64.whl", hash = "sha256:eb60699de43ba1a1f77363f563bb2c652f7748127ba3a774f7cf2c7804aa0d3d"}, - {file = "SQLAlchemy-2.0.19-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a752b7a9aceb0ba173955d4f780c64ee15a1a991f1c52d307d6215c6c73b3a4c"}, - {file = "SQLAlchemy-2.0.19-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7351c05db355da112e056a7b731253cbeffab9dfdb3be1e895368513c7d70106"}, - {file = "SQLAlchemy-2.0.19-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fa51ce4aea583b0c6b426f4b0563d3535c1c75986c4373a0987d84d22376585b"}, - {file = "SQLAlchemy-2.0.19-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ae7473a67cd82a41decfea58c0eac581209a0aa30f8bc9190926fbf628bb17f7"}, - {file = "SQLAlchemy-2.0.19-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:851a37898a8a39783aab603c7348eb5b20d83c76a14766a43f56e6ad422d1ec8"}, - {file = "SQLAlchemy-2.0.19-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:539010665c90e60c4a1650afe4ab49ca100c74e6aef882466f1de6471d414be7"}, - {file = "SQLAlchemy-2.0.19-cp39-cp39-win32.whl", hash = "sha256:f82c310ddf97b04e1392c33cf9a70909e0ae10a7e2ddc1d64495e3abdc5d19fb"}, - {file = "SQLAlchemy-2.0.19-cp39-cp39-win_amd64.whl", hash = "sha256:8e712cfd2e07b801bc6b60fdf64853bc2bd0af33ca8fa46166a23fe11ce0dbb0"}, - {file = "SQLAlchemy-2.0.19-py3-none-any.whl", hash = "sha256:314145c1389b021a9ad5aa3a18bac6f5d939f9087d7fc5443be28cba19d2c972"}, - {file = "SQLAlchemy-2.0.19.tar.gz", hash = "sha256:77a14fa20264af73ddcdb1e2b9c5a829b8cc6b8304d0f093271980e36c200a3f"}, + {file = "SQLAlchemy-1.4.49-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:2e126cf98b7fd38f1e33c64484406b78e937b1a280e078ef558b95bf5b6895f6"}, + {file = "SQLAlchemy-1.4.49-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:03db81b89fe7ef3857b4a00b63dedd632d6183d4ea5a31c5d8a92e000a41fc71"}, + {file = "SQLAlchemy-1.4.49-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:95b9df9afd680b7a3b13b38adf6e3a38995da5e162cc7524ef08e3be4e5ed3e1"}, + {file = "SQLAlchemy-1.4.49-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a63e43bf3f668c11bb0444ce6e809c1227b8f067ca1068898f3008a273f52b09"}, + {file = "SQLAlchemy-1.4.49-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f835c050ebaa4e48b18403bed2c0fda986525896efd76c245bdd4db995e51a4c"}, + {file = "SQLAlchemy-1.4.49-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c21b172dfb22e0db303ff6419451f0cac891d2e911bb9fbf8003d717f1bcf91"}, + {file = "SQLAlchemy-1.4.49-cp310-cp310-win32.whl", hash = "sha256:5fb1ebdfc8373b5a291485757bd6431de8d7ed42c27439f543c81f6c8febd729"}, + {file = "SQLAlchemy-1.4.49-cp310-cp310-win_amd64.whl", hash = "sha256:f8a65990c9c490f4651b5c02abccc9f113a7f56fa482031ac8cb88b70bc8ccaa"}, + {file = "SQLAlchemy-1.4.49-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8923dfdf24d5aa8a3adb59723f54118dd4fe62cf59ed0d0d65d940579c1170a4"}, + {file = "SQLAlchemy-1.4.49-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9ab2c507a7a439f13ca4499db6d3f50423d1d65dc9b5ed897e70941d9e135b0"}, + {file = "SQLAlchemy-1.4.49-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5debe7d49b8acf1f3035317e63d9ec8d5e4d904c6e75a2a9246a119f5f2fdf3d"}, + {file = "SQLAlchemy-1.4.49-cp311-cp311-win32.whl", hash = "sha256:82b08e82da3756765c2e75f327b9bf6b0f043c9c3925fb95fb51e1567fa4ee87"}, + {file = "SQLAlchemy-1.4.49-cp311-cp311-win_amd64.whl", hash = "sha256:171e04eeb5d1c0d96a544caf982621a1711d078dbc5c96f11d6469169bd003f1"}, + {file = "SQLAlchemy-1.4.49-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:36e58f8c4fe43984384e3fbe6341ac99b6b4e083de2fe838f0fdb91cebe9e9cb"}, + {file = "SQLAlchemy-1.4.49-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b31e67ff419013f99ad6f8fc73ee19ea31585e1e9fe773744c0f3ce58c039c30"}, + {file = "SQLAlchemy-1.4.49-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c14b29d9e1529f99efd550cd04dbb6db6ba5d690abb96d52de2bff4ed518bc95"}, + {file = "SQLAlchemy-1.4.49-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c40f3470e084d31247aea228aa1c39bbc0904c2b9ccbf5d3cfa2ea2dac06f26d"}, + {file = "SQLAlchemy-1.4.49-cp36-cp36m-win32.whl", hash = "sha256:706bfa02157b97c136547c406f263e4c6274a7b061b3eb9742915dd774bbc264"}, + {file = "SQLAlchemy-1.4.49-cp36-cp36m-win_amd64.whl", hash = "sha256:a7f7b5c07ae5c0cfd24c2db86071fb2a3d947da7bd487e359cc91e67ac1c6d2e"}, + {file = "SQLAlchemy-1.4.49-cp37-cp37m-macosx_11_0_x86_64.whl", hash = "sha256:4afbbf5ef41ac18e02c8dc1f86c04b22b7a2125f2a030e25bbb4aff31abb224b"}, + {file = "SQLAlchemy-1.4.49-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:24e300c0c2147484a002b175f4e1361f102e82c345bf263242f0449672a4bccf"}, + {file = "SQLAlchemy-1.4.49-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:201de072b818f8ad55c80d18d1a788729cccf9be6d9dc3b9d8613b053cd4836d"}, + {file = "SQLAlchemy-1.4.49-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7653ed6817c710d0c95558232aba799307d14ae084cc9b1f4c389157ec50df5c"}, + {file = "SQLAlchemy-1.4.49-cp37-cp37m-win32.whl", hash = "sha256:647e0b309cb4512b1f1b78471fdaf72921b6fa6e750b9f891e09c6e2f0e5326f"}, + {file = "SQLAlchemy-1.4.49-cp37-cp37m-win_amd64.whl", hash = "sha256:ab73ed1a05ff539afc4a7f8cf371764cdf79768ecb7d2ec691e3ff89abbc541e"}, + {file = "SQLAlchemy-1.4.49-cp38-cp38-macosx_11_0_x86_64.whl", hash = "sha256:37ce517c011560d68f1ffb28af65d7e06f873f191eb3a73af5671e9c3fada08a"}, + {file = "SQLAlchemy-1.4.49-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1878ce508edea4a879015ab5215546c444233881301e97ca16fe251e89f1c55"}, + {file = "SQLAlchemy-1.4.49-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0e8e608983e6f85d0852ca61f97e521b62e67969e6e640fe6c6b575d4db68557"}, + {file = "SQLAlchemy-1.4.49-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ccf956da45290df6e809ea12c54c02ace7f8ff4d765d6d3dfb3655ee876ce58d"}, + {file = "SQLAlchemy-1.4.49-cp38-cp38-win32.whl", hash = "sha256:f167c8175ab908ce48bd6550679cc6ea20ae169379e73c7720a28f89e53aa532"}, + {file = "SQLAlchemy-1.4.49-cp38-cp38-win_amd64.whl", hash = "sha256:45806315aae81a0c202752558f0df52b42d11dd7ba0097bf71e253b4215f34f4"}, + {file = "SQLAlchemy-1.4.49-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:b6d0c4b15d65087738a6e22e0ff461b407533ff65a73b818089efc8eb2b3e1de"}, + {file = "SQLAlchemy-1.4.49-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a843e34abfd4c797018fd8d00ffffa99fd5184c421f190b6ca99def4087689bd"}, + {file = "SQLAlchemy-1.4.49-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:1c890421651b45a681181301b3497e4d57c0d01dc001e10438a40e9a9c25ee77"}, + {file = "SQLAlchemy-1.4.49-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d26f280b8f0a8f497bc10573849ad6dc62e671d2468826e5c748d04ed9e670d5"}, + {file = "SQLAlchemy-1.4.49-cp39-cp39-win32.whl", hash = "sha256:ec2268de67f73b43320383947e74700e95c6770d0c68c4e615e9897e46296294"}, + {file = "SQLAlchemy-1.4.49-cp39-cp39-win_amd64.whl", hash = "sha256:bbdf16372859b8ed3f4d05f925a984771cd2abd18bd187042f24be4886c2a15f"}, + {file = "SQLAlchemy-1.4.49.tar.gz", hash = "sha256:06ff25cbae30c396c4b7737464f2a7fc37a67b7da409993b182b024cec80aed9"}, ] [package.dependencies] -greenlet = {version = "!=0.4.17", markers = "platform_machine == \"win32\" or platform_machine == \"WIN32\" or platform_machine == \"AMD64\" or platform_machine == \"amd64\" or platform_machine == \"x86_64\" or platform_machine == \"ppc64le\" or platform_machine == \"aarch64\""} -typing-extensions = ">=4.2.0" +greenlet = {version = "!=0.4.17", markers = "python_version >= \"3\" and (platform_machine == \"win32\" or platform_machine == \"WIN32\" or platform_machine == \"AMD64\" or platform_machine == \"amd64\" or platform_machine == \"x86_64\" or platform_machine == \"ppc64le\" or platform_machine == \"aarch64\")"} [package.extras] aiomysql = ["aiomysql", "greenlet (!=0.4.17)"] aiosqlite = ["aiosqlite", "greenlet (!=0.4.17)", "typing-extensions (!=3.10.0.1)"] asyncio = ["greenlet (!=0.4.17)"] -asyncmy = ["asyncmy (>=0.2.3,!=0.2.4,!=0.2.6)", "greenlet (!=0.4.17)"] -mariadb-connector = ["mariadb (>=1.0.1,!=1.1.2,!=1.1.5)"] +asyncmy = ["asyncmy (>=0.2.3,!=0.2.4)", "greenlet (!=0.4.17)"] +mariadb-connector = ["mariadb (>=1.0.1,!=1.1.2)"] mssql = ["pyodbc"] mssql-pymssql = ["pymssql"] mssql-pyodbc = ["pyodbc"] -mypy = ["mypy (>=0.910)"] -mysql = ["mysqlclient (>=1.4.0)"] +mypy = ["mypy (>=0.910)", "sqlalchemy2-stubs"] +mysql = ["mysqlclient (>=1.4.0)", "mysqlclient (>=1.4.0,<2)"] mysql-connector = ["mysql-connector-python"] -oracle = ["cx-oracle (>=7)"] -oracle-oracledb = ["oracledb (>=1.0.1)"] +oracle = ["cx-oracle (>=7)", "cx-oracle (>=7,<8)"] postgresql = ["psycopg2 (>=2.7)"] postgresql-asyncpg = ["asyncpg", "greenlet (!=0.4.17)"] -postgresql-pg8000 = ["pg8000 (>=1.29.1)"] -postgresql-psycopg = ["psycopg (>=3.0.7)"] +postgresql-pg8000 = ["pg8000 (>=1.16.6,!=1.29.0)"] postgresql-psycopg2binary = ["psycopg2-binary"] postgresql-psycopg2cffi = ["psycopg2cffi"] -postgresql-psycopgbinary = ["psycopg[binary] (>=3.0.7)"] -pymysql = ["pymysql"] +pymysql = ["pymysql", "pymysql (<1)"] sqlcipher = ["sqlcipher3-binary"] -[[package]] -name = "typing-extensions" -version = "4.7.1" -description = "Backported and Experimental Type Hints for Python 3.7+" -optional = false -python-versions = ">=3.7" -files = [ - {file = "typing_extensions-4.7.1-py3-none-any.whl", hash = "sha256:440d5dd3af93b060174bf433bccd69b0babc3b15b1a8dca43789fd7f61514b36"}, - {file = "typing_extensions-4.7.1.tar.gz", hash = "sha256:b75ddc264f0ba5615db7ba217daeb99701ad295353c45f9e95963337ceeeffb2"}, -] - [[package]] name = "tzlocal" version = "2.1" @@ -817,4 +822,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = "^3.8" -content-hash = "acacef90b58165739cce2b821d73ec032c78246c3a88a7cb5c6b5cb75b6d7884" +content-hash = "541fac7e10803d5006c96f7f6e061964dd98c8a22978e2d1779b0318e57d0cbe" diff --git a/pyproject.toml b/pyproject.toml index df3c16c..70277fb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,8 +15,11 @@ frestq = { git = "https://github.com/sequentech/frestq.git", branch = "feat/mast python = "^3.8" requests = "^2.31" Flask = "^2.3" -flask-sqlalchemy = "^3.0" +flask-sqlalchemy = "^2.5" cryptography = "^41.0" +psycopg2 = "^2.9.6" +sqlalchemy = "^1.4" + [build-system] requires = ["poetry-core"] From 2b2c2bed47184d63ce44ffd146ff0b65648cd7ec Mon Sep 17 00:00:00 2001 From: Eduardo Robles Date: Thu, 27 Jul 2023 03:08:58 +0000 Subject: [PATCH 15/26] WIP --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index e6d63ee..fcd6386 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,6 @@ devenv.local.nix # Direnv .direnv/ + +# execution +activity.json.log \ No newline at end of file From 93dc11c4d6fd8e1b30e14b6b5f46960616f42311 Mon Sep 17 00:00:00 2001 From: Eduardo Robles Date: Thu, 27 Jul 2023 04:02:45 +0000 Subject: [PATCH 16/26] making nix build work --- .gitignore | 5 ++- flake.lock | 105 ++++++++++++++++++++++++++++++++++++++++++++++++---- flake.nix | 26 +++++++++++-- poetry.lock | 2 +- 4 files changed, 126 insertions(+), 12 deletions(-) diff --git a/.gitignore b/.gitignore index fcd6386..2b0f1c2 100644 --- a/.gitignore +++ b/.gitignore @@ -9,5 +9,8 @@ devenv.local.nix # Direnv .direnv/ +# nix +result + # execution -activity.json.log \ No newline at end of file +activity.json.log diff --git a/flake.lock b/flake.lock index 29235fa..c254e13 100644 --- a/flake.lock +++ b/flake.lock @@ -8,11 +8,11 @@ "pre-commit-hooks": "pre-commit-hooks" }, "locked": { - "lastModified": 1690298817, - "narHash": "sha256-nf3FK1Nqc2EVMflVRVNOGwT0OXr+jcAXniSm9SPM4sY=", + "lastModified": 1690413082, + "narHash": "sha256-CPR3WcnrrIiDZJiMo4RlyZB0M3576pHmtlTUnMUTugA=", "owner": "cachix", "repo": "devenv", - "rev": "9b21ab9147b0d1d601f4a6e13c19aaba2858a6ad", + "rev": "148c4a21e50428728e97f3cdf59166b6007db8a7", "type": "github" }, "original": { @@ -88,6 +88,24 @@ "type": "github" } }, + "flake-utils_3": { + "inputs": { + "systems": "systems_2" + }, + "locked": { + "lastModified": 1689068808, + "narHash": "sha256-6ixXo3wt24N/melDWjq70UuHQLxGV8jZvooRanIHXw0=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "919d646de7be200f3bf08cb76ae1f09402b6f9b4", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, "gitignore": { "inputs": { "nixpkgs": [ @@ -165,6 +183,27 @@ "type": "github" } }, + "nix-github-actions": { + "inputs": { + "nixpkgs": [ + "poetry2nixFlake", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1688870561, + "narHash": "sha256-4UYkifnPEw1nAzqqPOTL2MvWtm3sNGw1UTYTalkTcGY=", + "owner": "nix-community", + "repo": "nix-github-actions", + "rev": "165b1650b753316aa7f1787f3005a8d2da0f5301", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "nix-github-actions", + "type": "github" + } + }, "nix2container": { "inputs": { "flake-utils": "flake-utils_2", @@ -254,11 +293,27 @@ }, "nixpkgs_2": { "locked": { - "lastModified": 1690179384, - "narHash": "sha256-+arbgqFTAtoeKtepW9wCnA0njCOyoiDFyl0Q0SBSOtE=", + "lastModified": 1690272529, + "narHash": "sha256-MakzcKXEdv/I4qJUtq/k/eG+rVmyOZLnYNC2w1mB59Y=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "ef99fa5c5ed624460217c31ac4271cfb5cb2502c", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_3": { + "locked": { + "lastModified": 1689192006, + "narHash": "sha256-QM0f0d8oPphOTYJebsHioR9+FzJcy1QNIzREyubB91U=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "b12803b6d90e2e583429bb79b859ca53c348b39a", + "rev": "2de8efefb6ce7f5e4e75bdf57376a96555986841", "type": "github" }, "original": { @@ -268,6 +323,26 @@ "type": "github" } }, + "poetry2nixFlake": { + "inputs": { + "flake-utils": "flake-utils_3", + "nix-github-actions": "nix-github-actions", + "nixpkgs": "nixpkgs_3" + }, + "locked": { + "lastModified": 1689849924, + "narHash": "sha256-d259Z2S7CS7Na04qQNQ6LYQILuI7cf4Rpe76qc4mz40=", + "owner": "nix-community", + "repo": "poetry2nix", + "rev": "1d7eda9336f336392d24e9602be5cb9be7ae405c", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "poetry2nix", + "type": "github" + } + }, "pre-commit-hooks": { "inputs": { "flake-compat": [ @@ -302,7 +377,8 @@ "flake-parts": "flake-parts", "mk-shell-bin": "mk-shell-bin", "nix2container": "nix2container", - "nixpkgs": "nixpkgs_2" + "nixpkgs": "nixpkgs_2", + "poetry2nixFlake": "poetry2nixFlake" } }, "systems": { @@ -319,6 +395,21 @@ "repo": "default", "type": "github" } + }, + "systems_2": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } } }, "root": "root", diff --git a/flake.nix b/flake.nix index 1200ca4..6762d7f 100644 --- a/flake.nix +++ b/flake.nix @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2013-2021 Sequent Tech Inc +# SPDX-FileCopyrightText: 2023 Sequent Tech Inc # SPDX-License-Identifier: AGPL-3.0-only # Usage: @@ -11,17 +11,23 @@ inputs = { nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; devenv.url = "github:cachix/devenv"; + nix2container.url = "github:nlewo/nix2container"; nix2container.inputs.nixpkgs.follows = "nixpkgs"; mk-shell-bin.url = "github:rrbutani/nix-mk-shell-bin"; flake-parts.url = "github:hercules-ci/flake-parts"; + + # Get poetry2nix directly from the GitHub source to get an updated + # cryptography lib, see the following link for more info: + # https://github.com/nix-community/poetry2nix/issues/413#issuecomment-1604998895 + poetry2nixFlake.url = "github:nix-community/poetry2nix"; }; nixConfig = { extra-trusted-public-keys = "devenv.cachix.org-1:w1cLUi8dv3hnoSPGAuibQv+f9TZLr6cv/Hm9XgU50cw="; extra-substituters = "https://devenv.cachix.org"; }; - outputs = inputs@{ flake-parts, ... }: + outputs = inputs@{ flake-parts, poetry2nixFlake, ... }: flake-parts.lib.mkFlake { inherit inputs; } { imports = [ inputs.devenv.flakeModule @@ -34,11 +40,25 @@ perSystem = { config, self', inputs', pkgs, system, lib, ... }: let python = pkgs.python3; + poetry2nix = poetry2nixFlake.legacyPackages.${pkgs.stdenv.system}; nix2containerInput = inputs.nix2container; nix2container = nix2containerInput.packages.${pkgs.stdenv.system}; - election_orchestra = pkgs.poetry2nix.mkPoetryApplication { + + # Fixes frestq build. See https://github.com/nix-community/poetry2nix/blob/master/docs/edgecases.md#modulenotfounderror-no-module-named-packagename + election_orchestra-build-requirements = { + frestq = [ "poetry" ]; + }; + election_orchestra-overrides = poetry2nix.defaultPoetryOverrides.extend ( + self: super: builtins.mapAttrs (package: build-requirements: + (builtins.getAttr package super).overridePythonAttrs (old: { + buildInputs = (old.buildInputs or [ ]) ++ (builtins.map (pkg: if builtins.isString pkg then builtins.getAttr pkg super else pkg) build-requirements); + }) + ) election_orchestra-build-requirements + ); + election_orchestra = poetry2nix.mkPoetryApplication { projectDir = ./.; python = python; + overrides = election_orchestra-overrides; }; servicePort = "9090"; dockerImage = nix2container.nix2container.buildImage { diff --git a/poetry.lock b/poetry.lock index 18788fb..2cdea78 100644 --- a/poetry.lock +++ b/poetry.lock @@ -344,7 +344,7 @@ sqlalchemy = "^1.4" type = "git" url = "https://github.com/sequentech/frestq.git" reference = "feat/master/k8s" -resolved_reference = "2a588c17b142d8c514902805cf52caf2926dcb54" +resolved_reference = "0d58ee349fb18c9abb2842e0aaf6b6b6929de996" [[package]] name = "greenlet" From 1f963a72b8403f9814a45c233354066b7d2cb4d7 Mon Sep 17 00:00:00 2001 From: Eduardo Robles Date: Thu, 27 Jul 2023 20:06:45 +0000 Subject: [PATCH 17/26] adding mixnet dep --- flake.lock | 89 +++++++++++++++++++++++++++++++++++++++++++++++++----- flake.nix | 16 ++++++++-- 2 files changed, 95 insertions(+), 10 deletions(-) diff --git a/flake.lock b/flake.lock index c254e13..bfc88e7 100644 --- a/flake.lock +++ b/flake.lock @@ -8,11 +8,11 @@ "pre-commit-hooks": "pre-commit-hooks" }, "locked": { - "lastModified": 1690413082, - "narHash": "sha256-CPR3WcnrrIiDZJiMo4RlyZB0M3576pHmtlTUnMUTugA=", + "lastModified": 1690478799, + "narHash": "sha256-KSHlCVZKPMfnDS89KXkC8xZ/QKe+RrhPSO2Zbjik9MA=", "owner": "cachix", "repo": "devenv", - "rev": "148c4a21e50428728e97f3cdf59166b6007db8a7", + "rev": "ad6d5cb93a4376b9d68d7e46585cd4e8b964ab92", "type": "github" }, "original": { @@ -55,6 +55,24 @@ "type": "github" } }, + "flake-parts_2": { + "inputs": { + "nixpkgs-lib": "nixpkgs-lib_2" + }, + "locked": { + "lastModified": 1688466019, + "narHash": "sha256-VeM2akYrBYMsb4W/MmBo1zmaMfgbL4cH3Pu8PGyIwJ0=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "8e8d955c22df93dbe24f19ea04f47a74adbdc5ec", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "flake-parts", + "type": "github" + } + }, "flake-utils": { "inputs": { "systems": "systems" @@ -144,6 +162,26 @@ "type": "github" } }, + "mixnet": { + "inputs": { + "flake-parts": "flake-parts_2", + "nixpkgs": "nixpkgs_2" + }, + "locked": { + "lastModified": 1690486966, + "narHash": "sha256-afmxdmVGKkKiqCmbVBOluOKum7C38xge+M6DnI1u9gY=", + "owner": "sequentech", + "repo": "mixnet", + "rev": "87a01bf17b3309bf8f61110cc6a1a02271e76fd2", + "type": "github" + }, + "original": { + "owner": "sequentech", + "ref": "feat/master/k8s", + "repo": "mixnet", + "type": "github" + } + }, "mk-shell-bin": { "locked": { "lastModified": 1677004959, @@ -259,6 +297,24 @@ "type": "github" } }, + "nixpkgs-lib_2": { + "locked": { + "dir": "lib", + "lastModified": 1688049487, + "narHash": "sha256-100g4iaKC9MalDjUW9iN6Jl/OocTDtXdeAj7pEGIRh4=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "4bc72cae107788bf3f24f30db2e2f685c9298dc9", + "type": "github" + }, + "original": { + "dir": "lib", + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, "nixpkgs-regression": { "locked": { "lastModified": 1643052045, @@ -308,6 +364,22 @@ } }, "nixpkgs_3": { + "locked": { + "lastModified": 1690272529, + "narHash": "sha256-MakzcKXEdv/I4qJUtq/k/eG+rVmyOZLnYNC2w1mB59Y=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "ef99fa5c5ed624460217c31ac4271cfb5cb2502c", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_4": { "locked": { "lastModified": 1689192006, "narHash": "sha256-QM0f0d8oPphOTYJebsHioR9+FzJcy1QNIzREyubB91U=", @@ -327,14 +399,14 @@ "inputs": { "flake-utils": "flake-utils_3", "nix-github-actions": "nix-github-actions", - "nixpkgs": "nixpkgs_3" + "nixpkgs": "nixpkgs_4" }, "locked": { - "lastModified": 1689849924, - "narHash": "sha256-d259Z2S7CS7Na04qQNQ6LYQILuI7cf4Rpe76qc4mz40=", + "lastModified": 1690448151, + "narHash": "sha256-0iZFEfpgAUH8s0IEBPYyT2fQAvndhJm9YQoRZpCoz7U=", "owner": "nix-community", "repo": "poetry2nix", - "rev": "1d7eda9336f336392d24e9602be5cb9be7ae405c", + "rev": "78fc8882411c29c8eb5f162b09fcafe08b8b03a3", "type": "github" }, "original": { @@ -375,9 +447,10 @@ "inputs": { "devenv": "devenv", "flake-parts": "flake-parts", + "mixnet": "mixnet", "mk-shell-bin": "mk-shell-bin", "nix2container": "nix2container", - "nixpkgs": "nixpkgs_2", + "nixpkgs": "nixpkgs_3", "poetry2nixFlake": "poetry2nixFlake" } }, diff --git a/flake.nix b/flake.nix index 6762d7f..74a50ad 100644 --- a/flake.nix +++ b/flake.nix @@ -16,6 +16,7 @@ nix2container.inputs.nixpkgs.follows = "nixpkgs"; mk-shell-bin.url = "github:rrbutani/nix-mk-shell-bin"; flake-parts.url = "github:hercules-ci/flake-parts"; + mixnet.url = "github:sequentech/mixnet/feat/master/k8s"; # Get poetry2nix directly from the GitHub source to get an updated # cryptography lib, see the following link for more info: @@ -27,7 +28,7 @@ extra-trusted-public-keys = "devenv.cachix.org-1:w1cLUi8dv3hnoSPGAuibQv+f9TZLr6cv/Hm9XgU50cw="; extra-substituters = "https://devenv.cachix.org"; }; - outputs = inputs@{ flake-parts, poetry2nixFlake, ... }: + outputs = inputs@{ flake-parts, poetry2nixFlake, mixnet, ... }: flake-parts.lib.mkFlake { inherit inputs; } { imports = [ inputs.devenv.flakeModule @@ -37,12 +38,13 @@ # Per-system attributes can be defined here. The self' and inputs' # module parameters provide easy access to attributes of the same # system. - perSystem = { config, self', inputs', pkgs, system, lib, ... }: + perSystem = perSystemInputs@{ config, self', inputs', pkgs, system, lib, ... }: let python = pkgs.python3; poetry2nix = poetry2nixFlake.legacyPackages.${pkgs.stdenv.system}; nix2containerInput = inputs.nix2container; nix2container = nix2containerInput.packages.${pkgs.stdenv.system}; + mixnetPackages = mixnet.packages.${pkgs.stdenv.system}; # Fixes frestq build. See https://github.com/nix-community/poetry2nix/blob/master/docs/edgecases.md#modulenotfounderror-no-module-named-packagename election_orchestra-build-requirements = { @@ -64,6 +66,16 @@ dockerImage = nix2container.nix2container.buildImage { name = "election_orchestra"; tag = "latest"; + copyToRoot = pkgs.buildEnv { + name = "root"; + paths = [ + pkgs.bashInteractive + pkgs.coreutils + election_orchestra.dependencyEnv + mixnetPackages.mixnet + ]; + pathsToLink = [ "/bin" "/lib" ]; + }; config = { entrypoint = [ #"${election_orchestra.dependencyEnv}/bin/flask" "run" From c405d7f33e64a0156e5c3a077eb795128d28b930 Mon Sep 17 00:00:00 2001 From: Eduardo Robles Date: Fri, 28 Jul 2023 12:41:56 +0000 Subject: [PATCH 18/26] WIP --- .env | 11 + .envrc | 3 - .gitignore | 1 + devenv.nix | 3 + docker-compose.yml | 55 + flake.nix | 44 +- pyproject.toml | 1 - scripts/create-cert.sh | 73 + scripts/docker-entrypoint.sh | 27 + scripts/encrypt-deps/bigint.js | 34 + scripts/encrypt-deps/class.js | 66 + scripts/encrypt-deps/elgamal.js | 599 ++++ scripts/encrypt-deps/jsbn.js | 562 +++ scripts/encrypt-deps/jsbn2.js | 650 ++++ scripts/encrypt-deps/moment.js | 5685 +++++++++++++++++++++++++++++++ scripts/encrypt-deps/random.js | 35 + scripts/encrypt-deps/sha1.js | 204 ++ scripts/encrypt-deps/sha2.js | 146 + scripts/encrypt-deps/sjcl.js | 52 + scripts/encrypt.js | 139 + scripts/eopeers | 317 ++ scripts/eotest | 494 +++ 22 files changed, 9187 insertions(+), 14 deletions(-) create mode 100644 .env delete mode 100644 .envrc create mode 100644 docker-compose.yml create mode 100755 scripts/create-cert.sh create mode 100644 scripts/docker-entrypoint.sh create mode 100644 scripts/encrypt-deps/bigint.js create mode 100644 scripts/encrypt-deps/class.js create mode 100644 scripts/encrypt-deps/elgamal.js create mode 100644 scripts/encrypt-deps/jsbn.js create mode 100644 scripts/encrypt-deps/jsbn2.js create mode 100644 scripts/encrypt-deps/moment.js create mode 100644 scripts/encrypt-deps/random.js create mode 100644 scripts/encrypt-deps/sha1.js create mode 100644 scripts/encrypt-deps/sha2.js create mode 100644 scripts/encrypt-deps/sjcl.js create mode 100644 scripts/encrypt.js create mode 100755 scripts/eopeers create mode 100755 scripts/eotest diff --git a/.env b/.env new file mode 100644 index 0000000..72965cb --- /dev/null +++ b/.env @@ -0,0 +1,11 @@ +EO_FLASK_RUN_PORT="8080" +EO_MAX_NUM_QUESTIONS_PER_ELECTION="10" +EO_AUTOACCEPT_REQUESTS="True" +EO_PRIVATE_DATA_PATH="/datastore/private" +EO_PUBLIC_DATA_PATH="/datastore/public" +EO_SSL_CERT_PATH="/datastore/certs/cert.pem" +EO_SSL_KEY_PATH="/datastore/certs/cert.key.pem" +EO_SSL_CALIST_PATH="/datastore/certs/cert.calist.pem" +VFORK_RANDOM_SOURCE="/datastore/mixnet_random_source" +COMPOSE_PROFILES=trustee1 +COMPOSE_PROJECT_NAME=trustee-network \ No newline at end of file diff --git a/.envrc b/.envrc deleted file mode 100644 index 6de8a8a..0000000 --- a/.envrc +++ /dev/null @@ -1,3 +0,0 @@ -source_url "https://raw.githubusercontent.com/cachix/devenv/d1f7b48e35e6dee421cfd0f51481d17f77586997/direnvrc" "sha256-YBzqskFZxmNb3kYVoKD9ZixoPXJh1C9ZvTLGFRkauZ0=" - -use devenv \ No newline at end of file diff --git a/.gitignore b/.gitignore index 2b0f1c2..5ba5f38 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,4 @@ result # execution activity.json.log +.data/ \ No newline at end of file diff --git a/devenv.nix b/devenv.nix index 303c3bf..9ad2b90 100644 --- a/devenv.nix +++ b/devenv.nix @@ -5,6 +5,9 @@ pkgs.git pkgs.ack + # used in eotest + pkgs.nodejs + # to create containers pkgs.docker diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..5101d10 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,55 @@ +# export COMPOSE_PROJECT_NAME=trustee-network +# docker compose up --build --force-recreate +version: "3.6" +services: + trustee1: + #restart: unless-stopped + image: election_orchestra:latest + environment: + - EO_SQLALCHEMY_DATABASE_URI=postgresql+psycopg2://db1:db1@db1/db1 + ports: + - 8080-8084 + - 4081/udp + depends_on: + - db1 + volumes: + - ./data/trustee1/datastore:/datastore + profiles: + - trustee1 + - election-orchestra + + #trustee2: + # restart: unless-stopped + # image: election_orchestra:latest + # environment: + # - EO_SQLALCHEMY_DATABASE_URI="postgresql+psycopg2:///db2" + # ports: + # - 8080-8084 + # - 4081/udp + # depends_on: + # - db2 + # volumes: + # - ./data/trustee1/datastore:/datastore + + db1: + image: postgres:15 + environment: + POSTGRES_USER: db1 + POSTGRES_PASSWORD: db1 + ports: + - "5432" + volumes: + - ./data/db1:/var/lib/postgresql/data + profiles: + - trustee1 + - db + #db2: + # image: postgres:15 + # environment: + # POSTGRES_USER: db2 + # POSTGRES_PASSWORD: db2 + # ports: + # - "5432" + # volumes: + # - ./data/db1:/var/lib/postgresql/data + \ No newline at end of file diff --git a/flake.nix b/flake.nix index 74a50ad..8e88cf2 100644 --- a/flake.nix +++ b/flake.nix @@ -46,17 +46,32 @@ nix2container = nix2containerInput.packages.${pkgs.stdenv.system}; mixnetPackages = mixnet.packages.${pkgs.stdenv.system}; - # Fixes frestq build. See https://github.com/nix-community/poetry2nix/blob/master/docs/edgecases.md#modulenotfounderror-no-module-named-packagename + # + # See https://github.com/nix-community/poetry2nix/blob/master/docs/edgecases.md#modulenotfounderror-no-module-named-packagename election_orchestra-build-requirements = { frestq = [ "poetry" ]; }; election_orchestra-overrides = poetry2nix.defaultPoetryOverrides.extend ( - self: super: builtins.mapAttrs (package: build-requirements: - (builtins.getAttr package super).overridePythonAttrs (old: { - buildInputs = (old.buildInputs or [ ]) ++ (builtins.map (pkg: if builtins.isString pkg then builtins.getAttr pkg super else pkg) build-requirements); - }) - ) election_orchestra-build-requirements + self: super: builtins.mapAttrs + (package: build-requirements: + (builtins.getAttr package super).overridePythonAttrs (old: { + buildInputs = + (old.buildInputs or [ ]) + ++ ( + builtins.map + (pkg: + if (builtins.isString pkg) + then (builtins.getAttr pkg super) + else pkg + ) + build-requirements + ); + }) + ) + election_orchestra-build-requirements ); + # + election_orchestra = poetry2nix.mkPoetryApplication { projectDir = ./.; python = python; @@ -71,8 +86,10 @@ paths = [ pkgs.bashInteractive pkgs.coreutils - election_orchestra.dependencyEnv + pkgs.nodejs + python.pkgs.gunicorn mixnetPackages.mixnet + election_orchestra.dependencyEnv ]; pathsToLink = [ "/bin" "/lib" ]; }; @@ -95,13 +112,20 @@ }; }; - # This is to not rebuild/push uwsgi and pythonEnv closures on a - # hello.py change. + # This is to not rebuild everything on code changes layers = [ (nix2container.nix2container.buildLayer { deps = [ - election_orchestra.dependencyEnv python.pkgs.gunicorn + pkgs.bashInteractive + pkgs.coreutils + pkgs.nodejs + mixnetPackages.mixnet + ]; + }) + (nix2container.nix2container.buildLayer { + deps = [ + election_orchestra.dependencyEnv ]; }) ]; diff --git a/pyproject.toml b/pyproject.toml index 70277fb..90a708e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,7 +20,6 @@ cryptography = "^41.0" psycopg2 = "^2.9.6" sqlalchemy = "^1.4" - [build-system] requires = ["poetry-core"] build-backend = "poetry.core.masonry.api" diff --git a/scripts/create-cert.sh b/scripts/create-cert.sh new file mode 100755 index 0000000..3b96847 --- /dev/null +++ b/scripts/create-cert.sh @@ -0,0 +1,73 @@ +#!/bin/bash + +set -eux -o pipefail + +CERT_COUNTRY="${CERT_COUNTRY:-EN}" +CERT_STATE="${CERT_STATE:-New York}" +LOCALITY="${LOCALITY:-New York}" +ORG="${ORG:-Example}" +ORG_UNIT="${ORG_UNIT:-Example}" +HOST="${HOST:-some-hostname}" +COMMON_NAME="${COMMON_NAME:-some-hostname}" +EMAIL="${EMAIL-info@example.com}" +DNS1="${DNS1:-dns1}" +KEY_LENGTH="${KEY_LENGTH:-4096}" +KEY_ALGORITHM="${KEY_ALGORITHM:-rsa}" + +CERT_DIR="${CERT_DIR:-/datastore/certs/}" +CERT_PATH="${CERT_PATH:-$CERT_DIR/cert.pem}" +CERT_KEY_PATH="${CERT_KEY_PATH:-$CERT_DIR/key-nopass.pem}" +CERT_CALIST_PATH="${CERT_CALIST_PATH:-$CERT_DIR/calist}" +CERT_DAYS="${CERT_DAYS:-3650}" +CERT_DIGEST="${CERT_DIGEST:-sha256}" + +CREATE_CERT=${CREATE_CERT:-true} +CALIST_COPY="" + +if [ ! -f "${CERT_PATH}" ]; then + CREATE_CERT=true + if [ ! -d "${CERT_DIR}" ]; then + mkdir -p "${CERT_DIR}" + fi +fi + +if [ true == "$CREATE_CERT" ]; then + openssl req \ + -nodes \ + -x509 \ + -newkey "${KEY_ALGORITHM}:${KEY_LENGTH}" \ + -extensions v3_ca \ + -keyout "${CERT_KEY_PATH}" \ + -out "${CERT_PATH}" \ + -days "${CERT_DAYS}" \ + -subj "/C=${CERT_COUNTRY}/ST=${CERT_STATE}/L=${LOCALITY}/O=${ORG}/OU=${ORG_UNIT}/CN=${COMMON_NAME}/emailAddress=${EMAIL}" \ + -config <(cat <<-EOF +[req] +default_bits = ${KEY_LENGTH} +default_md = ${CERT_DIGEST} +distinguished_name = req_distinguished_name +x509_extensions = v3_ca + +[ req_distinguished_name ] +countryName = ${CERT_COUNTRY} +stateOrProvinceName = ${STATE} +localityName = ${LOCALITY} +organizationName = ${ORG} +organizationalUnitName = ${ORG_UNIT} +commonName = ${COMMON_NAME} +emailAddress = ${EMAIL} + +[ v3_ca ] +# The extentions to add to a self-signed cert +subjectKeyIdentifier = hash +authorityKeyIdentifier = keyid:always,issuer:always +basicConstraints = CA:TRUE +keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment, keyAgreement, keyCertSign +subjectAltName = DNS:${DNS1},DNS:${COMMON_NAME} +issuerAltName = issuer:copy + +EOF +) + cp "$CERT_PATH" "$CERT_CALIST_PATH" + echo "$CALIST_COPY" >> "$CERT_CALIST_PATH" +fi diff --git a/scripts/docker-entrypoint.sh b/scripts/docker-entrypoint.sh new file mode 100644 index 0000000..1ad94f7 --- /dev/null +++ b/scripts/docker-entrypoint.sh @@ -0,0 +1,27 @@ +#!/bin/bash + +set -eux -o pipefail + +LOG_LEVEL="${LOG_LEVEL:-debug}" +FLASK_RUN_HOST="${FLASK_RUN_HOST:-0.0.0.0}" +VFORK_RANDOM_SOURCE="${VFORK_RANDOM_SOURCE:-/datastore/mixnet_random_source}" +VFORK_RANDOM_SEED="${VFORK_RANDOM_SEED:-/mixnet_random_seed}" +RANDOM_DEVICE="${RANDOM_DEVICE:-/dev/urandom}" + +create-cert.sh + +echo "mixnet: checking '${VFORK_RANDOM_SOURCE}'.." +if [ ! -f "${VFORK_RANDOM_SOURCE}" ]; then + echo "mixnet: '${VFORK_RANDOM_SOURCE}' not found, initializing it.." + vog -rndinit RandomDevice "${RANDOM_DEVICE}" +else + echo "mixnet: '${VFORK_RANDOM_SOURCE}' found" +fi + +echo "election-orchestra: launching gunicorn service.." + +gunicorn \ + -b "${FLASK_RUN_HOST}:${FLASK_RUN_PORT}" \ + --log-level" "${LOG_LEVEL}" \ + "${FLASK_APP}" +]; diff --git a/scripts/encrypt-deps/bigint.js b/scripts/encrypt-deps/bigint.js new file mode 100644 index 0000000..3367ad4 --- /dev/null +++ b/scripts/encrypt-deps/bigint.js @@ -0,0 +1,34 @@ +/* jshint ignore:start */ +/* + * This software incorporates components derived from the + * Secure Remote Password JavaScript demo developed by + * Tom Wu (tjw@CS.Stanford.EDU). + */ + +// A wrapper for java.math.BigInteger with some appropriate extra functions for JSON and +// generally being a nice JavaScript object. + +// why not? +var BigInt = BigInteger; +// ZERO AND ONE are already taken care of +BigInt.TWO = new BigInt("2", 10); + +BigInt.setup = function(callback, fail_callback) { + // nothing to do but go + callback(); +}; + +BigInt.prototype.toJSONObject = function() { + return this.toString(); +}; + +BigInt.fromJSONObject = function(s) { + return new BigInt(s, 10); +}; + +BigInt.fromInt = function(i) { + return BigInt.fromJSONObject("" + i); +}; + +BigInt.use_applet = false; +/* jshint ignore:end */ \ No newline at end of file diff --git a/scripts/encrypt-deps/class.js b/scripts/encrypt-deps/class.js new file mode 100644 index 0000000..2fe02c4 --- /dev/null +++ b/scripts/encrypt-deps/class.js @@ -0,0 +1,66 @@ +/* jshint ignore:start */ +/* + * John Resig's Class Inheritance + */ + +// Inspired by base2 and Prototype +(function(){ + var initializing = false, fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/; + + // The base Class implementation (does nothing) + this.Class = function(){}; + + // Create a new Class that inherits from this class + Class.extend = function(prop) { + var _super = this.prototype; + + // Instantiate a base class (but only create the instance, + // don't run the init constructor) + initializing = true; + var prototype = new this(); + initializing = false; + + // Copy the properties over onto the new prototype + for (var name in prop) { + // Check if we're overwriting an existing function + prototype[name] = typeof prop[name] == "function" && + typeof _super[name] == "function" && fnTest.test(prop[name]) ? + (function(name, fn){ + return function() { + var tmp = this._super; + + // Add a new ._super() method that is the same method + // but on the super-class + this._super = _super[name]; + + // The method only need to be bound temporarily, so we + // remove it when we're done executing + var ret = fn.apply(this, arguments); + this._super = tmp; + + return ret; + }; + })(name, prop[name]) : + prop[name]; + } + + // The dummy class constructor + function Class() { + // All construction is actually done in the init method + if ( !initializing && this.init ) + this.init.apply(this, arguments); + } + + // Populate our constructed prototype object + Class.prototype = prototype; + + // Enforce the constructor to be what we expect + Class.constructor = Class; + + // And make this class extendable + Class.extend = arguments.callee; + + return Class; + }; +})(); +/* jshint ignore:end */ \ No newline at end of file diff --git a/scripts/encrypt-deps/elgamal.js b/scripts/encrypt-deps/elgamal.js new file mode 100644 index 0000000..cfcd835 --- /dev/null +++ b/scripts/encrypt-deps/elgamal.js @@ -0,0 +1,599 @@ +/* jshint ignore:start */ +// +// inspired by George Danezis, rewritten by Ben Adida. +// + +ElGamal = {}; + +ElGamal.Params = Class.extend({ + init: function(p, q, g) { + this.p = p; + this.q = q; + this.g = g; + }, + + generate: function() { + // get the value x + var x = Random.getRandomInteger(this.q); + var y = this.g.modPow(x, this.p); + var pk = new ElGamal.PublicKey(this.p, this.q, this.g, y); + var sk = new ElGamal.SecretKey(x, pk); + return sk; + }, + + toJSONObject: function() { + return {g: this.g.toJSONObject(), p: this.p.toJSONObject(), q: this.q.toJSONObject()}; + } +}); + +ElGamal.Params.fromJSONObject = function(d) { + var params = new ElGamal.Params(); + params.p = BigInt.fromJSONObject(d.p); + params.q = BigInt.fromJSONObject(d.q); + params.g = BigInt.fromJSONObject(d.g); + return params; +}; + +ElGamal.PublicKey = Class.extend({ + init : function(p,q,g,y) { + this.p = p; + this.q = q; + this.g = g; + this.y = y; + }, + + toJSONObject: function() { + return {g : this.g.toJSONObject(), p : this.p.toJSONObject(), q : this.q.toJSONObject(), y : this.y.toJSONObject()}; + }, + + verifyKnowledgeOfSecretKey: function(proof, challenge_generator) { + // if challenge_generator is present, we have to check that the challenge was properly generated. + if (challenge_generator != null) { + if (!proof.challenge.equals(challenge_generator(proof.commitment))) { + return false; + } + } + + // verify that g^response = s * y^challenge + var check = this.g.modPow(proof.response, this.p).equals(this.y.modPow(proof.challenge, this.p).multiply(proof.commitment).mod(this.p)); + + return check; + }, + + // check if the decryption factor is correct for this public key, given the proof + verifyDecryptionFactor: function(ciphertext, decryption_factor, decryption_proof, challenge_generator) { + return decryption_proof.verify(this.g, ciphertext.alpha, this.y, decryption_factor, this.p, this.q, challenge_generator); + }, + + multiply: function(other) { + // base condition + if (other == 0 || other == 1) { + return this; + } + + // check params + if (!this.p.equals(other.p)) + throw "mismatched params"; + if (!this.g.equals(other.g)) + throw "mismatched params"; + + var new_pk = new ElGamal.PublicKey(this.p, this.q, this.g, this.y.multiply(other.y).mod(this.p)); + return new_pk; + }, + + equals: function(other) { + return (this.p.equals(other.p) && this.q.equals(other.q) && this.g.equals(other.g) && this.y.equals(other.y)); + } + +}); + +ElGamal.PublicKey.fromJSONObject = function(d) { + var pk = new ElGamal.PublicKey(); + pk.p = BigInt.fromJSONObject(d.p); + pk.q = BigInt.fromJSONObject(d.q); + pk.g = BigInt.fromJSONObject(d.g); + pk.y = BigInt.fromJSONObject(d.y); + return pk; +}; + +ElGamal.SecretKey = Class.extend({ + init: function(x, pk) { + this.x = x; + this.pk = pk; + }, + + toJSONObject: function() { + return {public_key: this.pk.toJSONObject(), x: this.x.toJSONObject()}; + }, + + // a decryption factor is *not yet* mod-inverted, because it needs to be part of the proof. + decryptionFactor: function(ciphertext) { + var decryption_factor = ciphertext.alpha.modPow(this.x, this.pk.p); + return decryption_factor; + }, + + decrypt: function(ciphertext, decryption_factor) { + if (!decryption_factor) + decryption_factor = this.decryptionFactor(ciphertext); + + // use the ciphertext's built-in decryption given a list of decryption factors. + return ciphertext.decrypt([decryption_factor]); + }, + + decryptAndProve: function(ciphertext, challenge_generator) { + var dec_factor_and_proof = this.decryptionFactorAndProof(ciphertext, challenge_generator); + + // decrypt, but using the already computed decryption factor + var plaintext = this.decrypt(ciphertext, dec_factor_and_proof.decryption_factor); + + return { + 'plaintext': plaintext, + 'proof': dec_factor_and_proof.decryption_proof + }; + }, + + decryptionFactorAndProof: function(ciphertext, challenge_generator) { + var decryption_factor = this.decryptionFactor(ciphertext); + + // the DH tuple we need to prove, given the secret key x, is: + // g, alpha, y, beta/m + var proof = ElGamal.Proof.generate(this.pk.g, ciphertext.alpha, this.x, this.pk.p, this.pk.q, challenge_generator); + + return { + 'decryption_factor' : decryption_factor, + 'decryption_proof' : proof + } + }, + + // generate a proof of knowledge of the secret exponent x + proveKnowledge: function(challenge_generator) { + // generate random w + var w = Random.getRandomInteger(this.pk.q); + + // compute s = g^w for random w. + var s = this.pk.g.modPow(w, this.pk.p); + + // get challenge + var challenge = challenge_generator(s); + + // compute response = w + x * challenge + var response = w.add(this.x.multiply(challenge)).mod(this.pk.q); + + return new ElGamal.DLogProof(s, challenge, response); + } +}); + +ElGamal.SecretKey.fromJSONObject = function(d) { + var sk = new ElGamal.SecretKey(); + sk.pk = ElGamal.PublicKey.fromJSONObject(d.public_key); + sk.x = BigInt.fromJSONObject(d.x); + return sk; +} + +ElGamal.Ciphertext = Class.extend({ + init: function(alpha, beta, pk) { + this.alpha = alpha; + this.beta = beta; + this.pk = pk; + }, + + toString: function() { + return this.alpha.toString() + ',' + this.beta.toString(); + }, + + toJSONObject: function() { + return {alpha: this.alpha.toJSONObject(), beta: this.beta.toJSONObject()} + }, + + multiply: function(other) { + // special case if other is 1 to enable easy aggregate ops + if (other == 1) + return this; + + // homomorphic multiply + return new ElGamal.Ciphertext(this.alpha.multiply(other.alpha).mod(this.pk.p), + this.beta.multiply(other.beta).mod(this.pk.p), + this.pk); + }, + + // a decryption method by decryption factors + decrypt: function(list_of_dec_factors) { + var running_decryption = this.beta; + var self = this; + _(list_of_dec_factors).each(function(dec_factor) { + running_decryption = dec_factor.modInverse(self.pk.p).multiply(running_decryption).mod(self.pk.p); + }); + + return new ElGamal.Plaintext(running_decryption, this.pk, false); + }, + + generateProof: function(plaintext, randomness, challenge_generator) { + // DH tuple to prove is + // g, y, alpha, beta/m + // with dlog randomness + var proof = ElGamal.Proof.generate(this.pk.g, this.pk.y, randomness, this.pk.p, this.pk.q, challenge_generator); + + return proof; + }, + + simulateProof: function(plaintext, challenge) { + // compute beta/plaintext, the completion of the DH tuple + var beta_over_plaintext = this.beta.multiply(plaintext.m.modInverse(this.pk.p)).mod(this.pk.p); + + // the DH tuple we are simulating here is + // g, y, alpha, beta/m + return ElGamal.Proof.simulate(this.pk.g, this.pk.y, this.alpha, beta_over_plaintext, this.pk.p, this.pk.q, challenge); + }, + + verifyProof: function(plaintext, proof, challenge_generator) { + // DH tuple to verify is + // g, y, alpha, beta/m + var beta_over_m = this.beta.multiply(plaintext.m.modInverse(this.pk.p)).mod(this.pk.p); + + return proof.verify(this.pk.g, this.pk.y, this.alpha, beta_over_m, this.pk.p, this.pk.q, challenge_generator); + }, + + /** + * Verifies the proof of knowledge of the plaintext + */ + verifyPlaintextProof: function(proof, challenge_generator) { + var challenge2 = challenge_generator(proof.commitment); + if (!challenge2.equals(proof.challenge)) { + return false; + } + + // check g^response == commitment * (g^t) ^ challenge == commitment * (alpha) ^ challenge + return this.pk.g.modPow(proof.response, this.pk.p).equals(this.alpha.modPow(proof.challenge, this.pk.p).multiply(proof.commitment.a).mod(this.pk.p)); + }, + + verifyDecryptionProof: function(plaintext, proof, challenge_generator) { + // DH tuple to verify is + // g, alpha, y, beta/m + // since the proven dlog is the secret key x, y=g^x. + var beta_over_m = this.beta.multiply(plaintext.m.modInverse(this.pk.p)).mod(this.pk.p); + + return proof.verify(this.pk.g, this.alpha, this.pk.y, beta_over_m, this.pk.p, this.pk.q, challenge_generator); + }, + + generateDisjunctiveProof: function(list_of_plaintexts, real_index, randomness, challenge_generator) { + // go through all plaintexts and simulate the ones that must be simulated. + // note how the interface is as such so that the result does not reveal which is the real proof. + var self = this; + + var proofs = _(list_of_plaintexts).map(function(plaintext, p_num) { + if (p_num == real_index) { + // no real proof yet + return {}; + } else { + // simulate! + return self.simulateProof(plaintext); + } + }); + + // do the real proof + var real_proof = this.generateProof(list_of_plaintexts[real_index], randomness, function(commitment) { + // now we generate the challenge for the real proof by first determining + // the challenge for the whole disjunctive proof. + + // set up the partial real proof so we're ready to get the hash; + proofs[real_index] = {'commitment' : commitment}; + + // get the commitments in a list and generate the whole disjunctive challenge + var commitments = _(proofs).map(function(proof) { + return proof.commitment; + }); + + var disjunctive_challenge = challenge_generator(commitments); + + // now we must subtract all of the other challenges from this challenge. + var real_challenge = disjunctive_challenge; + _(proofs).each(function(proof, proof_num) { + if (proof_num != real_index) + real_challenge = real_challenge.add(proof.challenge.negate()); + }); + + // make sure we mod q, the exponent modulus + return real_challenge.mod(self.pk.q); + }); + + // set the real proof + proofs[real_index] = real_proof; + return new ElGamal.DisjunctiveProof(proofs); + }, + + verifyDisjunctiveProof: function(list_of_plaintexts, disj_proof, challenge_generator) { + var result = true; + var proofs = disj_proof.proofs; + + // for loop because we want to bail out of the inner loop + // if we fail one of the verifications. + for (var i=0; i < list_of_plaintexts.length; i++) { + if (!this.verifyProof(list_of_plaintexts[i], proofs[i])) + return false; + } + + // check the overall challenge + + // first the one expected from the proofs + var commitments = _(proofs).map(function(proof) {return proof.commitment;}); + var expected_challenge = challenge_generator(commitments); + + // then the one that is the sum of the previous one. + var sum = new BigInt("0", 10); var self = this; + _(proofs).each(function(proof) {sum = sum.add(proof.challenge).mod(self.pk.q);}); + + return expected_challenge.equals(sum); + }, + + equals: function(other) { + return (this.alpha.equals(other.alpha) && this.beta.equals(other.beta)); + }, +}); + +ElGamal.Ciphertext.fromJSONObject = function(d, pk) { + return new ElGamal.Ciphertext(BigInt.fromJSONObject(d.alpha), BigInt.fromJSONObject(d.beta), pk); +}; + +// we need the public key to figure out how to encode m +ElGamal.Plaintext = Class.extend({ + init: function(m, pk, encode_m) { + if (m == null) { + alert('oy null m'); + return; + } + + this.pk = pk; + + // encode to m * legendre(m|p) + if (encode_m) { + // need to encode the message given that p = 2q+1 + var y = m.add(BigInt.ONE); + + // euler criterion to determine quadratic residuosity + var test = y.modPow(pk.q, pk.p); + if (test.equals(BigInt.ONE)) { + this.m = y; + } else { + this.m = y.negate().mod(pk.p); + } + } else { + this.m = m; + } + }, + + getPlaintext: function() { + var y; + + // if m < q + if (this.m.compareTo(this.pk.q) < 0) { + y = this.m; + } else { + y = this.m.negate().mod(this.pk.p); + } + + return y.subtract(BigInt.ONE); + }, + + getM: function() { + return this.m; + }, + + // generate a proof of knowledge of the plaintext (schnorr protocol) + // http://courses.csail.mit.edu/6.897/spring04/L19.pdf + proveKnowledge: function(alpha, randomness, challenge_generator, w) { + // generate random w if not provided + if (!w) { + w = Random.getRandomInteger(this.pk.q); + } + + // compute first part of commitment = g^w for random w. + var a = this.pk.g.modPow(w, this.pk.p); + + var commitment = new ElGamal.PlaintextCommitment(alpha, a); + + // get challenge + var challenge = challenge_generator(commitment); + + // compute response = w + randomness * challenge + var response = w.add(randomness.multiply(challenge)).mod(this.pk.q); + + return new ElGamal.DLogProof(commitment, challenge, response); + } +}); + +// +// Proof abstraction +// + +ElGamal.Proof = Class.extend({ + init: function(A, B, challenge, response) { + this.commitment = {}; + this.commitment.A = A; + this.commitment.B = B; + this.challenge = challenge; + this.response = response; + }, + + toJSONObject: function() { + return { + challenge : this.challenge.toJSONObject(), + commitment : {A: this.commitment.A.toJSONObject(), B: this.commitment.B.toJSONObject()}, + response : this.response.toJSONObject() + } + }, + + // verify a DH tuple proof + verify: function(little_g, little_h, big_g, big_h, p, q, challenge_generator) { + // check that little_g^response = A * big_g^challenge + var first_check = little_g.modPow(this.response, p).equals(big_g.modPow(this.challenge, p).multiply(this.commitment.A).mod(p)); + + // check that little_h^response = B * big_h^challenge + var second_check = little_h.modPow(this.response, p).equals(big_h.modPow(this.challenge, p).multiply(this.commitment.B).mod(p)); + + var third_check = true; + + if (challenge_generator) { + third_check = this.challenge.equals(challenge_generator(this.commitment)); + } + + return (first_check && second_check && third_check); + } +}); + +ElGamal.Proof.fromJSONObject = function(d) { + return new ElGamal.Proof( + BigInt.fromJSONObject(d.commitment.A), + BigInt.fromJSONObject(d.commitment.B), + BigInt.fromJSONObject(d.challenge), + BigInt.fromJSONObject(d.response)); +}; + +// a generic way to prove that four values are a DH tuple. +// a DH tuple is g,h,G,H where G = g^x and H=h^x +// challenge generator takes a commitment, whose subvalues are A and B +// all modulo p, with group order q, which we provide just in case. +// as it turns out, G and H are not necessary to generate this proof, given that they're implied by x. +ElGamal.Proof.generate = function(little_g, little_h, x, p, q, challenge_generator) { + // generate random w + var w = Random.getRandomInteger(q); + + // create a proof instance + var proof = new ElGamal.Proof(); + + // compute A=little_g^w, B=little_h^w + proof.commitment.A = little_g.modPow(w, p); + proof.commitment.B = little_h.modPow(w, p); + + // Get the challenge from the callback that generates it + proof.challenge = challenge_generator(proof.commitment); + + // Compute response = w + x * challenge + proof.response = w.add(x.multiply(proof.challenge)).mod(q); + + return proof; +}; + +// simulate a a DH-tuple proof, with a potentially assigned challenge (but can be null) +ElGamal.Proof.simulate = function(little_g, little_h, big_g, big_h, p, q, challenge) { + // generate a random challenge if not provided + if (challenge == null) { + challenge = Random.getRandomInteger(q); + } + + // random response, does not even need to depend on the challenge + var response = Random.getRandomInteger(q); + + // now we compute A and B + // A = little_g ^ w, and at verification time, g^response = G^challenge * A, so A = (G^challenge)^-1 * g^response + var A = big_g.modPow(challenge, p).modInverse(p).multiply(little_g.modPow(response, p)).mod(p); + + // B = little_h ^ w, and at verification time, h^response = H^challenge * B, so B = (H^challenge)^-1 * h^response + var B = big_h.modPow(challenge, p).modInverse(p).multiply(little_h.modPow(response, p)).mod(p); + + return new ElGamal.Proof(A, B, challenge, response); +}; + +ElGamal.DisjunctiveProof = Class.extend({ + init: function(list_of_proofs) { + this.proofs = list_of_proofs; + }, + + toJSONObject: function() { + return _(this.proofs).map(function(proof) { + return proof.toJSONObject(); + }); + } +}); + +ElGamal.DisjunctiveProof.fromJSONObject = function(d) { + if (d==null) + return null; + + return new ElGamal.DisjunctiveProof( + _(d).map(function(p) { + return ElGamal.Proof.fromJSONObject(p); + }) + ); +}; + +ElGamal.encrypt = function(pk, plaintext, r) { + if (plaintext.getM().equals(BigInt.ZERO)) + throw "Can't encrypt 0 with El Gamal" + + if (!r) + r = Random.getRandomInteger(pk.q); + + var alpha = pk.g.modPow(r, pk.p); + var beta = (pk.y.modPow(r, pk.p)).multiply(plaintext.m).mod(pk.p); + + return new ElGamal.Ciphertext(alpha, beta, pk); +}; + +// +// DLog Proof +// +ElGamal.DLogProof = Class.extend({ + init: function(commitment, challenge, response) { + this.commitment = commitment; + this.challenge = challenge; + this.response = response; + }, + + toJSONObject: function() { + return {'challenge' : this.challenge.toJSONObject(), 'commitment': this.commitment.toJSONObject(), 'response': this.response.toJSONObject()}; + } +}); + +ElGamal.DLogProof.fromJSONObject = function(d) { + return new ElGamal.DLogProof(BigInt.fromJSONObject(d.commitment || d.s), BigInt.fromJSONObject(d.challenge), BigInt.fromJSONObject(d.response)); +}; + +// +// PlaintextCommitment +// +ElGamal.PlaintextCommitment = Class.extend({ + init: function(alpha, a) { + this.alpha = alpha; + this.a = a; + }, + + toJSONObject: function() { + return this.a.toJSONObject(); + }, + + toString: function() { + return this.alpha.toJSONObject() + "/" + this.a.toJSONObject(); + } +}); + +ElGamal.DLogProof.fromJSONObject = function(d) { + return new ElGamal.DLogProof(BigInt.fromJSONObject(d.commitment || d.s), BigInt.fromJSONObject(d.challenge), BigInt.fromJSONObject(d.response)); +}; + +// a challenge generator based on a list of commitments of +// proofs of knowledge of plaintext. Just appends A and B with commas. +ElGamal.disjunctive_challenge_generator = function(commitments) { + var strings_to_hash = []; + + // go through all proofs and append the commitments + _(commitments).each(function(commitment) { + // toJSONObject instead of toString because of IE weirdness. + strings_to_hash[strings_to_hash.length] = commitment.A.toJSONObject(); + strings_to_hash[strings_to_hash.length] = commitment.B.toJSONObject(); + }); + + // console.log(strings_to_hash); + // STRINGS = strings_to_hash; + return new BigInt(hex_sha1(strings_to_hash.join(",")), 16); +}; + +// a challenge generator for Fiat-Shamir +ElGamal.fiatshamir_challenge_generator = function(commitment) { + return ElGamal.disjunctive_challenge_generator([commitment]); +}; + +ElGamal.fiatshamir_dlog_challenge_generator = function(commitment) { + // return new BigInt(hex_sha256(commitment.toJSONObject()), 16); + return new BigInt(hex_sha256(commitment.toString()), 16); +}; +/* jshint ignore:end */ diff --git a/scripts/encrypt-deps/jsbn.js b/scripts/encrypt-deps/jsbn.js new file mode 100644 index 0000000..d5a33a1 --- /dev/null +++ b/scripts/encrypt-deps/jsbn.js @@ -0,0 +1,562 @@ +/* jshint ignore:start */ +// Copyright (c) 2005 Tom Wu +// All Rights Reserved. +// See "LICENSE" for details. + +// Basic JavaScript BN library - subset useful for RSA encryption. + +// Bits per digit +var dbits; + +// JavaScript engine analysis +var canary = 0xdeadbeefcafe; +var j_lm = ((canary&0xffffff)==0xefcafe); + +// (public) Constructor +function BigInteger(a,b,c) { + this.arr = new Array(); + if(a != null) + if("number" == typeof a) this.fromNumber(a,b,c); + else if(b == null && "string" != typeof a) this.fromString(a,256); + else this.fromString(a,b); +} + +// return new, unset BigInteger +function nbi() { return new BigInteger(null); } + +// am: Compute w_j += (x*this_i), propagate carries, +// c is initial carry, returns final carry. +// c < 3*dvalue, x < 2*dvalue, this_i < dvalue +// We need to select the fastest one that works in this environment. + +// am1: use a single mult and divide to get the high bits, +// max digit bits should be 26 because +// max internal value = 2*dvalue^2-2*dvalue (< 2^53) +function am1(i,x,w,j,c,n) { + while(--n >= 0) { + var v = x*this.arr[i++]+w.arr[j]+c; + c = Math.floor(v/0x4000000); + w.arr[j++] = v&0x3ffffff; + } + return c; +} +// am2 avoids a big mult-and-extract completely. +// Max digit bits should be <= 30 because we do bitwise ops +// on values up to 2*hdvalue^2-hdvalue-1 (< 2^31) +function am2(i,x,w,j,c,n) { + var xl = x&0x7fff, xh = x>>15; + while(--n >= 0) { + var l = this.arr[i]&0x7fff; + var h = this.arr[i++]>>15; + var m = xh*l+h*xl; + l = xl*l+((m&0x7fff)<<15)+w.arr[j]+(c&0x3fffffff); + c = (l>>>30)+(m>>>15)+xh*h+(c>>>30); + w.arr[j++] = l&0x3fffffff; + } + return c; +} +// Alternately, set max digit bits to 28 since some +// browsers slow down when dealing with 32-bit numbers. +function am3(i,x,w,j,c,n) { + var xl = x&0x3fff, xh = x>>14; + while(--n >= 0) { + var l = this.arr[i]&0x3fff; + var h = this.arr[i++]>>14; + var m = xh*l+h*xl; + l = xl*l+((m&0x3fff)<<14)+w.arr[j]+c; + c = (l>>28)+(m>>14)+xh*h; + w.arr[j++] = l&0xfffffff; + } + return c; +} +if(j_lm && (navigator.appName == "Microsoft Internet Explorer")) { + BigInteger.prototype.am = am2; + dbits = 30; +} +else if(j_lm && (navigator.appName != "Netscape")) { + BigInteger.prototype.am = am1; + dbits = 26; +} +else { // Mozilla/Netscape seems to prefer am3 + BigInteger.prototype.am = am3; + dbits = 28; +} + +BigInteger.prototype.DB = dbits; +BigInteger.prototype.DM = ((1<= 0; --i) r.arr[i] = this.arr[i]; + r.t = this.t; + r.s = this.s; +} + +// (protected) set from integer value x, -DV <= x < DV +function bnpFromInt(x) { + this.t = 1; + this.s = (x<0)?-1:0; + if(x > 0) this.arr[0] = x; + else if(x < -1) this.arr[0] = x+DV; + else this.t = 0; +} + +// return bigint initialized to value +function nbv(i) { var r = nbi(); r.fromInt(i); return r; } + +// (protected) set from string and radix +function bnpFromString(s,b) { + var k; + if(b == 16) k = 4; + else if(b == 8) k = 3; + else if(b == 256) k = 8; // byte array + else if(b == 2) k = 1; + else if(b == 32) k = 5; + else if(b == 4) k = 2; + else { this.fromRadix(s,b); return; } + this.t = 0; + this.s = 0; + var i = s.length, mi = false, sh = 0; + while(--i >= 0) { + var x = (k==8)?s[i]&0xff:intAt(s,i); + if(x < 0) { + if(s.charAt(i) == "-") mi = true; + continue; + } + mi = false; + if(sh == 0) + this.arr[this.t++] = x; + else if(sh+k > this.DB) { + this.arr[this.t-1] |= (x&((1<<(this.DB-sh))-1))<>(this.DB-sh)); + } + else + this.arr[this.t-1] |= x<= this.DB) sh -= this.DB; + } + if(k == 8 && (s[0]&0x80) != 0) { + this.s = -1; + if(sh > 0) this.arr[this.t-1] |= ((1<<(this.DB-sh))-1)< 0 && this.arr[this.t-1] == c) --this.t; +} + +// (public) return string representation in given radix +function bnToString(b) { + if(this.s < 0) return "-"+this.negate().toString(b); + var k; + if(b == 16) k = 4; + else if(b == 8) k = 3; + else if(b == 2) k = 1; + else if(b == 32) k = 5; + else if(b == 4) k = 2; + else return this.toRadix(b); + var km = (1< 0) { + if(p < this.DB && (d = this.arr[i]>>p) > 0) { m = true; r = int2char(d); } + while(i >= 0) { + if(p < k) { + d = (this.arr[i]&((1<>(p+=this.DB-k); + } + else { + d = (this.arr[i]>>(p-=k))&km; + if(p <= 0) { p += this.DB; --i; } + } + if(d > 0) m = true; + if(m) r += int2char(d); + } + } + return m?r:"0"; +} + +// (public) -this +function bnNegate() { var r = nbi(); BigInteger.ZERO.subTo(this,r); return r; } + +// (public) |this| +function bnAbs() { return (this.s<0)?this.negate():this; } + +// (public) return + if this > a, - if this < a, 0 if equal +function bnCompareTo(a) { + var r = this.s-a.s; + if(r != 0) return r; + var i = this.t; + r = i-a.t; + if(r != 0) return r; + while(--i >= 0) if((r=this.arr[i]-a.arr[i]) != 0) return r; + return 0; +} + +// returns bit length of the integer x +function nbits(x) { + var r = 1, t; + if((t=x>>>16) != 0) { x = t; r += 16; } + if((t=x>>8) != 0) { x = t; r += 8; } + if((t=x>>4) != 0) { x = t; r += 4; } + if((t=x>>2) != 0) { x = t; r += 2; } + if((t=x>>1) != 0) { x = t; r += 1; } + return r; +} + +// (public) return the number of bits in "this" +function bnBitLength() { + if(this.t <= 0) return 0; + return this.DB*(this.t-1)+nbits(this.arr[this.t-1]^(this.s&this.DM)); +} + +// (protected) r = this << n*DB +function bnpDLShiftTo(n,r) { + var i; + for(i = this.t-1; i >= 0; --i) r.arr[i+n] = this.arr[i]; + for(i = n-1; i >= 0; --i) r.arr[i] = 0; + r.t = this.t+n; + r.s = this.s; +} + +// (protected) r = this >> n*DB +function bnpDRShiftTo(n,r) { + for(var i = n; i < this.t; ++i) r.arr[i-n] = this.arr[i]; + r.t = Math.max(this.t-n,0); + r.s = this.s; +} + +// (protected) r = this << n +function bnpLShiftTo(n,r) { + var bs = n%this.DB; + var cbs = this.DB-bs; + var bm = (1<= 0; --i) { + r.arr[i+ds+1] = (this.arr[i]>>cbs)|c; + c = (this.arr[i]&bm)<= 0; --i) r.arr[i] = 0; + r.arr[ds] = c; + r.t = this.t+ds+1; + r.s = this.s; + r.clamp(); +} + +// (protected) r = this >> n +function bnpRShiftTo(n,r) { + r.s = this.s; + var ds = Math.floor(n/this.DB); + if(ds >= this.t) { r.t = 0; return; } + var bs = n%this.DB; + var cbs = this.DB-bs; + var bm = (1<>bs; + for(var i = ds+1; i < this.t; ++i) { + r.arr[i-ds-1] |= (this.arr[i]&bm)<>bs; + } + if(bs > 0) r.arr[this.t-ds-1] |= (this.s&bm)<>= this.DB; + } + if(a.t < this.t) { + c -= a.s; + while(i < this.t) { + c += this.arr[i]; + r.arr[i++] = c&this.DM; + c >>= this.DB; + } + c += this.s; + } + else { + c += this.s; + while(i < a.t) { + c -= a.arr[i]; + r.arr[i++] = c&this.DM; + c >>= this.DB; + } + c -= a.s; + } + r.s = (c<0)?-1:0; + if(c < -1) r.arr[i++] = this.DV+c; + else if(c > 0) r.arr[i++] = c; + r.t = i; + r.clamp(); +} + +// (protected) r = this * a, r != this,a (HAC 14.12) +// "this" should be the larger one if appropriate. +function bnpMultiplyTo(a,r) { + var x = this.abs(), y = a.abs(); + var i = x.t; + r.t = i+y.t; + while(--i >= 0) r.arr[i] = 0; + for(i = 0; i < y.t; ++i) r.arr[i+x.t] = x.am(0,y.arr[i],r,i,0,x.t); + r.s = 0; + r.clamp(); + if(this.s != a.s) BigInteger.ZERO.subTo(r,r); +} + +// (protected) r = this^2, r != this (HAC 14.16) +function bnpSquareTo(r) { + var x = this.abs(); + var i = r.t = 2*x.t; + while(--i >= 0) r.arr[i] = 0; + for(i = 0; i < x.t-1; ++i) { + var c = x.am(i,x.arr[i],r,2*i,0,1); + if((r.arr[i+x.t]+=x.am(i+1,2*x.arr[i],r,2*i+1,c,x.t-i-1)) >= x.DV) { + r.arr[i+x.t] -= x.DV; + r.arr[i+x.t+1] = 1; + } + } + if(r.t > 0) r.arr[r.t-1] += x.am(i,x.arr[i],r,2*i,0,1); + r.s = 0; + r.clamp(); +} + +// (protected) divide this by m, quotient and remainder to q, r (HAC 14.20) +// r != q, this != m. q or r may be null. +function bnpDivRemTo(m,q,r) { + var pm = m.abs(); + if(pm.t <= 0) return; + var pt = this.abs(); + if(pt.t < pm.t) { + if(q != null) q.fromInt(0); + if(r != null) this.copyTo(r); + return; + } + if(r == null) r = nbi(); + var y = nbi(), ts = this.s, ms = m.s; + var nsh = this.DB-nbits(pm.arr[pm.t-1]); // normalize modulus + if(nsh > 0) { pm.lShiftTo(nsh,y); pt.lShiftTo(nsh,r); } + else { pm.copyTo(y); pt.copyTo(r); } + var ys = y.t; + var y0 = y.arr[ys-1]; + if(y0 == 0) return; + var yt = y0*(1<1)?y.arr[ys-2]>>this.F2:0); + var d1 = this.FV/yt, d2 = (1<= 0) { + r.arr[r.t++] = 1; + r.subTo(t,r); + } + BigInteger.ONE.dlShiftTo(ys,t); + t.subTo(y,y); // "negative" y so we can replace sub with am later + while(y.t < ys) y.arr[y.t++] = 0; + while(--j >= 0) { + // Estimate quotient digit + var qd = (r.arr[--i]==y0)?this.DM:Math.floor(r.arr[i]*d1+(r.arr[i-1]+e)*d2); + if((r.arr[i]+=y.am(0,qd,r,j,0,ys)) < qd) { // Try it out + y.dlShiftTo(j,t); + r.subTo(t,r); + while(r.arr[i] < --qd) r.subTo(t,r); + } + } + if(q != null) { + r.drShiftTo(ys,q); + if(ts != ms) BigInteger.ZERO.subTo(q,q); + } + r.t = ys; + r.clamp(); + if(nsh > 0) r.rShiftTo(nsh,r); // Denormalize remainder + if(ts < 0) BigInteger.ZERO.subTo(r,r); +} + +// (public) this mod a +function bnMod(a) { + var r = nbi(); + this.abs().divRemTo(a,null,r); + if(this.s < 0 && r.compareTo(BigInteger.ZERO) > 0) a.subTo(r,r); + return r; +} + +// Modular reduction using "classic" algorithm +function Classic(m) { this.m = m; } +function cConvert(x) { + if(x.s < 0 || x.compareTo(this.m) >= 0) return x.mod(this.m); + else return x; +} +function cRevert(x) { return x; } +function cReduce(x) { x.divRemTo(this.m,null,x); } +function cMulTo(x,y,r) { x.multiplyTo(y,r); this.reduce(r); } +function cSqrTo(x,r) { x.squareTo(r); this.reduce(r); } + +Classic.prototype.convert = cConvert; +Classic.prototype.revert = cRevert; +Classic.prototype.reduce = cReduce; +Classic.prototype.mulTo = cMulTo; +Classic.prototype.sqrTo = cSqrTo; + +// (protected) return "-1/this % 2^DB"; useful for Mont. reduction +// justification: +// xy == 1 (mod m) +// xy = 1+km +// xy(2-xy) = (1+km)(1-km) +// x.arr[y(2-xy)] = 1-k^2m^2 +// x.arr[y(2-xy)] == 1 (mod m^2) +// if y is 1/x mod m, then y(2-xy) is 1/x mod m^2 +// should reduce x and y(2-xy) by m^2 at each step to keep size bounded. +// JS multiply "overflows" differently from C/C++, so care is needed here. +function bnpInvDigit() { + if(this.t < 1) return 0; + var x = this.arr[0]; + if((x&1) == 0) return 0; + var y = x&3; // y == 1/x mod 2^2 + y = (y*(2-(x&0xf)*y))&0xf; // y == 1/x mod 2^4 + y = (y*(2-(x&0xff)*y))&0xff; // y == 1/x mod 2^8 + y = (y*(2-(((x&0xffff)*y)&0xffff)))&0xffff; // y == 1/x mod 2^16 + // last step - calculate inverse mod DV directly; + // assumes 16 < DB <= 32 and assumes ability to handle 48-bit ints + y = (y*(2-x*y%this.DV))%this.DV; // y == 1/x mod 2^dbits + // we really want the negative inverse, and -DV < y < DV + return (y>0)?this.DV-y:-y; +} + +// Montgomery reduction +function Montgomery(m) { + this.m = m; + this.mp = m.invDigit(); + this.mpl = this.mp&0x7fff; + this.mph = this.mp>>15; + this.um = (1<<(m.DB-15))-1; + this.mt2 = 2*m.t; +} + +// xR mod m +function montConvert(x) { + var r = nbi(); + x.abs().dlShiftTo(this.m.t,r); + r.divRemTo(this.m,null,r); + if(x.s < 0 && r.compareTo(BigInteger.ZERO) > 0) this.m.subTo(r,r); + return r; +} + +// x/R mod m +function montRevert(x) { + var r = nbi(); + x.copyTo(r); + this.reduce(r); + return r; +} + +// x = x/R mod m (HAC 14.32) +function montReduce(x) { + while(x.t <= this.mt2) // pad x so am has enough room later + x.arr[x.t++] = 0; + for(var i = 0; i < this.m.t; ++i) { + // faster way of calculating u0 = x.arr[i]*mp mod DV + var j = x.arr[i]&0x7fff; + var u0 = (j*this.mpl+(((j*this.mph+(x.arr[i]>>15)*this.mpl)&this.um)<<15))&x.DM; + // use am to combine the multiply-shift-add into one call + j = i+this.m.t; + x.arr[j] += this.m.am(0,u0,x,i,0,this.m.t); + // propagate carry + while(x.arr[j] >= x.DV) { x.arr[j] -= x.DV; x.arr[++j]++; } + } + x.clamp(); + x.drShiftTo(this.m.t,x); + if(x.compareTo(this.m) >= 0) x.subTo(this.m,x); +} + +// r = "x^2/R mod m"; x != r +function montSqrTo(x,r) { x.squareTo(r); this.reduce(r); } + +// r = "xy/R mod m"; x,y != r +function montMulTo(x,y,r) { x.multiplyTo(y,r); this.reduce(r); } + +Montgomery.prototype.convert = montConvert; +Montgomery.prototype.revert = montRevert; +Montgomery.prototype.reduce = montReduce; +Montgomery.prototype.mulTo = montMulTo; +Montgomery.prototype.sqrTo = montSqrTo; + +// (protected) true iff this is even +function bnpIsEven() { return ((this.t>0)?(this.arr[0]&1):this.s) == 0; } + +// (protected) this^e, e < 2^32, doing sqr and mul with "r" (HAC 14.79) +function bnpExp(e,z) { + if(e > 0xffffffff || e < 1) return BigInteger.ONE; + var r = nbi(), r2 = nbi(), g = z.convert(this), i = nbits(e)-1; + g.copyTo(r); + while(--i >= 0) { + z.sqrTo(r,r2); + if((e&(1< 0) z.mulTo(r2,g,r); + else { var t = r; r = r2; r2 = t; } + } + return z.revert(r); +} + +// (public) this^e % m, 0 <= e < 2^32 +function bnModPowInt(e,m) { + var z; + if(e < 256 || m.isEven()) z = new Classic(m); else z = new Montgomery(m); + return this.exp(e,z); +} + +// protected +BigInteger.prototype.copyTo = bnpCopyTo; +BigInteger.prototype.fromInt = bnpFromInt; +BigInteger.prototype.fromString = bnpFromString; +BigInteger.prototype.clamp = bnpClamp; +BigInteger.prototype.dlShiftTo = bnpDLShiftTo; +BigInteger.prototype.drShiftTo = bnpDRShiftTo; +BigInteger.prototype.lShiftTo = bnpLShiftTo; +BigInteger.prototype.rShiftTo = bnpRShiftTo; +BigInteger.prototype.subTo = bnpSubTo; +BigInteger.prototype.multiplyTo = bnpMultiplyTo; +BigInteger.prototype.squareTo = bnpSquareTo; +BigInteger.prototype.divRemTo = bnpDivRemTo; +BigInteger.prototype.invDigit = bnpInvDigit; +BigInteger.prototype.isEven = bnpIsEven; +BigInteger.prototype.exp = bnpExp; + +// public +BigInteger.prototype.toString = bnToString; +BigInteger.prototype.negate = bnNegate; +BigInteger.prototype.abs = bnAbs; +BigInteger.prototype.compareTo = bnCompareTo; +BigInteger.prototype.bitLength = bnBitLength; +BigInteger.prototype.mod = bnMod; +BigInteger.prototype.modPowInt = bnModPowInt; + +// "constants" +BigInteger.ZERO = nbv(0); +BigInteger.ONE = nbv(1); +/* jshint ignore:end */ \ No newline at end of file diff --git a/scripts/encrypt-deps/jsbn2.js b/scripts/encrypt-deps/jsbn2.js new file mode 100644 index 0000000..db4c887 --- /dev/null +++ b/scripts/encrypt-deps/jsbn2.js @@ -0,0 +1,650 @@ +/* jshint ignore:start */ +// Copyright (c) 2005-2009 Tom Wu +// All Rights Reserved. +// See "LICENSE" for details. + +// Extended JavaScript BN functions, required for RSA private ops. + +// Version 1.1: new BigInteger("0", 10) returns "proper" zero + +// (public) +function bnClone() { var r = nbi(); this.copyTo(r); return r; } + +// (public) return value as integer +function bnIntValue() { + if(this.s < 0) { + if(this.t == 1) return this.arr[0]-this.DV; + else if(this.t == 0) return -1; + } + else if(this.t == 1) return this.arr[0]; + else if(this.t == 0) return 0; + // assumes 16 < DB < 32 + return ((this.arr[1]&((1<<(32-this.DB))-1))<>24; } + +// (public) return value as short (assumes DB>=16) +function bnShortValue() { return (this.t==0)?this.s:(this.arr[0]<<16)>>16; } + +// (protected) return x s.t. r^x < DV +function bnpChunkSize(r) { return Math.floor(Math.LN2*this.DB/Math.log(r)); } + +// (public) 0 if this == 0, 1 if this > 0 +function bnSigNum() { + if(this.s < 0) return -1; + else if(this.t <= 0 || (this.t == 1 && this.arr[0] <= 0)) return 0; + else return 1; +} + +// (protected) convert to radix string +function bnpToRadix(b) { + if(b == null) b = 10; + if(this.signum() == 0 || b < 2 || b > 36) return "0"; + var cs = this.chunkSize(b); + var a = Math.pow(b,cs); + var d = nbv(a), y = nbi(), z = nbi(), r = ""; + this.divRemTo(d,y,z); + while(y.signum() > 0) { + r = (a+z.intValue()).toString(b).substr(1) + r; + y.divRemTo(d,y,z); + } + return z.intValue().toString(b) + r; +} + +// (protected) convert from radix string +function bnpFromRadix(s,b) { + this.fromInt(0); + if(b == null) b = 10; + var cs = this.chunkSize(b); + var d = Math.pow(b,cs), mi = false, j = 0, w = 0; + for(var i = 0; i < s.length; ++i) { + var x = intAt(s,i); + if(x < 0) { + if(s.charAt(i) == "-" && this.signum() == 0) mi = true; + continue; + } + w = b*w+x; + if(++j >= cs) { + this.dMultiply(d); + this.dAddOffset(w,0); + j = 0; + w = 0; + } + } + if(j > 0) { + this.dMultiply(Math.pow(b,j)); + this.dAddOffset(w,0); + } + if(mi) BigInteger.ZERO.subTo(this,this); +} + +// (protected) alternate constructor +function bnpFromNumber(a,b,c) { + if("number" == typeof b) { + // new BigInteger(int,int,RNG) + if(a < 2) this.fromInt(1); + else { + this.fromNumber(a,c); + if(!this.testBit(a-1)) // force MSB set + this.bitwiseTo(BigInteger.ONE.shiftLeft(a-1),op_or,this); + if(this.isEven()) this.dAddOffset(1,0); // force odd + while(!this.isProbablePrime(b)) { + this.dAddOffset(2,0); + if(this.bitLength() > a) this.subTo(BigInteger.ONE.shiftLeft(a-1),this); + } + } + } + else { + // new BigInteger(int,RNG) + var x = new Array(), t = a&7; + x.length = (a>>3)+1; + b.nextBytes(x); + if(t > 0) x.arr[0] &= ((1< 0) { + if(p < this.DB && (d = this.arr[i]>>p) != (this.s&this.DM)>>p) + r.arr[k++] = d|(this.s<<(this.DB-p)); + while(i >= 0) { + if(p < 8) { + d = (this.arr[i]&((1<>(p+=this.DB-8); + } + else { + d = (this.arr[i]>>(p-=8))&0xff; + if(p <= 0) { p += this.DB; --i; } + } + if((d&0x80) != 0) d |= -256; + if(k == 0 && (this.s&0x80) != (d&0x80)) ++k; + if(k > 0 || d != this.s) r.arr[k++] = d; + } + } + return r; +} + +function bnEquals(a) { return(this.compareTo(a)==0); } +function bnMin(a) { return(this.compareTo(a)<0)?this:a; } +function bnMax(a) { return(this.compareTo(a)>0)?this:a; } + +// (protected) r = this op a (bitwise) +function bnpBitwiseTo(a,op,r) { + var i, f, m = Math.min(a.t,this.t); + for(i = 0; i < m; ++i) r.arr[i] = op(this.arr[i],a.arr[i]); + if(a.t < this.t) { + f = a.s&this.DM; + for(i = m; i < this.t; ++i) r.arr[i] = op(this.arr[i],f); + r.t = this.t; + } + else { + f = this.s&this.DM; + for(i = m; i < a.t; ++i) r.arr[i] = op(f,a.arr[i]); + r.t = a.t; + } + r.s = op(this.s,a.s); + r.clamp(); +} + +// (public) this & a +function op_and(x,y) { return x&y; } +function bnAnd(a) { var r = nbi(); this.bitwiseTo(a,op_and,r); return r; } + +// (public) this | a +function op_or(x,y) { return x|y; } +function bnOr(a) { var r = nbi(); this.bitwiseTo(a,op_or,r); return r; } + +// (public) this ^ a +function op_xor(x,y) { return x^y; } +function bnXor(a) { var r = nbi(); this.bitwiseTo(a,op_xor,r); return r; } + +// (public) this & ~a +function op_andnot(x,y) { return x&~y; } +function bnAndNot(a) { var r = nbi(); this.bitwiseTo(a,op_andnot,r); return r; } + +// (public) ~this +function bnNot() { + var r = nbi(); + for(var i = 0; i < this.t; ++i) r.arr[i] = this.DM&~this.arr[i]; + r.t = this.t; + r.s = ~this.s; + return r; +} + +// (public) this << n +function bnShiftLeft(n) { + var r = nbi(); + if(n < 0) this.rShiftTo(-n,r); else this.lShiftTo(n,r); + return r; +} + +// (public) this >> n +function bnShiftRight(n) { + var r = nbi(); + if(n < 0) this.lShiftTo(-n,r); else this.rShiftTo(n,r); + return r; +} + +// return index of lowest 1-bit in x, x < 2^31 +function lbit(x) { + if(x == 0) return -1; + var r = 0; + if((x&0xffff) == 0) { x >>= 16; r += 16; } + if((x&0xff) == 0) { x >>= 8; r += 8; } + if((x&0xf) == 0) { x >>= 4; r += 4; } + if((x&3) == 0) { x >>= 2; r += 2; } + if((x&1) == 0) ++r; + return r; +} + +// (public) returns index of lowest 1-bit (or -1 if none) +function bnGetLowestSetBit() { + for(var i = 0; i < this.t; ++i) + if(this.arr[i] != 0) return i*this.DB+lbit(this.arr[i]); + if(this.s < 0) return this.t*this.DB; + return -1; +} + +// return number of 1 bits in x +function cbit(x) { + var r = 0; + while(x != 0) { x &= x-1; ++r; } + return r; +} + +// (public) return number of set bits +function bnBitCount() { + var r = 0, x = this.s&this.DM; + for(var i = 0; i < this.t; ++i) r += cbit(this.arr[i]^x); + return r; +} + +// (public) true iff nth bit is set +function bnTestBit(n) { + var j = Math.floor(n/this.DB); + if(j >= this.t) return(this.s!=0); + return((this.arr[j]&(1<<(n%this.DB)))!=0); +} + +// (protected) this op (1<>= this.DB; + } + if(a.t < this.t) { + c += a.s; + while(i < this.t) { + c += this.arr[i]; + r.arr[i++] = c&this.DM; + c >>= this.DB; + } + c += this.s; + } + else { + c += this.s; + while(i < a.t) { + c += a.arr[i]; + r.arr[i++] = c&this.DM; + c >>= this.DB; + } + c += a.s; + } + r.s = (c<0)?-1:0; + if(c > 0) r.arr[i++] = c; + else if(c < -1) r.arr[i++] = this.DV+c; + r.t = i; + r.clamp(); +} + +// (public) this + a +function bnAdd(a) { var r = nbi(); this.addTo(a,r); return r; } + +// (public) this - a +function bnSubtract(a) { var r = nbi(); this.subTo(a,r); return r; } + +// (public) this * a +function bnMultiply(a) { var r = nbi(); this.multiplyTo(a,r); return r; } + +// (public) this / a +function bnDivide(a) { var r = nbi(); this.divRemTo(a,r,null); return r; } + +// (public) this % a +function bnRemainder(a) { var r = nbi(); this.divRemTo(a,null,r); return r; } + +// (public) [this/a,this%a] +function bnDivideAndRemainder(a) { + var q = nbi(), r = nbi(); + this.divRemTo(a,q,r); + return new Array(q,r); +} + +// (protected) this *= n, this >= 0, 1 < n < DV +function bnpDMultiply(n) { + this.arr[this.t] = this.am(0,n-1,this,0,0,this.t); + ++this.t; + this.clamp(); +} + +// (protected) this += n << w words, this >= 0 +function bnpDAddOffset(n,w) { + if(n == 0) return; + while(this.t <= w) this.arr[this.t++] = 0; + this.arr[w] += n; + while(this.arr[w] >= this.DV) { + this.arr[w] -= this.DV; + if(++w >= this.t) this.arr[this.t++] = 0; + ++this.arr[w]; + } +} + +// A "null" reducer +function NullExp() {} +function nNop(x) { return x; } +function nMulTo(x,y,r) { x.multiplyTo(y,r); } +function nSqrTo(x,r) { x.squareTo(r); } + +NullExp.prototype.convert = nNop; +NullExp.prototype.revert = nNop; +NullExp.prototype.mulTo = nMulTo; +NullExp.prototype.sqrTo = nSqrTo; + +// (public) this^e +function bnPow(e) { return this.exp(e,new NullExp()); } + +// (protected) r = lower n words of "this * a", a.t <= n +// "this" should be the larger one if appropriate. +function bnpMultiplyLowerTo(a,n,r) { + var i = Math.min(this.t+a.t,n); + r.s = 0; // assumes a,this >= 0 + r.t = i; + while(i > 0) r.arr[--i] = 0; + var j; + for(j = r.t-this.t; i < j; ++i) r.arr[i+this.t] = this.am(0,a.arr[i],r,i,0,this.t); + for(j = Math.min(a.t,n); i < j; ++i) this.am(0,a.arr[i],r,i,0,n-i); + r.clamp(); +} + +// (protected) r = "this * a" without lower n words, n > 0 +// "this" should be the larger one if appropriate. +function bnpMultiplyUpperTo(a,n,r) { + --n; + var i = r.t = this.t+a.t-n; + r.s = 0; // assumes a,this >= 0 + while(--i >= 0) r.arr[i] = 0; + for(i = Math.max(n-this.t,0); i < a.t; ++i) + r.arr[this.t+i-n] = this.am(n-i,a.arr[i],r,0,0,this.t+i-n); + r.clamp(); + r.drShiftTo(1,r); +} + +// Barrett modular reduction +function Barrett(m) { + // setup Barrett + this.r2 = nbi(); + this.q3 = nbi(); + BigInteger.ONE.dlShiftTo(2*m.t,this.r2); + this.mu = this.r2.divide(m); + this.m = m; +} + +function barrettConvert(x) { + if(x.s < 0 || x.t > 2*this.m.t) return x.mod(this.m); + else if(x.compareTo(this.m) < 0) return x; + else { var r = nbi(); x.copyTo(r); this.reduce(r); return r; } +} + +function barrettRevert(x) { return x; } + +// x = x mod m (HAC 14.42) +function barrettReduce(x) { + x.drShiftTo(this.m.t-1,this.r2); + if(x.t > this.m.t+1) { x.t = this.m.t+1; x.clamp(); } + this.mu.multiplyUpperTo(this.r2,this.m.t+1,this.q3); + this.m.multiplyLowerTo(this.q3,this.m.t+1,this.r2); + while(x.compareTo(this.r2) < 0) x.dAddOffset(1,this.m.t+1); + x.subTo(this.r2,x); + while(x.compareTo(this.m) >= 0) x.subTo(this.m,x); +} + +// r = x^2 mod m; x != r +function barrettSqrTo(x,r) { x.squareTo(r); this.reduce(r); } + +// r = x*y mod m; x,y != r +function barrettMulTo(x,y,r) { x.multiplyTo(y,r); this.reduce(r); } + +Barrett.prototype.convert = barrettConvert; +Barrett.prototype.revert = barrettRevert; +Barrett.prototype.reduce = barrettReduce; +Barrett.prototype.mulTo = barrettMulTo; +Barrett.prototype.sqrTo = barrettSqrTo; + +// (public) this^e % m (HAC 14.85) +function bnModPow(e,m) { + var i = e.bitLength(), k, r = nbv(1), z; + if(i <= 0) return r; + else if(i < 18) k = 1; + else if(i < 48) k = 3; + else if(i < 144) k = 4; + else if(i < 768) k = 5; + else k = 6; + if(i < 8) + z = new Classic(m); + else if(m.isEven()) + z = new Barrett(m); + else + z = new Montgomery(m); + + // precomputation + var g = new Array(), n = 3, k1 = k-1, km = (1< 1) { + var g2 = nbi(); + z.sqrTo(g[1],g2); + while(n <= km) { + g[n] = nbi(); + z.mulTo(g2,g[n-2],g[n]); + n += 2; + } + } + + var j = e.t-1, w, is1 = true, r2 = nbi(), t; + i = nbits(e.arr[j])-1; + while(j >= 0) { + if(i >= k1) w = (e.arr[j]>>(i-k1))&km; + else { + w = (e.arr[j]&((1<<(i+1))-1))<<(k1-i); + if(j > 0) w |= e.arr[j-1]>>(this.DB+i-k1); + } + + n = k; + while((w&1) == 0) { w >>= 1; --n; } + if((i -= n) < 0) { i += this.DB; --j; } + if(is1) { // ret == 1, don't bother squaring or multiplying it + g[w].copyTo(r); + is1 = false; + } + else { + while(n > 1) { z.sqrTo(r,r2); z.sqrTo(r2,r); n -= 2; } + if(n > 0) z.sqrTo(r,r2); else { t = r; r = r2; r2 = t; } + z.mulTo(r2,g[w],r); + } + + while(j >= 0 && (e.arr[j]&(1< 0) { + x.rShiftTo(g,x); + y.rShiftTo(g,y); + } + while(x.signum() > 0) { + if((i = x.getLowestSetBit()) > 0) x.rShiftTo(i,x); + if((i = y.getLowestSetBit()) > 0) y.rShiftTo(i,y); + if(x.compareTo(y) >= 0) { + x.subTo(y,x); + x.rShiftTo(1,x); + } + else { + y.subTo(x,y); + y.rShiftTo(1,y); + } + } + if(g > 0) y.lShiftTo(g,y); + return y; +} + +// (protected) this % n, n < 2^26 +function bnpModInt(n) { + if(n <= 0) return 0; + var d = this.DV%n, r = (this.s<0)?n-1:0; + if(this.t > 0) + if(d == 0) r = this.arr[0]%n; + else for(var i = this.t-1; i >= 0; --i) r = (d*r+this.arr[i])%n; + return r; +} + +// (public) 1/this % m (HAC 14.61) +function bnModInverse(m) { + var ac = m.isEven(); + if((this.isEven() && ac) || m.signum() == 0) return BigInteger.ZERO; + var u = m.clone(), v = this.clone(); + var a = nbv(1), b = nbv(0), c = nbv(0), d = nbv(1); + while(u.signum() != 0) { + while(u.isEven()) { + u.rShiftTo(1,u); + if(ac) { + if(!a.isEven() || !b.isEven()) { a.addTo(this,a); b.subTo(m,b); } + a.rShiftTo(1,a); + } + else if(!b.isEven()) b.subTo(m,b); + b.rShiftTo(1,b); + } + while(v.isEven()) { + v.rShiftTo(1,v); + if(ac) { + if(!c.isEven() || !d.isEven()) { c.addTo(this,c); d.subTo(m,d); } + c.rShiftTo(1,c); + } + else if(!d.isEven()) d.subTo(m,d); + d.rShiftTo(1,d); + } + if(u.compareTo(v) >= 0) { + u.subTo(v,u); + if(ac) a.subTo(c,a); + b.subTo(d,b); + } + else { + v.subTo(u,v); + if(ac) c.subTo(a,c); + d.subTo(b,d); + } + } + if(v.compareTo(BigInteger.ONE) != 0) return BigInteger.ZERO; + if(d.compareTo(m) >= 0) return d.subtract(m); + if(d.signum() < 0) d.addTo(m,d); else return d; + if(d.signum() < 0) return d.add(m); else return d; +} + +var lowprimes = [2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97,101,103,107,109,113,127,131,137,139,149,151,157,163,167,173,179,181,191,193,197,199,211,223,227,229,233,239,241,251,257,263,269,271,277,281,283,293,307,311,313,317,331,337,347,349,353,359,367,373,379,383,389,397,401,409,419,421,431,433,439,443,449,457,461,463,467,479,487,491,499,503,509]; +var lplim = (1<<26)/lowprimes[lowprimes.length-1]; + +// (public) test primality with certainty >= 1-.5^t +function bnIsProbablePrime(t) { + var i, x = this.abs(); + if(x.t == 1 && x.arr[0] <= lowprimes[lowprimes.length-1]) { + for(i = 0; i < lowprimes.length; ++i) + if(x.arr[0] == lowprimes[i]) return true; + return false; + } + if(x.isEven()) return false; + i = 1; + while(i < lowprimes.length) { + var m = lowprimes[i], j = i+1; + while(j < lowprimes.length && m < lplim) m *= lowprimes[j++]; + m = x.modInt(m); + while(i < j) if(m%lowprimes[i++] == 0) return false; + } + return x.millerRabin(t); +} + +// (protected) true if probably prime (HAC 4.24, Miller-Rabin) +function bnpMillerRabin(t) { + var n1 = this.subtract(BigInteger.ONE); + var k = n1.getLowestSetBit(); + if(k <= 0) return false; + var r = n1.shiftRight(k); + t = (t+1)>>1; + if(t > lowprimes.length) t = lowprimes.length; + var a = nbi(); + for(var i = 0; i < t; ++i) { + a.fromInt(lowprimes[i]); + var y = a.modPow(r,this); + if(y.compareTo(BigInteger.ONE) != 0 && y.compareTo(n1) != 0) { + var j = 1; + while(j++ < k && y.compareTo(n1) != 0) { + y = y.modPowInt(2,this); + if(y.compareTo(BigInteger.ONE) == 0) return false; + } + if(y.compareTo(n1) != 0) return false; + } + } + return true; +} + +// protected +BigInteger.prototype.chunkSize = bnpChunkSize; +BigInteger.prototype.toRadix = bnpToRadix; +BigInteger.prototype.fromRadix = bnpFromRadix; +BigInteger.prototype.fromNumber = bnpFromNumber; +BigInteger.prototype.bitwiseTo = bnpBitwiseTo; +BigInteger.prototype.changeBit = bnpChangeBit; +BigInteger.prototype.addTo = bnpAddTo; +BigInteger.prototype.dMultiply = bnpDMultiply; +BigInteger.prototype.dAddOffset = bnpDAddOffset; +BigInteger.prototype.multiplyLowerTo = bnpMultiplyLowerTo; +BigInteger.prototype.multiplyUpperTo = bnpMultiplyUpperTo; +BigInteger.prototype.modInt = bnpModInt; +BigInteger.prototype.millerRabin = bnpMillerRabin; + +// public +BigInteger.prototype.clone = bnClone; +BigInteger.prototype.intValue = bnIntValue; +BigInteger.prototype.byteValue = bnByteValue; +BigInteger.prototype.shortValue = bnShortValue; +BigInteger.prototype.signum = bnSigNum; +BigInteger.prototype.toByteArray = bnToByteArray; +BigInteger.prototype.equals = bnEquals; +BigInteger.prototype.min = bnMin; +BigInteger.prototype.max = bnMax; +BigInteger.prototype.and = bnAnd; +BigInteger.prototype.or = bnOr; +BigInteger.prototype.xor = bnXor; +BigInteger.prototype.andNot = bnAndNot; +BigInteger.prototype.not = bnNot; +BigInteger.prototype.shiftLeft = bnShiftLeft; +BigInteger.prototype.shiftRight = bnShiftRight; +BigInteger.prototype.getLowestSetBit = bnGetLowestSetBit; +BigInteger.prototype.bitCount = bnBitCount; +BigInteger.prototype.testBit = bnTestBit; +BigInteger.prototype.setBit = bnSetBit; +BigInteger.prototype.clearBit = bnClearBit; +BigInteger.prototype.flipBit = bnFlipBit; +BigInteger.prototype.add = bnAdd; +BigInteger.prototype.subtract = bnSubtract; +BigInteger.prototype.multiply = bnMultiply; +BigInteger.prototype.divide = bnDivide; +BigInteger.prototype.remainder = bnRemainder; +BigInteger.prototype.divideAndRemainder = bnDivideAndRemainder; +BigInteger.prototype.modPow = bnModPow; +BigInteger.prototype.modInverse = bnModInverse; +BigInteger.prototype.pow = bnPow; +BigInteger.prototype.gcd = bnGCD; +BigInteger.prototype.isProbablePrime = bnIsProbablePrime; + +// BigInteger interfaces not implemented in jsbn: + +// BigInteger(int signum, byte[] magnitude) +// double doubleValue() +// float floatValue() +// int hashCode() +// long longValue() +// static BigInteger valueOf(long val) +/* jshint ignore:end */ diff --git a/scripts/encrypt-deps/moment.js b/scripts/encrypt-deps/moment.js new file mode 100644 index 0000000..05a63b1 --- /dev/null +++ b/scripts/encrypt-deps/moment.js @@ -0,0 +1,5685 @@ +//! moment.js +//! version : 2.29.4 +//! authors : Tim Wood, Iskren Chernev, Moment.js contributors +//! license : MIT +//! momentjs.com + +;(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : + typeof define === 'function' && define.amd ? define(factory) : + global.moment = factory() +}(this, (function () { 'use strict'; + + var hookCallback; + + function hooks() { + return hookCallback.apply(null, arguments); + } + + // This is done to register the method called with moment() + // without creating circular dependencies. + function setHookCallback(callback) { + hookCallback = callback; + } + + function isArray(input) { + return ( + input instanceof Array || + Object.prototype.toString.call(input) === '[object Array]' + ); + } + + function isObject(input) { + // IE8 will treat undefined and null as object if it wasn't for + // input != null + return ( + input != null && + Object.prototype.toString.call(input) === '[object Object]' + ); + } + + function hasOwnProp(a, b) { + return Object.prototype.hasOwnProperty.call(a, b); + } + + function isObjectEmpty(obj) { + if (Object.getOwnPropertyNames) { + return Object.getOwnPropertyNames(obj).length === 0; + } else { + var k; + for (k in obj) { + if (hasOwnProp(obj, k)) { + return false; + } + } + return true; + } + } + + function isUndefined(input) { + return input === void 0; + } + + function isNumber(input) { + return ( + typeof input === 'number' || + Object.prototype.toString.call(input) === '[object Number]' + ); + } + + function isDate(input) { + return ( + input instanceof Date || + Object.prototype.toString.call(input) === '[object Date]' + ); + } + + function map(arr, fn) { + var res = [], + i, + arrLen = arr.length; + for (i = 0; i < arrLen; ++i) { + res.push(fn(arr[i], i)); + } + return res; + } + + function extend(a, b) { + for (var i in b) { + if (hasOwnProp(b, i)) { + a[i] = b[i]; + } + } + + if (hasOwnProp(b, 'toString')) { + a.toString = b.toString; + } + + if (hasOwnProp(b, 'valueOf')) { + a.valueOf = b.valueOf; + } + + return a; + } + + function createUTC(input, format, locale, strict) { + return createLocalOrUTC(input, format, locale, strict, true).utc(); + } + + function defaultParsingFlags() { + // We need to deep clone this object. + return { + empty: false, + unusedTokens: [], + unusedInput: [], + overflow: -2, + charsLeftOver: 0, + nullInput: false, + invalidEra: null, + invalidMonth: null, + invalidFormat: false, + userInvalidated: false, + iso: false, + parsedDateParts: [], + era: null, + meridiem: null, + rfc2822: false, + weekdayMismatch: false, + }; + } + + function getParsingFlags(m) { + if (m._pf == null) { + m._pf = defaultParsingFlags(); + } + return m._pf; + } + + var some; + if (Array.prototype.some) { + some = Array.prototype.some; + } else { + some = function (fun) { + var t = Object(this), + len = t.length >>> 0, + i; + + for (i = 0; i < len; i++) { + if (i in t && fun.call(this, t[i], i, t)) { + return true; + } + } + + return false; + }; + } + + function isValid(m) { + if (m._isValid == null) { + var flags = getParsingFlags(m), + parsedParts = some.call(flags.parsedDateParts, function (i) { + return i != null; + }), + isNowValid = + !isNaN(m._d.getTime()) && + flags.overflow < 0 && + !flags.empty && + !flags.invalidEra && + !flags.invalidMonth && + !flags.invalidWeekday && + !flags.weekdayMismatch && + !flags.nullInput && + !flags.invalidFormat && + !flags.userInvalidated && + (!flags.meridiem || (flags.meridiem && parsedParts)); + + if (m._strict) { + isNowValid = + isNowValid && + flags.charsLeftOver === 0 && + flags.unusedTokens.length === 0 && + flags.bigHour === undefined; + } + + if (Object.isFrozen == null || !Object.isFrozen(m)) { + m._isValid = isNowValid; + } else { + return isNowValid; + } + } + return m._isValid; + } + + function createInvalid(flags) { + var m = createUTC(NaN); + if (flags != null) { + extend(getParsingFlags(m), flags); + } else { + getParsingFlags(m).userInvalidated = true; + } + + return m; + } + + // Plugins that add properties should also add the key here (null value), + // so we can properly clone ourselves. + var momentProperties = (hooks.momentProperties = []), + updateInProgress = false; + + function copyConfig(to, from) { + var i, + prop, + val, + momentPropertiesLen = momentProperties.length; + + if (!isUndefined(from._isAMomentObject)) { + to._isAMomentObject = from._isAMomentObject; + } + if (!isUndefined(from._i)) { + to._i = from._i; + } + if (!isUndefined(from._f)) { + to._f = from._f; + } + if (!isUndefined(from._l)) { + to._l = from._l; + } + if (!isUndefined(from._strict)) { + to._strict = from._strict; + } + if (!isUndefined(from._tzm)) { + to._tzm = from._tzm; + } + if (!isUndefined(from._isUTC)) { + to._isUTC = from._isUTC; + } + if (!isUndefined(from._offset)) { + to._offset = from._offset; + } + if (!isUndefined(from._pf)) { + to._pf = getParsingFlags(from); + } + if (!isUndefined(from._locale)) { + to._locale = from._locale; + } + + if (momentPropertiesLen > 0) { + for (i = 0; i < momentPropertiesLen; i++) { + prop = momentProperties[i]; + val = from[prop]; + if (!isUndefined(val)) { + to[prop] = val; + } + } + } + + return to; + } + + // Moment prototype object + function Moment(config) { + copyConfig(this, config); + this._d = new Date(config._d != null ? config._d.getTime() : NaN); + if (!this.isValid()) { + this._d = new Date(NaN); + } + // Prevent infinite loop in case updateOffset creates new moment + // objects. + if (updateInProgress === false) { + updateInProgress = true; + hooks.updateOffset(this); + updateInProgress = false; + } + } + + function isMoment(obj) { + return ( + obj instanceof Moment || (obj != null && obj._isAMomentObject != null) + ); + } + + function warn(msg) { + if ( + hooks.suppressDeprecationWarnings === false && + typeof console !== 'undefined' && + console.warn + ) { + console.warn('Deprecation warning: ' + msg); + } + } + + function deprecate(msg, fn) { + var firstTime = true; + + return extend(function () { + if (hooks.deprecationHandler != null) { + hooks.deprecationHandler(null, msg); + } + if (firstTime) { + var args = [], + arg, + i, + key, + argLen = arguments.length; + for (i = 0; i < argLen; i++) { + arg = ''; + if (typeof arguments[i] === 'object') { + arg += '\n[' + i + '] '; + for (key in arguments[0]) { + if (hasOwnProp(arguments[0], key)) { + arg += key + ': ' + arguments[0][key] + ', '; + } + } + arg = arg.slice(0, -2); // Remove trailing comma and space + } else { + arg = arguments[i]; + } + args.push(arg); + } + warn( + msg + + '\nArguments: ' + + Array.prototype.slice.call(args).join('') + + '\n' + + new Error().stack + ); + firstTime = false; + } + return fn.apply(this, arguments); + }, fn); + } + + var deprecations = {}; + + function deprecateSimple(name, msg) { + if (hooks.deprecationHandler != null) { + hooks.deprecationHandler(name, msg); + } + if (!deprecations[name]) { + warn(msg); + deprecations[name] = true; + } + } + + hooks.suppressDeprecationWarnings = false; + hooks.deprecationHandler = null; + + function isFunction(input) { + return ( + (typeof Function !== 'undefined' && input instanceof Function) || + Object.prototype.toString.call(input) === '[object Function]' + ); + } + + function set(config) { + var prop, i; + for (i in config) { + if (hasOwnProp(config, i)) { + prop = config[i]; + if (isFunction(prop)) { + this[i] = prop; + } else { + this['_' + i] = prop; + } + } + } + this._config = config; + // Lenient ordinal parsing accepts just a number in addition to + // number + (possibly) stuff coming from _dayOfMonthOrdinalParse. + // TODO: Remove "ordinalParse" fallback in next major release. + this._dayOfMonthOrdinalParseLenient = new RegExp( + (this._dayOfMonthOrdinalParse.source || this._ordinalParse.source) + + '|' + + /\d{1,2}/.source + ); + } + + function mergeConfigs(parentConfig, childConfig) { + var res = extend({}, parentConfig), + prop; + for (prop in childConfig) { + if (hasOwnProp(childConfig, prop)) { + if (isObject(parentConfig[prop]) && isObject(childConfig[prop])) { + res[prop] = {}; + extend(res[prop], parentConfig[prop]); + extend(res[prop], childConfig[prop]); + } else if (childConfig[prop] != null) { + res[prop] = childConfig[prop]; + } else { + delete res[prop]; + } + } + } + for (prop in parentConfig) { + if ( + hasOwnProp(parentConfig, prop) && + !hasOwnProp(childConfig, prop) && + isObject(parentConfig[prop]) + ) { + // make sure changes to properties don't modify parent config + res[prop] = extend({}, res[prop]); + } + } + return res; + } + + function Locale(config) { + if (config != null) { + this.set(config); + } + } + + var keys; + + if (Object.keys) { + keys = Object.keys; + } else { + keys = function (obj) { + var i, + res = []; + for (i in obj) { + if (hasOwnProp(obj, i)) { + res.push(i); + } + } + return res; + }; + } + + var defaultCalendar = { + sameDay: '[Today at] LT', + nextDay: '[Tomorrow at] LT', + nextWeek: 'dddd [at] LT', + lastDay: '[Yesterday at] LT', + lastWeek: '[Last] dddd [at] LT', + sameElse: 'L', + }; + + function calendar(key, mom, now) { + var output = this._calendar[key] || this._calendar['sameElse']; + return isFunction(output) ? output.call(mom, now) : output; + } + + function zeroFill(number, targetLength, forceSign) { + var absNumber = '' + Math.abs(number), + zerosToFill = targetLength - absNumber.length, + sign = number >= 0; + return ( + (sign ? (forceSign ? '+' : '') : '-') + + Math.pow(10, Math.max(0, zerosToFill)).toString().substr(1) + + absNumber + ); + } + + var formattingTokens = + /(\[[^\[]*\])|(\\)?([Hh]mm(ss)?|Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Qo?|N{1,5}|YYYYYY|YYYYY|YYYY|YY|y{2,4}|yo?|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|kk?|mm?|ss?|S{1,9}|x|X|zz?|ZZ?|.)/g, + localFormattingTokens = /(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g, + formatFunctions = {}, + formatTokenFunctions = {}; + + // token: 'M' + // padded: ['MM', 2] + // ordinal: 'Mo' + // callback: function () { this.month() + 1 } + function addFormatToken(token, padded, ordinal, callback) { + var func = callback; + if (typeof callback === 'string') { + func = function () { + return this[callback](); + }; + } + if (token) { + formatTokenFunctions[token] = func; + } + if (padded) { + formatTokenFunctions[padded[0]] = function () { + return zeroFill(func.apply(this, arguments), padded[1], padded[2]); + }; + } + if (ordinal) { + formatTokenFunctions[ordinal] = function () { + return this.localeData().ordinal( + func.apply(this, arguments), + token + ); + }; + } + } + + function removeFormattingTokens(input) { + if (input.match(/\[[\s\S]/)) { + return input.replace(/^\[|\]$/g, ''); + } + return input.replace(/\\/g, ''); + } + + function makeFormatFunction(format) { + var array = format.match(formattingTokens), + i, + length; + + for (i = 0, length = array.length; i < length; i++) { + if (formatTokenFunctions[array[i]]) { + array[i] = formatTokenFunctions[array[i]]; + } else { + array[i] = removeFormattingTokens(array[i]); + } + } + + return function (mom) { + var output = '', + i; + for (i = 0; i < length; i++) { + output += isFunction(array[i]) + ? array[i].call(mom, format) + : array[i]; + } + return output; + }; + } + + // format date using native date object + function formatMoment(m, format) { + if (!m.isValid()) { + return m.localeData().invalidDate(); + } + + format = expandFormat(format, m.localeData()); + formatFunctions[format] = + formatFunctions[format] || makeFormatFunction(format); + + return formatFunctions[format](m); + } + + function expandFormat(format, locale) { + var i = 5; + + function replaceLongDateFormatTokens(input) { + return locale.longDateFormat(input) || input; + } + + localFormattingTokens.lastIndex = 0; + while (i >= 0 && localFormattingTokens.test(format)) { + format = format.replace( + localFormattingTokens, + replaceLongDateFormatTokens + ); + localFormattingTokens.lastIndex = 0; + i -= 1; + } + + return format; + } + + var defaultLongDateFormat = { + LTS: 'h:mm:ss A', + LT: 'h:mm A', + L: 'MM/DD/YYYY', + LL: 'MMMM D, YYYY', + LLL: 'MMMM D, YYYY h:mm A', + LLLL: 'dddd, MMMM D, YYYY h:mm A', + }; + + function longDateFormat(key) { + var format = this._longDateFormat[key], + formatUpper = this._longDateFormat[key.toUpperCase()]; + + if (format || !formatUpper) { + return format; + } + + this._longDateFormat[key] = formatUpper + .match(formattingTokens) + .map(function (tok) { + if ( + tok === 'MMMM' || + tok === 'MM' || + tok === 'DD' || + tok === 'dddd' + ) { + return tok.slice(1); + } + return tok; + }) + .join(''); + + return this._longDateFormat[key]; + } + + var defaultInvalidDate = 'Invalid date'; + + function invalidDate() { + return this._invalidDate; + } + + var defaultOrdinal = '%d', + defaultDayOfMonthOrdinalParse = /\d{1,2}/; + + function ordinal(number) { + return this._ordinal.replace('%d', number); + } + + var defaultRelativeTime = { + future: 'in %s', + past: '%s ago', + s: 'a few seconds', + ss: '%d seconds', + m: 'a minute', + mm: '%d minutes', + h: 'an hour', + hh: '%d hours', + d: 'a day', + dd: '%d days', + w: 'a week', + ww: '%d weeks', + M: 'a month', + MM: '%d months', + y: 'a year', + yy: '%d years', + }; + + function relativeTime(number, withoutSuffix, string, isFuture) { + var output = this._relativeTime[string]; + return isFunction(output) + ? output(number, withoutSuffix, string, isFuture) + : output.replace(/%d/i, number); + } + + function pastFuture(diff, output) { + var format = this._relativeTime[diff > 0 ? 'future' : 'past']; + return isFunction(format) ? format(output) : format.replace(/%s/i, output); + } + + var aliases = {}; + + function addUnitAlias(unit, shorthand) { + var lowerCase = unit.toLowerCase(); + aliases[lowerCase] = aliases[lowerCase + 's'] = aliases[shorthand] = unit; + } + + function normalizeUnits(units) { + return typeof units === 'string' + ? aliases[units] || aliases[units.toLowerCase()] + : undefined; + } + + function normalizeObjectUnits(inputObject) { + var normalizedInput = {}, + normalizedProp, + prop; + + for (prop in inputObject) { + if (hasOwnProp(inputObject, prop)) { + normalizedProp = normalizeUnits(prop); + if (normalizedProp) { + normalizedInput[normalizedProp] = inputObject[prop]; + } + } + } + + return normalizedInput; + } + + var priorities = {}; + + function addUnitPriority(unit, priority) { + priorities[unit] = priority; + } + + function getPrioritizedUnits(unitsObj) { + var units = [], + u; + for (u in unitsObj) { + if (hasOwnProp(unitsObj, u)) { + units.push({ unit: u, priority: priorities[u] }); + } + } + units.sort(function (a, b) { + return a.priority - b.priority; + }); + return units; + } + + function isLeapYear(year) { + return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0; + } + + function absFloor(number) { + if (number < 0) { + // -0 -> 0 + return Math.ceil(number) || 0; + } else { + return Math.floor(number); + } + } + + function toInt(argumentForCoercion) { + var coercedNumber = +argumentForCoercion, + value = 0; + + if (coercedNumber !== 0 && isFinite(coercedNumber)) { + value = absFloor(coercedNumber); + } + + return value; + } + + function makeGetSet(unit, keepTime) { + return function (value) { + if (value != null) { + set$1(this, unit, value); + hooks.updateOffset(this, keepTime); + return this; + } else { + return get(this, unit); + } + }; + } + + function get(mom, unit) { + return mom.isValid() + ? mom._d['get' + (mom._isUTC ? 'UTC' : '') + unit]() + : NaN; + } + + function set$1(mom, unit, value) { + if (mom.isValid() && !isNaN(value)) { + if ( + unit === 'FullYear' && + isLeapYear(mom.year()) && + mom.month() === 1 && + mom.date() === 29 + ) { + value = toInt(value); + mom._d['set' + (mom._isUTC ? 'UTC' : '') + unit]( + value, + mom.month(), + daysInMonth(value, mom.month()) + ); + } else { + mom._d['set' + (mom._isUTC ? 'UTC' : '') + unit](value); + } + } + } + + // MOMENTS + + function stringGet(units) { + units = normalizeUnits(units); + if (isFunction(this[units])) { + return this[units](); + } + return this; + } + + function stringSet(units, value) { + if (typeof units === 'object') { + units = normalizeObjectUnits(units); + var prioritized = getPrioritizedUnits(units), + i, + prioritizedLen = prioritized.length; + for (i = 0; i < prioritizedLen; i++) { + this[prioritized[i].unit](units[prioritized[i].unit]); + } + } else { + units = normalizeUnits(units); + if (isFunction(this[units])) { + return this[units](value); + } + } + return this; + } + + var match1 = /\d/, // 0 - 9 + match2 = /\d\d/, // 00 - 99 + match3 = /\d{3}/, // 000 - 999 + match4 = /\d{4}/, // 0000 - 9999 + match6 = /[+-]?\d{6}/, // -999999 - 999999 + match1to2 = /\d\d?/, // 0 - 99 + match3to4 = /\d\d\d\d?/, // 999 - 9999 + match5to6 = /\d\d\d\d\d\d?/, // 99999 - 999999 + match1to3 = /\d{1,3}/, // 0 - 999 + match1to4 = /\d{1,4}/, // 0 - 9999 + match1to6 = /[+-]?\d{1,6}/, // -999999 - 999999 + matchUnsigned = /\d+/, // 0 - inf + matchSigned = /[+-]?\d+/, // -inf - inf + matchOffset = /Z|[+-]\d\d:?\d\d/gi, // +00:00 -00:00 +0000 -0000 or Z + matchShortOffset = /Z|[+-]\d\d(?::?\d\d)?/gi, // +00 -00 +00:00 -00:00 +0000 -0000 or Z + matchTimestamp = /[+-]?\d+(\.\d{1,3})?/, // 123456789 123456789.123 + // any word (or two) characters or numbers including two/three word month in arabic. + // includes scottish gaelic two word and hyphenated months + matchWord = + /[0-9]{0,256}['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFF07\uFF10-\uFFEF]{1,256}|[\u0600-\u06FF\/]{1,256}(\s*?[\u0600-\u06FF]{1,256}){1,2}/i, + regexes; + + regexes = {}; + + function addRegexToken(token, regex, strictRegex) { + regexes[token] = isFunction(regex) + ? regex + : function (isStrict, localeData) { + return isStrict && strictRegex ? strictRegex : regex; + }; + } + + function getParseRegexForToken(token, config) { + if (!hasOwnProp(regexes, token)) { + return new RegExp(unescapeFormat(token)); + } + + return regexes[token](config._strict, config._locale); + } + + // Code from http://stackoverflow.com/questions/3561493/is-there-a-regexp-escape-function-in-javascript + function unescapeFormat(s) { + return regexEscape( + s + .replace('\\', '') + .replace( + /\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g, + function (matched, p1, p2, p3, p4) { + return p1 || p2 || p3 || p4; + } + ) + ); + } + + function regexEscape(s) { + return s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'); + } + + var tokens = {}; + + function addParseToken(token, callback) { + var i, + func = callback, + tokenLen; + if (typeof token === 'string') { + token = [token]; + } + if (isNumber(callback)) { + func = function (input, array) { + array[callback] = toInt(input); + }; + } + tokenLen = token.length; + for (i = 0; i < tokenLen; i++) { + tokens[token[i]] = func; + } + } + + function addWeekParseToken(token, callback) { + addParseToken(token, function (input, array, config, token) { + config._w = config._w || {}; + callback(input, config._w, config, token); + }); + } + + function addTimeToArrayFromToken(token, input, config) { + if (input != null && hasOwnProp(tokens, token)) { + tokens[token](input, config._a, config, token); + } + } + + var YEAR = 0, + MONTH = 1, + DATE = 2, + HOUR = 3, + MINUTE = 4, + SECOND = 5, + MILLISECOND = 6, + WEEK = 7, + WEEKDAY = 8; + + function mod(n, x) { + return ((n % x) + x) % x; + } + + var indexOf; + + if (Array.prototype.indexOf) { + indexOf = Array.prototype.indexOf; + } else { + indexOf = function (o) { + // I know + var i; + for (i = 0; i < this.length; ++i) { + if (this[i] === o) { + return i; + } + } + return -1; + }; + } + + function daysInMonth(year, month) { + if (isNaN(year) || isNaN(month)) { + return NaN; + } + var modMonth = mod(month, 12); + year += (month - modMonth) / 12; + return modMonth === 1 + ? isLeapYear(year) + ? 29 + : 28 + : 31 - ((modMonth % 7) % 2); + } + + // FORMATTING + + addFormatToken('M', ['MM', 2], 'Mo', function () { + return this.month() + 1; + }); + + addFormatToken('MMM', 0, 0, function (format) { + return this.localeData().monthsShort(this, format); + }); + + addFormatToken('MMMM', 0, 0, function (format) { + return this.localeData().months(this, format); + }); + + // ALIASES + + addUnitAlias('month', 'M'); + + // PRIORITY + + addUnitPriority('month', 8); + + // PARSING + + addRegexToken('M', match1to2); + addRegexToken('MM', match1to2, match2); + addRegexToken('MMM', function (isStrict, locale) { + return locale.monthsShortRegex(isStrict); + }); + addRegexToken('MMMM', function (isStrict, locale) { + return locale.monthsRegex(isStrict); + }); + + addParseToken(['M', 'MM'], function (input, array) { + array[MONTH] = toInt(input) - 1; + }); + + addParseToken(['MMM', 'MMMM'], function (input, array, config, token) { + var month = config._locale.monthsParse(input, token, config._strict); + // if we didn't find a month name, mark the date as invalid. + if (month != null) { + array[MONTH] = month; + } else { + getParsingFlags(config).invalidMonth = input; + } + }); + + // LOCALES + + var defaultLocaleMonths = + 'January_February_March_April_May_June_July_August_September_October_November_December'.split( + '_' + ), + defaultLocaleMonthsShort = + 'Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec'.split('_'), + MONTHS_IN_FORMAT = /D[oD]?(\[[^\[\]]*\]|\s)+MMMM?/, + defaultMonthsShortRegex = matchWord, + defaultMonthsRegex = matchWord; + + function localeMonths(m, format) { + if (!m) { + return isArray(this._months) + ? this._months + : this._months['standalone']; + } + return isArray(this._months) + ? this._months[m.month()] + : this._months[ + (this._months.isFormat || MONTHS_IN_FORMAT).test(format) + ? 'format' + : 'standalone' + ][m.month()]; + } + + function localeMonthsShort(m, format) { + if (!m) { + return isArray(this._monthsShort) + ? this._monthsShort + : this._monthsShort['standalone']; + } + return isArray(this._monthsShort) + ? this._monthsShort[m.month()] + : this._monthsShort[ + MONTHS_IN_FORMAT.test(format) ? 'format' : 'standalone' + ][m.month()]; + } + + function handleStrictParse(monthName, format, strict) { + var i, + ii, + mom, + llc = monthName.toLocaleLowerCase(); + if (!this._monthsParse) { + // this is not used + this._monthsParse = []; + this._longMonthsParse = []; + this._shortMonthsParse = []; + for (i = 0; i < 12; ++i) { + mom = createUTC([2000, i]); + this._shortMonthsParse[i] = this.monthsShort( + mom, + '' + ).toLocaleLowerCase(); + this._longMonthsParse[i] = this.months(mom, '').toLocaleLowerCase(); + } + } + + if (strict) { + if (format === 'MMM') { + ii = indexOf.call(this._shortMonthsParse, llc); + return ii !== -1 ? ii : null; + } else { + ii = indexOf.call(this._longMonthsParse, llc); + return ii !== -1 ? ii : null; + } + } else { + if (format === 'MMM') { + ii = indexOf.call(this._shortMonthsParse, llc); + if (ii !== -1) { + return ii; + } + ii = indexOf.call(this._longMonthsParse, llc); + return ii !== -1 ? ii : null; + } else { + ii = indexOf.call(this._longMonthsParse, llc); + if (ii !== -1) { + return ii; + } + ii = indexOf.call(this._shortMonthsParse, llc); + return ii !== -1 ? ii : null; + } + } + } + + function localeMonthsParse(monthName, format, strict) { + var i, mom, regex; + + if (this._monthsParseExact) { + return handleStrictParse.call(this, monthName, format, strict); + } + + if (!this._monthsParse) { + this._monthsParse = []; + this._longMonthsParse = []; + this._shortMonthsParse = []; + } + + // TODO: add sorting + // Sorting makes sure if one month (or abbr) is a prefix of another + // see sorting in computeMonthsParse + for (i = 0; i < 12; i++) { + // make the regex if we don't have it already + mom = createUTC([2000, i]); + if (strict && !this._longMonthsParse[i]) { + this._longMonthsParse[i] = new RegExp( + '^' + this.months(mom, '').replace('.', '') + '$', + 'i' + ); + this._shortMonthsParse[i] = new RegExp( + '^' + this.monthsShort(mom, '').replace('.', '') + '$', + 'i' + ); + } + if (!strict && !this._monthsParse[i]) { + regex = + '^' + this.months(mom, '') + '|^' + this.monthsShort(mom, ''); + this._monthsParse[i] = new RegExp(regex.replace('.', ''), 'i'); + } + // test the regex + if ( + strict && + format === 'MMMM' && + this._longMonthsParse[i].test(monthName) + ) { + return i; + } else if ( + strict && + format === 'MMM' && + this._shortMonthsParse[i].test(monthName) + ) { + return i; + } else if (!strict && this._monthsParse[i].test(monthName)) { + return i; + } + } + } + + // MOMENTS + + function setMonth(mom, value) { + var dayOfMonth; + + if (!mom.isValid()) { + // No op + return mom; + } + + if (typeof value === 'string') { + if (/^\d+$/.test(value)) { + value = toInt(value); + } else { + value = mom.localeData().monthsParse(value); + // TODO: Another silent failure? + if (!isNumber(value)) { + return mom; + } + } + } + + dayOfMonth = Math.min(mom.date(), daysInMonth(mom.year(), value)); + mom._d['set' + (mom._isUTC ? 'UTC' : '') + 'Month'](value, dayOfMonth); + return mom; + } + + function getSetMonth(value) { + if (value != null) { + setMonth(this, value); + hooks.updateOffset(this, true); + return this; + } else { + return get(this, 'Month'); + } + } + + function getDaysInMonth() { + return daysInMonth(this.year(), this.month()); + } + + function monthsShortRegex(isStrict) { + if (this._monthsParseExact) { + if (!hasOwnProp(this, '_monthsRegex')) { + computeMonthsParse.call(this); + } + if (isStrict) { + return this._monthsShortStrictRegex; + } else { + return this._monthsShortRegex; + } + } else { + if (!hasOwnProp(this, '_monthsShortRegex')) { + this._monthsShortRegex = defaultMonthsShortRegex; + } + return this._monthsShortStrictRegex && isStrict + ? this._monthsShortStrictRegex + : this._monthsShortRegex; + } + } + + function monthsRegex(isStrict) { + if (this._monthsParseExact) { + if (!hasOwnProp(this, '_monthsRegex')) { + computeMonthsParse.call(this); + } + if (isStrict) { + return this._monthsStrictRegex; + } else { + return this._monthsRegex; + } + } else { + if (!hasOwnProp(this, '_monthsRegex')) { + this._monthsRegex = defaultMonthsRegex; + } + return this._monthsStrictRegex && isStrict + ? this._monthsStrictRegex + : this._monthsRegex; + } + } + + function computeMonthsParse() { + function cmpLenRev(a, b) { + return b.length - a.length; + } + + var shortPieces = [], + longPieces = [], + mixedPieces = [], + i, + mom; + for (i = 0; i < 12; i++) { + // make the regex if we don't have it already + mom = createUTC([2000, i]); + shortPieces.push(this.monthsShort(mom, '')); + longPieces.push(this.months(mom, '')); + mixedPieces.push(this.months(mom, '')); + mixedPieces.push(this.monthsShort(mom, '')); + } + // Sorting makes sure if one month (or abbr) is a prefix of another it + // will match the longer piece. + shortPieces.sort(cmpLenRev); + longPieces.sort(cmpLenRev); + mixedPieces.sort(cmpLenRev); + for (i = 0; i < 12; i++) { + shortPieces[i] = regexEscape(shortPieces[i]); + longPieces[i] = regexEscape(longPieces[i]); + } + for (i = 0; i < 24; i++) { + mixedPieces[i] = regexEscape(mixedPieces[i]); + } + + this._monthsRegex = new RegExp('^(' + mixedPieces.join('|') + ')', 'i'); + this._monthsShortRegex = this._monthsRegex; + this._monthsStrictRegex = new RegExp( + '^(' + longPieces.join('|') + ')', + 'i' + ); + this._monthsShortStrictRegex = new RegExp( + '^(' + shortPieces.join('|') + ')', + 'i' + ); + } + + // FORMATTING + + addFormatToken('Y', 0, 0, function () { + var y = this.year(); + return y <= 9999 ? zeroFill(y, 4) : '+' + y; + }); + + addFormatToken(0, ['YY', 2], 0, function () { + return this.year() % 100; + }); + + addFormatToken(0, ['YYYY', 4], 0, 'year'); + addFormatToken(0, ['YYYYY', 5], 0, 'year'); + addFormatToken(0, ['YYYYYY', 6, true], 0, 'year'); + + // ALIASES + + addUnitAlias('year', 'y'); + + // PRIORITIES + + addUnitPriority('year', 1); + + // PARSING + + addRegexToken('Y', matchSigned); + addRegexToken('YY', match1to2, match2); + addRegexToken('YYYY', match1to4, match4); + addRegexToken('YYYYY', match1to6, match6); + addRegexToken('YYYYYY', match1to6, match6); + + addParseToken(['YYYYY', 'YYYYYY'], YEAR); + addParseToken('YYYY', function (input, array) { + array[YEAR] = + input.length === 2 ? hooks.parseTwoDigitYear(input) : toInt(input); + }); + addParseToken('YY', function (input, array) { + array[YEAR] = hooks.parseTwoDigitYear(input); + }); + addParseToken('Y', function (input, array) { + array[YEAR] = parseInt(input, 10); + }); + + // HELPERS + + function daysInYear(year) { + return isLeapYear(year) ? 366 : 365; + } + + // HOOKS + + hooks.parseTwoDigitYear = function (input) { + return toInt(input) + (toInt(input) > 68 ? 1900 : 2000); + }; + + // MOMENTS + + var getSetYear = makeGetSet('FullYear', true); + + function getIsLeapYear() { + return isLeapYear(this.year()); + } + + function createDate(y, m, d, h, M, s, ms) { + // can't just apply() to create a date: + // https://stackoverflow.com/q/181348 + var date; + // the date constructor remaps years 0-99 to 1900-1999 + if (y < 100 && y >= 0) { + // preserve leap years using a full 400 year cycle, then reset + date = new Date(y + 400, m, d, h, M, s, ms); + if (isFinite(date.getFullYear())) { + date.setFullYear(y); + } + } else { + date = new Date(y, m, d, h, M, s, ms); + } + + return date; + } + + function createUTCDate(y) { + var date, args; + // the Date.UTC function remaps years 0-99 to 1900-1999 + if (y < 100 && y >= 0) { + args = Array.prototype.slice.call(arguments); + // preserve leap years using a full 400 year cycle, then reset + args[0] = y + 400; + date = new Date(Date.UTC.apply(null, args)); + if (isFinite(date.getUTCFullYear())) { + date.setUTCFullYear(y); + } + } else { + date = new Date(Date.UTC.apply(null, arguments)); + } + + return date; + } + + // start-of-first-week - start-of-year + function firstWeekOffset(year, dow, doy) { + var // first-week day -- which january is always in the first week (4 for iso, 1 for other) + fwd = 7 + dow - doy, + // first-week day local weekday -- which local weekday is fwd + fwdlw = (7 + createUTCDate(year, 0, fwd).getUTCDay() - dow) % 7; + + return -fwdlw + fwd - 1; + } + + // https://en.wikipedia.org/wiki/ISO_week_date#Calculating_a_date_given_the_year.2C_week_number_and_weekday + function dayOfYearFromWeeks(year, week, weekday, dow, doy) { + var localWeekday = (7 + weekday - dow) % 7, + weekOffset = firstWeekOffset(year, dow, doy), + dayOfYear = 1 + 7 * (week - 1) + localWeekday + weekOffset, + resYear, + resDayOfYear; + + if (dayOfYear <= 0) { + resYear = year - 1; + resDayOfYear = daysInYear(resYear) + dayOfYear; + } else if (dayOfYear > daysInYear(year)) { + resYear = year + 1; + resDayOfYear = dayOfYear - daysInYear(year); + } else { + resYear = year; + resDayOfYear = dayOfYear; + } + + return { + year: resYear, + dayOfYear: resDayOfYear, + }; + } + + function weekOfYear(mom, dow, doy) { + var weekOffset = firstWeekOffset(mom.year(), dow, doy), + week = Math.floor((mom.dayOfYear() - weekOffset - 1) / 7) + 1, + resWeek, + resYear; + + if (week < 1) { + resYear = mom.year() - 1; + resWeek = week + weeksInYear(resYear, dow, doy); + } else if (week > weeksInYear(mom.year(), dow, doy)) { + resWeek = week - weeksInYear(mom.year(), dow, doy); + resYear = mom.year() + 1; + } else { + resYear = mom.year(); + resWeek = week; + } + + return { + week: resWeek, + year: resYear, + }; + } + + function weeksInYear(year, dow, doy) { + var weekOffset = firstWeekOffset(year, dow, doy), + weekOffsetNext = firstWeekOffset(year + 1, dow, doy); + return (daysInYear(year) - weekOffset + weekOffsetNext) / 7; + } + + // FORMATTING + + addFormatToken('w', ['ww', 2], 'wo', 'week'); + addFormatToken('W', ['WW', 2], 'Wo', 'isoWeek'); + + // ALIASES + + addUnitAlias('week', 'w'); + addUnitAlias('isoWeek', 'W'); + + // PRIORITIES + + addUnitPriority('week', 5); + addUnitPriority('isoWeek', 5); + + // PARSING + + addRegexToken('w', match1to2); + addRegexToken('ww', match1to2, match2); + addRegexToken('W', match1to2); + addRegexToken('WW', match1to2, match2); + + addWeekParseToken( + ['w', 'ww', 'W', 'WW'], + function (input, week, config, token) { + week[token.substr(0, 1)] = toInt(input); + } + ); + + // HELPERS + + // LOCALES + + function localeWeek(mom) { + return weekOfYear(mom, this._week.dow, this._week.doy).week; + } + + var defaultLocaleWeek = { + dow: 0, // Sunday is the first day of the week. + doy: 6, // The week that contains Jan 6th is the first week of the year. + }; + + function localeFirstDayOfWeek() { + return this._week.dow; + } + + function localeFirstDayOfYear() { + return this._week.doy; + } + + // MOMENTS + + function getSetWeek(input) { + var week = this.localeData().week(this); + return input == null ? week : this.add((input - week) * 7, 'd'); + } + + function getSetISOWeek(input) { + var week = weekOfYear(this, 1, 4).week; + return input == null ? week : this.add((input - week) * 7, 'd'); + } + + // FORMATTING + + addFormatToken('d', 0, 'do', 'day'); + + addFormatToken('dd', 0, 0, function (format) { + return this.localeData().weekdaysMin(this, format); + }); + + addFormatToken('ddd', 0, 0, function (format) { + return this.localeData().weekdaysShort(this, format); + }); + + addFormatToken('dddd', 0, 0, function (format) { + return this.localeData().weekdays(this, format); + }); + + addFormatToken('e', 0, 0, 'weekday'); + addFormatToken('E', 0, 0, 'isoWeekday'); + + // ALIASES + + addUnitAlias('day', 'd'); + addUnitAlias('weekday', 'e'); + addUnitAlias('isoWeekday', 'E'); + + // PRIORITY + addUnitPriority('day', 11); + addUnitPriority('weekday', 11); + addUnitPriority('isoWeekday', 11); + + // PARSING + + addRegexToken('d', match1to2); + addRegexToken('e', match1to2); + addRegexToken('E', match1to2); + addRegexToken('dd', function (isStrict, locale) { + return locale.weekdaysMinRegex(isStrict); + }); + addRegexToken('ddd', function (isStrict, locale) { + return locale.weekdaysShortRegex(isStrict); + }); + addRegexToken('dddd', function (isStrict, locale) { + return locale.weekdaysRegex(isStrict); + }); + + addWeekParseToken(['dd', 'ddd', 'dddd'], function (input, week, config, token) { + var weekday = config._locale.weekdaysParse(input, token, config._strict); + // if we didn't get a weekday name, mark the date as invalid + if (weekday != null) { + week.d = weekday; + } else { + getParsingFlags(config).invalidWeekday = input; + } + }); + + addWeekParseToken(['d', 'e', 'E'], function (input, week, config, token) { + week[token] = toInt(input); + }); + + // HELPERS + + function parseWeekday(input, locale) { + if (typeof input !== 'string') { + return input; + } + + if (!isNaN(input)) { + return parseInt(input, 10); + } + + input = locale.weekdaysParse(input); + if (typeof input === 'number') { + return input; + } + + return null; + } + + function parseIsoWeekday(input, locale) { + if (typeof input === 'string') { + return locale.weekdaysParse(input) % 7 || 7; + } + return isNaN(input) ? null : input; + } + + // LOCALES + function shiftWeekdays(ws, n) { + return ws.slice(n, 7).concat(ws.slice(0, n)); + } + + var defaultLocaleWeekdays = + 'Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday'.split('_'), + defaultLocaleWeekdaysShort = 'Sun_Mon_Tue_Wed_Thu_Fri_Sat'.split('_'), + defaultLocaleWeekdaysMin = 'Su_Mo_Tu_We_Th_Fr_Sa'.split('_'), + defaultWeekdaysRegex = matchWord, + defaultWeekdaysShortRegex = matchWord, + defaultWeekdaysMinRegex = matchWord; + + function localeWeekdays(m, format) { + var weekdays = isArray(this._weekdays) + ? this._weekdays + : this._weekdays[ + m && m !== true && this._weekdays.isFormat.test(format) + ? 'format' + : 'standalone' + ]; + return m === true + ? shiftWeekdays(weekdays, this._week.dow) + : m + ? weekdays[m.day()] + : weekdays; + } + + function localeWeekdaysShort(m) { + return m === true + ? shiftWeekdays(this._weekdaysShort, this._week.dow) + : m + ? this._weekdaysShort[m.day()] + : this._weekdaysShort; + } + + function localeWeekdaysMin(m) { + return m === true + ? shiftWeekdays(this._weekdaysMin, this._week.dow) + : m + ? this._weekdaysMin[m.day()] + : this._weekdaysMin; + } + + function handleStrictParse$1(weekdayName, format, strict) { + var i, + ii, + mom, + llc = weekdayName.toLocaleLowerCase(); + if (!this._weekdaysParse) { + this._weekdaysParse = []; + this._shortWeekdaysParse = []; + this._minWeekdaysParse = []; + + for (i = 0; i < 7; ++i) { + mom = createUTC([2000, 1]).day(i); + this._minWeekdaysParse[i] = this.weekdaysMin( + mom, + '' + ).toLocaleLowerCase(); + this._shortWeekdaysParse[i] = this.weekdaysShort( + mom, + '' + ).toLocaleLowerCase(); + this._weekdaysParse[i] = this.weekdays(mom, '').toLocaleLowerCase(); + } + } + + if (strict) { + if (format === 'dddd') { + ii = indexOf.call(this._weekdaysParse, llc); + return ii !== -1 ? ii : null; + } else if (format === 'ddd') { + ii = indexOf.call(this._shortWeekdaysParse, llc); + return ii !== -1 ? ii : null; + } else { + ii = indexOf.call(this._minWeekdaysParse, llc); + return ii !== -1 ? ii : null; + } + } else { + if (format === 'dddd') { + ii = indexOf.call(this._weekdaysParse, llc); + if (ii !== -1) { + return ii; + } + ii = indexOf.call(this._shortWeekdaysParse, llc); + if (ii !== -1) { + return ii; + } + ii = indexOf.call(this._minWeekdaysParse, llc); + return ii !== -1 ? ii : null; + } else if (format === 'ddd') { + ii = indexOf.call(this._shortWeekdaysParse, llc); + if (ii !== -1) { + return ii; + } + ii = indexOf.call(this._weekdaysParse, llc); + if (ii !== -1) { + return ii; + } + ii = indexOf.call(this._minWeekdaysParse, llc); + return ii !== -1 ? ii : null; + } else { + ii = indexOf.call(this._minWeekdaysParse, llc); + if (ii !== -1) { + return ii; + } + ii = indexOf.call(this._weekdaysParse, llc); + if (ii !== -1) { + return ii; + } + ii = indexOf.call(this._shortWeekdaysParse, llc); + return ii !== -1 ? ii : null; + } + } + } + + function localeWeekdaysParse(weekdayName, format, strict) { + var i, mom, regex; + + if (this._weekdaysParseExact) { + return handleStrictParse$1.call(this, weekdayName, format, strict); + } + + if (!this._weekdaysParse) { + this._weekdaysParse = []; + this._minWeekdaysParse = []; + this._shortWeekdaysParse = []; + this._fullWeekdaysParse = []; + } + + for (i = 0; i < 7; i++) { + // make the regex if we don't have it already + + mom = createUTC([2000, 1]).day(i); + if (strict && !this._fullWeekdaysParse[i]) { + this._fullWeekdaysParse[i] = new RegExp( + '^' + this.weekdays(mom, '').replace('.', '\\.?') + '$', + 'i' + ); + this._shortWeekdaysParse[i] = new RegExp( + '^' + this.weekdaysShort(mom, '').replace('.', '\\.?') + '$', + 'i' + ); + this._minWeekdaysParse[i] = new RegExp( + '^' + this.weekdaysMin(mom, '').replace('.', '\\.?') + '$', + 'i' + ); + } + if (!this._weekdaysParse[i]) { + regex = + '^' + + this.weekdays(mom, '') + + '|^' + + this.weekdaysShort(mom, '') + + '|^' + + this.weekdaysMin(mom, ''); + this._weekdaysParse[i] = new RegExp(regex.replace('.', ''), 'i'); + } + // test the regex + if ( + strict && + format === 'dddd' && + this._fullWeekdaysParse[i].test(weekdayName) + ) { + return i; + } else if ( + strict && + format === 'ddd' && + this._shortWeekdaysParse[i].test(weekdayName) + ) { + return i; + } else if ( + strict && + format === 'dd' && + this._minWeekdaysParse[i].test(weekdayName) + ) { + return i; + } else if (!strict && this._weekdaysParse[i].test(weekdayName)) { + return i; + } + } + } + + // MOMENTS + + function getSetDayOfWeek(input) { + if (!this.isValid()) { + return input != null ? this : NaN; + } + var day = this._isUTC ? this._d.getUTCDay() : this._d.getDay(); + if (input != null) { + input = parseWeekday(input, this.localeData()); + return this.add(input - day, 'd'); + } else { + return day; + } + } + + function getSetLocaleDayOfWeek(input) { + if (!this.isValid()) { + return input != null ? this : NaN; + } + var weekday = (this.day() + 7 - this.localeData()._week.dow) % 7; + return input == null ? weekday : this.add(input - weekday, 'd'); + } + + function getSetISODayOfWeek(input) { + if (!this.isValid()) { + return input != null ? this : NaN; + } + + // behaves the same as moment#day except + // as a getter, returns 7 instead of 0 (1-7 range instead of 0-6) + // as a setter, sunday should belong to the previous week. + + if (input != null) { + var weekday = parseIsoWeekday(input, this.localeData()); + return this.day(this.day() % 7 ? weekday : weekday - 7); + } else { + return this.day() || 7; + } + } + + function weekdaysRegex(isStrict) { + if (this._weekdaysParseExact) { + if (!hasOwnProp(this, '_weekdaysRegex')) { + computeWeekdaysParse.call(this); + } + if (isStrict) { + return this._weekdaysStrictRegex; + } else { + return this._weekdaysRegex; + } + } else { + if (!hasOwnProp(this, '_weekdaysRegex')) { + this._weekdaysRegex = defaultWeekdaysRegex; + } + return this._weekdaysStrictRegex && isStrict + ? this._weekdaysStrictRegex + : this._weekdaysRegex; + } + } + + function weekdaysShortRegex(isStrict) { + if (this._weekdaysParseExact) { + if (!hasOwnProp(this, '_weekdaysRegex')) { + computeWeekdaysParse.call(this); + } + if (isStrict) { + return this._weekdaysShortStrictRegex; + } else { + return this._weekdaysShortRegex; + } + } else { + if (!hasOwnProp(this, '_weekdaysShortRegex')) { + this._weekdaysShortRegex = defaultWeekdaysShortRegex; + } + return this._weekdaysShortStrictRegex && isStrict + ? this._weekdaysShortStrictRegex + : this._weekdaysShortRegex; + } + } + + function weekdaysMinRegex(isStrict) { + if (this._weekdaysParseExact) { + if (!hasOwnProp(this, '_weekdaysRegex')) { + computeWeekdaysParse.call(this); + } + if (isStrict) { + return this._weekdaysMinStrictRegex; + } else { + return this._weekdaysMinRegex; + } + } else { + if (!hasOwnProp(this, '_weekdaysMinRegex')) { + this._weekdaysMinRegex = defaultWeekdaysMinRegex; + } + return this._weekdaysMinStrictRegex && isStrict + ? this._weekdaysMinStrictRegex + : this._weekdaysMinRegex; + } + } + + function computeWeekdaysParse() { + function cmpLenRev(a, b) { + return b.length - a.length; + } + + var minPieces = [], + shortPieces = [], + longPieces = [], + mixedPieces = [], + i, + mom, + minp, + shortp, + longp; + for (i = 0; i < 7; i++) { + // make the regex if we don't have it already + mom = createUTC([2000, 1]).day(i); + minp = regexEscape(this.weekdaysMin(mom, '')); + shortp = regexEscape(this.weekdaysShort(mom, '')); + longp = regexEscape(this.weekdays(mom, '')); + minPieces.push(minp); + shortPieces.push(shortp); + longPieces.push(longp); + mixedPieces.push(minp); + mixedPieces.push(shortp); + mixedPieces.push(longp); + } + // Sorting makes sure if one weekday (or abbr) is a prefix of another it + // will match the longer piece. + minPieces.sort(cmpLenRev); + shortPieces.sort(cmpLenRev); + longPieces.sort(cmpLenRev); + mixedPieces.sort(cmpLenRev); + + this._weekdaysRegex = new RegExp('^(' + mixedPieces.join('|') + ')', 'i'); + this._weekdaysShortRegex = this._weekdaysRegex; + this._weekdaysMinRegex = this._weekdaysRegex; + + this._weekdaysStrictRegex = new RegExp( + '^(' + longPieces.join('|') + ')', + 'i' + ); + this._weekdaysShortStrictRegex = new RegExp( + '^(' + shortPieces.join('|') + ')', + 'i' + ); + this._weekdaysMinStrictRegex = new RegExp( + '^(' + minPieces.join('|') + ')', + 'i' + ); + } + + // FORMATTING + + function hFormat() { + return this.hours() % 12 || 12; + } + + function kFormat() { + return this.hours() || 24; + } + + addFormatToken('H', ['HH', 2], 0, 'hour'); + addFormatToken('h', ['hh', 2], 0, hFormat); + addFormatToken('k', ['kk', 2], 0, kFormat); + + addFormatToken('hmm', 0, 0, function () { + return '' + hFormat.apply(this) + zeroFill(this.minutes(), 2); + }); + + addFormatToken('hmmss', 0, 0, function () { + return ( + '' + + hFormat.apply(this) + + zeroFill(this.minutes(), 2) + + zeroFill(this.seconds(), 2) + ); + }); + + addFormatToken('Hmm', 0, 0, function () { + return '' + this.hours() + zeroFill(this.minutes(), 2); + }); + + addFormatToken('Hmmss', 0, 0, function () { + return ( + '' + + this.hours() + + zeroFill(this.minutes(), 2) + + zeroFill(this.seconds(), 2) + ); + }); + + function meridiem(token, lowercase) { + addFormatToken(token, 0, 0, function () { + return this.localeData().meridiem( + this.hours(), + this.minutes(), + lowercase + ); + }); + } + + meridiem('a', true); + meridiem('A', false); + + // ALIASES + + addUnitAlias('hour', 'h'); + + // PRIORITY + addUnitPriority('hour', 13); + + // PARSING + + function matchMeridiem(isStrict, locale) { + return locale._meridiemParse; + } + + addRegexToken('a', matchMeridiem); + addRegexToken('A', matchMeridiem); + addRegexToken('H', match1to2); + addRegexToken('h', match1to2); + addRegexToken('k', match1to2); + addRegexToken('HH', match1to2, match2); + addRegexToken('hh', match1to2, match2); + addRegexToken('kk', match1to2, match2); + + addRegexToken('hmm', match3to4); + addRegexToken('hmmss', match5to6); + addRegexToken('Hmm', match3to4); + addRegexToken('Hmmss', match5to6); + + addParseToken(['H', 'HH'], HOUR); + addParseToken(['k', 'kk'], function (input, array, config) { + var kInput = toInt(input); + array[HOUR] = kInput === 24 ? 0 : kInput; + }); + addParseToken(['a', 'A'], function (input, array, config) { + config._isPm = config._locale.isPM(input); + config._meridiem = input; + }); + addParseToken(['h', 'hh'], function (input, array, config) { + array[HOUR] = toInt(input); + getParsingFlags(config).bigHour = true; + }); + addParseToken('hmm', function (input, array, config) { + var pos = input.length - 2; + array[HOUR] = toInt(input.substr(0, pos)); + array[MINUTE] = toInt(input.substr(pos)); + getParsingFlags(config).bigHour = true; + }); + addParseToken('hmmss', function (input, array, config) { + var pos1 = input.length - 4, + pos2 = input.length - 2; + array[HOUR] = toInt(input.substr(0, pos1)); + array[MINUTE] = toInt(input.substr(pos1, 2)); + array[SECOND] = toInt(input.substr(pos2)); + getParsingFlags(config).bigHour = true; + }); + addParseToken('Hmm', function (input, array, config) { + var pos = input.length - 2; + array[HOUR] = toInt(input.substr(0, pos)); + array[MINUTE] = toInt(input.substr(pos)); + }); + addParseToken('Hmmss', function (input, array, config) { + var pos1 = input.length - 4, + pos2 = input.length - 2; + array[HOUR] = toInt(input.substr(0, pos1)); + array[MINUTE] = toInt(input.substr(pos1, 2)); + array[SECOND] = toInt(input.substr(pos2)); + }); + + // LOCALES + + function localeIsPM(input) { + // IE8 Quirks Mode & IE7 Standards Mode do not allow accessing strings like arrays + // Using charAt should be more compatible. + return (input + '').toLowerCase().charAt(0) === 'p'; + } + + var defaultLocaleMeridiemParse = /[ap]\.?m?\.?/i, + // Setting the hour should keep the time, because the user explicitly + // specified which hour they want. So trying to maintain the same hour (in + // a new timezone) makes sense. Adding/subtracting hours does not follow + // this rule. + getSetHour = makeGetSet('Hours', true); + + function localeMeridiem(hours, minutes, isLower) { + if (hours > 11) { + return isLower ? 'pm' : 'PM'; + } else { + return isLower ? 'am' : 'AM'; + } + } + + var baseConfig = { + calendar: defaultCalendar, + longDateFormat: defaultLongDateFormat, + invalidDate: defaultInvalidDate, + ordinal: defaultOrdinal, + dayOfMonthOrdinalParse: defaultDayOfMonthOrdinalParse, + relativeTime: defaultRelativeTime, + + months: defaultLocaleMonths, + monthsShort: defaultLocaleMonthsShort, + + week: defaultLocaleWeek, + + weekdays: defaultLocaleWeekdays, + weekdaysMin: defaultLocaleWeekdaysMin, + weekdaysShort: defaultLocaleWeekdaysShort, + + meridiemParse: defaultLocaleMeridiemParse, + }; + + // internal storage for locale config files + var locales = {}, + localeFamilies = {}, + globalLocale; + + function commonPrefix(arr1, arr2) { + var i, + minl = Math.min(arr1.length, arr2.length); + for (i = 0; i < minl; i += 1) { + if (arr1[i] !== arr2[i]) { + return i; + } + } + return minl; + } + + function normalizeLocale(key) { + return key ? key.toLowerCase().replace('_', '-') : key; + } + + // pick the locale from the array + // try ['en-au', 'en-gb'] as 'en-au', 'en-gb', 'en', as in move through the list trying each + // substring from most specific to least, but move to the next array item if it's a more specific variant than the current root + function chooseLocale(names) { + var i = 0, + j, + next, + locale, + split; + + while (i < names.length) { + split = normalizeLocale(names[i]).split('-'); + j = split.length; + next = normalizeLocale(names[i + 1]); + next = next ? next.split('-') : null; + while (j > 0) { + locale = loadLocale(split.slice(0, j).join('-')); + if (locale) { + return locale; + } + if ( + next && + next.length >= j && + commonPrefix(split, next) >= j - 1 + ) { + //the next array item is better than a shallower substring of this one + break; + } + j--; + } + i++; + } + return globalLocale; + } + + function isLocaleNameSane(name) { + // Prevent names that look like filesystem paths, i.e contain '/' or '\' + return name.match('^[^/\\\\]*$') != null; + } + + function loadLocale(name) { + var oldLocale = null, + aliasedRequire; + // TODO: Find a better way to register and load all the locales in Node + if ( + locales[name] === undefined && + typeof module !== 'undefined' && + module && + module.exports && + isLocaleNameSane(name) + ) { + try { + oldLocale = globalLocale._abbr; + aliasedRequire = require; + aliasedRequire('./locale/' + name); + getSetGlobalLocale(oldLocale); + } catch (e) { + // mark as not found to avoid repeating expensive file require call causing high CPU + // when trying to find en-US, en_US, en-us for every format call + locales[name] = null; // null means not found + } + } + return locales[name]; + } + + // This function will load locale and then set the global locale. If + // no arguments are passed in, it will simply return the current global + // locale key. + function getSetGlobalLocale(key, values) { + var data; + if (key) { + if (isUndefined(values)) { + data = getLocale(key); + } else { + data = defineLocale(key, values); + } + + if (data) { + // moment.duration._locale = moment._locale = data; + globalLocale = data; + } else { + if (typeof console !== 'undefined' && console.warn) { + //warn user if arguments are passed but the locale could not be set + console.warn( + 'Locale ' + key + ' not found. Did you forget to load it?' + ); + } + } + } + + return globalLocale._abbr; + } + + function defineLocale(name, config) { + if (config !== null) { + var locale, + parentConfig = baseConfig; + config.abbr = name; + if (locales[name] != null) { + deprecateSimple( + 'defineLocaleOverride', + 'use moment.updateLocale(localeName, config) to change ' + + 'an existing locale. moment.defineLocale(localeName, ' + + 'config) should only be used for creating a new locale ' + + 'See http://momentjs.com/guides/#/warnings/define-locale/ for more info.' + ); + parentConfig = locales[name]._config; + } else if (config.parentLocale != null) { + if (locales[config.parentLocale] != null) { + parentConfig = locales[config.parentLocale]._config; + } else { + locale = loadLocale(config.parentLocale); + if (locale != null) { + parentConfig = locale._config; + } else { + if (!localeFamilies[config.parentLocale]) { + localeFamilies[config.parentLocale] = []; + } + localeFamilies[config.parentLocale].push({ + name: name, + config: config, + }); + return null; + } + } + } + locales[name] = new Locale(mergeConfigs(parentConfig, config)); + + if (localeFamilies[name]) { + localeFamilies[name].forEach(function (x) { + defineLocale(x.name, x.config); + }); + } + + // backwards compat for now: also set the locale + // make sure we set the locale AFTER all child locales have been + // created, so we won't end up with the child locale set. + getSetGlobalLocale(name); + + return locales[name]; + } else { + // useful for testing + delete locales[name]; + return null; + } + } + + function updateLocale(name, config) { + if (config != null) { + var locale, + tmpLocale, + parentConfig = baseConfig; + + if (locales[name] != null && locales[name].parentLocale != null) { + // Update existing child locale in-place to avoid memory-leaks + locales[name].set(mergeConfigs(locales[name]._config, config)); + } else { + // MERGE + tmpLocale = loadLocale(name); + if (tmpLocale != null) { + parentConfig = tmpLocale._config; + } + config = mergeConfigs(parentConfig, config); + if (tmpLocale == null) { + // updateLocale is called for creating a new locale + // Set abbr so it will have a name (getters return + // undefined otherwise). + config.abbr = name; + } + locale = new Locale(config); + locale.parentLocale = locales[name]; + locales[name] = locale; + } + + // backwards compat for now: also set the locale + getSetGlobalLocale(name); + } else { + // pass null for config to unupdate, useful for tests + if (locales[name] != null) { + if (locales[name].parentLocale != null) { + locales[name] = locales[name].parentLocale; + if (name === getSetGlobalLocale()) { + getSetGlobalLocale(name); + } + } else if (locales[name] != null) { + delete locales[name]; + } + } + } + return locales[name]; + } + + // returns locale data + function getLocale(key) { + var locale; + + if (key && key._locale && key._locale._abbr) { + key = key._locale._abbr; + } + + if (!key) { + return globalLocale; + } + + if (!isArray(key)) { + //short-circuit everything else + locale = loadLocale(key); + if (locale) { + return locale; + } + key = [key]; + } + + return chooseLocale(key); + } + + function listLocales() { + return keys(locales); + } + + function checkOverflow(m) { + var overflow, + a = m._a; + + if (a && getParsingFlags(m).overflow === -2) { + overflow = + a[MONTH] < 0 || a[MONTH] > 11 + ? MONTH + : a[DATE] < 1 || a[DATE] > daysInMonth(a[YEAR], a[MONTH]) + ? DATE + : a[HOUR] < 0 || + a[HOUR] > 24 || + (a[HOUR] === 24 && + (a[MINUTE] !== 0 || + a[SECOND] !== 0 || + a[MILLISECOND] !== 0)) + ? HOUR + : a[MINUTE] < 0 || a[MINUTE] > 59 + ? MINUTE + : a[SECOND] < 0 || a[SECOND] > 59 + ? SECOND + : a[MILLISECOND] < 0 || a[MILLISECOND] > 999 + ? MILLISECOND + : -1; + + if ( + getParsingFlags(m)._overflowDayOfYear && + (overflow < YEAR || overflow > DATE) + ) { + overflow = DATE; + } + if (getParsingFlags(m)._overflowWeeks && overflow === -1) { + overflow = WEEK; + } + if (getParsingFlags(m)._overflowWeekday && overflow === -1) { + overflow = WEEKDAY; + } + + getParsingFlags(m).overflow = overflow; + } + + return m; + } + + // iso 8601 regex + // 0000-00-00 0000-W00 or 0000-W00-0 + T + 00 or 00:00 or 00:00:00 or 00:00:00.000 + +00:00 or +0000 or +00) + var extendedIsoRegex = + /^\s*((?:[+-]\d{6}|\d{4})-(?:\d\d-\d\d|W\d\d-\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?::\d\d(?::\d\d(?:[.,]\d+)?)?)?)([+-]\d\d(?::?\d\d)?|\s*Z)?)?$/, + basicIsoRegex = + /^\s*((?:[+-]\d{6}|\d{4})(?:\d\d\d\d|W\d\d\d|W\d\d|\d\d\d|\d\d|))(?:(T| )(\d\d(?:\d\d(?:\d\d(?:[.,]\d+)?)?)?)([+-]\d\d(?::?\d\d)?|\s*Z)?)?$/, + tzRegex = /Z|[+-]\d\d(?::?\d\d)?/, + isoDates = [ + ['YYYYYY-MM-DD', /[+-]\d{6}-\d\d-\d\d/], + ['YYYY-MM-DD', /\d{4}-\d\d-\d\d/], + ['GGGG-[W]WW-E', /\d{4}-W\d\d-\d/], + ['GGGG-[W]WW', /\d{4}-W\d\d/, false], + ['YYYY-DDD', /\d{4}-\d{3}/], + ['YYYY-MM', /\d{4}-\d\d/, false], + ['YYYYYYMMDD', /[+-]\d{10}/], + ['YYYYMMDD', /\d{8}/], + ['GGGG[W]WWE', /\d{4}W\d{3}/], + ['GGGG[W]WW', /\d{4}W\d{2}/, false], + ['YYYYDDD', /\d{7}/], + ['YYYYMM', /\d{6}/, false], + ['YYYY', /\d{4}/, false], + ], + // iso time formats and regexes + isoTimes = [ + ['HH:mm:ss.SSSS', /\d\d:\d\d:\d\d\.\d+/], + ['HH:mm:ss,SSSS', /\d\d:\d\d:\d\d,\d+/], + ['HH:mm:ss', /\d\d:\d\d:\d\d/], + ['HH:mm', /\d\d:\d\d/], + ['HHmmss.SSSS', /\d\d\d\d\d\d\.\d+/], + ['HHmmss,SSSS', /\d\d\d\d\d\d,\d+/], + ['HHmmss', /\d\d\d\d\d\d/], + ['HHmm', /\d\d\d\d/], + ['HH', /\d\d/], + ], + aspNetJsonRegex = /^\/?Date\((-?\d+)/i, + // RFC 2822 regex: For details see https://tools.ietf.org/html/rfc2822#section-3.3 + rfc2822 = + /^(?:(Mon|Tue|Wed|Thu|Fri|Sat|Sun),?\s)?(\d{1,2})\s(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s(\d{2,4})\s(\d\d):(\d\d)(?::(\d\d))?\s(?:(UT|GMT|[ECMP][SD]T)|([Zz])|([+-]\d{4}))$/, + obsOffsets = { + UT: 0, + GMT: 0, + EDT: -4 * 60, + EST: -5 * 60, + CDT: -5 * 60, + CST: -6 * 60, + MDT: -6 * 60, + MST: -7 * 60, + PDT: -7 * 60, + PST: -8 * 60, + }; + + // date from iso format + function configFromISO(config) { + var i, + l, + string = config._i, + match = extendedIsoRegex.exec(string) || basicIsoRegex.exec(string), + allowTime, + dateFormat, + timeFormat, + tzFormat, + isoDatesLen = isoDates.length, + isoTimesLen = isoTimes.length; + + if (match) { + getParsingFlags(config).iso = true; + for (i = 0, l = isoDatesLen; i < l; i++) { + if (isoDates[i][1].exec(match[1])) { + dateFormat = isoDates[i][0]; + allowTime = isoDates[i][2] !== false; + break; + } + } + if (dateFormat == null) { + config._isValid = false; + return; + } + if (match[3]) { + for (i = 0, l = isoTimesLen; i < l; i++) { + if (isoTimes[i][1].exec(match[3])) { + // match[2] should be 'T' or space + timeFormat = (match[2] || ' ') + isoTimes[i][0]; + break; + } + } + if (timeFormat == null) { + config._isValid = false; + return; + } + } + if (!allowTime && timeFormat != null) { + config._isValid = false; + return; + } + if (match[4]) { + if (tzRegex.exec(match[4])) { + tzFormat = 'Z'; + } else { + config._isValid = false; + return; + } + } + config._f = dateFormat + (timeFormat || '') + (tzFormat || ''); + configFromStringAndFormat(config); + } else { + config._isValid = false; + } + } + + function extractFromRFC2822Strings( + yearStr, + monthStr, + dayStr, + hourStr, + minuteStr, + secondStr + ) { + var result = [ + untruncateYear(yearStr), + defaultLocaleMonthsShort.indexOf(monthStr), + parseInt(dayStr, 10), + parseInt(hourStr, 10), + parseInt(minuteStr, 10), + ]; + + if (secondStr) { + result.push(parseInt(secondStr, 10)); + } + + return result; + } + + function untruncateYear(yearStr) { + var year = parseInt(yearStr, 10); + if (year <= 49) { + return 2000 + year; + } else if (year <= 999) { + return 1900 + year; + } + return year; + } + + function preprocessRFC2822(s) { + // Remove comments and folding whitespace and replace multiple-spaces with a single space + return s + .replace(/\([^()]*\)|[\n\t]/g, ' ') + .replace(/(\s\s+)/g, ' ') + .replace(/^\s\s*/, '') + .replace(/\s\s*$/, ''); + } + + function checkWeekday(weekdayStr, parsedInput, config) { + if (weekdayStr) { + // TODO: Replace the vanilla JS Date object with an independent day-of-week check. + var weekdayProvided = defaultLocaleWeekdaysShort.indexOf(weekdayStr), + weekdayActual = new Date( + parsedInput[0], + parsedInput[1], + parsedInput[2] + ).getDay(); + if (weekdayProvided !== weekdayActual) { + getParsingFlags(config).weekdayMismatch = true; + config._isValid = false; + return false; + } + } + return true; + } + + function calculateOffset(obsOffset, militaryOffset, numOffset) { + if (obsOffset) { + return obsOffsets[obsOffset]; + } else if (militaryOffset) { + // the only allowed military tz is Z + return 0; + } else { + var hm = parseInt(numOffset, 10), + m = hm % 100, + h = (hm - m) / 100; + return h * 60 + m; + } + } + + // date and time from ref 2822 format + function configFromRFC2822(config) { + var match = rfc2822.exec(preprocessRFC2822(config._i)), + parsedArray; + if (match) { + parsedArray = extractFromRFC2822Strings( + match[4], + match[3], + match[2], + match[5], + match[6], + match[7] + ); + if (!checkWeekday(match[1], parsedArray, config)) { + return; + } + + config._a = parsedArray; + config._tzm = calculateOffset(match[8], match[9], match[10]); + + config._d = createUTCDate.apply(null, config._a); + config._d.setUTCMinutes(config._d.getUTCMinutes() - config._tzm); + + getParsingFlags(config).rfc2822 = true; + } else { + config._isValid = false; + } + } + + // date from 1) ASP.NET, 2) ISO, 3) RFC 2822 formats, or 4) optional fallback if parsing isn't strict + function configFromString(config) { + var matched = aspNetJsonRegex.exec(config._i); + if (matched !== null) { + config._d = new Date(+matched[1]); + return; + } + + configFromISO(config); + if (config._isValid === false) { + delete config._isValid; + } else { + return; + } + + configFromRFC2822(config); + if (config._isValid === false) { + delete config._isValid; + } else { + return; + } + + if (config._strict) { + config._isValid = false; + } else { + // Final attempt, use Input Fallback + hooks.createFromInputFallback(config); + } + } + + hooks.createFromInputFallback = deprecate( + 'value provided is not in a recognized RFC2822 or ISO format. moment construction falls back to js Date(), ' + + 'which is not reliable across all browsers and versions. Non RFC2822/ISO date formats are ' + + 'discouraged. Please refer to http://momentjs.com/guides/#/warnings/js-date/ for more info.', + function (config) { + config._d = new Date(config._i + (config._useUTC ? ' UTC' : '')); + } + ); + + // Pick the first defined of two or three arguments. + function defaults(a, b, c) { + if (a != null) { + return a; + } + if (b != null) { + return b; + } + return c; + } + + function currentDateArray(config) { + // hooks is actually the exported moment object + var nowValue = new Date(hooks.now()); + if (config._useUTC) { + return [ + nowValue.getUTCFullYear(), + nowValue.getUTCMonth(), + nowValue.getUTCDate(), + ]; + } + return [nowValue.getFullYear(), nowValue.getMonth(), nowValue.getDate()]; + } + + // convert an array to a date. + // the array should mirror the parameters below + // note: all values past the year are optional and will default to the lowest possible value. + // [year, month, day , hour, minute, second, millisecond] + function configFromArray(config) { + var i, + date, + input = [], + currentDate, + expectedWeekday, + yearToUse; + + if (config._d) { + return; + } + + currentDate = currentDateArray(config); + + //compute day of the year from weeks and weekdays + if (config._w && config._a[DATE] == null && config._a[MONTH] == null) { + dayOfYearFromWeekInfo(config); + } + + //if the day of the year is set, figure out what it is + if (config._dayOfYear != null) { + yearToUse = defaults(config._a[YEAR], currentDate[YEAR]); + + if ( + config._dayOfYear > daysInYear(yearToUse) || + config._dayOfYear === 0 + ) { + getParsingFlags(config)._overflowDayOfYear = true; + } + + date = createUTCDate(yearToUse, 0, config._dayOfYear); + config._a[MONTH] = date.getUTCMonth(); + config._a[DATE] = date.getUTCDate(); + } + + // Default to current date. + // * if no year, month, day of month are given, default to today + // * if day of month is given, default month and year + // * if month is given, default only year + // * if year is given, don't default anything + for (i = 0; i < 3 && config._a[i] == null; ++i) { + config._a[i] = input[i] = currentDate[i]; + } + + // Zero out whatever was not defaulted, including time + for (; i < 7; i++) { + config._a[i] = input[i] = + config._a[i] == null ? (i === 2 ? 1 : 0) : config._a[i]; + } + + // Check for 24:00:00.000 + if ( + config._a[HOUR] === 24 && + config._a[MINUTE] === 0 && + config._a[SECOND] === 0 && + config._a[MILLISECOND] === 0 + ) { + config._nextDay = true; + config._a[HOUR] = 0; + } + + config._d = (config._useUTC ? createUTCDate : createDate).apply( + null, + input + ); + expectedWeekday = config._useUTC + ? config._d.getUTCDay() + : config._d.getDay(); + + // Apply timezone offset from input. The actual utcOffset can be changed + // with parseZone. + if (config._tzm != null) { + config._d.setUTCMinutes(config._d.getUTCMinutes() - config._tzm); + } + + if (config._nextDay) { + config._a[HOUR] = 24; + } + + // check for mismatching day of week + if ( + config._w && + typeof config._w.d !== 'undefined' && + config._w.d !== expectedWeekday + ) { + getParsingFlags(config).weekdayMismatch = true; + } + } + + function dayOfYearFromWeekInfo(config) { + var w, weekYear, week, weekday, dow, doy, temp, weekdayOverflow, curWeek; + + w = config._w; + if (w.GG != null || w.W != null || w.E != null) { + dow = 1; + doy = 4; + + // TODO: We need to take the current isoWeekYear, but that depends on + // how we interpret now (local, utc, fixed offset). So create + // a now version of current config (take local/utc/offset flags, and + // create now). + weekYear = defaults( + w.GG, + config._a[YEAR], + weekOfYear(createLocal(), 1, 4).year + ); + week = defaults(w.W, 1); + weekday = defaults(w.E, 1); + if (weekday < 1 || weekday > 7) { + weekdayOverflow = true; + } + } else { + dow = config._locale._week.dow; + doy = config._locale._week.doy; + + curWeek = weekOfYear(createLocal(), dow, doy); + + weekYear = defaults(w.gg, config._a[YEAR], curWeek.year); + + // Default to current week. + week = defaults(w.w, curWeek.week); + + if (w.d != null) { + // weekday -- low day numbers are considered next week + weekday = w.d; + if (weekday < 0 || weekday > 6) { + weekdayOverflow = true; + } + } else if (w.e != null) { + // local weekday -- counting starts from beginning of week + weekday = w.e + dow; + if (w.e < 0 || w.e > 6) { + weekdayOverflow = true; + } + } else { + // default to beginning of week + weekday = dow; + } + } + if (week < 1 || week > weeksInYear(weekYear, dow, doy)) { + getParsingFlags(config)._overflowWeeks = true; + } else if (weekdayOverflow != null) { + getParsingFlags(config)._overflowWeekday = true; + } else { + temp = dayOfYearFromWeeks(weekYear, week, weekday, dow, doy); + config._a[YEAR] = temp.year; + config._dayOfYear = temp.dayOfYear; + } + } + + // constant that refers to the ISO standard + hooks.ISO_8601 = function () {}; + + // constant that refers to the RFC 2822 form + hooks.RFC_2822 = function () {}; + + // date from string and format string + function configFromStringAndFormat(config) { + // TODO: Move this to another part of the creation flow to prevent circular deps + if (config._f === hooks.ISO_8601) { + configFromISO(config); + return; + } + if (config._f === hooks.RFC_2822) { + configFromRFC2822(config); + return; + } + config._a = []; + getParsingFlags(config).empty = true; + + // This array is used to make a Date, either with `new Date` or `Date.UTC` + var string = '' + config._i, + i, + parsedInput, + tokens, + token, + skipped, + stringLength = string.length, + totalParsedInputLength = 0, + era, + tokenLen; + + tokens = + expandFormat(config._f, config._locale).match(formattingTokens) || []; + tokenLen = tokens.length; + for (i = 0; i < tokenLen; i++) { + token = tokens[i]; + parsedInput = (string.match(getParseRegexForToken(token, config)) || + [])[0]; + if (parsedInput) { + skipped = string.substr(0, string.indexOf(parsedInput)); + if (skipped.length > 0) { + getParsingFlags(config).unusedInput.push(skipped); + } + string = string.slice( + string.indexOf(parsedInput) + parsedInput.length + ); + totalParsedInputLength += parsedInput.length; + } + // don't parse if it's not a known token + if (formatTokenFunctions[token]) { + if (parsedInput) { + getParsingFlags(config).empty = false; + } else { + getParsingFlags(config).unusedTokens.push(token); + } + addTimeToArrayFromToken(token, parsedInput, config); + } else if (config._strict && !parsedInput) { + getParsingFlags(config).unusedTokens.push(token); + } + } + + // add remaining unparsed input length to the string + getParsingFlags(config).charsLeftOver = + stringLength - totalParsedInputLength; + if (string.length > 0) { + getParsingFlags(config).unusedInput.push(string); + } + + // clear _12h flag if hour is <= 12 + if ( + config._a[HOUR] <= 12 && + getParsingFlags(config).bigHour === true && + config._a[HOUR] > 0 + ) { + getParsingFlags(config).bigHour = undefined; + } + + getParsingFlags(config).parsedDateParts = config._a.slice(0); + getParsingFlags(config).meridiem = config._meridiem; + // handle meridiem + config._a[HOUR] = meridiemFixWrap( + config._locale, + config._a[HOUR], + config._meridiem + ); + + // handle era + era = getParsingFlags(config).era; + if (era !== null) { + config._a[YEAR] = config._locale.erasConvertYear(era, config._a[YEAR]); + } + + configFromArray(config); + checkOverflow(config); + } + + function meridiemFixWrap(locale, hour, meridiem) { + var isPm; + + if (meridiem == null) { + // nothing to do + return hour; + } + if (locale.meridiemHour != null) { + return locale.meridiemHour(hour, meridiem); + } else if (locale.isPM != null) { + // Fallback + isPm = locale.isPM(meridiem); + if (isPm && hour < 12) { + hour += 12; + } + if (!isPm && hour === 12) { + hour = 0; + } + return hour; + } else { + // this is not supposed to happen + return hour; + } + } + + // date from string and array of format strings + function configFromStringAndArray(config) { + var tempConfig, + bestMoment, + scoreToBeat, + i, + currentScore, + validFormatFound, + bestFormatIsValid = false, + configfLen = config._f.length; + + if (configfLen === 0) { + getParsingFlags(config).invalidFormat = true; + config._d = new Date(NaN); + return; + } + + for (i = 0; i < configfLen; i++) { + currentScore = 0; + validFormatFound = false; + tempConfig = copyConfig({}, config); + if (config._useUTC != null) { + tempConfig._useUTC = config._useUTC; + } + tempConfig._f = config._f[i]; + configFromStringAndFormat(tempConfig); + + if (isValid(tempConfig)) { + validFormatFound = true; + } + + // if there is any input that was not parsed add a penalty for that format + currentScore += getParsingFlags(tempConfig).charsLeftOver; + + //or tokens + currentScore += getParsingFlags(tempConfig).unusedTokens.length * 10; + + getParsingFlags(tempConfig).score = currentScore; + + if (!bestFormatIsValid) { + if ( + scoreToBeat == null || + currentScore < scoreToBeat || + validFormatFound + ) { + scoreToBeat = currentScore; + bestMoment = tempConfig; + if (validFormatFound) { + bestFormatIsValid = true; + } + } + } else { + if (currentScore < scoreToBeat) { + scoreToBeat = currentScore; + bestMoment = tempConfig; + } + } + } + + extend(config, bestMoment || tempConfig); + } + + function configFromObject(config) { + if (config._d) { + return; + } + + var i = normalizeObjectUnits(config._i), + dayOrDate = i.day === undefined ? i.date : i.day; + config._a = map( + [i.year, i.month, dayOrDate, i.hour, i.minute, i.second, i.millisecond], + function (obj) { + return obj && parseInt(obj, 10); + } + ); + + configFromArray(config); + } + + function createFromConfig(config) { + var res = new Moment(checkOverflow(prepareConfig(config))); + if (res._nextDay) { + // Adding is smart enough around DST + res.add(1, 'd'); + res._nextDay = undefined; + } + + return res; + } + + function prepareConfig(config) { + var input = config._i, + format = config._f; + + config._locale = config._locale || getLocale(config._l); + + if (input === null || (format === undefined && input === '')) { + return createInvalid({ nullInput: true }); + } + + if (typeof input === 'string') { + config._i = input = config._locale.preparse(input); + } + + if (isMoment(input)) { + return new Moment(checkOverflow(input)); + } else if (isDate(input)) { + config._d = input; + } else if (isArray(format)) { + configFromStringAndArray(config); + } else if (format) { + configFromStringAndFormat(config); + } else { + configFromInput(config); + } + + if (!isValid(config)) { + config._d = null; + } + + return config; + } + + function configFromInput(config) { + var input = config._i; + if (isUndefined(input)) { + config._d = new Date(hooks.now()); + } else if (isDate(input)) { + config._d = new Date(input.valueOf()); + } else if (typeof input === 'string') { + configFromString(config); + } else if (isArray(input)) { + config._a = map(input.slice(0), function (obj) { + return parseInt(obj, 10); + }); + configFromArray(config); + } else if (isObject(input)) { + configFromObject(config); + } else if (isNumber(input)) { + // from milliseconds + config._d = new Date(input); + } else { + hooks.createFromInputFallback(config); + } + } + + function createLocalOrUTC(input, format, locale, strict, isUTC) { + var c = {}; + + if (format === true || format === false) { + strict = format; + format = undefined; + } + + if (locale === true || locale === false) { + strict = locale; + locale = undefined; + } + + if ( + (isObject(input) && isObjectEmpty(input)) || + (isArray(input) && input.length === 0) + ) { + input = undefined; + } + // object construction must be done this way. + // https://github.com/moment/moment/issues/1423 + c._isAMomentObject = true; + c._useUTC = c._isUTC = isUTC; + c._l = locale; + c._i = input; + c._f = format; + c._strict = strict; + + return createFromConfig(c); + } + + function createLocal(input, format, locale, strict) { + return createLocalOrUTC(input, format, locale, strict, false); + } + + var prototypeMin = deprecate( + 'moment().min is deprecated, use moment.max instead. http://momentjs.com/guides/#/warnings/min-max/', + function () { + var other = createLocal.apply(null, arguments); + if (this.isValid() && other.isValid()) { + return other < this ? this : other; + } else { + return createInvalid(); + } + } + ), + prototypeMax = deprecate( + 'moment().max is deprecated, use moment.min instead. http://momentjs.com/guides/#/warnings/min-max/', + function () { + var other = createLocal.apply(null, arguments); + if (this.isValid() && other.isValid()) { + return other > this ? this : other; + } else { + return createInvalid(); + } + } + ); + + // Pick a moment m from moments so that m[fn](other) is true for all + // other. This relies on the function fn to be transitive. + // + // moments should either be an array of moment objects or an array, whose + // first element is an array of moment objects. + function pickBy(fn, moments) { + var res, i; + if (moments.length === 1 && isArray(moments[0])) { + moments = moments[0]; + } + if (!moments.length) { + return createLocal(); + } + res = moments[0]; + for (i = 1; i < moments.length; ++i) { + if (!moments[i].isValid() || moments[i][fn](res)) { + res = moments[i]; + } + } + return res; + } + + // TODO: Use [].sort instead? + function min() { + var args = [].slice.call(arguments, 0); + + return pickBy('isBefore', args); + } + + function max() { + var args = [].slice.call(arguments, 0); + + return pickBy('isAfter', args); + } + + var now = function () { + return Date.now ? Date.now() : +new Date(); + }; + + var ordering = [ + 'year', + 'quarter', + 'month', + 'week', + 'day', + 'hour', + 'minute', + 'second', + 'millisecond', + ]; + + function isDurationValid(m) { + var key, + unitHasDecimal = false, + i, + orderLen = ordering.length; + for (key in m) { + if ( + hasOwnProp(m, key) && + !( + indexOf.call(ordering, key) !== -1 && + (m[key] == null || !isNaN(m[key])) + ) + ) { + return false; + } + } + + for (i = 0; i < orderLen; ++i) { + if (m[ordering[i]]) { + if (unitHasDecimal) { + return false; // only allow non-integers for smallest unit + } + if (parseFloat(m[ordering[i]]) !== toInt(m[ordering[i]])) { + unitHasDecimal = true; + } + } + } + + return true; + } + + function isValid$1() { + return this._isValid; + } + + function createInvalid$1() { + return createDuration(NaN); + } + + function Duration(duration) { + var normalizedInput = normalizeObjectUnits(duration), + years = normalizedInput.year || 0, + quarters = normalizedInput.quarter || 0, + months = normalizedInput.month || 0, + weeks = normalizedInput.week || normalizedInput.isoWeek || 0, + days = normalizedInput.day || 0, + hours = normalizedInput.hour || 0, + minutes = normalizedInput.minute || 0, + seconds = normalizedInput.second || 0, + milliseconds = normalizedInput.millisecond || 0; + + this._isValid = isDurationValid(normalizedInput); + + // representation for dateAddRemove + this._milliseconds = + +milliseconds + + seconds * 1e3 + // 1000 + minutes * 6e4 + // 1000 * 60 + hours * 1000 * 60 * 60; //using 1000 * 60 * 60 instead of 36e5 to avoid floating point rounding errors https://github.com/moment/moment/issues/2978 + // Because of dateAddRemove treats 24 hours as different from a + // day when working around DST, we need to store them separately + this._days = +days + weeks * 7; + // It is impossible to translate months into days without knowing + // which months you are are talking about, so we have to store + // it separately. + this._months = +months + quarters * 3 + years * 12; + + this._data = {}; + + this._locale = getLocale(); + + this._bubble(); + } + + function isDuration(obj) { + return obj instanceof Duration; + } + + function absRound(number) { + if (number < 0) { + return Math.round(-1 * number) * -1; + } else { + return Math.round(number); + } + } + + // compare two arrays, return the number of differences + function compareArrays(array1, array2, dontConvert) { + var len = Math.min(array1.length, array2.length), + lengthDiff = Math.abs(array1.length - array2.length), + diffs = 0, + i; + for (i = 0; i < len; i++) { + if ( + (dontConvert && array1[i] !== array2[i]) || + (!dontConvert && toInt(array1[i]) !== toInt(array2[i])) + ) { + diffs++; + } + } + return diffs + lengthDiff; + } + + // FORMATTING + + function offset(token, separator) { + addFormatToken(token, 0, 0, function () { + var offset = this.utcOffset(), + sign = '+'; + if (offset < 0) { + offset = -offset; + sign = '-'; + } + return ( + sign + + zeroFill(~~(offset / 60), 2) + + separator + + zeroFill(~~offset % 60, 2) + ); + }); + } + + offset('Z', ':'); + offset('ZZ', ''); + + // PARSING + + addRegexToken('Z', matchShortOffset); + addRegexToken('ZZ', matchShortOffset); + addParseToken(['Z', 'ZZ'], function (input, array, config) { + config._useUTC = true; + config._tzm = offsetFromString(matchShortOffset, input); + }); + + // HELPERS + + // timezone chunker + // '+10:00' > ['10', '00'] + // '-1530' > ['-15', '30'] + var chunkOffset = /([\+\-]|\d\d)/gi; + + function offsetFromString(matcher, string) { + var matches = (string || '').match(matcher), + chunk, + parts, + minutes; + + if (matches === null) { + return null; + } + + chunk = matches[matches.length - 1] || []; + parts = (chunk + '').match(chunkOffset) || ['-', 0, 0]; + minutes = +(parts[1] * 60) + toInt(parts[2]); + + return minutes === 0 ? 0 : parts[0] === '+' ? minutes : -minutes; + } + + // Return a moment from input, that is local/utc/zone equivalent to model. + function cloneWithOffset(input, model) { + var res, diff; + if (model._isUTC) { + res = model.clone(); + diff = + (isMoment(input) || isDate(input) + ? input.valueOf() + : createLocal(input).valueOf()) - res.valueOf(); + // Use low-level api, because this fn is low-level api. + res._d.setTime(res._d.valueOf() + diff); + hooks.updateOffset(res, false); + return res; + } else { + return createLocal(input).local(); + } + } + + function getDateOffset(m) { + // On Firefox.24 Date#getTimezoneOffset returns a floating point. + // https://github.com/moment/moment/pull/1871 + return -Math.round(m._d.getTimezoneOffset()); + } + + // HOOKS + + // This function will be called whenever a moment is mutated. + // It is intended to keep the offset in sync with the timezone. + hooks.updateOffset = function () {}; + + // MOMENTS + + // keepLocalTime = true means only change the timezone, without + // affecting the local hour. So 5:31:26 +0300 --[utcOffset(2, true)]--> + // 5:31:26 +0200 It is possible that 5:31:26 doesn't exist with offset + // +0200, so we adjust the time as needed, to be valid. + // + // Keeping the time actually adds/subtracts (one hour) + // from the actual represented time. That is why we call updateOffset + // a second time. In case it wants us to change the offset again + // _changeInProgress == true case, then we have to adjust, because + // there is no such time in the given timezone. + function getSetOffset(input, keepLocalTime, keepMinutes) { + var offset = this._offset || 0, + localAdjust; + if (!this.isValid()) { + return input != null ? this : NaN; + } + if (input != null) { + if (typeof input === 'string') { + input = offsetFromString(matchShortOffset, input); + if (input === null) { + return this; + } + } else if (Math.abs(input) < 16 && !keepMinutes) { + input = input * 60; + } + if (!this._isUTC && keepLocalTime) { + localAdjust = getDateOffset(this); + } + this._offset = input; + this._isUTC = true; + if (localAdjust != null) { + this.add(localAdjust, 'm'); + } + if (offset !== input) { + if (!keepLocalTime || this._changeInProgress) { + addSubtract( + this, + createDuration(input - offset, 'm'), + 1, + false + ); + } else if (!this._changeInProgress) { + this._changeInProgress = true; + hooks.updateOffset(this, true); + this._changeInProgress = null; + } + } + return this; + } else { + return this._isUTC ? offset : getDateOffset(this); + } + } + + function getSetZone(input, keepLocalTime) { + if (input != null) { + if (typeof input !== 'string') { + input = -input; + } + + this.utcOffset(input, keepLocalTime); + + return this; + } else { + return -this.utcOffset(); + } + } + + function setOffsetToUTC(keepLocalTime) { + return this.utcOffset(0, keepLocalTime); + } + + function setOffsetToLocal(keepLocalTime) { + if (this._isUTC) { + this.utcOffset(0, keepLocalTime); + this._isUTC = false; + + if (keepLocalTime) { + this.subtract(getDateOffset(this), 'm'); + } + } + return this; + } + + function setOffsetToParsedOffset() { + if (this._tzm != null) { + this.utcOffset(this._tzm, false, true); + } else if (typeof this._i === 'string') { + var tZone = offsetFromString(matchOffset, this._i); + if (tZone != null) { + this.utcOffset(tZone); + } else { + this.utcOffset(0, true); + } + } + return this; + } + + function hasAlignedHourOffset(input) { + if (!this.isValid()) { + return false; + } + input = input ? createLocal(input).utcOffset() : 0; + + return (this.utcOffset() - input) % 60 === 0; + } + + function isDaylightSavingTime() { + return ( + this.utcOffset() > this.clone().month(0).utcOffset() || + this.utcOffset() > this.clone().month(5).utcOffset() + ); + } + + function isDaylightSavingTimeShifted() { + if (!isUndefined(this._isDSTShifted)) { + return this._isDSTShifted; + } + + var c = {}, + other; + + copyConfig(c, this); + c = prepareConfig(c); + + if (c._a) { + other = c._isUTC ? createUTC(c._a) : createLocal(c._a); + this._isDSTShifted = + this.isValid() && compareArrays(c._a, other.toArray()) > 0; + } else { + this._isDSTShifted = false; + } + + return this._isDSTShifted; + } + + function isLocal() { + return this.isValid() ? !this._isUTC : false; + } + + function isUtcOffset() { + return this.isValid() ? this._isUTC : false; + } + + function isUtc() { + return this.isValid() ? this._isUTC && this._offset === 0 : false; + } + + // ASP.NET json date format regex + var aspNetRegex = /^(-|\+)?(?:(\d*)[. ])?(\d+):(\d+)(?::(\d+)(\.\d*)?)?$/, + // from http://docs.closure-library.googlecode.com/git/closure_goog_date_date.js.source.html + // somewhat more in line with 4.4.3.2 2004 spec, but allows decimal anywhere + // and further modified to allow for strings containing both week and day + isoRegex = + /^(-|\+)?P(?:([-+]?[0-9,.]*)Y)?(?:([-+]?[0-9,.]*)M)?(?:([-+]?[0-9,.]*)W)?(?:([-+]?[0-9,.]*)D)?(?:T(?:([-+]?[0-9,.]*)H)?(?:([-+]?[0-9,.]*)M)?(?:([-+]?[0-9,.]*)S)?)?$/; + + function createDuration(input, key) { + var duration = input, + // matching against regexp is expensive, do it on demand + match = null, + sign, + ret, + diffRes; + + if (isDuration(input)) { + duration = { + ms: input._milliseconds, + d: input._days, + M: input._months, + }; + } else if (isNumber(input) || !isNaN(+input)) { + duration = {}; + if (key) { + duration[key] = +input; + } else { + duration.milliseconds = +input; + } + } else if ((match = aspNetRegex.exec(input))) { + sign = match[1] === '-' ? -1 : 1; + duration = { + y: 0, + d: toInt(match[DATE]) * sign, + h: toInt(match[HOUR]) * sign, + m: toInt(match[MINUTE]) * sign, + s: toInt(match[SECOND]) * sign, + ms: toInt(absRound(match[MILLISECOND] * 1000)) * sign, // the millisecond decimal point is included in the match + }; + } else if ((match = isoRegex.exec(input))) { + sign = match[1] === '-' ? -1 : 1; + duration = { + y: parseIso(match[2], sign), + M: parseIso(match[3], sign), + w: parseIso(match[4], sign), + d: parseIso(match[5], sign), + h: parseIso(match[6], sign), + m: parseIso(match[7], sign), + s: parseIso(match[8], sign), + }; + } else if (duration == null) { + // checks for null or undefined + duration = {}; + } else if ( + typeof duration === 'object' && + ('from' in duration || 'to' in duration) + ) { + diffRes = momentsDifference( + createLocal(duration.from), + createLocal(duration.to) + ); + + duration = {}; + duration.ms = diffRes.milliseconds; + duration.M = diffRes.months; + } + + ret = new Duration(duration); + + if (isDuration(input) && hasOwnProp(input, '_locale')) { + ret._locale = input._locale; + } + + if (isDuration(input) && hasOwnProp(input, '_isValid')) { + ret._isValid = input._isValid; + } + + return ret; + } + + createDuration.fn = Duration.prototype; + createDuration.invalid = createInvalid$1; + + function parseIso(inp, sign) { + // We'd normally use ~~inp for this, but unfortunately it also + // converts floats to ints. + // inp may be undefined, so careful calling replace on it. + var res = inp && parseFloat(inp.replace(',', '.')); + // apply sign while we're at it + return (isNaN(res) ? 0 : res) * sign; + } + + function positiveMomentsDifference(base, other) { + var res = {}; + + res.months = + other.month() - base.month() + (other.year() - base.year()) * 12; + if (base.clone().add(res.months, 'M').isAfter(other)) { + --res.months; + } + + res.milliseconds = +other - +base.clone().add(res.months, 'M'); + + return res; + } + + function momentsDifference(base, other) { + var res; + if (!(base.isValid() && other.isValid())) { + return { milliseconds: 0, months: 0 }; + } + + other = cloneWithOffset(other, base); + if (base.isBefore(other)) { + res = positiveMomentsDifference(base, other); + } else { + res = positiveMomentsDifference(other, base); + res.milliseconds = -res.milliseconds; + res.months = -res.months; + } + + return res; + } + + // TODO: remove 'name' arg after deprecation is removed + function createAdder(direction, name) { + return function (val, period) { + var dur, tmp; + //invert the arguments, but complain about it + if (period !== null && !isNaN(+period)) { + deprecateSimple( + name, + 'moment().' + + name + + '(period, number) is deprecated. Please use moment().' + + name + + '(number, period). ' + + 'See http://momentjs.com/guides/#/warnings/add-inverted-param/ for more info.' + ); + tmp = val; + val = period; + period = tmp; + } + + dur = createDuration(val, period); + addSubtract(this, dur, direction); + return this; + }; + } + + function addSubtract(mom, duration, isAdding, updateOffset) { + var milliseconds = duration._milliseconds, + days = absRound(duration._days), + months = absRound(duration._months); + + if (!mom.isValid()) { + // No op + return; + } + + updateOffset = updateOffset == null ? true : updateOffset; + + if (months) { + setMonth(mom, get(mom, 'Month') + months * isAdding); + } + if (days) { + set$1(mom, 'Date', get(mom, 'Date') + days * isAdding); + } + if (milliseconds) { + mom._d.setTime(mom._d.valueOf() + milliseconds * isAdding); + } + if (updateOffset) { + hooks.updateOffset(mom, days || months); + } + } + + var add = createAdder(1, 'add'), + subtract = createAdder(-1, 'subtract'); + + function isString(input) { + return typeof input === 'string' || input instanceof String; + } + + // type MomentInput = Moment | Date | string | number | (number | string)[] | MomentInputObject | void; // null | undefined + function isMomentInput(input) { + return ( + isMoment(input) || + isDate(input) || + isString(input) || + isNumber(input) || + isNumberOrStringArray(input) || + isMomentInputObject(input) || + input === null || + input === undefined + ); + } + + function isMomentInputObject(input) { + var objectTest = isObject(input) && !isObjectEmpty(input), + propertyTest = false, + properties = [ + 'years', + 'year', + 'y', + 'months', + 'month', + 'M', + 'days', + 'day', + 'd', + 'dates', + 'date', + 'D', + 'hours', + 'hour', + 'h', + 'minutes', + 'minute', + 'm', + 'seconds', + 'second', + 's', + 'milliseconds', + 'millisecond', + 'ms', + ], + i, + property, + propertyLen = properties.length; + + for (i = 0; i < propertyLen; i += 1) { + property = properties[i]; + propertyTest = propertyTest || hasOwnProp(input, property); + } + + return objectTest && propertyTest; + } + + function isNumberOrStringArray(input) { + var arrayTest = isArray(input), + dataTypeTest = false; + if (arrayTest) { + dataTypeTest = + input.filter(function (item) { + return !isNumber(item) && isString(input); + }).length === 0; + } + return arrayTest && dataTypeTest; + } + + function isCalendarSpec(input) { + var objectTest = isObject(input) && !isObjectEmpty(input), + propertyTest = false, + properties = [ + 'sameDay', + 'nextDay', + 'lastDay', + 'nextWeek', + 'lastWeek', + 'sameElse', + ], + i, + property; + + for (i = 0; i < properties.length; i += 1) { + property = properties[i]; + propertyTest = propertyTest || hasOwnProp(input, property); + } + + return objectTest && propertyTest; + } + + function getCalendarFormat(myMoment, now) { + var diff = myMoment.diff(now, 'days', true); + return diff < -6 + ? 'sameElse' + : diff < -1 + ? 'lastWeek' + : diff < 0 + ? 'lastDay' + : diff < 1 + ? 'sameDay' + : diff < 2 + ? 'nextDay' + : diff < 7 + ? 'nextWeek' + : 'sameElse'; + } + + function calendar$1(time, formats) { + // Support for single parameter, formats only overload to the calendar function + if (arguments.length === 1) { + if (!arguments[0]) { + time = undefined; + formats = undefined; + } else if (isMomentInput(arguments[0])) { + time = arguments[0]; + formats = undefined; + } else if (isCalendarSpec(arguments[0])) { + formats = arguments[0]; + time = undefined; + } + } + // We want to compare the start of today, vs this. + // Getting start-of-today depends on whether we're local/utc/offset or not. + var now = time || createLocal(), + sod = cloneWithOffset(now, this).startOf('day'), + format = hooks.calendarFormat(this, sod) || 'sameElse', + output = + formats && + (isFunction(formats[format]) + ? formats[format].call(this, now) + : formats[format]); + + return this.format( + output || this.localeData().calendar(format, this, createLocal(now)) + ); + } + + function clone() { + return new Moment(this); + } + + function isAfter(input, units) { + var localInput = isMoment(input) ? input : createLocal(input); + if (!(this.isValid() && localInput.isValid())) { + return false; + } + units = normalizeUnits(units) || 'millisecond'; + if (units === 'millisecond') { + return this.valueOf() > localInput.valueOf(); + } else { + return localInput.valueOf() < this.clone().startOf(units).valueOf(); + } + } + + function isBefore(input, units) { + var localInput = isMoment(input) ? input : createLocal(input); + if (!(this.isValid() && localInput.isValid())) { + return false; + } + units = normalizeUnits(units) || 'millisecond'; + if (units === 'millisecond') { + return this.valueOf() < localInput.valueOf(); + } else { + return this.clone().endOf(units).valueOf() < localInput.valueOf(); + } + } + + function isBetween(from, to, units, inclusivity) { + var localFrom = isMoment(from) ? from : createLocal(from), + localTo = isMoment(to) ? to : createLocal(to); + if (!(this.isValid() && localFrom.isValid() && localTo.isValid())) { + return false; + } + inclusivity = inclusivity || '()'; + return ( + (inclusivity[0] === '(' + ? this.isAfter(localFrom, units) + : !this.isBefore(localFrom, units)) && + (inclusivity[1] === ')' + ? this.isBefore(localTo, units) + : !this.isAfter(localTo, units)) + ); + } + + function isSame(input, units) { + var localInput = isMoment(input) ? input : createLocal(input), + inputMs; + if (!(this.isValid() && localInput.isValid())) { + return false; + } + units = normalizeUnits(units) || 'millisecond'; + if (units === 'millisecond') { + return this.valueOf() === localInput.valueOf(); + } else { + inputMs = localInput.valueOf(); + return ( + this.clone().startOf(units).valueOf() <= inputMs && + inputMs <= this.clone().endOf(units).valueOf() + ); + } + } + + function isSameOrAfter(input, units) { + return this.isSame(input, units) || this.isAfter(input, units); + } + + function isSameOrBefore(input, units) { + return this.isSame(input, units) || this.isBefore(input, units); + } + + function diff(input, units, asFloat) { + var that, zoneDelta, output; + + if (!this.isValid()) { + return NaN; + } + + that = cloneWithOffset(input, this); + + if (!that.isValid()) { + return NaN; + } + + zoneDelta = (that.utcOffset() - this.utcOffset()) * 6e4; + + units = normalizeUnits(units); + + switch (units) { + case 'year': + output = monthDiff(this, that) / 12; + break; + case 'month': + output = monthDiff(this, that); + break; + case 'quarter': + output = monthDiff(this, that) / 3; + break; + case 'second': + output = (this - that) / 1e3; + break; // 1000 + case 'minute': + output = (this - that) / 6e4; + break; // 1000 * 60 + case 'hour': + output = (this - that) / 36e5; + break; // 1000 * 60 * 60 + case 'day': + output = (this - that - zoneDelta) / 864e5; + break; // 1000 * 60 * 60 * 24, negate dst + case 'week': + output = (this - that - zoneDelta) / 6048e5; + break; // 1000 * 60 * 60 * 24 * 7, negate dst + default: + output = this - that; + } + + return asFloat ? output : absFloor(output); + } + + function monthDiff(a, b) { + if (a.date() < b.date()) { + // end-of-month calculations work correct when the start month has more + // days than the end month. + return -monthDiff(b, a); + } + // difference in months + var wholeMonthDiff = (b.year() - a.year()) * 12 + (b.month() - a.month()), + // b is in (anchor - 1 month, anchor + 1 month) + anchor = a.clone().add(wholeMonthDiff, 'months'), + anchor2, + adjust; + + if (b - anchor < 0) { + anchor2 = a.clone().add(wholeMonthDiff - 1, 'months'); + // linear across the month + adjust = (b - anchor) / (anchor - anchor2); + } else { + anchor2 = a.clone().add(wholeMonthDiff + 1, 'months'); + // linear across the month + adjust = (b - anchor) / (anchor2 - anchor); + } + + //check for negative zero, return zero if negative zero + return -(wholeMonthDiff + adjust) || 0; + } + + hooks.defaultFormat = 'YYYY-MM-DDTHH:mm:ssZ'; + hooks.defaultFormatUtc = 'YYYY-MM-DDTHH:mm:ss[Z]'; + + function toString() { + return this.clone().locale('en').format('ddd MMM DD YYYY HH:mm:ss [GMT]ZZ'); + } + + function toISOString(keepOffset) { + if (!this.isValid()) { + return null; + } + var utc = keepOffset !== true, + m = utc ? this.clone().utc() : this; + if (m.year() < 0 || m.year() > 9999) { + return formatMoment( + m, + utc + ? 'YYYYYY-MM-DD[T]HH:mm:ss.SSS[Z]' + : 'YYYYYY-MM-DD[T]HH:mm:ss.SSSZ' + ); + } + if (isFunction(Date.prototype.toISOString)) { + // native implementation is ~50x faster, use it when we can + if (utc) { + return this.toDate().toISOString(); + } else { + return new Date(this.valueOf() + this.utcOffset() * 60 * 1000) + .toISOString() + .replace('Z', formatMoment(m, 'Z')); + } + } + return formatMoment( + m, + utc ? 'YYYY-MM-DD[T]HH:mm:ss.SSS[Z]' : 'YYYY-MM-DD[T]HH:mm:ss.SSSZ' + ); + } + + /** + * Return a human readable representation of a moment that can + * also be evaluated to get a new moment which is the same + * + * @link https://nodejs.org/dist/latest/docs/api/util.html#util_custom_inspect_function_on_objects + */ + function inspect() { + if (!this.isValid()) { + return 'moment.invalid(/* ' + this._i + ' */)'; + } + var func = 'moment', + zone = '', + prefix, + year, + datetime, + suffix; + if (!this.isLocal()) { + func = this.utcOffset() === 0 ? 'moment.utc' : 'moment.parseZone'; + zone = 'Z'; + } + prefix = '[' + func + '("]'; + year = 0 <= this.year() && this.year() <= 9999 ? 'YYYY' : 'YYYYYY'; + datetime = '-MM-DD[T]HH:mm:ss.SSS'; + suffix = zone + '[")]'; + + return this.format(prefix + year + datetime + suffix); + } + + function format(inputString) { + if (!inputString) { + inputString = this.isUtc() + ? hooks.defaultFormatUtc + : hooks.defaultFormat; + } + var output = formatMoment(this, inputString); + return this.localeData().postformat(output); + } + + function from(time, withoutSuffix) { + if ( + this.isValid() && + ((isMoment(time) && time.isValid()) || createLocal(time).isValid()) + ) { + return createDuration({ to: this, from: time }) + .locale(this.locale()) + .humanize(!withoutSuffix); + } else { + return this.localeData().invalidDate(); + } + } + + function fromNow(withoutSuffix) { + return this.from(createLocal(), withoutSuffix); + } + + function to(time, withoutSuffix) { + if ( + this.isValid() && + ((isMoment(time) && time.isValid()) || createLocal(time).isValid()) + ) { + return createDuration({ from: this, to: time }) + .locale(this.locale()) + .humanize(!withoutSuffix); + } else { + return this.localeData().invalidDate(); + } + } + + function toNow(withoutSuffix) { + return this.to(createLocal(), withoutSuffix); + } + + // If passed a locale key, it will set the locale for this + // instance. Otherwise, it will return the locale configuration + // variables for this instance. + function locale(key) { + var newLocaleData; + + if (key === undefined) { + return this._locale._abbr; + } else { + newLocaleData = getLocale(key); + if (newLocaleData != null) { + this._locale = newLocaleData; + } + return this; + } + } + + var lang = deprecate( + 'moment().lang() is deprecated. Instead, use moment().localeData() to get the language configuration. Use moment().locale() to change languages.', + function (key) { + if (key === undefined) { + return this.localeData(); + } else { + return this.locale(key); + } + } + ); + + function localeData() { + return this._locale; + } + + var MS_PER_SECOND = 1000, + MS_PER_MINUTE = 60 * MS_PER_SECOND, + MS_PER_HOUR = 60 * MS_PER_MINUTE, + MS_PER_400_YEARS = (365 * 400 + 97) * 24 * MS_PER_HOUR; + + // actual modulo - handles negative numbers (for dates before 1970): + function mod$1(dividend, divisor) { + return ((dividend % divisor) + divisor) % divisor; + } + + function localStartOfDate(y, m, d) { + // the date constructor remaps years 0-99 to 1900-1999 + if (y < 100 && y >= 0) { + // preserve leap years using a full 400 year cycle, then reset + return new Date(y + 400, m, d) - MS_PER_400_YEARS; + } else { + return new Date(y, m, d).valueOf(); + } + } + + function utcStartOfDate(y, m, d) { + // Date.UTC remaps years 0-99 to 1900-1999 + if (y < 100 && y >= 0) { + // preserve leap years using a full 400 year cycle, then reset + return Date.UTC(y + 400, m, d) - MS_PER_400_YEARS; + } else { + return Date.UTC(y, m, d); + } + } + + function startOf(units) { + var time, startOfDate; + units = normalizeUnits(units); + if (units === undefined || units === 'millisecond' || !this.isValid()) { + return this; + } + + startOfDate = this._isUTC ? utcStartOfDate : localStartOfDate; + + switch (units) { + case 'year': + time = startOfDate(this.year(), 0, 1); + break; + case 'quarter': + time = startOfDate( + this.year(), + this.month() - (this.month() % 3), + 1 + ); + break; + case 'month': + time = startOfDate(this.year(), this.month(), 1); + break; + case 'week': + time = startOfDate( + this.year(), + this.month(), + this.date() - this.weekday() + ); + break; + case 'isoWeek': + time = startOfDate( + this.year(), + this.month(), + this.date() - (this.isoWeekday() - 1) + ); + break; + case 'day': + case 'date': + time = startOfDate(this.year(), this.month(), this.date()); + break; + case 'hour': + time = this._d.valueOf(); + time -= mod$1( + time + (this._isUTC ? 0 : this.utcOffset() * MS_PER_MINUTE), + MS_PER_HOUR + ); + break; + case 'minute': + time = this._d.valueOf(); + time -= mod$1(time, MS_PER_MINUTE); + break; + case 'second': + time = this._d.valueOf(); + time -= mod$1(time, MS_PER_SECOND); + break; + } + + this._d.setTime(time); + hooks.updateOffset(this, true); + return this; + } + + function endOf(units) { + var time, startOfDate; + units = normalizeUnits(units); + if (units === undefined || units === 'millisecond' || !this.isValid()) { + return this; + } + + startOfDate = this._isUTC ? utcStartOfDate : localStartOfDate; + + switch (units) { + case 'year': + time = startOfDate(this.year() + 1, 0, 1) - 1; + break; + case 'quarter': + time = + startOfDate( + this.year(), + this.month() - (this.month() % 3) + 3, + 1 + ) - 1; + break; + case 'month': + time = startOfDate(this.year(), this.month() + 1, 1) - 1; + break; + case 'week': + time = + startOfDate( + this.year(), + this.month(), + this.date() - this.weekday() + 7 + ) - 1; + break; + case 'isoWeek': + time = + startOfDate( + this.year(), + this.month(), + this.date() - (this.isoWeekday() - 1) + 7 + ) - 1; + break; + case 'day': + case 'date': + time = startOfDate(this.year(), this.month(), this.date() + 1) - 1; + break; + case 'hour': + time = this._d.valueOf(); + time += + MS_PER_HOUR - + mod$1( + time + (this._isUTC ? 0 : this.utcOffset() * MS_PER_MINUTE), + MS_PER_HOUR + ) - + 1; + break; + case 'minute': + time = this._d.valueOf(); + time += MS_PER_MINUTE - mod$1(time, MS_PER_MINUTE) - 1; + break; + case 'second': + time = this._d.valueOf(); + time += MS_PER_SECOND - mod$1(time, MS_PER_SECOND) - 1; + break; + } + + this._d.setTime(time); + hooks.updateOffset(this, true); + return this; + } + + function valueOf() { + return this._d.valueOf() - (this._offset || 0) * 60000; + } + + function unix() { + return Math.floor(this.valueOf() / 1000); + } + + function toDate() { + return new Date(this.valueOf()); + } + + function toArray() { + var m = this; + return [ + m.year(), + m.month(), + m.date(), + m.hour(), + m.minute(), + m.second(), + m.millisecond(), + ]; + } + + function toObject() { + var m = this; + return { + years: m.year(), + months: m.month(), + date: m.date(), + hours: m.hours(), + minutes: m.minutes(), + seconds: m.seconds(), + milliseconds: m.milliseconds(), + }; + } + + function toJSON() { + // new Date(NaN).toJSON() === null + return this.isValid() ? this.toISOString() : null; + } + + function isValid$2() { + return isValid(this); + } + + function parsingFlags() { + return extend({}, getParsingFlags(this)); + } + + function invalidAt() { + return getParsingFlags(this).overflow; + } + + function creationData() { + return { + input: this._i, + format: this._f, + locale: this._locale, + isUTC: this._isUTC, + strict: this._strict, + }; + } + + addFormatToken('N', 0, 0, 'eraAbbr'); + addFormatToken('NN', 0, 0, 'eraAbbr'); + addFormatToken('NNN', 0, 0, 'eraAbbr'); + addFormatToken('NNNN', 0, 0, 'eraName'); + addFormatToken('NNNNN', 0, 0, 'eraNarrow'); + + addFormatToken('y', ['y', 1], 'yo', 'eraYear'); + addFormatToken('y', ['yy', 2], 0, 'eraYear'); + addFormatToken('y', ['yyy', 3], 0, 'eraYear'); + addFormatToken('y', ['yyyy', 4], 0, 'eraYear'); + + addRegexToken('N', matchEraAbbr); + addRegexToken('NN', matchEraAbbr); + addRegexToken('NNN', matchEraAbbr); + addRegexToken('NNNN', matchEraName); + addRegexToken('NNNNN', matchEraNarrow); + + addParseToken( + ['N', 'NN', 'NNN', 'NNNN', 'NNNNN'], + function (input, array, config, token) { + var era = config._locale.erasParse(input, token, config._strict); + if (era) { + getParsingFlags(config).era = era; + } else { + getParsingFlags(config).invalidEra = input; + } + } + ); + + addRegexToken('y', matchUnsigned); + addRegexToken('yy', matchUnsigned); + addRegexToken('yyy', matchUnsigned); + addRegexToken('yyyy', matchUnsigned); + addRegexToken('yo', matchEraYearOrdinal); + + addParseToken(['y', 'yy', 'yyy', 'yyyy'], YEAR); + addParseToken(['yo'], function (input, array, config, token) { + var match; + if (config._locale._eraYearOrdinalRegex) { + match = input.match(config._locale._eraYearOrdinalRegex); + } + + if (config._locale.eraYearOrdinalParse) { + array[YEAR] = config._locale.eraYearOrdinalParse(input, match); + } else { + array[YEAR] = parseInt(input, 10); + } + }); + + function localeEras(m, format) { + var i, + l, + date, + eras = this._eras || getLocale('en')._eras; + for (i = 0, l = eras.length; i < l; ++i) { + switch (typeof eras[i].since) { + case 'string': + // truncate time + date = hooks(eras[i].since).startOf('day'); + eras[i].since = date.valueOf(); + break; + } + + switch (typeof eras[i].until) { + case 'undefined': + eras[i].until = +Infinity; + break; + case 'string': + // truncate time + date = hooks(eras[i].until).startOf('day').valueOf(); + eras[i].until = date.valueOf(); + break; + } + } + return eras; + } + + function localeErasParse(eraName, format, strict) { + var i, + l, + eras = this.eras(), + name, + abbr, + narrow; + eraName = eraName.toUpperCase(); + + for (i = 0, l = eras.length; i < l; ++i) { + name = eras[i].name.toUpperCase(); + abbr = eras[i].abbr.toUpperCase(); + narrow = eras[i].narrow.toUpperCase(); + + if (strict) { + switch (format) { + case 'N': + case 'NN': + case 'NNN': + if (abbr === eraName) { + return eras[i]; + } + break; + + case 'NNNN': + if (name === eraName) { + return eras[i]; + } + break; + + case 'NNNNN': + if (narrow === eraName) { + return eras[i]; + } + break; + } + } else if ([name, abbr, narrow].indexOf(eraName) >= 0) { + return eras[i]; + } + } + } + + function localeErasConvertYear(era, year) { + var dir = era.since <= era.until ? +1 : -1; + if (year === undefined) { + return hooks(era.since).year(); + } else { + return hooks(era.since).year() + (year - era.offset) * dir; + } + } + + function getEraName() { + var i, + l, + val, + eras = this.localeData().eras(); + for (i = 0, l = eras.length; i < l; ++i) { + // truncate time + val = this.clone().startOf('day').valueOf(); + + if (eras[i].since <= val && val <= eras[i].until) { + return eras[i].name; + } + if (eras[i].until <= val && val <= eras[i].since) { + return eras[i].name; + } + } + + return ''; + } + + function getEraNarrow() { + var i, + l, + val, + eras = this.localeData().eras(); + for (i = 0, l = eras.length; i < l; ++i) { + // truncate time + val = this.clone().startOf('day').valueOf(); + + if (eras[i].since <= val && val <= eras[i].until) { + return eras[i].narrow; + } + if (eras[i].until <= val && val <= eras[i].since) { + return eras[i].narrow; + } + } + + return ''; + } + + function getEraAbbr() { + var i, + l, + val, + eras = this.localeData().eras(); + for (i = 0, l = eras.length; i < l; ++i) { + // truncate time + val = this.clone().startOf('day').valueOf(); + + if (eras[i].since <= val && val <= eras[i].until) { + return eras[i].abbr; + } + if (eras[i].until <= val && val <= eras[i].since) { + return eras[i].abbr; + } + } + + return ''; + } + + function getEraYear() { + var i, + l, + dir, + val, + eras = this.localeData().eras(); + for (i = 0, l = eras.length; i < l; ++i) { + dir = eras[i].since <= eras[i].until ? +1 : -1; + + // truncate time + val = this.clone().startOf('day').valueOf(); + + if ( + (eras[i].since <= val && val <= eras[i].until) || + (eras[i].until <= val && val <= eras[i].since) + ) { + return ( + (this.year() - hooks(eras[i].since).year()) * dir + + eras[i].offset + ); + } + } + + return this.year(); + } + + function erasNameRegex(isStrict) { + if (!hasOwnProp(this, '_erasNameRegex')) { + computeErasParse.call(this); + } + return isStrict ? this._erasNameRegex : this._erasRegex; + } + + function erasAbbrRegex(isStrict) { + if (!hasOwnProp(this, '_erasAbbrRegex')) { + computeErasParse.call(this); + } + return isStrict ? this._erasAbbrRegex : this._erasRegex; + } + + function erasNarrowRegex(isStrict) { + if (!hasOwnProp(this, '_erasNarrowRegex')) { + computeErasParse.call(this); + } + return isStrict ? this._erasNarrowRegex : this._erasRegex; + } + + function matchEraAbbr(isStrict, locale) { + return locale.erasAbbrRegex(isStrict); + } + + function matchEraName(isStrict, locale) { + return locale.erasNameRegex(isStrict); + } + + function matchEraNarrow(isStrict, locale) { + return locale.erasNarrowRegex(isStrict); + } + + function matchEraYearOrdinal(isStrict, locale) { + return locale._eraYearOrdinalRegex || matchUnsigned; + } + + function computeErasParse() { + var abbrPieces = [], + namePieces = [], + narrowPieces = [], + mixedPieces = [], + i, + l, + eras = this.eras(); + + for (i = 0, l = eras.length; i < l; ++i) { + namePieces.push(regexEscape(eras[i].name)); + abbrPieces.push(regexEscape(eras[i].abbr)); + narrowPieces.push(regexEscape(eras[i].narrow)); + + mixedPieces.push(regexEscape(eras[i].name)); + mixedPieces.push(regexEscape(eras[i].abbr)); + mixedPieces.push(regexEscape(eras[i].narrow)); + } + + this._erasRegex = new RegExp('^(' + mixedPieces.join('|') + ')', 'i'); + this._erasNameRegex = new RegExp('^(' + namePieces.join('|') + ')', 'i'); + this._erasAbbrRegex = new RegExp('^(' + abbrPieces.join('|') + ')', 'i'); + this._erasNarrowRegex = new RegExp( + '^(' + narrowPieces.join('|') + ')', + 'i' + ); + } + + // FORMATTING + + addFormatToken(0, ['gg', 2], 0, function () { + return this.weekYear() % 100; + }); + + addFormatToken(0, ['GG', 2], 0, function () { + return this.isoWeekYear() % 100; + }); + + function addWeekYearFormatToken(token, getter) { + addFormatToken(0, [token, token.length], 0, getter); + } + + addWeekYearFormatToken('gggg', 'weekYear'); + addWeekYearFormatToken('ggggg', 'weekYear'); + addWeekYearFormatToken('GGGG', 'isoWeekYear'); + addWeekYearFormatToken('GGGGG', 'isoWeekYear'); + + // ALIASES + + addUnitAlias('weekYear', 'gg'); + addUnitAlias('isoWeekYear', 'GG'); + + // PRIORITY + + addUnitPriority('weekYear', 1); + addUnitPriority('isoWeekYear', 1); + + // PARSING + + addRegexToken('G', matchSigned); + addRegexToken('g', matchSigned); + addRegexToken('GG', match1to2, match2); + addRegexToken('gg', match1to2, match2); + addRegexToken('GGGG', match1to4, match4); + addRegexToken('gggg', match1to4, match4); + addRegexToken('GGGGG', match1to6, match6); + addRegexToken('ggggg', match1to6, match6); + + addWeekParseToken( + ['gggg', 'ggggg', 'GGGG', 'GGGGG'], + function (input, week, config, token) { + week[token.substr(0, 2)] = toInt(input); + } + ); + + addWeekParseToken(['gg', 'GG'], function (input, week, config, token) { + week[token] = hooks.parseTwoDigitYear(input); + }); + + // MOMENTS + + function getSetWeekYear(input) { + return getSetWeekYearHelper.call( + this, + input, + this.week(), + this.weekday(), + this.localeData()._week.dow, + this.localeData()._week.doy + ); + } + + function getSetISOWeekYear(input) { + return getSetWeekYearHelper.call( + this, + input, + this.isoWeek(), + this.isoWeekday(), + 1, + 4 + ); + } + + function getISOWeeksInYear() { + return weeksInYear(this.year(), 1, 4); + } + + function getISOWeeksInISOWeekYear() { + return weeksInYear(this.isoWeekYear(), 1, 4); + } + + function getWeeksInYear() { + var weekInfo = this.localeData()._week; + return weeksInYear(this.year(), weekInfo.dow, weekInfo.doy); + } + + function getWeeksInWeekYear() { + var weekInfo = this.localeData()._week; + return weeksInYear(this.weekYear(), weekInfo.dow, weekInfo.doy); + } + + function getSetWeekYearHelper(input, week, weekday, dow, doy) { + var weeksTarget; + if (input == null) { + return weekOfYear(this, dow, doy).year; + } else { + weeksTarget = weeksInYear(input, dow, doy); + if (week > weeksTarget) { + week = weeksTarget; + } + return setWeekAll.call(this, input, week, weekday, dow, doy); + } + } + + function setWeekAll(weekYear, week, weekday, dow, doy) { + var dayOfYearData = dayOfYearFromWeeks(weekYear, week, weekday, dow, doy), + date = createUTCDate(dayOfYearData.year, 0, dayOfYearData.dayOfYear); + + this.year(date.getUTCFullYear()); + this.month(date.getUTCMonth()); + this.date(date.getUTCDate()); + return this; + } + + // FORMATTING + + addFormatToken('Q', 0, 'Qo', 'quarter'); + + // ALIASES + + addUnitAlias('quarter', 'Q'); + + // PRIORITY + + addUnitPriority('quarter', 7); + + // PARSING + + addRegexToken('Q', match1); + addParseToken('Q', function (input, array) { + array[MONTH] = (toInt(input) - 1) * 3; + }); + + // MOMENTS + + function getSetQuarter(input) { + return input == null + ? Math.ceil((this.month() + 1) / 3) + : this.month((input - 1) * 3 + (this.month() % 3)); + } + + // FORMATTING + + addFormatToken('D', ['DD', 2], 'Do', 'date'); + + // ALIASES + + addUnitAlias('date', 'D'); + + // PRIORITY + addUnitPriority('date', 9); + + // PARSING + + addRegexToken('D', match1to2); + addRegexToken('DD', match1to2, match2); + addRegexToken('Do', function (isStrict, locale) { + // TODO: Remove "ordinalParse" fallback in next major release. + return isStrict + ? locale._dayOfMonthOrdinalParse || locale._ordinalParse + : locale._dayOfMonthOrdinalParseLenient; + }); + + addParseToken(['D', 'DD'], DATE); + addParseToken('Do', function (input, array) { + array[DATE] = toInt(input.match(match1to2)[0]); + }); + + // MOMENTS + + var getSetDayOfMonth = makeGetSet('Date', true); + + // FORMATTING + + addFormatToken('DDD', ['DDDD', 3], 'DDDo', 'dayOfYear'); + + // ALIASES + + addUnitAlias('dayOfYear', 'DDD'); + + // PRIORITY + addUnitPriority('dayOfYear', 4); + + // PARSING + + addRegexToken('DDD', match1to3); + addRegexToken('DDDD', match3); + addParseToken(['DDD', 'DDDD'], function (input, array, config) { + config._dayOfYear = toInt(input); + }); + + // HELPERS + + // MOMENTS + + function getSetDayOfYear(input) { + var dayOfYear = + Math.round( + (this.clone().startOf('day') - this.clone().startOf('year')) / 864e5 + ) + 1; + return input == null ? dayOfYear : this.add(input - dayOfYear, 'd'); + } + + // FORMATTING + + addFormatToken('m', ['mm', 2], 0, 'minute'); + + // ALIASES + + addUnitAlias('minute', 'm'); + + // PRIORITY + + addUnitPriority('minute', 14); + + // PARSING + + addRegexToken('m', match1to2); + addRegexToken('mm', match1to2, match2); + addParseToken(['m', 'mm'], MINUTE); + + // MOMENTS + + var getSetMinute = makeGetSet('Minutes', false); + + // FORMATTING + + addFormatToken('s', ['ss', 2], 0, 'second'); + + // ALIASES + + addUnitAlias('second', 's'); + + // PRIORITY + + addUnitPriority('second', 15); + + // PARSING + + addRegexToken('s', match1to2); + addRegexToken('ss', match1to2, match2); + addParseToken(['s', 'ss'], SECOND); + + // MOMENTS + + var getSetSecond = makeGetSet('Seconds', false); + + // FORMATTING + + addFormatToken('S', 0, 0, function () { + return ~~(this.millisecond() / 100); + }); + + addFormatToken(0, ['SS', 2], 0, function () { + return ~~(this.millisecond() / 10); + }); + + addFormatToken(0, ['SSS', 3], 0, 'millisecond'); + addFormatToken(0, ['SSSS', 4], 0, function () { + return this.millisecond() * 10; + }); + addFormatToken(0, ['SSSSS', 5], 0, function () { + return this.millisecond() * 100; + }); + addFormatToken(0, ['SSSSSS', 6], 0, function () { + return this.millisecond() * 1000; + }); + addFormatToken(0, ['SSSSSSS', 7], 0, function () { + return this.millisecond() * 10000; + }); + addFormatToken(0, ['SSSSSSSS', 8], 0, function () { + return this.millisecond() * 100000; + }); + addFormatToken(0, ['SSSSSSSSS', 9], 0, function () { + return this.millisecond() * 1000000; + }); + + // ALIASES + + addUnitAlias('millisecond', 'ms'); + + // PRIORITY + + addUnitPriority('millisecond', 16); + + // PARSING + + addRegexToken('S', match1to3, match1); + addRegexToken('SS', match1to3, match2); + addRegexToken('SSS', match1to3, match3); + + var token, getSetMillisecond; + for (token = 'SSSS'; token.length <= 9; token += 'S') { + addRegexToken(token, matchUnsigned); + } + + function parseMs(input, array) { + array[MILLISECOND] = toInt(('0.' + input) * 1000); + } + + for (token = 'S'; token.length <= 9; token += 'S') { + addParseToken(token, parseMs); + } + + getSetMillisecond = makeGetSet('Milliseconds', false); + + // FORMATTING + + addFormatToken('z', 0, 0, 'zoneAbbr'); + addFormatToken('zz', 0, 0, 'zoneName'); + + // MOMENTS + + function getZoneAbbr() { + return this._isUTC ? 'UTC' : ''; + } + + function getZoneName() { + return this._isUTC ? 'Coordinated Universal Time' : ''; + } + + var proto = Moment.prototype; + + proto.add = add; + proto.calendar = calendar$1; + proto.clone = clone; + proto.diff = diff; + proto.endOf = endOf; + proto.format = format; + proto.from = from; + proto.fromNow = fromNow; + proto.to = to; + proto.toNow = toNow; + proto.get = stringGet; + proto.invalidAt = invalidAt; + proto.isAfter = isAfter; + proto.isBefore = isBefore; + proto.isBetween = isBetween; + proto.isSame = isSame; + proto.isSameOrAfter = isSameOrAfter; + proto.isSameOrBefore = isSameOrBefore; + proto.isValid = isValid$2; + proto.lang = lang; + proto.locale = locale; + proto.localeData = localeData; + proto.max = prototypeMax; + proto.min = prototypeMin; + proto.parsingFlags = parsingFlags; + proto.set = stringSet; + proto.startOf = startOf; + proto.subtract = subtract; + proto.toArray = toArray; + proto.toObject = toObject; + proto.toDate = toDate; + proto.toISOString = toISOString; + proto.inspect = inspect; + if (typeof Symbol !== 'undefined' && Symbol.for != null) { + proto[Symbol.for('nodejs.util.inspect.custom')] = function () { + return 'Moment<' + this.format() + '>'; + }; + } + proto.toJSON = toJSON; + proto.toString = toString; + proto.unix = unix; + proto.valueOf = valueOf; + proto.creationData = creationData; + proto.eraName = getEraName; + proto.eraNarrow = getEraNarrow; + proto.eraAbbr = getEraAbbr; + proto.eraYear = getEraYear; + proto.year = getSetYear; + proto.isLeapYear = getIsLeapYear; + proto.weekYear = getSetWeekYear; + proto.isoWeekYear = getSetISOWeekYear; + proto.quarter = proto.quarters = getSetQuarter; + proto.month = getSetMonth; + proto.daysInMonth = getDaysInMonth; + proto.week = proto.weeks = getSetWeek; + proto.isoWeek = proto.isoWeeks = getSetISOWeek; + proto.weeksInYear = getWeeksInYear; + proto.weeksInWeekYear = getWeeksInWeekYear; + proto.isoWeeksInYear = getISOWeeksInYear; + proto.isoWeeksInISOWeekYear = getISOWeeksInISOWeekYear; + proto.date = getSetDayOfMonth; + proto.day = proto.days = getSetDayOfWeek; + proto.weekday = getSetLocaleDayOfWeek; + proto.isoWeekday = getSetISODayOfWeek; + proto.dayOfYear = getSetDayOfYear; + proto.hour = proto.hours = getSetHour; + proto.minute = proto.minutes = getSetMinute; + proto.second = proto.seconds = getSetSecond; + proto.millisecond = proto.milliseconds = getSetMillisecond; + proto.utcOffset = getSetOffset; + proto.utc = setOffsetToUTC; + proto.local = setOffsetToLocal; + proto.parseZone = setOffsetToParsedOffset; + proto.hasAlignedHourOffset = hasAlignedHourOffset; + proto.isDST = isDaylightSavingTime; + proto.isLocal = isLocal; + proto.isUtcOffset = isUtcOffset; + proto.isUtc = isUtc; + proto.isUTC = isUtc; + proto.zoneAbbr = getZoneAbbr; + proto.zoneName = getZoneName; + proto.dates = deprecate( + 'dates accessor is deprecated. Use date instead.', + getSetDayOfMonth + ); + proto.months = deprecate( + 'months accessor is deprecated. Use month instead', + getSetMonth + ); + proto.years = deprecate( + 'years accessor is deprecated. Use year instead', + getSetYear + ); + proto.zone = deprecate( + 'moment().zone is deprecated, use moment().utcOffset instead. http://momentjs.com/guides/#/warnings/zone/', + getSetZone + ); + proto.isDSTShifted = deprecate( + 'isDSTShifted is deprecated. See http://momentjs.com/guides/#/warnings/dst-shifted/ for more information', + isDaylightSavingTimeShifted + ); + + function createUnix(input) { + return createLocal(input * 1000); + } + + function createInZone() { + return createLocal.apply(null, arguments).parseZone(); + } + + function preParsePostFormat(string) { + return string; + } + + var proto$1 = Locale.prototype; + + proto$1.calendar = calendar; + proto$1.longDateFormat = longDateFormat; + proto$1.invalidDate = invalidDate; + proto$1.ordinal = ordinal; + proto$1.preparse = preParsePostFormat; + proto$1.postformat = preParsePostFormat; + proto$1.relativeTime = relativeTime; + proto$1.pastFuture = pastFuture; + proto$1.set = set; + proto$1.eras = localeEras; + proto$1.erasParse = localeErasParse; + proto$1.erasConvertYear = localeErasConvertYear; + proto$1.erasAbbrRegex = erasAbbrRegex; + proto$1.erasNameRegex = erasNameRegex; + proto$1.erasNarrowRegex = erasNarrowRegex; + + proto$1.months = localeMonths; + proto$1.monthsShort = localeMonthsShort; + proto$1.monthsParse = localeMonthsParse; + proto$1.monthsRegex = monthsRegex; + proto$1.monthsShortRegex = monthsShortRegex; + proto$1.week = localeWeek; + proto$1.firstDayOfYear = localeFirstDayOfYear; + proto$1.firstDayOfWeek = localeFirstDayOfWeek; + + proto$1.weekdays = localeWeekdays; + proto$1.weekdaysMin = localeWeekdaysMin; + proto$1.weekdaysShort = localeWeekdaysShort; + proto$1.weekdaysParse = localeWeekdaysParse; + + proto$1.weekdaysRegex = weekdaysRegex; + proto$1.weekdaysShortRegex = weekdaysShortRegex; + proto$1.weekdaysMinRegex = weekdaysMinRegex; + + proto$1.isPM = localeIsPM; + proto$1.meridiem = localeMeridiem; + + function get$1(format, index, field, setter) { + var locale = getLocale(), + utc = createUTC().set(setter, index); + return locale[field](utc, format); + } + + function listMonthsImpl(format, index, field) { + if (isNumber(format)) { + index = format; + format = undefined; + } + + format = format || ''; + + if (index != null) { + return get$1(format, index, field, 'month'); + } + + var i, + out = []; + for (i = 0; i < 12; i++) { + out[i] = get$1(format, i, field, 'month'); + } + return out; + } + + // () + // (5) + // (fmt, 5) + // (fmt) + // (true) + // (true, 5) + // (true, fmt, 5) + // (true, fmt) + function listWeekdaysImpl(localeSorted, format, index, field) { + if (typeof localeSorted === 'boolean') { + if (isNumber(format)) { + index = format; + format = undefined; + } + + format = format || ''; + } else { + format = localeSorted; + index = format; + localeSorted = false; + + if (isNumber(format)) { + index = format; + format = undefined; + } + + format = format || ''; + } + + var locale = getLocale(), + shift = localeSorted ? locale._week.dow : 0, + i, + out = []; + + if (index != null) { + return get$1(format, (index + shift) % 7, field, 'day'); + } + + for (i = 0; i < 7; i++) { + out[i] = get$1(format, (i + shift) % 7, field, 'day'); + } + return out; + } + + function listMonths(format, index) { + return listMonthsImpl(format, index, 'months'); + } + + function listMonthsShort(format, index) { + return listMonthsImpl(format, index, 'monthsShort'); + } + + function listWeekdays(localeSorted, format, index) { + return listWeekdaysImpl(localeSorted, format, index, 'weekdays'); + } + + function listWeekdaysShort(localeSorted, format, index) { + return listWeekdaysImpl(localeSorted, format, index, 'weekdaysShort'); + } + + function listWeekdaysMin(localeSorted, format, index) { + return listWeekdaysImpl(localeSorted, format, index, 'weekdaysMin'); + } + + getSetGlobalLocale('en', { + eras: [ + { + since: '0001-01-01', + until: +Infinity, + offset: 1, + name: 'Anno Domini', + narrow: 'AD', + abbr: 'AD', + }, + { + since: '0000-12-31', + until: -Infinity, + offset: 1, + name: 'Before Christ', + narrow: 'BC', + abbr: 'BC', + }, + ], + dayOfMonthOrdinalParse: /\d{1,2}(th|st|nd|rd)/, + ordinal: function (number) { + var b = number % 10, + output = + toInt((number % 100) / 10) === 1 + ? 'th' + : b === 1 + ? 'st' + : b === 2 + ? 'nd' + : b === 3 + ? 'rd' + : 'th'; + return number + output; + }, + }); + + // Side effect imports + + hooks.lang = deprecate( + 'moment.lang is deprecated. Use moment.locale instead.', + getSetGlobalLocale + ); + hooks.langData = deprecate( + 'moment.langData is deprecated. Use moment.localeData instead.', + getLocale + ); + + var mathAbs = Math.abs; + + function abs() { + var data = this._data; + + this._milliseconds = mathAbs(this._milliseconds); + this._days = mathAbs(this._days); + this._months = mathAbs(this._months); + + data.milliseconds = mathAbs(data.milliseconds); + data.seconds = mathAbs(data.seconds); + data.minutes = mathAbs(data.minutes); + data.hours = mathAbs(data.hours); + data.months = mathAbs(data.months); + data.years = mathAbs(data.years); + + return this; + } + + function addSubtract$1(duration, input, value, direction) { + var other = createDuration(input, value); + + duration._milliseconds += direction * other._milliseconds; + duration._days += direction * other._days; + duration._months += direction * other._months; + + return duration._bubble(); + } + + // supports only 2.0-style add(1, 's') or add(duration) + function add$1(input, value) { + return addSubtract$1(this, input, value, 1); + } + + // supports only 2.0-style subtract(1, 's') or subtract(duration) + function subtract$1(input, value) { + return addSubtract$1(this, input, value, -1); + } + + function absCeil(number) { + if (number < 0) { + return Math.floor(number); + } else { + return Math.ceil(number); + } + } + + function bubble() { + var milliseconds = this._milliseconds, + days = this._days, + months = this._months, + data = this._data, + seconds, + minutes, + hours, + years, + monthsFromDays; + + // if we have a mix of positive and negative values, bubble down first + // check: https://github.com/moment/moment/issues/2166 + if ( + !( + (milliseconds >= 0 && days >= 0 && months >= 0) || + (milliseconds <= 0 && days <= 0 && months <= 0) + ) + ) { + milliseconds += absCeil(monthsToDays(months) + days) * 864e5; + days = 0; + months = 0; + } + + // The following code bubbles up values, see the tests for + // examples of what that means. + data.milliseconds = milliseconds % 1000; + + seconds = absFloor(milliseconds / 1000); + data.seconds = seconds % 60; + + minutes = absFloor(seconds / 60); + data.minutes = minutes % 60; + + hours = absFloor(minutes / 60); + data.hours = hours % 24; + + days += absFloor(hours / 24); + + // convert days to months + monthsFromDays = absFloor(daysToMonths(days)); + months += monthsFromDays; + days -= absCeil(monthsToDays(monthsFromDays)); + + // 12 months -> 1 year + years = absFloor(months / 12); + months %= 12; + + data.days = days; + data.months = months; + data.years = years; + + return this; + } + + function daysToMonths(days) { + // 400 years have 146097 days (taking into account leap year rules) + // 400 years have 12 months === 4800 + return (days * 4800) / 146097; + } + + function monthsToDays(months) { + // the reverse of daysToMonths + return (months * 146097) / 4800; + } + + function as(units) { + if (!this.isValid()) { + return NaN; + } + var days, + months, + milliseconds = this._milliseconds; + + units = normalizeUnits(units); + + if (units === 'month' || units === 'quarter' || units === 'year') { + days = this._days + milliseconds / 864e5; + months = this._months + daysToMonths(days); + switch (units) { + case 'month': + return months; + case 'quarter': + return months / 3; + case 'year': + return months / 12; + } + } else { + // handle milliseconds separately because of floating point math errors (issue #1867) + days = this._days + Math.round(monthsToDays(this._months)); + switch (units) { + case 'week': + return days / 7 + milliseconds / 6048e5; + case 'day': + return days + milliseconds / 864e5; + case 'hour': + return days * 24 + milliseconds / 36e5; + case 'minute': + return days * 1440 + milliseconds / 6e4; + case 'second': + return days * 86400 + milliseconds / 1000; + // Math.floor prevents floating point math errors here + case 'millisecond': + return Math.floor(days * 864e5) + milliseconds; + default: + throw new Error('Unknown unit ' + units); + } + } + } + + // TODO: Use this.as('ms')? + function valueOf$1() { + if (!this.isValid()) { + return NaN; + } + return ( + this._milliseconds + + this._days * 864e5 + + (this._months % 12) * 2592e6 + + toInt(this._months / 12) * 31536e6 + ); + } + + function makeAs(alias) { + return function () { + return this.as(alias); + }; + } + + var asMilliseconds = makeAs('ms'), + asSeconds = makeAs('s'), + asMinutes = makeAs('m'), + asHours = makeAs('h'), + asDays = makeAs('d'), + asWeeks = makeAs('w'), + asMonths = makeAs('M'), + asQuarters = makeAs('Q'), + asYears = makeAs('y'); + + function clone$1() { + return createDuration(this); + } + + function get$2(units) { + units = normalizeUnits(units); + return this.isValid() ? this[units + 's']() : NaN; + } + + function makeGetter(name) { + return function () { + return this.isValid() ? this._data[name] : NaN; + }; + } + + var milliseconds = makeGetter('milliseconds'), + seconds = makeGetter('seconds'), + minutes = makeGetter('minutes'), + hours = makeGetter('hours'), + days = makeGetter('days'), + months = makeGetter('months'), + years = makeGetter('years'); + + function weeks() { + return absFloor(this.days() / 7); + } + + var round = Math.round, + thresholds = { + ss: 44, // a few seconds to seconds + s: 45, // seconds to minute + m: 45, // minutes to hour + h: 22, // hours to day + d: 26, // days to month/week + w: null, // weeks to month + M: 11, // months to year + }; + + // helper function for moment.fn.from, moment.fn.fromNow, and moment.duration.fn.humanize + function substituteTimeAgo(string, number, withoutSuffix, isFuture, locale) { + return locale.relativeTime(number || 1, !!withoutSuffix, string, isFuture); + } + + function relativeTime$1(posNegDuration, withoutSuffix, thresholds, locale) { + var duration = createDuration(posNegDuration).abs(), + seconds = round(duration.as('s')), + minutes = round(duration.as('m')), + hours = round(duration.as('h')), + days = round(duration.as('d')), + months = round(duration.as('M')), + weeks = round(duration.as('w')), + years = round(duration.as('y')), + a = + (seconds <= thresholds.ss && ['s', seconds]) || + (seconds < thresholds.s && ['ss', seconds]) || + (minutes <= 1 && ['m']) || + (minutes < thresholds.m && ['mm', minutes]) || + (hours <= 1 && ['h']) || + (hours < thresholds.h && ['hh', hours]) || + (days <= 1 && ['d']) || + (days < thresholds.d && ['dd', days]); + + if (thresholds.w != null) { + a = + a || + (weeks <= 1 && ['w']) || + (weeks < thresholds.w && ['ww', weeks]); + } + a = a || + (months <= 1 && ['M']) || + (months < thresholds.M && ['MM', months]) || + (years <= 1 && ['y']) || ['yy', years]; + + a[2] = withoutSuffix; + a[3] = +posNegDuration > 0; + a[4] = locale; + return substituteTimeAgo.apply(null, a); + } + + // This function allows you to set the rounding function for relative time strings + function getSetRelativeTimeRounding(roundingFunction) { + if (roundingFunction === undefined) { + return round; + } + if (typeof roundingFunction === 'function') { + round = roundingFunction; + return true; + } + return false; + } + + // This function allows you to set a threshold for relative time strings + function getSetRelativeTimeThreshold(threshold, limit) { + if (thresholds[threshold] === undefined) { + return false; + } + if (limit === undefined) { + return thresholds[threshold]; + } + thresholds[threshold] = limit; + if (threshold === 's') { + thresholds.ss = limit - 1; + } + return true; + } + + function humanize(argWithSuffix, argThresholds) { + if (!this.isValid()) { + return this.localeData().invalidDate(); + } + + var withSuffix = false, + th = thresholds, + locale, + output; + + if (typeof argWithSuffix === 'object') { + argThresholds = argWithSuffix; + argWithSuffix = false; + } + if (typeof argWithSuffix === 'boolean') { + withSuffix = argWithSuffix; + } + if (typeof argThresholds === 'object') { + th = Object.assign({}, thresholds, argThresholds); + if (argThresholds.s != null && argThresholds.ss == null) { + th.ss = argThresholds.s - 1; + } + } + + locale = this.localeData(); + output = relativeTime$1(this, !withSuffix, th, locale); + + if (withSuffix) { + output = locale.pastFuture(+this, output); + } + + return locale.postformat(output); + } + + var abs$1 = Math.abs; + + function sign(x) { + return (x > 0) - (x < 0) || +x; + } + + function toISOString$1() { + // for ISO strings we do not use the normal bubbling rules: + // * milliseconds bubble up until they become hours + // * days do not bubble at all + // * months bubble up until they become years + // This is because there is no context-free conversion between hours and days + // (think of clock changes) + // and also not between days and months (28-31 days per month) + if (!this.isValid()) { + return this.localeData().invalidDate(); + } + + var seconds = abs$1(this._milliseconds) / 1000, + days = abs$1(this._days), + months = abs$1(this._months), + minutes, + hours, + years, + s, + total = this.asSeconds(), + totalSign, + ymSign, + daysSign, + hmsSign; + + if (!total) { + // this is the same as C#'s (Noda) and python (isodate)... + // but not other JS (goog.date) + return 'P0D'; + } + + // 3600 seconds -> 60 minutes -> 1 hour + minutes = absFloor(seconds / 60); + hours = absFloor(minutes / 60); + seconds %= 60; + minutes %= 60; + + // 12 months -> 1 year + years = absFloor(months / 12); + months %= 12; + + // inspired by https://github.com/dordille/moment-isoduration/blob/master/moment.isoduration.js + s = seconds ? seconds.toFixed(3).replace(/\.?0+$/, '') : ''; + + totalSign = total < 0 ? '-' : ''; + ymSign = sign(this._months) !== sign(total) ? '-' : ''; + daysSign = sign(this._days) !== sign(total) ? '-' : ''; + hmsSign = sign(this._milliseconds) !== sign(total) ? '-' : ''; + + return ( + totalSign + + 'P' + + (years ? ymSign + years + 'Y' : '') + + (months ? ymSign + months + 'M' : '') + + (days ? daysSign + days + 'D' : '') + + (hours || minutes || seconds ? 'T' : '') + + (hours ? hmsSign + hours + 'H' : '') + + (minutes ? hmsSign + minutes + 'M' : '') + + (seconds ? hmsSign + s + 'S' : '') + ); + } + + var proto$2 = Duration.prototype; + + proto$2.isValid = isValid$1; + proto$2.abs = abs; + proto$2.add = add$1; + proto$2.subtract = subtract$1; + proto$2.as = as; + proto$2.asMilliseconds = asMilliseconds; + proto$2.asSeconds = asSeconds; + proto$2.asMinutes = asMinutes; + proto$2.asHours = asHours; + proto$2.asDays = asDays; + proto$2.asWeeks = asWeeks; + proto$2.asMonths = asMonths; + proto$2.asQuarters = asQuarters; + proto$2.asYears = asYears; + proto$2.valueOf = valueOf$1; + proto$2._bubble = bubble; + proto$2.clone = clone$1; + proto$2.get = get$2; + proto$2.milliseconds = milliseconds; + proto$2.seconds = seconds; + proto$2.minutes = minutes; + proto$2.hours = hours; + proto$2.days = days; + proto$2.weeks = weeks; + proto$2.months = months; + proto$2.years = years; + proto$2.humanize = humanize; + proto$2.toISOString = toISOString$1; + proto$2.toString = toISOString$1; + proto$2.toJSON = toISOString$1; + proto$2.locale = locale; + proto$2.localeData = localeData; + + proto$2.toIsoString = deprecate( + 'toIsoString() is deprecated. Please use toISOString() instead (notice the capitals)', + toISOString$1 + ); + proto$2.lang = lang; + + // FORMATTING + + addFormatToken('X', 0, 0, 'unix'); + addFormatToken('x', 0, 0, 'valueOf'); + + // PARSING + + addRegexToken('x', matchSigned); + addRegexToken('X', matchTimestamp); + addParseToken('X', function (input, array, config) { + config._d = new Date(parseFloat(input) * 1000); + }); + addParseToken('x', function (input, array, config) { + config._d = new Date(toInt(input)); + }); + + //! moment.js + + hooks.version = '2.29.4'; + + setHookCallback(createLocal); + + hooks.fn = proto; + hooks.min = min; + hooks.max = max; + hooks.now = now; + hooks.utc = createUTC; + hooks.unix = createUnix; + hooks.months = listMonths; + hooks.isDate = isDate; + hooks.locale = getSetGlobalLocale; + hooks.invalid = createInvalid; + hooks.duration = createDuration; + hooks.isMoment = isMoment; + hooks.weekdays = listWeekdays; + hooks.parseZone = createInZone; + hooks.localeData = getLocale; + hooks.isDuration = isDuration; + hooks.monthsShort = listMonthsShort; + hooks.weekdaysMin = listWeekdaysMin; + hooks.defineLocale = defineLocale; + hooks.updateLocale = updateLocale; + hooks.locales = listLocales; + hooks.weekdaysShort = listWeekdaysShort; + hooks.normalizeUnits = normalizeUnits; + hooks.relativeTimeRounding = getSetRelativeTimeRounding; + hooks.relativeTimeThreshold = getSetRelativeTimeThreshold; + hooks.calendarFormat = getCalendarFormat; + hooks.prototype = proto; + + // currently HTML5 input type only supports 24-hour formats + hooks.HTML5_FMT = { + DATETIME_LOCAL: 'YYYY-MM-DDTHH:mm', // + DATETIME_LOCAL_SECONDS: 'YYYY-MM-DDTHH:mm:ss', // + DATETIME_LOCAL_MS: 'YYYY-MM-DDTHH:mm:ss.SSS', // + DATE: 'YYYY-MM-DD', // + TIME: 'HH:mm', // + TIME_SECONDS: 'HH:mm:ss', // + TIME_MS: 'HH:mm:ss.SSS', // + WEEK: 'GGGG-[W]WW', // + MONTH: 'YYYY-MM', // + }; + + return hooks; + +}))); diff --git a/scripts/encrypt-deps/random.js b/scripts/encrypt-deps/random.js new file mode 100644 index 0000000..bac63db --- /dev/null +++ b/scripts/encrypt-deps/random.js @@ -0,0 +1,35 @@ +/* jshint ignore:start */ +/* + * Random Number generation, now uses the glue to Java + */ + +Random = {}; + +Random.GENERATOR = null; + +Random.setupGenerator = function() { +/* if (Random.GENERATOR == null && !USE_SJCL) { + if (BigInt.use_applet) { + var foo = BigInt.APPLET.newSecureRandom(); + Random.GENERATOR = BigInt.APPLET.newSecureRandom(); + } else { + // we do it twice because of some weird bug; + var foo = new java.security.SecureRandom(); + Random.GENERATOR = new java.security.SecureRandom(); + } + } + */ +}; + +Random.getRandomInteger = function(max) { + var bit_length = max.bitLength(); + Random.setupGenerator(); + var random; + random = sjcl.random.randomWords(bit_length / 32, 0); + // we get a bit array instead of a BigInteger in this case + var rand_bi = new BigInt(sjcl.codec.hex.fromBits(random), 16); + return rand_bi.mod(max); + return BigInt._from_java_object(random).mod(max); +}; + +/* jshint ignore:end */ \ No newline at end of file diff --git a/scripts/encrypt-deps/sha1.js b/scripts/encrypt-deps/sha1.js new file mode 100644 index 0000000..e2eb9fc --- /dev/null +++ b/scripts/encrypt-deps/sha1.js @@ -0,0 +1,204 @@ +/* jshint ignore:start */ +/* + * A JavaScript implementation of the Secure Hash Algorithm, SHA-1, as defined + * in FIPS PUB 180-1 + * Version 2.1a Copyright Paul Johnston 2000 - 2002. + * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet + * Distributed under the BSD License + * See http://pajhome.org.uk/crypt/md5 for details. + */ + +/* + * Configurable variables. You may need to tweak these to be compatible with + * the server-side, but the defaults work in most cases. + */ +var hexcase = 0; /* hex output format. 0 - lowercase; 1 - uppercase */ +var b64pad = ""; /* base-64 pad character. "=" for strict RFC compliance */ +var chrsz = 8; /* bits per input character. 8 - ASCII; 16 - Unicode */ + +/* + * These are the functions you'll usually want to call + * They take string arguments and return either hex or base-64 encoded strings + */ +function hex_sha1(s){return binb2hex(core_sha1(str2binb(s),s.length * chrsz));} +function b64_sha1(s){return binb2b64(core_sha1(str2binb(s),s.length * chrsz));} +function str_sha1(s){return binb2str(core_sha1(str2binb(s),s.length * chrsz));} +function hex_hmac_sha1(key, data){ return binb2hex(core_hmac_sha1(key, data));} +function b64_hmac_sha1(key, data){ return binb2b64(core_hmac_sha1(key, data));} +function str_hmac_sha1(key, data){ return binb2str(core_hmac_sha1(key, data));} + +/* + * Perform a simple self-test to see if the VM is working + */ +function sha1_vm_test() +{ + return hex_sha1("abc") == "a9993e364706816aba3e25717850c26c9cd0d89d"; +} + +/* + * Calculate the SHA-1 of an array of big-endian words, and a bit length + */ +function core_sha1(x, len) +{ + /* append padding */ + x[len >> 5] |= 0x80 << (24 - len % 32); + x[((len + 64 >> 9) << 4) + 15] = len; + + var w = Array(80); + var a = 1732584193; + var b = -271733879; + var c = -1732584194; + var d = 271733878; + var e = -1009589776; + + for(var i = 0; i < x.length; i += 16) + { + var olda = a; + var oldb = b; + var oldc = c; + var oldd = d; + var olde = e; + + for(var j = 0; j < 80; j++) + { + if(j < 16) w[j] = x[i + j]; + else w[j] = rol(w[j-3] ^ w[j-8] ^ w[j-14] ^ w[j-16], 1); + var t = safe_add(safe_add(rol(a, 5), sha1_ft(j, b, c, d)), + safe_add(safe_add(e, w[j]), sha1_kt(j))); + e = d; + d = c; + c = rol(b, 30); + b = a; + a = t; + } + + a = safe_add(a, olda); + b = safe_add(b, oldb); + c = safe_add(c, oldc); + d = safe_add(d, oldd); + e = safe_add(e, olde); + } + return Array(a, b, c, d, e); + +} + +/* + * Perform the appropriate triplet combination function for the current + * iteration + */ +function sha1_ft(t, b, c, d) +{ + if(t < 20) return (b & c) | ((~b) & d); + if(t < 40) return b ^ c ^ d; + if(t < 60) return (b & c) | (b & d) | (c & d); + return b ^ c ^ d; +} + +/* + * Determine the appropriate additive constant for the current iteration + */ +function sha1_kt(t) +{ + return (t < 20) ? 1518500249 : (t < 40) ? 1859775393 : + (t < 60) ? -1894007588 : -899497514; +} + +/* + * Calculate the HMAC-SHA1 of a key and some data + */ +function core_hmac_sha1(key, data) +{ + var bkey = str2binb(key); + if(bkey.length > 16) bkey = core_sha1(bkey, key.length * chrsz); + + var ipad = Array(16), opad = Array(16); + for(var i = 0; i < 16; i++) + { + ipad[i] = bkey[i] ^ 0x36363636; + opad[i] = bkey[i] ^ 0x5C5C5C5C; + } + + var hash = core_sha1(ipad.concat(str2binb(data)), 512 + data.length * chrsz); + return core_sha1(opad.concat(hash), 512 + 160); +} + +/* + * Add integers, wrapping at 2^32. This uses 16-bit operations internally + * to work around bugs in some JS interpreters. + */ +function safe_add(x, y) +{ + var lsw = (x & 0xFFFF) + (y & 0xFFFF); + var msw = (x >> 16) + (y >> 16) + (lsw >> 16); + return (msw << 16) | (lsw & 0xFFFF); +} + +/* + * Bitwise rotate a 32-bit number to the left. + */ +function rol(num, cnt) +{ + return (num << cnt) | (num >>> (32 - cnt)); +} + +/* + * Convert an 8-bit or 16-bit string to an array of big-endian words + * In 8-bit function, characters >255 have their hi-byte silently ignored. + */ +function str2binb(str) +{ + var bin = Array(); + var mask = (1 << chrsz) - 1; + for(var i = 0; i < str.length * chrsz; i += chrsz) + bin[i>>5] |= (str.charCodeAt(i / chrsz) & mask) << (32 - chrsz - i%32); + return bin; +} + +/* + * Convert an array of big-endian words to a string + */ +function binb2str(bin) +{ + var str = ""; + var mask = (1 << chrsz) - 1; + for(var i = 0; i < bin.length * 32; i += chrsz) + str += String.fromCharCode((bin[i>>5] >>> (32 - chrsz - i%32)) & mask); + return str; +} + +/* + * Convert an array of big-endian words to a hex string. + */ +function binb2hex(binarray) +{ + var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef"; + var str = ""; + for(var i = 0; i < binarray.length * 4; i++) + { + str += hex_tab.charAt((binarray[i>>2] >> ((3 - i%4)*8+4)) & 0xF) + + hex_tab.charAt((binarray[i>>2] >> ((3 - i%4)*8 )) & 0xF); + } + return str; +} + +/* + * Convert an array of big-endian words to a base-64 string + */ +function binb2b64(binarray) +{ + var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + var str = ""; + for(var i = 0; i < binarray.length * 4; i += 3) + { + var triplet = (((binarray[i >> 2] >> 8 * (3 - i %4)) & 0xFF) << 16) + | (((binarray[i+1 >> 2] >> 8 * (3 - (i+1)%4)) & 0xFF) << 8 ) + | ((binarray[i+2 >> 2] >> 8 * (3 - (i+2)%4)) & 0xFF); + for(var j = 0; j < 4; j++) + { + if(i * 8 + j * 6 > binarray.length * 32) str += b64pad; + else str += tab.charAt((triplet >> 6*(3-j)) & 0x3F); + } + } + return str; +} +/* jshint ignore:end */ \ No newline at end of file diff --git a/scripts/encrypt-deps/sha2.js b/scripts/encrypt-deps/sha2.js new file mode 100644 index 0000000..9bed12b --- /dev/null +++ b/scripts/encrypt-deps/sha2.js @@ -0,0 +1,146 @@ +/* jshint ignore:start */ +/* A JavaScript implementation of the Secure Hash Standard + * Version 0.3 Copyright Angel Marin 2003-2004 - http://anmar.eu.org/ + * Distributed under the BSD License + * Some bits taken from Paul Johnston's SHA-1 implementation + */ +var chrsz = 8; /* bits per input character. 8 - ASCII; 16 - Unicode */ +var hexcase = 0;/* hex output format. 0 - lowercase; 1 - uppercase */ + +function safe_add (x, y) { + var lsw = (x & 0xFFFF) + (y & 0xFFFF); + var msw = (x >> 16) + (y >> 16) + (lsw >> 16); + return (msw << 16) | (lsw & 0xFFFF); +} + +function S (X, n) {return ( X >>> n ) | (X << (32 - n));} + +function R (X, n) {return ( X >>> n );} + +function Ch(x, y, z) {return ((x & y) ^ ((~x) & z));} + +function Maj(x, y, z) {return ((x & y) ^ (x & z) ^ (y & z));} + +function Sigma0256(x) {return (S(x, 2) ^ S(x, 13) ^ S(x, 22));} + +function Sigma1256(x) {return (S(x, 6) ^ S(x, 11) ^ S(x, 25));} + +function Gamma0256(x) {return (S(x, 7) ^ S(x, 18) ^ R(x, 3));} + +function Gamma1256(x) {return (S(x, 17) ^ S(x, 19) ^ R(x, 10));} + +function Sigma0512(x) {return (S(x, 28) ^ S(x, 34) ^ S(x, 39));} + +function Sigma1512(x) {return (S(x, 14) ^ S(x, 18) ^ S(x, 41));} + +function Gamma0512(x) {return (S(x, 1) ^ S(x, 8) ^ R(x, 7));} + +function Gamma1512(x) {return (S(x, 19) ^ S(x, 61) ^ R(x, 6));} + +function core_sha256 (m, l) { + var K = new Array(0x428A2F98,0x71374491,0xB5C0FBCF,0xE9B5DBA5,0x3956C25B,0x59F111F1,0x923F82A4,0xAB1C5ED5,0xD807AA98,0x12835B01,0x243185BE,0x550C7DC3,0x72BE5D74,0x80DEB1FE,0x9BDC06A7,0xC19BF174,0xE49B69C1,0xEFBE4786,0xFC19DC6,0x240CA1CC,0x2DE92C6F,0x4A7484AA,0x5CB0A9DC,0x76F988DA,0x983E5152,0xA831C66D,0xB00327C8,0xBF597FC7,0xC6E00BF3,0xD5A79147,0x6CA6351,0x14292967,0x27B70A85,0x2E1B2138,0x4D2C6DFC,0x53380D13,0x650A7354,0x766A0ABB,0x81C2C92E,0x92722C85,0xA2BFE8A1,0xA81A664B,0xC24B8B70,0xC76C51A3,0xD192E819,0xD6990624,0xF40E3585,0x106AA070,0x19A4C116,0x1E376C08,0x2748774C,0x34B0BCB5,0x391C0CB3,0x4ED8AA4A,0x5B9CCA4F,0x682E6FF3,0x748F82EE,0x78A5636F,0x84C87814,0x8CC70208,0x90BEFFFA,0xA4506CEB,0xBEF9A3F7,0xC67178F2); + var HASH = new Array(0x6A09E667, 0xBB67AE85, 0x3C6EF372, 0xA54FF53A, 0x510E527F, 0x9B05688C, 0x1F83D9AB, 0x5BE0CD19); + var W = new Array(64); + var a, b, c, d, e, f, g, h, i, j; + var T1, T2; + + /* append padding */ + m[l >> 5] |= 0x80 << (24 - l % 32); + m[((l + 64 >> 9) << 4) + 15] = l; + + for ( var i = 0; i>5] |= (str.charCodeAt(i / chrsz) & mask) << (24 - i%32); + return bin; +} + +function binb2str (bin) { + var str = ""; + var mask = (1 << chrsz) - 1; + for(var i = 0; i < bin.length * 32; i += chrsz) + str += String.fromCharCode((bin[i>>5] >>> (24 - i%32)) & mask); + return str; +} + +function binb2hex (binarray) { + var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef"; + var str = ""; + for(var i = 0; i < binarray.length * 4; i++) + { + str += hex_tab.charAt((binarray[i>>2] >> ((3 - i%4)*8+4)) & 0xF) + + hex_tab.charAt((binarray[i>>2] >> ((3 - i%4)*8 )) & 0xF); + } + return str; +} + +function binb2b64 (binarray) { + var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + var str = ""; + for(var i = 0; i < binarray.length * 4; i += 3) + { + var triplet = (((binarray[i >> 2] >> 8 * (3 - i %4)) & 0xFF) << 16) + | (((binarray[i+1 >> 2] >> 8 * (3 - (i+1)%4)) & 0xFF) << 8 ) + | ((binarray[i+2 >> 2] >> 8 * (3 - (i+2)%4)) & 0xFF); + for(var j = 0; j < 4; j++) + { + if(i * 8 + j * 6 > binarray.length * 32) str += b64pad; + else str += tab.charAt((triplet >> 6*(3-j)) & 0x3F); + } + } + return str; +} + +function hex_sha256(s){return binb2hex(core_sha256(str2binb(s),s.length * chrsz));} +function b64_sha256(s){return binb2b64(core_sha256(str2binb(s),s.length * chrsz));} +function str_sha256(s){return binb2str(core_sha256(str2binb(s),s.length * chrsz));} +/* jshint ignore:end */ \ No newline at end of file diff --git a/scripts/encrypt-deps/sjcl.js b/scripts/encrypt-deps/sjcl.js new file mode 100644 index 0000000..a2795cd --- /dev/null +++ b/scripts/encrypt-deps/sjcl.js @@ -0,0 +1,52 @@ +/* jshint ignore:start */ +"use strict";function q(a){throw a;}var t=void 0,u=!1;var sjcl={cipher:{},hash:{},keyexchange:{},mode:{},misc:{},codec:{},exception:{corrupt:function(a){this.toString=function(){return"CORRUPT: "+this.message};this.message=a},invalid:function(a){this.toString=function(){return"INVALID: "+this.message};this.message=a},bug:function(a){this.toString=function(){return"BUG: "+this.message};this.message=a},notReady:function(a){this.toString=function(){return"NOT READY: "+this.message};this.message=a}}}; +"undefined"!=typeof module&&module.exports&&(module.exports=sjcl); +sjcl.cipher.aes=function(a){this.j[0][0][0]||this.D();var b,c,d,e,f=this.j[0][4],g=this.j[1];b=a.length;var h=1;4!==b&&(6!==b&&8!==b)&&q(new sjcl.exception.invalid("invalid aes key size"));this.a=[d=a.slice(0),e=[]];for(a=b;a<4*b+28;a++){c=d[a-1];if(0===a%b||8===b&&4===a%b)c=f[c>>>24]<<24^f[c>>16&255]<<16^f[c>>8&255]<<8^f[c&255],0===a%b&&(c=c<<8^c>>>24^h<<24,h=h<<1^283*(h>>7));d[a]=d[a-b]^c}for(b=0;a;b++,a--)c=d[b&3?a:a-4],e[b]=4>=a||4>b?c:g[0][f[c>>>24]]^g[1][f[c>>16&255]]^g[2][f[c>>8&255]]^g[3][f[c& +255]]}; +sjcl.cipher.aes.prototype={encrypt:function(a){return y(this,a,0)},decrypt:function(a){return y(this,a,1)},j:[[[],[],[],[],[]],[[],[],[],[],[]]],D:function(){var a=this.j[0],b=this.j[1],c=a[4],d=b[4],e,f,g,h=[],l=[],k,n,m,p;for(e=0;0x100>e;e++)l[(h[e]=e<<1^283*(e>>7))^e]=e;for(f=g=0;!c[f];f^=k||1,g=l[g]||1){m=g^g<<1^g<<2^g<<3^g<<4;m=m>>8^m&255^99;c[f]=m;d[m]=f;n=h[e=h[k=h[f]]];p=0x1010101*n^0x10001*e^0x101*k^0x1010100*f;n=0x101*h[m]^0x1010100*m;for(e=0;4>e;e++)a[e][f]=n=n<<24^n>>>8,b[e][m]=p=p<<24^p>>>8}for(e= +0;5>e;e++)a[e]=a[e].slice(0),b[e]=b[e].slice(0)}}; +function y(a,b,c){4!==b.length&&q(new sjcl.exception.invalid("invalid aes block size"));var d=a.a[c],e=b[0]^d[0],f=b[c?3:1]^d[1],g=b[2]^d[2];b=b[c?1:3]^d[3];var h,l,k,n=d.length/4-2,m,p=4,s=[0,0,0,0];h=a.j[c];a=h[0];var r=h[1],v=h[2],w=h[3],x=h[4];for(m=0;m>>24]^r[f>>16&255]^v[g>>8&255]^w[b&255]^d[p],l=a[f>>>24]^r[g>>16&255]^v[b>>8&255]^w[e&255]^d[p+1],k=a[g>>>24]^r[b>>16&255]^v[e>>8&255]^w[f&255]^d[p+2],b=a[b>>>24]^r[e>>16&255]^v[f>>8&255]^w[g&255]^d[p+3],p+=4,e=h,f=l,g=k;for(m=0;4> +m;m++)s[c?3&-m:m]=x[e>>>24]<<24^x[f>>16&255]<<16^x[g>>8&255]<<8^x[b&255]^d[p++],h=e,e=f,f=g,g=b,b=h;return s} +sjcl.bitArray={bitSlice:function(a,b,c){a=sjcl.bitArray.P(a.slice(b/32),32-(b&31)).slice(1);return c===t?a:sjcl.bitArray.clamp(a,c-b)},extract:function(a,b,c){var d=Math.floor(-b-c&31);return((b+c-1^b)&-32?a[b/32|0]<<32-d^a[b/32+1|0]>>>d:a[b/32|0]>>>d)&(1<>b-1,1));return a},partial:function(a,b,c){return 32===a?b:(c?b|0:b<<32-a)+0x10000000000*a},getPartial:function(a){return Math.round(a/0x10000000000)||32},equal:function(a,b){if(sjcl.bitArray.bitLength(a)!==sjcl.bitArray.bitLength(b))return u;var c=0,d;for(d=0;d>>b),c=a[e]<<32-b;e=a.length?a[a.length-1]:0;a=sjcl.bitArray.getPartial(e);d.push(sjcl.bitArray.partial(b+a&31,32>>24),e<<=8;return decodeURIComponent(escape(b))},toBits:function(a){a=unescape(encodeURIComponent(a));var b=[],c,d=0;for(c=0;c>>e)>>>26),6>e?(g=a[c]<<6-e,e+=26,c++):(g<<=6,e-=6);for(;d.length&3&&!b;)d+="=";return d},toBits:function(a,b){a=a.replace(/\s|=/g,"");var c=[],d,e=0,f=sjcl.codec.base64.J,g=0,h;b&&(f=f.substr(0,62)+"-_");for(d=0;dh&&q(new sjcl.exception.invalid("this isn't base64!")),26>>e),g=h<<32-e):(e+=6,g^=h<<32-e);e&56&&c.push(sjcl.bitArray.partial(e&56,g,1));return c}};sjcl.codec.base64url={fromBits:function(a){return sjcl.codec.base64.fromBits(a,1,1)},toBits:function(a){return sjcl.codec.base64.toBits(a,1)}};sjcl.hash.sha256=function(a){this.a[0]||this.D();a?(this.q=a.q.slice(0),this.n=a.n.slice(0),this.g=a.g):this.reset()};sjcl.hash.sha256.hash=function(a){return(new sjcl.hash.sha256).update(a).finalize()}; +sjcl.hash.sha256.prototype={blockSize:512,reset:function(){this.q=this.N.slice(0);this.n=[];this.g=0;return this},update:function(a){"string"===typeof a&&(a=sjcl.codec.utf8String.toBits(a));var b,c=this.n=sjcl.bitArray.concat(this.n,a);b=this.g;a=this.g=b+sjcl.bitArray.bitLength(a);for(b=512+b&-512;b<=a;b+=512)z(this,c.splice(0,16));return this},finalize:function(){var a,b=this.n,c=this.q,b=sjcl.bitArray.concat(b,[sjcl.bitArray.partial(1,1)]);for(a=b.length+2;a&15;a++)b.push(0);b.push(Math.floor(this.g/ +4294967296));for(b.push(this.g|0);b.length;)z(this,b.splice(0,16));this.reset();return c},N:[],a:[],D:function(){function a(a){return 0x100000000*(a-Math.floor(a))|0}var b=0,c=2,d;a:for(;64>b;c++){for(d=2;d*d<=c;d++)if(0===c%d)continue a;8>b&&(this.N[b]=a(Math.pow(c,0.5)));this.a[b]=a(Math.pow(c,1/3));b++}}}; +function z(a,b){var c,d,e,f=b.slice(0),g=a.q,h=a.a,l=g[0],k=g[1],n=g[2],m=g[3],p=g[4],s=g[5],r=g[6],v=g[7];for(c=0;64>c;c++)16>c?d=f[c]:(d=f[c+1&15],e=f[c+14&15],d=f[c&15]=(d>>>7^d>>>18^d>>>3^d<<25^d<<14)+(e>>>17^e>>>19^e>>>10^e<<15^e<<13)+f[c&15]+f[c+9&15]|0),d=d+v+(p>>>6^p>>>11^p>>>25^p<<26^p<<21^p<<7)+(r^p&(s^r))+h[c],v=r,r=s,s=p,p=m+d|0,m=n,n=k,k=l,l=d+(k&n^m&(k^n))+(k>>>2^k>>>13^k>>>22^k<<30^k<<19^k<<10)|0;g[0]=g[0]+l|0;g[1]=g[1]+k|0;g[2]=g[2]+n|0;g[3]=g[3]+m|0;g[4]=g[4]+p|0;g[5]=g[5]+s|0;g[6]= +g[6]+r|0;g[7]=g[7]+v|0} +sjcl.mode.ccm={name:"ccm",encrypt:function(a,b,c,d,e){var f,g=b.slice(0),h=sjcl.bitArray,l=h.bitLength(c)/8,k=h.bitLength(g)/8;e=e||64;d=d||[];7>l&&q(new sjcl.exception.invalid("ccm: iv must be at least 7 bytes"));for(f=2;4>f&&k>>>8*f;f++);f<15-l&&(f=15-l);c=h.clamp(c,8*(15-f));b=sjcl.mode.ccm.L(a,b,c,d,e,f);g=sjcl.mode.ccm.o(a,g,c,b,e,f);return h.concat(g.data,g.tag)},decrypt:function(a,b,c,d,e){e=e||64;d=d||[];var f=sjcl.bitArray,g=f.bitLength(c)/8,h=f.bitLength(b),l=f.clamp(b,h-e),k=f.bitSlice(b, +h-e),h=(h-e)/8;7>g&&q(new sjcl.exception.invalid("ccm: iv must be at least 7 bytes"));for(b=2;4>b&&h>>>8*b;b++);b<15-g&&(b=15-g);c=f.clamp(c,8*(15-b));l=sjcl.mode.ccm.o(a,l,c,k,e,b);a=sjcl.mode.ccm.L(a,l.data,c,d,e,b);f.equal(l.tag,a)||q(new sjcl.exception.corrupt("ccm: tag doesn't match"));return l.data},L:function(a,b,c,d,e,f){var g=[],h=sjcl.bitArray,l=h.k;e/=8;(e%2||4>e||16=c?g=[h.partial(16,c)]:0xffffffff>=c&&(g=h.concat([h.partial(16,65534)],[c]));g=h.concat(g,d);for(d=0;de.bitLength(c)&&(h=f(h,d(h)),c=e.concat(c,[-2147483648,0,0,0]));g=f(g,c);return a.encrypt(f(d(f(h, +d(h))),g))},H:function(a){return[a[0]<<1^a[1]>>>31,a[1]<<1^a[2]>>>31,a[2]<<1^a[3]>>>31,a[3]<<1^135*(a[0]>>>31)]}}; +sjcl.mode.gcm={name:"gcm",encrypt:function(a,b,c,d,e){var f=b.slice(0);b=sjcl.bitArray;d=d||[];a=sjcl.mode.gcm.o(!0,a,f,d,c,e||128);return b.concat(a.data,a.tag)},decrypt:function(a,b,c,d,e){var f=b.slice(0),g=sjcl.bitArray,h=g.bitLength(f);e=e||128;d=d||[];e<=h?(b=g.bitSlice(f,h-e),f=g.bitSlice(f,0,h-e)):(b=f,f=[]);a=sjcl.mode.gcm.o(u,a,f,d,c,e);g.equal(a.tag,b)||q(new sjcl.exception.corrupt("gcm: tag doesn't match"));return a.data},W:function(a,b){var c,d,e,f,g,h=sjcl.bitArray.k;e=[0,0,0,0];f=b.slice(0); +for(c=0;128>c;c++){(d=0!==(a[Math.floor(c/32)]&1<<31-c%32))&&(e=h(e,f));g=0!==(f[3]&1);for(d=3;0>>1|(f[d-1]&1)<<31;f[0]>>>=1;g&&(f[0]^=-0x1f000000)}return e},f:function(a,b,c){var d,e=c.length;b=b.slice(0);for(d=0;de&&(a=b.hash(a));for(d=0;dd||0>c)&&q(sjcl.exception.invalid("invalid params to pbkdf2"));"string"===typeof a&&(a=sjcl.codec.utf8String.toBits(a));"string"===typeof b&&(b=sjcl.codec.utf8String.toBits(b));e=e||sjcl.misc.hmac;a=new e(a);var f,g,h,l,k=[],n=sjcl.bitArray;for(l=1;32*k.length<(d||1);l++){e=f=a.encrypt(n.concat(b,[l]));for(g=1;gg;g++)e.push(0x100000000*Math.random()|0);for(g=0;g=1<this.i&&(this.i=f);this.F++; +this.a=sjcl.hash.sha256.hash(this.a.concat(e));this.A=new sjcl.cipher.aes(this.a);for(d=0;4>d&&!(this.e[d]=this.e[d]+1|0,this.e[d]);d++);}for(d=0;d>>=1;this.b[g].update([d,this.C++,2,b,f,a.length].concat(a))}break;case "string":b===t&&(b=a.length);this.b[g].update([d,this.C++,3,b,f,a.length]);this.b[g].update(a); +break;default:l=1}l&&q(new sjcl.exception.bug("random: addEntropy only supports number, array of numbers or string"));this.h[g]+=b;this.c+=b;h===this.l&&(this.isReady()!==this.l&&C("seeded",Math.max(this.i,this.c)),C("progress",this.getProgress()))},isReady:function(a){a=this.I[a!==t?a:this.B];return this.i&&this.i>=a?this.h[0]>this.R&&(new Date).valueOf()>this.O?this.w|this.u:this.u:this.c>=a?this.w|this.l:this.l},getProgress:function(a){a=this.I[a?a:this.B];return this.i>=a?1:this.c>a?1:this.c/ +a},startCollectors:function(){this.p||(window.addEventListener?(window.addEventListener("load",this.r,u),window.addEventListener("mousemove",this.s,u)):document.attachEvent?(document.attachEvent("onload",this.r),document.attachEvent("onmousemove",this.s)):q(new sjcl.exception.bug("can't attach event")),this.p=!0)},stopCollectors:function(){this.p&&(window.removeEventListener?(window.removeEventListener("load",this.r,u),window.removeEventListener("mousemove",this.s,u)):window.detachEvent&&(window.detachEvent("onload", +this.r),window.detachEvent("onmousemove",this.s)),this.p=u)},addEventListener:function(a,b){this.z[a][this.U++]=b},removeEventListener:function(a,b){var c,d,e=this.z[a],f=[];for(d in e)e.hasOwnProperty(d)&&e[d]===b&&f.push(d);for(c=0;cb&&!(a.e[b]=a.e[b]+1|0,a.e[b]);b++);return a.A.encrypt(a.e)}sjcl.random=new sjcl.prng(6); +try{if("undefined"!==typeof module&&module.exports){var D=require("crypto").randomBytes(128);sjcl.random.addEntropy(D,1024,"crypto['randomBytes']")}else if(window&&window.crypto&&window.crypto.getRandomValues){var E=new Uint32Array(32);window.crypto.getRandomValues(E);sjcl.random.addEntropy(E,1024,"crypto['getRandomValues']")}}catch(F){} +sjcl.json={defaults:{v:1,iter:1E3,ks:128,ts:64,mode:"ccm",adata:"",cipher:"aes"},encrypt:function(a,b,c,d){c=c||{};d=d||{};var e=sjcl.json,f=e.d({iv:sjcl.random.randomWords(4,0)},e.defaults),g;e.d(f,c);c=f.adata;"string"===typeof f.salt&&(f.salt=sjcl.codec.base64.toBits(f.salt));"string"===typeof f.iv&&(f.iv=sjcl.codec.base64.toBits(f.iv));(!sjcl.mode[f.mode]||!sjcl.cipher[f.cipher]||"string"===typeof a&&100>=f.iter||64!==f.ts&&96!==f.ts&&128!==f.ts||128!==f.ks&&192!==f.ks&&0x100!==f.ks||2>f.iv.length|| +4=b.iter||64!==b.ts&&96!==b.ts&&128!==b.ts||128!==b.ks&&192!==b.ks&&0x100!==b.ks||!b.iv||2>b.iv.length||4 + + * deployment-tool is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License. + + * deployment-tool is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + + * You should have received a copy of the GNU Affero General Public License + * along with deployment-tool. If not, see . +**/ + +var fs = require('fs'); + +// otherwise one of the scripts complains +var navigator = { + "appName": "foo" +}; + +filedata = fs.readFileSync('encrypt-deps/jsbn.js','utf8'); +eval(filedata); + +filedata = fs.readFileSync('encrypt-deps/jsbn2.js','utf8'); +eval(filedata); + +filedata = fs.readFileSync('encrypt-deps/bigint.js','utf8'); +eval(filedata); + +filedata = fs.readFileSync('encrypt-deps/class.js','utf8'); +eval(filedata); + +filedata = fs.readFileSync('encrypt-deps/elgamal.js','utf8'); +eval(filedata); + +filedata = fs.readFileSync('encrypt-deps/random.js','utf8'); +eval(filedata); + +filedata = fs.readFileSync('encrypt-deps/sha1.js','utf8'); +eval(filedata); +filedata = fs.readFileSync('encrypt-deps/sha2.js','utf8'); +eval(filedata); + +var sjcl = require('encrypt-deps/sjcl.js'); +var moment = require('encrypt-deps/moment.js'); + +// FIXME copied from voting_booth.js as it is a hassle to import the whole sequent view structure +var encryptAnswer = function(pk_json, plain_answer) { + + var pk = ElGamal.PublicKey.fromJSONObject(pk_json); + var plaintext = new ElGamal.Plaintext(BigInt.fromInt(plain_answer), pk, true); + var randomness = Random.getRandomInteger(pk.q); + var ctext = ElGamal.encrypt(pk, plaintext, randomness); + var proof = plaintext.proveKnowledge(ctext.alpha, randomness, ElGamal.fiatshamir_dlog_challenge_generator); + var ciphertext = ctext.toJSONObject(); + var json_proof = proof.toJSONObject(); + var enc_answer = { + alpha: ciphertext.alpha, + beta: ciphertext.beta, + commitment: proof.commitment, + response: proof.response, + challenge: proof.challenge + }; + + var verified = ctext.verifyPlaintextProof(proof, ElGamal.fiatshamir_dlog_challenge_generator); + console.warn("> Node: proof verified = " + new Boolean(verified).toString()); + return enc_answer; +} + +var updateTally = function(tally, vote) { + if(!(vote in tally)) { + tally[vote] = 1; + } + else { + tally[vote] = tally[vote] + 1; + } +} + +if(process.argv.length < 4) { + console.error("* Node: Need public key and votes.json file args to encrypt votes"); + process.exit(1); +} +else { + try { + console.warn("> Node: reading pk "); + + var pkStr = fs.readFileSync(process.argv[2], 'utf8'); + var pk = JSON.parse(pkStr); + + var tally = {}; + + // voting_booth.js:castVote + var ballots = []; + var answersStr = fs.readFileSync(process.argv[3], 'utf8'); + var answers = JSON.parse(answersStr); + + for(var i = 0; i < answers.length; i++) { + var answer = [answers[i]]; + updateTally(tally, answers[i]); + console.warn('> Node: encrypting answer \'' + answer + '\''); + ballot = { + 'is_vote_secret': true, + 'action': 'vote' + }; + ballot['issue_date'] = moment().format(); + var random = sjcl.random.randomWords(5, 0); + var rand_bi = new BigInt(sjcl.codec.hex.fromBits(random), 16); + ballot['unique_randomness'] = rand_bi.toRadix(16); + ballot['question0'] = encryptAnswer(pk, BigInt.fromInt(answer)); + + ballots.push(ballot); + } + + if(process.argv.length == 5) { + var totalVotes = process.argv[4]; + if(totalVotes > answers.length) { + console.warn('> Node: duplicating votes to reach ' + totalVotes); + for(var i = answers.length; i < totalVotes; i++) { + var nextVote = Math.floor((Math.random()*answers.length)); + // console.warn('> Node: duplicating ' + answers[nextVote]); + updateTally(tally, answers[nextVote]); + ballots.push(ballots[nextVote]); + } + } + } + var serialized = JSON.stringify(ballots) + console.warn('> Node: tally = ' + JSON.stringify(tally)); + console.warn('> Node: outputting votes..'); + console.log(serialized); + } + catch(err) { + console.error("* Exception encrypting votes " + err); + process.exit(1) + } +} diff --git a/scripts/eopeers b/scripts/eopeers new file mode 100755 index 0000000..fc192e2 --- /dev/null +++ b/scripts/eopeers @@ -0,0 +1,317 @@ +#!/usr/bin/env python + +# This file is part of misc-tools. +# Copyright (C) 2014-2016 Sequent Tech Inc + +# misc-tools is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License. + +# misc-tools is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with misc-tools. If not, see . + +import argparse +import os +import sys +import json +import subprocess + +EO_CONF = os.environ.get('EO_CONF', '/datastore/eoconf.json') +EO_PEER_LIST = os.environ.get('EO_CONF', '/datastore/eopeerlist/') +CERT_DIR = os.environ.get('CERT_DIR', '/datastore/certs') +CERT_PATH = os.environ.get('CERT_PATH', f'${CERT_DIR}/cert.pem') +CERT_KEY_PATH = os.environ.get('CERT_KEY_PATH', f'${CERT_DIR}/key-nopass.pem') +CERT_CALIST_PATH = os.environ.get('CERT_CALIST_PATH', f'${CERT_DIR}/calist') + +try: + # EO_CONF file should have the following format: + # { + # "VERSION": 1, + # "PUBLIC_IP_ADDRESS": "192.168.0.1", + # "PRIVATE_IP_ADDRESS": "192.168.0.1", + # "HOSTNAME": "election-orchestra", + # "PORT": 5000, + # "KEYSTORE_PASS": "supersecret" + # } + CONFIG = json.load(open(EO_CONF)) +except: + print(f"Configuration file at '${EO_CONF}' not found") + sys.exit(1) + +EO_VERSION = CONFIG['VERSION'] +EO_PUBLIC_IP_ADDRESS = CONFIG['PUBLIC_IP_ADDRESS'] +EO_PRIVATE_IP_ADDRESS = CONFIG['PRIVATE_IP_ADDRESS'] +EO_HOSTNAME = CONFIG['HOSTNAME'] +EO_PORT = CONFIG['PORT'] + +def _validate_package(el_json): + ''' + Validates a package + ''' + check_list = [ + dict( + key="ssl_certificate", + existence="required", + checks=[ + lambda val: isinstance(val, str) and len(val) < 10000 + ] + ), + dict( + key="ip_address", + existence="required", + checks=[ + lambda val: isinstance(val, str) and len(val) < 100 + ] + ), + dict( + key="hostname", + existence="required", + checks=[ + lambda val: isinstance(val, str) and len(val) < 100 + ] + ), + dict( + key="port", + existence="optional", + checks=[ + lambda val: isinstance(val, int) and val > 0 and val < 65536 + ] + ), + dict( + key="version", + existence="optional", + checks=[ + lambda val: isinstance(val, int) and val > 0 and val < 10000, + lambda val: val <= EO_VERSION or\ + "Package from a incompatible version (package is version "\ + "%d, eopeers is version %d)" % (EO_VERSION, val) + ] + ) + ] + valid_keys = set([check['key'] for check in check_list]) + unknown_keys = valid_keys.difference(set(el_json.keys())) + + if len(unknown_keys) > 0: + print("Unknown keys: %s" % ", ".join(list(unknown_keys))) + + for check in check_list: + if check['key'] not in el_json.keys(): + if check['existence'] == 'required': + print("missing required key: %s" % check['key']) + exit(1) + else: + continue + data = el_json[check['key']] + for check_f in check['checks']: + check_ret = check_f(data) + if check_ret == True: + continue + if isinstance(check_ret, str): + print(check_ret) + exit(1) + else: + print("Invalid data for key %s: %s" % (check['key'], str(data)[:100])) + exit(1) + +def install(PEER_LIST, path, keystore=None): + ''' + install the peer package by path + ''' + if not os.path.exists(PEER_LIST): + os.mkdir(PEER_LIST) + + if not os.path.isfile(path): + print("Could not read file: %s" % path) + exit(1) + try: + with open(path, 'r') as f: + el_json = json.loads(f.read()) + _validate_package(el_json) + except: + print("error loading file: %s" % path) + import traceback + traceback.print_exc() + exit(1) + + # check it's not already installed + bname = el_json['hostname'] + if os.path.exists(os.path.join(PEER_LIST, bname)): + print("package for hostname %s already installed" % bname) + exit(1) + + # add to hosts if needed + with open("/etc/hosts", "r") as f: + hosts_data = f.read() + hostline = "\n%s %s" % (el_json['ip_address'], el_json["hostname"]) + if hostline not in hosts_data: + subprocess.call("echo '%s' >> /etc/hosts" % hostline, shell=True) + + # add to ssl certs + with open(CERT_CALIST_PATH, "r") as f: + calist_data = f.read().strip() + if el_json["ssl_certificate"] not in calist_data: + cert = el_json["ssl_certificate"] + subprocess.call( + f"echo '{cert}' >> {CERT_CALIST_PATH}", + shell=True + ) + + # save peer package + path = os.path.join(PEER_LIST, el_json['hostname'] + ".package") + with open(path, 'w') as f: + f.write(json.dumps(el_json)) + + if keystore: + keystore = keystore[0] + # adding the key to the keystore + temppem = "/tmp/eopeers-auth.pem" + with open(temppem, "w") as f: + f.write(el_json["ssl_certificate"]) + #keytool --delete mykey -keystore keystore.jks + subprocess.call("keytool -noprompt -import -file %s -keystore %s -storepass '%s' -alias %s" % (temppem, keystore, CONFIG['KEYSTORE_PASS'], bname), shell=True) + os.unlink(temppem) + +def uninstall(PEER_LIST, hostname, keystore=None): + ''' + uninstall the peer package by hostname + ''' + # check it's not already installed + path = os.path.join(PEER_LIST, hostname + ".package") + if not os.path.exists(path): + print("package for hostname %s is not installed" % hostname) + exit(1) + try: + with open(path, 'r') as f: + el_json = json.loads(f.read()) + _validate_package(el_json) + except: + print("error loading file: %s" % path) + import traceback + traceback.print_exc() + exit(1) + + # remove hostname from hosts + with open('/etc/hosts', 'r') as f: + data = f.read() + hostline = "\n%s %s" % (el_json['ip_address'], el_json["hostname"]) + data = data.replace(hostline, "") + with open('/etc/hosts', 'w') as f: + f.write(data) + + # remove from ssl certs + with open(CERT_CALIST_PATH, 'r') as f: + data = f.read() + data = data.replace(el_json["ssl_certificate"], "") + with open(CERT_CALIST_PATH, 'w') as f: + f.write(data) + + # finally remove the package + os.unlink(path) + + if keystore: + keystore = keystore[0] + # removing the key from the keystore + subprocess.call("keytool -noprompt -delete -alias %s -keystore %s -storepass '%s'" % (hostname, keystore, CONFIG['KEYSTORE_PASS']), shell=True) + +def showmine(pargs): + ''' + install the peer package by path + ''' + with open(CERT_PATH, 'r') as f: + ssl_certificate = f.read() + ip = EO_PRIVATE_IP_ADDRESS if pargs.private_ip else EO_PUBLIC_IP_ADDRESS + us = { + "ssl_certificate": ssl_certificate, + "ip_address": ip, + "hostname": EO_HOSTNAME, + "port": EO_PORT, + "version": EO_VERSION + } + print(json.dumps(us)) + +def listall(PEER_LIST): + ''' + return a list of peer packagers loaded in json + ''' + if not os.path.isdir(PEER_LIST): + return [] + + l = os.listdir(PEER_LIST) + if len(l) == 0: + return [] + + ret = [] + for el in l: + path = os.path.join(PEER_LIST, el) + try: + with open(path, 'r') as f: + ret.append(json.loads(f.read())) + except: + print("error loading: %s" % el) + import traceback + traceback.print_exc() + exit(1) + return ret + +def show_pkg(hostname): + ''' + show the content of a peer package referred by hostname + ''' + # check it's not already installed + path = os.path.join(EO_PEER_LIST, hostname + ".package") + if not os.path.exists(path): + print("package for hostname %s is not installed" % hostname) + exit(1) + try: + with open(path, 'r') as f: + print(f.read()) + except: + print("error loading file: %s" % path) + import traceback + traceback.print_exc() + exit(1) + +def main(): + ''' + Main function + ''' + parser = argparse.ArgumentParser(prog='eopeers') + parser.add_argument("--list", help="list installed peer packages by hostname", action="store_true") + parser.add_argument('--install', nargs='+', help='install a peer package') + parser.add_argument('--uninstall', nargs='+', help='uninstall peer package(s) by hostname') + parser.add_argument('--show-mine', help='show our peer package', action="store_true") + parser.add_argument('--private-ip', help='together with --show-mine, uses ' + 'private ip instead of the public one', action="store_true") + parser.add_argument('--show', help="show the content of an installed package " + "by hostname") + parser.add_argument('--keystore', nargs=1, help='The keystore path ' + 'to add or remove with keytool') + + pargs = parser.parse_args() + + if pargs.list: + l = listall(EO_PEER_LIST) + if len(l) == 0: + print("No peer package installed.") + exit(0) + print("Packages in %s:" % EO_PEER_LIST) + for el in l: + print(" * %s" % el['hostname']) + elif pargs.install: + for path in pargs.install: + install(EO_PEER_LIST, path, pargs.keystore) + elif pargs.uninstall: + for path in pargs.uninstall: + uninstall(EO_PEER_LIST, path, pargs.keystore) + elif pargs.show_mine: + showmine(pargs) + elif pargs.show: + show_pkg(pargs.show) + +if __name__ == "__main__": + main() diff --git a/scripts/eotest b/scripts/eotest new file mode 100755 index 0000000..2ce8da1 --- /dev/null +++ b/scripts/eotest @@ -0,0 +1,494 @@ +#!/usr/bin/env python3 + +# This file is part of misc-tools. +# Copyright (C) 2014-2023 Sequent Tech Inc + +# misc-tools is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License. + +# misc-tools is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with misc-tools. If not, see . + +import requests +import ssl +from requests.adapters import HTTPAdapter +import json +import time +import random + +from functools import partial +from base64 import urlsafe_b64encode + +from http.server import ( + BaseHTTPRequestHandler, + HTTPServer, + SimpleHTTPRequestHandler +) +from socketserver import ThreadingMixIn +import threading + +import subprocess + +import argparse +import sys +import __main__ +from argparse import RawTextHelpFormatter + +from datetime import datetime +import hashlib +import codecs +import traceback + +import os.path +import os + +BUF_SIZE = 10*1024 + +CERT_DIR = os.environ.get('CERT_DIR', '/datastore/certs/') +CERT_PREFIX = os.environ.get('CERT_PREFIX', 'cert') +CERT_PATH = os.environ.get('CERT_PATH', f'${CERT_DIR}/${CERT_PREFIX}.pem') +CERT_KEY_PATH = os.environ.get('CERT_KEY_PATH', f'${CERT_DIR}/key-nopass.pem') +CERT_CALIST_PATH = os.environ.get('CERT_CALIST_PATH', f'${CERT_DIR}/calist') + +# with three authorities 60 seconds was not enough +EOTEST_PK_TIMEOUT = int(os.environ.get('PK_TIMEOUT', '600')) + +# 6 hours should be enough for a tally.. +EOTEST_TALLY_TIMEOUT = int(os.environ.get('PK_TIMEOUT', '21600')) + +EOTEST_DATA_DIR = os.environ.get('DATA_DIR', '/datastore/eotest/data') +NODE_BIN_PATH = os.environ.get('NODE_BIN', '/bin/node') +EOTEST_VMND_BIN_PATH = os.environ.get('EOTEST_VMND_BIN_PATH', '/bin/vmnd') +EOTEST_LOCAL_PORT = int(os.environ.get('LOCAL_PORT', '8084')) + + +if not os.path.exists(EOTEST_DATA_DIR): + os.makedirs(EOTEST_DATA_DIR) + +os.chdir(EOTEST_DATA_DIR) + +class RejectAdapter(HTTPAdapter): + def send(self, request, stream=False, timeout=None, verify=True, cert=None, proxies=None): + raise Exception('Policy set to reject connection to ' + request.url) + +def getPeerPkg(mypeerpkg): + if mypeerpkg is None: + mypeerpkg = subprocess.check_output(["eopeers", "--show-mine"]) + if isinstance(mypeerpkg, bytes): + return json.loads(mypeerpkg.decode("utf-8")) + else: + return mypeerpkg + +def getTallyData(mypeerpkg): + mypeerpkg = getPeerPkg(mypeerpkg) + localServer = mypeerpkg["hostname"] + return { + # 'election_id': electionId, + "callback_url": "https://" + localServer + ":" + str(EOTEST_LOCAL_PORT) + "/receive_tally", + "votes_url": "https://" + localServer + ":" + str(EOTEST_LOCAL_PORT) + "/", + "votes_hash": "ni:///sha-256;" + } + + +def _apiUrl(path, pkg): + ''' + Given a full api (without the initial '/') path and a package dict, returns + a well formed url + ''' + return "https://%(hostname)s:%(port)d/%(path)s" % dict( + hostname=pkg["hostname"], + port=pkg.get("port", 5000), + path=path + ) + +def grabAuthData(eopeers_dir, mypeerpkg, eopeers): + mypeerpkg = getPeerPkg(mypeerpkg) + + if not os.path.isdir(eopeers_dir): + print("%s is not a directory" % eopeers_dir) + exit(1) + + l = os.listdir(eopeers_dir) + if len(l) == 0: + print("%s is an empty directory" % eopeers_dir) + print("one authority") + #exit(1) + + auths_data = [mypeerpkg] + for fname in os.listdir(eopeers_dir): + path = os.path.join(eopeers_dir, fname) + with open(path, 'r') as f: + auths_data.append(json.loads(f.read())) + + if eopeers is not None: + if mypeerpkg['hostname'] not in eopeers: + eopeers.append(mypeerpkg['hostname']) + auths_data = [auth for auth in auths_data if auth['hostname'] in eopeers] + hostnames = [a['hostname'] for a in auths_data] + if len(hostnames) != len(eopeers): + not_found = str(list(set(eopeers) - set(hostnames))) + print("some peers were not found: %s" % not_found) + exit(1) + + print("\nusing the following authorities:") + i = 1 + ret_data = [] + for auth in auths_data: + ret_data.append({ + "name": "Auth%d" %i, + "orchestra_url": _apiUrl("api/queues", auth), + "ssl_cert": auth["ssl_certificate"] + }) + if i == 1: + print(" 1. %s (this is us, acting as orchestra " + "director)" % auth['hostname']) + else: + print(" %d. %s" % (i, auth['hostname'])) + i += 1 + print("\n") + return ret_data + +def getStartData(eopeers_dir, mypeerpkg, eopeers): + mypeerpkg = getPeerPkg(mypeerpkg) + return { + "callback_url": "https://" + mypeerpkg["hostname"] + ":" + str(EOTEST_LOCAL_PORT) + "/key_done", + "title": "Test election", + "description": "election description", + 'presentation': {}, + "questions": [{ + "title": "Who Should be President?", + "tally_type": "plurality-at-large", + "randomize_answer_order": True, + 'num_winners': 1, + "answer_total_votes_percentage": "over-total-valid-votes", + 'layouy': 'plurality', + "answers": [ + { + 'details': '', + 'text': 'Alice', + 'id': 0, + 'category': '', + 'sort_order': 0, + 'urls': [], + }, + { + 'id': 1, + 'category': '', + 'sort_order': 1, + 'urls': [], + 'text': 'Bob' + } + ], + "max": 1, "min": 0 + }], + "start_date": "2013-12-06T18:17:14.457000", + "end_date": "2013-12-09T18:17:14.457000", + "authorities": grabAuthData(eopeers_dir, mypeerpkg, eopeers) + } + +# code + +# thread signalling +cv = threading.Condition() + +class ThreadingHTTPServer(ThreadingMixIn, HTTPServer): + pass + +class RequestHandler(SimpleHTTPRequestHandler): + def do_GET(self): + print("> HTTP GET received " + self.path) + if(self.path == "/exit"): + self.send_response(204) + self.end_headers() + print("> HTTP GET sent response") + cv.acquire() + cv.done = True + cv.notify() + cv.release() + else: + SimpleHTTPRequestHandler.do_GET(self) + + def do_POST(self): + length = int(self.headers['Content-Length']) + print("> HTTP POST received " + self.path + " (" + str(length) + ")") + raw = self.rfile.read(length).decode('utf-8') + data = json.loads(raw) + + # print(data) + self.send_response(200) + self.end_headers() + print("> HTTP POST sent response") + cv.acquire() + cv.done = True + cv.data = data + cv.notify() + cv.release() + +def hash_file(file_path): + ''' + Returns the hexdigest of the hash of the contents of a file, given the file + path. + ''' + hash = hashlib.sha256() + f = open(os.path.join(EOTEST_DATA_DIR, file_path), 'rb') + for chunk in iter(partial(f.read, BUF_SIZE), b''): + hash.update(chunk) + f.close() + return urlsafe_b64encode(hash.digest()).decode('utf-8') + + +def writeVotes(votesData, fileName): + # forms/election.py:save + votes = [] + for vote in votesData: + data = { + "proofs": [], + "choices": [], + "issue_date": str(datetime.now()) + } + + q_answer = vote['question0'] + data["proofs"].append(dict( + commitment=q_answer['commitment'], + response=q_answer['response'], + challenge=q_answer['challenge'] + )) + data["choices"].append(dict( + alpha=q_answer['alpha'], + beta=q_answer['beta'] + )) + + votes.append(data) + + # tasks/election.py:launch_encrypted_tally + with codecs.open(os.path.join(EOTEST_DATA_DIR, fileName), encoding='utf-8', mode='w+') as votes_file: + for vote in votes: + votes_file.write(json.dumps(vote, sort_keys=True) + "\n") + + +def startServer(port): + import ssl + print("> Starting server on port " + str(port)) + server = ThreadingHTTPServer(('', port), RequestHandler) + server.socket = ssl.wrap_socket(server.socket, certfile=CERT_PATH, keyfile=CERT_KEY_PATH, server_side=True) + thread = threading.Thread(target = server.serve_forever, daemon=True) + thread.start() + +def startElection(electionId, url, data): + session = requests.sessions.Session() + session.mount('http://', RejectAdapter()) + data['id'] = int(electionId) + print("> Creating election %s" % electionId) + cv.done = False + r = session.request('post', url, data=json.dumps(data), verify=CERT_CALIST_PATH, cert=(CERT_PATH, CERT_KEY_PATH)) + print("> " + str(r)) + +def waitForPublicKey(): + start = time.time() + cv.acquire() + cv.wait(EOTEST_PK_TIMEOUT) + pk = '' + if(cv.done): + diff = time.time() - start + try: + pk = cv.data['session_data'][0]['pubkey'] + print("> Election created " + str(diff) + " sec, public key is") + print(pk) + except: + print("* Could not retrieve public key " + str(cv.data)) + print(traceback.print_exc()) + else: + print("* Timeout waiting for public key") + cv.release() + + return pk + +def doTally(electionId, url, data, votesFile, hash): + session = requests.sessions.Session() + session.mount('http://', RejectAdapter()) + data['votes_url'] = data['votes_url'] + votesFile + data['votes_hash'] = data['votes_hash'] + hash + data['election_id'] = int(electionId) + # print("> Tally post with " + json.dumps(data)) + print("> Requesting tally..") + cv.done = False + r = session.request('post', url, data=json.dumps(data), verify=CERT_CALIST_PATH, cert=(CERT_PATH, CERT_KEY_PATH)) + print("> " + str(r)) + if r.status_code == 400: + print("> error:" + str(r.text)) + +def waitForTally(): + start = time.time() + cv.acquire() + cv.wait(EOTEST_TALLY_TIMEOUT) + ret = '' + if(cv.done): + diff = time.time() - start + # print("> Received tally data (" + str(diff) + " sec) " + str(cv.data)) + print("> Received tally data " + str(diff) + " sec") + if('tally_url' in cv.data['data']): + ret = cv.data['data'] + else: + print("* Timeout waiting for tally") + cv.release() + + return ret + +def downloadTally(url, electionId): + session = requests.sessions.Session() + session.mount('http://', RejectAdapter()) + fileName = electionId + '.tar.gz' + path = os.path.join(EOTEST_DATA_DIR, fileName) + print("> Downloading to %s" % path) + with open(path, 'wb') as handle: + request = session.request('get',url, stream=True, verify=CERT_CALIST_PATH, cert=(CERT_PATH, CERT_KEY_PATH)) + + for block in request.iter_content(1024): + if not block: + break + + handle.write(block) + +''' driving functions ''' + +def create(args): + electionId = args.electionId + startServer(EOTEST_LOCAL_PORT) + mypeerpkg = getPeerPkg(args.mypeerpkg) + startData = getStartData(args.eopeers_dir, args.mypeerpkg, args.peers) + startUrl = _apiUrl("public_api/election", mypeerpkg) + startElection(electionId, startUrl, startData) + publicKey = waitForPublicKey() + pkFile = 'pk' + electionId + + if(len(publicKey) > 0): + print("> Saving pk to " + pkFile) + with codecs.open(os.path.join(EOTEST_DATA_DIR, pkFile), encoding='utf-8', mode='w+') as votes_file: + votes_file.write(json.dumps(publicKey)) + else: + print("No public key, exiting..") + exit(1) + + return pkFile + +def encrypt(args): + electionId = args.electionId + pkFile = 'pk' + electionId + votesFile = args.vfile + votesCount = args.vcount + ctexts = 'ctexts' + electionId + # encrypting with vmnd + if(args.vmnd): + vmndFile = os.path.join(EOTEST_DATA_DIR, "vmndCtexts" + electionId) + if(votesCount == 0): + votesCount = 10 + subprocess.call([EOTEST_VMND_BIN_PATH, electionId, str(votesCount), vmndFile]) + if (os.path.isfile(vmndFile)): + with open(vmndFile, 'r') as content_file: + lines = content_file.readlines() + votes = [] + + for line in lines: + fields = {} + lineJson = json.loads(line) + lineJson["commitment"] = "foo" + lineJson["response"] = "foo" + lineJson["challenge"] = "foo" + fields["question0"] = lineJson + votes.append(fields) + + writeVotes(votes, ctexts) + + else: + print("Could not read vmnd votes file " + vmndFile) + exit(1) + else: + print("> Encrypting votes (" + votesFile + ", pk = " + pkFile + ", " + str(votesCount) + ")..") + pkPath = os.path.join(EOTEST_DATA_DIR, pkFile) + votesPath = os.path.join(EOTEST_DATA_DIR, votesFile) + # if not present in data dir, use current directory + if not (os.path.isfile(votesPath)): + votesPath = votesFile + if(os.path.isfile(pkPath)) and (os.path.isfile(votesPath)): + output, error = subprocess.Popen([NODE_BIN_PATH, "encrypt.js", pkPath, votesPath, str(votesCount)], stdout = subprocess.PIPE).communicate() + + print("> Received Nodejs output (" + str(len(output)) + " chars)") + parsed = json.loads(output) + + + print("> Writing file to " + ctexts) + writeVotes(parsed, ctexts) + else: + print("No public key or votes file, exiting..") + exit(1) + +def tally(args): + if(args.command[0] == "tally"): + startServer(EOTEST_LOCAL_PORT) + + electionId = args.electionId + + ctexts = 'ctexts' + electionId + # need hash + hash = hash_file(ctexts) + print("> Votes hash is " + hash) + tallyData = getTallyData(args.mypeerpkg) + mypeerpkg = getPeerPkg(args.mypeerpkg) + tallyUrl = _apiUrl('public_api/tally', mypeerpkg) + doTally(electionId, tallyUrl, tallyData, ctexts, hash) + tallyResponse = waitForTally() + + if('tally_url' in tallyResponse): + print("> Downloading tally from " + tallyResponse['tally_url']) + downloadTally(tallyResponse['tally_url'], electionId) + else: + print("* Tally not found in http data") + +def full(args): + electionId = args.electionId + + pkFile = create(args) + + if(os.path.isfile(os.path.join(EOTEST_DATA_DIR, pkFile))): + encrypt(args) + tally(args) + else: + print("No public key, exiting..") + +def main(argv): + if not (os.path.isdir(EOTEST_DATA_DIR)): + print("> Creating data directory") + os.makedirs(EOTEST_DATA_DIR) + parser = argparse.ArgumentParser(description='EO testing script', formatter_class=RawTextHelpFormatter) + parser.add_argument('command', nargs='+', default='full', help='''create: creates an election +encrypt : encrypts votes +tally : launches tally +full: does the whole process''') + parser.add_argument('--eopeers-dir', help='directory with the eopeers packages to use as authorities', default = '/etc/eopeers/') + parser.add_argument('--peers', nargs='+', default=None, help='list of authorities to pair with. defaults to all of them') + parser.add_argument('--mypeerpkg', help='path to my peer package. if empty, calls to eopeers --show-mine', default = None) + parser.add_argument('--vfile', help='json file to read votes from', default = 'votes.json') + parser.add_argument('--vcount', help='number of votes to generate (generates duplicates if more than in json file)', type=int, default = 0) + parser.add_argument('--vmnd', action='store_true', help='encrypts with vmnd') + args = parser.parse_args() + command = args.command[0] + if hasattr(__main__, command): + if(command == 'create') or (command == 'full'): + args.electionId = str(random.randint(100, 10000)) + elif(len(args.command) == 2): + args.electionId = args.command[1] + else: + parser.print_help() + exit(1) + eval(command + "(args)") + else: + parser.print_help() + +if __name__ == "__main__": + main(sys.argv[1:]) From d61b1c2c91d543222bcadce8118c0a82fc4579a2 Mon Sep 17 00:00:00 2001 From: Eduardo Robles Date: Fri, 28 Jul 2023 13:32:48 +0000 Subject: [PATCH 19/26] adding starship config --- .config/starship.toml | 28 ++++++++++++++++++++++++++++ .env | 3 ++- devenv.nix | 3 +++ 3 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 .config/starship.toml diff --git a/.config/starship.toml b/.config/starship.toml new file mode 100644 index 0000000..7a65ab6 --- /dev/null +++ b/.config/starship.toml @@ -0,0 +1,28 @@ +# Get editor completions based on the config schema +"$schema" = 'https://starship.rs/config-schema.json' + +# Inserts a blank line between shell prompts +add_newline = true + +# Replace the '❯' symbol in the prompt with '➜' +[character] # The name of the module we are configuring is 'character' +success_symbol = '[➜](bold green)' # The 'success_symbol' segment is being set to '➜' with the color 'bold green' + +[docker_context] +disabled = true + +[container] +disabled = true + +[nix_shell] +disabled = true + +[python] +disabled = true + +[kubernetes] +disabled = true + +# Disable the package module, hiding it from the prompt completely +[package] +disabled = true \ No newline at end of file diff --git a/.env b/.env index 72965cb..88cd1d4 100644 --- a/.env +++ b/.env @@ -8,4 +8,5 @@ EO_SSL_KEY_PATH="/datastore/certs/cert.key.pem" EO_SSL_CALIST_PATH="/datastore/certs/cert.calist.pem" VFORK_RANDOM_SOURCE="/datastore/mixnet_random_source" COMPOSE_PROFILES=trustee1 -COMPOSE_PROJECT_NAME=trustee-network \ No newline at end of file +COMPOSE_PROJECT_NAME=trustee-network +STARSHIP_CONFIG=/workspaces/election-orchestra/.config/starship.toml diff --git a/devenv.nix b/devenv.nix index 9ad2b90..f0457fe 100644 --- a/devenv.nix +++ b/devenv.nix @@ -35,6 +35,9 @@ # HACK: The venv is missing from PYTHONPATH and PATH so I add them manually enterShell = ''git --version;''; + dotenv.enable = true; + starship.enable = true; + # https://devenv.sh/languages/ languages.nix.enable = true; languages.python = { From e70d844a04bca3c111b9fe34c9deb5e1ca334374 Mon Sep 17 00:00:00 2001 From: Eduardo Robles Date: Sat, 29 Jul 2023 08:38:59 +0000 Subject: [PATCH 20/26] improving env vars management --- .env | 24 ++++++---- .gitignore | 2 +- docker-compose.yml | 51 +++++++++++---------- flake.lock | 24 +++++----- flake.nix | 56 ++++++++++++++--------- scripts/{ => bin}/create-cert.sh | 40 ++++++++-------- scripts/{ => bin}/docker-entrypoint.sh | 9 +++- scripts/{ => bin}/eopeers | 0 scripts/{ => bin}/eotest | 0 scripts/{ => lib}/encrypt-deps/bigint.js | 0 scripts/{ => lib}/encrypt-deps/class.js | 0 scripts/{ => lib}/encrypt-deps/elgamal.js | 0 scripts/{ => lib}/encrypt-deps/jsbn.js | 0 scripts/{ => lib}/encrypt-deps/jsbn2.js | 0 scripts/{ => lib}/encrypt-deps/moment.js | 0 scripts/{ => lib}/encrypt-deps/random.js | 0 scripts/{ => lib}/encrypt-deps/sha1.js | 0 scripts/{ => lib}/encrypt-deps/sha2.js | 0 scripts/{ => lib}/encrypt-deps/sjcl.js | 0 scripts/{ => lib}/encrypt.js | 0 scripts/nix/utils.nix | 25 ++++++++++ 21 files changed, 142 insertions(+), 89 deletions(-) rename scripts/{ => bin}/create-cert.sh (57%) rename scripts/{ => bin}/docker-entrypoint.sh (74%) mode change 100644 => 100755 rename scripts/{ => bin}/eopeers (100%) rename scripts/{ => bin}/eotest (100%) rename scripts/{ => lib}/encrypt-deps/bigint.js (100%) rename scripts/{ => lib}/encrypt-deps/class.js (100%) rename scripts/{ => lib}/encrypt-deps/elgamal.js (100%) rename scripts/{ => lib}/encrypt-deps/jsbn.js (100%) rename scripts/{ => lib}/encrypt-deps/jsbn2.js (100%) rename scripts/{ => lib}/encrypt-deps/moment.js (100%) rename scripts/{ => lib}/encrypt-deps/random.js (100%) rename scripts/{ => lib}/encrypt-deps/sha1.js (100%) rename scripts/{ => lib}/encrypt-deps/sha2.js (100%) rename scripts/{ => lib}/encrypt-deps/sjcl.js (100%) rename scripts/{ => lib}/encrypt.js (100%) create mode 100644 scripts/nix/utils.nix diff --git a/.env b/.env index 88cd1d4..a9b2479 100644 --- a/.env +++ b/.env @@ -1,12 +1,16 @@ -EO_FLASK_RUN_PORT="8080" -EO_MAX_NUM_QUESTIONS_PER_ELECTION="10" -EO_AUTOACCEPT_REQUESTS="True" -EO_PRIVATE_DATA_PATH="/datastore/private" -EO_PUBLIC_DATA_PATH="/datastore/public" -EO_SSL_CERT_PATH="/datastore/certs/cert.pem" -EO_SSL_KEY_PATH="/datastore/certs/cert.key.pem" -EO_SSL_CALIST_PATH="/datastore/certs/cert.calist.pem" -VFORK_RANDOM_SOURCE="/datastore/mixnet_random_source" -COMPOSE_PROFILES=trustee1 +EO_FLASK_APP=election_orchestra.app:app +EO_FLASK_RUN_PORT=8080 +EO_FLASK_RUN_HOST=0.0.0.0 +EO_MAX_NUM_QUESTIONS_PER_ELECTION=10 +EO_AUTOACCEPT_REQUESTS=True +EO_PRIVATE_DATA_PATH=/datastore/private +EO_PUBLIC_DATA_PATH=/datastore/public +EO_SSL_CERT_PATH=/datastore/certs/cert.pem +EO_SSL_KEY_PATH=/datastore/certs/cert.key.pem +EO_SSL_CALIST_PATH=/datastore/certs/cert.calist.pem + +CLASSPATH=/share/java/jecn.jar:/share/java/jgmpmee.jar:/share/java/mixnet.jar +VFORK_RANDOM_SOURCE=/datastore/mixnet_random_source +COMPOSE_PROFILES=trustee1,trustee2 COMPOSE_PROJECT_NAME=trustee-network STARSHIP_CONFIG=/workspaces/election-orchestra/.config/starship.toml diff --git a/.gitignore b/.gitignore index 5ba5f38..0273710 100644 --- a/.gitignore +++ b/.gitignore @@ -14,4 +14,4 @@ result # execution activity.json.log -.data/ \ No newline at end of file +data/ \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 5101d10..bf060d5 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -3,7 +3,7 @@ version: "3.6" services: trustee1: - #restart: unless-stopped + restart: unless-stopped image: election_orchestra:latest environment: - EO_SQLALCHEMY_DATABASE_URI=postgresql+psycopg2://db1:db1@db1/db1 @@ -18,18 +18,21 @@ services: - trustee1 - election-orchestra - #trustee2: - # restart: unless-stopped - # image: election_orchestra:latest - # environment: - # - EO_SQLALCHEMY_DATABASE_URI="postgresql+psycopg2:///db2" - # ports: - # - 8080-8084 - # - 4081/udp - # depends_on: - # - db2 - # volumes: - # - ./data/trustee1/datastore:/datastore + trustee2: + restart: unless-stopped + image: election_orchestra:latest + environment: + - EO_SQLALCHEMY_DATABASE_URI=postgresql+psycopg2://db2:db2@db2/db2 + ports: + - 8080-8084 + - 4081/udp + depends_on: + - db2 + volumes: + - ./data/trustee2/datastore:/datastore + profiles: + - trustee2 + - election-orchestra db1: image: postgres:15 @@ -43,13 +46,15 @@ services: profiles: - trustee1 - db - #db2: - # image: postgres:15 - # environment: - # POSTGRES_USER: db2 - # POSTGRES_PASSWORD: db2 - # ports: - # - "5432" - # volumes: - # - ./data/db1:/var/lib/postgresql/data - \ No newline at end of file + db2: + image: postgres:15 + environment: + POSTGRES_USER: db2 + POSTGRES_PASSWORD: db2 + ports: + - "5432" + volumes: + - ./data/db2:/var/lib/postgresql/data + profiles: + - trustee2 + - db diff --git a/flake.lock b/flake.lock index bfc88e7..8a12608 100644 --- a/flake.lock +++ b/flake.lock @@ -8,11 +8,11 @@ "pre-commit-hooks": "pre-commit-hooks" }, "locked": { - "lastModified": 1690478799, - "narHash": "sha256-KSHlCVZKPMfnDS89KXkC8xZ/QKe+RrhPSO2Zbjik9MA=", + "lastModified": 1690534632, + "narHash": "sha256-kOXS9x5y17VKliC7wZxyszAYrWdRl1JzggbQl0gyo94=", "owner": "cachix", "repo": "devenv", - "rev": "ad6d5cb93a4376b9d68d7e46585cd4e8b964ab92", + "rev": "6568e7e485a46bbf32051e4d6347fa1fed8b2f25", "type": "github" }, "original": { @@ -168,11 +168,11 @@ "nixpkgs": "nixpkgs_2" }, "locked": { - "lastModified": 1690486966, - "narHash": "sha256-afmxdmVGKkKiqCmbVBOluOKum7C38xge+M6DnI1u9gY=", + "lastModified": 1690563975, + "narHash": "sha256-btAl9JTPE3ZU8qU0XrlBUhLdCyBQ2gujaJxzhGJAifo=", "owner": "sequentech", "repo": "mixnet", - "rev": "87a01bf17b3309bf8f61110cc6a1a02271e76fd2", + "rev": "2dc1d5e59e065ccf63bde5caa0b4b37541384f04", "type": "github" }, "original": { @@ -365,11 +365,11 @@ }, "nixpkgs_3": { "locked": { - "lastModified": 1690272529, - "narHash": "sha256-MakzcKXEdv/I4qJUtq/k/eG+rVmyOZLnYNC2w1mB59Y=", + "lastModified": 1690367991, + "narHash": "sha256-2VwOn1l8y6+cu7zjNE8MgeGJNNz1eat1HwHrINeogFA=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "ef99fa5c5ed624460217c31ac4271cfb5cb2502c", + "rev": "c9cf0708f00fbe553319258e48ca89ff9a413703", "type": "github" }, "original": { @@ -402,11 +402,11 @@ "nixpkgs": "nixpkgs_4" }, "locked": { - "lastModified": 1690448151, - "narHash": "sha256-0iZFEfpgAUH8s0IEBPYyT2fQAvndhJm9YQoRZpCoz7U=", + "lastModified": 1690516266, + "narHash": "sha256-giv42E9Bxu07l+YpAq9Em7QvMIPTotI3oERVHZ1O3m0=", "owner": "nix-community", "repo": "poetry2nix", - "rev": "78fc8882411c29c8eb5f162b09fcafe08b8b03a3", + "rev": "aed88688ec9c281882ef400950c5e1c78094d20d", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 8e88cf2..ddd2635 100644 --- a/flake.nix +++ b/flake.nix @@ -24,10 +24,6 @@ poetry2nixFlake.url = "github:nix-community/poetry2nix"; }; - nixConfig = { - extra-trusted-public-keys = "devenv.cachix.org-1:w1cLUi8dv3hnoSPGAuibQv+f9TZLr6cv/Hm9XgU50cw="; - extra-substituters = "https://devenv.cachix.org"; - }; outputs = inputs@{ flake-parts, poetry2nixFlake, mixnet, ... }: flake-parts.lib.mkFlake { inherit inputs; } { imports = [ @@ -73,11 +69,20 @@ # election_orchestra = poetry2nix.mkPoetryApplication { - projectDir = ./.; + projectDir = poetry2nix.cleanPythonSources { src = ./.; }; python = python; overrides = election_orchestra-overrides; + postInstall = '' + mkdir -p $out/bin $out/lib/node_modules/election_orchestra/ + cp scripts/bin/* $out/bin/ + cp -r scripts/lib/* $out/lib/node_modules/election_orchestra/ + ''; }; - servicePort = "9090"; + utils = import ./scripts/nix/utils.nix {inherit lib;}; + envFile = (utils.loadEnv ./.env); + servicePort = if (builtins.hasAttr "EO_FLASK_RUN_PORT" envFile) + then envFile.EO_FLASK_RUN_PORT + else "9090"; dockerImage = nix2container.nix2container.buildImage { name = "election_orchestra"; tag = "latest"; @@ -88,29 +93,33 @@ pkgs.coreutils pkgs.nodejs python.pkgs.gunicorn + pkgs.openssl + pkgs.jre8 + pkgs.gmp mixnetPackages.mixnet election_orchestra.dependencyEnv ]; - pathsToLink = [ "/bin" "/lib" ]; + pathsToLink = [ "/bin" "/lib" "/share" ]; }; config = { - entrypoint = [ - #"${election_orchestra.dependencyEnv}/bin/flask" "run" - "${python.pkgs.gunicorn}/bin/gunicorn" - "-b" "0.0.0.0:${servicePort}" - "--log-level" "debug" - "election_orchestra.app:app" - ]; - Env = lib.mapAttrsToList (name: value: "${name}=${value}") { - FLASK_APP = "election_orchestra.app:app"; - FLASK_RUN_PORT = "${servicePort}"; - FLASK_RUN_HOST = "0.0.0.0"; - PYTHONPATH = "${election_orchestra.dependencyEnv}/lib/python3.10/site-packages"; - }; + # docker-entrypoint.sh comes from election_orchestra package, + # especifically from /scripts/bin/docker-entrypoint.sh + entrypoint = ["/bin/docker-entrypoint.sh"]; + + # Load env that is the result of the ".env" file plus some + # overriding from this flake + Env = lib.mapAttrsToList + (name: value: "${name}=${value}") + (let + baseEnv = { + FLASK_RUN_PORT = "${servicePort}"; + PYTHONPATH = "${election_orchestra.dependencyEnv}/lib/python3.10/site-packages"; + }; + joined = envFile // baseEnv; + in joined); ExposedPorts = { "${servicePort}/tcp" = {}; }; - }; # This is to not rebuild everything on code changes layers = [ @@ -120,6 +129,11 @@ pkgs.bashInteractive pkgs.coreutils pkgs.nodejs + pkgs.openssl + ]; + }) + (nix2container.nix2container.buildLayer { + deps = [ mixnetPackages.mixnet ]; }) diff --git a/scripts/create-cert.sh b/scripts/bin/create-cert.sh similarity index 57% rename from scripts/create-cert.sh rename to scripts/bin/create-cert.sh index 3b96847..0b12e65 100755 --- a/scripts/create-cert.sh +++ b/scripts/bin/create-cert.sh @@ -4,24 +4,24 @@ set -eux -o pipefail CERT_COUNTRY="${CERT_COUNTRY:-EN}" CERT_STATE="${CERT_STATE:-New York}" -LOCALITY="${LOCALITY:-New York}" -ORG="${ORG:-Example}" -ORG_UNIT="${ORG_UNIT:-Example}" +CERT_LOCALITY="${CERT_LOCALITY:-New York}" +CERT_ORG="${CERT_ORG:-Example}" +CERT_ORG_UNIT="${CERT_ORG_UNIT:-Example}" HOST="${HOST:-some-hostname}" -COMMON_NAME="${COMMON_NAME:-some-hostname}" -EMAIL="${EMAIL-info@example.com}" -DNS1="${DNS1:-dns1}" -KEY_LENGTH="${KEY_LENGTH:-4096}" -KEY_ALGORITHM="${KEY_ALGORITHM:-rsa}" +CERT_COMMON_NAME="${CERT_COMMON_NAME:-some-hostname}" +CERT_EMAIL="${CERT_EMAIL-info@example.com}" +CERT_DNS1="${CERT_DNS1:-dns1}" +CERT_KEY_LENGTH="${CERT_KEY_LENGTH:-4096}" +CERT_KEY_ALGORITHM="${CERT_KEY_ALGORITHM:-rsa}" -CERT_DIR="${CERT_DIR:-/datastore/certs/}" +CERT_DIR="${CERT_DIR:-/datastore/certs}" CERT_PATH="${CERT_PATH:-$CERT_DIR/cert.pem}" CERT_KEY_PATH="${CERT_KEY_PATH:-$CERT_DIR/key-nopass.pem}" CERT_CALIST_PATH="${CERT_CALIST_PATH:-$CERT_DIR/calist}" CERT_DAYS="${CERT_DAYS:-3650}" CERT_DIGEST="${CERT_DIGEST:-sha256}" -CREATE_CERT=${CREATE_CERT:-true} +CREATE_CERT=${CREATE_CERT:-false} CALIST_COPY="" if [ ! -f "${CERT_PATH}" ]; then @@ -35,27 +35,27 @@ if [ true == "$CREATE_CERT" ]; then openssl req \ -nodes \ -x509 \ - -newkey "${KEY_ALGORITHM}:${KEY_LENGTH}" \ + -newkey "${CERT_KEY_ALGORITHM}:${CERT_KEY_LENGTH}" \ -extensions v3_ca \ -keyout "${CERT_KEY_PATH}" \ -out "${CERT_PATH}" \ -days "${CERT_DAYS}" \ - -subj "/C=${CERT_COUNTRY}/ST=${CERT_STATE}/L=${LOCALITY}/O=${ORG}/OU=${ORG_UNIT}/CN=${COMMON_NAME}/emailAddress=${EMAIL}" \ + -subj "/C=${CERT_COUNTRY}/ST=${CERT_STATE}/L=${CERT_LOCALITY}/O=${CERT_ORG}/OU=${CERT_ORG_UNIT}/CN=${CERT_COMMON_NAME}/emailAddress=${CERT_EMAIL}" \ -config <(cat <<-EOF [req] -default_bits = ${KEY_LENGTH} +default_bits = ${CERT_KEY_LENGTH} default_md = ${CERT_DIGEST} distinguished_name = req_distinguished_name x509_extensions = v3_ca [ req_distinguished_name ] countryName = ${CERT_COUNTRY} -stateOrProvinceName = ${STATE} -localityName = ${LOCALITY} -organizationName = ${ORG} -organizationalUnitName = ${ORG_UNIT} -commonName = ${COMMON_NAME} -emailAddress = ${EMAIL} +stateOrProvinceName = ${CERT_STATE} +localityName = ${CERT_LOCALITY} +organizationName = ${CERT_ORG} +organizationalUnitName = ${CERT_ORG_UNIT} +commonName = ${CERT_COMMON_NAME} +emailAddress = ${CERT_EMAIL} [ v3_ca ] # The extentions to add to a self-signed cert @@ -63,7 +63,7 @@ subjectKeyIdentifier = hash authorityKeyIdentifier = keyid:always,issuer:always basicConstraints = CA:TRUE keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment, keyAgreement, keyCertSign -subjectAltName = DNS:${DNS1},DNS:${COMMON_NAME} +subjectAltName = DNS:${CERT_DNS1},DNS:${CERT_COMMON_NAME} issuerAltName = issuer:copy EOF diff --git a/scripts/docker-entrypoint.sh b/scripts/bin/docker-entrypoint.sh old mode 100644 new mode 100755 similarity index 74% rename from scripts/docker-entrypoint.sh rename to scripts/bin/docker-entrypoint.sh index 1ad94f7..37cf090 --- a/scripts/docker-entrypoint.sh +++ b/scripts/bin/docker-entrypoint.sh @@ -3,6 +3,7 @@ set -eux -o pipefail LOG_LEVEL="${LOG_LEVEL:-debug}" +FLASK_APP="${FLASK_APP:-election_orchestra.app:app}" FLASK_RUN_HOST="${FLASK_RUN_HOST:-0.0.0.0}" VFORK_RANDOM_SOURCE="${VFORK_RANDOM_SOURCE:-/datastore/mixnet_random_source}" VFORK_RANDOM_SEED="${VFORK_RANDOM_SEED:-/mixnet_random_seed}" @@ -18,10 +19,14 @@ else echo "mixnet: '${VFORK_RANDOM_SOURCE}' found" fi +echo "election-orchestra: ensuring DB tables are in place.." +python -m election_orchestra.app --createdb +echo "election-orchestra: database tables created" + echo "election-orchestra: launching gunicorn service.." gunicorn \ -b "${FLASK_RUN_HOST}:${FLASK_RUN_PORT}" \ - --log-level" "${LOG_LEVEL}" \ + --log-level "${LOG_LEVEL}" \ "${FLASK_APP}" -]; + diff --git a/scripts/eopeers b/scripts/bin/eopeers similarity index 100% rename from scripts/eopeers rename to scripts/bin/eopeers diff --git a/scripts/eotest b/scripts/bin/eotest similarity index 100% rename from scripts/eotest rename to scripts/bin/eotest diff --git a/scripts/encrypt-deps/bigint.js b/scripts/lib/encrypt-deps/bigint.js similarity index 100% rename from scripts/encrypt-deps/bigint.js rename to scripts/lib/encrypt-deps/bigint.js diff --git a/scripts/encrypt-deps/class.js b/scripts/lib/encrypt-deps/class.js similarity index 100% rename from scripts/encrypt-deps/class.js rename to scripts/lib/encrypt-deps/class.js diff --git a/scripts/encrypt-deps/elgamal.js b/scripts/lib/encrypt-deps/elgamal.js similarity index 100% rename from scripts/encrypt-deps/elgamal.js rename to scripts/lib/encrypt-deps/elgamal.js diff --git a/scripts/encrypt-deps/jsbn.js b/scripts/lib/encrypt-deps/jsbn.js similarity index 100% rename from scripts/encrypt-deps/jsbn.js rename to scripts/lib/encrypt-deps/jsbn.js diff --git a/scripts/encrypt-deps/jsbn2.js b/scripts/lib/encrypt-deps/jsbn2.js similarity index 100% rename from scripts/encrypt-deps/jsbn2.js rename to scripts/lib/encrypt-deps/jsbn2.js diff --git a/scripts/encrypt-deps/moment.js b/scripts/lib/encrypt-deps/moment.js similarity index 100% rename from scripts/encrypt-deps/moment.js rename to scripts/lib/encrypt-deps/moment.js diff --git a/scripts/encrypt-deps/random.js b/scripts/lib/encrypt-deps/random.js similarity index 100% rename from scripts/encrypt-deps/random.js rename to scripts/lib/encrypt-deps/random.js diff --git a/scripts/encrypt-deps/sha1.js b/scripts/lib/encrypt-deps/sha1.js similarity index 100% rename from scripts/encrypt-deps/sha1.js rename to scripts/lib/encrypt-deps/sha1.js diff --git a/scripts/encrypt-deps/sha2.js b/scripts/lib/encrypt-deps/sha2.js similarity index 100% rename from scripts/encrypt-deps/sha2.js rename to scripts/lib/encrypt-deps/sha2.js diff --git a/scripts/encrypt-deps/sjcl.js b/scripts/lib/encrypt-deps/sjcl.js similarity index 100% rename from scripts/encrypt-deps/sjcl.js rename to scripts/lib/encrypt-deps/sjcl.js diff --git a/scripts/encrypt.js b/scripts/lib/encrypt.js similarity index 100% rename from scripts/encrypt.js rename to scripts/lib/encrypt.js diff --git a/scripts/nix/utils.nix b/scripts/nix/utils.nix new file mode 100644 index 0000000..a33bbfa --- /dev/null +++ b/scripts/nix/utils.nix @@ -0,0 +1,25 @@ +{ lib, ... }: +{ + # Load env-file and convert its contents to a Nix set (dictionary) + # Example usage: + # loadEnv "/path/to/your/.env" + loadEnv = filePath: + let + envContent = lib.fileContents(filePath); + nonEmptyLines = builtins.filter + (line: line != "") + (lib.splitString "\n" envContent); + keyValues = builtins.map + ( + line: + let + splitLine = lib.splitString "=" line; + in { + name = builtins.head (splitLine); + value = lib.last (splitLine); + } + ) + nonEmptyLines; + in + lib.listToAttrs keyValues; +} \ No newline at end of file From 2a6cb3f4aac4c26e4ccb72fee4fa458f62efbab8 Mon Sep 17 00:00:00 2001 From: Eduardo Robles Date: Sun, 30 Jul 2023 08:56:11 +0000 Subject: [PATCH 21/26] WIP --- .env | 16 ++- docker-compose.yml | 104 +++++++++++++-- election_orchestra/app.py | 6 +- election_orchestra/base_settings.py | 53 -------- election_orchestra/second_settings.py | 38 ------ election_orchestra/utils.py | 8 +- flake.nix | 30 ++++- scripts/bin/create-cert.sh | 21 ++- scripts/bin/docker-entrypoint.sh | 71 +++++++++- scripts/bin/eopeers | 121 ++++++++---------- scripts/bin/eotest | 49 ++++--- scripts/nginx/nginx.conf | 2 + scripts/nginx/templates/default.conf.template | 100 +++++++++++++++ scripts/nix/utils.nix | 15 ++- 14 files changed, 411 insertions(+), 223 deletions(-) delete mode 100644 election_orchestra/base_settings.py delete mode 100644 election_orchestra/second_settings.py create mode 100644 scripts/nginx/nginx.conf create mode 100644 scripts/nginx/templates/default.conf.template diff --git a/.env b/.env index a9b2479..afee408 100644 --- a/.env +++ b/.env @@ -1,16 +1,30 @@ EO_FLASK_APP=election_orchestra.app:app -EO_FLASK_RUN_PORT=8080 EO_FLASK_RUN_HOST=0.0.0.0 +EO_SERVICE_PORT=8080 +EO_FLASK_RUN_PORT=8081 +EO_VFORK_SERVER_PORT=8082 +EO_TEST_PORT=8083 +EO_VFORK_HINT_SERVER_PORT=8084 EO_MAX_NUM_QUESTIONS_PER_ELECTION=10 EO_AUTOACCEPT_REQUESTS=True EO_PRIVATE_DATA_PATH=/datastore/private EO_PUBLIC_DATA_PATH=/datastore/public +EO_SSL_CERT_DIR=/datastore/certs/ EO_SSL_CERT_PATH=/datastore/certs/cert.pem EO_SSL_KEY_PATH=/datastore/certs/cert.key.pem EO_SSL_CALIST_PATH=/datastore/certs/cert.calist.pem CLASSPATH=/share/java/jecn.jar:/share/java/jgmpmee.jar:/share/java/mixnet.jar VFORK_RANDOM_SOURCE=/datastore/mixnet_random_source + +TRUSTEES=trustee1,trustee2 +ETCD_ENDPOINT=http://etcd_service:2379 + +NGINX_ERROR_LOG_LEVEL=info +NGINX_HTTP_MAX_BODY_SIZE=2m +NGINX_HTTP_CLIENT_MAX_HEADER_SIZE=8k + COMPOSE_PROFILES=trustee1,trustee2 COMPOSE_PROJECT_NAME=trustee-network + STARSHIP_CONFIG=/workspaces/election-orchestra/.config/starship.toml diff --git a/docker-compose.yml b/docker-compose.yml index bf060d5..629f159 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,17 +1,42 @@ # export COMPOSE_PROJECT_NAME=trustee-network # docker compose up --build --force-recreate version: "3.6" + +x-trustee-common: &trustee_common + restart: unless-stopped + image: election_orchestra:latest + environment: + ETCD_ENDPOINT: http://etcd_service:2379 + TRUSTEES: trustee1,trustee2 + ports: + - 8081-8083 + - 8084/udp + depends_on: &trustee_depends_on + etcd_service: + required: true + condition: service_started + +x-trustee-nginx-common: &trustee_nginx_common + restart: unless-stopped + image: nginx:1.25 + env_file: .env + ports: + - "8080" + environment: &trustee_nginx_env + NGINX_PORT: "8080" + DOLLAR: "$" + services: trustee1: - restart: unless-stopped - image: election_orchestra:latest + <<: *trustee_common environment: - - EO_SQLALCHEMY_DATABASE_URI=postgresql+psycopg2://db1:db1@db1/db1 - ports: - - 8080-8084 - - 4081/udp + SERVICE_NAME: trustee1 + CERT_COMMON_NAME: trustee1_nginx + EO_SQLALCHEMY_DATABASE_URI: postgresql+psycopg2://db1:db1@db1/db1 depends_on: - - db1 + <<: *trustee_depends_on + db1: + condition: service_started volumes: - ./data/trustee1/datastore:/datastore profiles: @@ -19,21 +44,53 @@ services: - election-orchestra trustee2: - restart: unless-stopped - image: election_orchestra:latest + <<: *trustee_common environment: - - EO_SQLALCHEMY_DATABASE_URI=postgresql+psycopg2://db2:db2@db2/db2 - ports: - - 8080-8084 - - 4081/udp + SERVICE_NAME: trustee2 + CERT_COMMON_NAME: trustee2_nginx + EO_SQLALCHEMY_DATABASE_URI: postgresql+psycopg2://db2:db2@db2/db2 depends_on: - - db2 + <<: *trustee_depends_on + db2: + condition: service_started volumes: - ./data/trustee2/datastore:/datastore profiles: - trustee2 - election-orchestra + trustee1_nginx: + <<: *trustee_nginx_common + depends_on: + - trustee1 + volumes: + - ./scripts/nginx:/etc/nginx + - ./data/trustee1/nginx:/etc/nginx/conf.d + - ./data/trustee1/datastore:/datastore + environment: + <<: *trustee_nginx_env + UPSTREAM_SERVICE_NAME: trustee1 + NGINX_HOST: trustee1_nginx + profiles: + - nginx + - trustee1 + + trustee2_nginx: + <<: *trustee_nginx_common + depends_on: + - trustee2 + volumes: + - ./scripts/nginx:/etc/nginx + - ./data/trustee2/nginx:/etc/nginx/conf.d + - ./data/trustee2/datastore:/datastore + environment: + <<: *trustee_nginx_env + UPSTREAM_SERVICE_NAME: trustee2 + NGINX_HOST: trustee2_nginx + profiles: + - nginx + - trustee2 + db1: image: postgres:15 environment: @@ -58,3 +115,22 @@ services: profiles: - trustee2 - db + + etcd_service: + image: quay.io/coreos/etcd:v3.4.0 + command: > + /usr/local/bin/etcd + --name etcd_service + --listen-client-urls http://0.0.0.0:2379 + --advertise-client-urls http://etcd_service:2379 + --listen-peer-urls http://0.0.0.0:2380 + --initial-advertise-peer-urls http://etcd_service:2380 + --initial-cluster=etcd_service=http://etcd_service:2380 + --initial-cluster-token etcd-cluster-1 + --initial-cluster-state new + --heartbeat-interval=250 + --election-timeout=1250 + ports: + - 2379-2380 + volumes: + - ./data/etcd_service:/etcd_data \ No newline at end of file diff --git a/election_orchestra/app.py b/election_orchestra/app.py index 87f3188..d0f5926 100755 --- a/election_orchestra/app.py +++ b/election_orchestra/app.py @@ -29,14 +29,14 @@ class DefaultConfig(FrestqDefaultConfig): # URL to our HTTP server VFORK_SERVER_URL = 'http://127.0.0.1' - VFORK_SERVER_PORT_RANGE = [4081, 4083] + VFORK_SERVER_PORT = "8082" # Socket address given as : to our hint server. # A hint server is a simple UDP server that reduces latency and # traffic on the HTTP servers. VFORK_HINT_SERVER_SOCKET = '127.0.0.1' - VFORK_HINT_SERVER_PORT_RANGE = [8081, 8083] + VFORK_HINT_SERVER_PORT = "8084" ROOT_PATH = os.path.split(os.path.abspath(__file__))[0] @@ -71,7 +71,7 @@ def configure_app(app): for variable, value in os.environ.items(): if variable.startswith(config_var_prefix): env_name = variable.split(config_var_prefix)[1] - logging.debug(f"SET:from-env-var config.${env_name} = ${value}") + logging.debug(f"SET:from-env-var config.{env_name} = {value}") setattr(config_object, env_name, value) app.configure_app(scheduler=False, config_object=config_object) app.register_blueprint(public_api, url_prefix='/public_api') diff --git a/election_orchestra/base_settings.py b/election_orchestra/base_settings.py deleted file mode 100644 index fe25751..0000000 --- a/election_orchestra/base_settings.py +++ /dev/null @@ -1,53 +0,0 @@ -# -# SPDX-FileCopyrightText: 2013-2021 Sequent Tech Inc -# -# SPDX-License-Identifier: AGPL-3.0-only -# -DEBUG = True - -# see https://stackoverflow.com/questions/33738467/how-do-i-know-if-i-can-disable-sqlalchemy-track-modifications/33790196#33790196 -SQLALCHEMY_TRACK_MODIFICATIONS = False - -ROOT_URL = 'https://127.0.0.1:5000/api/queues' - -# URL to our HTTP server -VFORK_SERVER_URL = 'http://127.0.0.1' - -VFORK_SERVER_PORT_RANGE = [4081, 4083] - -# Socket address given as : to our hint server. -# A hint server is a simple UDP server that reduces latency and -# traffic on the HTTP servers. -VFORK_HINT_SERVER_SOCKET = '127.0.0.1' - -VFORK_HINT_SERVER_PORT_RANGE = [8081, 8083] - -import os -ROOT_PATH = os.path.split(os.path.abspath(__file__))[0] - -SQLALCHEMY_DATABASE_URI = 'sqlite:///%s/db.sqlite' % ROOT_PATH - -PRIVATE_DATA_PATH = os.path.join(ROOT_PATH, 'datastore/private') -PUBLIC_DATA_PATH = '/srv/election-orchestra/server1/public' -PUBLIC_DATA_BASE_URL = 'https://127.0.0.1:5000/public_data' - -# security configuration -SSL_CERT_PATH = '%s/certs/selfsigned/cert.pem' % ROOT_PATH -SSL_KEY_PATH = '%s/certs/selfsigned/key-nopass.pem' % ROOT_PATH -SSL_CALIST_PATH = '%s/certs/selfsigned/calist' % ROOT_PATH -ALLOW_ONLY_SSL_CONNECTIONS = True - -AUTOACCEPT_REQUESTS = True - -MAX_NUM_QUESTIONS_PER_ELECTION = 40 - -KILL_ALL_VFORK_BEFORE_START_NEW = False - -QUEUES_OPTIONS = { - 'launch_task': { - 'max_threads': 1 - }, - 'mixnet_queue': { - 'max_threads': 1, - } -} \ No newline at end of file diff --git a/election_orchestra/second_settings.py b/election_orchestra/second_settings.py deleted file mode 100644 index 019284c..0000000 --- a/election_orchestra/second_settings.py +++ /dev/null @@ -1,38 +0,0 @@ -# -*- coding: utf-8 -*- - -# -# SPDX-FileCopyrightText: 2013-2021 Sequent Tech Inc -# -# SPDX-License-Identifier: AGPL-3.0-only -# -import os -ROOT_PATH = os.path.split(os.path.abspath(__file__))[0] -SQLALCHEMY_DATABASE_URI = 'sqlite:///%s/db2.sqlite' % ROOT_PATH - -PRIVATE_DATA_PATH = os.path.join(ROOT_PATH, 'datastore2/private') -PUBLIC_DATA_PATH = '/srv/election-orchestra/server2/public' -PUBLIC_DATA_BASE_URL = 'https://127.0.0.1:5001/public_data' - -SERVER_NAME = '127.0.0.1:5001' - -SERVER_PORT = 5001 - -ROOT_URL = 'https://127.0.0.1:5001/api/queues' - -VFORK_SERVER_PORT_RANGE = [4084, 4087] - -VFORK_HINT_SERVER_PORT_RANGE = [8084, 8087] - -# security configuration -SSL_CERT_PATH = '%s/certs/selfsigned2/cert.pem' % ROOT_PATH -SSL_KEY_PATH = '%s/certs/selfsigned2/key-nopass.pem' % ROOT_PATH -SSL_CALIST_PATH = '%s/certs/selfsigned2/calist' % ROOT_PATH -ALLOW_ONLY_SSL_CONNECTIONS = True - -AUTOACCEPT_REQUESTS = True - -QUEUES_OPTIONS = { - 'mixnet_queue': { - 'max_threads': 1, - } -} \ No newline at end of file diff --git a/election_orchestra/utils.py b/election_orchestra/utils.py index e3bd42e..3702146 100644 --- a/election_orchestra/utils.py +++ b/election_orchestra/utils.py @@ -30,15 +30,15 @@ def get_server_url(): ''' Return a server url that can be used ''' - return "%s:%d" % (app.config.get('VFORK_SERVER_URL', ''), - app.config.get('VFORK_SERVER_PORT_RANGE', '')[0]) + return "%s:%s" % (app.config.get('VFORK_SERVER_URL', ''), + app.config.get('VFORK_SERVER_PORT', '')) def get_hint_server_url(): ''' Return a hint server url that can be used ''' - return "%s:%d" % (app.config.get('VFORK_HINT_SERVER_SOCKET', ''), - app.config.get('VFORK_HINT_SERVER_PORT_RANGE', '')[0]) + return "%s:%s" % (app.config.get('VFORK_HINT_SERVER_SOCKET', ''), + app.config.get('VFORK_HINT_SERVER_PORT', '')) def call_cmd(cmd, timeout=-1, output_filter=None, cwd=None, check_ret=None): ''' diff --git a/flake.nix b/flake.nix index ddd2635..8306151 100644 --- a/flake.nix +++ b/flake.nix @@ -80,9 +80,22 @@ }; utils = import ./scripts/nix/utils.nix {inherit lib;}; envFile = (utils.loadEnv ./.env); - servicePort = if (builtins.hasAttr "EO_FLASK_RUN_PORT" envFile) - then envFile.EO_FLASK_RUN_PORT - else "9090"; + flaskPort = utils.getAttrDefault + "EO_FLASK_RUN_PORT" + envFile + "8081"; + vForkServerPort = utils.getAttrDefault + "EO_VFORK_SERVER_PORT" + envFile + "8082"; + eotestPort = utils.getAttrDefault + "EO_TEST_PORT" + envFile + "8083"; + vForkHintServerPort = utils.getAttrDefault + "EO_VFORK_HINT_SERVER_PORT" + envFile + "8084"; dockerImage = nix2container.nix2container.buildImage { name = "election_orchestra"; tag = "latest"; @@ -95,7 +108,9 @@ python.pkgs.gunicorn pkgs.openssl pkgs.jre8 + pkgs.vim pkgs.gmp + pkgs.etcd mixnetPackages.mixnet election_orchestra.dependencyEnv ]; @@ -112,13 +127,16 @@ (name: value: "${name}=${value}") (let baseEnv = { - FLASK_RUN_PORT = "${servicePort}"; + FLASK_RUN_PORT = "${flaskPort}"; PYTHONPATH = "${election_orchestra.dependencyEnv}/lib/python3.10/site-packages"; }; joined = envFile // baseEnv; in joined); - ExposedPorts = { - "${servicePort}/tcp" = {}; + ExposedPorts = { + "${flaskPort}/tcp" = {}; + "${vForkServerPort}/tcp" = {}; + "${eotestPort}/tcp" = {}; + "${vForkHintServerPort}/udp" = {}; }; }; # This is to not rebuild everything on code changes diff --git a/scripts/bin/create-cert.sh b/scripts/bin/create-cert.sh index 0b12e65..7065a10 100755 --- a/scripts/bin/create-cert.sh +++ b/scripts/bin/create-cert.sh @@ -14,20 +14,20 @@ CERT_DNS1="${CERT_DNS1:-dns1}" CERT_KEY_LENGTH="${CERT_KEY_LENGTH:-4096}" CERT_KEY_ALGORITHM="${CERT_KEY_ALGORITHM:-rsa}" -CERT_DIR="${CERT_DIR:-/datastore/certs}" -CERT_PATH="${CERT_PATH:-$CERT_DIR/cert.pem}" -CERT_KEY_PATH="${CERT_KEY_PATH:-$CERT_DIR/key-nopass.pem}" -CERT_CALIST_PATH="${CERT_CALIST_PATH:-$CERT_DIR/calist}" +EO_SSL_CERT_DIR="${EO_SSL_CERT_DIR:-/datastore/certs}" +EO_SSL_CERT_PATH="${EO_SSL_CERT_PATH:-$EO_SSL_CERT_DIR/cert.pem}" +EO_SSL_KEY_PATH="${EO_SSL_KEY_PATH:-$EO_SSL_CERT_DIR/cert.key.pem}" +EO_SSL_CALIST_PATH="${EO_SSL_CALIST_PATH:-$EO_SSL_CERT_DIR/cert.calist.pem}" CERT_DAYS="${CERT_DAYS:-3650}" CERT_DIGEST="${CERT_DIGEST:-sha256}" CREATE_CERT=${CREATE_CERT:-false} CALIST_COPY="" -if [ ! -f "${CERT_PATH}" ]; then +if [ ! -f "${EO_SSL_CERT_PATH}" ]; then CREATE_CERT=true - if [ ! -d "${CERT_DIR}" ]; then - mkdir -p "${CERT_DIR}" + if [ ! -d "${EO_SSL_CERT_DIR}" ]; then + mkdir -p "${EO_SSL_CERT_DIR}" fi fi @@ -37,8 +37,8 @@ if [ true == "$CREATE_CERT" ]; then -x509 \ -newkey "${CERT_KEY_ALGORITHM}:${CERT_KEY_LENGTH}" \ -extensions v3_ca \ - -keyout "${CERT_KEY_PATH}" \ - -out "${CERT_PATH}" \ + -keyout "${EO_SSL_KEY_PATH}" \ + -out "${EO_SSL_CERT_PATH}" \ -days "${CERT_DAYS}" \ -subj "/C=${CERT_COUNTRY}/ST=${CERT_STATE}/L=${CERT_LOCALITY}/O=${CERT_ORG}/OU=${CERT_ORG_UNIT}/CN=${CERT_COMMON_NAME}/emailAddress=${CERT_EMAIL}" \ -config <(cat <<-EOF @@ -68,6 +68,5 @@ issuerAltName = issuer:copy EOF ) - cp "$CERT_PATH" "$CERT_CALIST_PATH" - echo "$CALIST_COPY" >> "$CERT_CALIST_PATH" + cp "$EO_SSL_CERT_PATH" "$EO_SSL_CALIST_PATH" fi diff --git a/scripts/bin/docker-entrypoint.sh b/scripts/bin/docker-entrypoint.sh index 37cf090..aef4353 100755 --- a/scripts/bin/docker-entrypoint.sh +++ b/scripts/bin/docker-entrypoint.sh @@ -2,15 +2,80 @@ set -eux -o pipefail +echo "entrypoint: state of the vars before assuming defaults:" +export + +# Set some default values LOG_LEVEL="${LOG_LEVEL:-debug}" FLASK_APP="${FLASK_APP:-election_orchestra.app:app}" -FLASK_RUN_HOST="${FLASK_RUN_HOST:-0.0.0.0}" +EO_FLASK_RUN_HOST="${EO_FLASK_RUN_HOST:-0.0.0.0}" +EO_FLASK_RUN_PORT="${EO_FLASK_RUN_PORT:-8081}" VFORK_RANDOM_SOURCE="${VFORK_RANDOM_SOURCE:-/datastore/mixnet_random_source}" VFORK_RANDOM_SEED="${VFORK_RANDOM_SEED:-/mixnet_random_seed}" RANDOM_DEVICE="${RANDOM_DEVICE:-/dev/urandom}" +TRUSTEES="${TRUSTEES:-trustee1,trustee2}" +ETCD_ENDPOINT="${ETCD_ENDPOINT:-}" +SERVICE_NAME="${SERVICE_NAME:-trustee1}" + +echo "entrypoint: state of the vars after assuming defaults:" +export + +run_cmd() { + set +e # Temporarily disable the 'errexit' option + [ -f exit_status.txt ] && rm -f exit_status.txt + touch exit_status.txt + export run_stdout=$($1; echo $? > exit_status.txt) + export run_exit_status=$(cat exit_status.txt) + [ -f exit_status.txt ] && rm -f exit_status.txt + set -e # Re-enable the 'errexit' option +} +# Ensure we have our own TLS certificates create-cert.sh +# we will now upload our certificate and update the other trustees certificates +# but only if $ETCD_ENDPOINT var is set +if [[ ! -z "${ETCD_ENDPOINT}" ]]; then + # Upload our eopeer package to etcd + EO_PACKAGE="$(eopeers --show-mine)" + etcdctl --endpoints="${ETCD_ENDPOINT}" put "/${SERVICE_NAME}/eopackage" "${EO_PACKAGE}" + + + IFS=',' read -ra trustees_array <<< "$TRUSTEES" + + # Iterate over the array, wait to get their package; and update it if it's different + for trustee in "${trustees_array[@]}"; do + # skip ourselves + if [[ "$SERVICE_NAME" == "$trustee" ]]; then + continue + fi + + # obtain the peer package from etcd + NEW_EO_PACKAGE="" + while [[ -z "${NEW_EO_PACKAGE}" ]]; do + echo "eopeers: obtaining $trustee package.." + NEW_EO_PACKAGE="$(etcdctl --endpoints=${ETCD_ENDPOINT} get --print-value-only "/${trustee}/eopackage")" + sleep 2 + done + echo "eopeers: obtained the peer package for $trustee" + run_cmd "eopeers --show ${trustee}" + OLD_EO_PACKAGE="$run_stdout" + + # the package was installed but it changed, so updated it + if [[ "$run_exit_status" == "0" && "${OLD_EO_PACKAGE}" != "${NEW_EO_PACKAGE}" ]]; then + echo "eopeers: the $trustee package was installed but it changed, so reinstalling it.." + eopeers --uninstall "${trustee}" + echo "${NEW_EO_PACKAGE}" > "${trustee}-tmp.pkg" + eopeers --install "${trustee}-tmp.pkg" + elif [[ "$run_exit_status" != "0" ]]; then + echo "eopeers: the $trustee package was not installed, so installing it.." + echo "${NEW_EO_PACKAGE}" > "${trustee}-tmp.pkg" + eopeers --install "${trustee}-tmp.pkg" + fi + done +fi + +# Ensure mixnet randomness is initialized echo "mixnet: checking '${VFORK_RANDOM_SOURCE}'.." if [ ! -f "${VFORK_RANDOM_SOURCE}" ]; then echo "mixnet: '${VFORK_RANDOM_SOURCE}' not found, initializing it.." @@ -19,14 +84,16 @@ else echo "mixnet: '${VFORK_RANDOM_SOURCE}' found" fi +# Ensure that the DB tables are created echo "election-orchestra: ensuring DB tables are in place.." python -m election_orchestra.app --createdb echo "election-orchestra: database tables created" echo "election-orchestra: launching gunicorn service.." +# Launch the election-orchestra service gunicorn \ - -b "${FLASK_RUN_HOST}:${FLASK_RUN_PORT}" \ + -b "${EO_FLASK_RUN_HOST}:${EO_FLASK_RUN_PORT}" \ --log-level "${LOG_LEVEL}" \ "${FLASK_APP}" diff --git a/scripts/bin/eopeers b/scripts/bin/eopeers index fc192e2..067d395 100755 --- a/scripts/bin/eopeers +++ b/scripts/bin/eopeers @@ -17,37 +17,25 @@ import argparse import os -import sys +import socket import json import subprocess +EO_VERSION = 1 EO_CONF = os.environ.get('EO_CONF', '/datastore/eoconf.json') -EO_PEER_LIST = os.environ.get('EO_CONF', '/datastore/eopeerlist/') -CERT_DIR = os.environ.get('CERT_DIR', '/datastore/certs') -CERT_PATH = os.environ.get('CERT_PATH', f'${CERT_DIR}/cert.pem') -CERT_KEY_PATH = os.environ.get('CERT_KEY_PATH', f'${CERT_DIR}/key-nopass.pem') -CERT_CALIST_PATH = os.environ.get('CERT_CALIST_PATH', f'${CERT_DIR}/calist') +EO_PEER_LIST = os.environ.get('EO_PEER_LIST', '/datastore/eopeerlist/') +EO_CERT_DIR = os.environ.get('EO_CERT_DIR', '/datastore/certs') +EO_CERT_PATH = os.environ.get('EO_CERT_PATH', f'{EO_CERT_DIR}/cert.pem') +EO_CERT_KEY_PATH = os.environ.get( + 'EO_CERT_KEY_PATH', f'{EO_CERT_DIR}/cert.key.pem' +) +EO_CERT_CALIST_PATH = os.environ.get( + 'EO_CERT_CALIST_PATH', f'{EO_CERT_DIR}/cert.calist.pem' +) +CERT_COMMON_NAME = os.environ.get('CERT_COMMON_NAME', socket.gethostname()) +KEYSTORE_PASS = os.environ.get('KEYSTORE_PASS', '') +EO_SERVICE_PORT = int(os.environ.get('EO_SERVICE_PORT', '8080')) -try: - # EO_CONF file should have the following format: - # { - # "VERSION": 1, - # "PUBLIC_IP_ADDRESS": "192.168.0.1", - # "PRIVATE_IP_ADDRESS": "192.168.0.1", - # "HOSTNAME": "election-orchestra", - # "PORT": 5000, - # "KEYSTORE_PASS": "supersecret" - # } - CONFIG = json.load(open(EO_CONF)) -except: - print(f"Configuration file at '${EO_CONF}' not found") - sys.exit(1) - -EO_VERSION = CONFIG['VERSION'] -EO_PUBLIC_IP_ADDRESS = CONFIG['PUBLIC_IP_ADDRESS'] -EO_PRIVATE_IP_ADDRESS = CONFIG['PRIVATE_IP_ADDRESS'] -EO_HOSTNAME = CONFIG['HOSTNAME'] -EO_PORT = CONFIG['PORT'] def _validate_package(el_json): ''' @@ -61,13 +49,6 @@ def _validate_package(el_json): lambda val: isinstance(val, str) and len(val) < 10000 ] ), - dict( - key="ip_address", - existence="required", - checks=[ - lambda val: isinstance(val, str) and len(val) < 100 - ] - ), dict( key="hostname", existence="required", @@ -144,20 +125,13 @@ def install(PEER_LIST, path, keystore=None): print("package for hostname %s already installed" % bname) exit(1) - # add to hosts if needed - with open("/etc/hosts", "r") as f: - hosts_data = f.read() - hostline = "\n%s %s" % (el_json['ip_address'], el_json["hostname"]) - if hostline not in hosts_data: - subprocess.call("echo '%s' >> /etc/hosts" % hostline, shell=True) - # add to ssl certs - with open(CERT_CALIST_PATH, "r") as f: + with open(EO_CERT_CALIST_PATH, "r") as f: calist_data = f.read().strip() if el_json["ssl_certificate"] not in calist_data: cert = el_json["ssl_certificate"] subprocess.call( - f"echo '{cert}' >> {CERT_CALIST_PATH}", + f"echo '{cert}' >> {EO_CERT_CALIST_PATH}", shell=True ) @@ -173,7 +147,7 @@ def install(PEER_LIST, path, keystore=None): with open(temppem, "w") as f: f.write(el_json["ssl_certificate"]) #keytool --delete mykey -keystore keystore.jks - subprocess.call("keytool -noprompt -import -file %s -keystore %s -storepass '%s' -alias %s" % (temppem, keystore, CONFIG['KEYSTORE_PASS'], bname), shell=True) + subprocess.call("keytool -noprompt -import -file %s -keystore %s -storepass '%s' -alias %s" % (temppem, keystore, KEYSTORE_PASS, bname), shell=True) os.unlink(temppem) def uninstall(PEER_LIST, hostname, keystore=None): @@ -195,19 +169,11 @@ def uninstall(PEER_LIST, hostname, keystore=None): traceback.print_exc() exit(1) - # remove hostname from hosts - with open('/etc/hosts', 'r') as f: - data = f.read() - hostline = "\n%s %s" % (el_json['ip_address'], el_json["hostname"]) - data = data.replace(hostline, "") - with open('/etc/hosts', 'w') as f: - f.write(data) - # remove from ssl certs - with open(CERT_CALIST_PATH, 'r') as f: + with open(EO_CERT_CALIST_PATH, 'r') as f: data = f.read() data = data.replace(el_json["ssl_certificate"], "") - with open(CERT_CALIST_PATH, 'w') as f: + with open(EO_CERT_CALIST_PATH, 'w') as f: f.write(data) # finally remove the package @@ -216,20 +182,18 @@ def uninstall(PEER_LIST, hostname, keystore=None): if keystore: keystore = keystore[0] # removing the key from the keystore - subprocess.call("keytool -noprompt -delete -alias %s -keystore %s -storepass '%s'" % (hostname, keystore, CONFIG['KEYSTORE_PASS']), shell=True) + subprocess.call("keytool -noprompt -delete -alias %s -keystore %s -storepass '%s'" % (hostname, keystore, KEYSTORE_PASS), shell=True) def showmine(pargs): ''' install the peer package by path ''' - with open(CERT_PATH, 'r') as f: + with open(EO_CERT_PATH, 'r') as f: ssl_certificate = f.read() - ip = EO_PRIVATE_IP_ADDRESS if pargs.private_ip else EO_PUBLIC_IP_ADDRESS us = { "ssl_certificate": ssl_certificate, - "ip_address": ip, - "hostname": EO_HOSTNAME, - "port": EO_PORT, + "hostname": CERT_COMMON_NAME, + "port": EO_SERVICE_PORT, "version": EO_VERSION } print(json.dumps(us)) @@ -281,16 +245,35 @@ def main(): Main function ''' parser = argparse.ArgumentParser(prog='eopeers') - parser.add_argument("--list", help="list installed peer packages by hostname", action="store_true") - parser.add_argument('--install', nargs='+', help='install a peer package') - parser.add_argument('--uninstall', nargs='+', help='uninstall peer package(s) by hostname') - parser.add_argument('--show-mine', help='show our peer package', action="store_true") - parser.add_argument('--private-ip', help='together with --show-mine, uses ' - 'private ip instead of the public one', action="store_true") - parser.add_argument('--show', help="show the content of an installed package " - "by hostname") - parser.add_argument('--keystore', nargs=1, help='The keystore path ' - 'to add or remove with keytool') + parser.add_argument( + "--list", + help="list installed peer packages by hostname", + action="store_true" + ) + parser.add_argument( + '--install', + nargs='+', + help='install a peer package' + ) + parser.add_argument( + '--uninstall', + nargs='+', + help='uninstall peer package(s) by hostname' + ) + parser.add_argument( + '--show-mine', + help='show our peer package', + action="store_true" + ) + parser.add_argument( + '--show', + help="show the content of an installed package by hostname" + ) + parser.add_argument( + '--keystore', + nargs=1, + help='The keystore path to add or remove with keytool' + ) pargs = parser.parse_args() diff --git a/scripts/bin/eotest b/scripts/bin/eotest index 2ce8da1..1a581b6 100755 --- a/scripts/bin/eotest +++ b/scripts/bin/eotest @@ -50,11 +50,16 @@ import os BUF_SIZE = 10*1024 -CERT_DIR = os.environ.get('CERT_DIR', '/datastore/certs/') -CERT_PREFIX = os.environ.get('CERT_PREFIX', 'cert') -CERT_PATH = os.environ.get('CERT_PATH', f'${CERT_DIR}/${CERT_PREFIX}.pem') -CERT_KEY_PATH = os.environ.get('CERT_KEY_PATH', f'${CERT_DIR}/key-nopass.pem') -CERT_CALIST_PATH = os.environ.get('CERT_CALIST_PATH', f'${CERT_DIR}/calist') +EO_CERT_DIR = os.environ.get('EO_CERT_DIR', '/datastore/certs') +EO_CERT_PATH = os.environ.get('EO_CERT_PATH', f'{EO_CERT_DIR}/cert.pem') +EO_CERT_KEY_PATH = os.environ.get( + 'EO_CERT_KEY_PATH', f'{EO_CERT_DIR}/cert.key.pem' +) +EO_CERT_CALIST_PATH = os.environ.get( + 'EO_CERT_CALIST_PATH', f'{EO_CERT_DIR}/cert.calist.pem' +) +CERT_COMMON_NAME = os.environ.get('CERT_COMMON_NAME', 'trustee1') + # with three authorities 60 seconds was not enough EOTEST_PK_TIMEOUT = int(os.environ.get('PK_TIMEOUT', '600')) @@ -65,7 +70,8 @@ EOTEST_TALLY_TIMEOUT = int(os.environ.get('PK_TIMEOUT', '21600')) EOTEST_DATA_DIR = os.environ.get('DATA_DIR', '/datastore/eotest/data') NODE_BIN_PATH = os.environ.get('NODE_BIN', '/bin/node') EOTEST_VMND_BIN_PATH = os.environ.get('EOTEST_VMND_BIN_PATH', '/bin/vmnd') -EOTEST_LOCAL_PORT = int(os.environ.get('LOCAL_PORT', '8084')) +EO_TEST_PORT = int(os.environ.get('EO_TEST_PORT', '8083')) +EO_PEER_LIST = os.environ.get('EO_PEER_LIST', '/datastore/eopeerlist/') if not os.path.exists(EOTEST_DATA_DIR): @@ -81,7 +87,8 @@ def getPeerPkg(mypeerpkg): if mypeerpkg is None: mypeerpkg = subprocess.check_output(["eopeers", "--show-mine"]) if isinstance(mypeerpkg, bytes): - return json.loads(mypeerpkg.decode("utf-8")) + data = json.loads(mypeerpkg.decode("utf-8")) + return data else: return mypeerpkg @@ -90,8 +97,8 @@ def getTallyData(mypeerpkg): localServer = mypeerpkg["hostname"] return { # 'election_id': electionId, - "callback_url": "https://" + localServer + ":" + str(EOTEST_LOCAL_PORT) + "/receive_tally", - "votes_url": "https://" + localServer + ":" + str(EOTEST_LOCAL_PORT) + "/", + "callback_url": "https://" + localServer + ":" + str(EO_TEST_PORT) + "/receive_tally", + "votes_url": "https://" + localServer + ":" + str(EO_TEST_PORT) + "/", "votes_hash": "ni:///sha-256;" } @@ -103,7 +110,7 @@ def _apiUrl(path, pkg): ''' return "https://%(hostname)s:%(port)d/%(path)s" % dict( hostname=pkg["hostname"], - port=pkg.get("port", 5000), + port=pkg.get("port", EO_TEST_PORT), path=path ) @@ -157,7 +164,7 @@ def grabAuthData(eopeers_dir, mypeerpkg, eopeers): def getStartData(eopeers_dir, mypeerpkg, eopeers): mypeerpkg = getPeerPkg(mypeerpkg) return { - "callback_url": "https://" + mypeerpkg["hostname"] + ":" + str(EOTEST_LOCAL_PORT) + "/key_done", + "callback_url": f"https://{mypeerpkg['hostname']}:{EO_TEST_PORT}/key_done", "title": "Test election", "description": "election description", 'presentation': {}, @@ -276,7 +283,12 @@ def startServer(port): import ssl print("> Starting server on port " + str(port)) server = ThreadingHTTPServer(('', port), RequestHandler) - server.socket = ssl.wrap_socket(server.socket, certfile=CERT_PATH, keyfile=CERT_KEY_PATH, server_side=True) + server.socket = ssl.wrap_socket( + server.socket, + certfile=EO_CERT_PATH, + keyfile=EO_CERT_KEY_PATH, + server_side=True + ) thread = threading.Thread(target = server.serve_forever, daemon=True) thread.start() @@ -286,7 +298,7 @@ def startElection(electionId, url, data): data['id'] = int(electionId) print("> Creating election %s" % electionId) cv.done = False - r = session.request('post', url, data=json.dumps(data), verify=CERT_CALIST_PATH, cert=(CERT_PATH, CERT_KEY_PATH)) + r = session.request('post', url, data=json.dumps(data), verify=EO_CERT_CALIST_PATH, cert=(EO_CERT_PATH, EO_CERT_KEY_PATH)) print("> " + str(r)) def waitForPublicKey(): @@ -318,7 +330,7 @@ def doTally(electionId, url, data, votesFile, hash): # print("> Tally post with " + json.dumps(data)) print("> Requesting tally..") cv.done = False - r = session.request('post', url, data=json.dumps(data), verify=CERT_CALIST_PATH, cert=(CERT_PATH, CERT_KEY_PATH)) + r = session.request('post', url, data=json.dumps(data), verify=EO_CERT_CALIST_PATH, cert=(EO_CERT_PATH, EO_CERT_KEY_PATH)) print("> " + str(r)) if r.status_code == 400: print("> error:" + str(r.text)) @@ -347,7 +359,7 @@ def downloadTally(url, electionId): path = os.path.join(EOTEST_DATA_DIR, fileName) print("> Downloading to %s" % path) with open(path, 'wb') as handle: - request = session.request('get',url, stream=True, verify=CERT_CALIST_PATH, cert=(CERT_PATH, CERT_KEY_PATH)) + request = session.request('get',url, stream=True, verify=EO_CERT_CALIST_PATH, cert=(EO_CERT_PATH, EO_CERT_KEY_PATH)) for block in request.iter_content(1024): if not block: @@ -359,9 +371,9 @@ def downloadTally(url, electionId): def create(args): electionId = args.electionId - startServer(EOTEST_LOCAL_PORT) + startServer(EO_TEST_PORT) mypeerpkg = getPeerPkg(args.mypeerpkg) - startData = getStartData(args.eopeers_dir, args.mypeerpkg, args.peers) + startData = getStartData(EO_PEER_LIST, args.mypeerpkg, args.peers) startUrl = _apiUrl("public_api/election", mypeerpkg) startElection(electionId, startUrl, startData) publicKey = waitForPublicKey() @@ -430,7 +442,7 @@ def encrypt(args): def tally(args): if(args.command[0] == "tally"): - startServer(EOTEST_LOCAL_PORT) + startServer(EO_TEST_PORT) electionId = args.electionId @@ -470,7 +482,6 @@ def main(argv): encrypt : encrypts votes tally : launches tally full: does the whole process''') - parser.add_argument('--eopeers-dir', help='directory with the eopeers packages to use as authorities', default = '/etc/eopeers/') parser.add_argument('--peers', nargs='+', default=None, help='list of authorities to pair with. defaults to all of them') parser.add_argument('--mypeerpkg', help='path to my peer package. if empty, calls to eopeers --show-mine', default = None) parser.add_argument('--vfile', help='json file to read votes from', default = 'votes.json') diff --git a/scripts/nginx/nginx.conf b/scripts/nginx/nginx.conf new file mode 100644 index 0000000..9ddf3ef --- /dev/null +++ b/scripts/nginx/nginx.conf @@ -0,0 +1,2 @@ +# only one include, our election-orchestra service +include /etc/nginx/conf.d/default.conf; \ No newline at end of file diff --git a/scripts/nginx/templates/default.conf.template b/scripts/nginx/templates/default.conf.template new file mode 100644 index 0000000..2e27e4c --- /dev/null +++ b/scripts/nginx/templates/default.conf.template @@ -0,0 +1,100 @@ +user nginx; +worker_processes auto; + +error_log /var/log/nginx/error.log ${NGINX_ERROR_LOG_LEVEL}; +pid /var/run/nginx.pid; + +events { + worker_connections 1024; +} + +http { + log_format main '${DOLLAR}remote_addr - ${DOLLAR}remote_user ' + '[${DOLLAR}time_local] "${DOLLAR}request" ' + '${DOLLAR}status ${DOLLAR}body_bytes_sent "${DOLLAR}http_referer" ' + '"${DOLLAR}http_user_agent" "${DOLLAR}http_x_forwarded_for"'; + + access_log /var/log/nginx/access.log main; + + server { + listen ${EO_SERVICE_PORT} ssl; + server_name ${NGINX_HOST}; + + ## + # Basic Settings + ## + + sendfile on; + tcp_nopush on; + tcp_nodelay on; + types_hash_max_size 2048; + server_tokens off; + + ## + # Timeouts + ## + + client_body_timeout 12; + client_header_timeout 12; + keepalive_timeout 65; + send_timeout 10; + + default_type application/octet-stream; + + + ## + # Gzip Settings + ## + + gzip on; + gzip_disable "msie6"; + gzip_vary on; + gzip_min_length 10240; + gzip_proxied any; + gzip_comp_level 4; + gzip_http_version 1.1; + gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript; + + ## + # Buffers + ## + + client_body_buffer_size 2K; + client_header_buffer_size 1k; + client_max_body_size ${NGINX_HTTP_MAX_BODY_SIZE}; + large_client_header_buffers 4 ${NGINX_HTTP_CLIENT_MAX_HEADER_SIZE}; + + add_header Strict-Transport-Security max-age=31536000; + ssl_verify_client on; + ssl_client_certificate ${EO_SSL_CALIST_PATH}; + ssl_certificate ${EO_SSL_CERT_PATH}; + ssl_certificate_key ${EO_SSL_KEY_PATH}; + + # see: + # https://raymii.org/s/tutorials/Pass_the_SSL_Labs_Test_on_NGINX_%28Mitigate_the_CRIME_and_BEAST_attack_-_Disable_SSLv2_-_Enable_PFS%29.html + # https://blog.hasgeek.com/2013/https-everywhere-at-hasgeek + ssl_session_cache builtin:1000 shared:SSL:10m; + ssl_session_timeout 5m; + ssl_ecdh_curve secp521r1; + + ssl_protocols TLSv1 TLSv1.1 TLSv1.2; + ssl_prefer_server_ciphers on; + + # The following is all one long line. We use an explicit list of ciphers to enable + # forward secrecy without exposing ciphers vulnerable to the BEAST attack + ssl_ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-RC4-SHA:ECDHE-RSA-RC4-SHA:ECDH-ECDSA-RC4-SHA:ECDH-RSA-RC4-SHA:ECDHE-RSA-AES256-SHA:RC4-SHA:HIGH:!aNULL:!eNULL:!LOW:!3DES:!MD5:!EXP:!CBC:!EDH:!kEDH:!PSK:!SRP:!kECDH; + + location /public_data { + autoindex on; + alias ${EO_PUBLIC_DATA_PATH}; + } + + location / { + proxy_pass http://${UPSTREAM_SERVICE_NAME}:${EO_FLASK_RUN_PORT}; + proxy_set_header Host ${DOLLAR}http_host; + proxy_set_header X-Real-IP ${DOLLAR}remote_addr; + proxy_set_header X-Scheme ${DOLLAR}scheme; + proxy_set_header X-Sender-SSL-Certificate ${DOLLAR}ssl_client_cert; + } + } +} \ No newline at end of file diff --git a/scripts/nix/utils.nix b/scripts/nix/utils.nix index a33bbfa..5410cb6 100644 --- a/scripts/nix/utils.nix +++ b/scripts/nix/utils.nix @@ -3,11 +3,13 @@ # Load env-file and convert its contents to a Nix set (dictionary) # Example usage: # loadEnv "/path/to/your/.env" + # Note: It doesn't support multiline with """ nor quoting with "", but it does + # support comments with # loadEnv = filePath: let envContent = lib.fileContents(filePath); - nonEmptyLines = builtins.filter - (line: line != "") + assignmentLines = builtins.filter + (line: line != "" && (builtins.match "^#.*" line) == null) (lib.splitString "\n" envContent); keyValues = builtins.map ( @@ -19,7 +21,14 @@ value = lib.last (splitLine); } ) - nonEmptyLines; + assignmentLines; in lib.listToAttrs keyValues; + + # Similar to builtins.getAttr, but with a third parameter providing a + # default value to return if the attrKey is not inside the set + getAttrDefault = attrKey: set: default: + if (set ? attrKey) + then (set."${attrKey}") + else default; } \ No newline at end of file From 62ac8e610fb6cfe22adf38c118b8324e1dd6674d Mon Sep 17 00:00:00 2001 From: Eduardo Robles Date: Sun, 30 Jul 2023 11:28:05 +0000 Subject: [PATCH 22/26] wip --- .env | 1 + docker-compose.yml | 2 +- election_orchestra/app.py | 13 +++++++------ election_orchestra/taskqueue.py | 18 +++++++++++++++--- 4 files changed, 24 insertions(+), 10 deletions(-) diff --git a/.env b/.env index afee408..8899dce 100644 --- a/.env +++ b/.env @@ -13,6 +13,7 @@ EO_SSL_CERT_DIR=/datastore/certs/ EO_SSL_CERT_PATH=/datastore/certs/cert.pem EO_SSL_KEY_PATH=/datastore/certs/cert.key.pem EO_SSL_CALIST_PATH=/datastore/certs/cert.calist.pem +EO_ROOT_URL=http://127.0.0.1:8081/api/queues CLASSPATH=/share/java/jecn.jar:/share/java/jgmpmee.jar:/share/java/mixnet.jar VFORK_RANDOM_SOURCE=/datastore/mixnet_random_source diff --git a/docker-compose.yml b/docker-compose.yml index 629f159..341402b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,5 +1,5 @@ -# export COMPOSE_PROJECT_NAME=trustee-network # docker compose up --build --force-recreate +# docker compose exec -it trustee1 eotest full --vmnd --vcount 100 --peers trustee2_nginx version: "3.6" x-trustee-common: &trustee_common diff --git a/election_orchestra/app.py b/election_orchestra/app.py index d0f5926..05c5b3b 100755 --- a/election_orchestra/app.py +++ b/election_orchestra/app.py @@ -61,7 +61,7 @@ def extra_run(self): return False -def configure_app(app): +def configure_app(app, launch_scheduler): ''' override config from environment variables, using defaults from the class DefaultConfig. @@ -73,22 +73,23 @@ def configure_app(app): env_name = variable.split(config_var_prefix)[1] logging.debug(f"SET:from-env-var config.{env_name} = {value}") setattr(config_object, env_name, value) - app.configure_app(scheduler=False, config_object=config_object) + app.configure_app(scheduler=launch_scheduler, config_object=config_object) app.register_blueprint(public_api, url_prefix='/public_api') -configure_app(app) if __name__ == "__main__": + configure_app(app, launch_scheduler=False) if len(sys.argv) == 3 and sys.argv[1] == "create-tarball": from tools import create_tarball create_tarball.create(sys.argv[2]) exit(0) + logging.debug(f"launching app.run") app.run( parse_args=True, extra_parse_func=extra_parse_args, extra_run=extra_run ) else: - # used when run using uwsgi or similar - with app.app_context(): - start_queue() + configure_app(app, launch_scheduler=True) + app.app_context().push() + start_queue() diff --git a/election_orchestra/taskqueue.py b/election_orchestra/taskqueue.py index 2eec2ec..6676dd2 100644 --- a/election_orchestra/taskqueue.py +++ b/election_orchestra/taskqueue.py @@ -3,6 +3,7 @@ # # SPDX-License-Identifier: AGPL-3.0-only # +import logging import pickle import base64 import json @@ -15,13 +16,17 @@ def safe_dequeue(): + app.app_context().push() + logging.debug("safe_dequeue(): starting") try: dequeue_task() return True - except Exception as e: + except Exception as error: + logging.debug(f"safe_dequeue(): exception {error}") return False def start_queue(queue_continue=False): + logging.debug(f"launching start_queue(queue_continue={queue_continue})") if not queue_continue: doing = db.session.query(QueryQueue).all() for i in doing: @@ -38,17 +43,23 @@ def start_queue(queue_continue=False): def dequeue_task(): + logging.debug("dequeue_task(): starting") doing = db.session.query(QueryQueue).filter(QueryQueue.doing == True) - if not doing.count(): + todos = db.session.query(QueryQueue).filter(QueryQueue.doing == False) + logging.debug(f"dequeue_task(): doing.count() = {doing.count()}") + if not doing.count() and todos.count() > 0: + logging.debug(f"dequeue_task(): getting next todo task") todo = db.session.query(QueryQueue)\ .with_for_update(nowait=True, of=QueryQueue)\ .order_by(QueryQueue.id)\ .first() todo.doing = True + logging.debug(f"dequeue_task(): todo.id = {todo.id}") db.session.commit() apply_task(todo.task, todo.data) - + else: + logging.debug("no task to do?") def queue_task(task='election', data=None): data = data or {} @@ -61,6 +72,7 @@ def queue_task(task='election', data=None): def apply_task(task, data): + logging.debug(f"apply_task(task={task}, data={data})") d = pickle.loads(base64.b64decode(data.encode('utf-8'))) if task == 'election': r = election_task(d) From 20f0d35d1d7b668f4f47aae25fe2111764112f79 Mon Sep 17 00:00:00 2001 From: Eduardo Robles Date: Mon, 31 Jul 2023 03:32:57 +0000 Subject: [PATCH 23/26] WIP --- .env | 3 ++- docker-compose.yml | 3 +++ election_orchestra/app.py | 4 ++-- .../create_election/performer_jobs.py | 2 +- .../tally_election/director_jobs.py | 2 +- election_orchestra/taskqueue.py | 12 +++++++----- flake.lock | 12 ++++++------ flake.nix | 7 ++++--- poetry.lock | 17 ++++++++++++++++- scripts/bin/docker-entrypoint.sh | 2 ++ 10 files changed, 44 insertions(+), 20 deletions(-) diff --git a/.env b/.env index 8899dce..4375417 100644 --- a/.env +++ b/.env @@ -13,7 +13,7 @@ EO_SSL_CERT_DIR=/datastore/certs/ EO_SSL_CERT_PATH=/datastore/certs/cert.pem EO_SSL_KEY_PATH=/datastore/certs/cert.key.pem EO_SSL_CALIST_PATH=/datastore/certs/cert.calist.pem -EO_ROOT_URL=http://127.0.0.1:8081/api/queues +EO_SSL_HEADER_NAME=HTTP_X_SENDER_SSL_CERTIFICATE CLASSPATH=/share/java/jecn.jar:/share/java/jgmpmee.jar:/share/java/mixnet.jar VFORK_RANDOM_SOURCE=/datastore/mixnet_random_source @@ -24,6 +24,7 @@ ETCD_ENDPOINT=http://etcd_service:2379 NGINX_ERROR_LOG_LEVEL=info NGINX_HTTP_MAX_BODY_SIZE=2m NGINX_HTTP_CLIENT_MAX_HEADER_SIZE=8k +GUNICORN_THREADS=8 COMPOSE_PROFILES=trustee1,trustee2 COMPOSE_PROJECT_NAME=trustee-network diff --git a/docker-compose.yml b/docker-compose.yml index 341402b..2912282 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -33,6 +33,7 @@ services: SERVICE_NAME: trustee1 CERT_COMMON_NAME: trustee1_nginx EO_SQLALCHEMY_DATABASE_URI: postgresql+psycopg2://db1:db1@db1/db1 + EO_ROOT_URL: https://trustee1_nginx:8080/api/queues depends_on: <<: *trustee_depends_on db1: @@ -49,6 +50,8 @@ services: SERVICE_NAME: trustee2 CERT_COMMON_NAME: trustee2_nginx EO_SQLALCHEMY_DATABASE_URI: postgresql+psycopg2://db2:db2@db2/db2 + + EO_ROOT_URL: https://trustee1_nginx:8080/api/queues depends_on: <<: *trustee_depends_on db2: diff --git a/election_orchestra/app.py b/election_orchestra/app.py index 05c5b3b..af010ae 100755 --- a/election_orchestra/app.py +++ b/election_orchestra/app.py @@ -14,7 +14,8 @@ from frestq.app import app, DefaultConfig as FrestqDefaultConfig from .models import * -from .tally_election import performer_jobs +from .create_election import director_jobs, performer_jobs +from .tally_election import director_jobs, performer_jobs from .public_api import public_api from .taskqueue import start_queue @@ -91,5 +92,4 @@ def configure_app(app, launch_scheduler): ) else: configure_app(app, launch_scheduler=True) - app.app_context().push() start_queue() diff --git a/election_orchestra/create_election/performer_jobs.py b/election_orchestra/create_election/performer_jobs.py index 37bfbd4..f46195c 100644 --- a/election_orchestra/create_election/performer_jobs.py +++ b/election_orchestra/create_election/performer_jobs.py @@ -79,7 +79,7 @@ def check_election_data(data, check_extra): raise TaskError(dict(reason='no authorities')) if not isinstance(questions, list) or len(questions) < 1 or\ - len(questions) > app.config.get('MAX_NUM_QUESTIONS_PER_ELECTION', 15): + len(questions) > int(app.config.get('MAX_NUM_QUESTIONS_PER_ELECTION', 15)): raise TaskError(dict(reason='Unsupported number of questions in the election')) diff --git a/election_orchestra/tally_election/director_jobs.py b/election_orchestra/tally_election/director_jobs.py index fb6026f..5333931 100644 --- a/election_orchestra/tally_election/director_jobs.py +++ b/election_orchestra/tally_election/director_jobs.py @@ -21,7 +21,7 @@ from ..reject_adapter import RejectAdapter from ..utils import mkdir_recursive -from taskqueue import end_task +from ..taskqueue import end_task @decorators.local_task @decorators.task(action="tally_election", queue="launch_task") diff --git a/election_orchestra/taskqueue.py b/election_orchestra/taskqueue.py index 6676dd2..cff9aba 100644 --- a/election_orchestra/taskqueue.py +++ b/election_orchestra/taskqueue.py @@ -16,7 +16,6 @@ def safe_dequeue(): - app.app_context().push() logging.debug("safe_dequeue(): starting") try: dequeue_task() @@ -59,7 +58,7 @@ def dequeue_task(): apply_task(todo.task, todo.data) else: - logging.debug("no task to do?") + logging.debug("no task to do") def queue_task(task='election', data=None): data = data or {} @@ -98,14 +97,15 @@ def end_task(): ### TASKS def election_task(data): + logging.debug(f"election_task(): running election_task..") if not data: - print("invalid json") + logging.error(f"no data") return False try: check_election_data(data, True) - except Exception as e: - print("ERROR", e) + except Exception as error: + logging.error(f"election_task(): invalid json {error}") return False e = Election( @@ -140,6 +140,8 @@ def election_task(data): 'election_id': data['id'] } ) + + logging.error(f"election_task(): sending task..") task.create_and_send() return task diff --git a/flake.lock b/flake.lock index 8a12608..a1e7d56 100644 --- a/flake.lock +++ b/flake.lock @@ -8,11 +8,11 @@ "pre-commit-hooks": "pre-commit-hooks" }, "locked": { - "lastModified": 1690534632, - "narHash": "sha256-kOXS9x5y17VKliC7wZxyszAYrWdRl1JzggbQl0gyo94=", + "lastModified": 1690638457, + "narHash": "sha256-3EBd8PHd0lbSCMief9eQdzTOvEofnB3koR+Q4wvvDbA=", "owner": "cachix", "repo": "devenv", - "rev": "6568e7e485a46bbf32051e4d6347fa1fed8b2f25", + "rev": "da313abf0fb6d21210f6d555acabf40425e080f1", "type": "github" }, "original": { @@ -365,11 +365,11 @@ }, "nixpkgs_3": { "locked": { - "lastModified": 1690367991, - "narHash": "sha256-2VwOn1l8y6+cu7zjNE8MgeGJNNz1eat1HwHrINeogFA=", + "lastModified": 1690640159, + "narHash": "sha256-5DZUYnkeMOsVb/eqPYb9zns5YsnQXRJRC8Xx/nPMcno=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "c9cf0708f00fbe553319258e48ca89ff9a413703", + "rev": "e6ab46982debeab9831236869539a507f670a129", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 8306151..b1ddeeb 100644 --- a/flake.nix +++ b/flake.nix @@ -2,9 +2,9 @@ # SPDX-License-Identifier: AGPL-3.0-only # Usage: -# time nix run .#dockerImage.copyToDockerDaemon && docker images election_orchestra:latest && docker run -it --network election-orchestra_devcontainer_default election_orchestra:latest +# time nix run .#dockerImage.copyToDockerDaemon && docker images election_orchestra:latest && docker compose up --build --force-recreate # Then: -# curl http://172.18.0.3:9090 +# docker compose exec -it trustee1 eotest full --vmnd --vcount 100 --peers trustee2_nginx { description = "test-devenv test project"; @@ -46,6 +46,7 @@ # See https://github.com/nix-community/poetry2nix/blob/master/docs/edgecases.md#modulenotfounderror-no-module-named-packagename election_orchestra-build-requirements = { frestq = [ "poetry" ]; + sqlalchemy-json = [ "setuptools" ]; }; election_orchestra-overrides = poetry2nix.defaultPoetryOverrides.extend ( self: super: builtins.mapAttrs @@ -108,8 +109,8 @@ python.pkgs.gunicorn pkgs.openssl pkgs.jre8 - pkgs.vim pkgs.gmp + pkgs.vim pkgs.etcd mixnetPackages.mixnet election_orchestra.dependencyEnv diff --git a/poetry.lock b/poetry.lock index 2cdea78..940f1bc 100644 --- a/poetry.lock +++ b/poetry.lock @@ -339,12 +339,13 @@ prettytable = "^3.8.0" pyOpenSSL = "23.2.0" requests = "^2.31" sqlalchemy = "^1.4" +sqlalchemy-json = "^0.6.0" [package.source] type = "git" url = "https://github.com/sequentech/frestq.git" reference = "feat/master/k8s" -resolved_reference = "0d58ee349fb18c9abb2842e0aaf6b6b6929de996" +resolved_reference = "88285677cb054801fae1d713ee98b716aba0361f" [[package]] name = "greenlet" @@ -745,6 +746,20 @@ postgresql-psycopg2cffi = ["psycopg2cffi"] pymysql = ["pymysql", "pymysql (<1)"] sqlcipher = ["sqlcipher3-binary"] +[[package]] +name = "sqlalchemy-json" +version = "0.6.0" +description = "JSON type with nested change tracking for SQLAlchemy" +optional = false +python-versions = ">= 3.6" +files = [ + {file = "sqlalchemy-json-0.6.0.tar.gz", hash = "sha256:94785325716f0649ec6f4d0304ce8803a5bf25ff8aa2d998ce6b6334192f7448"}, + {file = "sqlalchemy_json-0.6.0-py3-none-any.whl", hash = "sha256:06f124c093368903859367c7ab22416c4428e90c09ae14def8391f06a2373c1f"}, +] + +[package.dependencies] +sqlalchemy = ">=0.7" + [[package]] name = "tzlocal" version = "2.1" diff --git a/scripts/bin/docker-entrypoint.sh b/scripts/bin/docker-entrypoint.sh index aef4353..61cde70 100755 --- a/scripts/bin/docker-entrypoint.sh +++ b/scripts/bin/docker-entrypoint.sh @@ -16,6 +16,7 @@ RANDOM_DEVICE="${RANDOM_DEVICE:-/dev/urandom}" TRUSTEES="${TRUSTEES:-trustee1,trustee2}" ETCD_ENDPOINT="${ETCD_ENDPOINT:-}" SERVICE_NAME="${SERVICE_NAME:-trustee1}" +GUNICORN_THREADS="${GUNICORN_THREADS:-4}" echo "entrypoint: state of the vars after assuming defaults:" export @@ -95,5 +96,6 @@ echo "election-orchestra: launching gunicorn service.." gunicorn \ -b "${EO_FLASK_RUN_HOST}:${EO_FLASK_RUN_PORT}" \ --log-level "${LOG_LEVEL}" \ + --threads "${GUNICORN_THREADS}" \ "${FLASK_APP}" From 387c684ce7338e07b38acb1f51d95190a7de89ed Mon Sep 17 00:00:00 2001 From: Eduardo Robles Date: Mon, 31 Jul 2023 11:07:10 +0000 Subject: [PATCH 24/26] wip --- .env | 1 + docker-compose.yml | 2 +- flake.lock | 6 +- flake.nix | 5 +- poetry.lock | 190 ++++-------------- pyproject.toml | 2 +- scripts/nginx/templates/default.conf.template | 2 +- 7 files changed, 53 insertions(+), 155 deletions(-) diff --git a/.env b/.env index 4375417..8b43d01 100644 --- a/.env +++ b/.env @@ -14,6 +14,7 @@ EO_SSL_CERT_PATH=/datastore/certs/cert.pem EO_SSL_KEY_PATH=/datastore/certs/cert.key.pem EO_SSL_CALIST_PATH=/datastore/certs/cert.calist.pem EO_SSL_HEADER_NAME=HTTP_X_SENDER_SSL_CERTIFICATE +ALLOW_ONLY_SSL_CONNECTIONS=False CLASSPATH=/share/java/jecn.jar:/share/java/jgmpmee.jar:/share/java/mixnet.jar VFORK_RANDOM_SOURCE=/datastore/mixnet_random_source diff --git a/docker-compose.yml b/docker-compose.yml index 2912282..e0c9dfa 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -51,7 +51,7 @@ services: CERT_COMMON_NAME: trustee2_nginx EO_SQLALCHEMY_DATABASE_URI: postgresql+psycopg2://db2:db2@db2/db2 - EO_ROOT_URL: https://trustee1_nginx:8080/api/queues + EO_ROOT_URL: https://trustee2_nginx:8080/api/queues depends_on: <<: *trustee_depends_on db2: diff --git a/flake.lock b/flake.lock index a1e7d56..4b63738 100644 --- a/flake.lock +++ b/flake.lock @@ -168,11 +168,11 @@ "nixpkgs": "nixpkgs_2" }, "locked": { - "lastModified": 1690563975, - "narHash": "sha256-btAl9JTPE3ZU8qU0XrlBUhLdCyBQ2gujaJxzhGJAifo=", + "lastModified": 1690785572, + "narHash": "sha256-l/JBbtoVayWijNpYoP/ertWpk6lyOxJCFXgGCuBYgmw=", "owner": "sequentech", "repo": "mixnet", - "rev": "2dc1d5e59e065ccf63bde5caa0b4b37541384f04", + "rev": "28e76dee1af8b82de2b13aa778f47645359ae79e", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index b1ddeeb..6826786 100644 --- a/flake.nix +++ b/flake.nix @@ -104,12 +104,13 @@ name = "root"; paths = [ pkgs.bashInteractive - pkgs.coreutils + #pkgs.coreutils pkgs.nodejs python.pkgs.gunicorn pkgs.openssl pkgs.jre8 pkgs.gmp + pkgs.busybox pkgs.vim pkgs.etcd mixnetPackages.mixnet @@ -130,6 +131,7 @@ baseEnv = { FLASK_RUN_PORT = "${flaskPort}"; PYTHONPATH = "${election_orchestra.dependencyEnv}/lib/python3.10/site-packages"; + LD_LIBRARY_PATH = "/lib"; }; joined = envFile // baseEnv; in joined); @@ -146,7 +148,6 @@ deps = [ python.pkgs.gunicorn pkgs.bashInteractive - pkgs.coreutils pkgs.nodejs pkgs.openssl ]; diff --git a/poetry.lock b/poetry.lock index 940f1bc..e4565f6 100644 --- a/poetry.lock +++ b/poetry.lock @@ -338,87 +338,13 @@ ipaddress = "^1.0" prettytable = "^3.8.0" pyOpenSSL = "23.2.0" requests = "^2.31" -sqlalchemy = "^1.4" -sqlalchemy-json = "^0.6.0" +sqlalchemy = "~1.3" [package.source] type = "git" url = "https://github.com/sequentech/frestq.git" reference = "feat/master/k8s" -resolved_reference = "88285677cb054801fae1d713ee98b716aba0361f" - -[[package]] -name = "greenlet" -version = "2.0.2" -description = "Lightweight in-process concurrent programming" -optional = false -python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*" -files = [ - {file = "greenlet-2.0.2-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:bdfea8c661e80d3c1c99ad7c3ff74e6e87184895bbaca6ee8cc61209f8b9b85d"}, - {file = "greenlet-2.0.2-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:9d14b83fab60d5e8abe587d51c75b252bcc21683f24699ada8fb275d7712f5a9"}, - {file = "greenlet-2.0.2-cp27-cp27m-win32.whl", hash = "sha256:6c3acb79b0bfd4fe733dff8bc62695283b57949ebcca05ae5c129eb606ff2d74"}, - {file = "greenlet-2.0.2-cp27-cp27m-win_amd64.whl", hash = "sha256:283737e0da3f08bd637b5ad058507e578dd462db259f7f6e4c5c365ba4ee9343"}, - {file = "greenlet-2.0.2-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:d27ec7509b9c18b6d73f2f5ede2622441de812e7b1a80bbd446cb0633bd3d5ae"}, - {file = "greenlet-2.0.2-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:30bcf80dda7f15ac77ba5af2b961bdd9dbc77fd4ac6105cee85b0d0a5fcf74df"}, - {file = "greenlet-2.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26fbfce90728d82bc9e6c38ea4d038cba20b7faf8a0ca53a9c07b67318d46088"}, - {file = "greenlet-2.0.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9190f09060ea4debddd24665d6804b995a9c122ef5917ab26e1566dcc712ceeb"}, - {file = "greenlet-2.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d75209eed723105f9596807495d58d10b3470fa6732dd6756595e89925ce2470"}, - {file = "greenlet-2.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:3a51c9751078733d88e013587b108f1b7a1fb106d402fb390740f002b6f6551a"}, - {file = "greenlet-2.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:76ae285c8104046b3a7f06b42f29c7b73f77683df18c49ab5af7983994c2dd91"}, - {file = "greenlet-2.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:2d4686f195e32d36b4d7cf2d166857dbd0ee9f3d20ae349b6bf8afc8485b3645"}, - {file = "greenlet-2.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c4302695ad8027363e96311df24ee28978162cdcdd2006476c43970b384a244c"}, - {file = "greenlet-2.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c48f54ef8e05f04d6eff74b8233f6063cb1ed960243eacc474ee73a2ea8573ca"}, - {file = "greenlet-2.0.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a1846f1b999e78e13837c93c778dcfc3365902cfb8d1bdb7dd73ead37059f0d0"}, - {file = "greenlet-2.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a06ad5312349fec0ab944664b01d26f8d1f05009566339ac6f63f56589bc1a2"}, - {file = "greenlet-2.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:eff4eb9b7eb3e4d0cae3d28c283dc16d9bed6b193c2e1ace3ed86ce48ea8df19"}, - {file = "greenlet-2.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5454276c07d27a740c5892f4907c86327b632127dd9abec42ee62e12427ff7e3"}, - {file = "greenlet-2.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:7cafd1208fdbe93b67c7086876f061f660cfddc44f404279c1585bbf3cdc64c5"}, - {file = "greenlet-2.0.2-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:910841381caba4f744a44bf81bfd573c94e10b3045ee00de0cbf436fe50673a6"}, - {file = "greenlet-2.0.2-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:18a7f18b82b52ee85322d7a7874e676f34ab319b9f8cce5de06067384aa8ff43"}, - {file = "greenlet-2.0.2-cp35-cp35m-win32.whl", hash = "sha256:03a8f4f3430c3b3ff8d10a2a86028c660355ab637cee9333d63d66b56f09d52a"}, - {file = "greenlet-2.0.2-cp35-cp35m-win_amd64.whl", hash = "sha256:4b58adb399c4d61d912c4c331984d60eb66565175cdf4a34792cd9600f21b394"}, - {file = "greenlet-2.0.2-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:703f18f3fda276b9a916f0934d2fb6d989bf0b4fb5a64825260eb9bfd52d78f0"}, - {file = "greenlet-2.0.2-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:32e5b64b148966d9cccc2c8d35a671409e45f195864560829f395a54226408d3"}, - {file = "greenlet-2.0.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2dd11f291565a81d71dab10b7033395b7a3a5456e637cf997a6f33ebdf06f8db"}, - {file = "greenlet-2.0.2-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e0f72c9ddb8cd28532185f54cc1453f2c16fb417a08b53a855c4e6a418edd099"}, - {file = "greenlet-2.0.2-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cd021c754b162c0fb55ad5d6b9d960db667faad0fa2ff25bb6e1301b0b6e6a75"}, - {file = "greenlet-2.0.2-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:3c9b12575734155d0c09d6c3e10dbd81665d5c18e1a7c6597df72fd05990c8cf"}, - {file = "greenlet-2.0.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:b9ec052b06a0524f0e35bd8790686a1da006bd911dd1ef7d50b77bfbad74e292"}, - {file = "greenlet-2.0.2-cp36-cp36m-win32.whl", hash = "sha256:dbfcfc0218093a19c252ca8eb9aee3d29cfdcb586df21049b9d777fd32c14fd9"}, - {file = "greenlet-2.0.2-cp36-cp36m-win_amd64.whl", hash = "sha256:9f35ec95538f50292f6d8f2c9c9f8a3c6540bbfec21c9e5b4b751e0a7c20864f"}, - {file = "greenlet-2.0.2-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:d5508f0b173e6aa47273bdc0a0b5ba055b59662ba7c7ee5119528f466585526b"}, - {file = "greenlet-2.0.2-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:f82d4d717d8ef19188687aa32b8363e96062911e63ba22a0cff7802a8e58e5f1"}, - {file = "greenlet-2.0.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9c59a2120b55788e800d82dfa99b9e156ff8f2227f07c5e3012a45a399620b7"}, - {file = "greenlet-2.0.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2780572ec463d44c1d3ae850239508dbeb9fed38e294c68d19a24d925d9223ca"}, - {file = "greenlet-2.0.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:937e9020b514ceedb9c830c55d5c9872abc90f4b5862f89c0887033ae33c6f73"}, - {file = "greenlet-2.0.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:36abbf031e1c0f79dd5d596bfaf8e921c41df2bdf54ee1eed921ce1f52999a86"}, - {file = "greenlet-2.0.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:18e98fb3de7dba1c0a852731c3070cf022d14f0d68b4c87a19cc1016f3bb8b33"}, - {file = "greenlet-2.0.2-cp37-cp37m-win32.whl", hash = "sha256:3f6ea9bd35eb450837a3d80e77b517ea5bc56b4647f5502cd28de13675ee12f7"}, - {file = "greenlet-2.0.2-cp37-cp37m-win_amd64.whl", hash = "sha256:7492e2b7bd7c9b9916388d9df23fa49d9b88ac0640db0a5b4ecc2b653bf451e3"}, - {file = "greenlet-2.0.2-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:b864ba53912b6c3ab6bcb2beb19f19edd01a6bfcbdfe1f37ddd1778abfe75a30"}, - {file = "greenlet-2.0.2-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:ba2956617f1c42598a308a84c6cf021a90ff3862eddafd20c3333d50f0edb45b"}, - {file = "greenlet-2.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc3a569657468b6f3fb60587e48356fe512c1754ca05a564f11366ac9e306526"}, - {file = "greenlet-2.0.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8eab883b3b2a38cc1e050819ef06a7e6344d4a990d24d45bc6f2cf959045a45b"}, - {file = "greenlet-2.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:acd2162a36d3de67ee896c43effcd5ee3de247eb00354db411feb025aa319857"}, - {file = "greenlet-2.0.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:0bf60faf0bc2468089bdc5edd10555bab6e85152191df713e2ab1fcc86382b5a"}, - {file = "greenlet-2.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b0ef99cdbe2b682b9ccbb964743a6aca37905fda5e0452e5ee239b1654d37f2a"}, - {file = "greenlet-2.0.2-cp38-cp38-win32.whl", hash = "sha256:b80f600eddddce72320dbbc8e3784d16bd3fb7b517e82476d8da921f27d4b249"}, - {file = "greenlet-2.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:4d2e11331fc0c02b6e84b0d28ece3a36e0548ee1a1ce9ddde03752d9b79bba40"}, - {file = "greenlet-2.0.2-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:88d9ab96491d38a5ab7c56dd7a3cc37d83336ecc564e4e8816dbed12e5aaefc8"}, - {file = "greenlet-2.0.2-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:561091a7be172ab497a3527602d467e2b3fbe75f9e783d8b8ce403fa414f71a6"}, - {file = "greenlet-2.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:971ce5e14dc5e73715755d0ca2975ac88cfdaefcaab078a284fea6cfabf866df"}, - {file = "greenlet-2.0.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:be4ed120b52ae4d974aa40215fcdfde9194d63541c7ded40ee12eb4dda57b76b"}, - {file = "greenlet-2.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94c817e84245513926588caf1152e3b559ff794d505555211ca041f032abbb6b"}, - {file = "greenlet-2.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:1a819eef4b0e0b96bb0d98d797bef17dc1b4a10e8d7446be32d1da33e095dbb8"}, - {file = "greenlet-2.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7efde645ca1cc441d6dc4b48c0f7101e8d86b54c8530141b09fd31cef5149ec9"}, - {file = "greenlet-2.0.2-cp39-cp39-win32.whl", hash = "sha256:ea9872c80c132f4663822dd2a08d404073a5a9b5ba6155bea72fb2a79d1093b5"}, - {file = "greenlet-2.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:db1a39669102a1d8d12b57de2bb7e2ec9066a6f2b3da35ae511ff93b01b5d564"}, - {file = "greenlet-2.0.2.tar.gz", hash = "sha256:e7c8dc13af7db097bed64a051d2dd49e9f0af495c26995c00a9ee842690d34c0"}, -] - -[package.extras] -docs = ["Sphinx", "docutils (<0.18)"] -test = ["objgraph", "psutil"] +resolved_reference = "0e98c135231af445dbbd45a2df4ef20684720512" [[package]] name = "idna" @@ -677,88 +603,58 @@ files = [ [[package]] name = "sqlalchemy" -version = "1.4.49" +version = "1.3.24" description = "Database Abstraction Library" optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ - {file = "SQLAlchemy-1.4.49-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:2e126cf98b7fd38f1e33c64484406b78e937b1a280e078ef558b95bf5b6895f6"}, - {file = "SQLAlchemy-1.4.49-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:03db81b89fe7ef3857b4a00b63dedd632d6183d4ea5a31c5d8a92e000a41fc71"}, - {file = "SQLAlchemy-1.4.49-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:95b9df9afd680b7a3b13b38adf6e3a38995da5e162cc7524ef08e3be4e5ed3e1"}, - {file = "SQLAlchemy-1.4.49-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a63e43bf3f668c11bb0444ce6e809c1227b8f067ca1068898f3008a273f52b09"}, - {file = "SQLAlchemy-1.4.49-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f835c050ebaa4e48b18403bed2c0fda986525896efd76c245bdd4db995e51a4c"}, - {file = "SQLAlchemy-1.4.49-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c21b172dfb22e0db303ff6419451f0cac891d2e911bb9fbf8003d717f1bcf91"}, - {file = "SQLAlchemy-1.4.49-cp310-cp310-win32.whl", hash = "sha256:5fb1ebdfc8373b5a291485757bd6431de8d7ed42c27439f543c81f6c8febd729"}, - {file = "SQLAlchemy-1.4.49-cp310-cp310-win_amd64.whl", hash = "sha256:f8a65990c9c490f4651b5c02abccc9f113a7f56fa482031ac8cb88b70bc8ccaa"}, - {file = "SQLAlchemy-1.4.49-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8923dfdf24d5aa8a3adb59723f54118dd4fe62cf59ed0d0d65d940579c1170a4"}, - {file = "SQLAlchemy-1.4.49-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9ab2c507a7a439f13ca4499db6d3f50423d1d65dc9b5ed897e70941d9e135b0"}, - {file = "SQLAlchemy-1.4.49-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5debe7d49b8acf1f3035317e63d9ec8d5e4d904c6e75a2a9246a119f5f2fdf3d"}, - {file = "SQLAlchemy-1.4.49-cp311-cp311-win32.whl", hash = "sha256:82b08e82da3756765c2e75f327b9bf6b0f043c9c3925fb95fb51e1567fa4ee87"}, - {file = "SQLAlchemy-1.4.49-cp311-cp311-win_amd64.whl", hash = "sha256:171e04eeb5d1c0d96a544caf982621a1711d078dbc5c96f11d6469169bd003f1"}, - {file = "SQLAlchemy-1.4.49-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:36e58f8c4fe43984384e3fbe6341ac99b6b4e083de2fe838f0fdb91cebe9e9cb"}, - {file = "SQLAlchemy-1.4.49-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b31e67ff419013f99ad6f8fc73ee19ea31585e1e9fe773744c0f3ce58c039c30"}, - {file = "SQLAlchemy-1.4.49-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c14b29d9e1529f99efd550cd04dbb6db6ba5d690abb96d52de2bff4ed518bc95"}, - {file = "SQLAlchemy-1.4.49-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c40f3470e084d31247aea228aa1c39bbc0904c2b9ccbf5d3cfa2ea2dac06f26d"}, - {file = "SQLAlchemy-1.4.49-cp36-cp36m-win32.whl", hash = "sha256:706bfa02157b97c136547c406f263e4c6274a7b061b3eb9742915dd774bbc264"}, - {file = "SQLAlchemy-1.4.49-cp36-cp36m-win_amd64.whl", hash = "sha256:a7f7b5c07ae5c0cfd24c2db86071fb2a3d947da7bd487e359cc91e67ac1c6d2e"}, - {file = "SQLAlchemy-1.4.49-cp37-cp37m-macosx_11_0_x86_64.whl", hash = "sha256:4afbbf5ef41ac18e02c8dc1f86c04b22b7a2125f2a030e25bbb4aff31abb224b"}, - {file = "SQLAlchemy-1.4.49-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:24e300c0c2147484a002b175f4e1361f102e82c345bf263242f0449672a4bccf"}, - {file = "SQLAlchemy-1.4.49-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:201de072b818f8ad55c80d18d1a788729cccf9be6d9dc3b9d8613b053cd4836d"}, - {file = "SQLAlchemy-1.4.49-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7653ed6817c710d0c95558232aba799307d14ae084cc9b1f4c389157ec50df5c"}, - {file = "SQLAlchemy-1.4.49-cp37-cp37m-win32.whl", hash = "sha256:647e0b309cb4512b1f1b78471fdaf72921b6fa6e750b9f891e09c6e2f0e5326f"}, - {file = "SQLAlchemy-1.4.49-cp37-cp37m-win_amd64.whl", hash = "sha256:ab73ed1a05ff539afc4a7f8cf371764cdf79768ecb7d2ec691e3ff89abbc541e"}, - {file = "SQLAlchemy-1.4.49-cp38-cp38-macosx_11_0_x86_64.whl", hash = "sha256:37ce517c011560d68f1ffb28af65d7e06f873f191eb3a73af5671e9c3fada08a"}, - {file = "SQLAlchemy-1.4.49-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1878ce508edea4a879015ab5215546c444233881301e97ca16fe251e89f1c55"}, - {file = "SQLAlchemy-1.4.49-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0e8e608983e6f85d0852ca61f97e521b62e67969e6e640fe6c6b575d4db68557"}, - {file = "SQLAlchemy-1.4.49-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ccf956da45290df6e809ea12c54c02ace7f8ff4d765d6d3dfb3655ee876ce58d"}, - {file = "SQLAlchemy-1.4.49-cp38-cp38-win32.whl", hash = "sha256:f167c8175ab908ce48bd6550679cc6ea20ae169379e73c7720a28f89e53aa532"}, - {file = "SQLAlchemy-1.4.49-cp38-cp38-win_amd64.whl", hash = "sha256:45806315aae81a0c202752558f0df52b42d11dd7ba0097bf71e253b4215f34f4"}, - {file = "SQLAlchemy-1.4.49-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:b6d0c4b15d65087738a6e22e0ff461b407533ff65a73b818089efc8eb2b3e1de"}, - {file = "SQLAlchemy-1.4.49-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a843e34abfd4c797018fd8d00ffffa99fd5184c421f190b6ca99def4087689bd"}, - {file = "SQLAlchemy-1.4.49-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:1c890421651b45a681181301b3497e4d57c0d01dc001e10438a40e9a9c25ee77"}, - {file = "SQLAlchemy-1.4.49-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d26f280b8f0a8f497bc10573849ad6dc62e671d2468826e5c748d04ed9e670d5"}, - {file = "SQLAlchemy-1.4.49-cp39-cp39-win32.whl", hash = "sha256:ec2268de67f73b43320383947e74700e95c6770d0c68c4e615e9897e46296294"}, - {file = "SQLAlchemy-1.4.49-cp39-cp39-win_amd64.whl", hash = "sha256:bbdf16372859b8ed3f4d05f925a984771cd2abd18bd187042f24be4886c2a15f"}, - {file = "SQLAlchemy-1.4.49.tar.gz", hash = "sha256:06ff25cbae30c396c4b7737464f2a7fc37a67b7da409993b182b024cec80aed9"}, + {file = "SQLAlchemy-1.3.24-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:87a2725ad7d41cd7376373c15fd8bf674e9c33ca56d0b8036add2d634dba372e"}, + {file = "SQLAlchemy-1.3.24-cp27-cp27m-win32.whl", hash = "sha256:f597a243b8550a3a0b15122b14e49d8a7e622ba1c9d29776af741f1845478d79"}, + {file = "SQLAlchemy-1.3.24-cp27-cp27m-win_amd64.whl", hash = "sha256:fc4cddb0b474b12ed7bdce6be1b9edc65352e8ce66bc10ff8cbbfb3d4047dbf4"}, + {file = "SQLAlchemy-1.3.24-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:f1149d6e5c49d069163e58a3196865e4321bad1803d7886e07d8710de392c548"}, + {file = "SQLAlchemy-1.3.24-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:14f0eb5db872c231b20c18b1e5806352723a3a89fb4254af3b3e14f22eaaec75"}, + {file = "SQLAlchemy-1.3.24-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:e98d09f487267f1e8d1179bf3b9d7709b30a916491997137dd24d6ae44d18d79"}, + {file = "SQLAlchemy-1.3.24-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:fc1f2a5a5963e2e73bac4926bdaf7790c4d7d77e8fc0590817880e22dd9d0b8b"}, + {file = "SQLAlchemy-1.3.24-cp35-cp35m-win32.whl", hash = "sha256:f3c5c52f7cb8b84bfaaf22d82cb9e6e9a8297f7c2ed14d806a0f5e4d22e83fb7"}, + {file = "SQLAlchemy-1.3.24-cp35-cp35m-win_amd64.whl", hash = "sha256:0352db1befcbed2f9282e72843f1963860bf0e0472a4fa5cf8ee084318e0e6ab"}, + {file = "SQLAlchemy-1.3.24-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:2ed6343b625b16bcb63c5b10523fd15ed8934e1ed0f772c534985e9f5e73d894"}, + {file = "SQLAlchemy-1.3.24-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:34fcec18f6e4b24b4a5f6185205a04f1eab1e56f8f1d028a2a03694ebcc2ddd4"}, + {file = "SQLAlchemy-1.3.24-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:e47e257ba5934550d7235665eee6c911dc7178419b614ba9e1fbb1ce6325b14f"}, + {file = "SQLAlchemy-1.3.24-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:816de75418ea0953b5eb7b8a74933ee5a46719491cd2b16f718afc4b291a9658"}, + {file = "SQLAlchemy-1.3.24-cp36-cp36m-win32.whl", hash = "sha256:26155ea7a243cbf23287f390dba13d7927ffa1586d3208e0e8d615d0c506f996"}, + {file = "SQLAlchemy-1.3.24-cp36-cp36m-win_amd64.whl", hash = "sha256:f03bd97650d2e42710fbe4cf8a59fae657f191df851fc9fc683ecef10746a375"}, + {file = "SQLAlchemy-1.3.24-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:a006d05d9aa052657ee3e4dc92544faae5fcbaafc6128217310945610d862d39"}, + {file = "SQLAlchemy-1.3.24-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:1e2f89d2e5e3c7a88e25a3b0e43626dba8db2aa700253023b82e630d12b37109"}, + {file = "SQLAlchemy-1.3.24-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:0d5d862b1cfbec5028ce1ecac06a3b42bc7703eb80e4b53fceb2738724311443"}, + {file = "SQLAlchemy-1.3.24-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:0172423a27fbcae3751ef016663b72e1a516777de324a76e30efa170dbd3dd2d"}, + {file = "SQLAlchemy-1.3.24-cp37-cp37m-win32.whl", hash = "sha256:d37843fb8df90376e9e91336724d78a32b988d3d20ab6656da4eb8ee3a45b63c"}, + {file = "SQLAlchemy-1.3.24-cp37-cp37m-win_amd64.whl", hash = "sha256:c10ff6112d119f82b1618b6dc28126798481b9355d8748b64b9b55051eb4f01b"}, + {file = "SQLAlchemy-1.3.24-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:861e459b0e97673af6cc5e7f597035c2e3acdfb2608132665406cded25ba64c7"}, + {file = "SQLAlchemy-1.3.24-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:5de2464c254380d8a6c20a2746614d5a436260be1507491442cf1088e59430d2"}, + {file = "SQLAlchemy-1.3.24-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:d375d8ccd3cebae8d90270f7aa8532fe05908f79e78ae489068f3b4eee5994e8"}, + {file = "SQLAlchemy-1.3.24-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:014ea143572fee1c18322b7908140ad23b3994036ef4c0d630110faf942652f8"}, + {file = "SQLAlchemy-1.3.24-cp38-cp38-win32.whl", hash = "sha256:6607ae6cd3a07f8a4c3198ffbf256c261661965742e2b5265a77cd5c679c9bba"}, + {file = "SQLAlchemy-1.3.24-cp38-cp38-win_amd64.whl", hash = "sha256:fcb251305fa24a490b6a9ee2180e5f8252915fb778d3dafc70f9cc3f863827b9"}, + {file = "SQLAlchemy-1.3.24-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:01aa5f803db724447c1d423ed583e42bf5264c597fd55e4add4301f163b0be48"}, + {file = "SQLAlchemy-1.3.24-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:4d0e3515ef98aa4f0dc289ff2eebb0ece6260bbf37c2ea2022aad63797eacf60"}, + {file = "SQLAlchemy-1.3.24-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:bce28277f308db43a6b4965734366f533b3ff009571ec7ffa583cb77539b84d6"}, + {file = "SQLAlchemy-1.3.24-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:8110e6c414d3efc574543109ee618fe2c1f96fa31833a1ff36cc34e968c4f233"}, + {file = "SQLAlchemy-1.3.24-cp39-cp39-win32.whl", hash = "sha256:ee5f5188edb20a29c1cc4a039b074fdc5575337c9a68f3063449ab47757bb064"}, + {file = "SQLAlchemy-1.3.24-cp39-cp39-win_amd64.whl", hash = "sha256:09083c2487ca3c0865dc588e07aeaa25416da3d95f7482c07e92f47e080aa17b"}, + {file = "SQLAlchemy-1.3.24.tar.gz", hash = "sha256:ebbb777cbf9312359b897bf81ba00dae0f5cb69fba2a18265dcc18a6f5ef7519"}, ] -[package.dependencies] -greenlet = {version = "!=0.4.17", markers = "python_version >= \"3\" and (platform_machine == \"win32\" or platform_machine == \"WIN32\" or platform_machine == \"AMD64\" or platform_machine == \"amd64\" or platform_machine == \"x86_64\" or platform_machine == \"ppc64le\" or platform_machine == \"aarch64\")"} - [package.extras] -aiomysql = ["aiomysql", "greenlet (!=0.4.17)"] -aiosqlite = ["aiosqlite", "greenlet (!=0.4.17)", "typing-extensions (!=3.10.0.1)"] -asyncio = ["greenlet (!=0.4.17)"] -asyncmy = ["asyncmy (>=0.2.3,!=0.2.4)", "greenlet (!=0.4.17)"] -mariadb-connector = ["mariadb (>=1.0.1,!=1.1.2)"] mssql = ["pyodbc"] mssql-pymssql = ["pymssql"] mssql-pyodbc = ["pyodbc"] -mypy = ["mypy (>=0.910)", "sqlalchemy2-stubs"] -mysql = ["mysqlclient (>=1.4.0)", "mysqlclient (>=1.4.0,<2)"] -mysql-connector = ["mysql-connector-python"] -oracle = ["cx-oracle (>=7)", "cx-oracle (>=7,<8)"] -postgresql = ["psycopg2 (>=2.7)"] -postgresql-asyncpg = ["asyncpg", "greenlet (!=0.4.17)"] -postgresql-pg8000 = ["pg8000 (>=1.16.6,!=1.29.0)"] +mysql = ["mysqlclient"] +oracle = ["cx-oracle"] +postgresql = ["psycopg2"] +postgresql-pg8000 = ["pg8000 (<1.16.6)"] postgresql-psycopg2binary = ["psycopg2-binary"] postgresql-psycopg2cffi = ["psycopg2cffi"] pymysql = ["pymysql", "pymysql (<1)"] -sqlcipher = ["sqlcipher3-binary"] - -[[package]] -name = "sqlalchemy-json" -version = "0.6.0" -description = "JSON type with nested change tracking for SQLAlchemy" -optional = false -python-versions = ">= 3.6" -files = [ - {file = "sqlalchemy-json-0.6.0.tar.gz", hash = "sha256:94785325716f0649ec6f4d0304ce8803a5bf25ff8aa2d998ce6b6334192f7448"}, - {file = "sqlalchemy_json-0.6.0-py3-none-any.whl", hash = "sha256:06f124c093368903859367c7ab22416c4428e90c09ae14def8391f06a2373c1f"}, -] - -[package.dependencies] -sqlalchemy = ">=0.7" [[package]] name = "tzlocal" @@ -837,4 +733,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = "^3.8" -content-hash = "541fac7e10803d5006c96f7f6e061964dd98c8a22978e2d1779b0318e57d0cbe" +content-hash = "81b89caa08e7dad5a76e7228d335d0cbeb657787f790e9b36419407c42a19be7" diff --git a/pyproject.toml b/pyproject.toml index 90a708e..25753ef 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,7 +18,7 @@ Flask = "^2.3" flask-sqlalchemy = "^2.5" cryptography = "^41.0" psycopg2 = "^2.9.6" -sqlalchemy = "^1.4" +sqlalchemy = "~1.3" [build-system] requires = ["poetry-core"] diff --git a/scripts/nginx/templates/default.conf.template b/scripts/nginx/templates/default.conf.template index 2e27e4c..5082df2 100644 --- a/scripts/nginx/templates/default.conf.template +++ b/scripts/nginx/templates/default.conf.template @@ -65,7 +65,7 @@ http { large_client_header_buffers 4 ${NGINX_HTTP_CLIENT_MAX_HEADER_SIZE}; add_header Strict-Transport-Security max-age=31536000; - ssl_verify_client on; + #ssl_verify_client on; ssl_client_certificate ${EO_SSL_CALIST_PATH}; ssl_certificate ${EO_SSL_CERT_PATH}; ssl_certificate_key ${EO_SSL_KEY_PATH}; From d5cb8ad02cd367306ae6ef90b3565d98a95faf22 Mon Sep 17 00:00:00 2001 From: Eduardo Robles Date: Tue, 1 Aug 2023 06:05:27 +0000 Subject: [PATCH 25/26] WIP --- .../create_election/performer_jobs.py | 23 ++++++++++--------- .../tally_election/performer_jobs.py | 12 +++++----- election_orchestra/utils.py | 5 ++-- election_orchestra/vmn.py | 2 ++ poetry.lock | 2 +- 5 files changed, 24 insertions(+), 20 deletions(-) diff --git a/election_orchestra/create_election/performer_jobs.py b/election_orchestra/create_election/performer_jobs.py index f46195c..b50d33c 100644 --- a/election_orchestra/create_election/performer_jobs.py +++ b/election_orchestra/create_election/performer_jobs.py @@ -8,7 +8,7 @@ import re import os import codecs -import subprocess +import logging import json import shutil import signal @@ -197,7 +197,8 @@ def generate_private_info(task): # 2. create base local data from received input in case it's needed: # create election models, dirs and stubs if we are not the director - if certs_differ(task.get_data()['sender_ssl_cert'], app.config.get('SSL_CERT_STRING', '')): + if task.get_data()['sender_url'] != app.config.get('ROOT_URL'): + logging.debug(f"generate_private_info(): we are NOT the director") if os.path.exists(election_privpath): raise TaskError(dict( reason="Already existing election id %d" % input_data['id'] @@ -236,6 +237,7 @@ def generate_private_info(task): db.session.add(session_model) session_privpath = os.path.join(election_privpath, session['id']) + logging.debug(f"generate_private_info(): creating dir session_privpath={session_privpath}") mkdir_recursive(session_privpath) stub_path = os.path.join(session_privpath, 'stub.xml') stub_file = codecs.open(stub_path, 'w', encoding='utf-8') @@ -244,6 +246,7 @@ def generate_private_info(task): i += 1 db.session.commit() else: + logging.debug(f"generate_private_info(): we are the director") # if we are the director, models, dirs and stubs have been created # already, so we just get the election from the database election = db.session.query(Election)\ @@ -340,6 +343,7 @@ def generate_public_key(task): ''' Generates the local private info for a new election ''' + logging.debug(f"generate_public_key(): start") input_data = task.get_data()['input_data'] session_id = input_data['session_id'] election_id = input_data['election_id'] @@ -349,15 +353,18 @@ def generate_public_key(task): # some sanity checks, as this is not a local task if not os.path.exists(session_privpath): + logging.debug(f"generate_public_key(): not os.path.exists(session_privpath)") raise TaskError(dict( reason="invalid session_id / election_id: " + session_privpath )) if os.path.exists(os.path.join(session_privpath, 'publicKey_raw')) or\ os.path.exists(os.path.join(session_privpath, 'publicKey_json')): + logging.debug(f"generate_public_key(): pubkey already created") raise TaskError(dict(reason="pubkey already created")) # if it's not local, we have to create the merged protInfo.xml protinfo_path = os.path.join(session_privpath, 'protInfo.xml') + logging.debug(f"generate_public_key(): if it's not local, we have to create the merged protInfo.xml") if not os.path.exists(protinfo_path): protinfo_file = codecs.open(protinfo_path, 'w', encoding='utf-8') protinfo_file.write(input_data['protInfo_content']) @@ -375,17 +382,9 @@ def output_filter(p, o, output): #call_cmd(["vmn", "-keygen", "publicKey_raw"], cwd=session_privpath, # timeout=10*60, check_ret=0, output_filter=output_filter) + logging.debug(f"calling v_gen_public_key..") v_gen_public_key(session_privpath, output_filter) - - def output_filter2(p, o, output): - ''' - detect common errors and kill process in that case - ''' - if "Failed to parse info files!" in o: - p.kill(signal.SIGKILL) - raise TaskError(dict(reason='error executing mixnet')) - # transform it into json format #call_cmd(["vmnc", "-pkey", "-outi", "json", "publicKey_raw", # "publicKey_json"], cwd=session_privpath, @@ -405,3 +404,5 @@ def output_filter2(p, o, output): protinfo_privpath = os.path.join(session_privpath, 'protInfo.xml') protinfo_pubpath = os.path.join(session_pubpath, 'protInfo.xml') shutil.copyfile(protinfo_privpath, protinfo_pubpath) + logging.debug(f"generate_public_key(): session_privpath={session_privpath}") + logging.debug(f"generate_public_key(): session_privpath={session_privpath} end") diff --git a/election_orchestra/tally_election/performer_jobs.py b/election_orchestra/tally_election/performer_jobs.py index eee5e3c..e290ebb 100644 --- a/election_orchestra/tally_election/performer_jobs.py +++ b/election_orchestra/tally_election/performer_jobs.py @@ -72,7 +72,7 @@ def review_tally(task): ''' Generates the local private info for a new election ''' - sender_ssl_cert = task.get_data()['sender_ssl_cert'] + sender_url = task.get_data()['sender_url'] data = task.get_data()['input_data'] # check input data @@ -107,7 +107,7 @@ def review_tally(task): # check sender is legitimate found_director = False for auth in election.authorities.all(): - if not certs_differ(auth.ssl_cert, sender_ssl_cert): + if auth.orchestra_url != sender_url: found_director = True if not found_director: raise TaskError(dict(reason="review tally sent by an invalid authority")) @@ -346,7 +346,7 @@ def execute(self): Performs the tally in a synchronized way with the other authorities ''' input_data = self.task.get_data()['input_data'] - sender_ssl_cert = self.task.get_data()['sender_ssl_cert'] + sender_url = self.task.get_data()['sender_url'] election_id = input_data['election_id'] session_id = input_data['session_id'] @@ -363,7 +363,7 @@ def execute(self): # check sender is legitimate found_director = False for auth in election.authorities.all(): - if not certs_differ(auth.ssl_cert, sender_ssl_cert): + if auth.orchestra_url != sender_url: found_director = True if not found_director: raise TaskError(dict( @@ -432,7 +432,7 @@ def verify_and_publish_tally(task): ''' Once a tally has been performed, verify the result and if it's ok publish it ''' - sender_ssl_cert = task.get_data()['sender_ssl_cert'] + sender_url = task.get_data()['sender_url'] input_data = task.get_data()['input_data'] election_id = input_data['election_id'] if election_id <= 0: @@ -446,7 +446,7 @@ def verify_and_publish_tally(task): # check sender is legitimate found_director = False for auth in election.authorities.all(): - if not certs_differ(auth.ssl_cert, sender_ssl_cert): + if auth.orchestra_url != sender_url: found_director = True if not found_director: raise TaskError(dict( diff --git a/election_orchestra/utils.py b/election_orchestra/utils.py index 3702146..49600f7 100644 --- a/election_orchestra/utils.py +++ b/election_orchestra/utils.py @@ -9,6 +9,7 @@ import signal import time import subprocess +import logging import hashlib from frestq.app import app @@ -45,7 +46,7 @@ def call_cmd(cmd, timeout=-1, output_filter=None, cwd=None, check_ret=None): Utility to call a command. timeout is in seconds. ''' - print("call_cmd: calling " + " ".join(cmd)) + logging.debug("call_cmd: calling " + " ".join(cmd)) p = Process(cmd, cwd=cwd, stderr=subprocess.STDOUT) launch_time = time.process_time() output = "" @@ -56,7 +57,7 @@ def call_cmd(cmd, timeout=-1, output_filter=None, cwd=None, check_ret=None): # print any new output o = p.read().decode('utf-8') if len(o) > 0: - print("output = %s" % o) + logging.debug("output = %s" % o) if output_filter: output_filter(p, o, output) diff --git a/election_orchestra/vmn.py b/election_orchestra/vmn.py index f3b5288..d43cee0 100644 --- a/election_orchestra/vmn.py +++ b/election_orchestra/vmn.py @@ -4,6 +4,7 @@ # SPDX-License-Identifier: AGPL-3.0-only # import subprocess +import logging from .utils import * from frestq.app import app @@ -49,6 +50,7 @@ def v_merge(protinfos, session_privpath): @pre_kill_mixnet def v_gen_public_key(session_privpath, output_filter): + logging.debug("v_gen_public_key(): called") return call_cmd(["vmn", "-keygen", "publicKey_raw"], cwd=session_privpath, timeout=10*60, check_ret=0, output_filter=output_filter) diff --git a/poetry.lock b/poetry.lock index e4565f6..21c5a27 100644 --- a/poetry.lock +++ b/poetry.lock @@ -344,7 +344,7 @@ sqlalchemy = "~1.3" type = "git" url = "https://github.com/sequentech/frestq.git" reference = "feat/master/k8s" -resolved_reference = "0e98c135231af445dbbd45a2df4ef20684720512" +resolved_reference = "1c9de47aa5b8f2d3beaacc3821cf5e8d483c12bf" [[package]] name = "idna" From 619a8d423447bb921413781f6bd75689427c63c2 Mon Sep 17 00:00:00 2001 From: Eduardo Robles Date: Tue, 1 Aug 2023 08:43:47 +0000 Subject: [PATCH 26/26] WIP --- docker-compose.yml | 6 +++++- election_orchestra/utils.py | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index e0c9dfa..117de31 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -22,6 +22,7 @@ x-trustee-nginx-common: &trustee_nginx_common env_file: .env ports: - "8080" + - 8084/udp environment: &trustee_nginx_env NGINX_PORT: "8080" DOLLAR: "$" @@ -34,6 +35,8 @@ services: CERT_COMMON_NAME: trustee1_nginx EO_SQLALCHEMY_DATABASE_URI: postgresql+psycopg2://db1:db1@db1/db1 EO_ROOT_URL: https://trustee1_nginx:8080/api/queues + EO_VFORK_SERVER_URL: http://trustee1 + EO_VFORK_HINT_SERVER_SOCKET: trustee1 depends_on: <<: *trustee_depends_on db1: @@ -50,8 +53,9 @@ services: SERVICE_NAME: trustee2 CERT_COMMON_NAME: trustee2_nginx EO_SQLALCHEMY_DATABASE_URI: postgresql+psycopg2://db2:db2@db2/db2 - EO_ROOT_URL: https://trustee2_nginx:8080/api/queues + EO_VFORK_SERVER_URL: http://trustee2 + EO_VFORK_HINT_SERVER_SOCKET: trustee2 depends_on: <<: *trustee_depends_on db2: diff --git a/election_orchestra/utils.py b/election_orchestra/utils.py index 49600f7..521e14e 100644 --- a/election_orchestra/utils.py +++ b/election_orchestra/utils.py @@ -46,7 +46,7 @@ def call_cmd(cmd, timeout=-1, output_filter=None, cwd=None, check_ret=None): Utility to call a command. timeout is in seconds. ''' - logging.debug("call_cmd: calling " + " ".join(cmd)) + logging.debug(f"call_cmd: calling `{' '.join(cmd)}` with cwd=`{cwd}`") p = Process(cmd, cwd=cwd, stderr=subprocess.STDOUT) launch_time = time.process_time() output = ""