From f501d519afe8341b1b1c084dd87f41019c2f5e42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois?= Date: Mon, 3 Jun 2024 18:43:35 +0200 Subject: [PATCH] first commit --- LICENSE | 515 ++ README.md | 42 +- bin/.applyTemplate-completion.bash | 30 + bin/.commonFunctions.sh | 382 ++ bin/.container-completion.bash | 66 + bin/.dns-completion.bash | 19 + bin/.foreign-domain-completion.bash | 79 + bin/.gestContainers-completion.bash | 22 + bin/.kazDockerNet-completion.bash | 51 + bin/.kazList-completion.bash | 83 + bin/.mvOrga2Nas-completion.bash | 39 + bin/.orga-gen-completion.bash | 63 + bin/.updateLook-completion.bash | 11 + bin/applyTemplate.sh | 102 + bin/checkEnvFiles.sh | 315 + bin/checkEnvPassword.sh | 11 + bin/configKaz.sh | 24 + bin/configKaz.sh.templates | 11 + bin/container.sh | 319 + bin/createEmptyPasswd.sh | 104 + bin/createSrcDocker.sh | 16 + bin/createUser.sh | 796 +++ bin/cron-cloud.sh | 7 + bin/dns.sh | 209 + bin/envoiMails.sh | 66 + bin/foreign-domain.sh | 240 + bin/gestContainers.sh | 648 +++ bin/gestContainers_v2.sh | 659 +++ bin/gestUsers.sh | 1151 ++++ bin/indicateurs.sh | 77 + bin/init.sh | 220 + bin/install.sh | 144 + bin/installDepollueur.sh | 27 + bin/interoPaheko.sh | 166 + bin/iptables.sh | 14 + bin/kazDockerNet.sh | 110 + bin/kazList.sh | 181 + bin/ldap/ldap_sauve.sh | 12 + bin/ldap/ldapvi.sh | 23 + bin/ldap/migrate_to_ldap.sh | 222 + bin/ldap/tests/nc_orphans.sh | 20 + bin/look/feminin/kaz-entier.png | Bin 0 -> 26978 bytes bin/look/feminin/kaz-entier.svg | 90 + bin/look/feminin/kaz-signature.png | Bin 0 -> 2505 bytes bin/look/feminin/kaz-tete.png | Bin 0 -> 2284 bytes bin/look/feminin/kaz-tete.svg | 89 + bin/look/feminin/kazdate.png | Bin 0 -> 10795 bytes bin/look/feminin/kazmel.png | Bin 0 -> 9042 bytes bin/look/greve/kaz-entier.png | Bin 0 -> 29216 bytes bin/look/greve/kaz-entier.svg | 100 + bin/look/greve/kaz-signature.png | Bin 0 -> 2905 bytes bin/look/greve/kaz-tete.png | Bin 0 -> 3021 bytes bin/look/greve/kaz-tete.svg | 98 + bin/look/greve/kazdate.png | Bin 0 -> 11601 bytes bin/look/greve/kazmel.png | Bin 0 -> 9897 bytes bin/look/kaz/kaz-entier.png | Bin 0 -> 23986 bytes bin/look/kaz/kaz-entier.svg | 79 + bin/look/kaz/kaz-signature.png | Bin 0 -> 2333 bytes bin/look/kaz/kaz-tete.png | Bin 0 -> 2160 bytes bin/look/kaz/kaz-tete.svg | 66 + bin/look/kaz/kazdate.png | Bin 0 -> 10205 bytes bin/look/kaz/kazmel.png | Bin 0 -> 8445 bytes bin/look/noel/kaz-entier.png | Bin 0 -> 43954 bytes bin/look/noel/kaz-entier.svg | 87 + bin/look/noel/kaz-signature.png | Bin 0 -> 2678 bytes bin/look/noel/kaz-tete.png | Bin 0 -> 11272 bytes bin/look/noel/kaz-tete.svg | 75 + bin/look/noel/kazdate.png | Bin 0 -> 12068 bytes bin/look/noel/kazmel.png | Bin 0 -> 9799 bytes bin/manageAgora.sh | 180 + bin/manageCastopod.sh | 117 + bin/manageCloud.sh | 393 ++ bin/manageWiki.sh | 177 + bin/manageWp.sh | 130 + bin/migVersProdX.sh | 148 + bin/migration.sh | 44 + bin/mvOrga2Nas.sh | 172 + bin/nettoie | 83 + bin/nextcloud_maintenance.sh | 24 + bin/postfix-superviz.sh | 27 + bin/runAlertings.sh | 17 + bin/sauve_memory.sh | 37 + bin/sauve_serveur.sh | 18 + bin/scriptBorg.sh | 385 ++ bin/scriptSauve.sh | 161 + bin/secretGen.sh | 72 + bin/setOwner.sh | 58 + bin/updateAllOrga.sh | 13 + bin/updateDockerPassword.sh | 121 + bin/updateGit.sh | 447 ++ bin/updateLook.sh | 36 + bin/upgradeDockerCompose.sh | 13 + bin/verifExistenceMails.sh | 72 + bin/vide_poubelle | 11 + config/container-mail.list.tmpl | 4 + config/container-orga.list.tmpl | 1 + config/container-proxy.list.tmpl | 2 + config/container-withMail.list.tmpl | 12 + config/container-withoutMail.list.tmpl | 6 + config/dockers.tmpl.env | 153 + config/orgaTmpl/.env | 1 + config/orgaTmpl/app/Dockerfile | 58 + config/orgaTmpl/app/entrypoint.sh | 82 + config/orgaTmpl/docker-compose.yml | 307 + config/orgaTmpl/init-db.sh | 75 + config/orgaTmpl/init-paheko.sh | 27 + config/orgaTmpl/init-volume.sh | 39 + config/orgaTmpl/initdb.d/orga.sql | 3 + config/orgaTmpl/orga-gen.sh | 486 ++ config/orgaTmpl/orga-rm.sh | 75 + config/orgaTmpl/reload.sh | 15 + config/orgaTmpl/wiki-conf/acl.auth.php | 10 + config/orgaTmpl/wiki-conf/local.php | 26 + config/orgaTmpl/wiki-conf/users.auth.php | 13 + config/proxy/proxy_params | 21 + config/skip-file.txt | 89 + dockers/apikaz/Readme.txt | 22 + dockers/apikaz/docker-compose.yml | 56 + dockers/apikaz/source/Dockerfile | 31 + dockers/apikaz/source/Sympa/Constants.pm | 75 + dockers/apikaz/source/Sympa/Language.pm | 1225 ++++ dockers/apikaz/source/Sympa/Regexps.pm | 128 + dockers/apikaz/source/Sympa/Tools/Data.pm | 506 ++ dockers/apikaz/source/Sympa/Tools/Text.pm | 942 +++ .../apikaz/source/Sympa/sympa_soap_client.pl | 327 ++ dockers/apikaz/source/app.py | 1851 ++++++ dockers/apikaz/source/index.html | 1 + dockers/apikaz/source/requirements.txt | 9 + dockers/apikaz/source/static/favicon.ico | Bin 0 -> 1086 bytes dockers/apikaz/source/templates/email.css | 82 + .../apikaz/source/templates/email_footer.html | 9 + .../apikaz/source/templates/email_header.html | 6 + .../source/templates/email_inscription.html | 94 + dockers/cachet/.env | 1 + dockers/cachet/docker-compose.yml | 47 + dockers/castopod/.env | 1 + dockers/castopod/docker-compose.yml | 55 + dockers/castopod/first.sh | 13 + dockers/cloud/.env | 1 + dockers/cloud/Readme.txt | 90 + dockers/cloud/docker-compose.yml | 77 + dockers/cloud/first.sh | 10 + dockers/cloud/media/favicon.ico | Bin 0 -> 9086 bytes dockers/cloud/media/logo.png | Bin 0 -> 88468 bytes dockers/cloud/media/logoheader.png | Bin 0 -> 9994 bytes dockers/cloud/reindex.sh | 3 + dockers/collabora/.env | 1 + dockers/collabora/Readme.txt | 37 + dockers/collabora/docker-compose.yml | 40 + dockers/dokuwiki/.env | 1 + dockers/dokuwiki/Dockerfile | 85 + dockers/dokuwiki/docker-compose.yml | 40 + dockers/dokuwiki/download.sh | 19 + dockers/dokuwiki/first.sh | 10 + dockers/dokuwiki/htaccess | 43 + dockers/dokuwiki/wiki-conf/acl.auth.php | 10 + dockers/dokuwiki/wiki-conf/favicon.ico | Bin 0 -> 9086 bytes dockers/dokuwiki/wiki-conf/local.php | 26 + dockers/dokuwiki/wiki-conf/logo.png | Bin 0 -> 9389 bytes dockers/dokuwiki/wiki-conf/users.auth.php | 13 + dockers/ethercalc/.env | 1 + dockers/ethercalc/Dockerfile | 9 + dockers/ethercalc/docker-compose.yml | 39 + dockers/etherpad/.env | 1 + dockers/etherpad/docker-compose.yml | 64 + dockers/etherpad/settings.json | 650 +++ dockers/framadate/.env | 1 + dockers/framadate/Dockerfile | 67 + dockers/framadate/build.sh | 9 + dockers/framadate/composer-setup.sh | 18 + dockers/framadate/config/framadate.conf | 31 + dockers/framadate/docker-compose.yml | 58 + dockers/framadate/download.sh | 24 + dockers/framadate/first.sh | 44 + dockers/framadate/kazclassic.png | Bin 0 -> 37588 bytes dockers/framadate/kazdate.png | Bin 0 -> 10205 bytes dockers/framadate/kazdates.png | Bin 0 -> 32740 bytes dockers/framadate/patch/adminstuds.php.patch | 15 + .../patch/create_classic_poll.php.patch | 17 + .../patch/create_date_poll.php.patch | 17 + dockers/framadate/patch/en.json.patch | 10 + dockers/framadate/patch/find_polls.php.patch | 17 + dockers/framadate/patch/fr.json.patch | 10 + dockers/gitea/.env | 1 + dockers/gitea/docker-compose.yml | 53 + dockers/gitea/first.sh | 72 + dockers/gitea/logo.svg | 979 ++++ dockers/grafana/.env | 1 + dockers/grafana/docker-compose.yml | 67 + dockers/grafana/grafana.env | 6 + .../provisioning/dashboards/dashboard.yml | 21 + .../dashboards/reverse-proxy_rev1.json | 1293 +++++ .../provisioning/datasources/datasource.yml | 50 + dockers/grafana/prometheus/alert.rules | 11 + dockers/grafana/prometheus/prometheus.yml | 12 + dockers/imapsync/.env | 1 + dockers/imapsync/docker-compose.yml | 31 + dockers/imapsync/imapsync_form.css | 66 + dockers/imapsync/imapsync_form_extra.html | 484 ++ dockers/jirafeau/.env | 1 + dockers/jirafeau/Dockerfile | 75 + dockers/jirafeau/build.sh | 32 + dockers/jirafeau/composer-setup.sh | 18 + dockers/jirafeau/config/composer.json | 5 + dockers/jirafeau/config/jirafeau.conf | 24 + dockers/jirafeau/docker-compose.yml | 44 + dockers/jirafeau/download.sh | 25 + dockers/jirafeau/first.sh | 50 + dockers/jirafeau/media/kaz/bandeau.png | Bin 0 -> 4712 bytes dockers/jirafeau/media/kaz/email.png | Bin 0 -> 596 bytes dockers/jirafeau/media/kaz/error.png | Bin 0 -> 525 bytes dockers/jirafeau/media/kaz/favicon.ico | Bin 0 -> 1150 bytes dockers/jirafeau/media/kaz/jyraphe.png | Bin 0 -> 11453 bytes dockers/jirafeau/media/kaz/kaz.png | Bin 0 -> 9389 bytes dockers/jirafeau/media/kaz/ok.png | Bin 0 -> 492 bytes dockers/jirafeau/media/kaz/style.css.php | 205 + dockers/ldap/.env | 1 + dockers/ldap/UIHooks/post-hook.sh | 16 + dockers/ldap/base/acl.ldif.tmpl | 21 + dockers/ldap/base/kaz-schema.ldif.tmpl | 35 + dockers/ldap/base/skeleton.ldif.tmpl | 36 + dockers/ldap/br.inc.php | 156 + dockers/ldap/docker-compose.yml | 84 + dockers/ldap/first.sh | 88 + dockers/ldap/schemas/kaz.ldif | 11 + dockers/ldap/schemas/nextcloud.ldif | 25 + dockers/ldap/schemas/postfixbook.ldif | 41 + dockers/ldap/update.sh | 40 + dockers/mattermost/.env | 1 + dockers/mattermost/docker-compose.yml | 94 + dockers/mattermost/first.sh | 13 + dockers/mobilizon/.env | 1 + dockers/mobilizon/config.exs | 98 + dockers/mobilizon/docker-compose.yml | 59 + dockers/paheko/.env | 1 + dockers/paheko/Dockerfile | 94 + dockers/paheko/build.sh | 38 + dockers/paheko/composer-setup.sh | 18 + dockers/paheko/config/config.local.tmpl.php | 129 + dockers/paheko/config/factory_cron.sh | 17 + dockers/paheko/config/factory_cron_emails.sh | 14 + dockers/paheko/config/facturation.tar.gz | Bin 0 -> 231964 bytes dockers/paheko/config/paheko.conf | 26 + dockers/paheko/config/setupWebRights.sh | 3 + dockers/paheko/docker-compose-gen.sh | 24 + dockers/paheko/docker-compose.yml | 38 + dockers/paheko/first.sh | 19 + dockers/paheko/paheko-gen.sh | 12 + dockers/paheko/reload.sh | 4 + dockers/postfix/.env | 1 + dockers/postfix/Dockerfile | 65 + dockers/postfix/Readme.txt | 14 + dockers/postfix/build.sh | 11 + dockers/postfix/config | 1 + dockers/postfix/docker-compose.yml | 64 + dockers/postfix/env-config | 430 ++ dockers/postfix/first.sh | 33 + dockers/postfix2/.env | 1 + dockers/postfix2/docker-compose.yml | 42 + dockers/postfix2/env-config | 416 ++ dockers/proxy/.env | 1 + dockers/proxy/Dockerfile | 22 + dockers/proxy/Readme.txt | 43 + dockers/proxy/build.sh | 11 + dockers/proxy/config/nginx.tmpl.conf | 734 +++ dockers/proxy/docker-compose.tmpl.yml.dist | 242 + dockers/proxy/proxy-gen.sh | 127 + dockers/proxy/reload.sh | 3 + dockers/proxy/todo-ssl | 68 + dockers/quotas/.env | 1 + dockers/quotas/Dockerfile | 21 + dockers/quotas/build.sh | 12 + dockers/quotas/docker-compose.yml | 45 + dockers/quotas/html/.gitignore | 2 + .../quotas/html/LancementAffichageQuota.txt | 51 + dockers/quotas/html/README.md | 50 + dockers/quotas/html/express_webapp/app.js | 52 + dockers/quotas/html/express_webapp/bin/www | 90 + dockers/quotas/html/express_webapp/data/db.js | 0 .../quotas/html/express_webapp/database.js | 18 + .../html/express_webapp/package-lock.json | 3149 ++++++++++ .../quotas/html/express_webapp/package.json | 24 + .../express_webapp/public/images/favicon.ico | Bin 0 -> 9086 bytes .../express_webapp/public/images/groupe.png | Bin 0 -> 32532 bytes .../public/images/individuel.png | Bin 0 -> 32941 bytes .../express_webapp/public/images/quotas.png | Bin 0 -> 32134 bytes .../public/images/serviceglobal.png | Bin 0 -> 36226 bytes .../public/stylesheets/style.css | 210 + .../html/express_webapp/routes/groupes.js | 16 + .../html/express_webapp/routes/index.js | 15 + .../html/express_webapp/routes/indexAdmin.js | 15 + .../html/express_webapp/routes/serviceg.js | 68 + .../html/express_webapp/routes/users.js | 209 + .../html/express_webapp/routes/usersAdmin.js | 100 + .../html/express_webapp/views/error.jade | 6 + .../html/express_webapp/views/groupes.jade | 32 + .../html/express_webapp/views/index.jade | 29 + .../html/express_webapp/views/indexAdmin.jade | 39 + .../html/express_webapp/views/layout.jade | 7 + .../html/express_webapp/views/serviceg.jade | 34 + .../html/express_webapp/views/users.jade | 41 + .../html/express_webapp/views/usersError.jade | 40 + .../html/express_webapp/views/usersfound.jade | 110 + dockers/quotas/html/groupes.html | 32 + dockers/quotas/html/images/favicon.ico | Bin 0 -> 9086 bytes dockers/quotas/html/images/groupe.png | Bin 0 -> 32532 bytes dockers/quotas/html/images/individuel.png | Bin 0 -> 32941 bytes dockers/quotas/html/images/quotas.png | Bin 0 -> 32134 bytes dockers/quotas/html/images/serviceglobal.png | Bin 0 -> 36226 bytes dockers/quotas/html/index.html | 35 + dockers/quotas/html/individuels.html | 32 + .../quotas/html/kaz.quota/package-lock.json | 167 + dockers/quotas/html/kaz.quota/package.json | 14 + dockers/quotas/html/package-lock.json | 6 + dockers/quotas/html/package.json | 1 + dockers/quotas/html/serviceG.html | 33 + dockers/quotas/html/style.css | 74 + dockers/quotas/initdb.d/quotas.sql | 28 + dockers/roundcube/.env | 1 + dockers/roundcube/Readme.txt | 50 + .../roundcube/config/add-to-config.inc.php | 14 + .../roundcube/config/custom_config.inc.php | 4 + dockers/roundcube/config/kazmel.png | Bin 0 -> 8445 bytes dockers/roundcube/docker-compose.yml | 54 + dockers/roundcube/first.sh | 20 + dockers/sympa/.env | 1 + dockers/sympa/Dockerfile | 127 + dockers/sympa/alerting/filter.sh | 9 + dockers/sympa/alerting/mailq.sh | 9 + dockers/sympa/alerting/sympa.sh | 80 + dockers/sympa/build.sh | 10 + dockers/sympa/config/aliases.sympa.postfix | 11 + dockers/sympa/config/postfix-wrapper.sh | 32 + dockers/sympa/config/postfix.sh | 41 + dockers/sympa/config/start.sh | 51 + dockers/sympa/config/supervisord.conf | 90 + dockers/sympa/config/sympa.preseed | 15 + dockers/sympa/config/transport | 13 + .../sympa/config/trusted_applications.conf | 5 + dockers/sympa/docker-compose.yml | 72 + dockers/sympa/first.sh | 17 + dockers/sympa/reload.sh | 10 + dockers/sympa/updateFirewall.sh | 16 + dockers/traefik/.env | 1 + .../conf/dynamic/certificates.yml.tmpl | 20 + dockers/traefik/conf/dynamic/conf.yml | 17 + dockers/traefik/conf/traefik.yml.old | 54 + dockers/traefik/docker-compose.tmpl.yml.dist | 219 + dockers/traefik/proxy-gen.sh | 168 + dockers/traefik/reload.sh | 4 + dockers/vaultwarden/.env | 1 + dockers/vaultwarden/docker-compose.yml | 65 + .../templates/email/admin_reset_password.hbs | 6 + .../email/admin_reset_password.html.hbs | 11 + .../templates/email/change_email.hbs | 8 + .../templates/email/change_email.html.hbs | 16 + .../templates/email/delete_account.hbs | 8 + .../templates/email/delete_account.html.hbs | 24 + .../templates/email/email_footer.hbs | 24 + .../templates/email/email_footer_text.hbs | 3 + .../templates/email/email_header.hbs | 94 + .../emergency_access_invite_accepted.hbs | 8 + .../emergency_access_invite_accepted.html.hbs | 21 + .../emergency_access_invite_confirmed.hbs | 6 + ...emergency_access_invite_confirmed.html.hbs | 16 + .../emergency_access_recovery_approved.hbs | 4 + ...mergency_access_recovery_approved.html.hbs | 11 + .../emergency_access_recovery_initiated.hbs | 6 + ...ergency_access_recovery_initiated.html.hbs | 16 + .../emergency_access_recovery_rejected.hbs | 4 + ...mergency_access_recovery_rejected.html.hbs | 11 + .../emergency_access_recovery_reminder.hbs | 6 + ...mergency_access_recovery_reminder.html.hbs | 16 + .../emergency_access_recovery_timed_out.hbs | 4 + ...ergency_access_recovery_timed_out.html.hbs | 11 + .../templates/email/incomplete_2fa_login.hbs | 10 + .../email/incomplete_2fa_login.html.hbs | 31 + .../templates/email/invite_accepted.hbs | 5 + .../templates/email/invite_accepted.html.hbs | 21 + .../templates/email/invite_confirmed.hbs | 5 + .../templates/email/invite_confirmed.html.hbs | 17 + .../templates/email/new_device_logged_in.hbs | 10 + .../email/new_device_logged_in.html.hbs | 31 + .../templates/email/pw_hint_none.hbs | 8 + .../templates/email/pw_hint_none.html.hbs | 21 + .../templates/email/pw_hint_some.hbs | 11 + .../templates/email/pw_hint_some.html.hbs | 27 + .../email/send_2fa_removed_from_org.hbs | 9 + .../email/send_2fa_removed_from_org.html.hbs | 16 + .../email/send_emergency_access_invite.hbs | 8 + .../send_emergency_access_invite.html.hbs | 24 + .../templates/email/send_org_invite.hbs | 10 + .../templates/email/send_org_invite.html.hbs | 24 + .../send_single_org_removed_from_org.hbs | 5 + .../send_single_org_removed_from_org.html.hbs | 11 + .../vaultwarden/templates/email/smtp_test.hbs | 6 + .../templates/email/smtp_test.html.hbs | 16 + .../templates/email/twofactor_email.hbs | 6 + .../templates/email/twofactor_email.html.hbs | 16 + .../templates/email/verify_email.hbs | 8 + .../templates/email/verify_email.html.hbs | 24 + .../vaultwarden/templates/email/welcome.hbs | 6 + .../templates/email/welcome.html.hbs | 16 + .../templates/email/welcome_must_verify.hbs | 8 + .../email/welcome_must_verify.html.hbs | 24 + dockers/web/.env | 1 + dockers/web/Readme.txt | 29 + dockers/web/docker-compose.yml | 27 + dockers/web/first.sh | 19 + dockers/web/html/LICENSE.txt | 63 + dockers/web/html/README.txt | 38 + dockers/web/html/actualite.html | 113 + .../html/assets/css/fontawesome-all.min.css | 5 + .../web/html/assets/css/fonts.googleapis.css | 168 + .../assets/css/fonts.googleapis.inutilise.css | 168 + dockers/web/html/assets/css/main.css | 3346 +++++++++++ dockers/web/html/assets/css/noscript.css | 15 + ...ydSBYKcSV-LCoeQqfX1RYOo3ig4vwkxduz8A.woff2 | Bin 0 -> 9540 bytes ...ydSBYKcSV-LCoeQqfX1RYOo3ig4vwlBduz8A.woff2 | Bin 0 -> 8280 bytes ...6xKydSBYKcSV-LCoeQqfX1RYOo3ig4vwlxdu.woff2 | Bin 0 -> 15764 bytes ...ydSBYKcSV-LCoeQqfX1RYOo3ig4vwmBduz8A.woff2 | Bin 0 -> 5676 bytes ...ydSBYKcSV-LCoeQqfX1RYOo3ig4vwmRduz8A.woff2 | Bin 0 -> 14808 bytes ...ydSBYKcSV-LCoeQqfX1RYOo3ig4vwmhduz8A.woff2 | Bin 0 -> 6696 bytes ...ydSBYKcSV-LCoeQqfX1RYOo3ig4vwmxduz8A.woff2 | Bin 0 -> 7360 bytes ...ydSBYKcSV-LCoeQqfX1RYOo3ik4zwkxduz8A.woff2 | Bin 0 -> 9532 bytes ...ydSBYKcSV-LCoeQqfX1RYOo3ik4zwlBduz8A.woff2 | Bin 0 -> 8384 bytes ...6xKydSBYKcSV-LCoeQqfX1RYOo3ik4zwlxdu.woff2 | Bin 0 -> 16064 bytes ...ydSBYKcSV-LCoeQqfX1RYOo3ik4zwmBduz8A.woff2 | Bin 0 -> 5764 bytes ...ydSBYKcSV-LCoeQqfX1RYOo3ik4zwmRduz8A.woff2 | Bin 0 -> 15136 bytes ...ydSBYKcSV-LCoeQqfX1RYOo3ik4zwmxduz8A.woff2 | Bin 0 -> 7516 bytes ...ydSBYKcSV-LCoeQqfX1RYOo3iu4nwkxduz8A.woff2 | Bin 0 -> 9212 bytes ...ydSBYKcSV-LCoeQqfX1RYOo3iu4nwlBduz8A.woff2 | Bin 0 -> 8080 bytes ...6xKydSBYKcSV-LCoeQqfX1RYOo3iu4nwlxdu.woff2 | Bin 0 -> 15324 bytes ...ydSBYKcSV-LCoeQqfX1RYOo3iu4nwmBduz8A.woff2 | Bin 0 -> 5576 bytes ...ydSBYKcSV-LCoeQqfX1RYOo3iu4nwmRduz8A.woff2 | Bin 0 -> 14508 bytes ...ydSBYKcSV-LCoeQqfX1RYOo3iu4nwmhduz8A.woff2 | Bin 0 -> 6572 bytes ...ydSBYKcSV-LCoeQqfX1RYOo3iu4nwmxduz8A.woff2 | Bin 0 -> 7196 bytes dockers/web/html/assets/html/footer.html | 127 + dockers/web/html/assets/html/header.html | 15 + dockers/web/html/assets/html/insert_js.html | 6 + dockers/web/html/assets/html/menu.html | 13 + dockers/web/html/assets/js/HTMLInclude.js | 65 + dockers/web/html/assets/js/HTMLInclude.min.js | 2 + dockers/web/html/assets/js/breakpoints.min.js | 2 + dockers/web/html/assets/js/browser.min.js | 2 + dockers/web/html/assets/js/jquery.min.js | 2 + dockers/web/html/assets/js/main.js | 185 + dockers/web/html/assets/js/util.js | 587 ++ dockers/web/html/assets/sass/base/_page.scss | 47 + dockers/web/html/assets/sass/base/_reset.scss | 76 + .../html/assets/sass/base/_typography.scss | 189 + .../html/assets/sass/components/_actions.scss | 101 + .../web/html/assets/sass/components/_box.scss | 26 + .../html/assets/sass/components/_button.scss | 86 + .../html/assets/sass/components/_form.scss | 212 + .../html/assets/sass/components/_icon.scss | 66 + .../html/assets/sass/components/_icons.scss | 19 + .../html/assets/sass/components/_image.scss | 64 + .../html/assets/sass/components/_list.scss | 56 + .../web/html/assets/sass/components/_row.scss | 31 + .../html/assets/sass/components/_section.scss | 25 + .../html/assets/sass/components/_table.scss | 81 + .../html/assets/sass/components/_tiles.scss | 258 + .../web/html/assets/sass/layout/_footer.scss | 139 + .../web/html/assets/sass/layout/_header.scss | 136 + .../web/html/assets/sass/layout/_main.scss | 15 + .../web/html/assets/sass/layout/_menu.scss | 164 + .../web/html/assets/sass/layout/_wrapper.scss | 26 + .../html/assets/sass/libs/_breakpoints.scss | 223 + .../web/html/assets/sass/libs/_functions.scss | 90 + .../web/html/assets/sass/libs/_html-grid.scss | 149 + .../web/html/assets/sass/libs/_mixins.scss | 78 + dockers/web/html/assets/sass/libs/_vars.scss | 50 + .../web/html/assets/sass/libs/_vendor.scss | 376 ++ dockers/web/html/assets/sass/main.scss | 54 + dockers/web/html/assets/sass/noscript.scss | 23 + .../html/assets/webfonts/fa-brands-400.eot | Bin 0 -> 129352 bytes .../html/assets/webfonts/fa-brands-400.svg | 3442 +++++++++++ .../html/assets/webfonts/fa-brands-400.ttf | Bin 0 -> 129048 bytes .../html/assets/webfonts/fa-brands-400.woff | Bin 0 -> 87352 bytes .../html/assets/webfonts/fa-brands-400.woff2 | Bin 0 -> 74508 bytes .../html/assets/webfonts/fa-regular-400.eot | Bin 0 -> 34388 bytes .../html/assets/webfonts/fa-regular-400.svg | 803 +++ .../html/assets/webfonts/fa-regular-400.ttf | Bin 0 -> 34092 bytes .../html/assets/webfonts/fa-regular-400.woff | Bin 0 -> 16804 bytes .../html/assets/webfonts/fa-regular-400.woff2 | Bin 0 -> 13580 bytes .../web/html/assets/webfonts/fa-solid-900.eot | Bin 0 -> 192116 bytes .../web/html/assets/webfonts/fa-solid-900.svg | 4649 +++++++++++++++ .../web/html/assets/webfonts/fa-solid-900.ttf | Bin 0 -> 191832 bytes .../html/assets/webfonts/fa-solid-900.woff | Bin 0 -> 98020 bytes .../html/assets/webfonts/fa-solid-900.woff2 | Bin 0 -> 75440 bytes dockers/web/html/cgi-bin/sendmail | 3 + dockers/web/html/cgi-bin/test.html | 1 + .../style.scssc | Bin 0 -> 1737 bytes .../extend.scssc | Bin 0 -> 3007 bytes .../fonts.scssc | Bin 0 -> 65130 bytes .../global.scssc | Bin 0 -> 20540 bytes .../minxi.scssc | Bin 0 -> 41639 bytes .../variables.scssc | Bin 0 -> 1265 bytes .../style.scssc | Bin 0 -> 1745 bytes .../extend.scssc | Bin 0 -> 3022 bytes .../fonts.scssc | Bin 0 -> 65145 bytes .../global.scssc | Bin 0 -> 20145 bytes .../minxi.scssc | Bin 0 -> 41654 bytes .../variables.scssc | Bin 0 -> 1463 bytes .../style.scssc | Bin 0 -> 1763 bytes .../main.scssc | Bin 0 -> 67800 bytes .../responsive.scssc | Bin 0 -> 7510 bytes .../extend.scssc | Bin 0 -> 3025 bytes .../fonts.scssc | Bin 0 -> 65148 bytes .../global.scssc | Bin 0 -> 18087 bytes .../minxi.scssc | Bin 0 -> 41657 bytes .../variables.scssc | Bin 0 -> 1265 bytes .../extend.scssc | Bin 0 -> 2999 bytes .../fonts.scssc | Bin 0 -> 65122 bytes .../global.scssc | Bin 0 -> 51303 bytes .../minxi.scssc | Bin 0 -> 41631 bytes .../variables.scssc | Bin 0 -> 1257 bytes .../style.scssc | Bin 0 -> 1760 bytes .../main.scssc | Bin 0 -> 68342 bytes .../responsive.scssc | Bin 0 -> 16555 bytes .../main.scssc | Bin 0 -> 50757 bytes .../responsive.scssc | Bin 0 -> 5944 bytes .../main.scssc | Bin 0 -> 78644 bytes .../responsive.scssc | Bin 0 -> 10592 bytes .../extend.scssc | Bin 0 -> 3022 bytes .../fonts.scssc | Bin 0 -> 65145 bytes .../global.scssc | Bin 0 -> 21311 bytes .../minxi.scssc | Bin 0 -> 41654 bytes .../variables.scssc | Bin 0 -> 1055 bytes .../style.scssc | Bin 0 -> 1760 bytes .../main.scssc | Bin 0 -> 34289 bytes .../responsive.scssc | Bin 0 -> 5337 bytes .../html/colorlib-regform-16/css/style.css | 612 ++ .../colorlib-regform-16/css/style.css.map | 7 + .../css/material-design-iconic-font.css | 5166 +++++++++++++++++ .../css/material-design-iconic-font.min.css | 1 + .../fonts/Material-Design-Iconic-Font.eot | Bin 0 -> 42495 bytes .../fonts/Material-Design-Iconic-Font.svg | 787 +++ .../fonts/Material-Design-Iconic-Font.ttf | Bin 0 -> 99212 bytes .../fonts/Material-Design-Iconic-Font.woff | Bin 0 -> 50312 bytes .../fonts/Material-Design-Iconic-Font.woff2 | Bin 0 -> 38384 bytes .../fonts/poppins/poppins-v5-latin-300.eot | Bin 0 -> 8696 bytes .../fonts/poppins/poppins-v5-latin-300.svg | 320 + .../fonts/poppins/poppins-v5-latin-300.ttf | Bin 0 -> 15764 bytes .../fonts/poppins/poppins-v5-latin-300.woff | Bin 0 -> 10408 bytes .../fonts/poppins/poppins-v5-latin-300.woff2 | Bin 0 -> 7864 bytes .../poppins/poppins-v5-latin-300italic.eot | Bin 0 -> 10018 bytes .../poppins/poppins-v5-latin-300italic.svg | 342 ++ .../poppins/poppins-v5-latin-300italic.ttf | Bin 0 -> 17264 bytes .../poppins/poppins-v5-latin-300italic.woff | Bin 0 -> 11868 bytes .../poppins/poppins-v5-latin-300italic.woff2 | Bin 0 -> 9012 bytes .../fonts/poppins/poppins-v5-latin-500.eot | Bin 0 -> 8716 bytes .../fonts/poppins/poppins-v5-latin-500.svg | 322 + .../fonts/poppins/poppins-v5-latin-500.ttf | Bin 0 -> 15620 bytes .../fonts/poppins/poppins-v5-latin-500.woff | Bin 0 -> 10420 bytes .../fonts/poppins/poppins-v5-latin-500.woff2 | Bin 0 -> 7884 bytes .../poppins/poppins-v5-latin-500italic.eot | Bin 0 -> 10148 bytes .../poppins/poppins-v5-latin-500italic.svg | 341 ++ .../poppins/poppins-v5-latin-500italic.ttf | Bin 0 -> 17128 bytes .../poppins/poppins-v5-latin-500italic.woff | Bin 0 -> 11912 bytes .../poppins/poppins-v5-latin-500italic.woff2 | Bin 0 -> 9136 bytes .../fonts/poppins/poppins-v5-latin-600.eot | Bin 0 -> 8638 bytes .../fonts/poppins/poppins-v5-latin-600.svg | 321 + .../fonts/poppins/poppins-v5-latin-600.ttf | Bin 0 -> 15548 bytes .../fonts/poppins/poppins-v5-latin-600.woff | Bin 0 -> 10364 bytes .../fonts/poppins/poppins-v5-latin-600.woff2 | Bin 0 -> 7812 bytes .../fonts/poppins/poppins-v5-latin-700.eot | Bin 0 -> 8616 bytes .../fonts/poppins/poppins-v5-latin-700.svg | 321 + .../fonts/poppins/poppins-v5-latin-700.ttf | Bin 0 -> 15388 bytes .../fonts/poppins/poppins-v5-latin-700.woff | Bin 0 -> 10304 bytes .../fonts/poppins/poppins-v5-latin-700.woff2 | Bin 0 -> 7832 bytes .../poppins/poppins-v5-latin-700italic.eot | Bin 0 -> 10015 bytes .../poppins/poppins-v5-latin-700italic.svg | 340 ++ .../poppins/poppins-v5-latin-700italic.ttf | Bin 0 -> 16884 bytes .../poppins/poppins-v5-latin-700italic.woff | Bin 0 -> 11748 bytes .../poppins/poppins-v5-latin-700italic.woff2 | Bin 0 -> 9008 bytes .../fonts/poppins/poppins-v5-latin-800.eot | Bin 0 -> 8626 bytes .../fonts/poppins/poppins-v5-latin-800.svg | 321 + .../fonts/poppins/poppins-v5-latin-800.ttf | Bin 0 -> 15408 bytes .../fonts/poppins/poppins-v5-latin-800.woff | Bin 0 -> 10320 bytes .../fonts/poppins/poppins-v5-latin-800.woff2 | Bin 0 -> 7808 bytes .../poppins/poppins-v5-latin-800italic.eot | Bin 0 -> 10010 bytes .../poppins/poppins-v5-latin-800italic.svg | 341 ++ .../poppins/poppins-v5-latin-800italic.ttf | Bin 0 -> 16940 bytes .../poppins/poppins-v5-latin-800italic.woff | Bin 0 -> 11784 bytes .../poppins/poppins-v5-latin-800italic.woff2 | Bin 0 -> 9028 bytes .../fonts/poppins/poppins-v5-latin-900.eot | Bin 0 -> 8382 bytes .../fonts/poppins/poppins-v5-latin-900.svg | 322 + .../fonts/poppins/poppins-v5-latin-900.ttf | Bin 0 -> 15320 bytes .../fonts/poppins/poppins-v5-latin-900.woff | Bin 0 -> 10096 bytes .../fonts/poppins/poppins-v5-latin-900.woff2 | Bin 0 -> 7640 bytes .../fonts/poppins/poppins-v5-latin-italic.eot | Bin 0 -> 10007 bytes .../fonts/poppins/poppins-v5-latin-italic.svg | 340 ++ .../fonts/poppins/poppins-v5-latin-italic.ttf | Bin 0 -> 17132 bytes .../poppins/poppins-v5-latin-italic.woff | Bin 0 -> 11868 bytes .../poppins/poppins-v5-latin-italic.woff2 | Bin 0 -> 9024 bytes .../poppins/poppins-v5-latin-regular.eot | Bin 0 -> 8692 bytes .../poppins/poppins-v5-latin-regular.svg | 323 ++ .../poppins/poppins-v5-latin-regular.ttf | Bin 0 -> 15724 bytes .../poppins/poppins-v5-latin-regular.woff | Bin 0 -> 10400 bytes .../poppins/poppins-v5-latin-regular.woff2 | Bin 0 -> 7904 bytes .../roboto-condensed-v16-latin-700.eot | Bin 0 -> 17368 bytes .../roboto-condensed-v16-latin-700.svg | 309 + .../roboto-condensed-v16-latin-700.ttf | Bin 0 -> 35368 bytes .../roboto-condensed-v16-latin-700.woff | Bin 0 -> 19768 bytes .../roboto-condensed-v16-latin-700.woff2 | Bin 0 -> 15252 bytes .../roboto-condensed-v16-latin-700italic.eot | Bin 0 -> 18904 bytes .../roboto-condensed-v16-latin-700italic.svg | 322 + .../roboto-condensed-v16-latin-700italic.ttf | Bin 0 -> 36604 bytes .../roboto-condensed-v16-latin-700italic.woff | Bin 0 -> 21276 bytes ...roboto-condensed-v16-latin-700italic.woff2 | Bin 0 -> 16684 bytes .../roboto-condensed-v16-latin-italic.eot | Bin 0 -> 19247 bytes .../roboto-condensed-v16-latin-italic.svg | 326 ++ .../roboto-condensed-v16-latin-italic.ttf | Bin 0 -> 37116 bytes .../roboto-condensed-v16-latin-italic.woff | Bin 0 -> 21584 bytes .../roboto-condensed-v16-latin-italic.woff2 | Bin 0 -> 16992 bytes .../roboto-condensed-v16-latin-regular.eot | Bin 0 -> 17389 bytes .../roboto-condensed-v16-latin-regular.svg | 306 + .../roboto-condensed-v16-latin-regular.ttf | Bin 0 -> 35376 bytes .../roboto-condensed-v16-latin-regular.woff | Bin 0 -> 19716 bytes .../roboto-condensed-v16-latin-regular.woff2 | Bin 0 -> 15332 bytes .../colorlib-regform-16/images/form-img.jpg | Bin 0 -> 39080 bytes .../images/form-img.jpg.kaz | Bin 0 -> 58858 bytes .../web/html/colorlib-regform-16/index.html | 139 + .../web/html/colorlib-regform-16/js/main.js | 80 + .../web/html/colorlib-regform-16/readme.txt | 2 + .../scss/common/extend.scss | 11 + .../scss/common/fonts.scss | 156 + .../scss/common/global.scss | 52 + .../scss/common/minxi.scss | 152 + .../scss/common/variables.scss | 3 + .../scss/layouts/main.scss | 332 ++ .../scss/layouts/responsive.scss | 55 + .../html/colorlib-regform-16/scss/style.scss | 12 + .../dist/additional-methods.js | 1158 ++++ .../dist/additional-methods.min.js | 4 + .../jquery-validation/dist/jquery.validate.js | 1601 +++++ .../dist/jquery.validate.min.js | 4 + .../dist/localization/messages_ar.js | 35 + .../dist/localization/messages_ar.min.js | 4 + .../dist/localization/messages_az.js | 35 + .../dist/localization/messages_az.min.js | 4 + .../dist/localization/messages_bg.js | 35 + .../dist/localization/messages_bg.min.js | 4 + .../dist/localization/messages_bn_BD.js | 35 + .../dist/localization/messages_bn_BD.min.js | 4 + .../dist/localization/messages_ca.js | 35 + .../dist/localization/messages_ca.min.js | 4 + .../dist/localization/messages_cs.js | 35 + .../dist/localization/messages_cs.min.js | 4 + .../dist/localization/messages_da.js | 32 + .../dist/localization/messages_da.min.js | 4 + .../dist/localization/messages_de.js | 32 + .../dist/localization/messages_de.min.js | 4 + .../dist/localization/messages_el.js | 35 + .../dist/localization/messages_el.min.js | 4 + .../dist/localization/messages_es.js | 38 + .../dist/localization/messages_es.min.js | 4 + .../dist/localization/messages_es_AR.js | 39 + .../dist/localization/messages_es_AR.min.js | 4 + .../dist/localization/messages_es_PE.js | 39 + .../dist/localization/messages_es_PE.min.js | 4 + .../dist/localization/messages_et.js | 33 + .../dist/localization/messages_et.min.js | 4 + .../dist/localization/messages_eu.js | 35 + .../dist/localization/messages_eu.min.js | 4 + .../dist/localization/messages_fa.js | 38 + .../dist/localization/messages_fa.min.js | 4 + .../dist/localization/messages_fi.js | 33 + .../dist/localization/messages_fi.min.js | 4 + .../dist/localization/messages_fr.js | 63 + .../dist/localization/messages_fr.min.js | 4 + .../dist/localization/messages_ge.js | 35 + .../dist/localization/messages_ge.min.js | 4 + .../dist/localization/messages_gl.js | 40 + .../dist/localization/messages_gl.min.js | 4 + .../dist/localization/messages_he.js | 35 + .../dist/localization/messages_he.min.js | 4 + .../dist/localization/messages_hr.js | 35 + .../dist/localization/messages_hr.min.js | 4 + .../dist/localization/messages_hu.js | 35 + .../dist/localization/messages_hu.min.js | 4 + .../dist/localization/messages_hy_AM.js | 35 + .../dist/localization/messages_hy_AM.min.js | 4 + .../dist/localization/messages_id.js | 34 + .../dist/localization/messages_id.min.js | 4 + .../dist/localization/messages_is.js | 33 + .../dist/localization/messages_is.min.js | 4 + .../dist/localization/messages_it.js | 39 + .../dist/localization/messages_it.min.js | 4 + .../dist/localization/messages_ja.js | 36 + .../dist/localization/messages_ja.min.js | 4 + .../dist/localization/messages_ka.js | 35 + .../dist/localization/messages_ka.min.js | 4 + .../dist/localization/messages_kk.js | 35 + .../dist/localization/messages_kk.min.js | 4 + .../dist/localization/messages_ko.js | 35 + .../dist/localization/messages_ko.min.js | 4 + .../dist/localization/messages_lt.js | 35 + .../dist/localization/messages_lt.min.js | 4 + .../dist/localization/messages_lv.js | 35 + .../dist/localization/messages_lv.min.js | 4 + .../dist/localization/messages_mk.js | 35 + .../dist/localization/messages_mk.min.js | 4 + .../dist/localization/messages_my.js | 35 + .../dist/localization/messages_my.min.js | 4 + .../dist/localization/messages_nl.js | 46 + .../dist/localization/messages_nl.min.js | 4 + .../dist/localization/messages_no.js | 36 + .../dist/localization/messages_no.min.js | 4 + .../dist/localization/messages_pl.js | 37 + .../dist/localization/messages_pl.min.js | 4 + .../dist/localization/messages_pt_BR.js | 88 + .../dist/localization/messages_pt_BR.min.js | 4 + .../dist/localization/messages_pt_PT.js | 39 + .../dist/localization/messages_pt_PT.min.js | 4 + .../dist/localization/messages_ro.js | 35 + .../dist/localization/messages_ro.min.js | 4 + .../dist/localization/messages_ru.js | 35 + .../dist/localization/messages_ru.min.js | 4 + .../dist/localization/messages_sd.js | 35 + .../dist/localization/messages_sd.min.js | 4 + .../dist/localization/messages_si.js | 35 + .../dist/localization/messages_si.min.js | 4 + .../dist/localization/messages_sk.js | 32 + .../dist/localization/messages_sk.min.js | 4 + .../dist/localization/messages_sl.js | 35 + .../dist/localization/messages_sl.min.js | 4 + .../dist/localization/messages_sr.js | 35 + .../dist/localization/messages_sr.min.js | 4 + .../dist/localization/messages_sr_lat.js | 35 + .../dist/localization/messages_sr_lat.min.js | 4 + .../dist/localization/messages_sv.js | 33 + .../dist/localization/messages_sv.min.js | 4 + .../dist/localization/messages_th.js | 35 + .../dist/localization/messages_th.min.js | 4 + .../dist/localization/messages_tj.js | 35 + .../dist/localization/messages_tj.min.js | 4 + .../dist/localization/messages_tr.js | 36 + .../dist/localization/messages_tr.min.js | 4 + .../dist/localization/messages_uk.js | 35 + .../dist/localization/messages_uk.min.js | 4 + .../dist/localization/messages_ur.js | 35 + .../dist/localization/messages_ur.min.js | 4 + .../dist/localization/messages_vi.js | 35 + .../dist/localization/messages_vi.min.js | 4 + .../dist/localization/messages_zh.js | 35 + .../dist/localization/messages_zh.min.js | 4 + .../dist/localization/messages_zh_TW.js | 36 + .../dist/localization/messages_zh_TW.min.js | 4 + .../dist/localization/methods_de.js | 24 + .../dist/localization/methods_de.min.js | 4 + .../dist/localization/methods_es_CL.js | 24 + .../dist/localization/methods_es_CL.min.js | 4 + .../dist/localization/methods_fi.js | 24 + .../dist/localization/methods_fi.min.js | 4 + .../dist/localization/methods_nl.js | 24 + .../dist/localization/methods_nl.min.js | 4 + .../dist/localization/methods_pt.js | 21 + .../dist/localization/methods_pt.min.js | 4 + .../vendor/jquery/jquery-ui.min.js | 13 + .../vendor/jquery/jquery.min.js | 2 + .../vendor/nouislider/nouislider.min.css | 1 + .../vendor/nouislider/nouislider.min.js | 3 + .../colorlib-regform-16/vendor/wnumb/wNumb.js | 357 ++ dockers/web/html/confiance.html | 69 + dockers/web/html/deja_kaznaute.html | 129 + dockers/web/html/elements.html | 354 ++ dockers/web/html/favicon.ico | Bin 0 -> 9086 bytes dockers/web/html/images/1f609.png | Bin 0 -> 5996 bytes dockers/web/html/images/2705.png | Bin 0 -> 2689 bytes dockers/web/html/images/actualites.png | Bin 0 -> 347761 bytes dockers/web/html/images/calc.jpg | Bin 0 -> 49009 bytes dockers/web/html/images/chatons.png | Bin 0 -> 2732 bytes .../html/images/confiance_agglo_vannes.png | Bin 0 -> 92805 bytes dockers/web/html/images/confiance_ess.png | Bin 0 -> 46205 bytes dockers/web/html/images/confiance_iut.png | Bin 0 -> 46012 bytes dockers/web/html/images/date.jpg | Bin 0 -> 48408 bytes dockers/web/html/images/deja_kaznaute.jpg | Bin 0 -> 9018 bytes dockers/web/html/images/file.jpg | Bin 0 -> 45972 bytes dockers/web/html/images/forms.jpg | Bin 0 -> 36557 bytes dockers/web/html/images/generic/pic01.jpg | Bin 0 -> 6311 bytes dockers/web/html/images/generic/pic02.jpg | Bin 0 -> 6084 bytes dockers/web/html/images/generic/pic03.jpg | Bin 0 -> 5788 bytes dockers/web/html/images/generic/pic04.jpg | Bin 0 -> 6499 bytes dockers/web/html/images/generic/pic05.jpg | Bin 0 -> 5232 bytes dockers/web/html/images/generic/pic06.jpg | Bin 0 -> 6704 bytes dockers/web/html/images/generic/pic07.jpg | Bin 0 -> 5606 bytes dockers/web/html/images/generic/pic08.jpg | Bin 0 -> 5889 bytes dockers/web/html/images/generic/pic09.jpg | Bin 0 -> 5808 bytes dockers/web/html/images/generic/pic10.jpg | Bin 0 -> 6489 bytes dockers/web/html/images/generic/pic11.jpg | Bin 0 -> 6338 bytes dockers/web/html/images/generic/pic12.jpg | Bin 0 -> 6261 bytes dockers/web/html/images/generic/pic13.jpg | Bin 0 -> 17129 bytes dockers/web/html/images/generic/pic14.jpg | Bin 0 -> 2580 bytes dockers/web/html/images/generic/pic15.jpg | Bin 0 -> 2560 bytes dockers/web/html/images/instant_messaging.jpg | Bin 0 -> 28464 bytes dockers/web/html/images/link_rbg.png | Bin 0 -> 29840 bytes dockers/web/html/images/link_resolu.png | Bin 0 -> 69223 bytes dockers/web/html/images/link_tycloud.png | Bin 0 -> 21088 bytes dockers/web/html/images/logo.svg | 72 + dockers/web/html/images/nextcloud.png | Bin 0 -> 31072 bytes .../web/html/images/offre_associations.jpg | Bin 0 -> 45169 bytes .../images/offre_associations_bandeau.jpg | Bin 0 -> 16117 bytes dockers/web/html/images/offre_decouverte.jpg | Bin 0 -> 36860 bytes .../html/images/offre_decouverte_bandeau.jpg | Bin 0 -> 14673 bytes dockers/web/html/images/offre_dediee.jpg | Bin 0 -> 45169 bytes .../web/html/images/offre_dediee_bandeau.png | Bin 0 -> 282917 bytes dockers/web/html/images/offre_entreprises.jpg | Bin 0 -> 45169 bytes .../html/images/offre_entreprises_bandeau.png | Bin 0 -> 282917 bytes dockers/web/html/images/offre_familles.jpg | Bin 0 -> 45169 bytes .../html/images/offre_familles_bandeau.jpg | Bin 0 -> 16143 bytes .../web/html/images/offre_organisations.jpg | Bin 0 -> 45169 bytes .../images/offre_organisations_bandeau.jpg | Bin 0 -> 16117 bytes .../web/html/images/offre_particuliers.jpg | Bin 0 -> 47063 bytes .../images/offre_particuliers_bandeau.jpg | Bin 0 -> 15669 bytes dockers/web/html/images/pad.jpg | Bin 0 -> 35199 bytes dockers/web/html/images/pourquoi_kaz.png | Bin 0 -> 282917 bytes dockers/web/html/images/qui_sommes_nous.png | Bin 0 -> 437362 bytes dockers/web/html/images/tableur.jpg | Bin 0 -> 31825 bytes dockers/web/html/images/visio.jpg | Bin 0 -> 64937 bytes dockers/web/html/images/wiki.jpg | Bin 0 -> 38024 bytes dockers/web/html/index.html | 265 + dockers/web/html/m/Readme.txt | 2 + dockers/web/html/m/coche.png | Bin 0 -> 1802 bytes dockers/web/html/m/email.css | 69 + dockers/web/html/m/kaz-50.png | 1 + dockers/web/html/m/kaz-du-libre-23.png | 1 + dockers/web/html/m/logo.png | Bin 0 -> 4099 bytes dockers/web/html/mail-cleanup | 1 + dockers/web/html/mail/config-v1.1.xml | 28 + dockers/web/html/mentions_legales.html | 97 + dockers/web/html/offre_associations.html | 77 + dockers/web/html/offre_decouverte.html | 91 + dockers/web/html/offre_dedie.html | 55 + dockers/web/html/offre_dediee.html | 69 + dockers/web/html/offre_entreprises.html | 68 + dockers/web/html/offre_familles.html | 72 + dockers/web/html/offre_organisations.html | 89 + dockers/web/html/offre_particuliers.html | 71 + dockers/web/html/pourquoi_choisir_kaz.html | 101 + dockers/web/html/qui_sommes_nous.html | 74 + dockers/web/html/rgpd.html | 101 + dockers/web/html/robot.txt | 2 + dockers/web/html/sites_amis.html | 79 + dockers/web/web-gen.sh | 120 + secret.tmpl/Readme.txt | 11 + secret.tmpl/SetAllPass.sh | 305 + secret.tmpl/allow_admin_ip | 10 + secret.tmpl/env-apikazServ | 32 + secret.tmpl/env-castopodDB | 4 + secret.tmpl/env-castopodServ | 7 + secret.tmpl/env-dokuwikiServ | 4 + secret.tmpl/env-ethercalcServ | 3 + secret.tmpl/env-etherpadDB | 5 + secret.tmpl/env-etherpadServ | 16 + secret.tmpl/env-framadateDB | 5 + secret.tmpl/env-framadateServ | 3 + secret.tmpl/env-gandi | 3 + secret.tmpl/env-gitDB | 5 + secret.tmpl/env-gitServ | 3 + secret.tmpl/env-jirafeauServ | 2 + secret.tmpl/env-ldapServ | 9 + secret.tmpl/env-ldapUI | 9 + secret.tmpl/env-mailServ | 3 + secret.tmpl/env-mattermostDB | 8 + secret.tmpl/env-mattermostServ | 15 + secret.tmpl/env-mobilizonDB | 4 + secret.tmpl/env-mobilizonServ | 27 + secret.tmpl/env-nextcloudDB | 8 + secret.tmpl/env-nextcloudServ | 5 + secret.tmpl/env-officeServ | 3 + secret.tmpl/env-roundcubeDB | 4 + secret.tmpl/env-roundcubeServ | 6 + secret.tmpl/env-sympaDB | 4 + secret.tmpl/env-sympaServ | 10 + secret.tmpl/env-vaultwardenDB | 4 + secret.tmpl/env-vaultwardenServ | 2 + secret.tmpl/env-vigiloDB | 4 + secret.tmpl/env-vigiloServ | 7 + secret.tmpl/env-wpDB | 8 + secret.tmpl/env-wpServ | 6 + 883 files changed, 71550 insertions(+), 2 deletions(-) create mode 100644 LICENSE create mode 100755 bin/.applyTemplate-completion.bash create mode 100755 bin/.commonFunctions.sh create mode 100755 bin/.container-completion.bash create mode 100755 bin/.dns-completion.bash create mode 100755 bin/.foreign-domain-completion.bash create mode 100644 bin/.gestContainers-completion.bash create mode 100755 bin/.kazDockerNet-completion.bash create mode 100755 bin/.kazList-completion.bash create mode 100755 bin/.mvOrga2Nas-completion.bash create mode 100755 bin/.orga-gen-completion.bash create mode 100755 bin/.updateLook-completion.bash create mode 100755 bin/applyTemplate.sh create mode 100755 bin/checkEnvFiles.sh create mode 100755 bin/checkEnvPassword.sh create mode 100755 bin/configKaz.sh create mode 100755 bin/configKaz.sh.templates create mode 100755 bin/container.sh create mode 100755 bin/createEmptyPasswd.sh create mode 100755 bin/createSrcDocker.sh create mode 100755 bin/createUser.sh create mode 100755 bin/cron-cloud.sh create mode 100755 bin/dns.sh create mode 100755 bin/envoiMails.sh create mode 100755 bin/foreign-domain.sh create mode 100755 bin/gestContainers.sh create mode 100755 bin/gestContainers_v2.sh create mode 100755 bin/gestUsers.sh create mode 100755 bin/indicateurs.sh create mode 100755 bin/init.sh create mode 100755 bin/install.sh create mode 100755 bin/installDepollueur.sh create mode 100755 bin/interoPaheko.sh create mode 100755 bin/iptables.sh create mode 100755 bin/kazDockerNet.sh create mode 100755 bin/kazList.sh create mode 100755 bin/ldap/ldap_sauve.sh create mode 100755 bin/ldap/ldapvi.sh create mode 100755 bin/ldap/migrate_to_ldap.sh create mode 100755 bin/ldap/tests/nc_orphans.sh create mode 100644 bin/look/feminin/kaz-entier.png create mode 100644 bin/look/feminin/kaz-entier.svg create mode 100644 bin/look/feminin/kaz-signature.png create mode 100644 bin/look/feminin/kaz-tete.png create mode 100644 bin/look/feminin/kaz-tete.svg create mode 100644 bin/look/feminin/kazdate.png create mode 100644 bin/look/feminin/kazmel.png create mode 100644 bin/look/greve/kaz-entier.png create mode 100644 bin/look/greve/kaz-entier.svg create mode 100644 bin/look/greve/kaz-signature.png create mode 100644 bin/look/greve/kaz-tete.png create mode 100644 bin/look/greve/kaz-tete.svg create mode 100644 bin/look/greve/kazdate.png create mode 100644 bin/look/greve/kazmel.png create mode 100644 bin/look/kaz/kaz-entier.png create mode 100644 bin/look/kaz/kaz-entier.svg create mode 100644 bin/look/kaz/kaz-signature.png create mode 100644 bin/look/kaz/kaz-tete.png create mode 100644 bin/look/kaz/kaz-tete.svg create mode 100644 bin/look/kaz/kazdate.png create mode 100644 bin/look/kaz/kazmel.png create mode 100644 bin/look/noel/kaz-entier.png create mode 100644 bin/look/noel/kaz-entier.svg create mode 100644 bin/look/noel/kaz-signature.png create mode 100644 bin/look/noel/kaz-tete.png create mode 100644 bin/look/noel/kaz-tete.svg create mode 100644 bin/look/noel/kazdate.png create mode 100644 bin/look/noel/kazmel.png create mode 100755 bin/manageAgora.sh create mode 100755 bin/manageCastopod.sh create mode 100755 bin/manageCloud.sh create mode 100755 bin/manageWiki.sh create mode 100755 bin/manageWp.sh create mode 100755 bin/migVersProdX.sh create mode 100755 bin/migration.sh create mode 100755 bin/mvOrga2Nas.sh create mode 100755 bin/nettoie create mode 100755 bin/nextcloud_maintenance.sh create mode 100755 bin/postfix-superviz.sh create mode 100755 bin/runAlertings.sh create mode 100755 bin/sauve_memory.sh create mode 100755 bin/sauve_serveur.sh create mode 100755 bin/scriptBorg.sh create mode 100755 bin/scriptSauve.sh create mode 100755 bin/secretGen.sh create mode 100755 bin/setOwner.sh create mode 100755 bin/updateAllOrga.sh create mode 100755 bin/updateDockerPassword.sh create mode 100755 bin/updateGit.sh create mode 100755 bin/updateLook.sh create mode 100755 bin/upgradeDockerCompose.sh create mode 100755 bin/verifExistenceMails.sh create mode 100755 bin/vide_poubelle create mode 100644 config/container-mail.list.tmpl create mode 100644 config/container-orga.list.tmpl create mode 100644 config/container-proxy.list.tmpl create mode 100644 config/container-withMail.list.tmpl create mode 100644 config/container-withoutMail.list.tmpl create mode 100644 config/dockers.tmpl.env create mode 120000 config/orgaTmpl/.env create mode 100644 config/orgaTmpl/app/Dockerfile create mode 100755 config/orgaTmpl/app/entrypoint.sh create mode 100644 config/orgaTmpl/docker-compose.yml create mode 100755 config/orgaTmpl/init-db.sh create mode 100755 config/orgaTmpl/init-paheko.sh create mode 100755 config/orgaTmpl/init-volume.sh create mode 100644 config/orgaTmpl/initdb.d/orga.sql create mode 100755 config/orgaTmpl/orga-gen.sh create mode 100755 config/orgaTmpl/orga-rm.sh create mode 100755 config/orgaTmpl/reload.sh create mode 100644 config/orgaTmpl/wiki-conf/acl.auth.php create mode 100644 config/orgaTmpl/wiki-conf/local.php create mode 100644 config/orgaTmpl/wiki-conf/users.auth.php create mode 100644 config/proxy/proxy_params create mode 100644 config/skip-file.txt create mode 100644 dockers/apikaz/Readme.txt create mode 100644 dockers/apikaz/docker-compose.yml create mode 100644 dockers/apikaz/source/Dockerfile create mode 100644 dockers/apikaz/source/Sympa/Constants.pm create mode 100644 dockers/apikaz/source/Sympa/Language.pm create mode 100644 dockers/apikaz/source/Sympa/Regexps.pm create mode 100644 dockers/apikaz/source/Sympa/Tools/Data.pm create mode 100644 dockers/apikaz/source/Sympa/Tools/Text.pm create mode 100755 dockers/apikaz/source/Sympa/sympa_soap_client.pl create mode 100644 dockers/apikaz/source/app.py create mode 100644 dockers/apikaz/source/index.html create mode 100644 dockers/apikaz/source/requirements.txt create mode 100644 dockers/apikaz/source/static/favicon.ico create mode 100644 dockers/apikaz/source/templates/email.css create mode 100644 dockers/apikaz/source/templates/email_footer.html create mode 100644 dockers/apikaz/source/templates/email_header.html create mode 100644 dockers/apikaz/source/templates/email_inscription.html create mode 120000 dockers/cachet/.env create mode 100644 dockers/cachet/docker-compose.yml create mode 120000 dockers/castopod/.env create mode 100644 dockers/castopod/docker-compose.yml create mode 100755 dockers/castopod/first.sh create mode 120000 dockers/cloud/.env create mode 100644 dockers/cloud/Readme.txt create mode 100644 dockers/cloud/docker-compose.yml create mode 100755 dockers/cloud/first.sh create mode 100644 dockers/cloud/media/favicon.ico create mode 100644 dockers/cloud/media/logo.png create mode 100644 dockers/cloud/media/logoheader.png create mode 100755 dockers/cloud/reindex.sh create mode 120000 dockers/collabora/.env create mode 100644 dockers/collabora/Readme.txt create mode 100644 dockers/collabora/docker-compose.yml create mode 120000 dockers/dokuwiki/.env create mode 100644 dockers/dokuwiki/Dockerfile create mode 100644 dockers/dokuwiki/docker-compose.yml create mode 100755 dockers/dokuwiki/download.sh create mode 100755 dockers/dokuwiki/first.sh create mode 100644 dockers/dokuwiki/htaccess create mode 100644 dockers/dokuwiki/wiki-conf/acl.auth.php create mode 100644 dockers/dokuwiki/wiki-conf/favicon.ico create mode 100644 dockers/dokuwiki/wiki-conf/local.php create mode 100644 dockers/dokuwiki/wiki-conf/logo.png create mode 100644 dockers/dokuwiki/wiki-conf/users.auth.php create mode 120000 dockers/ethercalc/.env create mode 100644 dockers/ethercalc/Dockerfile create mode 100644 dockers/ethercalc/docker-compose.yml create mode 120000 dockers/etherpad/.env create mode 100644 dockers/etherpad/docker-compose.yml create mode 100644 dockers/etherpad/settings.json create mode 120000 dockers/framadate/.env create mode 100644 dockers/framadate/Dockerfile create mode 100755 dockers/framadate/build.sh create mode 100755 dockers/framadate/composer-setup.sh create mode 100644 dockers/framadate/config/framadate.conf create mode 100644 dockers/framadate/docker-compose.yml create mode 100755 dockers/framadate/download.sh create mode 100755 dockers/framadate/first.sh create mode 100644 dockers/framadate/kazclassic.png create mode 100644 dockers/framadate/kazdate.png create mode 100644 dockers/framadate/kazdates.png create mode 100644 dockers/framadate/patch/adminstuds.php.patch create mode 100644 dockers/framadate/patch/create_classic_poll.php.patch create mode 100644 dockers/framadate/patch/create_date_poll.php.patch create mode 100644 dockers/framadate/patch/en.json.patch create mode 100644 dockers/framadate/patch/find_polls.php.patch create mode 100644 dockers/framadate/patch/fr.json.patch create mode 120000 dockers/gitea/.env create mode 100644 dockers/gitea/docker-compose.yml create mode 100755 dockers/gitea/first.sh create mode 100644 dockers/gitea/logo.svg create mode 120000 dockers/grafana/.env create mode 100644 dockers/grafana/docker-compose.yml create mode 100644 dockers/grafana/grafana.env create mode 100644 dockers/grafana/grafana/provisioning/dashboards/dashboard.yml create mode 100644 dockers/grafana/grafana/provisioning/dashboards/reverse-proxy_rev1.json create mode 100644 dockers/grafana/grafana/provisioning/datasources/datasource.yml create mode 100644 dockers/grafana/prometheus/alert.rules create mode 100644 dockers/grafana/prometheus/prometheus.yml create mode 120000 dockers/imapsync/.env create mode 100644 dockers/imapsync/docker-compose.yml create mode 100644 dockers/imapsync/imapsync_form.css create mode 100644 dockers/imapsync/imapsync_form_extra.html create mode 120000 dockers/jirafeau/.env create mode 100644 dockers/jirafeau/Dockerfile create mode 100755 dockers/jirafeau/build.sh create mode 100755 dockers/jirafeau/composer-setup.sh create mode 100644 dockers/jirafeau/config/composer.json create mode 100644 dockers/jirafeau/config/jirafeau.conf create mode 100644 dockers/jirafeau/docker-compose.yml create mode 100755 dockers/jirafeau/download.sh create mode 100755 dockers/jirafeau/first.sh create mode 100644 dockers/jirafeau/media/kaz/bandeau.png create mode 100644 dockers/jirafeau/media/kaz/email.png create mode 100644 dockers/jirafeau/media/kaz/error.png create mode 100644 dockers/jirafeau/media/kaz/favicon.ico create mode 100644 dockers/jirafeau/media/kaz/jyraphe.png create mode 100644 dockers/jirafeau/media/kaz/kaz.png create mode 100644 dockers/jirafeau/media/kaz/ok.png create mode 100644 dockers/jirafeau/media/kaz/style.css.php create mode 120000 dockers/ldap/.env create mode 100755 dockers/ldap/UIHooks/post-hook.sh create mode 100644 dockers/ldap/base/acl.ldif.tmpl create mode 100644 dockers/ldap/base/kaz-schema.ldif.tmpl create mode 100644 dockers/ldap/base/skeleton.ldif.tmpl create mode 100644 dockers/ldap/br.inc.php create mode 100644 dockers/ldap/docker-compose.yml create mode 100755 dockers/ldap/first.sh create mode 100644 dockers/ldap/schemas/kaz.ldif create mode 100644 dockers/ldap/schemas/nextcloud.ldif create mode 100644 dockers/ldap/schemas/postfixbook.ldif create mode 100755 dockers/ldap/update.sh create mode 120000 dockers/mattermost/.env create mode 100644 dockers/mattermost/docker-compose.yml create mode 100755 dockers/mattermost/first.sh create mode 120000 dockers/mobilizon/.env create mode 100644 dockers/mobilizon/config.exs create mode 100644 dockers/mobilizon/docker-compose.yml create mode 120000 dockers/paheko/.env create mode 100644 dockers/paheko/Dockerfile create mode 100755 dockers/paheko/build.sh create mode 100755 dockers/paheko/composer-setup.sh create mode 100644 dockers/paheko/config/config.local.tmpl.php create mode 100755 dockers/paheko/config/factory_cron.sh create mode 100755 dockers/paheko/config/factory_cron_emails.sh create mode 100644 dockers/paheko/config/facturation.tar.gz create mode 100644 dockers/paheko/config/paheko.conf create mode 100755 dockers/paheko/config/setupWebRights.sh create mode 100755 dockers/paheko/docker-compose-gen.sh create mode 100644 dockers/paheko/docker-compose.yml create mode 100755 dockers/paheko/first.sh create mode 100755 dockers/paheko/paheko-gen.sh create mode 100755 dockers/paheko/reload.sh create mode 120000 dockers/postfix/.env create mode 100644 dockers/postfix/Dockerfile create mode 100644 dockers/postfix/Readme.txt create mode 100755 dockers/postfix/build.sh create mode 120000 dockers/postfix/config create mode 100644 dockers/postfix/docker-compose.yml create mode 100644 dockers/postfix/env-config create mode 100755 dockers/postfix/first.sh create mode 120000 dockers/postfix2/.env create mode 100644 dockers/postfix2/docker-compose.yml create mode 100644 dockers/postfix2/env-config create mode 120000 dockers/proxy/.env create mode 100644 dockers/proxy/Dockerfile create mode 100644 dockers/proxy/Readme.txt create mode 100755 dockers/proxy/build.sh create mode 100644 dockers/proxy/config/nginx.tmpl.conf create mode 100644 dockers/proxy/docker-compose.tmpl.yml.dist create mode 100755 dockers/proxy/proxy-gen.sh create mode 100755 dockers/proxy/reload.sh create mode 100644 dockers/proxy/todo-ssl create mode 120000 dockers/quotas/.env create mode 100644 dockers/quotas/Dockerfile create mode 100755 dockers/quotas/build.sh create mode 100644 dockers/quotas/docker-compose.yml create mode 100644 dockers/quotas/html/.gitignore create mode 100644 dockers/quotas/html/LancementAffichageQuota.txt create mode 100644 dockers/quotas/html/README.md create mode 100644 dockers/quotas/html/express_webapp/app.js create mode 100755 dockers/quotas/html/express_webapp/bin/www create mode 100644 dockers/quotas/html/express_webapp/data/db.js create mode 100644 dockers/quotas/html/express_webapp/database.js create mode 100644 dockers/quotas/html/express_webapp/package-lock.json create mode 100644 dockers/quotas/html/express_webapp/package.json create mode 100644 dockers/quotas/html/express_webapp/public/images/favicon.ico create mode 100644 dockers/quotas/html/express_webapp/public/images/groupe.png create mode 100644 dockers/quotas/html/express_webapp/public/images/individuel.png create mode 100644 dockers/quotas/html/express_webapp/public/images/quotas.png create mode 100644 dockers/quotas/html/express_webapp/public/images/serviceglobal.png create mode 100644 dockers/quotas/html/express_webapp/public/stylesheets/style.css create mode 100644 dockers/quotas/html/express_webapp/routes/groupes.js create mode 100644 dockers/quotas/html/express_webapp/routes/index.js create mode 100644 dockers/quotas/html/express_webapp/routes/indexAdmin.js create mode 100644 dockers/quotas/html/express_webapp/routes/serviceg.js create mode 100644 dockers/quotas/html/express_webapp/routes/users.js create mode 100644 dockers/quotas/html/express_webapp/routes/usersAdmin.js create mode 100644 dockers/quotas/html/express_webapp/views/error.jade create mode 100644 dockers/quotas/html/express_webapp/views/groupes.jade create mode 100644 dockers/quotas/html/express_webapp/views/index.jade create mode 100644 dockers/quotas/html/express_webapp/views/indexAdmin.jade create mode 100644 dockers/quotas/html/express_webapp/views/layout.jade create mode 100644 dockers/quotas/html/express_webapp/views/serviceg.jade create mode 100644 dockers/quotas/html/express_webapp/views/users.jade create mode 100644 dockers/quotas/html/express_webapp/views/usersError.jade create mode 100644 dockers/quotas/html/express_webapp/views/usersfound.jade create mode 100644 dockers/quotas/html/groupes.html create mode 100644 dockers/quotas/html/images/favicon.ico create mode 100644 dockers/quotas/html/images/groupe.png create mode 100644 dockers/quotas/html/images/individuel.png create mode 100644 dockers/quotas/html/images/quotas.png create mode 100644 dockers/quotas/html/images/serviceglobal.png create mode 100644 dockers/quotas/html/index.html create mode 100644 dockers/quotas/html/individuels.html create mode 100644 dockers/quotas/html/kaz.quota/package-lock.json create mode 100644 dockers/quotas/html/kaz.quota/package.json create mode 100644 dockers/quotas/html/package-lock.json create mode 100644 dockers/quotas/html/package.json create mode 100644 dockers/quotas/html/serviceG.html create mode 100644 dockers/quotas/html/style.css create mode 100644 dockers/quotas/initdb.d/quotas.sql create mode 120000 dockers/roundcube/.env create mode 100644 dockers/roundcube/Readme.txt create mode 100644 dockers/roundcube/config/add-to-config.inc.php create mode 100644 dockers/roundcube/config/custom_config.inc.php create mode 100644 dockers/roundcube/config/kazmel.png create mode 100644 dockers/roundcube/docker-compose.yml create mode 100755 dockers/roundcube/first.sh create mode 120000 dockers/sympa/.env create mode 100644 dockers/sympa/Dockerfile create mode 100755 dockers/sympa/alerting/filter.sh create mode 100755 dockers/sympa/alerting/mailq.sh create mode 100755 dockers/sympa/alerting/sympa.sh create mode 100755 dockers/sympa/build.sh create mode 100644 dockers/sympa/config/aliases.sympa.postfix create mode 100755 dockers/sympa/config/postfix-wrapper.sh create mode 100755 dockers/sympa/config/postfix.sh create mode 100755 dockers/sympa/config/start.sh create mode 100644 dockers/sympa/config/supervisord.conf create mode 100644 dockers/sympa/config/sympa.preseed create mode 100644 dockers/sympa/config/transport create mode 100644 dockers/sympa/config/trusted_applications.conf create mode 100644 dockers/sympa/docker-compose.yml create mode 100755 dockers/sympa/first.sh create mode 100755 dockers/sympa/reload.sh create mode 100755 dockers/sympa/updateFirewall.sh create mode 120000 dockers/traefik/.env create mode 100644 dockers/traefik/conf/dynamic/certificates.yml.tmpl create mode 100644 dockers/traefik/conf/dynamic/conf.yml create mode 100644 dockers/traefik/conf/traefik.yml.old create mode 100644 dockers/traefik/docker-compose.tmpl.yml.dist create mode 100755 dockers/traefik/proxy-gen.sh create mode 100755 dockers/traefik/reload.sh create mode 120000 dockers/vaultwarden/.env create mode 100644 dockers/vaultwarden/docker-compose.yml create mode 100644 dockers/vaultwarden/templates/email/admin_reset_password.hbs create mode 100644 dockers/vaultwarden/templates/email/admin_reset_password.html.hbs create mode 100644 dockers/vaultwarden/templates/email/change_email.hbs create mode 100644 dockers/vaultwarden/templates/email/change_email.html.hbs create mode 100644 dockers/vaultwarden/templates/email/delete_account.hbs create mode 100644 dockers/vaultwarden/templates/email/delete_account.html.hbs create mode 100644 dockers/vaultwarden/templates/email/email_footer.hbs create mode 100644 dockers/vaultwarden/templates/email/email_footer_text.hbs create mode 100644 dockers/vaultwarden/templates/email/email_header.hbs create mode 100644 dockers/vaultwarden/templates/email/emergency_access_invite_accepted.hbs create mode 100644 dockers/vaultwarden/templates/email/emergency_access_invite_accepted.html.hbs create mode 100644 dockers/vaultwarden/templates/email/emergency_access_invite_confirmed.hbs create mode 100644 dockers/vaultwarden/templates/email/emergency_access_invite_confirmed.html.hbs create mode 100644 dockers/vaultwarden/templates/email/emergency_access_recovery_approved.hbs create mode 100644 dockers/vaultwarden/templates/email/emergency_access_recovery_approved.html.hbs create mode 100644 dockers/vaultwarden/templates/email/emergency_access_recovery_initiated.hbs create mode 100644 dockers/vaultwarden/templates/email/emergency_access_recovery_initiated.html.hbs create mode 100644 dockers/vaultwarden/templates/email/emergency_access_recovery_rejected.hbs create mode 100644 dockers/vaultwarden/templates/email/emergency_access_recovery_rejected.html.hbs create mode 100644 dockers/vaultwarden/templates/email/emergency_access_recovery_reminder.hbs create mode 100644 dockers/vaultwarden/templates/email/emergency_access_recovery_reminder.html.hbs create mode 100644 dockers/vaultwarden/templates/email/emergency_access_recovery_timed_out.hbs create mode 100644 dockers/vaultwarden/templates/email/emergency_access_recovery_timed_out.html.hbs create mode 100644 dockers/vaultwarden/templates/email/incomplete_2fa_login.hbs create mode 100644 dockers/vaultwarden/templates/email/incomplete_2fa_login.html.hbs create mode 100644 dockers/vaultwarden/templates/email/invite_accepted.hbs create mode 100644 dockers/vaultwarden/templates/email/invite_accepted.html.hbs create mode 100644 dockers/vaultwarden/templates/email/invite_confirmed.hbs create mode 100644 dockers/vaultwarden/templates/email/invite_confirmed.html.hbs create mode 100644 dockers/vaultwarden/templates/email/new_device_logged_in.hbs create mode 100644 dockers/vaultwarden/templates/email/new_device_logged_in.html.hbs create mode 100644 dockers/vaultwarden/templates/email/pw_hint_none.hbs create mode 100644 dockers/vaultwarden/templates/email/pw_hint_none.html.hbs create mode 100644 dockers/vaultwarden/templates/email/pw_hint_some.hbs create mode 100644 dockers/vaultwarden/templates/email/pw_hint_some.html.hbs create mode 100644 dockers/vaultwarden/templates/email/send_2fa_removed_from_org.hbs create mode 100644 dockers/vaultwarden/templates/email/send_2fa_removed_from_org.html.hbs create mode 100644 dockers/vaultwarden/templates/email/send_emergency_access_invite.hbs create mode 100644 dockers/vaultwarden/templates/email/send_emergency_access_invite.html.hbs create mode 100644 dockers/vaultwarden/templates/email/send_org_invite.hbs create mode 100644 dockers/vaultwarden/templates/email/send_org_invite.html.hbs create mode 100644 dockers/vaultwarden/templates/email/send_single_org_removed_from_org.hbs create mode 100644 dockers/vaultwarden/templates/email/send_single_org_removed_from_org.html.hbs create mode 100644 dockers/vaultwarden/templates/email/smtp_test.hbs create mode 100644 dockers/vaultwarden/templates/email/smtp_test.html.hbs create mode 100644 dockers/vaultwarden/templates/email/twofactor_email.hbs create mode 100644 dockers/vaultwarden/templates/email/twofactor_email.html.hbs create mode 100644 dockers/vaultwarden/templates/email/verify_email.hbs create mode 100644 dockers/vaultwarden/templates/email/verify_email.html.hbs create mode 100644 dockers/vaultwarden/templates/email/welcome.hbs create mode 100644 dockers/vaultwarden/templates/email/welcome.html.hbs create mode 100644 dockers/vaultwarden/templates/email/welcome_must_verify.hbs create mode 100644 dockers/vaultwarden/templates/email/welcome_must_verify.html.hbs create mode 120000 dockers/web/.env create mode 100644 dockers/web/Readme.txt create mode 100644 dockers/web/docker-compose.yml create mode 100755 dockers/web/first.sh create mode 100644 dockers/web/html/LICENSE.txt create mode 100644 dockers/web/html/README.txt create mode 100644 dockers/web/html/actualite.html create mode 100644 dockers/web/html/assets/css/fontawesome-all.min.css create mode 100644 dockers/web/html/assets/css/fonts.googleapis.css create mode 100644 dockers/web/html/assets/css/fonts.googleapis.inutilise.css create mode 100644 dockers/web/html/assets/css/main.css create mode 100644 dockers/web/html/assets/css/noscript.css create mode 100644 dockers/web/html/assets/gfonts/6xKydSBYKcSV-LCoeQqfX1RYOo3ig4vwkxduz8A.woff2 create mode 100644 dockers/web/html/assets/gfonts/6xKydSBYKcSV-LCoeQqfX1RYOo3ig4vwlBduz8A.woff2 create mode 100644 dockers/web/html/assets/gfonts/6xKydSBYKcSV-LCoeQqfX1RYOo3ig4vwlxdu.woff2 create mode 100644 dockers/web/html/assets/gfonts/6xKydSBYKcSV-LCoeQqfX1RYOo3ig4vwmBduz8A.woff2 create mode 100644 dockers/web/html/assets/gfonts/6xKydSBYKcSV-LCoeQqfX1RYOo3ig4vwmRduz8A.woff2 create mode 100644 dockers/web/html/assets/gfonts/6xKydSBYKcSV-LCoeQqfX1RYOo3ig4vwmhduz8A.woff2 create mode 100644 dockers/web/html/assets/gfonts/6xKydSBYKcSV-LCoeQqfX1RYOo3ig4vwmxduz8A.woff2 create mode 100644 dockers/web/html/assets/gfonts/6xKydSBYKcSV-LCoeQqfX1RYOo3ik4zwkxduz8A.woff2 create mode 100644 dockers/web/html/assets/gfonts/6xKydSBYKcSV-LCoeQqfX1RYOo3ik4zwlBduz8A.woff2 create mode 100644 dockers/web/html/assets/gfonts/6xKydSBYKcSV-LCoeQqfX1RYOo3ik4zwlxdu.woff2 create mode 100644 dockers/web/html/assets/gfonts/6xKydSBYKcSV-LCoeQqfX1RYOo3ik4zwmBduz8A.woff2 create mode 100644 dockers/web/html/assets/gfonts/6xKydSBYKcSV-LCoeQqfX1RYOo3ik4zwmRduz8A.woff2 create mode 100644 dockers/web/html/assets/gfonts/6xKydSBYKcSV-LCoeQqfX1RYOo3ik4zwmxduz8A.woff2 create mode 100644 dockers/web/html/assets/gfonts/6xKydSBYKcSV-LCoeQqfX1RYOo3iu4nwkxduz8A.woff2 create mode 100644 dockers/web/html/assets/gfonts/6xKydSBYKcSV-LCoeQqfX1RYOo3iu4nwlBduz8A.woff2 create mode 100644 dockers/web/html/assets/gfonts/6xKydSBYKcSV-LCoeQqfX1RYOo3iu4nwlxdu.woff2 create mode 100644 dockers/web/html/assets/gfonts/6xKydSBYKcSV-LCoeQqfX1RYOo3iu4nwmBduz8A.woff2 create mode 100644 dockers/web/html/assets/gfonts/6xKydSBYKcSV-LCoeQqfX1RYOo3iu4nwmRduz8A.woff2 create mode 100644 dockers/web/html/assets/gfonts/6xKydSBYKcSV-LCoeQqfX1RYOo3iu4nwmhduz8A.woff2 create mode 100644 dockers/web/html/assets/gfonts/6xKydSBYKcSV-LCoeQqfX1RYOo3iu4nwmxduz8A.woff2 create mode 100644 dockers/web/html/assets/html/footer.html create mode 100644 dockers/web/html/assets/html/header.html create mode 100644 dockers/web/html/assets/html/insert_js.html create mode 100644 dockers/web/html/assets/html/menu.html create mode 100644 dockers/web/html/assets/js/HTMLInclude.js create mode 100644 dockers/web/html/assets/js/HTMLInclude.min.js create mode 100644 dockers/web/html/assets/js/breakpoints.min.js create mode 100644 dockers/web/html/assets/js/browser.min.js create mode 100644 dockers/web/html/assets/js/jquery.min.js create mode 100644 dockers/web/html/assets/js/main.js create mode 100644 dockers/web/html/assets/js/util.js create mode 100644 dockers/web/html/assets/sass/base/_page.scss create mode 100644 dockers/web/html/assets/sass/base/_reset.scss create mode 100644 dockers/web/html/assets/sass/base/_typography.scss create mode 100644 dockers/web/html/assets/sass/components/_actions.scss create mode 100644 dockers/web/html/assets/sass/components/_box.scss create mode 100644 dockers/web/html/assets/sass/components/_button.scss create mode 100644 dockers/web/html/assets/sass/components/_form.scss create mode 100644 dockers/web/html/assets/sass/components/_icon.scss create mode 100644 dockers/web/html/assets/sass/components/_icons.scss create mode 100644 dockers/web/html/assets/sass/components/_image.scss create mode 100644 dockers/web/html/assets/sass/components/_list.scss create mode 100644 dockers/web/html/assets/sass/components/_row.scss create mode 100644 dockers/web/html/assets/sass/components/_section.scss create mode 100644 dockers/web/html/assets/sass/components/_table.scss create mode 100644 dockers/web/html/assets/sass/components/_tiles.scss create mode 100644 dockers/web/html/assets/sass/layout/_footer.scss create mode 100644 dockers/web/html/assets/sass/layout/_header.scss create mode 100644 dockers/web/html/assets/sass/layout/_main.scss create mode 100644 dockers/web/html/assets/sass/layout/_menu.scss create mode 100644 dockers/web/html/assets/sass/layout/_wrapper.scss create mode 100644 dockers/web/html/assets/sass/libs/_breakpoints.scss create mode 100644 dockers/web/html/assets/sass/libs/_functions.scss create mode 100644 dockers/web/html/assets/sass/libs/_html-grid.scss create mode 100644 dockers/web/html/assets/sass/libs/_mixins.scss create mode 100644 dockers/web/html/assets/sass/libs/_vars.scss create mode 100644 dockers/web/html/assets/sass/libs/_vendor.scss create mode 100644 dockers/web/html/assets/sass/main.scss create mode 100644 dockers/web/html/assets/sass/noscript.scss create mode 100644 dockers/web/html/assets/webfonts/fa-brands-400.eot create mode 100644 dockers/web/html/assets/webfonts/fa-brands-400.svg create mode 100644 dockers/web/html/assets/webfonts/fa-brands-400.ttf create mode 100644 dockers/web/html/assets/webfonts/fa-brands-400.woff create mode 100644 dockers/web/html/assets/webfonts/fa-brands-400.woff2 create mode 100644 dockers/web/html/assets/webfonts/fa-regular-400.eot create mode 100644 dockers/web/html/assets/webfonts/fa-regular-400.svg create mode 100644 dockers/web/html/assets/webfonts/fa-regular-400.ttf create mode 100644 dockers/web/html/assets/webfonts/fa-regular-400.woff create mode 100644 dockers/web/html/assets/webfonts/fa-regular-400.woff2 create mode 100644 dockers/web/html/assets/webfonts/fa-solid-900.eot create mode 100644 dockers/web/html/assets/webfonts/fa-solid-900.svg create mode 100644 dockers/web/html/assets/webfonts/fa-solid-900.ttf create mode 100644 dockers/web/html/assets/webfonts/fa-solid-900.woff create mode 100644 dockers/web/html/assets/webfonts/fa-solid-900.woff2 create mode 100755 dockers/web/html/cgi-bin/sendmail create mode 100755 dockers/web/html/cgi-bin/test.html create mode 100755 dockers/web/html/colorlib-regform-16/.sass-cache/21880c0ffe15753a53f8402e9f64e1077e071510/style.scssc create mode 100755 dockers/web/html/colorlib-regform-16/.sass-cache/2866be86c578bb583fbf023501bb9fd24cc3bf22/extend.scssc create mode 100755 dockers/web/html/colorlib-regform-16/.sass-cache/2866be86c578bb583fbf023501bb9fd24cc3bf22/fonts.scssc create mode 100755 dockers/web/html/colorlib-regform-16/.sass-cache/2866be86c578bb583fbf023501bb9fd24cc3bf22/global.scssc create mode 100755 dockers/web/html/colorlib-regform-16/.sass-cache/2866be86c578bb583fbf023501bb9fd24cc3bf22/minxi.scssc create mode 100755 dockers/web/html/colorlib-regform-16/.sass-cache/2866be86c578bb583fbf023501bb9fd24cc3bf22/variables.scssc create mode 100755 dockers/web/html/colorlib-regform-16/.sass-cache/3948936f84e1a30385e73a9be140b58b48c82e15/style.scssc create mode 100755 dockers/web/html/colorlib-regform-16/.sass-cache/41c45612afe01e9f2496d846f6e06c0f4eae7672/extend.scssc create mode 100755 dockers/web/html/colorlib-regform-16/.sass-cache/41c45612afe01e9f2496d846f6e06c0f4eae7672/fonts.scssc create mode 100755 dockers/web/html/colorlib-regform-16/.sass-cache/41c45612afe01e9f2496d846f6e06c0f4eae7672/global.scssc create mode 100755 dockers/web/html/colorlib-regform-16/.sass-cache/41c45612afe01e9f2496d846f6e06c0f4eae7672/minxi.scssc create mode 100755 dockers/web/html/colorlib-regform-16/.sass-cache/41c45612afe01e9f2496d846f6e06c0f4eae7672/variables.scssc create mode 100755 dockers/web/html/colorlib-regform-16/.sass-cache/5c394fb1b61974d1fcc7e7bce6daf8df739d1747/style.scssc create mode 100755 dockers/web/html/colorlib-regform-16/.sass-cache/643489b018602f15119d8f44aeb7fc79468873a9/main.scssc create mode 100755 dockers/web/html/colorlib-regform-16/.sass-cache/643489b018602f15119d8f44aeb7fc79468873a9/responsive.scssc create mode 100755 dockers/web/html/colorlib-regform-16/.sass-cache/68358af483fbd41941949162bcf87696e73f5956/extend.scssc create mode 100755 dockers/web/html/colorlib-regform-16/.sass-cache/68358af483fbd41941949162bcf87696e73f5956/fonts.scssc create mode 100755 dockers/web/html/colorlib-regform-16/.sass-cache/68358af483fbd41941949162bcf87696e73f5956/global.scssc create mode 100755 dockers/web/html/colorlib-regform-16/.sass-cache/68358af483fbd41941949162bcf87696e73f5956/minxi.scssc create mode 100755 dockers/web/html/colorlib-regform-16/.sass-cache/68358af483fbd41941949162bcf87696e73f5956/variables.scssc create mode 100755 dockers/web/html/colorlib-regform-16/.sass-cache/7643c8aa2e335ff5e104bc47d8dd7f1a0693bb39/extend.scssc create mode 100755 dockers/web/html/colorlib-regform-16/.sass-cache/7643c8aa2e335ff5e104bc47d8dd7f1a0693bb39/fonts.scssc create mode 100755 dockers/web/html/colorlib-regform-16/.sass-cache/7643c8aa2e335ff5e104bc47d8dd7f1a0693bb39/global.scssc create mode 100755 dockers/web/html/colorlib-regform-16/.sass-cache/7643c8aa2e335ff5e104bc47d8dd7f1a0693bb39/minxi.scssc create mode 100755 dockers/web/html/colorlib-regform-16/.sass-cache/7643c8aa2e335ff5e104bc47d8dd7f1a0693bb39/variables.scssc create mode 100755 dockers/web/html/colorlib-regform-16/.sass-cache/82ffc783cd596580cf9adcef3bc24b981935f37f/style.scssc create mode 100755 dockers/web/html/colorlib-regform-16/.sass-cache/9dc767a56f3dc2f8a933300255cd2265f13191d4/main.scssc create mode 100755 dockers/web/html/colorlib-regform-16/.sass-cache/9dc767a56f3dc2f8a933300255cd2265f13191d4/responsive.scssc create mode 100755 dockers/web/html/colorlib-regform-16/.sass-cache/c790b871e732c41046d2d09671dc101cd76728e2/main.scssc create mode 100755 dockers/web/html/colorlib-regform-16/.sass-cache/c790b871e732c41046d2d09671dc101cd76728e2/responsive.scssc create mode 100755 dockers/web/html/colorlib-regform-16/.sass-cache/cc2f6914db28d61b7d0c6dff455cf4df6db5b30f/main.scssc create mode 100755 dockers/web/html/colorlib-regform-16/.sass-cache/cc2f6914db28d61b7d0c6dff455cf4df6db5b30f/responsive.scssc create mode 100755 dockers/web/html/colorlib-regform-16/.sass-cache/d686493398f83d759b5ae7b15feb395ac4a1f85e/extend.scssc create mode 100755 dockers/web/html/colorlib-regform-16/.sass-cache/d686493398f83d759b5ae7b15feb395ac4a1f85e/fonts.scssc create mode 100755 dockers/web/html/colorlib-regform-16/.sass-cache/d686493398f83d759b5ae7b15feb395ac4a1f85e/global.scssc create mode 100755 dockers/web/html/colorlib-regform-16/.sass-cache/d686493398f83d759b5ae7b15feb395ac4a1f85e/minxi.scssc create mode 100755 dockers/web/html/colorlib-regform-16/.sass-cache/d686493398f83d759b5ae7b15feb395ac4a1f85e/variables.scssc create mode 100755 dockers/web/html/colorlib-regform-16/.sass-cache/dd2deb9c5f01209b6937898f47e048cde30208ce/style.scssc create mode 100755 dockers/web/html/colorlib-regform-16/.sass-cache/f8f0098c6d87e34d84a0704fb71588a6cc87b962/main.scssc create mode 100755 dockers/web/html/colorlib-regform-16/.sass-cache/f8f0098c6d87e34d84a0704fb71588a6cc87b962/responsive.scssc create mode 100755 dockers/web/html/colorlib-regform-16/css/style.css create mode 100755 dockers/web/html/colorlib-regform-16/css/style.css.map create mode 100755 dockers/web/html/colorlib-regform-16/fonts/material-icon/css/material-design-iconic-font.css create mode 100755 dockers/web/html/colorlib-regform-16/fonts/material-icon/css/material-design-iconic-font.min.css create mode 100755 dockers/web/html/colorlib-regform-16/fonts/material-icon/fonts/Material-Design-Iconic-Font.eot create mode 100755 dockers/web/html/colorlib-regform-16/fonts/material-icon/fonts/Material-Design-Iconic-Font.svg create mode 100755 dockers/web/html/colorlib-regform-16/fonts/material-icon/fonts/Material-Design-Iconic-Font.ttf create mode 100755 dockers/web/html/colorlib-regform-16/fonts/material-icon/fonts/Material-Design-Iconic-Font.woff create mode 100755 dockers/web/html/colorlib-regform-16/fonts/material-icon/fonts/Material-Design-Iconic-Font.woff2 create mode 100755 dockers/web/html/colorlib-regform-16/fonts/poppins/poppins-v5-latin-300.eot create mode 100755 dockers/web/html/colorlib-regform-16/fonts/poppins/poppins-v5-latin-300.svg create mode 100755 dockers/web/html/colorlib-regform-16/fonts/poppins/poppins-v5-latin-300.ttf create mode 100755 dockers/web/html/colorlib-regform-16/fonts/poppins/poppins-v5-latin-300.woff create mode 100755 dockers/web/html/colorlib-regform-16/fonts/poppins/poppins-v5-latin-300.woff2 create mode 100755 dockers/web/html/colorlib-regform-16/fonts/poppins/poppins-v5-latin-300italic.eot create mode 100755 dockers/web/html/colorlib-regform-16/fonts/poppins/poppins-v5-latin-300italic.svg create mode 100755 dockers/web/html/colorlib-regform-16/fonts/poppins/poppins-v5-latin-300italic.ttf create mode 100755 dockers/web/html/colorlib-regform-16/fonts/poppins/poppins-v5-latin-300italic.woff create mode 100755 dockers/web/html/colorlib-regform-16/fonts/poppins/poppins-v5-latin-300italic.woff2 create mode 100755 dockers/web/html/colorlib-regform-16/fonts/poppins/poppins-v5-latin-500.eot create mode 100755 dockers/web/html/colorlib-regform-16/fonts/poppins/poppins-v5-latin-500.svg create mode 100755 dockers/web/html/colorlib-regform-16/fonts/poppins/poppins-v5-latin-500.ttf create mode 100755 dockers/web/html/colorlib-regform-16/fonts/poppins/poppins-v5-latin-500.woff create mode 100755 dockers/web/html/colorlib-regform-16/fonts/poppins/poppins-v5-latin-500.woff2 create mode 100755 dockers/web/html/colorlib-regform-16/fonts/poppins/poppins-v5-latin-500italic.eot create mode 100755 dockers/web/html/colorlib-regform-16/fonts/poppins/poppins-v5-latin-500italic.svg create mode 100755 dockers/web/html/colorlib-regform-16/fonts/poppins/poppins-v5-latin-500italic.ttf create mode 100755 dockers/web/html/colorlib-regform-16/fonts/poppins/poppins-v5-latin-500italic.woff create mode 100755 dockers/web/html/colorlib-regform-16/fonts/poppins/poppins-v5-latin-500italic.woff2 create mode 100755 dockers/web/html/colorlib-regform-16/fonts/poppins/poppins-v5-latin-600.eot create mode 100755 dockers/web/html/colorlib-regform-16/fonts/poppins/poppins-v5-latin-600.svg create mode 100755 dockers/web/html/colorlib-regform-16/fonts/poppins/poppins-v5-latin-600.ttf create mode 100755 dockers/web/html/colorlib-regform-16/fonts/poppins/poppins-v5-latin-600.woff create mode 100755 dockers/web/html/colorlib-regform-16/fonts/poppins/poppins-v5-latin-600.woff2 create mode 100755 dockers/web/html/colorlib-regform-16/fonts/poppins/poppins-v5-latin-700.eot create mode 100755 dockers/web/html/colorlib-regform-16/fonts/poppins/poppins-v5-latin-700.svg create mode 100755 dockers/web/html/colorlib-regform-16/fonts/poppins/poppins-v5-latin-700.ttf create mode 100755 dockers/web/html/colorlib-regform-16/fonts/poppins/poppins-v5-latin-700.woff create mode 100755 dockers/web/html/colorlib-regform-16/fonts/poppins/poppins-v5-latin-700.woff2 create mode 100755 dockers/web/html/colorlib-regform-16/fonts/poppins/poppins-v5-latin-700italic.eot create mode 100755 dockers/web/html/colorlib-regform-16/fonts/poppins/poppins-v5-latin-700italic.svg create mode 100755 dockers/web/html/colorlib-regform-16/fonts/poppins/poppins-v5-latin-700italic.ttf create mode 100755 dockers/web/html/colorlib-regform-16/fonts/poppins/poppins-v5-latin-700italic.woff create mode 100755 dockers/web/html/colorlib-regform-16/fonts/poppins/poppins-v5-latin-700italic.woff2 create mode 100755 dockers/web/html/colorlib-regform-16/fonts/poppins/poppins-v5-latin-800.eot create mode 100755 dockers/web/html/colorlib-regform-16/fonts/poppins/poppins-v5-latin-800.svg create mode 100755 dockers/web/html/colorlib-regform-16/fonts/poppins/poppins-v5-latin-800.ttf create mode 100755 dockers/web/html/colorlib-regform-16/fonts/poppins/poppins-v5-latin-800.woff create mode 100755 dockers/web/html/colorlib-regform-16/fonts/poppins/poppins-v5-latin-800.woff2 create mode 100755 dockers/web/html/colorlib-regform-16/fonts/poppins/poppins-v5-latin-800italic.eot create mode 100755 dockers/web/html/colorlib-regform-16/fonts/poppins/poppins-v5-latin-800italic.svg create mode 100755 dockers/web/html/colorlib-regform-16/fonts/poppins/poppins-v5-latin-800italic.ttf create mode 100755 dockers/web/html/colorlib-regform-16/fonts/poppins/poppins-v5-latin-800italic.woff create mode 100755 dockers/web/html/colorlib-regform-16/fonts/poppins/poppins-v5-latin-800italic.woff2 create mode 100755 dockers/web/html/colorlib-regform-16/fonts/poppins/poppins-v5-latin-900.eot create mode 100755 dockers/web/html/colorlib-regform-16/fonts/poppins/poppins-v5-latin-900.svg create mode 100755 dockers/web/html/colorlib-regform-16/fonts/poppins/poppins-v5-latin-900.ttf create mode 100755 dockers/web/html/colorlib-regform-16/fonts/poppins/poppins-v5-latin-900.woff create mode 100755 dockers/web/html/colorlib-regform-16/fonts/poppins/poppins-v5-latin-900.woff2 create mode 100755 dockers/web/html/colorlib-regform-16/fonts/poppins/poppins-v5-latin-italic.eot create mode 100755 dockers/web/html/colorlib-regform-16/fonts/poppins/poppins-v5-latin-italic.svg create mode 100755 dockers/web/html/colorlib-regform-16/fonts/poppins/poppins-v5-latin-italic.ttf create mode 100755 dockers/web/html/colorlib-regform-16/fonts/poppins/poppins-v5-latin-italic.woff create mode 100755 dockers/web/html/colorlib-regform-16/fonts/poppins/poppins-v5-latin-italic.woff2 create mode 100755 dockers/web/html/colorlib-regform-16/fonts/poppins/poppins-v5-latin-regular.eot create mode 100755 dockers/web/html/colorlib-regform-16/fonts/poppins/poppins-v5-latin-regular.svg create mode 100755 dockers/web/html/colorlib-regform-16/fonts/poppins/poppins-v5-latin-regular.ttf create mode 100755 dockers/web/html/colorlib-regform-16/fonts/poppins/poppins-v5-latin-regular.woff create mode 100755 dockers/web/html/colorlib-regform-16/fonts/poppins/poppins-v5-latin-regular.woff2 create mode 100755 dockers/web/html/colorlib-regform-16/fonts/poppins/roboto-condensed-v16-latin-700.eot create mode 100755 dockers/web/html/colorlib-regform-16/fonts/poppins/roboto-condensed-v16-latin-700.svg create mode 100755 dockers/web/html/colorlib-regform-16/fonts/poppins/roboto-condensed-v16-latin-700.ttf create mode 100755 dockers/web/html/colorlib-regform-16/fonts/poppins/roboto-condensed-v16-latin-700.woff create mode 100755 dockers/web/html/colorlib-regform-16/fonts/poppins/roboto-condensed-v16-latin-700.woff2 create mode 100755 dockers/web/html/colorlib-regform-16/fonts/poppins/roboto-condensed-v16-latin-700italic.eot create mode 100755 dockers/web/html/colorlib-regform-16/fonts/poppins/roboto-condensed-v16-latin-700italic.svg create mode 100755 dockers/web/html/colorlib-regform-16/fonts/poppins/roboto-condensed-v16-latin-700italic.ttf create mode 100755 dockers/web/html/colorlib-regform-16/fonts/poppins/roboto-condensed-v16-latin-700italic.woff create mode 100755 dockers/web/html/colorlib-regform-16/fonts/poppins/roboto-condensed-v16-latin-700italic.woff2 create mode 100755 dockers/web/html/colorlib-regform-16/fonts/poppins/roboto-condensed-v16-latin-italic.eot create mode 100755 dockers/web/html/colorlib-regform-16/fonts/poppins/roboto-condensed-v16-latin-italic.svg create mode 100755 dockers/web/html/colorlib-regform-16/fonts/poppins/roboto-condensed-v16-latin-italic.ttf create mode 100755 dockers/web/html/colorlib-regform-16/fonts/poppins/roboto-condensed-v16-latin-italic.woff create mode 100755 dockers/web/html/colorlib-regform-16/fonts/poppins/roboto-condensed-v16-latin-italic.woff2 create mode 100755 dockers/web/html/colorlib-regform-16/fonts/poppins/roboto-condensed-v16-latin-regular.eot create mode 100755 dockers/web/html/colorlib-regform-16/fonts/poppins/roboto-condensed-v16-latin-regular.svg create mode 100755 dockers/web/html/colorlib-regform-16/fonts/poppins/roboto-condensed-v16-latin-regular.ttf create mode 100755 dockers/web/html/colorlib-regform-16/fonts/poppins/roboto-condensed-v16-latin-regular.woff create mode 100755 dockers/web/html/colorlib-regform-16/fonts/poppins/roboto-condensed-v16-latin-regular.woff2 create mode 100755 dockers/web/html/colorlib-regform-16/images/form-img.jpg create mode 100755 dockers/web/html/colorlib-regform-16/images/form-img.jpg.kaz create mode 100755 dockers/web/html/colorlib-regform-16/index.html create mode 100755 dockers/web/html/colorlib-regform-16/js/main.js create mode 100755 dockers/web/html/colorlib-regform-16/readme.txt create mode 100755 dockers/web/html/colorlib-regform-16/scss/common/extend.scss create mode 100755 dockers/web/html/colorlib-regform-16/scss/common/fonts.scss create mode 100755 dockers/web/html/colorlib-regform-16/scss/common/global.scss create mode 100755 dockers/web/html/colorlib-regform-16/scss/common/minxi.scss create mode 100755 dockers/web/html/colorlib-regform-16/scss/common/variables.scss create mode 100755 dockers/web/html/colorlib-regform-16/scss/layouts/main.scss create mode 100755 dockers/web/html/colorlib-regform-16/scss/layouts/responsive.scss create mode 100755 dockers/web/html/colorlib-regform-16/scss/style.scss create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/additional-methods.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/additional-methods.min.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/jquery.validate.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/jquery.validate.min.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/messages_ar.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/messages_ar.min.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/messages_az.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/messages_az.min.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/messages_bg.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/messages_bg.min.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/messages_bn_BD.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/messages_bn_BD.min.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/messages_ca.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/messages_ca.min.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/messages_cs.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/messages_cs.min.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/messages_da.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/messages_da.min.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/messages_de.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/messages_de.min.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/messages_el.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/messages_el.min.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/messages_es.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/messages_es.min.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/messages_es_AR.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/messages_es_AR.min.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/messages_es_PE.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/messages_es_PE.min.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/messages_et.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/messages_et.min.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/messages_eu.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/messages_eu.min.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/messages_fa.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/messages_fa.min.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/messages_fi.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/messages_fi.min.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/messages_fr.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/messages_fr.min.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/messages_ge.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/messages_ge.min.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/messages_gl.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/messages_gl.min.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/messages_he.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/messages_he.min.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/messages_hr.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/messages_hr.min.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/messages_hu.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/messages_hu.min.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/messages_hy_AM.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/messages_hy_AM.min.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/messages_id.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/messages_id.min.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/messages_is.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/messages_is.min.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/messages_it.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/messages_it.min.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/messages_ja.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/messages_ja.min.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/messages_ka.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/messages_ka.min.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/messages_kk.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/messages_kk.min.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/messages_ko.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/messages_ko.min.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/messages_lt.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/messages_lt.min.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/messages_lv.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/messages_lv.min.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/messages_mk.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/messages_mk.min.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/messages_my.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/messages_my.min.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/messages_nl.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/messages_nl.min.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/messages_no.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/messages_no.min.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/messages_pl.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/messages_pl.min.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/messages_pt_BR.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/messages_pt_BR.min.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/messages_pt_PT.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/messages_pt_PT.min.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/messages_ro.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/messages_ro.min.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/messages_ru.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/messages_ru.min.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/messages_sd.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/messages_sd.min.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/messages_si.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/messages_si.min.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/messages_sk.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/messages_sk.min.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/messages_sl.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/messages_sl.min.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/messages_sr.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/messages_sr.min.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/messages_sr_lat.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/messages_sr_lat.min.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/messages_sv.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/messages_sv.min.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/messages_th.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/messages_th.min.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/messages_tj.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/messages_tj.min.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/messages_tr.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/messages_tr.min.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/messages_uk.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/messages_uk.min.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/messages_ur.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/messages_ur.min.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/messages_vi.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/messages_vi.min.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/messages_zh.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/messages_zh.min.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/messages_zh_TW.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/messages_zh_TW.min.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/methods_de.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/methods_de.min.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/methods_es_CL.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/methods_es_CL.min.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/methods_fi.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/methods_fi.min.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/methods_nl.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/methods_nl.min.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/methods_pt.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery-validation/dist/localization/methods_pt.min.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery/jquery-ui.min.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/jquery/jquery.min.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/nouislider/nouislider.min.css create mode 100755 dockers/web/html/colorlib-regform-16/vendor/nouislider/nouislider.min.js create mode 100755 dockers/web/html/colorlib-regform-16/vendor/wnumb/wNumb.js create mode 100644 dockers/web/html/confiance.html create mode 100644 dockers/web/html/deja_kaznaute.html create mode 100644 dockers/web/html/elements.html create mode 100644 dockers/web/html/favicon.ico create mode 100644 dockers/web/html/images/1f609.png create mode 100644 dockers/web/html/images/2705.png create mode 100644 dockers/web/html/images/actualites.png create mode 100644 dockers/web/html/images/calc.jpg create mode 100644 dockers/web/html/images/chatons.png create mode 100644 dockers/web/html/images/confiance_agglo_vannes.png create mode 100644 dockers/web/html/images/confiance_ess.png create mode 100644 dockers/web/html/images/confiance_iut.png create mode 100644 dockers/web/html/images/date.jpg create mode 100644 dockers/web/html/images/deja_kaznaute.jpg create mode 100644 dockers/web/html/images/file.jpg create mode 100644 dockers/web/html/images/forms.jpg create mode 100644 dockers/web/html/images/generic/pic01.jpg create mode 100644 dockers/web/html/images/generic/pic02.jpg create mode 100644 dockers/web/html/images/generic/pic03.jpg create mode 100644 dockers/web/html/images/generic/pic04.jpg create mode 100644 dockers/web/html/images/generic/pic05.jpg create mode 100644 dockers/web/html/images/generic/pic06.jpg create mode 100644 dockers/web/html/images/generic/pic07.jpg create mode 100644 dockers/web/html/images/generic/pic08.jpg create mode 100644 dockers/web/html/images/generic/pic09.jpg create mode 100644 dockers/web/html/images/generic/pic10.jpg create mode 100644 dockers/web/html/images/generic/pic11.jpg create mode 100644 dockers/web/html/images/generic/pic12.jpg create mode 100644 dockers/web/html/images/generic/pic13.jpg create mode 100644 dockers/web/html/images/generic/pic14.jpg create mode 100644 dockers/web/html/images/generic/pic15.jpg create mode 100644 dockers/web/html/images/instant_messaging.jpg create mode 100644 dockers/web/html/images/link_rbg.png create mode 100644 dockers/web/html/images/link_resolu.png create mode 100644 dockers/web/html/images/link_tycloud.png create mode 100644 dockers/web/html/images/logo.svg create mode 100644 dockers/web/html/images/nextcloud.png create mode 100644 dockers/web/html/images/offre_associations.jpg create mode 100644 dockers/web/html/images/offre_associations_bandeau.jpg create mode 100644 dockers/web/html/images/offre_decouverte.jpg create mode 100644 dockers/web/html/images/offre_decouverte_bandeau.jpg create mode 100644 dockers/web/html/images/offre_dediee.jpg create mode 100644 dockers/web/html/images/offre_dediee_bandeau.png create mode 100644 dockers/web/html/images/offre_entreprises.jpg create mode 100644 dockers/web/html/images/offre_entreprises_bandeau.png create mode 100644 dockers/web/html/images/offre_familles.jpg create mode 100644 dockers/web/html/images/offre_familles_bandeau.jpg create mode 100644 dockers/web/html/images/offre_organisations.jpg create mode 100644 dockers/web/html/images/offre_organisations_bandeau.jpg create mode 100644 dockers/web/html/images/offre_particuliers.jpg create mode 100644 dockers/web/html/images/offre_particuliers_bandeau.jpg create mode 100644 dockers/web/html/images/pad.jpg create mode 100644 dockers/web/html/images/pourquoi_kaz.png create mode 100644 dockers/web/html/images/qui_sommes_nous.png create mode 100644 dockers/web/html/images/tableur.jpg create mode 100644 dockers/web/html/images/visio.jpg create mode 100644 dockers/web/html/images/wiki.jpg create mode 100644 dockers/web/html/index.html create mode 100644 dockers/web/html/m/Readme.txt create mode 100644 dockers/web/html/m/coche.png create mode 100644 dockers/web/html/m/email.css create mode 120000 dockers/web/html/m/kaz-50.png create mode 120000 dockers/web/html/m/kaz-du-libre-23.png create mode 100644 dockers/web/html/m/logo.png create mode 120000 dockers/web/html/mail-cleanup create mode 100644 dockers/web/html/mail/config-v1.1.xml create mode 100644 dockers/web/html/mentions_legales.html create mode 100644 dockers/web/html/offre_associations.html create mode 100644 dockers/web/html/offre_decouverte.html create mode 100644 dockers/web/html/offre_dedie.html create mode 100644 dockers/web/html/offre_dediee.html create mode 100644 dockers/web/html/offre_entreprises.html create mode 100644 dockers/web/html/offre_familles.html create mode 100644 dockers/web/html/offre_organisations.html create mode 100644 dockers/web/html/offre_particuliers.html create mode 100644 dockers/web/html/pourquoi_choisir_kaz.html create mode 100644 dockers/web/html/qui_sommes_nous.html create mode 100644 dockers/web/html/rgpd.html create mode 100644 dockers/web/html/robot.txt create mode 100644 dockers/web/html/sites_amis.html create mode 100755 dockers/web/web-gen.sh create mode 100644 secret.tmpl/Readme.txt create mode 100755 secret.tmpl/SetAllPass.sh create mode 100644 secret.tmpl/allow_admin_ip create mode 100644 secret.tmpl/env-apikazServ create mode 100644 secret.tmpl/env-castopodDB create mode 100644 secret.tmpl/env-castopodServ create mode 100644 secret.tmpl/env-dokuwikiServ create mode 100644 secret.tmpl/env-ethercalcServ create mode 100644 secret.tmpl/env-etherpadDB create mode 100644 secret.tmpl/env-etherpadServ create mode 100644 secret.tmpl/env-framadateDB create mode 100644 secret.tmpl/env-framadateServ create mode 100644 secret.tmpl/env-gandi create mode 100644 secret.tmpl/env-gitDB create mode 100644 secret.tmpl/env-gitServ create mode 100644 secret.tmpl/env-jirafeauServ create mode 100644 secret.tmpl/env-ldapServ create mode 100644 secret.tmpl/env-ldapUI create mode 100644 secret.tmpl/env-mailServ create mode 100644 secret.tmpl/env-mattermostDB create mode 100644 secret.tmpl/env-mattermostServ create mode 100644 secret.tmpl/env-mobilizonDB create mode 100644 secret.tmpl/env-mobilizonServ create mode 100644 secret.tmpl/env-nextcloudDB create mode 100644 secret.tmpl/env-nextcloudServ create mode 100644 secret.tmpl/env-officeServ create mode 100644 secret.tmpl/env-roundcubeDB create mode 100644 secret.tmpl/env-roundcubeServ create mode 100644 secret.tmpl/env-sympaDB create mode 100644 secret.tmpl/env-sympaServ create mode 100644 secret.tmpl/env-vaultwardenDB create mode 100644 secret.tmpl/env-vaultwardenServ create mode 100644 secret.tmpl/env-vigiloDB create mode 100644 secret.tmpl/env-vigiloServ create mode 100644 secret.tmpl/env-wpDB create mode 100644 secret.tmpl/env-wpServ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..c02c70e --- /dev/null +++ b/LICENSE @@ -0,0 +1,515 @@ + +CeCILL-B FREE SOFTWARE LICENSE AGREEMENT + + + Notice + +This Agreement is a Free Software license agreement that is the result +of discussions between its authors in order to ensure compliance with +the two main principles guiding its drafting: + + * firstly, compliance with the principles governing the distribution + of Free Software: access to source code, broad rights granted to + users, + * secondly, the election of a governing law, French law, with which + it is conformant, both as regards the law of torts and + intellectual property law, and the protection that it offers to + both authors and holders of the economic rights over software. + +The authors of the CeCILL-B (for Ce[a] C[nrs] I[nria] L[ogiciel] L[ibre]) +license are: + +Commissariat à l'Energie Atomique - CEA, a public scientific, technical +and industrial research establishment, having its principal place of +business at 25 rue Leblanc, immeuble Le Ponant D, 75015 Paris, France. + +Centre National de la Recherche Scientifique - CNRS, a public scientific +and technological establishment, having its principal place of business +at 3 rue Michel-Ange, 75794 Paris cedex 16, France. + +Institut National de Recherche en Informatique et en Automatique - +INRIA, a public scientific and technological establishment, having its +principal place of business at Domaine de Voluceau, Rocquencourt, BP +105, 78153 Le Chesnay cedex, France. + + + Preamble + +This Agreement is an open source software license intended to give users +significant freedom to modify and redistribute the software licensed +hereunder. + +The exercising of this freedom is conditional upon a strong obligation +of giving credits for everybody that distributes a software +incorporating a software ruled by the current license so as all +contributions to be properly identified and acknowledged. + +In consideration of access to the source code and the rights to copy, +modify and redistribute granted by the license, users are provided only +with a limited warranty and the software's author, the holder of the +economic rights, and the successive licensors only have limited liability. + +In this respect, the risks associated with loading, using, modifying +and/or developing or reproducing the software by the user are brought to +the user's attention, given its Free Software status, which may make it +complicated to use, with the result that its use is reserved for +developers and experienced professionals having in-depth computer +knowledge. Users are therefore encouraged to load and test the +suitability of the software as regards their requirements in conditions +enabling the security of their systems and/or data to be ensured and, +more generally, to use and operate it in the same conditions of +security. This Agreement may be freely reproduced and published, +provided it is not altered, and that no provisions are either added or +removed herefrom. + +This Agreement may apply to any or all software for which the holder of +the economic rights decides to submit the use thereof to its provisions. + + + Article 1 - DEFINITIONS + +For the purpose of this Agreement, when the following expressions +commence with a capital letter, they shall have the following meaning: + +Agreement: means this license agreement, and its possible subsequent +versions and annexes. + +Software: means the software in its Object Code and/or Source Code form +and, where applicable, its documentation, "as is" when the Licensee +accepts the Agreement. + +Initial Software: means the Software in its Source Code and possibly its +Object Code form and, where applicable, its documentation, "as is" when +it is first distributed under the terms and conditions of the Agreement. + +Modified Software: means the Software modified by at least one +Contribution. + +Source Code: means all the Software's instructions and program lines to +which access is required so as to modify the Software. + +Object Code: means the binary files originating from the compilation of +the Source Code. + +Holder: means the holder(s) of the economic rights over the Initial +Software. + +Licensee: means the Software user(s) having accepted the Agreement. + +Contributor: means a Licensee having made at least one Contribution. + +Licensor: means the Holder, or any other individual or legal entity, who +distributes the Software under the Agreement. + +Contribution: means any or all modifications, corrections, translations, +adaptations and/or new functions integrated into the Software by any or +all Contributors, as well as any or all Internal Modules. + +Module: means a set of sources files including their documentation that +enables supplementary functions or services in addition to those offered +by the Software. + +External Module: means any or all Modules, not derived from the +Software, so that this Module and the Software run in separate address +spaces, with one calling the other when they are run. + +Internal Module: means any or all Module, connected to the Software so +that they both execute in the same address space. + +Parties: mean both the Licensee and the Licensor. + +These expressions may be used both in singular and plural form. + + + Article 2 - PURPOSE + +The purpose of the Agreement is the grant by the Licensor to the +Licensee of a non-exclusive, transferable and worldwide license for the +Software as set forth in Article 5 hereinafter for the whole term of the +protection granted by the rights over said Software. + + + Article 3 - ACCEPTANCE + +3.1 The Licensee shall be deemed as having accepted the terms and +conditions of this Agreement upon the occurrence of the first of the +following events: + + * (i) loading the Software by any or all means, notably, by + downloading from a remote server, or by loading from a physical + medium; + * (ii) the first time the Licensee exercises any of the rights + granted hereunder. + +3.2 One copy of the Agreement, containing a notice relating to the +characteristics of the Software, to the limited warranty, and to the +fact that its use is restricted to experienced users has been provided +to the Licensee prior to its acceptance as set forth in Article 3.1 +hereinabove, and the Licensee hereby acknowledges that it has read and +understood it. + + + Article 4 - EFFECTIVE DATE AND TERM + + + 4.1 EFFECTIVE DATE + +The Agreement shall become effective on the date when it is accepted by +the Licensee as set forth in Article 3.1. + + + 4.2 TERM + +The Agreement shall remain in force for the entire legal term of +protection of the economic rights over the Software. + + + Article 5 - SCOPE OF RIGHTS GRANTED + +The Licensor hereby grants to the Licensee, who accepts, the following +rights over the Software for any or all use, and for the term of the +Agreement, on the basis of the terms and conditions set forth hereinafter. + +Besides, if the Licensor owns or comes to own one or more patents +protecting all or part of the functions of the Software or of its +components, the Licensor undertakes not to enforce the rights granted by +these patents against successive Licensees using, exploiting or +modifying the Software. If these patents are transferred, the Licensor +undertakes to have the transferees subscribe to the obligations set +forth in this paragraph. + + + 5.1 RIGHT OF USE + +The Licensee is authorized to use the Software, without any limitation +as to its fields of application, with it being hereinafter specified +that this comprises: + + 1. permanent or temporary reproduction of all or part of the Software + by any or all means and in any or all form. + + 2. loading, displaying, running, or storing the Software on any or + all medium. + + 3. entitlement to observe, study or test its operation so as to + determine the ideas and principles behind any or all constituent + elements of said Software. This shall apply when the Licensee + carries out any or all loading, displaying, running, transmission + or storage operation as regards the Software, that it is entitled + to carry out hereunder. + + + 5.2 ENTITLEMENT TO MAKE CONTRIBUTIONS + +The right to make Contributions includes the right to translate, adapt, +arrange, or make any or all modifications to the Software, and the right +to reproduce the resulting software. + +The Licensee is authorized to make any or all Contributions to the +Software provided that it includes an explicit notice that it is the +author of said Contribution and indicates the date of the creation thereof. + + + 5.3 RIGHT OF DISTRIBUTION + +In particular, the right of distribution includes the right to publish, +transmit and communicate the Software to the general public on any or +all medium, and by any or all means, and the right to market, either in +consideration of a fee, or free of charge, one or more copies of the +Software by any means. + +The Licensee is further authorized to distribute copies of the modified +or unmodified Software to third parties according to the terms and +conditions set forth hereinafter. + + + 5.3.1 DISTRIBUTION OF SOFTWARE WITHOUT MODIFICATION + +The Licensee is authorized to distribute true copies of the Software in +Source Code or Object Code form, provided that said distribution +complies with all the provisions of the Agreement and is accompanied by: + + 1. a copy of the Agreement, + + 2. a notice relating to the limitation of both the Licensor's + warranty and liability as set forth in Articles 8 and 9, + +and that, in the event that only the Object Code of the Software is +redistributed, the Licensee allows effective access to the full Source +Code of the Software at a minimum during the entire period of its +distribution of the Software, it being understood that the additional +cost of acquiring the Source Code shall not exceed the cost of +transferring the data. + + + 5.3.2 DISTRIBUTION OF MODIFIED SOFTWARE + +If the Licensee makes any Contribution to the Software, the resulting +Modified Software may be distributed under a license agreement other +than this Agreement subject to compliance with the provisions of Article +5.3.4. + + + 5.3.3 DISTRIBUTION OF EXTERNAL MODULES + +When the Licensee has developed an External Module, the terms and +conditions of this Agreement do not apply to said External Module, that +may be distributed under a separate license agreement. + + + 5.3.4 CREDITS + +Any Licensee who may distribute a Modified Software hereby expressly +agrees to: + + 1. indicate in the related documentation that it is based on the + Software licensed hereunder, and reproduce the intellectual + property notice for the Software, + + 2. ensure that written indications of the Software intended use, + intellectual property notice and license hereunder are included in + easily accessible format from the Modified Software interface, + + 3. mention, on a freely accessible website describing the Modified + Software, at least throughout the distribution term thereof, that + it is based on the Software licensed hereunder, and reproduce the + Software intellectual property notice, + + 4. where it is distributed to a third party that may distribute a + Modified Software without having to make its source code + available, make its best efforts to ensure that said third party + agrees to comply with the obligations set forth in this Article . + +If the Software, whether or not modified, is distributed with an +External Module designed for use in connection with the Software, the +Licensee shall submit said External Module to the foregoing obligations. + + + 5.3.5 COMPATIBILITY WITH THE CeCILL AND CeCILL-C LICENSES + +Where a Modified Software contains a Contribution subject to the CeCILL +license, the provisions set forth in Article 5.3.4 shall be optional. + +A Modified Software may be distributed under the CeCILL-C license. In +such a case the provisions set forth in Article 5.3.4 shall be optional. + + + Article 6 - INTELLECTUAL PROPERTY + + + 6.1 OVER THE INITIAL SOFTWARE + +The Holder owns the economic rights over the Initial Software. Any or +all use of the Initial Software is subject to compliance with the terms +and conditions under which the Holder has elected to distribute its work +and no one shall be entitled to modify the terms and conditions for the +distribution of said Initial Software. + +The Holder undertakes that the Initial Software will remain ruled at +least by this Agreement, for the duration set forth in Article 4.2. + + + 6.2 OVER THE CONTRIBUTIONS + +The Licensee who develops a Contribution is the owner of the +intellectual property rights over this Contribution as defined by +applicable law. + + + 6.3 OVER THE EXTERNAL MODULES + +The Licensee who develops an External Module is the owner of the +intellectual property rights over this External Module as defined by +applicable law and is free to choose the type of agreement that shall +govern its distribution. + + + 6.4 JOINT PROVISIONS + +The Licensee expressly undertakes: + + 1. not to remove, or modify, in any manner, the intellectual property + notices attached to the Software; + + 2. to reproduce said notices, in an identical manner, in the copies + of the Software modified or not. + +The Licensee undertakes not to directly or indirectly infringe the +intellectual property rights of the Holder and/or Contributors on the +Software and to take, where applicable, vis-à-vis its staff, any and all +measures required to ensure respect of said intellectual property rights +of the Holder and/or Contributors. + + + Article 7 - RELATED SERVICES + +7.1 Under no circumstances shall the Agreement oblige the Licensor to +provide technical assistance or maintenance services for the Software. + +However, the Licensor is entitled to offer this type of services. The +terms and conditions of such technical assistance, and/or such +maintenance, shall be set forth in a separate instrument. Only the +Licensor offering said maintenance and/or technical assistance services +shall incur liability therefor. + +7.2 Similarly, any Licensor is entitled to offer to its licensees, under +its sole responsibility, a warranty, that shall only be binding upon +itself, for the redistribution of the Software and/or the Modified +Software, under terms and conditions that it is free to decide. Said +warranty, and the financial terms and conditions of its application, +shall be subject of a separate instrument executed between the Licensor +and the Licensee. + + + Article 8 - LIABILITY + +8.1 Subject to the provisions of Article 8.2, the Licensee shall be +entitled to claim compensation for any direct loss it may have suffered +from the Software as a result of a fault on the part of the relevant +Licensor, subject to providing evidence thereof. + +8.2 The Licensor's liability is limited to the commitments made under +this Agreement and shall not be incurred as a result of in particular: +(i) loss due the Licensee's total or partial failure to fulfill its +obligations, (ii) direct or consequential loss that is suffered by the +Licensee due to the use or performance of the Software, and (iii) more +generally, any consequential loss. In particular the Parties expressly +agree that any or all pecuniary or business loss (i.e. loss of data, +loss of profits, operating loss, loss of customers or orders, +opportunity cost, any disturbance to business activities) or any or all +legal proceedings instituted against the Licensee by a third party, +shall constitute consequential loss and shall not provide entitlement to +any or all compensation from the Licensor. + + + Article 9 - WARRANTY + +9.1 The Licensee acknowledges that the scientific and technical +state-of-the-art when the Software was distributed did not enable all +possible uses to be tested and verified, nor for the presence of +possible defects to be detected. In this respect, the Licensee's +attention has been drawn to the risks associated with loading, using, +modifying and/or developing and reproducing the Software which are +reserved for experienced users. + +The Licensee shall be responsible for verifying, by any or all means, +the suitability of the product for its requirements, its good working +order, and for ensuring that it shall not cause damage to either persons +or properties. + +9.2 The Licensor hereby represents, in good faith, that it is entitled +to grant all the rights over the Software (including in particular the +rights set forth in Article 5). + +9.3 The Licensee acknowledges that the Software is supplied "as is" by +the Licensor without any other express or tacit warranty, other than +that provided for in Article 9.2 and, in particular, without any warranty +as to its commercial value, its secured, safe, innovative or relevant +nature. + +Specifically, the Licensor does not warrant that the Software is free +from any error, that it will operate without interruption, that it will +be compatible with the Licensee's own equipment and software +configuration, nor that it will meet the Licensee's requirements. + +9.4 The Licensor does not either expressly or tacitly warrant that the +Software does not infringe any third party intellectual property right +relating to a patent, software or any other property right. Therefore, +the Licensor disclaims any and all liability towards the Licensee +arising out of any or all proceedings for infringement that may be +instituted in respect of the use, modification and redistribution of the +Software. Nevertheless, should such proceedings be instituted against +the Licensee, the Licensor shall provide it with technical and legal +assistance for its defense. Such technical and legal assistance shall be +decided on a case-by-case basis between the relevant Licensor and the +Licensee pursuant to a memorandum of understanding. The Licensor +disclaims any and all liability as regards the Licensee's use of the +name of the Software. No warranty is given as regards the existence of +prior rights over the name of the Software or as regards the existence +of a trademark. + + + Article 10 - TERMINATION + +10.1 In the event of a breach by the Licensee of its obligations +hereunder, the Licensor may automatically terminate this Agreement +thirty (30) days after notice has been sent to the Licensee and has +remained ineffective. + +10.2 A Licensee whose Agreement is terminated shall no longer be +authorized to use, modify or distribute the Software. However, any +licenses that it may have granted prior to termination of the Agreement +shall remain valid subject to their having been granted in compliance +with the terms and conditions hereof. + + + Article 11 - MISCELLANEOUS + + + 11.1 EXCUSABLE EVENTS + +Neither Party shall be liable for any or all delay, or failure to +perform the Agreement, that may be attributable to an event of force +majeure, an act of God or an outside cause, such as defective +functioning or interruptions of the electricity or telecommunications +networks, network paralysis following a virus attack, intervention by +government authorities, natural disasters, water damage, earthquakes, +fire, explosions, strikes and labor unrest, war, etc. + +11.2 Any failure by either Party, on one or more occasions, to invoke +one or more of the provisions hereof, shall under no circumstances be +interpreted as being a waiver by the interested Party of its right to +invoke said provision(s) subsequently. + +11.3 The Agreement cancels and replaces any or all previous agreements, +whether written or oral, between the Parties and having the same +purpose, and constitutes the entirety of the agreement between said +Parties concerning said purpose. No supplement or modification to the +terms and conditions hereof shall be effective as between the Parties +unless it is made in writing and signed by their duly authorized +representatives. + +11.4 In the event that one or more of the provisions hereof were to +conflict with a current or future applicable act or legislative text, +said act or legislative text shall prevail, and the Parties shall make +the necessary amendments so as to comply with said act or legislative +text. All other provisions shall remain effective. Similarly, invalidity +of a provision of the Agreement, for any reason whatsoever, shall not +cause the Agreement as a whole to be invalid. + + + 11.5 LANGUAGE + +The Agreement is drafted in both French and English and both versions +are deemed authentic. + + + Article 12 - NEW VERSIONS OF THE AGREEMENT + +12.1 Any person is authorized to duplicate and distribute copies of this +Agreement. + +12.2 So as to ensure coherence, the wording of this Agreement is +protected and may only be modified by the authors of the License, who +reserve the right to periodically publish updates or new versions of the +Agreement, each with a separate number. These subsequent versions may +address new issues encountered by Free Software. + +12.3 Any Software distributed under a given version of the Agreement may +only be subsequently distributed under the same version of the Agreement +or a subsequent version. + + + Article 13 - GOVERNING LAW AND JURISDICTION + +13.1 The Agreement is governed by French law. The Parties agree to +endeavor to seek an amicable solution to any disagreements or disputes +that may arise during the performance of the Agreement. + +13.2 Failing an amicable solution within two (2) months as from their +occurrence, and unless emergency proceedings are necessary, the +disagreements or disputes shall be referred to the Paris Courts having +jurisdiction, by the more diligent Party. + + +Version 1.0 dated 2006-09-05. diff --git a/README.md b/README.md index 0bfbbe3..388502e 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,41 @@ -# KazV2 +# kaz -Ensemble des services de KAZ \ No newline at end of file +[Kaz](https://kaz.bzh/) est un CHATONS du Morbihan. Nous proposons ici un moyen de le répliquer dans d'autres lieux. Il y a des éléments de configuration à définir avant d'initialiser ce simulateur. + + +Il est possible de simuler notre CHATONS dans une VirtualBox pour mettre au point nos différents services (voir [kaz-vagrant](https://git.kaz.bzh/KAZ/kaz-vagrant)). + +## Pré-requis + +Dans la suite, on imagine que vous disposer d'une machine sous Linux (par exemple dernière version de [Debian](https://fr.wikipedia.org/wiki/Debian)) avec un minimum de paquets. + +```bash +DEBIAN_FRONTEND=noninteractive apt-get -y upgrade +DEBIAN_FRONTEND=noninteractive apt-get -y dist-upgrade +DEBIAN_FRONTEND=noninteractive apt-get install -y apg curl git unzip rsync net-tools libnss3-tools +DEBIAN_FRONTEND=noninteractive apt-get remove --purge -y exim4-base exim4-config exim4-daemon-light +``` + +## Installation + +* Télécharger le dépôt kaz-vagrant ou utilisez la commande : +```bash +git clone https://git.kaz.bzh/KAZ/kaz.git # pour essayer +git clone git+ssh://git@git.kaz.bzh:2202/KAZ/kaz.git # pour contribuer +cd kaz/ +``` +* Personalisez votre simulateur avec la commande (au besoin ajustez la mémoire et les cpus utilisés dans Vagrantfile) : +```bash +./bin/init.sh +``` + +Pour terminer la création et tous les dockers, il faut lancer la commande +```bash +./bin/install.sh web jirafeau ethercalc etherpad postfix roundcube proxy framadate paheko dokuwiki > >(tee stdout.log) 2> >(tee stderr.log >&2) +``` + +* Une fois la commande de création de l'univers, il faut patienter... + +* Après il faut patienter encore un peu si on a pas la fibre ... + +## Utilisation diff --git a/bin/.applyTemplate-completion.bash b/bin/.applyTemplate-completion.bash new file mode 100755 index 0000000..e3c1097 --- /dev/null +++ b/bin/.applyTemplate-completion.bash @@ -0,0 +1,30 @@ +#/usr/bin/env bash + +_applyTemplate_completions () { + declare -a options + OPTIONS=("-help" "-timestamp") + COMPREPLY=() + + local CUR OPTIONS_COUNT=0 + CUR=${COMP_WORDS[COMP_CWORD]} + + # compte les options déjà utilisé et les retire de la liste des possible + for ITEM in ${COMP_WORDS[@]:1} + do + if [[ " ${OPTIONS[*]} " =~ " ${ITEM} " ]] ; then + let "OPTIONS_COUNT++" + OPTIONS=(${OPTIONS[@]/${ITEM}}) + else + break + fi + done + + # si la position est dans des options "ou juste après" + ((COMP_CWORD <= OPTIONS_COUNT+1)) && [[ "${CUR}" =~ ^- ]] && COMPREPLY=( $(compgen -W "${OPTIONS[*]}" -- "${CUR}" ) ) && return 0 + + # si la position est après des options ou que l'on ne commence pas par "-" on cherche des fichiers + ((COMP_CWORD <= OPTIONS_COUNT+2)) && COMPREPLY=($(compgen -f -- "${CUR}")) && return 0 + + return 0 +} +complete -F _applyTemplate_completions applyTemplate.sh diff --git a/bin/.commonFunctions.sh b/bin/.commonFunctions.sh new file mode 100755 index 0000000..0e5c6c0 --- /dev/null +++ b/bin/.commonFunctions.sh @@ -0,0 +1,382 @@ +# commun fonctions for KAZ + +#TODO; toutes les fonctions ci-dessous devraient être commentées + +#KI : françois +#KOI : tout un tas de trucs utiles pour la gestion de l'infra kaz (à mettre dans chaque script) +#KAN : +# maj le 27/01/2024 by FAB: recherche de tous les srv kaz dispo (via le DNS) +# maj le 15/04/2024 by FAB: correction getPahekoOrgaList + +# https://wiki.bash-hackers.org/scripting/terminalcodes +BOLD='' +RED='' +GREEN='' +YELLOW='' +BLUE='' +MAGENTA='' +CYAN='' +NC='' # No Color +NL=' +' + +######################################## +setKazVars () { + # KAZ_ROOT must be set + if [ -z "${KAZ_ROOT}" ]; then + printKazError "\n\n *** KAZ_ROOT not defined! ***\n" + exit + fi + export KAZ_KEY_DIR="${KAZ_ROOT}/secret" + export KAZ_BIN_DIR="${KAZ_ROOT}/bin" + export KAZ_CONF_DIR="${KAZ_ROOT}/config" + export KAZ_CONF_PROXY_DIR="${KAZ_CONF_DIR}/proxy" + export KAZ_COMP_DIR="${KAZ_ROOT}/dockers" + export KAZ_STATE_DIR="${KAZ_ROOT}/state" + + export KAZ_GIT_DIR="${KAZ_ROOT}/git" + export KAZ_DNLD_DIR="${KAZ_ROOT}/download" + export KAZ_DNLD_PAHEKO_DIR="${KAZ_DNLD_DIR}/paheko" + + export APPLY_TMPL=${KAZ_BIN_DIR}/applyTemplate.sh + export DOCKERS_ENV="${KAZ_CONF_DIR}/dockers.env" + + export DOCK_LIB="/var/lib/docker" + export DOCK_VOL="${DOCK_LIB}/volumes" + export DOCK_VOL_PAHEKO_ORGA="${DOCK_LIB}/volumes/paheko_assoUsers/_data/" + + export NAS_VOL="/mnt/disk-nas1/docker/volumes/" +} + +######################################## + +printKazMsg () { + # $1 msg + echo -e "${CYAN}${BOLD}$1${NC}" +} + +printKazError () { + # $1 msb + echo -e "${RED}${BOLD}$1${NC}" +} + +######################################## +checkContinue () { + local rep + while : ; do + read -p "Do you want to continue? [yes]" rep + case "${rep}" in + ""|[yYoO]* ) + break + ;; + [Nn]* ) + exit + ;; + * ) + echo "Please answer yes or no." + ;; + esac + done +} + +checkDockerRunning () { + # $1 docker name + # $2 service name + if ! [[ "$(docker ps -f "name=$1" | grep -w "$1")" ]]; then + printKazError "$2 not running... abort" + return 1 + fi + return 0 +} + +######################################## +testValidIp () { + # $1 ip + local ip=$1 + local stat=1 + + if [[ $ip =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then + OIFS=$IFS + IFS='.' + ip=($ip) + IFS=$OIFS + [[ ${ip[0]} -le 255 && ${ip[1]} -le 255 && ${ip[2]} -le 255 && ${ip[3]} -le 255 ]] + stat=$? + fi + return $stat +} + +######################################## +getValInFile () { + # $1 filename + # $2 varname + grep "^\s*$2\s*=" $1 2>/dev/null | head -1 | sed "s%^\s*$2\s*=\(.*\)$%\1%" +} + +getList () { + # $1 filename + (cat "$1"|sort; echo) | sed -e "s/\(.*\)[ \t]*#.*$/\1/" -e "s/^[ \t]*\(.*\)$/\1/" -e "/^$/d" +} + +getPahekoPluginList () { + ls "${KAZ_DNLD_PAHEKO_DIR}" | grep -v "paheko-" +} + +getPahekoOrgaList () { +# ls "${DOCK_VOL_PAHEKO_ORGA}" + find ${DOCK_VOL_PAHEKO_ORGA} -mindepth 1 -maxdepth 1 -type d -printf '%f\n' | sort +} + +getAvailableComposes () { + ls "${KAZ_COMP_DIR}" | grep -v -- "^.*-orga$" +} + +getAvailableOrgas () { + +#KI : Fab +#KOI : donne la liste de toutes les orgas pour un serveur donné sinon serveur courant +#KAN : 27/01/2024 + +#en entrée +SITE_DST="$1" + + if [ -n "${SITE_DST}" ];then + ssh -p 2201 root@${SITE_DST}.${domain} "ls \"${KAZ_COMP_DIR}\" | grep -- \"^.*-orga$\"" + else + ls "${KAZ_COMP_DIR}" | grep -- "^.*-orga$" + fi + +} + +getAvailableServices () { + local service + for service in paheko cloud collabora agora wiki wp; do + echo "${service}" + done +} + +######################################## +filterInList () { + # $* ref list filter + # stdin candidats + local compose + while read compose ; do + if [[ " $* " =~ " ${compose} " ]]; then + echo ${compose} + fi + done | sort -u +} + +filterNotInList () { + # $* ref list filter + # stdin candidats + local compose + while read compose ; do + if [[ ! " $* " =~ " ${compose} " ]]; then + echo ${compose} + fi + done | sort -u +} + +filterAvailableComposes () { + # $* candidats + local AVAILABLE_COMPOSES=$(getAvailableComposes;getAvailableOrgas) + if [ $# -eq 0 ] ; then + echo ${AVAILABLE_COMPOSES} + fi + local compose + for compose in $* + do + compose=${compose%/} + if [[ ! "${NL}${AVAILABLE_COMPOSES}${NL}" =~ "${NL}${compose}${NL}" ]]; then + local subst="" + for item in ${AVAILABLE_COMPOSES}; do + [[ "${item}" =~ "${compose}" ]] && echo ${item} && subst="${subst} ${item}" + done + if [ -z "${subst}" ] ; then + echo "${RED}${BOLD}Unknown compose: ${compose} not in "${AVAILABLE_COMPOSES}"${NC}" >&2 + #exit 1 + else + echo "${BLUE}${BOLD}substitute compose: ${compose} => "${subst}"${NC}" >&2 + fi + else + echo "${compose}" + fi + done | sort -u +} + +######################################## +serviceOnInOrga () { + # $1 orga name + # $2 service name + # default value + local composeFile="${KAZ_COMP_DIR}/$1-orga/docker-compose.yml" + if [[ ! -f "${composeFile}" ]] + then + echo "$3" + else + grep -q "$2" "${composeFile}" 2>/dev/null && echo on || echo off + fi +} + +######################################## +waitUrl () { + # $1 URL to waitfor + # $2 timeout en secondes (optional) + starttime=$(date +%s) + if [[ $(curl --connect-timeout 2 -s -D - "$1" -o /dev/null 2>/dev/null | head -n1) != *[23]0[0-9]* ]]; then + printKazMsg "service not available ($1). Please wait..." + echo curl --connect-timeout 2 -s -D - "$1" -o /dev/null \| head -n1 + while [[ $(curl --connect-timeout 2 -s -D - "$1" -o /dev/null 2>/dev/null | head -n1) != *[23]0[0-9]* ]] + do + sleep 5 + if [ $# -gt 1 ]; then + actualtime=$(date +%s) + delta=$(($actualtime-$starttime)) + [[ $2 -lt $delta ]] && return 1 + fi + done + fi + return 0 +} + +######################################## +waitContainerHealthy () { + # $1 ContainerName + # $2 timeout en secondes (optional) + + healthy="false" + starttime=$(date +%s) + running="false" + [[ $(docker ps -f name="$1" | grep -w "$1") ]] && running="true" + [[ $running == "true" && $(docker inspect -f {{.State.Health.Status}} "$1") == "healthy" ]] && healthy="true" + if [[ ! $running == "true" || ! $healthy == "true" ]]; then + printKazMsg "Docker not healthy ($1). Please wait..." + while [[ ! $running == "true" || ! $healthy == "true" ]] + do + sleep 5 + if [ $# -gt 1 ]; then + actualtime=$(date +%s) + delta=$(($actualtime-$starttime)) + [[ $2 -lt $delta ]] && printKazMsg "Docker not healthy ($1)... abort..." && return 1 + fi + [[ ! $running == "true" ]] && [[ $(docker ps -f name="$1" | grep -w "$1") ]] && running="true" + [[ $running == "true" && $(docker inspect -f {{.State.Health.Status}} "$1") == "healthy" ]] && healthy="true" + done + fi + return 0 +} +######################################## +waitContainerRunning () { + # $1 ContainerName + # $2 timeout en secondes (optional) + + starttime=$(date +%s) + running="false" + [[ $(docker ps -f name="$1" | grep -w "$1") ]] && running="true" + if [[ ! $running == "true" ]]; then + printKazMsg "Docker not running ($1). Please wait..." + while [[ ! $running == "true" ]] + do + sleep 5 + if [ $# -gt 1 ]; then + actualtime=$(date +%s) + delta=$(($actualtime-$starttime)) + [[ $2 -lt $delta ]] && printKazMsg "Docker did not start ($1)... abort..." && return 1 + fi + [[ ! $running == "true" ]] && [[ $(docker ps -f name="$1" | grep -w "$1") ]] && running="true" + done + fi + return 0 +} + +######################################## +downloadFile () { + # $1 URL to download + # $2 new filename (optional) + if [ $# -lt 1 ] || [ $# -gt 2 ]; then + printKazError "downloadFile: bad arg number" + return + fi + URL=$1 + if [ -z "$2" ]; then + FILENAME="$(basename $1)" + else + FILENAME="$2" + fi + + if [ ! -f "${FILENAME}" ]; then + printKazMsg " - load ${URL}" + curl -L -o "${FILENAME}" "${URL}" + else + TMP="${FILENAME}.tmp" + rm -f "${TMP}" + curl -L -o "${TMP}" "${URL}" + if ! cmp -s "${TMP}" "${FILENAME}" 2>/dev/null; then + mv "${TMP}" "${FILENAME}" + else + rm -f "${TMP}" + fi + fi +} + +unzipInDir () { + # $1 zipfile + # $2 destDir + + if [ $# -ne 2 ]; then + printKazError "unzipInDir: bad arg number" + return + fi + if ! [[ $1 == *.zip ]]; then + printKazError "unzipInDir: $1 is not a zip file" + return + fi + if ! [[ -d $2 ]]; then + printKazError "$2 is not destination dir" + return + fi + + destName="$2/$(basename "${1%.zip}")" + if [[ -d "${destName}" ]]; then + printKazError "${destName} already exist" + return + fi + + tmpDir=$2/tmp-$$ + trap 'rm -rf "${tmpDir}"' EXIT + unzip "$1" -d "${tmpDir}" + srcDir=$(ls -1 "${tmpDir}") + case $(wc -l <<< $srcDir) in + 0) + printKazError "empty zip file : $1" + rmdir "${tmpDir}" + return + ;; + 1) + mv "${tmpDir}/${srcDir}" "${destName}" + rmdir "${tmpDir}" + ;; + *) + printKazError "zip file $1 is not a tree (${srcDir})" + return + ;; + esac +} +######################################## + +get_Serveurs_Kaz () { + +#KI : Fab +#KOI : donne la liste de tous les serveurs kaz sous le format srv1;srv2;srv3;.... en intérogeant le DNS +#KAN : 27/01/2024 + + liste=`dig -t TXT srv.kaz.bzh +short` + #on nettoie + liste=$(echo "$liste" | sed 's/\;/ /g') + liste=$(echo "$liste" | sed 's/\"//g') + #renvoi srv1 srv2 srv3 .... + echo ${liste} +} +######################################## + diff --git a/bin/.container-completion.bash b/bin/.container-completion.bash new file mode 100755 index 0000000..6871a4d --- /dev/null +++ b/bin/.container-completion.bash @@ -0,0 +1,66 @@ +#/usr/bin/env bash + +_container_completions () { + KAZ_ROOT=$(cd "$(dirname ${COMP_WORDS[0]})"/..; pwd) + COMPREPLY=() + . "${KAZ_ROOT}/bin/.commonFunctions.sh" + setKazVars + + local cword=${COMP_CWORD} cur=${COMP_WORDS[COMP_CWORD]} card=${#COMP_WORDS[@]} i w skip=0 + for ((i=1 ; i/dev/null) + COMPREPLY=($(compgen -W "${available_orga}" -- "${cur}")) + ;; + esac + return 0 +} +complete -F _gestContainers_completion gestContainers.sh diff --git a/bin/.kazDockerNet-completion.bash b/bin/.kazDockerNet-completion.bash new file mode 100755 index 0000000..c309cbd --- /dev/null +++ b/bin/.kazDockerNet-completion.bash @@ -0,0 +1,51 @@ +#/usr/bin/env bash + +_kazDockerNet_completion () { + KAZ_ROOT=$(cd "$(dirname ${COMP_WORDS[0]})"/..; pwd) + COMPREPLY=() + . "${KAZ_ROOT}/bin/.commonFunctions.sh" + setKazVars + + local cword=${COMP_CWORD} cur=${COMP_WORDS[COMP_CWORD]} card=${#COMP_WORDS[@]} i w skip=0 + for ((i=1 ; i/dev/null) + local used=$("${KAZ_BIN_DIR}/kazDockerNet.sh" "list" | grep "name:" | sed -e "s%\bname:\s*%%" -e "s%\bbridge\b\s*%%" -e "s%Net\b%%g") + local proposal item + for item in ${available_args} ; do + [[ " ${names} " =~ " ${item} " ]] || [[ " ${used} " =~ " ${item} " ]] || proposal="${proposal} ${item}" + done + COMPREPLY=($(compgen -W "${proposal}" -- "${cur}")) + ;; + esac + ;; + esac + return 0 +} +complete -F _kazDockerNet_completion kazDockerNet.sh diff --git a/bin/.kazList-completion.bash b/bin/.kazList-completion.bash new file mode 100755 index 0000000..ffc1530 --- /dev/null +++ b/bin/.kazList-completion.bash @@ -0,0 +1,83 @@ +#/usr/bin/env bash + +_kazList_completions () { + #KAZ_ROOT=$(cd "$(dirname ${COMP_WORDS[0]})"/..; pwd) + COMPREPLY=() + local cword=${COMP_CWORD} cur=${COMP_WORDS[COMP_CWORD]} card=${#COMP_WORDS[@]} i w skip=0 + for ((i=1 ; i> /dev/pts/1) + + case "${cur}" in + -*) + COMPREPLY=($(compgen -W "-h --help" -- "${cur}")) + ;; + *) + local cmd_available="compose service" + local opt_available="available validate enable disable status" + case "${arg_pos}" in + 1) + # $1 of kazList.sh + COMPREPLY=($(compgen -W "${cmd_available}" -- "${cur}")) + ;; + 2) + # $2 of kazList.sh + COMPREPLY=($(compgen -W "${opt_available}" -- "${cur}")) + ;; + *) + # $3-* of kazList.sh + [[ " ${cmd_available} " =~ " ${cmd} " ]] || return 0 + # select set of names + local names_set="${opt}" + local available_args + case "${cmd}" in + service) + case "${names_set}" in + available|validate) + return 0 + ;; + *) + available_args=$("${COMP_WORDS[0]}" "compose" "enable" "orga" 2>/dev/null) + ;; + esac + ;; + compose) + case "${names_set}" in + validate|enable|disable) + ;; + *) + names_set="available" + ;; + esac + available_args=$("${COMP_WORDS[0]}" "${cmd}" "${names_set}") + ;; + esac + # remove previous selected target + local proposal item + for item in ${available_args} ; do + [[ " ${names} " =~ " ${item} " ]] || proposal="${proposal} ${item}" + done + COMPREPLY=($(compgen -W "${proposal}" -- "${cur}")) + ;; + esac + esac + return 0 +} + +complete -F _kazList_completions kazList.sh diff --git a/bin/.mvOrga2Nas-completion.bash b/bin/.mvOrga2Nas-completion.bash new file mode 100755 index 0000000..d961e25 --- /dev/null +++ b/bin/.mvOrga2Nas-completion.bash @@ -0,0 +1,39 @@ +#/usr/bin/env bash + +_mv_orga_nas_completion () { + KAZ_ROOT=$(cd "$(dirname ${COMP_WORDS[0]})"/..; pwd) + COMPREPLY=() + . "${KAZ_ROOT}/bin/.commonFunctions.sh" + setKazVars + local cword=${COMP_CWORD} cur=${COMP_WORDS[COMP_CWORD]} card=${#COMP_WORDS[@]} i w skip=0 + for ((i=1 ; i/dev/null | sed "s/-orga\b//g") + local proposal= item + for item in ${available_orga} ; do + [[ " ${names} " =~ " ${item} " ]] || proposal="${proposal} ${item}" + done + COMPREPLY=($(compgen -W "${proposal}" -- "${cur}")) + ;; + esac + return 0 +} +complete -F _mv_orga_nas_completion mvOrga2Nas.sh diff --git a/bin/.orga-gen-completion.bash b/bin/.orga-gen-completion.bash new file mode 100755 index 0000000..9d611bb --- /dev/null +++ b/bin/.orga-gen-completion.bash @@ -0,0 +1,63 @@ +#/usr/bin/env bash + +_orga_gen_completion () { + KAZ_ROOT=$(cd "$(dirname ${COMP_WORDS[0]})"/../..; pwd) + ORGA_DIR=$(cd "$(dirname ${COMP_WORDS[0]})"; basename $(pwd)) + COMPREPLY=() + . "${KAZ_ROOT}/bin/.commonFunctions.sh" + setKazVars + local cword=${COMP_CWORD} cur=${COMP_WORDS[COMP_CWORD]} card=${#COMP_WORDS[@]} i w skip=0 + for ((i=1 ; i/dev/null | tr ' ' '\n' | sed "s/\(..*\)/-\1/") + for item in ${available_services} ; do + [[ " ${rmOpt} " =~ " ${item} " ]] || proposal="${proposal} ${item}" + done + COMPREPLY=( $(compgen -W "${proposal}" -- "${cur}" ) ) + ;; + '+'*) + local available_services item proposal= listOpt="available" + [ -n "${names}" ] && listOpt="disable ${names}" + [[ "${ORGA_DIR}" = "orgaTmpl" ]] || listOpt="disable ${ORGA_DIR%-orga}" + available_services=$("${KAZ_LIST}" service ${listOpt} 2>/dev/null | tr ' ' '\n' | sed "s/\(..*\)/+\1/") + for item in ${available_services} ; do + [[ " ${addOpt} " =~ " ${item} " ]] || proposal="${proposal} ${item}" + done + COMPREPLY=( $(compgen -W "${proposal}" -- "${cur}" ) ) + ;; + *) + [[ "${ORGA_DIR}" = "orgaTmpl" ]] || return 0; + local available_orga=$("${KAZ_LIST}" "compose" "enable" "orga" 2>/dev/null | sed "s/-orga\b//g") + local proposal= item + for item in ${available_orga} ; do + [[ " ${names} " =~ " ${item} " ]] || proposal="${proposal} ${item}" + done + COMPREPLY=($(compgen -W "${proposal}" -- "${cur}")) + ;; + esac + return 0 +} +complete -F _orga_gen_completion orga-gen.sh diff --git a/bin/.updateLook-completion.bash b/bin/.updateLook-completion.bash new file mode 100755 index 0000000..ef0bfcd --- /dev/null +++ b/bin/.updateLook-completion.bash @@ -0,0 +1,11 @@ +#/usr/bin/env bash + +_update_look_nas_completion () { + COMPREPLY=() + local cur=${COMP_WORDS[COMP_CWORD]} + local THEMES=$(cd "$(dirname ${COMP_WORDS[0]})"/look ; ls -F -- '.' | grep '/$' | sed 's%/%%' | tr '\n' ' ' | sed 's% $%%') + + COMPREPLY=($(compgen -W "${THEMES}" -- "${cur}")) + return 0 +} +complete -F _update_look_nas_completion updateLook.sh diff --git a/bin/applyTemplate.sh b/bin/applyTemplate.sh new file mode 100755 index 0000000..9a7f145 --- /dev/null +++ b/bin/applyTemplate.sh @@ -0,0 +1,102 @@ +#!/bin/bash + +# Met à jour la configuration de ${CONF} en fonction du modèle ${TMPL} +# Viariables misent à jour : +# - __DOMAIN__ +# Il est possible de prendre en considération ou d'occulter des blocks. +# Le début du block est repéré par une ligne contenant {{XXX +# La fin du block est repéré par une ligne contenant }} +# L'affiche est fonction de XXX +# XXX = on => affichage systématique +# XXX = off => masquage systématique +# XXX = compose => affichage si la variable d'environnement proxy_compose à la valeur on + +KAZ_ROOT=$(cd "$(dirname $0)/.."; pwd) +. "${KAZ_ROOT}/bin/.commonFunctions.sh" +setKazVars + +. "${DOCKERS_ENV}" +. "${KAZ_KEY_DIR}/SetAllPass.sh" + +usage () { + echo $(basename "$0") " [-h] [-help] [-timestamp] template dst" + echo " -h" + echo " -help Display this help." + echo " -timestamp produce timestamp comment." +} + +TIMESTAMP="" + +case "$1" in + '-h' | '-help' ) + usage + shift + exit;; + '-time' | '-timestamp' ) + TIMESTAMP=YES + shift;; +esac + +# no more export in .env +PROXY_VARS=$(set | grep "proxy_.*=") +for var in ${PROXY_VARS} +do + export ${var} +done + +( + # $1 = template + # $2 = target + if [ "${TIMESTAMP}" == "YES" ]; then + echo "# Generated by $(pwd)$(basename $0)" + echo "# à partir du modèle $1" + echo "#" $(date "+%x %X") + echo + fi + + sed \ + -e "/^[ \t]*$/d"\ + -e "/^[ ]*#.*$/d"\ + -e "s|__CACHET_HOST__|${cachetHost}|g"\ + -e "s|__CALC_HOST__|${calcHost}|g"\ + -e "s|__CLOUD_HOST__|${cloudHost}|g"\ + -e "s|__DATE_HOST__|${dateHost}|g"\ + -e "s|__DOKUWIKI_HOST__|${dokuwikiHost}|g"\ + -e "s|__DOMAIN__|${domain}|g"\ + -e "s|__FILE_HOST__|${fileHost}|g"\ + -e "s|__PAHEKO_API_PASSWORD__|${paheko_API_PASSWORD}|g"\ + -e "s|__PAHEKO_API_USER__|${paheko_API_USER}|g"\ + -e "s|__PAHEKO_HOST__|${pahekoHost}|g"\ + -e "s|__GIT_HOST__|${gitHost}|g"\ + -e "s|__GRAV_HOST__|${gravHost}|g"\ + -e "s|__HTTP_PROTO__|${httpProto}|g"\ + -e "s|__LDAP_HOST__|${ldapHost}|g"\ + -e "s|__LDAPUI_HOST__|${ldapUIHost}|g"\ + -e "s|__MATTER_HOST__|${matterHost}|g"\ + -e "s|__OFFICE_HOST__|${officeHost}|g"\ + -e "s|__PAD_HOST__|${padHost}|g"\ + -e "s|__QUOTAS_HOST__|${quotasHost}|g"\ + -e "s|__SMTP_HOST__|${smtpHost}|g"\ + -e "s|__SYMPADB__|${sympaDBName}|g"\ + -e "s|__SYMPA_HOST__|${sympaHost}|g"\ + -e "s|__SYMPA_MYSQL_DATABASE__|${sympa_MYSQL_DATABASE}|g"\ + -e "s|__SYMPA_MYSQL_PASSWORD__|${sympa_MYSQL_PASSWORD}|g"\ + -e "s|__SYMPA_MYSQL_USER__|${sympa_MYSQL_USER}|g"\ + -e "s|__VIGILO_HOST__|${vigiloHost}|g"\ + -e "s|__WEBMAIL_HOST__|${webmailHost}|g"\ + -e "s|__CASTOPOD_HOST__|${castopodHost}|g"\ + -e "s|__IMAPSYNC_HOST__|${imapsyncHost}|g"\ + -e "s|__YAKFORMS_HOST__|${yakformsHost}|g"\ + -e "s|__WORDPRESS_HOST__|${wordpressHost}|g"\ + -e "s|__MOBILIZON_HOST__|${mobilizonHost}|g"\ + -e "s|__API_HOST__|${apiHost}|g"\ + -e "s|__VAULTWARDEN_HOST__|${vaultwardenHost}|g"\ + -e "s|__DOMAIN_SYMPA__|${domain_sympa}|g"\ + $1 | awk ' + BEGIN {cp=1} + /}}/ {cp=1 ; next}; + /{{on/ {cp=1; next}; + /{{off/ {cp=0; next}; + match($0, /{{[a-zA-Z0-9_]+/) {cp=(ENVIRON["proxy_" substr($0,RSTART+2,RLENGTH)] == "on"); next}; + {if (cp) print $0};' +) > $2 diff --git a/bin/checkEnvFiles.sh b/bin/checkEnvFiles.sh new file mode 100755 index 0000000..382d390 --- /dev/null +++ b/bin/checkEnvFiles.sh @@ -0,0 +1,315 @@ +#!/bin/bash + +export KAZ_ROOT=$(cd "$(dirname $0)/.."; pwd) +. "${KAZ_ROOT}/bin/.commonFunctions.sh" +setKazVars + +RUN_PASS_DIR="secret" +TMPL_PASS_DIR="secret.tmpl" +RUN_PASS_FILE="${RUN_PASS_DIR}/SetAllPass.sh" +TMPL_PASS_FILE="${TMPL_PASS_DIR}/SetAllPass.sh" +NEED_GEN= + +######################################## + +usage () { + echo "Usage: $0 [-n] [-h]" + echo " -h help" + exit 1 +} + +case "$1" in + '-h' | '-help' ) + usage + ;; +esac + +[ "$#" -eq 0 ] || usage + +######################################## +# check system + +for prg in kompare; do + if ! type "${prg}" > /dev/null; then + printKazError "$0 need ${prg}" + echo "please run \"apt-get install ${prg}\"" + exit + fi +done + +cd "${KAZ_ROOT}" +######################################## +# get lvalues in script +getVars () { + # $1 : filename + grep "^[^#]*=" $1 | sed 's/\([^=]*\).*/\1/' | sort -u +} + +# get lvalues in script +getSettedVars () { + # $1 : filename + grep "^[^#]*=..*" $1 | grep -v '^[^#]*=".*--clean_val--.*"' | grep -v '^[^#]*="${' | sort -u +} + +getVarFormVal () { + # $1 searched value + # $2 filename + grep "^[^#]*=$1" $2 | sed 's/\s*\([^=]*\).*/\1/' +} + +######################################## +# synchronized SetAllPass.sh (find missing lvalues) +updatePassFile () { + # $1 : ref filename + # $2 : target filename + + REF_FILE="$1" + TARGET_FILE="$2" + NEED_UPDATE= + while : ; do + declare -a listRef listTarget missing + listRef=($(getVars "${REF_FILE}")) + listTarget=($(getVars "${TARGET_FILE}")) + missing=($(comm -23 <(printf "%s\n" ${listRef[@]}) <(printf "%s\n" ${listTarget[@]}))) + if [ -n "${missing}" ]; then + echo "missing vars in ${YELLOW}${BOLD}${TARGET_FILE}${NC}:${RED}${BOLD}" ${missing[@]} "${NC}" + read -p "Do you want to add them? [y/n]: " yn + case $yn in + ""|[Yy]*) + emacs "${REF_FILE}" "${TARGET_FILE}" + NEED_UPDATE=true + break + ;; + [Nn]*) + break + ;; + esac + else + break + fi + done +} + +updatePassFile "${TMPL_PASS_FILE}" "${RUN_PASS_FILE}" +[ -n "${NEED_UPDATE}" ] && NEED_GEN=true +updatePassFile "${RUN_PASS_FILE}" "${TMPL_PASS_FILE}" + +######################################## +# check empty pass in TMPL_PASS_FILE +declare -a settedVars +settedVars=($(getSettedVars "${TMPL_PASS_FILE}")) +if [ -n "${settedVars}" ]; then + echo "unclear password in ${YELLOW}${BOLD}${TMPL_PASS_FILE}${NC}:${BLUE}${BOLD}" + for var in ${settedVars[@]}; do + echo -e "\t${var}" + done + echo "${NC}" + read -p "Do you want to clear them? [y/n]: " yn + case $yn in + ""|[Yy]*) + emacs "${TMPL_PASS_FILE}" + ;; + esac +fi + +######################################## +# check new files env-* +createMissingEnv () { + # $1 : ref dir + # $2 : target dir + REF_DIR="$1" + TARGET_DIR="$2" + NEED_UPDATE= + + declare -a listRef listTarget missing + listRef=($(cd "${REF_DIR}"; ls -1 env-* | grep -v '~$')) + listTarget=($(cd "${TARGET_DIR}"; ls -1 env-* | grep -v '~$')) + missing=($(comm -23 <(printf "%s\n" ${listRef[@]}) <(printf "%s\n" ${listTarget[@]}))) + for envFile in ${missing[@]}; do + read -p "Do you want to create ${GREEN}${BOLD}${TARGET_DIR}/${envFile}${NC}? [y/n]: " yn + case $yn in + ""|[Yy]*) + cp "${REF_DIR}/${envFile}" "${TARGET_DIR}/${envFile}" + NEED_UPDATE=true + ;; + esac + done +} + +createMissingEnv "${RUN_PASS_DIR}" "${TMPL_PASS_DIR}" +[ -n "${NEED_UPDATE}" ] && NEED_GEN=true +createMissingEnv "${TMPL_PASS_DIR}" "${RUN_PASS_DIR}" +[ -n "${NEED_UPDATE}" ] && NEED_GEN=true + +######################################## +# check missing values in env-* between RUN and TMPL +declare -a listTmpl listRun listCommonFiles +listTmplFiles=($(cd "${TMPL_PASS_DIR}"; ls -1 env-* | grep -v '~$')) +listRunFiles=($(cd "${RUN_PASS_DIR}"; ls -1 env-* | grep -v '~$')) +listCommonFiles=($(comm -3 <(printf "%s\n" ${listTmplFiles[@]}) <(printf "%s\n" ${listRunFiles[@]}))) +for envFile in ${listCommonFiles[@]}; do + while : ; do + TMPL_FILE="${TMPL_PASS_DIR}/${envFile}" + RUN_FILE="${RUN_PASS_DIR}/${envFile}" + declare -a listRef list2Target missingInRun missingInTmpl + listTmplVars=($(getVars "${TMPL_FILE}")) + listRunVars=($(getVars "${RUN_FILE}")) + missingInTmpl=($(comm -23 <(printf "%s\n" ${listTmplVars[@]}) <(printf "%s\n" ${listRunVars[@]}))) + missingInRun=($(comm -13 <(printf "%s\n" ${listTmplVars[@]}) <(printf "%s\n" ${listRunVars[@]}))) + if [ -n "${missingInTmpl}" ] || [ -n "${missingInRun}" ]; then + [ -n "${missingInTmpl}" ] && + echo "missing vars in ${YELLOW}${BOLD}${TMPL_FILE}${NC}:${RED}${BOLD}" ${missingInTmpl[@]} "${NC}" + [ -n "${missingInRun}" ] && + echo "missing vars in ${YELLOW}${BOLD}${RUN_FILE}${NC}:${RED}${BOLD}" ${missingInRun[@]} "${NC}" + read -p "Do you want to add them? [y/n]: " yn + case $yn in + ""|[Yy]*) + emacs "${TMPL_FILE}" "${RUN_FILE}" + [ -n "${missingInTmpl}" ] && NEED_GEN=true + break + ;; + [Nn]*) + break + ;; + esac + else + break + fi + done +done + +######################################## +# check empty pass in env-* +for envFile in $(ls -1 "${TMPL_PASS_DIR}/"env-* | grep -v '~$'); do + settedVars=($(getSettedVars "${envFile}")) + if [ -n "${settedVars}" ]; then + echo "unclear password in ${GREEN}${BOLD}${envFile}${NC}:${BLUE}${BOLD}" + for var in ${settedVars[@]}; do + echo -e "\t${var}" + done + echo "${NC}" + read -p "Do you want to clear them? [y/n]: " yn + case $yn in + ""|[Yy]*) + emacs "${envFile}" + ;; + esac + fi +done + +######################################## +# check extention in dockers.env +declare -a missing +missing=($(for DIR in "${RUN_PASS_DIR}" "${TMPL_PASS_DIR}"; do + for envFile in $(ls -1 "${DIR}/"env-* | grep -v '~$'); do + val="${envFile#*env-}" + varName=$(getVarFormVal "${val}" "${DOCKERS_ENV}") + if [ -z "${varName}" ]; then + echo "${val}" + fi + done + done | sort -u)) +if [ -n "${missing}" ]; then + echo "missing def in ${GREEN}${BOLD}${DOCKERS_ENV}${NC}:${BLUE}${BOLD}" + for var in ${missing[@]}; do + echo -e "\t${var}" + done + echo "${NC}" + read -p "Do you want to add them? [y/n]: " yn + case $yn in + ""|[Yy]*) + emacs "${DOCKERS_ENV}" + ;; + esac +fi + +######################################## +# check env-* in updateDockerPassword.sh +missing=($(for DIR in "${RUN_PASS_DIR}" "${TMPL_PASS_DIR}"; do + for envFile in $(ls -1 "${DIR}/"env-* | grep -v '~$'); do + val="${envFile#*env-}" + varName=$(getVarFormVal "${val}" "${DOCKERS_ENV}") + [ -z "${varName}" ] && continue + prefixe=$(grep "^\s*updateEnv.*${varName}" "${KAZ_BIN_DIR}/updateDockerPassword.sh" | + sed 's/\s*updateEnv[^"]*"\([^"]*\)".*/\1/' | sort -u) + if [ -z "${prefixe}" ]; then + echo "${envFile#*/}_(\${KAZ_KEY_DIR}/env-\${"${varName}"})" + fi + done + done | sort -u)) +if [ -n "${missing}" ]; then + echo "missing update in ${GREEN}${BOLD}${KAZ_BIN_DIR}/updateDockerPassword.sh${NC}:${BLUE}${BOLD}" + for var in ${missing[@]}; do + echo -e "\t${var}" + done + echo "${NC}" + read -p "Do you want to add them? [y/n]: " yn + case $yn in + ""|[Yy]*) + emacs "${KAZ_BIN_DIR}/updateDockerPassword.sh" + ;; + esac +fi + +######################################## +# synchronized SetAllPass.sh and env-* +updateEnvFiles () { + # $1 secret dir + DIR=$1 + listRef=($(getVars "${DIR}/SetAllPass.sh")) + missing=($(for envFile in $(ls -1 "${DIR}/"env-* | grep -v '~$'); do + val="${envFile#*env-}" + varName=$(getVarFormVal "${val}" "${DOCKERS_ENV}") + [ -z "${varName}" ] && continue + prefixe=$(grep "^\s*updateEnv.*${varName}" "${KAZ_BIN_DIR}/updateDockerPassword.sh" | + sed 's/\s*updateEnv[^"]*"\([^"]*\)".*/\1/' | sort -u) + [ -z "${prefixe}" ] && continue + listVarsInEnv=($(getVars "${envFile}")) + for var in ${listVarsInEnv[@]}; do + [[ ! " ${listRef[@]} " =~ " ${prefixe}_${var} " ]] && echo "${prefixe}_${var}" + done + # XXX doit exister dans SetAllPass.sh avec le prefixe + done)) + if [ -n "${missing}" ]; then + echo "missing update in ${GREEN}${BOLD}${DIR}/SetAllPass.sh${NC}:${BLUE}${BOLD}" + for var in ${missing[@]}; do + echo -e "\t${var}" + done + echo "${NC}" + read -p "Do you want to add them? [y/n]: " yn + case $yn in + ""|[Yy]*) + emacs "${DIR}/SetAllPass.sh" + ;; + esac + fi +} + +updateEnvFiles "${RUN_PASS_DIR}" +updateEnvFiles "${TMPL_PASS_DIR}" + +# XXX chercher les variables non utilisées dans les SetAllPass.sh + +if [ -n "${NEED_GEN}" ]; then + while : ; do + read -p "Do you want to generate blank values? [y/n]: " yn + case $yn in + ""|[Yy]*) + "${KAZ_BIN_DIR}/secretGen.sh" + break + ;; + [Nn]*) + break + ;; + esac + done +fi + + +# XXX config/dockers.tmpl.env + + +# XXX ! vérifier init pour dockers.env + + + diff --git a/bin/checkEnvPassword.sh b/bin/checkEnvPassword.sh new file mode 100755 index 0000000..0a3f5f5 --- /dev/null +++ b/bin/checkEnvPassword.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +KAZ_ROOT=$(cd $(dirname $0)/..; pwd) +. "${KAZ_ROOT}/bin/.commonFunctions.sh" +setKazVars + +for filename in "${KAZ_KEY_DIR}/"env-*Serv "${KAZ_KEY_DIR}/"env-*DB; do + if grep -q "^[^#=]*=\s*$" "${filename}" 2>/dev/null; then + echo "${filename}" + fi +done diff --git a/bin/configKaz.sh b/bin/configKaz.sh new file mode 100755 index 0000000..0668a4d --- /dev/null +++ b/bin/configKaz.sh @@ -0,0 +1,24 @@ +#!/bin/sh -e + +. /usr/share/debconf/confmodule +db_version 2.0 + +if [ "$1" = "fset" ]; then + db_fset kaz/mode seen false + db_fset kaz/domain seen false + db_go +fi +if [ "$1" = "reset" ]; then + db_reset kaz/mode + db_reset kaz/domain + db_go +fi + +#db_set kaz/domain test + + +db_title "a b c" + +db_input critical kaz/mode +db_input critical kaz/domain +db_go diff --git a/bin/configKaz.sh.templates b/bin/configKaz.sh.templates new file mode 100755 index 0000000..ec5a07c --- /dev/null +++ b/bin/configKaz.sh.templates @@ -0,0 +1,11 @@ +Template: kaz/mode +Type: select +Choices: prod, dev, local +Default: local +Description: Mode + +Template: kaz/domain +Type: string +Description: domain name +Default: kaz.bzh + diff --git a/bin/container.sh b/bin/container.sh new file mode 100755 index 0000000..dac3d2c --- /dev/null +++ b/bin/container.sh @@ -0,0 +1,319 @@ +#!/bin/bash + +# En cas d'absence de postfix, il faut lancer : +# docker network create postfix_mailNet + +# démare/arrête un compose +# sauvegarde la base de données d'un compose +# met à jours les paramètres de configuration du mandataire (proxy) + +KAZ_ROOT=$(cd "$(dirname $0)/.."; pwd) +. "${KAZ_ROOT}/bin/.commonFunctions.sh" +setKazVars + +cd "${KAZ_BIN_DIR}" +PATH_SAUVE="/home/sauve/" +export SIMU="" + +declare -a availableComposesNoNeedMail availableMailComposes availableComposesNeedMail availableProxyComposes availableOrga +availableComposesNoNeedMail=($(getList "${KAZ_CONF_DIR}/container-withoutMail.list")) +availableMailComposes=($(getList "${KAZ_CONF_DIR}/container-mail.list")) +availableComposesNeedMail=($(getList "${KAZ_CONF_DIR}/container-withMail.list")) +availableProxyComposes=($(getList "${KAZ_CONF_DIR}/container-proxy.list")) +availableOrga=($(getList "${KAZ_CONF_DIR}/container-orga.list")) +availableComposesNeedMail+=( "${availableOrga[@]}" ) + +knownedComposes+=( ${availableMailComposes[@]} ) +knownedComposes+=( ${availableProxyComposes[@]} ) +knownedComposes+=( ${availableComposesNoNeedMail[@]} ) +knownedComposes+=( ${availableComposesNeedMail[@]} ) + +usage () { + echo "Usage: $0 [-n] {status|start|stop|save} [compose]..." + echo " -n : simulation" + echo " status : docker-compose status (default all compose available)" + echo " start : start composes (default all compose validate)" + echo " stop : stop composes (default all compose enable)" + echo " save : save all known database" + echo " [compose] : in ${knownedComposes[@]}" + exit 1 +} + +doCompose () { + # $1 dans ("up -d" "down") + # $2 nom du répertoire du compose + echo "compose: $1 $2" + ${SIMU} cd "${KAZ_COMP_DIR}/$2" + if [ ! -h .env ] ; then + echo "create .env in $2" + ${SIMU} ln -fs ../../config/dockers.env .env + fi + ${SIMU} docker-compose $1 + + if [ "$2" = "cachet" ] && [ "$1" != "down" ]; then + NEW_KEY=$(cd "${KAZ_COMP_DIR}/$2" ; docker-compose logs | grep APP_KEY=base64: | sed "s/^.*'APP_KEY=\(base64:[^']*\)'.*$/\1/" | tail -1) + if [ -n "${NEW_KEY}" ]; then + printKazMsg "cachet key change" + # change key + ${SIMU} sed -i \ + -e 's%^\(\s*cachet_APP_KEY=\).*$%\1"'"${NEW_KEY}"'"%' \ + "${KAZ_KEY_DIR}/SetAllPass.sh" + ${SIMU} "${KAZ_BIN_DIR}/secretGen.sh" + # restart + ${SIMU} docker-compose $1 + fi + fi +} + +doComposes () { + # $1 dans ("up -d" "down") + # $2+ nom des répertoires des composes + cmd=$1 + shift + for compose in $@ ; do + doCompose "${cmd}" ${compose} + done +} + +updateProxy () { + # $1 dans ("on" "off") + # $2 nom des répertoires des composes + cmd=$1 + shift + echo "update proxy ${cmd}: $@" + date=$(date "+%x %X") + for compose in $@ ; do + composeFlag=${compose//-/_} + entry="proxy_${composeFlag}=" + newline="${entry}${cmd} # update by $(basename $0) at ${date}" + if ! grep -q "proxy_${composeFlag}=" "${DOCKERS_ENV}" 2> /dev/null ; then + if [[ -n "${SIMU}" ]] ; then + echo "${newline} >> ${DOCKERS_ENV}" + else + echo "${newline}" >> "${DOCKERS_ENV}" + fi + else + ${SIMU} sed -i \ + -e "s|${entry}.*|${newline}|g" \ + "${DOCKERS_ENV}" + fi + done + for item in "${availableProxyComposes[@]}"; do + ${SIMU} ${KAZ_COMP_DIR}/${item}/proxy-gen.sh + done +} + +saveDB () { + #attention, soucis avec l'option "-ti" qui ne semble pas rendre la main avec docker exec + + containerName=$1 + userName=$2 + userPass=$3 + dbName=$4 + backName=$5 + if [[ -n "${SIMU}" ]] ; then + ${SIMU} "docker exec ${containerName} mysqldump --user=${userName} --password=${userPass} ${dbName} | gzip > $PATH_SAUVE${backName}.sql.gz" + else + docker exec ${containerName} mysqldump --user=${userName} --password=${userPass} ${dbName} | gzip > $PATH_SAUVE${backName}.sql.gz + fi +} + +declare -a enableComposesNoNeedMail enableMailComposes enableComposesNeedMail enableProxyComposes + +enableComposesNoNeedMail=() +enableMailComposes=() +enableComposesNeedMail=() +enableProxyComposes=() + +startComposes () { + ./kazDockerNet.sh add ${enableComposesNoNeedMail[@]} ${enableProxyComposes[@]} ${enableMailComposes[@]} ${enableComposesNeedMail[@]} + [ ${#enableComposesNeedMail[@]} -ne 0 ] && [[ ! "${enableMailComposes[@]}" =~ "postfix" ]] && ./kazDockerNet.sh add postfix + [[ "${enableComposesNeedMail[@]}" =~ "paheko" ]] && ${SIMU} ${KAZ_COMP_DIR}/paheko/paheko-gen.sh + doComposes "up -d" ${enableComposesNoNeedMail[@]} + doComposes "up -d" ${enableMailComposes[@]} + doComposes "up -d" ${enableComposesNeedMail[@]} + updateProxy "on" ${enableComposesNoNeedMail[@]} ${enableComposesNeedMail[@]} + doComposes "up -d" ${enableProxyComposes[@]} + for item in "${enableProxyComposes[@]}"; do + ${SIMU} ${KAZ_COMP_DIR}/${item}/reload.sh + done + if grep -q "^.s*proxy_web.s*=.s*on" "${DOCKERS_ENV}" 2> /dev/null ; then + ${SIMU} ${KAZ_COMP_DIR}/web/web-gen.sh + fi +} + +stopComposes () { + updateProxy "off" ${enableComposesNoNeedMail[@]} ${enableComposesNeedMail[@]} + doComposes "down" ${enableProxyComposes[@]} + doComposes "down" ${enableComposesNeedMail[@]} + doComposes "down" ${enableMailComposes[@]} + doComposes "down" ${enableComposesNoNeedMail[@]} + if grep -q "^.s*proxy_web.s*=.s*on" "${DOCKERS_ENV}" 2> /dev/null ; then + ${SIMU} ${KAZ_COMP_DIR}/web/web-gen.sh + fi +} + +statusComposes () { + ${KAZ_ROOT}/bin/kazList.sh compose status ${enableMailComposes[@]} ${enableProxyComposes[@]} ${enableComposesNoNeedMail[@]} ${enableComposesNeedMail[@]} +} + +saveComposes () { + . "${DOCKERS_ENV}" + . "${KAZ_ROOT}/secret/SetAllPass.sh" + + savedComposes+=( ${enableMailComposes[@]} ) + savedComposes+=( ${enableProxyComposes[@]} ) + savedComposes+=( ${enableComposesNoNeedMail[@]} ) + savedComposes+=( ${enableComposesNeedMail[@]} ) + + for compose in ${savedComposes[@]} + do + case "${compose}" in + jirafeau) + # rien à faire (fichiers) + ;; + ethercalc) + #inutile car le backup de /var/lib/docker/volumes/ethercalc_calcDB/_data/dump.rdb est suffisant + ;; + #grav) + # ??? + #;; + #postfix) + sympa) + echo "save sympa" + saveDB ${sympaDBName} "${sympa_MYSQL_USER}" "${sympa_MYSQL_PASSWORD}" "${sympa_MYSQL_DATABASE}" sympa + ;; + web) + # rien à faire (fichiers) + ;; + etherpad) + echo "save pad" + saveDB ${etherpadDBName} "${etherpad_MYSQL_USER}" "${etherpad_MYSQL_PASSWORD}" "${etherpad_MYSQL_DATABASE}" etherpad + ;; + framadate) + echo "save date" + saveDB ${framadateDBName} "${framadate_MYSQL_USER}" "${framadate_MYSQL_PASSWORD}" "${framadate_MYSQL_DATABASE}" framadate + ;; + cloud) + echo "save cloud" + saveDB ${nextcloudDBName} "${nextcloud_MYSQL_USER}" "${nextcloud_MYSQL_PASSWORD}" "${nextcloud_MYSQL_DATABASE}" nextcloud + ;; + paheko) + # rien à faire (fichiers) + ;; + mattermost) + echo "save mattermost" + saveDB ${mattermostDBName} "${mattermost_MYSQL_USER}" "${mattermost_MYSQL_PASSWORD}" "${mattermost_MYSQL_DATABASE}" mattermost + ;; + dokuwiki) + # rien à faire (fichiers) + ;; + *-orga) + ORGA=${compose%-orga} + echo "save ${ORGA}" + if grep -q "cloud:" "${KAZ_COMP_DIR}/${compose}/docker-compose.yml" 2> /dev/null ; then + echo " => cloud" + saveDB "${ORGA}-DB" "${nextcloud_MYSQL_USER}" "${nextcloud_MYSQL_PASSWORD}" "${nextcloud_MYSQL_DATABASE}" "${ORGA}-cloud" + fi + if grep -q "agora:" "${KAZ_COMP_DIR}/${compose}/docker-compose.yml" 2> /dev/null ; then + echo " => mattermost" + saveDB "${ORGA}-DB" "${mattermost_MYSQL_USER}" "${mattermost_MYSQL_PASSWORD}" "${mattermost_MYSQL_DATABASE}" "${ORGA}-mattermost" + fi + if grep -q "wordpress:" "${KAZ_COMP_DIR}/${compose}/docker-compose.yml" 2> /dev/null ; then + echo " => wordpress" + saveDB "${ORGA}-DB" "${wp_MYSQL_USER}" "${wp_MYSQL_PASSWORD}" "${wp_MYSQL_DATABASE}" "${ORGA}-wordpress" + fi + ;; + esac + done +} + +if [ "$#" -eq 0 ] ; then + usage +fi + +if [ "$1" == "-h" ] ; then + usage + shift +fi + +if [ "$1" == "-n" ] ; then + export SIMU=echo + shift +fi + +DCK_CMD="" +SAVE_CMD="" +case "$1" in + start) + DCK_CMD="startComposes" + shift + ;; + + stop) + DCK_CMD="stopComposes" + shift + ;; + + save) + SAVE_CMD="saveComposes" + shift + ;; + + status) + DCK_CMD="statusComposes" + shift + ;; + *) + usage + ;; +esac + +if [ $# -eq 0 ] ; then + enableComposesNoNeedMail=("${availableComposesNoNeedMail[@]}") + enableMailComposes=("${availableMailComposes[@]}") + enableComposesNeedMail=("${availableComposesNeedMail[@]}") + enableProxyComposes=("${availableProxyComposes[@]}") +else + if [ "${DCK_CMD}" = "startComposes" ] ; then + enableProxyComposes=("${availableProxyComposes[@]}") + fi +fi + +for compose in $* +do + compose=${compose%/} + if [[ ! " ${knownedComposes[@]} " =~ " ${compose} " ]]; then + declare -a subst + subst=() + for item in "${knownedComposes[@]}"; do + [[ "${item}" =~ "${compose}" ]] && subst+=(${item}) + done + if [ "${subst}" = "" ] ; then + echo + echo "Unknown compose: ${RED}${BOLD}${compose}${NC} not in ${YELLOW}${BOLD}${knownedComposes[*]}${NC}" + echo + exit 1 + else + echo "substitute compose: ${YELLOW}${BOLD}${compose} => ${subst[@]}${NC}" + fi + fi + for item in "${availableMailComposes[@]}"; do + [[ "${item}" =~ "${compose}" ]] && enableMailComposes+=("${item}") + done + for item in "${availableProxyComposes[@]}"; do + [[ "${item}" =~ "${compose}" ]] && enableProxyComposes=("${item}") + done + for item in "${availableComposesNoNeedMail[@]}"; do + [[ "${item}" =~ "${compose}" ]] && enableComposesNoNeedMail+=("${item}") + done + for item in "${availableComposesNeedMail[@]}"; do + [[ "${item}" =~ "${compose}" ]] && enableComposesNeedMail+=("${item}") + done +done + +[[ ! -z "${DCK_CMD}" ]] && "${DCK_CMD}" && exit 0 + +[[ ! -z "${SAVE_CMD}" ]] && "${SAVE_CMD}" && exit 0 + +exit 1 diff --git a/bin/createEmptyPasswd.sh b/bin/createEmptyPasswd.sh new file mode 100755 index 0000000..cb8e694 --- /dev/null +++ b/bin/createEmptyPasswd.sh @@ -0,0 +1,104 @@ +#!/bin/bash + +cd $(dirname $0)/.. + +mkdir -p emptySecret +rsync -aHAX --info=progress2 --delete secret/ emptySecret/ + +cd emptySecret/ + +. ../config/dockers.env +. ./SetAllPass.sh + +# pour mise au point +# SIMU=echo + +cleanEnvDB(){ + # $1 = prefix + # $2 = envName + # $3 = containerName of DB + rootPass="--root_password--" + dbName="--database_name--" + userName="--user_name--" + userPass="--user_password--" + + ${SIMU} sed -i \ + -e "s/MYSQL_ROOT_PASSWORD=.*/MYSQL_ROOT_PASSWORD=${rootPass}/g" \ + -e "s/MYSQL_DATABASE=.*/MYSQL_DATABASE=${dbName}/g" \ + -e "s/MYSQL_USER=.*/MYSQL_USER=${userName}/g" \ + -e "s/MYSQL_PASSWORD=.*/MYSQL_PASSWORD=${userPass}/g" \ + "$2" +} + +cleanEnv(){ + # $1 = prefix + # $2 = envName + for varName in $(grep "^[a-zA-Z_]*=" $2 | sed "s/^\([^=]*\)=.*/\1/g") + do + srcName="$1_${varName}" + srcVal="--clean_val--" + ${SIMU} sed -i \ + -e "s~^[ ]*${varName}=.*$~${varName}=${srcVal}~" \ + "$2" + done +} + +cleanPasswd(){ + ${SIMU} sed -i \ + -e 's/^\([# ]*[^#= ]*\)=".[^{][^"]*"/\1="--clean_val--"/g' \ + ./SetAllPass.sh +} + +#################### +# main + +# read -r -p "Do you want to remove all password? [Y/n] " input + +# case $input in +# [yY][eE][sS]|[yY]) +# echo "Remove all password" +# ;; +# [nN][oO]|[nN]) +# echo "Abort" +# ;; +# *) +# echo "Invalid input..." +# exit 1 +# ;; +# esac + +cleanPasswd + +cleanEnvDB "etherpad" "./env-${etherpadDBName}" "${etherpadDBName}" +cleanEnvDB "framadate" "./env-${framadateDBName}" "${framadateDBName}" +cleanEnvDB "git" "./env-${gitDBName}" "${gitDBName}" +cleanEnvDB "mattermost" "./env-${mattermostDBName}" "${mattermostDBName}" +cleanEnvDB "nextcloud" "./env-${nextcloudDBName}" "${nextcloudDBName}" +cleanEnvDB "roundcube" "./env-${roundcubeDBName}" "${roundcubeDBName}" +cleanEnvDB "sso" "./env-${ssoDBName}" "${ssoDBName}" +cleanEnvDB "sympa" "./env-${sympaDBName}" "${sympaDBName}" +cleanEnvDB "vigilo" "./env-${vigiloDBName}" "${vigiloDBName}" +cleanEnvDB "wp" "./env-${wordpressDBName}" "${wordpressDBName}" + +cleanEnv "etherpad" "./env-${etherpadServName}" +cleanEnv "gandi" "./env-gandi" +cleanEnv "jirafeau" "./env-${jirafeauServName}" +cleanEnv "mattermost" "./env-${mattermostServName}" +cleanEnv "nextcloud" "./env-${nextcloudServName}" +cleanEnv "office" "./env-${officeServName}" +cleanEnv "roundcube" "./env-${roundcubeServName}" +cleanEnv "sso" "./env-${ssoServName}" +cleanEnv "vigilo" "./env-${vigiloServName}" +cleanEnv "wp" "./env-${wordpressServName}" + +cat > allow_admin_ip < no orga + CREATE_ORGA= + shift;; + '-v' | '-V' ) + echo "${PRG}, root : ${KAZ_ROOT}, on domain : ${URL_SITE}" + exit + ;; + *) + usage + echo "${PRG}: ${RED}unknown parameter${NC}" + shift + exit;; + esac +done + +################################## +# Inventaire des comptes à créer # +################################## + +# la recherche des comptes à créé avec la commande : +# bin/interoPaheko.sh + +# création d'un fichier vide +# TODO : même code ici et dans interoPaheko.sh => risque de divergence +if [ ! -s "${FILE}" ];then + echo "${RED}" + echo "ERREUR : le fichier ${FILE} n'existait pas" + echo "Il vient d'être créé. Vous pouvez le compléter." + echo "${NC}" + cat > "${FILE}" < "${i}" && chmod +x "${i}" +done + +echo "numero,nom,quota_disque,action_auto" > "${TEMP_PAHEKO}" +echo "curl \"https://${paheko_API_USER}:${paheko_API_PASSWORD}@kaz-paheko.kaz.bzh/api/user/import\" -T \"${TEMP_PAHEKO}\"" >> "${CMD_PAHEKO}" + +#echo "récupération des login postfix... " +## on stocke les emails et les alias KAZ déjà créés +#( +# ${SETUP_MAIL} email list +# ${SETUP_MAIL} alias list +#) | cut -d ' ' -f 2 | grep @ | sort > "${TFILE_EMAIL}" +# did on supprime le ^M en fin de fichier pour pas faire planter les grep +#dos2unix "${TFILE_EMAIL}" + +echo "on récupère tous les emails (secours/alias/kaz) sur le ldap" +FILE_LDIF=/home/sauve/ldap.ldif +/kaz/bin/ldap/ldap_sauve.sh +gunzip ${FILE_LDIF}.gz -f +grep -aEiorh '([[:alnum:]]+([._-][[:alnum:]]+)*@[[:alnum:]]+([._-][[:alnum:]]+)*\.[[:alpha:]]{2,6})' ${FILE_LDIF} | sort -u > ${TFILE_EMAIL} + +echo "récupération des login mattermost... " +docker exec -ti mattermostServ bin/mmctl user list --all | grep ":.*(" | cut -d ':' -f 2 | cut -d ' ' -f 2 | sort > "${TFILE_MM}" + +dos2unix "${TFILE_MM}" +echo "done" + +# se connecter à l'agora pour ensuite pouvoir passer toutes les commandes mmctl +echo "docker exec -ti mattermostServ bin/mmctl auth login ${httpProto}://${URL_AGORA} --name local-server --username ${mattermost_user} --password ${mattermost_pass}" | tee -a "${CMD_INIT}" + +# vérif des emails +regex="^(([A-Za-z0-9]+((\.|\-|\_|\+)?[A-Za-z0-9]?)*[A-Za-z0-9]+)|[A-Za-z0-9]+)@(([A-Za-z0-9]+)+((\.|\-|\_)?([A-Za-z0-9]+)+)*)+\.([A-Za-z]{2,})+$" +function validator { + if ! [[ "$1" =~ ${regex} ]]; then + # printf "* %-48s \e[1;31m[fail]\e[m\n" "${1}" + ( + echo + echo "ERREUR : le paramètre ${RED}${BOLD}$1 n'est pas un email valide${NC} - on stoppe tout - aucun utilisateur de créé" + echo + ) | tee -a "${LOG}" + exit 1 + fi +} + +###################################### +# Boucle lecture des comptes à créer # +###################################### +echo -e "$(date '+%Y-%m-%d %H:%M:%S') : ${PRG} - sauvegarde des utilisateurs à créer" | tee "${LOG}" +cat "${FILE}" >> "${LOG}" + +LDAP_IP=$(docker inspect -f '{{.NetworkSettings.Networks.ldapNet.IPAddress}}' ldapServ) +ALL_ORGA= + +while read ligne; do + + # | xargs permet de faire un trim + NOM=$(awk -F ";" '{print $1}' <<< "${ligne}" | xargs) + PRENOM=$(awk -F ";" '{print $2}' <<< "${ligne}" | xargs) + + declare -A tab_email + tab_email[EMAIL_SOUHAITE]=$(awk -F ";" '{print $3}' <<< "${ligne}" | xargs) + tab_email[EMAIL_SECOURS]=$(awk -F ";" '{print $4}' <<< "${ligne}" | xargs) + + ORGA=$(awk -F ";" '{print $5}' <<< "${ligne}" | xargs) + ORGA=${ORGA,,} + + declare -A service + service[ADMIN_ORGA]=$(awk -F ";" '{print $6}' <<< "${ligne}" | xargs) + service[NC_ORGA]=$(awk -F ";" '{print $7}' <<< "${ligne}" | xargs) + service[PAHEKO_ORGA]=$(awk -F ";" '{print $8}' <<< "${ligne}" | xargs) + service[WP_ORGA]=$(awk -F ";" '{print $9}' <<< "${ligne}" | xargs) + service[AGORA_ORGA]=$(awk -F ";" '{print $10}' <<< "${ligne}" | xargs) + service[WIKI_ORGA]=$(awk -F ";" '{print $11}' <<< "${ligne}" | xargs) + service[NC_BASE]=$(awk -F ";" '{print $12}' <<< "${ligne}" | xargs) + + GROUPE_NC_BASE=$(awk -F ";" '{print $13}' <<< "${ligne}" | xargs) + GROUPE_NC_BASE="${GROUPE_NC_BASE,,}" + EQUIPE_AGORA=$(awk -F ";" '{print $14}' <<< "${ligne}" | xargs) + EQUIPE_AGORA=${EQUIPE_AGORA,,} + QUOTA=$(awk -F ";" '{print $15}' <<< "${ligne}" | xargs) + PASSWORD=$(awk -F ";" '{print $16}' <<< "${ligne}" | xargs) + + IDENT_KAZ=$(unaccent utf8 "${PRENOM,,}.${NOM,,}") + EMAIL_SOUHAITE=${tab_email[EMAIL_SOUHAITE]} + EMAIL_SECOURS=${tab_email[EMAIL_SECOURS]} + + echo -e "${NL}***************************** traitement de ${ligne}" | tee -a "${LOG}" + + ########################### + # Vérification des champs # + ########################### + + for k in "${!tab_email[@]}"; do + validator "${tab_email[${k}]}" + done + + # vérif des champs O/N + for k in "${!service[@]}"; do + if [ "${service[${k}]}" != "O" -a "${service[${k}]}" != "N" ]; then + ( + echo "${RED}" + echo "${k} : ${service[${k}]}" + echo "ERREUR : le paramètre ${k} accepte O ou N - on stoppe tout - aucun utilisateur de créé" + echo "${NC}" + ) | tee -a "${LOG}" + exit 1 + fi + done + + # taille ORGA et EQUIPE_AGORA + TAILLE_MAX="23" + if [ "${#ORGA}" -gt "${TAILLE_MAX}" ]; then + ( + echo "${RED}" + echo "ERREUR : le paramètre ORGA est trop grand : ${ORGA} , taille max : ${TAILLE_MAX} - on stoppe tout - aucun utilisateur de créé" + echo "${NC}" + ) | tee -a "${LOG}" + exit 1 + fi + if [ "${#ORGA}" -gt "0" ]; then + if [[ "${ORGA}" =~ ^[[:alnum:]-]+$ ]]; then + echo "ok" + else + ( + echo "${RED}" + echo "ERREUR : le paramètre ORGA ne contient pas les caractères autorisés : ${ORGA} - on stoppe tout - aucun utilisateur de créé" + echo "${NC}" + ) | tee -a "${LOG}" + exit 1 + fi + fi + + if [ "${#EQUIPE_AGORA}" -gt "${TAILLE_MAX}" ]; then + ( + echo "${RED}" + echo "ERREUR : le paramètre EQUIPE_AGORA est trop grand : ${EQUIPE_AGORA} , taille max : ${TAILLE_MAX} - on stoppe tout - aucun utilisateur de créé" + echo "${NC}" + ) | tee -a "${LOG}" + exit 1 + fi + + # vérif quota est entier + if ! [[ "${QUOTA}" =~ ^[[:digit:]]+$ ]]; then + ( + echo + echo "ERREUR : ${RED}${BOLD}QUOTA n'est pas numérique : ${QUOTA}${NC} - on stoppe tout - aucun utilisateur de créé" + ) | tee -a "${LOG}" + fi + + #################################################### + # cree un mdp acceptable par postfix/nc/mattermost # + #################################################### + if [ -z ${PASSWORD} ]; then + PASSWORD=_`apg -n 1 -m 10 -M NCL -d`_ + fi + SEND_MSG_CREATE= + if [ -n "${ORGA}" -a -z "${CREATE_ORGA}" ]; then + # skeep orga + continue + fi + + #################################################################### + # TODO: Test de l'identKAZ du ldap, il faut l'unicité. si KO, STOP # + #################################################################### + + ################################### + # Création du compe de messagerie # + ################################### + # le mail existe t-il déjà ? + if grep -q "^${EMAIL_SOUHAITE}$" "${TFILE_EMAIL}"; then + echo "${EMAIL_SOUHAITE} existe déjà" | tee -a "${LOG}" + else + SEND_MSG_CREATE=true + echo "${EMAIL_SOUHAITE} n'existe pas" | tee -a "${LOG}" + echo "${SETUP_MAIL} email add ${EMAIL_SOUHAITE} ${PASSWORD}" | tee -a "${CMD_LOGIN}" + echo "${SETUP_MAIL} quota set ${EMAIL_SOUHAITE} ${QUOTA}G" | tee -a "${CMD_LOGIN}" + # LDAP, à tester + user=$(echo ${EMAIL_SOUHAITE} | awk -F '@' '{print $1}') + domain=$(echo ${EMAIL_SOUHAITE} | awk -F '@' '{print $2}') + pass=$(mkpasswd -m sha512crypt ${PASSWORD}) + echo "echo -e '\n\ndn: cn=${EMAIL_SOUHAITE},ou=users,${ldap_root}\n\ +changeType: add\n\ +objectclass: inetOrgPerson\n\ +objectClass: PostfixBookMailAccount\n\ +objectClass: nextcloudAccount\n\ +objectClass: kaznaute\n\ +sn: ${PRENOM} ${NOM}\n\ +mail: ${EMAIL_SOUHAITE}\n\ +mailEnabled: TRUE\n\ +mailGidNumber: 5000\n\ +mailHomeDirectory: /var/mail/${domain}/${user}/\n\ +mailQuota: ${QUOTA}G\n\ +mailStorageDirectory: maildir:/var/mail/${domain}/${user}/\n\ +mailUidNumber: 5000\n\ +mailDeSecours: ${EMAIL_SECOURS}\n\ +identifiantKaz: ${IDENT_KAZ}\n\ +quota: ${QUOTA}\n\ +nextcloudEnabled: TRUE\n\ +nextcloudQuota: ${QUOTA} GB\n\ +mobilizonEnabled: TRUE\n\ +agoraEnabled: TRUE\n\ +userPassword: {CRYPT}${pass}\n\n' | ldapmodify -c -H ldap://${LDAP_IP} -D \"cn=${ldap_LDAP_ADMIN_USERNAME},${ldap_root}\" -x -w ${ldap_LDAP_ADMIN_PASSWORD}" | tee -a "${CMD_LOGIN}" + fi +#userPassword: {CRYPT}\$6\$${pass}\n\n\" | ldapmodify -c -H ldap://${LDAP_IP} -D \"cn=${ldap_LDAP_CONFIG_ADMIN_USERNAME},${ldap_root}\" -x -w ${ldap_LDAP_CONFIG_ADMIN_PASSWORD}" | tee -a "${CMD_LOGIN}" + + CREATE_ORGA_SERVICES="" + + ############# + # NEXTCLOUD # + ############# + # on recalcul l'url de NC + if [ "${ORGA}" != "" -a "${service[NC_ORGA]}" == "O" ]; then + URL_NC="${ORGA}-${cloudHost}.${domain}" + + # si le cloud de l'orga n'est pas up alors on le créé + nb=$(docker ps | grep "${ORGA}-${cloudHost}" | wc -l) + if [ "${nb}" == "0" ];then + echo " * +cloud +collabora ${ORGA}" + CREATE_ORGA_SERVICES="${CREATE_ORGA_SERVICES} +cloud +collabora" + # installe les plugins initiaux dans "/kaz/bin/gestClouds.sh" + fi + + NB_SERVICES_DEDIES=$((NB_SERVICES_DEDIES+1)) + + else + URL_NC="${cloudHost}.${domain}" + NB_SERVICES_BASE=$((NB_SERVICES_BASE+1)) + fi + + MESSAGE_MAIL_ORGA_1="${MESSAGE_MAIL_ORGA_1}${NL}* un bureau virtuel pour stocker des fichiers/calendriers/contacts et partager avec vos connaissances : ${httpProto}://${URL_NC}" + + # le user existe t-il déjà sur NC ? + curl -o "${TEMP_USER_NC}" -X GET -H 'OCS-APIRequest:true' "${httpProto}://admin:${nextcloud_NEXTCLOUD_ADMIN_PASSWORD}@${URL_NC}/ocs/v1.php/cloud/users?search=${IDENT_KAZ}" + if grep -q "${IDENT_KAZ}" "${TEMP_USER_NC}"; then + echo "${IDENT_KAZ} existe déjà sur ${URL_NC}" | tee -a "${LOG}" + else + + # on créé l'utilisateur sur NC sauf si c'est le NC général, on ne créé jamais l'utilisateur7 + if [ ${URL_NC} != "${cloudHost}.${domain}" ]; then + + echo "curl -X POST -H 'OCS-APIRequest:true' ${httpProto}://admin:${nextcloud_NEXTCLOUD_ADMIN_PASSWORD}@${URL_NC}/ocs/v1.php/cloud/users \ +-d userid='${IDENT_KAZ}' \ +-d displayName='${PRENOM} ${NOM}' \ +-d password='${PASSWORD}' \ +-d email='${EMAIL_SOUHAITE}' \ +-d quota='${QUOTA}GB' \ +-d language='fr' \ +" | tee -a "${CMD_INIT}" + + fi + + # s'il est admin de son orga, on le met admin + if [ "${service[ADMIN_ORGA]}" == "O" -a "${ORGA}" != "" -a "${service[NC_ORGA]}" == "O" ]; then + echo "curl -X POST -H 'OCS-APIRequest:true' ${httpProto}://${nextcloud_NEXTCLOUD_ADMIN_USER}:${nextcloud_NEXTCLOUD_ADMIN_PASSWORD}@${URL_NC}/ocs/v1.php/cloud/users/${IDENT_KAZ}/groups -d groupid='admin'" | tee -a "${CMD_INIT}" + fi + + # faut-il mettre le user NC dans un groupe particulier sur le NC de base ? + if [ "${GROUPE_NC_BASE}" != "" -a "${service[NC_BASE]}" == "O" ]; then + # le groupe existe t-il déjà ? + curl -o "${TEMP_GROUP_NC}" -X GET -H 'OCS-APIRequest:true' "${httpProto}://admin:${nextcloud_NEXTCLOUD_ADMIN_PASSWORD}@${URL_NC}/ocs/v1.php/cloud/groups?search=${GROUPE_NC_BASE}" + nb=$(grep "${GROUPE_NC_BASE}" "${TEMP_GROUP_NC}" | wc -l) + if [ "${nb}" == "0" ];then + echo "curl -X POST -H 'OCS-APIRequest:true' ${httpProto}://admin:${nextcloud_NEXTCLOUD_ADMIN_PASSWORD}@${URL_NC}/ocs/v1.php/cloud/groups -d groupid=${GROUPE_NC_BASE}" | tee -a "${CMD_INIT}" + fi + # puis attacher le user au groupe + echo "curl -X POST -H 'OCS-APIRequest:true' ${httpProto}://admin:${nextcloud_NEXTCLOUD_ADMIN_PASSWORD}@${URL_NC}/ocs/v1.php/cloud/users/${IDENT_KAZ}/groups -d groupid=${GROUPE_NC_BASE}" | tee -a "${CMD_INIT}" + fi + fi + + ############# + # WORDPRESS # + ############# + + # TODO : pour l'utilisation de l'api : https://www.hostinger.com/tutorials/wordpress-rest-api + + if [ "${ORGA}" != "" -a "${service[WP_ORGA]}" == "O" ]; then + + URL_WP_ORGA="${ORGA}-${wordpressHost}.${domain}" + + # si le wp de l'orga n'est pas up alors on le créé + nb=$(docker ps | grep "${ORGA}-${wordpressHost}" | wc -l) + if [ "${nb}" == "0" ];then + echo " * +wp ${ORGA}" + CREATE_ORGA_SERVICES="${CREATE_ORGA_SERVICES} +wp" + fi + + NB_SERVICES_DEDIES=$((NB_SERVICES_DEDIES+1)) + MESSAGE_MAIL_ORGA_1="${MESSAGE_MAIL_ORGA_1}${NL}* un site web de type wordpress : ${httpProto}://${URL_WP_ORGA}/wp-admin/" + + # TODO : vérif existance user + # # le user existe t-il déjà sur le wp ? + # curl -o "${TEMP_USER_WP}" -X GET "${httpProto}://${wp_WORDPRESS_ADMIN_USER}:${wp_WORDPRESS_ADMIN_PASSWORD}@${URL_WP_ORGA}/ocs/v1.php/cloud/users?search=${IDENT_KAZ}" + # nb_user_wp_orga=$(grep "${IDENT_KAZ}" "${TEMP_USER_WP}" | wc -l) + # if [ "${nb_user_wp_orga}" != "0" ];then + # ( + # echo "${RED}" + # echo "ERREUR : ${IDENT_KAZ} existe déjà sur ${URL_WP_ORGA} - on stoppe tout - aucun utilisateur de créé" + # echo "${NC}" + # ) | tee -a "${LOG}" + # + # # ROLLBACK - on vire le user de NC + # if [ "${nb_user_nc_orga}" != "0" ];then + # ( + # echo "${RED}" + # echo "ERREUR : ${IDENT_KAZ} existe déjà sur ${URL_NC} - on stoppe tout - aucun utilisateur de créé" + # echo "${NC}" + # ) | tee -a "${LOG}" + # + # # on supprime l'utilisateur sur NC. + # echo "curl -X DELETE -H 'OCS-APIRequest:true' ${httpProto}://admin:${nextcloud_NEXTCLOUD_ADMIN_PASSWORD}@${URL_NC}/ocs/v1.php/cloud/users \ + # -d userid='${IDENT_KAZ}' \ + # " | tee -a "${CMD_INIT}" + # fi + # + # exit 1 + # fi + + # TODO : créer le user et le mettre admin si nécessaire + # if [ "${service[ADMIN_ORGA]}" == "O" ]; then + # : + # else + # : + # fi + fi + + ############ + # PAHEKO # + ############ + + + if [ "${ORGA}" != "" -a "${service[PAHEKO_ORGA]}" == "O" ]; then + + URL_PAHEKO_ORGA="${ORGA}-${pahekoHost}.${domain}" + + # il n'y a pas de docker spécifique paheko (je cree toujours paheko) + echo " * +paheko ${ORGA}" + CREATE_ORGA_SERVICES="${CREATE_ORGA_SERVICES} +paheko" + + NB_SERVICES_DEDIES=$((NB_SERVICES_DEDIES+1)) + MESSAGE_MAIL_ORGA_1="${MESSAGE_MAIL_ORGA_1}${NL}* un service de gestion adhérents/clients : ${httpProto}://${URL_PAHEKO_ORGA}" + + if [ "${service[ADMIN_ORGA]}" == "O" ]; then + MESSAGE_MAIL_ORGA_1="${MESSAGE_MAIL_ORGA_1} (l'installation est à terminer en vous rendant sur le site)" + fi + fi + + + ############ + # DOKUWIKI # + ############ + + if [ "${ORGA}" != "" -a "${service[WIKI_ORGA]}" == "O" ]; then + + URL_WIKI_ORGA="${ORGA}-${dokuwikiHost}.${domain}" + + # si le wiki de l'orga n'est pas up alors on le créé + nb=$(docker ps | grep "${ORGA}-${dokuwikiHost}" | wc -l) + if [ "${nb}" == "0" ];then + echo " * +wiki ${ORGA}" + CREATE_ORGA_SERVICES="${CREATE_ORGA_SERVICES} +wiki" + fi + + NB_SERVICES_DEDIES=$((NB_SERVICES_DEDIES+1)) + MESSAGE_MAIL_ORGA_1="${MESSAGE_MAIL_ORGA_1}${NL}* un wiki dédié pour votre documentation : ${httpProto}://${URL_WIKI_ORGA}" + + + # TODO : ??? à voir https://www.dokuwiki.org/devel:xmlrpc:clients + + if grep -q "^${IDENT_KAZ}:" "${DOCK_VOL}/orga_${ORGA}-wikiConf/_data/users.auth.php" 2>/dev/null; then + echo "${IDENT_KAZ} existe déjà sur ${URL_WIKI_ORGA}" | tee -a "${LOG}" + else + echo "echo \"${IDENT_KAZ}:$(htpasswd -bnBC 10 "" ${PASSWORD}):${PRENOM} ${NOM}:${EMAIL_SOUHAITE}:admin,user\" >> \"${DOCK_VOL}/orga_${ORGA}-wikiConf/_data/users.auth.php\"" | tee -a "${CMD_INIT}" + fi + fi + + ############## + # MATTERMOST # + ############## + + # on ne gère pas la création du docker dédié mattermost + if [ "${ORGA}" != "" -a "${service[AGORA_ORGA]}" == "O" ]; then + echo "# ******************************************************************************" | tee -a "${CMD_INIT}" + echo "# **************************** ATTENTION ***************************************" | tee -a "${CMD_INIT}" + echo "# ******************************************************************************" | tee -a "${CMD_INIT}" + echo "# Mattermost dédié : on ne fait rien." | tee -a "${CMD_INIT}" + echo "# ******************************************************************************" | tee -a "${CMD_INIT}" + fi + + if grep -q "^${IDENT_KAZ}$" "${TFILE_MM}" 2>/dev/null; then + echo "${IDENT_KAZ} existe déjà sur mattermost" | tee -a "${LOG}" + else + # on créé le compte mattermost + echo "docker exec -ti mattermostServ bin/mmctl user create --email ${EMAIL_SOUHAITE} --username ${IDENT_KAZ} --password ${PASSWORD}" | tee -a "${CMD_LOGIN}" + # et enfin on ajoute toujours le user à l'équipe KAZ et aux 2 channels publiques + echo "docker exec -ti mattermostServ bin/mmctl team users add kaz ${EMAIL_SOUHAITE}" | tee -a "${CMD_LOGIN}" + echo "docker exec -ti mattermostServ bin/mmctl channel users add kaz:une-question--un-soucis ${EMAIL_SOUHAITE}" | tee -a "${CMD_LOGIN}" + echo "docker exec -ti mattermostServ bin/mmctl channel users add kaz:cafe-du-commerce--ouvert-2424h ${EMAIL_SOUHAITE}" | tee -a "${CMD_LOGIN}" + NB_SERVICES_BASE=$((NB_SERVICES_BASE+1)) + fi + + if [ "${EQUIPE_AGORA}" != "" -a "${EQUIPE_AGORA}" != "kaz" ]; then + # l'équipe existe t-elle déjà ? + nb=$(docker exec mattermostServ bin/mmctl team list | grep -w "${EQUIPE_AGORA}" | wc -l) + if [ "${nb}" == "0" ];then # non, on la créé en mettant le user en admin de l'équipe + echo "docker exec -ti mattermostServ bin/mmctl team create --name ${EQUIPE_AGORA} --display_name ${EQUIPE_AGORA} --email ${EMAIL_SOUHAITE}" --private | tee -a "${CMD_INIT}" + fi + # puis ajouter le user à l'équipe + echo "docker exec -ti mattermostServ bin/mmctl team users add ${EQUIPE_AGORA} ${EMAIL_SOUHAITE}" | tee -a "${CMD_INIT}" + fi + + if [ -n "${CREATE_ORGA_SERVICES}" ]; then + SEND_MSG_CREATE=true + echo "${CREATE_ORGA_CMD}" --create ${CREATE_ORGA_SERVICES} "${ORGA}" | tee -a "${CMD_ORGA}" + echo "${CREATE_ORGA_CMD}" --init ${CREATE_ORGA_SERVICES} "${ORGA}" | tee -a "${CMD_FIRST}" + ALL_ORGA="${ALL_ORGA} ${ORGA}" + fi + + ########################## + # Inscription newsletter # + ########################## + + # TODO : utiliser liste sur dev également + + # on inscrit le user sur sympa, à la liste infos@${domain_sympa} + # docker exec -ti sympaServ /usr/lib/sympa/bin/sympa_soap_client.pl --soap_url=https://listes.kaz.sns/sympasoap --trusted_application=SOAP_USER --trusted_application_password=SOAP_PASSWORD --proxy_vars="USER_EMAIL=contact1@kaz.sns" --service=which + if [[ "${mode}" = "dev" ]]; then + echo "# DEV, on teste l'inscription à sympa"| tee -a "${CMD_SYMPA}" + LISTMASTER=$(echo ${sympa_LISTMASTERS} | cut -d',' -f1) + echo "docker exec -ti sympaServ /usr/lib/sympa/bin/sympa_soap_client.pl --soap_url=${httpProto}://${URL_LISTE}/sympasoap --trusted_application=${sympa_SOAP_USER} --trusted_application_password=${sympa_SOAP_PASSWORD} --proxy_vars=\"USER_EMAIL=${LISTMASTER}\" --service=add --service_parameters=\"${NL_LIST},${EMAIL_SOUHAITE}\"" | tee -a "${CMD_SYMPA}" + else + echo "# PROD, on inscrit à sympa"| tee -a "${CMD_SYMPA}" + LISTMASTER=$(echo ${sympa_LISTMASTERS} | cut -d',' -f1) + echo "docker exec -ti sympaServ /usr/lib/sympa/bin/sympa_soap_client.pl --soap_url=${httpProto}://${URL_LISTE}/sympasoap --trusted_application=${sympa_SOAP_USER} --trusted_application_password=${sympa_SOAP_PASSWORD} --proxy_vars=\"USER_EMAIL=${LISTMASTER}\" --service=add --service_parameters=\"${NL_LIST},${EMAIL_SOUHAITE}\"" | tee -a "${CMD_SYMPA}" + echo "docker exec -ti sympaServ /usr/lib/sympa/bin/sympa_soap_client.pl --soap_url=${httpProto}://${URL_LISTE}/sympasoap --trusted_application=${sympa_SOAP_USER} --trusted_application_password=${sympa_SOAP_PASSWORD} --proxy_vars=\"USER_EMAIL=${LISTMASTER}\" --service=add --service_parameters=\"${NL_LIST},${EMAIL_SECOURS}\"" | tee -a "${CMD_SYMPA}" + fi + + if [ "${service[ADMIN_ORGA]}" == "O" ]; then + MESSAGE_MAIL_ORGA_2="${MESSAGE_MAIL_ORGA_2}Comme administrateur de votre organisation, vous pouvez créer des listes de diffusion en vous rendant sur ${httpProto}://${URL_LISTE}" + fi + + ################### + # update paheko # + ################### + + # TODO : problème si 2 comptes partagent le même email souhaité (cela ne devrait pas arriver) + curl -s "https://${paheko_API_USER}:${paheko_API_PASSWORD}@kaz-paheko.kaz.bzh/api/sql" -d "SELECT numero,nom,quota_disque from users WHERE email='${EMAIL_SOUHAITE}'" | jq '.results[] | .numero,.nom,.quota_disque ' | tr \\n ',' | sed 's/,$/,Aucune\n/' >> "${TEMP_PAHEKO}" + + #################### + # Inscription MAIL # + #################### + + if [ "${NB_SERVICES_DEDIES}" != "0" ];then + MESSAGE_MAIL_ORGA_1="${NL} dont ${NB_SERVICES_DEDIES} service(s) dédié(s) pour votre organisation:${NL} ${MESSAGE_MAIL_ORGA_1}" + fi + + if [ -z "${SEND_MSG_CREATE}" ]; then + # rien de créé => pas de message + continue + fi + + #si admin alors msg pour indiquer qu'il faut nous renvoyer ce qu'il souhaite comme service. + if [ "${service[ADMIN_ORGA]}" == "O" ]; then + MESSAGE_MAIL_ORGA_3="${MESSAGE_MAIL_ORGA_3}En tant qu'association/famille/société. Vous avez la possibilité d'ouvrir, quand vous le voulez, des services kaz, il vous suffit de nous le demander. + +Pourquoi n'ouvrons-nous pas tous les services tout de suite ? parce que nous aimons la sobriété et que nous préservons notre espace disque ;) +A quoi sert d'avoir un site web si on ne l'utilise pas, n'est-ce pas ? + +Par retour de mail, dites-nous de quoi vous avez besoin tout de suite entre: +* une comptabilité : un service de gestion adhérents/clients +* un site web de type WordPress +* un cloud : bureau virtuel pour stocker des fichiers/calendriers/contacts et partager avec vos connaissances + +Une fois que vous aurez répondu à ce mail, votre demande sera traitée manuellement. +" + fi + + # on envoie le mail de bienvenue + MAIL_KAZ="Bonjour, + +Bienvenue chez KAZ! + +Vous disposez de $((${NB_SERVICES_BASE} + ${NB_SERVICES_DEDIES})) services kaz avec authentification : + +* une messagerie classique : ${httpProto}://${URL_WEBMAIL} +* une messagerie instantanée pour discuter au sein d'équipes : ${httpProto}://${URL_AGORA} + +Votre email et identifiant pour tous ces services : ${EMAIL_SOUHAITE} +Le mot de passe : ${PASSWORD} + +Pour changer votre mot de passe de messagerie, c'est ici: ${httpProto}://${URL_MDP} +Si vous avez perdu votre mot de passe, c'est ici: ${httpProto}://${URL_MDP}/?action=sendtoken + +Vous pouvez accéder à votre messagerie : +* soit depuis votre webmail : ${httpProto}://${URL_WEBMAIL} +* soit depuis votre bureau virtuel : ${httpProto}://${URL_NC} +* soit depuis un client de messagerie comme thunderbird + +${MESSAGE_MAIL_ORGA_3} + +Vous avez quelques docs intéressantes sur le wiki de kaz: + +* Migrer son site internet wordpress vers kaz +https://wiki.kaz.bzh/wordpress/start#migrer_son_site_wordpress_vers_kaz + +* Migrer sa messagerie vers kaz +https://wiki.kaz.bzh/messagerie/gmail/start + +* Démarrer simplement avec son cloud +https://wiki.kaz.bzh/nextcloud/start + +Votre quota est de ${QUOTA}GB. Si vous souhaitez plus de place pour vos fichiers ou la messagerie, faites-nous signe ! + +Pour accéder à la messagerie instantanée et communiquer avec les membres de votre équipe ou ceux de kaz : ${httpProto}://${URL_AGORA}/login + +${MESSAGE_MAIL_ORGA_2} + +Enfin, vous disposez de tous les autres services KAZ où l'authentification n'est pas nécessaire : ${httpProto}://${URL_SITE} + +En cas de soucis, n'hésitez pas à poser vos questions sur le canal 'Une question ? un soucis' de l'agora dispo ici : ${httpProto}://${URL_AGORA} + +Si vous avez besoin d'accompagnement pour votre site, votre cloud, votre compta, votre migration de messagerie,... nous proposons des formations mensuelles gratuites. Si vous souhaitez être accompagné par un professionnel, nous pouvons vous donner une liste de pros, référencés par KAZ. + +À bientôt ;) + +La collégiale de KAZ. " + + echo "docker exec -i mailServ mailx -a 'Content-Type: text/plain; charset=\"UTF-8\"' -r contact@kaz.bzh -s \"KAZ: confirmation d'inscription\" ${EMAIL_SOUHAITE} ${EMAIL_SECOURS} << EOF +${MAIL_KAZ} +EOF" | tee -a "${CMD_MSG}" + + # on envoie le mail de confirmation d'inscription à contact + MAIL_KAZ="*****POST AUTOMATIQUE****** +Hello, +${NOM} ${PRENOM} vient d'être inscrit avec l'email ${EMAIL_SOUHAITE} +quota : ${QUOTA}GB + +NC_BASE : ${service[NC_BASE]} +groupe NC base : ${GROUPE_NC_BASE} +équipe agora base : ${EQUIPE_AGORA} +email de secours : ${EMAIL_SECOURS} + +ORGA : ${ORGA} +ADMIN_ORGA : ${service[ADMIN_ORGA]} +NC_ORGA : ${service[NC_ORGA]} +PAHEKO_ORGA : ${service[PAHEKO_ORGA]} +WP_ORGA : ${service[WP_ORGA]} +AGORA_ORGA : ${service[AGORA_ORGA]} +WIKI_ORGA : ${service[WIKI_ORGA]} + +bisou!" + echo "docker exec -i mailServ mailx -a 'Content-Type: text/plain; charset=\"UTF-8\"' -r contact@kaz.bzh -s \"KAZ: confirmation d'inscription\" ${EMAIL_CONTACT} << EOF +${MAIL_KAZ} +EOF" | tee -a "${CMD_MSG}" + + echo " # on envoie la confirmation d'inscription sur l'agora " | tee -a "${CMD_MSG}" + echo "docker exec -ti mattermostServ bin/mmctl post create kaz:Creation-Comptes --message \"${MAIL_KAZ}\"" | tee -a "${CMD_MSG}" + + # fin des inscriptions +done <<< "${ALL_LINES}" + +if [[ -n "${ALL_ORGA}" ]]; then + echo "sleep 2" | tee -a "${CMD_PROXY}" + echo "${KAZ_BIN_DIR}/container.sh start ${ALL_ORGA}" | tee -a "${CMD_PROXY}" + for item in "${availableProxyComposes[@]}"; do + echo "cd \"${KAZ_COMP_DIR}/${item}/\"; ./proxy-gen.sh; docker-compose up -d; ./reload.sh " | tee -a "${CMD_PROXY}" + done +fi + +########################### +# Lancement des commandes # +########################### +if [ "${SIMULATION}" == "NO" ]; then + echo "on exécute" + "${CMD_LOGIN}" + # on attend qques secondes que le mail soit bien créé avant de continuer (prob de lecture de la BAL : à investiguer) + # je rallonge à 20s car je vois que le docker sympa ne connait pas toujours l'email kaz créé + echo "on attend 20s pour que la création des emails soit certaine" + sleep 20 + "${CMD_SYMPA}" + "${CMD_ORGA}" + "${CMD_PROXY}" + "${CMD_FIRST}" + "${CMD_INIT}" + "${CMD_PAHEKO}" + "${CMD_MSG}" +else + echo "Aucune commande n'a été lancée : Possibilité de le faire à la main. cf ${KAZ_ROOT}/tmp/${RACINE}_cmds_to_run-*.sh" +fi + +# END diff --git a/bin/cron-cloud.sh b/bin/cron-cloud.sh new file mode 100755 index 0000000..742bb54 --- /dev/null +++ b/bin/cron-cloud.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +for cloud in $(docker ps | grep -i nextcloudServ |awk '{print $12}') +do + docker exec -u www-data $cloud php cron.php + +done diff --git a/bin/dns.sh b/bin/dns.sh new file mode 100755 index 0000000..d09625a --- /dev/null +++ b/bin/dns.sh @@ -0,0 +1,209 @@ +#!/bin/bash + +# list/ajout/supprime/ un sous-domaine + +KAZ_ROOT=$(cd "$(dirname $0)"/..; pwd) +. "${KAZ_ROOT}/bin/.commonFunctions.sh" +setKazVars +. "${DOCKERS_ENV}" + +cd "${KAZ_ROOT}" +export PRG="$0" +export IP="127.0.0.1" +export ETC_HOSTS="/etc/hosts" + +# no more export in .env +export $(set | grep "domain=") + +declare -a forbidenName +forbidenName=(${calcHost} calc ${cloudHost} bureau ${dateHost} date ${dokuwikiHost} dokuwiki ${fileHost} file ${ldapHost} ${pahekoHost} ${gitHost} ${gravHost} ${matterHost} ${officeHost} collabora ${padHost} ${sympaHost} listes ${webmailHost} ${wordpressHost} www ${vigiloHost} form) + +export FORCE="NO" +export CMD="" +export SIMU="" + +usage(){ + echo "Usage: ${PRG} list [sub-domain...]" + echo " ${PRG} [-n] [-f] {add/del} sub-domain..." + echo " -h help" + echo " -n simulation" + echo " -f force protected domain" + exit 1 +} + +for ARG in $@ +do + case "${ARG}" in + '-h' | '-help' ) + usage + ;; + '-f' ) + shift + export FORCE="YES" + ;; + '-n' ) + shift + export SIMU="echo" + ;; + 'list'|'add'|'del' ) + shift + CMD="${ARG}" + break + ;; + * ) + usage + ;; + esac +done + +if [ -z "${CMD}" ]; then + usage +fi + +. "${KAZ_KEY_DIR}/env-gandi" + +if [[ -z "${GANDI_KEY}" ]] ; then + echo + echo "no GANDI_KEY set in ${KAZ_KEY_DIR}/env-gandi" + usage +fi + + +waitNet () { + if [[ "${domain}" = "kaz.local" ]]; then + return + fi + + ### wait when error code 503 + if [[ $(curl -H "authorization: Apikey ${GANDI_KEY}" --connect-timeout 2 -s -D - "${GANDI_API}" -o /dev/null 2>/dev/null | head -n1) != *200* ]]; then + echo "DNS not available. Please wait..." + while [[ $(curl -H "authorization: Apikey ${GANDI_KEY}" --connect-timeout 2 -s -D - "${GANDI_API}" -o /dev/null 2>/dev/null | head -n1) != *200* ]] + do + sleep 5 + done + exit + fi +} + +list(){ + if [[ "${domain}" = "kaz.local" ]]; then + grep --perl-regex "^${IP}\s.*${domain}" "${ETC_HOSTS}" 2> /dev/null | sed -e "s|^${IP}\s*\([0-9a-z.-]${domain}\)$|\1|g" + return + fi + waitNet + trap 'rm -f "${TMPFILE}"' EXIT + TMPFILE="$(mktemp)" || exit 1 + if [[ -n "${SIMU}" ]] ; then + ${SIMU} curl -X GET "${GANDI_API}/records" -H "authorization: Apikey ${GANDI_KEY}" + else + curl -X GET "${GANDI_API}/records" -H "authorization: Apikey ${GANDI_KEY}" 2>/dev/null | \ + sed "s/,{/\n/g" | \ + sed 's/.*rrset_name":"\([^"]*\)".*rrset_values":\["\([^"]*\)".*/\1:\2/g'| \ + grep -v '^[_@]'| \ + grep -e ":${domain}\.*$" -e ":prod[0-9]*$" > ${TMPFILE} + fi + if [ $# -lt 1 ]; then + cat ${TMPFILE} + else + for ARG in $@ + do + cat ${TMPFILE} | grep "${ARG}.*:" + done + fi +} + +saveDns () { + for ARG in $@ ; do + if [[ "${ARG}" =~ .local$ ]] ; then + echo "${PRG}: old fasion style (remove .local at the end)" + usage; + fi + if [[ "${ARG}" =~ .bzh$ ]] ; then + echo "${PRG}: old fasion style (remove .bzh at the end)" + usage; + fi + if [[ "${ARG}" =~ .dev$ ]] ; then + echo "${PRG}: old fasion style (remove .dev at the end)" + usage; + fi + done + if [[ "${domain}" = "kaz.local" ]]; then + return + fi + waitNet + ${SIMU} curl -X POST "${GANDI_API}/snapshots" -H "authorization: Apikey ${GANDI_KEY}" 2>/dev/null +} + +badName(){ + [[ -z "$1" ]] && return 0; + for item in "${forbidenName[@]}"; do + [[ "${item}" == "$1" ]] && [[ "${FORCE}" == "NO" ]] && return 0 + done + return 1 +} + +add(){ + if [ $# -lt 1 ]; then + exit + fi + saveDns $@ + declare -a ADDED + for ARG in $@ + do + if badName "${ARG}" ; then + echo "can't manage '${ARG}'. Use -f option" + continue + fi + case "${domain}" in + kaz.local ) + if grep -q --perl-regex "^${IP}.*[ \t]${ARG}.${domain}" "${ETC_HOSTS}" 2> /dev/null ; then + break + fi + if grep -q --perl-regex "^${IP}[ \t]" "${ETC_HOSTS}" 2> /dev/null ; then + ${SIMU} sudo sed -i -e "0,/^${IP}[ \t]/s/^\(${IP}[ \t]\)/\1${ARG}.${domain} /g" "${ETC_HOSTS}" + else + ${SIMU} sudo sed -i -e "$ a ${IP}\t${ARG}.${domain}" "${ETC_HOSTS}" 2> /dev/null + fi + ;; + *) + ${SIMU} curl -X POST "${GANDI_API}/records" -H "authorization: Apikey ${GANDI_KEY}" -H 'content-type: application/json' -d '{"rrset_type":"CNAME", "rrset_name":"'${ARG}'", "rrset_values":["'${site}'"]}' + echo + ;; + esac + ADDED+=("${ARG}") + done + echo "Domains added to ${domain}: ${ADDED[@]}" +} + +del(){ + if [ $# -lt 1 ]; then + exit + fi + saveDns $@ + declare -a REMOVED + for ARG in $@ + do + if badName "${ARG}" ; then + echo "can't manage '${ARG}'. Use -f option" + continue + fi + case "${domain}" in + kaz.local ) + if !grep -q --perl-regex "^${IP}.*[ \t]${ARG}.${domain}" "${ETC_HOSTS}" 2> /dev/null ; then + break + fi + ${SIMU} sudo sed -i -e "/^${IP}[ \t]*${ARG}.${domain}[ \t]*$/d" \ + -e "s|^\(${IP}.*\)[ \t]${ARG}.${domain}|\1|g" "${ETC_HOSTS}" + ;; + * ) + ${SIMU} curl -X DELETE "${GANDI_API}/records/${ARG}" -H "authorization: Apikey ${GANDI_KEY}" + echo + ;; + esac + REMOVED+=("${ARG}") + done + echo "Domains removed from ${domain}: ${REMOVED[@]}" +} + +#echo "CMD: ${CMD} $*" +${CMD} $* diff --git a/bin/envoiMails.sh b/bin/envoiMails.sh new file mode 100755 index 0000000..7346b89 --- /dev/null +++ b/bin/envoiMails.sh @@ -0,0 +1,66 @@ +#!/bin/bash + +#kan: 09/09/2022 +#koi: envoyer un mail à une liste (sans utiliser sympa sur listes.kaz.bzh) +#ki : fab + +#on récupère toutes les variables et mdp +# on prend comme source des repertoire le dossier du dessus ( /kaz dans notre cas ) +KAZ_ROOT=$(cd "$(dirname $0)"/..; pwd) +. "${KAZ_ROOT}/bin/.commonFunctions.sh" +setKazVars + +SIMULATION=NO + +CMD="/tmp/envoiMail_cmds_to_run.sh" +echo "#!/bin/bash" > ${CMD} && chmod +x ${CMD} + +################################################################################################################# +MAIL_KAZ=" +KAZ, association morbihannaise, propose de \"dégoogliser\" l’internet avec des solutions et services numériques libres alternatifs à ceux des GAFAM. Elle invite les habitants et les élus de Vannes et de sa région à une réunion publique d’information et d’échange : +Le jeudi 22 septembre 2022 à 18 heures +à la Maison des associations, 31 rue Guillaume Le Bartz à Vannes. + +Cette invitation est destinée à toute personne sensible aux enjeux du numérique, aux risques pour la démocratie et les libertés, à sa participation au dérèglement climatique et à l’épuisement des ressources de notre planète. + +Nous dirons qui nous sommes, quelles sont nos valeurs et quels sont concrètement les solutions et services que nous proposons, leurs conditions d’accès et l’accompagnement utile pour leur prise en main. + +Les premières pierres de KAZ ont été posées voilà bientôt deux ans, en pleine pandémie de la COVID, par sept citoyens qui ont voulu répondre à l’appel de Framasoft de dégoogliser l’internet. Ne plus se lamenter sur la puissance des GAFAM, mais proposer des solutions et des services numériques alternatifs à ceux de Google, Amazon, Facebook, Apple et Microsoft en s’appuyant sur les fondamentaux Sobre, Libre, Éthique et Local. + +A ce jour, près de 200 particuliers ont ouvert une adresse @kaz.bz, plus de 80 organisations ont souscrit au bouquet de services de KAZ et près de 800 collaborateurs d’organisations utilisatrices des services de KAZ participent peu ou prou au réseau de KAZ. +Beaucoup de services sont gratuits et accessibles sur le site https://kaz.bzh. D’autres demandent l’ouverture d’un compte moyennant une petite participation financière de 10€ par an pour les particuliers et de 30€ par an pour les organisations. Ceci est permis par le bénévolat des membres de la collégiale qui dirigent l’association et administrent les serveurs et les services. + +A ce stade, de nombreuses questions se posent à KAZ. Quelle ambition de couverture de ses services auprès des particuliers et des organisations sur son territoire, le Morbihan ? Quel accompagnement dans la prise en main des outils ? Quels nouveaux services seraient utiles ? + +Nous serions heureux de votre participation à notre réunion publique du 22 septembre d'une durée totale de 2h : + * Présentation valeurs / contexte (15mn) + * Présentation outils (45mn) + * Questions / Réponses (1h) + +En restant à votre disposition, + +La Collégiale de KAZ +" +################################################################################################################# + +FIC_MAILS="/tmp/fic_mails" + +while read EMAIL_CIBLE +do + + echo "docker exec -i mailServ mailx -a 'Content-Type: text/plain; charset=\"UTF-8\"' -r contact@kaz.bzh -s \"Invitation rencontre KAZ jeudi 22 septembre à 18h00 à la Maison des assos à Vannes\" ${EMAIL_CIBLE} << EOF +${MAIL_KAZ} +EOF" | tee -a ${CMD} + + echo "sleep 2" | tee -a ${CMD} + +done < ${FIC_MAILS} + +# des commandes à lancer ? +if [ "${SIMULATION}" == "NO" ];then + echo "on exécute" + ${CMD} +else + echo "Aucune commande n'a été lancée: Possibilité de le faire à la main. cf ${CMD}" +fi + diff --git a/bin/foreign-domain.sh b/bin/foreign-domain.sh new file mode 100755 index 0000000..2343361 --- /dev/null +++ b/bin/foreign-domain.sh @@ -0,0 +1,240 @@ +#!/bin/bash + +# list/ajout/supprime/ les domaines extérieurs à kaz.bzh + +KAZ_ROOT=$(cd "$(dirname $0)"/..; pwd) +. "${KAZ_ROOT}/bin/.commonFunctions.sh" +setKazVars + +export PRG="$0" +cd $(dirname $0) + +. "${DOCKERS_ENV}" + +LETS_DIR="/etc/letsencrypt/$([ "${mode}" == "local" ] && echo "local" || echo "live")" + +declare -a availableComposes availableOrga +availableComposes=(${pahekoHost} ${cloudHost} ${dokuwikiHost} ${wordpressHost} ${matterHost} ${castopodHost}) +availableOrga=($(sed -e "s/\(.*\)[ \t]*#.*$/\1/" -e "s/^[ \t]*\(.*\)-orga$/\1/" -e "/^$/d" "${KAZ_CONF_DIR}/container-orga.list")) +availableProxyComposes=($(getList "${KAZ_CONF_DIR}/container-proxy.list")) + +# no more export in .env +export $(set | grep "domain=") + +export CMD="" +export SIMU="" +export CHANGE="" + +usage(){ + echo "Usage: ${PRG} list [friend-domain...]" + echo " ${PRG} [-n] add orga [${pahekoHost} ${cloudHost} ${dokuwikiHost} ${wordpressHost} ${matterHost} ${castopodHost}] [friend-domain...] " + echo " ${PRG} [-n] del [friend-domain...]" + echo " ${PRG} -l" + echo " -l short list" + echo " -renewAll" + echo " -h help" + echo " -n simulation" + exit 1 +} + +export CERT_CFG="${KAZ_CONF_PROXY_DIR}/foreign-certificate" + +createCert () { + ( + fileName="${LETS_DIR}/$1-key.pem" + #[ -f "${fileName}" ] || return + # if [ -f "${fileName}" ]; then + # fileTime=$(stat --format='%Y' "${fileName}") + # current_time=$(date +%s) + # if (( "${fileTime}" > ( "${current_time}" - ( 60 * 60 * 24 * 89 ) ) )); then + # exit + # fi + # fi + printKazMsg "create certificat for $1" + ${SIMU} docker exec -i proxyServ bash -c "/opt/certbot/bin/certbot certonly -n --nginx -d $1" + ) + +} + +for ARG in $@; do + case "${ARG}" in + '-h' | '-help' ) + usage + ;; + '-n' ) + shift + export SIMU="echo" + ;; + '-renewAll') + for i in $("${KAZ_BIN_DIR}/foreign-domain.sh" -l); do + echo "$i" + createCert "$i" |grep failed + done + exit + ;; + '-l') + for compose in ${availableComposes[@]} ; do + grep "server_name" "${KAZ_CONF_PROXY_DIR}/${compose}_kaz_name" | sed -e "s/[ \t]*\([^#]*\)#.*/\1/g" -e "/^$/d" -e "s/.*server_name[ \t]\([^ ;]*\).*/\1/" + done + exit + ;; + 'list'|'add'|'del' ) + shift + CMD="${ARG}" + break + ;; + * ) + usage + ;; + esac +done + +if [ -z "${CMD}" ]; then + echo "Commande missing" + usage +fi + +######################################## +badDomaine () { + [[ -z "$1" ]] && return 0; + [[ ! "$1" =~ ^[-.a-zA-Z0-9]*$ ]] && return 0; + return 1 +} +badOrga () { + [[ -z "$1" ]] && return 0; + [[ ! " ${availableOrga[*]} " =~ " $1 " ]] && return 0 + return 1 +} +badCompose () { + [[ -z "$1" ]] && return 0; + [[ ! " ${availableComposes[*]} " =~ " $1 " ]] && return 0 + return 1 +} + +######################################## +listServ () { + for compose in ${availableComposes[@]} ; do + sed -e "s/[ \t]*\([^#]*\)#.*/\1/g" -e "/^$/d" -e "s/.*server_name[ \t]\([^ ;]*\).*/\1 : ${compose}/" "${KAZ_CONF_PROXY_DIR}/${compose}_kaz_name" + done +} + +listOrgaServ () { + for compose in ${availableComposes[@]} ; do + sed -e "s/[ \t]*\([^#]*\)#.*/\1/g" -e "/^$/d" -e "s/\([^ ]*\)[ \t]*\([^ \t;]*\).*/\1 => \2 : ${compose}/" "${KAZ_CONF_PROXY_DIR}/${compose}_kaz_map" + done +} + +######################################## +list () { + previousOrga=$(listOrgaServ) + previousServ=$(listServ) + if [ $# -lt 1 ]; then + [ -n "${previousOrga}" ] && echo "${previousOrga}" + [ -n "${previousServ}" ] && echo "${previousServ}" + return + fi + for ARG in $@ + do + orga=$(echo "${previousOrga}" | grep "${ARG}.* =>") + serv=$(echo "${previousServ}" | grep "${ARG}.* =>") + [ -n "${orga}" ] && echo "${orga}" + [ -n "${serv}" ] && echo "${serv}" + done +} + +######################################## +add () { + # $1 : orga + # $2 : service + # $3 : friend-domain + [ $# -lt 3 ] && usage + badOrga $1 && echo "bad orga: ${RED}$1${NC} not in ${GREEN}${availableOrga[@]}${NC}" && usage + badCompose $2 && echo "bad compose: ${RED}$2${NC} not in ${GREEN}${availableComposes[@]}${NC}" && usage + ORGA=$1 + COMPOSE=$2 + shift; shift + CLOUD_SERVNAME="${ORGA}-${nextcloudServName}" + CLOUD_CONFIG="${DOCK_VOL}/orga_${ORGA}-cloudConfig/_data/config.php" + + # XXX check compose exist in orga ? + # /kaz/bin/kazList.sh service enable ${ORGA} + if [ "${COMPOSE}" = "${cloudHost}" ]; then + if ! [[ "$(docker ps -f name=${CLOUD_SERVNAME} | grep -w ${CLOUD_SERVNAME})" ]]; then + printKazError "${CLOUD_SERVNAME} not running... abort" + exit + fi + fi + + for FRIEND in $@; do + badDomaine "${FRIEND}" && echo "bad domaine: ${RED}${FRIEND}${NC}" && usage + done + + for FRIEND in $@; do + createCert "${FRIEND}" + if [ "${COMPOSE}" = "${cloudHost}" ]; then + IDX=$(awk 'BEGIN {flag=0; cpt=0} /trusted_domains/ {flag=1} /)/ {if (flag) {print cpt+1; exit 0}} / => / {if (flag && cpt<$1) cpt=$1}' "${CLOUD_CONFIG}") + ${SIMU} docker exec -ti -u 33 "${CLOUD_SERVNAME}" /var/www/html/occ config:system:set trusted_domains "${IDX}" --value="${FRIEND}" + fi + + previousOrga=$(listOrgaServ | grep "${FRIEND}") + [[ " ${previousOrga}" =~ " ${FRIEND} => ${ORGA} : ${COMPOSE}" ]] && echo " - already done" && continue + [[ " ${previousOrga}" =~ " ${FRIEND} " ]] && echo " - ${YELLOW}${BOLD}$(echo "${previousOrga}" | grep -e "${FRIEND}")${NC} must be deleted before" && return + if [[ -n "${SIMU}" ]] ; then + echo "${FRIEND} ${ORGA}; => ${KAZ_CONF_PROXY_DIR}/${COMPOSE}_kaz_map" + cat < ${KAZ_CONF_PROXY_DIR}/${COMPOSE}_kaz_name +server_name ${FRIEND}; +EOF + else + echo "${FRIEND} ${ORGA};" >> "${KAZ_CONF_PROXY_DIR}/${COMPOSE}_kaz_map" + cat >> "${KAZ_CONF_PROXY_DIR}/${COMPOSE}_kaz_name" <\s*'${FRIEND}'/d" -i "${CLOUD_CONFIG}" + fi + ${SIMU} sed -e "/^[ \t]*${FRIEND}[ \t]/d" -i "${KAZ_CONF_PROXY_DIR}/${COMPOSE}_kaz_map" + fi + if grep -q -e "^[ \t]*server_name ${FRIEND};" "${KAZ_CONF_PROXY_DIR}/${COMPOSE}_kaz_name" ; then + ${SIMU} sed -i "${KAZ_CONF_PROXY_DIR}/${COMPOSE}_kaz_name" \ + -e "/^[ \t]*server_name ${FRIEND};/d" + fi + done + echo "${PRG}: ${FRIEND} deleted" + CHANGE="del" + done +} + +######################################## +${CMD} $@ + +if [ -n "${CHANGE}" ] ; then + echo "Reload proxy conf" + for item in "${availableProxyComposes[@]}"; do + ${SIMU} ${KAZ_COMP_DIR}/${item}/proxy-gen.sh + ${SIMU} "${KAZ_COMP_DIR}/proxy/reload.sh" + done +fi + +######################################## diff --git a/bin/gestContainers.sh b/bin/gestContainers.sh new file mode 100755 index 0000000..1678d0c --- /dev/null +++ b/bin/gestContainers.sh @@ -0,0 +1,648 @@ +#!/bin/bash +# Script de manipulation des containers en masse +# init /versions / restart ... +# + +KAZ_ROOT=$(cd "$(dirname $0)"/..; pwd) +. $KAZ_ROOT/bin/.commonFunctions.sh +setKazVars +. $DOCKERS_ENV +. $KAZ_ROOT/secret/SetAllPass.sh + +PRG=$(basename $0) + +#TODO: ce tab doit être construit à partir de la liste des machines dispos et pas en dur +tab_sites_destinations_possibles=("kazoulet" "prod2" "prod1") + +#GLOBAL VARS +NAS_VOL="/mnt/disk-nas1/docker/volumes/" + +availableOrga=($(getList "${KAZ_CONF_DIR}/container-orga.list")) +AVAILABLE_ORGAS=${availableOrga[*]//-orga/} + +availableContainersCommuns=( $(getList "${KAZ_CONF_DIR}/container-withMail.list") $(getList "${KAZ_CONF_DIR}/container-withoutMail.list")) + +OPERATE_ON_MAIN= # par defaut NON on ne traite que des orgas +OPERATE_ON_NAS_ORGA="OUI" # par defaut oui, on va aussi sur les orgas du NAS +OPERATE_LOCAL_ORGA="OUI" # par defaut oui + +TEMPO_ACTION_STOP=2 # Lors de redémarrage avec tempo, on attend après le stop +TEMPO_ACTION_START=60 # Lors de redémarrage avec tempo, avant de reload le proxy + +CONTAINERS_TYPES= + +defaultContainersTypes="cloud agora wp wiki office paheko castopod" # les containers gérés par ce script. + +declare -A DockerServNames # le nom des containers correspondant +DockerServNames=( [cloud]="${nextcloudServName}" [agora]="${mattermostServName}" [wiki]="${dokuwikiServName}" [wp]="${wordpressServName}" [office]="${officeServName}" [paheko]="${pahekoServName}" [castopod]="${castopodServName}" ) + +declare -A FilterLsVolume # Pour trouver quel volume appartient à quel container +FilterLsVolume=( [cloud]="cloudMain" [agora]="matterConfig" [wiki]="wikiConf" [wp]="wordpress" [castopod]="castopodMedia" ) + +declare -A composeDirs # Le nom du repertoire compose pour le commun +composeDirs=( [cloud]="cloud" [agora]="mattermost" [wiki]="dokuwiki" [office]="collabora" [paheko]="paheko" [castopod]="castopod" ) + +declare -A serviceNames # Le nom du du service dans le dockerfile d'orga +serviceNames=( [cloud]="cloud" [agora]="agora" [wiki]="dokuwiki" [wp]="wordpress" [office]="collabora" [castopod]="castopod") + +declare -A subScripts +subScripts=( [cloud]="manageCloud.sh" [agora]="manageAgora.sh" [wiki]="manageWiki.sh" [wp]="manageWp.sh" [castopod]="manageCastopod.sh" ) + +declare -A OrgasOnNAS +declare -A OrgasLocales +declare -A NbOrgas +declare -A RunningOrgas +declare -A Posts + +QUIET="1" # redirection des echo + +OCCCOMANDS=() +MMCTLCOMANDS=() +EXECCOMANDS=() + +# CLOUD +APPLIS_PAR_DEFAUT="tasks calendar contacts bookmarks mail richdocuments external drawio snappymail ransomware_protection" #rainloop richdocumentscode + + +usage() { +echo "${PRG} [OPTION] [CONTAINERS_TYPES] [COMMANDES] [ORGAS] +Ce script regroupe l'ensemble des opérations que l'on souhaite automatiser sur plusieurs containers +Par defaut, sur les orgas, mais on peut aussi ajouter les communs + +OPTIONS + -h|--help Cette aide :-) + -n|--simu SIMULATION + -q|--quiet On ne parle pas (utile avec le -n pour avoir que les commandes) + -m|--main Traite aussi le container commun (cloud commun / agora commun / wiki commun) + -M Ne traite que le container commun, et pas les orgas + --nas Ne traite QUE les orgas sur le NAS + --local Ne traite pas les orgas sur le NAS + -v|--version Donne la version des containers et signale les MàJ + -l|--list Liste des containers (up / down, local ou nas) de cette machine + +CONTAINERS_TYPES + -cloud Pour agir sur les clouds + -agora Pour agir sur les agoras + -wp Les wp + -wiki Les wiki + -office Les collabora + -paheko Le paheko + -castopod Les castopod + +COMMANDES (on peut en mettre plusieurs dans l'ordre souhaité) + -I|--install L'initialisation du container + -t Redémarre avec tempo (docker-compose down puis sleep ${TEMPO_ACTION_STOP} puis up puis sleep ${TEMPO_ACTION_START}) + -r Redémarre sans tempo (docker restart) + -exec \"command\" Envoie une commande docker exec + + --optim Lance la procédure Nextcloud pour optimiser les performances ** ** + -occ \"command\" Envoie une commande via occ ** ** + -u Mets à jour les applis ** SPECIFIQUES ** + -i Install des applis ** CLOUD ** + -a \"app1 app2 ...\" Choix des appli à installer ou mettre à jour (entre guillemets) ** ** + -U|--upgrade Upgrade des clouds ** ** + + -mmctl \"command\" Envoie une commande via mmctl ** SPECIFIQUES ** + -p|--post \"team\" \"message\" Poste un message dans une team agora ** AGORA ** + +ORGAS + [orga1 orga2 ... ] on peut filtrer parmi : ${AVAILABLE_ORGAS} + + +Exemples : +${PRG} -office -m -r restart de tous les collaboras (libére RAM) +${PRG} -cloud -u -r -q -n Affiche toutes les commandes (-n -q ) pour mettre à jour toutes les applis des clouds + restart (-u -r) +${PRG} -p \"monorga:town-square\" \"Hello\" monorga # envoie Hello sur le centreville de l'orga monorga sur son mattermost dédié + +" +} + + +#################################################### +################ fonctions clefs ################### +#################################################### + +_populate_lists(){ + # récupère les listes d'orga à traiter + # on rempli les tableaux OrgasOnNAS / OrgasLocales / NbOrgas ... par type de container + + if [ -z "${CONTAINERS_TYPES}" ]; then + # wow, on traite tout le monde d'un coup... + CONTAINERS_TYPES="$defaultContainersTypes" + fi + + for TYPE in ${CONTAINERS_TYPES}; do + if [ -n "${FilterLsVolume[$TYPE]}" ] ; then # on regarde dans les volumes + [ -n "$OPERATE_ON_NAS_ORGA" ] && OrgasOnNAS["$TYPE"]=$( _getListOrgas ${NAS_VOL} ${FilterLsVolume[$TYPE]} ) + [ -n "$OPERATE_LOCAL_ORGA" ] && OrgasLocales["$TYPE"]=$( _getListOrgas ${DOCK_VOL} ${FilterLsVolume[$TYPE]} "SANSLN") + else # un docker ps s'il n'y a pas de volumes + [ -n "$OPERATE_LOCAL_ORGA" ] && OrgasLocales["$TYPE"]=$(docker ps --format '{{.Names}}' | grep ${DockerServNames[$TYPE]} | sed -e "s/-*${DockerServNames[$TYPE]}//") + fi + NbOrgas["$TYPE"]=$(($(echo ${OrgasOnNAS["$TYPE"]} | wc -w) + $(echo ${OrgasLocales["$TYPE"]} | wc -w))) + RunningOrgas["$TYPE"]=$(docker ps --format '{{.Names}}' | grep ${DockerServNames[$TYPE]} | sed -e "s/-*${DockerServNames[$TYPE]}//") + done +} + +_getListOrgas(){ + # retrouve les orgas à partir des volume présents + # $1 where to lookup + # $2 filter + # $3 removeSymbolicLinks + [ ! -d $1 ] || [ -z "$2" ] && return 1 # si le repertoire n'existe pas on skip + LIST=$(ls "${1}" | grep -i orga | grep -i "$2" | sed -e "s/-${2}$//g" | sed -e 's/^orga_//') + [ -n "$3" ] && LIST=$(ls -F "${1}" | grep '/' | grep -i orga | grep -i "$2" | sed -e "s/-${2}\/$//g" | sed -e 's/^orga_//') + LIST=$(comm -12 <(printf '%s\n' ${LIST} | sort) <(printf '%s\n' ${AVAILABLE_ORGAS} | sort)) + echo "$LIST" +} + +_executeFunctionForAll(){ + # Parcours des container et lancement des commandes + # Les commandes ont en derniers paramètres le type et l'orga et une string parmi KAZ/ORGANAS/ORGALOCAL pour savoir sur quoi on opère + # $1 function + # $2 nom de la commande + # $3 quel types de containers + # $4 params : quels paramètres à passer à la commande (les clefs sont #ORGA# #DOCKERSERVNAME# #SURNAS# #ISMAIN# #TYPE# #COMPOSEDIR# ) + for TYPE in ${3}; do + if [ -n "$OPERATE_ON_MAIN" ]; then + if [[ -n "${composeDirs[$TYPE]}" && "${availableContainersCommuns[*]}" =~ "${composeDirs[$TYPE]}" ]]; then # pas de cloud / agora / wp / wiki sur cette instance + Dockername=${DockerServNames[$TYPE]} + PARAMS=$(echo $4 | sed -e "s/#ORGA#//g;s/#DOCKERSERVNAME#/$Dockername/g;s/#ISMAIN#/OUI/g;s/#SURNAS#/NON/g;s/#TYPE#/$TYPE/g;s%#COMPOSEDIR#%${KAZ_COMP_DIR}/${composeDirs[$TYPE]}%g" ) + echo "-------- $2 $TYPE COMMUN ----------------------------" >& $QUIET + eval "$1" $PARAMS + fi + fi + if [[ ${NbOrgas[$TYPE]} -gt 0 ]]; then + echo "-------- $2 des $TYPE des ORGAS ----------------------------" >& $QUIET + COMPTEUR=1 + if [ -n "$OPERATE_LOCAL_ORGA" ]; then + for ORGA in ${OrgasLocales[$TYPE]}; do + Dockername=${ORGA}-${DockerServNames[$TYPE]} + PARAMS=$(echo $4 | sed -e "s/#ORGA#/${ORGA}/g;s/#DOCKERSERVNAME#/$Dockername/g;s/#ISMAIN#/NON/g;s/#SURNAS#/NON/g;s/#TYPE#/$TYPE/g;s%#COMPOSEDIR#%${KAZ_COMP_DIR}/${ORGA}-orga%g" ) + echo "${RED} ${ORGA}-orga ${NC}($COMPTEUR/${NbOrgas[$TYPE]})" >& $QUIET + eval "$1" $PARAMS + COMPTEUR=$((COMPTEUR + 1)) + done + fi + if [ -n "$OPERATE_ON_NAS_ORGA" ]; then + for ORGA in ${OrgasOnNAS[$TYPE]}; do + Dockername=${ORGA}-${DockerServNames[$TYPE]} + PARAMS=$(echo $4 | sed -e "s/#ORGA#/${ORGA}/g;s/#DOCKERSERVNAME#/$Dockername/g;s/#ISMAIN#/NON/g;s/#SURNAS#/OUI/g;s/#TYPE#/$TYPE/g;s%#COMPOSEDIR#%${KAZ_COMP_DIR}/${ORGA}-orga%g" ) + echo "${RED} ${ORGA}-orga ${NC}($COMPTEUR/${NbOrgas[$TYPE]})" >& $QUIET + eval "$1" $PARAMS + COMPTEUR=$((COMPTEUR + 1)) + done + fi + fi + done +} + + +############################################## +################ COMMANDES ################### +############################################## +Init(){ + # Initialisation des containers + echo "${NC}--------------------------------------------------------" >& $QUIET + echo "Initialisation" >& $QUIET + echo "--------------------------------------------------------" >& $QUIET + + _executeFunctionForAll "_initContainer" "Initialisation" "${CONTAINERS_TYPES[@]}" "#TYPE# #ISMAIN# #SURNAS# #ORGA# " +} + +restart-compose() { + # Parcours les containers et redémarre avec tempo + echo "${NC}--------------------------------------------------------" >& $QUIET + echo "DOCKER-COMPOSE DOWN puis sleep ${TEMPO_ACTION_STOP}" >& $QUIET + echo "DOCKER-COMPOSE UP puis sleep ${TEMPO_ACTION_START}" >& $QUIET + echo "de ${CONTAINERS_TYPES} pour $NB_ORGAS_STR" >& $QUIET + echo "--------------------------------------------------------" >& $QUIET + + _executeFunctionForAll "_restartContainerAvecTempo" "Restart" "${CONTAINERS_TYPES[@]}" "#TYPE# #ISMAIN# #COMPOSEDIR#" + + ${SIMU} sleep ${TEMPO_ACTION_START} + _reloadProxy + echo "--------------------------------------------------------" >& $QUIET + echo "${GREEN}FIN${NC} " >& $QUIET + echo "--------------------------------------------------------" >& $QUIET +} + +restart() { + # Parcours les containers et redémarre + echo "${NC}--------------------------------------------------------" >& $QUIET + echo "DOCKER RESTART des ${CONTAINERS_TYPES} pour $NB_ORGAS_STR" >& $QUIET + echo "--------------------------------------------------------" >& $QUIET + + _executeFunctionForAll "_restartContainer" "Restart" "${CONTAINERS_TYPES[@]}" "#DOCKERSERVNAME#" + + _reloadProxy + echo "--------------------------------------------------------" >& $QUIET + echo "${GREEN}FIN${NC} " >& $QUIET + echo "--------------------------------------------------------" >& $QUIET +} + +version(){ + # Parcours les containers et affiche leurs versions + echo "${NC}--------------------------------------------------------" >& $QUIET + echo "VERSIONS" >& $QUIET + echo "--------------------------------------------------------" >& $QUIET + _executeFunctionForAll "_versionContainer" "Version" "${CONTAINERS_TYPES[@]}" "#TYPE# #ISMAIN# #ORGA#" +} + + +listContainers(){ + echo "${NC}--------------------------------------------------------" + echo "LISTES" + echo "------------------------------------------------------------" + for TYPE in ${CONTAINERS_TYPES}; do + echo "****************** $TYPE ****************" + _listContainer "$TYPE" + done +} + + + +######################## Fonctions génériques ####################### + +_initContainer(){ + # $1 type + # $2 COMMUN + # $3 ON NAS + # $4 orgas + if [ -n "${subScripts[$1]}" ] ; then + evalStr="${KAZ_BIN_DIR}/${subScripts[$1]} --install" + if [ "$3" = "OUI" ]; then evalStr="${evalStr} -nas" ; fi + if [ ! "$QUIET" = "1" ]; then evalStr="${evalStr} -q" ; fi + if [ -n "$SIMU" ]; then evalStr="${evalStr} -n" ; fi + if [ ! "$2" = "OUI" ]; then evalStr="${evalStr} $4" ; fi + eval $evalStr + fi +} + +_restartContainer(){ + # $1 Dockername + echo -n "${NC}Redemarrage ... " >& $QUIET + ${SIMU} + ${SIMU} docker restart $1 + echo "${GREEN}OK${NC}" >& $QUIET +} + +_restartContainerAvecTempo(){ + # $1 type + # $2 main container + # $2 composeDir + dir=$3 + if [ -z $dir ]; then return 1; fi # le compose n'existe pas ... par exemple wordpress commun + cd "$dir" + echo -n "${NC}Arrêt ... " >& $QUIET + ${SIMU} + if [ "$2" = "OUI" ]; then ${SIMU} docker-compose stop ; + else ${SIMU} docker-compose stop "${serviceNames[$1]}" + fi + ${SIMU} sleep ${TEMPO_ACTION_STOP} + echo "${GREEN}OK${NC}" >& $QUIET + echo -n "${NC}Démarrage ... " >& $QUIET + if [ "$2" = "OUI" ]; then ${SIMU} docker-compose up -d ; + else ${SIMU} docker-compose up -d "${serviceNames[$1]}" + fi + ${SIMU} sleep ${TEMPO_ACTION_START} + echo "${GREEN}OK${NC}" >& $QUIET +} + + +_reloadProxy() { + availableProxyComposes=($(getList "${KAZ_CONF_DIR}/container-proxy.list")) + + for item in "${availableProxyComposes[@]}"; do + ${SIMU} ${KAZ_COMP_DIR}/${item}/reload.sh + done +} + +_versionContainer() { + # Affiche la version d'un container donné + # $1 type + # $2 COMMUN + # $3 orgas + if [ -n "${subScripts[$1]}" ] ; then + evalStr="${KAZ_BIN_DIR}/${subScripts[$1]} --version" + if [ ! "$2" = "OUI" ]; then evalStr="${evalStr} $3" ; fi + eval $evalStr + fi +} + + +_listContainer(){ + # pour un type donné (cloud / agora / wiki / wp), fait une synthèse de qui est up et down / nas ou local + # $1 type + RUNNING_FROM_NAS=$(comm -12 <(printf '%s\n' ${OrgasOnNAS[$1]} | sort) <(printf '%s\n' ${RunningOrgas[$1]} | sort) | sed -e ':a;N;$!ba;s/\n/ /g') + RUNNING_LOCAL=$(comm -12 <(printf '%s\n' ${OrgasLocales[$1]} | sort) <(printf '%s\n' ${RunningOrgas[$1]} | sort) | sed -e ':a;N;$!ba;s/\n/ /g') + # tu l'a vu la belle commande pour faire une exclusion de liste + DOWN_ON_NAS=$(comm -23 <(printf '%s\n' ${OrgasOnNAS[$1]} | sort) <(printf '%s\n' ${RunningOrgas[$1]} | sort) | sed -e ':a;N;$!ba;s/\n/ /g') + DOWN_LOCAL=$(comm -23 <(printf '%s\n' ${OrgasLocales[$1]} | sort) <(printf '%s\n' ${RunningOrgas[$1]} | sort)| sed -e ':a;N;$!ba;s/\n/ /g') + NB_SUR_NAS=$(echo ${OrgasOnNAS[$1]} | wc -w) + NB_LOCAUX=$(echo ${OrgasLocales[$1]} | wc -w) + NB_RUNNING_SUR_NAS=$(echo $RUNNING_FROM_NAS | wc -w) + NB_RUNNING_LOCALLY=$(echo $RUNNING_LOCAL | wc -w) + MAIN_RUNNING="${RED}DOWN${NC}" + if docker ps | grep -q " ${DockerServNames[$1]}" + then + MAIN_RUNNING="${GREEN}UP${NC}" + fi + + [ -n "${composeDirs[${1}]}" ] && echo "${NC}Le ${1} commun est $MAIN_RUNNING" + if [[ ${NbOrgas[$1]} -gt 0 ]]; then + ENLOCALSTR= + if [[ ${NB_RUNNING_SUR_NAS[$1]} -gt 0 ]]; then ENLOCALSTR=" en local" ; fi + echo "Orgas : $NB_RUNNING_LOCALLY / $NB_LOCAUX running ${1}$ENLOCALSTR" + echo "${NC}UP : ${GREEN}${RUNNING_LOCAL}" + echo "${NC}DOWN : ${RED}$DOWN_LOCAL${NC}" + if [[ ${NB_RUNNING_SUR_NAS[$1]} -gt 0 ]]; then + echo "${NC}Orgas : $NB_RUNNING_SUR_NAS / $NB_SUR_NAS running depuis le NAS :" + echo "${NC}UP : ${GREEN}${RUNNING_FROM_NAS}" + echo "${NC}DOWN : ${RED}$DOWN_ON_NAS${NC}" + fi + fi +} + + +######################################################### +############# FONCTIONS SPECIFIQUES ##################### +######################################################### + +################################## +############### CLOUD ############ +################################## + +UpgradeClouds() { + echo "${NC}--------------------------------------------------------" >& $QUIET + echo "UPGRADE des cloud" >& $QUIET + echo "--------------------------------------------------------" >& $QUIET + RunOCCCommand "upgrade" +} + +OptimiseClouds() { + echo "${NC}--------------------------------------------------------" >& $QUIET + echo "Optimisation des cloud" >& $QUIET + echo "--------------------------------------------------------" >& $QUIET + RunOCCCommand "db:add-missing-indices" "db:convert-filecache-bigint --no-interaction" +} + +InstallApps(){ + echo "${NC}--------------------------------------------------------" >& $QUIET + echo "INSTALL DES APPLIS sur les clouds : ${LISTE_APPS}" >& $QUIET + echo "-------------------------------------------------------------" >& $QUIET + if [ -z "${LISTE_APPS}" ]; then + echo "Aucune appli n'est précisée, j'installe les applis par défaut : ${APPLIS_PAR_DEFAUT}" >& $QUIET + LISTE_APPS="${APPLIS_PAR_DEFAUT}" + fi + PARAMS="-a \"$LISTE_APPS\"" + if [ ! "$QUIET" = "1" ]; then PARAMS="${PARAMS} -q" ; fi + if [ -n "$SIMU" ]; then PARAMS="${PARAMS} -n" ; fi + _executeFunctionForAll "${KAZ_BIN_DIR}/${subScripts["cloud"]} -i $PARAMS" "Install des applis" "cloud" "#ORGA#" +} + +UpdateApplis() { + echo "${NC}--------------------------------------------------------" >& $QUIET + echo "UPDATE DES APPLIS des cloud : ${LISTE_APPS}" >& $QUIET + echo "--------------------------------------------------------" >& $QUIET + PARAMS="-a ${LISTE_APPS}" + if [ -z "${LISTE_APPS}" ]; then + echo "Aucune appli n'est précisée, je les met toutes à jour! " >& $QUIET + PARAMS= + fi + if [ ! "$QUIET" = "1" ]; then PARAMS="${PARAMS} -q" ; fi + if [ -n "$SIMU" ]; then PARAMS="${PARAMS} -n" ; fi + _executeFunctionForAll "${KAZ_BIN_DIR}/${subScripts["cloud"]} -u $PARAMS" "Maj des applis" "cloud" "#ORGA#" +} + + +################################## +############### AGORA ############ +################################## + + +PostMessages(){ + echo "${NC}--------------------------------------------------------" >& $QUIET + echo "Envoi de messages sur mattermost" >& $QUIET + echo "--------------------------------------------------------" >& $QUIET + + for TEAM in "${!Posts[@]}" + do + MSG=${Posts[$TEAM]/\"/\\\"} + PARAMS="-p \"$TEAM\" \"$MSG\"" + if [ ! "$QUIET" = "1" ]; then PARAMS="${PARAMS} -q" ; fi + if [ -n "$SIMU" ]; then PARAMS="${PARAMS} -n" ; fi + _executeFunctionForAll "${KAZ_BIN_DIR}/${subScripts["agora"]} $PARAMS" "Post vers $TEAM sur l'agora" "agora" "#ORGA#" + done +} + + +########## LANCEMENT COMMANDES OCC / MMCTL ############ + +RunCommands() { + echo "${NC}--------------------------------------------------------" >& $QUIET + echo "Envoi de commandes en direct" >& $QUIET + echo "--------------------------------------------------------" >& $QUIET + # $1 OCC / MMCTL / EXEC + # $ suivants : les commandes + for command in "${@:2}" + do + if [ $1 = "OCC" ]; then RunOCCCommand "$command" ; fi + if [ $1 = "MMCTL" ]; then RunMMCTLCommand "$command" ; fi + if [ $1 = "EXEC" ]; then RunEXECCommand "$command" ; fi + done +} + +_runSingleOccCommand(){ + # $1 Command + # $2 Dockername + ${SIMU} docker exec -u 33 $2 /var/www/html/occ $1 +} + +_runSingleMmctlCommand(){ + # $1 Command + # $2 Dockername + ${SIMU} docker exec $2 bin/mmctl $1 +} + +_runSingleExecCommand(){ + # $1 Command + # $2 Dockername + ${SIMU} docker exec $2 $1 +} + +RunOCCCommand() { + # $1 Command + _executeFunctionForAll "_runSingleOccCommand \"${1}\"" "OCC $1" "cloud" "#DOCKERSERVNAME#" +} + +RunMMCTLCommand() { + # $1 Command + _executeFunctionForAll "_runSingleMmctlCommand \"${1}\"" "MMCTL $1" "agora" "#DOCKERSERVNAME#" +} + +RunEXECCommand() { + # $1 Command + _executeFunctionForAll "_runSingleExecCommand \"${1}\"" "docker exec $1" "${CONTAINERS_TYPES[@]}" "#DOCKERSERVNAME#" +} + + + +########## Main ################# +for ARG in "$@"; do + if [ -n "${GETOCCCOMAND}" ]; then # après un -occ + OCCCOMANDS+=("${ARG}") + GETOCCCOMAND= + elif [ -n "${GETEXECCOMAND}" ]; then # après un -exec + EXECCOMANDS+=("${ARG}") + GETEXECCOMAND= + elif [ -n "${GETAPPS}" ]; then # après un -a + LISTE_APPS="${LISTE_APPS} ${ARG}" + GETAPPS="" + elif [ -n "${GETMMCTLCOMAND}" ]; then # après un -mmctl + MMCTLCOMANDS+=("${ARG}") + GETMMCTLCOMAND= + elif [ -n "${GETTEAM}" ]; then # après un --post + GETMESSAGE="now" + GETTEAM="" + TEAM="${ARG}" + elif [ -n "${GETMESSAGE}" ]; then # après un --post "team:channel" + if [[ $TEAM == "-*" && ${#TEAM} -le 5 ]]; then echo "J'envoie mon message à \"${TEAM}\" ?? Arf, ça me plait pas j'ai l'impression que tu t'es planté sur la commande."; usage ; exit 1 ; fi + if [[ $ARG == "-*" && ${#ARG} -le 5 ]]; then echo "J'envoie le message \"${ARG}\" ?? Arf, ça me plait pas j'ai l'impression que tu t'es planté sur la commande."; usage ; exit 1 ; fi + if [[ ! $TEAM =~ .*:.+ ]]; then echo "Il faut mettre un destinataire sous la forme team:channel. Recommence !"; usage ; exit 1 ; fi + Posts+=( ["${TEAM}"]="$ARG" ) + GETMESSAGE="" + TEAM="" + else + case "${ARG}" in + '-h' | '--help' ) + usage && exit ;; + '-n' | '--simu') + SIMU="echo" ;; + '-q' ) + QUIET="/dev/null" ;; + '-m' | '--main' ) + OPERATE_ON_MAIN="OUI-OUI" ;; + '-M' ) + AVAILABLE_ORGAS= && OPERATE_ON_MAIN="OUI-OUI" ;; #pas d'orgas + '--nas' | '-nas' ) + OPERATE_LOCAL_ORGA= ;; # pas les locales + '--local' | '-local' ) + OPERATE_ON_NAS_ORGA= ;; # pas celles sur NAS + '-cloud'|'--cloud') + CONTAINERS_TYPES="${CONTAINERS_TYPES} cloud" ;; + '-agora'|'--agora'|'-mm'|'--mm'|'-matter'*|'--matter'*) + CONTAINERS_TYPES="${CONTAINERS_TYPES} agora" ;; + '-wiki'|'--wiki') + CONTAINERS_TYPES="${CONTAINERS_TYPES} wiki" ;; + '-wp'|'--wp') + CONTAINERS_TYPES="${CONTAINERS_TYPES} wp" ;; + '-office'|'--office'|'-collab'*|'--collab'*) + CONTAINERS_TYPES="${CONTAINERS_TYPES} office" ;; + '-paheko'|'--paheko') + CONTAINERS_TYPES="${CONTAINERS_TYPES} paheko" ;; + '-pod'|'--pod'|'-castopod'|'--castopod') + CONTAINERS_TYPES="${CONTAINERS_TYPES} castopod" ;; + '-t' ) + COMMANDS="${COMMANDS} RESTART-COMPOSE" ;; + '-r' ) + COMMANDS="${COMMANDS} RESTART-DOCKER" ;; + '-l' | '--list' ) + COMMANDS="$(echo "${COMMANDS} LIST" | sed "s/\s/\n/g" | sort | uniq)" ;; + '-v' | '--version') + COMMANDS="$(echo "${COMMANDS} VERSION" | sed "s/\s/\n/g" | sort | uniq)" ;; + '-I' | '--install' ) + COMMANDS="$(echo "${COMMANDS} INIT" | sed "s/\s/\n/g" | sort | uniq)" ;; # le sed sort uniq, c'est pour pas l'avoir en double + '-U' | '--upgrade') + COMMANDS="$(echo "${COMMANDS} UPGRADE" | sed "s/\s/\n/g" | sort | uniq)" ;; + '--optim' ) + COMMANDS="$(echo "${COMMANDS} OPTIMISE-CLOUD" | sed "s/\s/\n/g" | sort | uniq)" ;; + '-u' ) + COMMANDS="$(echo "${COMMANDS} UPDATE-CLOUD-APP" | sed "s/\s/\n/g" | sort | uniq)" ;; + '-i' ) + COMMANDS="$(echo "${COMMANDS} INSTALL-CLOUD-APP" | sed "s/\s/\n/g" | sort | uniq)" ;; + '-a' ) + GETAPPS="now" ;; + '-occ' ) + COMMANDS="$(echo "${COMMANDS} RUN-CLOUD-OCC" | sed "s/\s/\n/g" | sort | uniq)" + GETOCCCOMAND="now" ;; + '-mmctl' ) + COMMANDS="$(echo "${COMMANDS} RUN-AGORA-MMCTL" | sed "s/\s/\n/g" | sort | uniq)" + GETMMCTLCOMAND="now" ;; + '-exec' ) + COMMANDS="$(echo "${COMMANDS} RUN-DOCKER-EXEC" | sed "s/\s/\n/g" | sort | uniq)" + GETEXECCOMAND="now" ;; + '-p' | '--post' ) + COMMANDS="$(echo "${COMMANDS} POST-AGORA" | sed "s/\s/\n/g" | sort | uniq)" + GETTEAM="now" ;; + '-*' ) # ignore + ;; + *) + GIVEN_ORGA="${GIVEN_ORGA} ${ARG%-orga}" + ;; + esac + fi +done + + +if [[ "${COMMANDS[*]}" =~ "RESTART-COMPOSE" && "${COMMANDS[*]}" =~ "RESTART-TYPE" ]]; then + echo "Je restarte via docker-compose ou via docker mais pas les deux !" + usage + exit 1 +fi +if [ -z "${COMMANDS}" ]; then + usage && exit +fi +if [ -n "${GIVEN_ORGA}" ]; then + # intersection des 2 listes : quelle commande de ouf !! + AVAILABLE_ORGAS=$(comm -12 <(printf '%s\n' ${AVAILABLE_ORGAS} | sort) <(printf '%s\n' ${GIVEN_ORGA} | sort)) +fi + +NB_ORGAS=$(echo "${AVAILABLE_ORGAS}" | wc -w ) + +if [[ $NB_ORGAS = 0 && -z "${OPERATE_ON_MAIN}" ]]; then + echo "Aucune orga trouvée." + exit 1 +fi + +NB_ORGAS_STR="$NB_ORGAS orgas" +[ -n "${OPERATE_ON_MAIN}" ] && NB_ORGAS_STR="$NB_ORGAS_STR + les communs" + +_populate_lists # on récupère les clouds / agora / wiki / wp correspondants aux orga + +if [[ $NB_ORGAS -gt 2 && "${COMMANDS[*]}" =~ 'INIT' ]]; then + ETLECLOUDCOMMUN= + [ -n "${OPERATE_ON_MAIN}" ] && ETLECLOUDCOMMUN=" ainsi que les containers commun" + echo "On s'apprête à initialiser les ${CONTAINERS_TYPES} suivants : ${AVAILABLE_ORGAS}${ETLECLOUDCOMMUN}" + checkContinue +fi + +for COMMAND in ${COMMANDS}; do + case "${COMMAND}" in + 'LIST' ) + listContainers && exit ;; + 'VERSION' ) + version && exit ;; + 'OPTIMISE-CLOUD' ) + OptimiseClouds ;; + 'RESTART-COMPOSE' ) + restart-compose ;; + 'RESTART-DOCKER' ) + restart ;; + 'UPDATE-CLOUD-APP' ) + UpdateApplis ;; + 'UPGRADE' ) + UpgradeClouds ;; + 'INIT' ) + Init ;; + 'INSTALL-CLOUD-APP' ) + InstallApps ;; + 'RUN-CLOUD-OCC' ) + RunCommands "OCC" "${OCCCOMANDS[@]}" ;; + 'RUN-AGORA-MMCTL' ) + RunCommands "MMCTL" "${MMCTLCOMANDS[@]}" ;; + 'RUN-DOCKER-EXEC' ) + RunCommands "EXEC" "${EXECCOMANDS[@]}" ;; + 'POST-AGORA' ) + PostMessages ${Posts} ;; + esac +done diff --git a/bin/gestContainers_v2.sh b/bin/gestContainers_v2.sh new file mode 100755 index 0000000..840ab8d --- /dev/null +++ b/bin/gestContainers_v2.sh @@ -0,0 +1,659 @@ +#!/bin/bash +# Script de manipulation des containers en masse +# init /versions / restart ... +# + +KAZ_ROOT=$(cd "$(dirname $0)"/..; pwd) +. $KAZ_ROOT/bin/.commonFunctions.sh +setKazVars +. $DOCKERS_ENV +. $KAZ_ROOT/secret/SetAllPass.sh + +PRG=$(basename $0) + +tab_sites_destinations_possibles=($(get_Serveurs_Kaz)) +# ${SIMU} ssh -p 2201 root@${SITE_DST}.${domain} "${CMD}" +# SITE_DST="${tab_sites_destinations_possibles[1]}" +# ${tab_sites_destinations_possibles[@]} + +#GLOBAL VARS +NAS_VOL="/mnt/disk-nas1/docker/volumes/" + +availableOrga=($(getList "${KAZ_CONF_DIR}/container-orga.list")) +AVAILABLE_ORGAS=${availableOrga[*]//-orga/} + +availableContainersCommuns=( $(getList "${KAZ_CONF_DIR}/container-withMail.list") $(getList "${KAZ_CONF_DIR}/container-withoutMail.list")) + +OPERATE_ON_MAIN= # par defaut NON on ne traite que des orgas +OPERATE_ON_NAS_ORGA="OUI" # par defaut oui, on va aussi sur les orgas du NAS +OPERATE_LOCAL_ORGA="OUI" # par defaut oui + +TEMPO_ACTION_STOP=2 # Lors de redémarrage avec tempo, on attend après le stop +TEMPO_ACTION_START=120 # Lors de redémarrage avec tempo, avant de reload le proxy + +CONTAINERS_TYPES= + +defaultContainersTypes="cloud agora wp wiki office paheko" # les containers gérés par ce script. + +declare -A DockerServNames # le nom des containers correspondant +DockerServNames=( [cloud]="${nextcloudServName}" [agora]="${mattermostServName}" [wiki]="${dokuwikiServName}" [wp]="${wordpressServName}" [office]="${officeServName}" [paheko]="${pahekoServName}" ) + +declare -A FilterLsVolume # Pour trouver quel volume appartient à quel container +FilterLsVolume=( [cloud]="cloudMain" [agora]="matterConfig" [wiki]="wikiConf" [wp]="wordpress" ) + +declare -A composeDirs # Le nom du repertoire compose pour le commun +composeDirs=( [cloud]="cloud" [agora]="mattermost" [wiki]="dokuwiki" [office]="collabora" [paheko]="paheko" ) + +declare -A serviceNames # Le nom du du service dans le dockerfile d'orga +serviceNames=( [cloud]="cloud" [agora]="agora" [wiki]="dokuwiki" [wp]="wordpress" [office]="collabora") + +declare -A subScripts +subScripts=( [cloud]="manageCloud.sh" [agora]="manageAgora.sh" [wiki]="manageWiki.sh" [wp]="manageWp.sh" ) + +declare -A OrgasOnNAS +declare -A OrgasLocales +declare -A NbOrgas +declare -A RunningOrgas +declare -A Posts + +QUIET="1" # redirection des echo + +OCCCOMANDS=() +MMCTLCOMANDS=() +EXECCOMANDS=() + +# CLOUD +APPLIS_PAR_DEFAUT="tasks calendar contacts bookmarks richdocuments external drawio snappymail" + +usage() { +echo "${PRG} [OPTION] [CONTAINERS_TYPES] [COMMANDES] [SERVEURS] [ORGAS] +Ce script regroupe l'ensemble des opérations que l'on souhaite automatiser sur plusieurs containers, sur un ou plusieurs sites. +Par defaut, sur les orgas, mais on peut aussi ajouter les communs + +OPTIONS + -h|--help Cette aide :-) + -n|--simu SIMULATION + -q|--quiet On ne parle pas (utile avec le -n pour avoir que les commandes) + -m|--main Traite aussi le container commun (cloud commun / agora commun / wiki commun) + -M Ne traite que le container commun, et pas les orgas + --nas Ne traite QUE les orgas sur le NAS + --local Ne traite pas les orgas sur le NAS + -v|--version Donne la version des containers et signale les MàJ + -l|--list Liste des containers (up / down, local ou nas) de cette machine + +CONTAINERS_TYPES + -cloud Pour agir sur les clouds + -agora Pour agir sur les agoras + -wp Les wp + -wiki Les wiki + -office Les collabora + +COMMANDES (on peut en mettre plusieurs dans l'ordre souhaité) + -I|--install L'initialisation du container + -t Redémarre avec tempo (docker-compose down puis sleep ${TEMPO_ACTION_STOP} puis up puis sleep ${TEMPO_ACTION_START}) + -r Redémarre sans tempo (docker restart) + -exec \"command\" Envoie une commande docker exec + + --optim Lance la procédure Nextcloud pour optimiser les performances ** ** + -occ \"command\" Envoie une commande via occ ** ** + -u Mets à jour les applis ** SPECIFIQUES ** + -i Install des applis ** CLOUD ** + -a \"app1 app2 ...\" Choix des appli à installer ou mettre à jour (entre guillemets) ** ** + -U|--upgrade Upgrade des clouds ** ** + + -mmctl \"command\" Envoie une commande via mmctl ** SPECIFIQUES ** + -p|--post \"team\" \"message\" Poste un message dans une team agora ** AGORA ** + +SERVEURS + --all-srv Lance sur tous les serveurs ${tab_sites_destinations_possibles[@]}, sinon c'est uniquement sur ${site} + +ORGAS sur ${site} + [orga1 orga2 ... ] on peut filtrer parmi : ${AVAILABLE_ORGAS} + + +Exemples : +${PRG} -office -m -r # restart de tous les collaboras (libére RAM) +${PRG} -cloud -u -r -q -n # affiche toutes les commandes (-n -q ) pour mettre à jour toutes les applis des clouds + restart (-u -r) +${PRG} -p \"monorga:town-square\" \"Hello\" monorga # envoie Hello sur le centreville de l'orga monorga sur son mattermost dédié +${PRG} -cloud -occ \"config:system:set default_phone_region --value='FR'\" --all-srv # modifie la variable default_phone_region dans le config.php de tous les clouds de tous les serveurs +" +} + + +#################################################### +################ fonctions clefs ################### +#################################################### + +_populate_lists(){ + # récupère les listes d'orga à traiter + # on rempli les tableaux OrgasOnNAS / OrgasLocales / NbOrgas ... par type de container + + if [ -z "${CONTAINERS_TYPES}" ]; then + # wow, on traite tout le monde d'un coup... + CONTAINERS_TYPES="$defaultContainersTypes" + fi + + for TYPE in ${CONTAINERS_TYPES}; do + if [ -n "${FilterLsVolume[$TYPE]}" ] ; then # on regarde dans les volumes + [ -n "$OPERATE_ON_NAS_ORGA" ] && OrgasOnNAS["$TYPE"]=$( _getListOrgas ${NAS_VOL} ${FilterLsVolume[$TYPE]} ) + [ -n "$OPERATE_LOCAL_ORGA" ] && OrgasLocales["$TYPE"]=$( _getListOrgas ${DOCK_VOL} ${FilterLsVolume[$TYPE]} "SANSLN") + else # un docker ps s'il n'y a pas de volumes + [ -n "$OPERATE_LOCAL_ORGA" ] && OrgasLocales["$TYPE"]=$(docker ps --format '{{.Names}}' | grep ${DockerServNames[$TYPE]} | sed -e "s/-*${DockerServNames[$TYPE]}//") + fi + NbOrgas["$TYPE"]=$(($(echo ${OrgasOnNAS["$TYPE"]} | wc -w) + $(echo ${OrgasLocales["$TYPE"]} | wc -w))) + RunningOrgas["$TYPE"]=$(docker ps --format '{{.Names}}' | grep ${DockerServNames[$TYPE]} | sed -e "s/-*${DockerServNames[$TYPE]}//") + done +} + +_getListOrgas(){ + # retrouve les orgas à partir des volume présents + # $1 where to lookup + # $2 filter + # $3 removeSymbolicLinks + [ ! -d $1 ] || [ -z "$2" ] && return 1 # si le repertoire n'existe pas on skip + LIST=$(ls "${1}" | grep -i orga | grep -i "$2" | sed -e "s/-${2}$//g" | sed -e 's/^orga_//') + [ -n "$3" ] && LIST=$(ls -F "${1}" | grep '/' | grep -i orga | grep -i "$2" | sed -e "s/-${2}\/$//g" | sed -e 's/^orga_//') + LIST=$(comm -12 <(printf '%s\n' ${LIST} | sort) <(printf '%s\n' ${AVAILABLE_ORGAS} | sort)) + echo "$LIST" +} + +_executeFunctionForAll(){ + # Parcours des container et lancement des commandes + # Les commandes ont en derniers paramètres le type et l'orga et une string parmi KAZ/ORGANAS/ORGALOCAL pour savoir sur quoi on opère + # $1 function + # $2 nom de la commande + # $3 quel types de containers + # $4 params : quels paramètres à passer à la commande (les clefs sont #ORGA# #DOCKERSERVNAME# #SURNAS# #ISMAIN# #TYPE# #COMPOSEDIR# ) + for TYPE in ${3}; do + if [ -n "$OPERATE_ON_MAIN" ]; then + if [[ -n "${composeDirs[$TYPE]}" && "${availableContainersCommuns[*]}" =~ "${composeDirs[$TYPE]}" ]]; then # pas de cloud / agora / wp / wiki sur cette instance + Dockername=${DockerServNames[$TYPE]} + PARAMS=$(echo $4 | sed -e "s/#ORGA#//g;s/#DOCKERSERVNAME#/$Dockername/g;s/#ISMAIN#/OUI/g;s/#SURNAS#/NON/g;s/#TYPE#/$TYPE/g;s%#COMPOSEDIR#%${KAZ_COMP_DIR}/${composeDirs[$TYPE]}%g" ) + echo "-------- $2 $TYPE COMMUN ----------------------------" >& $QUIET + eval "$1" $PARAMS + fi + fi + if [[ ${NbOrgas[$TYPE]} -gt 0 ]]; then + echo "-------- $2 des $TYPE des ORGAS ----------------------------" >& $QUIET + COMPTEUR=1 + if [ -n "$OPERATE_LOCAL_ORGA" ]; then + for ORGA in ${OrgasLocales[$TYPE]}; do + Dockername=${ORGA}-${DockerServNames[$TYPE]} + PARAMS=$(echo $4 | sed -e "s/#ORGA#/${ORGA}/g;s/#DOCKERSERVNAME#/$Dockername/g;s/#ISMAIN#/NON/g;s/#SURNAS#/NON/g;s/#TYPE#/$TYPE/g;s%#COMPOSEDIR#%${KAZ_COMP_DIR}/${ORGA}-orga%g" ) + echo "${RED} ${ORGA}-orga ${NC}($COMPTEUR/${NbOrgas[$TYPE]})" >& $QUIET + eval "$1" $PARAMS + COMPTEUR=$((COMPTEUR + 1)) + done + fi + if [ -n "$OPERATE_ON_NAS_ORGA" ]; then + for ORGA in ${OrgasOnNAS[$TYPE]}; do + Dockername=${ORGA}-${DockerServNames[$TYPE]} + PARAMS=$(echo $4 | sed -e "s/#ORGA#/${ORGA}/g;s/#DOCKERSERVNAME#/$Dockername/g;s/#ISMAIN#/NON/g;s/#SURNAS#/OUI/g;s/#TYPE#/$TYPE/g;s%#COMPOSEDIR#%${KAZ_COMP_DIR}/${ORGA}-orga%g" ) + echo "${RED} ${ORGA}-orga ${NC}($COMPTEUR/${NbOrgas[$TYPE]})" >& $QUIET + eval "$1" $PARAMS + COMPTEUR=$((COMPTEUR + 1)) + done + fi + fi + done +} + + +############################################## +################ COMMANDES ################### +############################################## +Init(){ + # Initialisation des containers + echo "${NC}--------------------------------------------------------" >& $QUIET + echo "Initialisation" >& $QUIET + echo "--------------------------------------------------------" >& $QUIET + + _executeFunctionForAll "_initContainer" "Initialisation" "${CONTAINERS_TYPES[@]}" "#TYPE# #ISMAIN# #SURNAS# #ORGA# " +} + +restart-compose() { + # Parcours les containers et redémarre avec tempo + echo "${NC}--------------------------------------------------------" >& $QUIET + echo "DOCKER-COMPOSE DOWN puis sleep ${TEMPO_ACTION_STOP}" >& $QUIET + echo "DOCKER-COMPOSE UP puis sleep ${TEMPO_ACTION_START}" >& $QUIET + echo "de ${CONTAINERS_TYPES} pour $NB_ORGAS_STR" >& $QUIET + echo "--------------------------------------------------------" >& $QUIET + + _executeFunctionForAll "_restartContainerAvecTempo" "Restart" "${CONTAINERS_TYPES[@]}" "#TYPE# #ISMAIN# #COMPOSEDIR#" + + ${SIMU} sleep ${TEMPO_ACTION_START} + _reloadProxy + echo "--------------------------------------------------------" >& $QUIET + echo "${GREEN}FIN${NC} " >& $QUIET + echo "--------------------------------------------------------" >& $QUIET +} + +restart() { + # Parcours les containers et redémarre + echo "${NC}--------------------------------------------------------" >& $QUIET + echo "DOCKER RESTART des ${CONTAINERS_TYPES} pour $NB_ORGAS_STR" >& $QUIET + echo "--------------------------------------------------------" >& $QUIET + + _executeFunctionForAll "_restartContainer" "Restart" "${CONTAINERS_TYPES[@]}" "#DOCKERSERVNAME#" + + _reloadProxy + echo "--------------------------------------------------------" >& $QUIET + echo "${GREEN}FIN${NC} " >& $QUIET + echo "--------------------------------------------------------" >& $QUIET +} + +version(){ + # Parcours les containers et affiche leurs versions + echo "${NC}--------------------------------------------------------" >& $QUIET + echo "VERSIONS" >& $QUIET + echo "--------------------------------------------------------" >& $QUIET + _executeFunctionForAll "_versionContainer" "Version" "${CONTAINERS_TYPES[@]}" "#TYPE# #ISMAIN# #ORGA#" +} + + +listContainers(){ + echo "${NC}--------------------------------------------------------" + echo "LISTES" + echo "------------------------------------------------------------" + for TYPE in ${CONTAINERS_TYPES}; do + echo "****************** $TYPE ****************" + _listContainer "$TYPE" + done +} + + + +######################## Fonctions génériques ####################### + +_initContainer(){ + # $1 type + # $2 COMMUN + # $3 ON NAS + # $4 orgas + if [ -n "${subScripts[$1]}" ] ; then + evalStr="${KAZ_BIN_DIR}/${subScripts[$1]} --install" + if [ "$3" = "OUI" ]; then evalStr="${evalStr} -nas" ; fi + if [ ! "$QUIET" = "1" ]; then evalStr="${evalStr} -q" ; fi + if [ -n "$SIMU" ]; then evalStr="${evalStr} -n" ; fi + if [ ! "$2" = "OUI" ]; then evalStr="${evalStr} $4" ; fi + eval $evalStr + fi +} + +_restartContainer(){ + # $1 Dockername + echo -n "${NC}Redemarrage ... " >& $QUIET + ${SIMU} + ${SIMU} docker restart $1 + echo "${GREEN}OK${NC}" >& $QUIET +} + +_restartContainerAvecTempo(){ + # $1 type + # $2 main container + # $2 composeDir + dir=$3 + if [ -z $dir ]; then return 1; fi # le compose n'existe pas ... par exemple wordpress commun + cd "$dir" + echo -n "${NC}Arrêt ... " >& $QUIET + ${SIMU} + if [ "$2" = "OUI" ]; then ${SIMU} docker-compose stop ; + else ${SIMU} docker-compose stop "${serviceNames[$1]}" + fi + ${SIMU} sleep ${TEMPO_ACTION_STOP} + echo "${GREEN}OK${NC}" >& $QUIET + echo -n "${NC}Démarrage ... " >& $QUIET + if [ "$2" = "OUI" ]; then ${SIMU} docker-compose up -d ; + else ${SIMU} docker-compose up -d "${serviceNames[$1]}" + fi + ${SIMU} sleep ${TEMPO_ACTION_START} + echo "${GREEN}OK${NC}" >& $QUIET +} + + +_reloadProxy() { + availableProxyComposes=($(getList "${KAZ_CONF_DIR}/container-proxy.list")) + + for item in "${availableProxyComposes[@]}"; do + ${SIMU} ${KAZ_COMP_DIR}/${item}/reload.sh + done +} + +_versionContainer() { + # Affiche la version d'un container donné + # $1 type + # $2 COMMUN + # $3 orgas + if [ -n "${subScripts[$1]}" ] ; then + evalStr="${KAZ_BIN_DIR}/${subScripts[$1]} --version" + if [ ! "$2" = "OUI" ]; then evalStr="${evalStr} $3" ; fi + eval $evalStr + fi +} + + +_listContainer(){ + # pour un type donné (cloud / agora / wiki / wp), fait une synthèse de qui est up et down / nas ou local + # $1 type + RUNNING_FROM_NAS=$(comm -12 <(printf '%s\n' ${OrgasOnNAS[$1]} | sort) <(printf '%s\n' ${RunningOrgas[$1]} | sort) | sed -e ':a;N;$!ba;s/\n/ /g') + RUNNING_LOCAL=$(comm -12 <(printf '%s\n' ${OrgasLocales[$1]} | sort) <(printf '%s\n' ${RunningOrgas[$1]} | sort) | sed -e ':a;N;$!ba;s/\n/ /g') + # tu l'a vu la belle commande pour faire une exclusion de liste + DOWN_ON_NAS=$(comm -23 <(printf '%s\n' ${OrgasOnNAS[$1]} | sort) <(printf '%s\n' ${RunningOrgas[$1]} | sort) | sed -e ':a;N;$!ba;s/\n/ /g') + DOWN_LOCAL=$(comm -23 <(printf '%s\n' ${OrgasLocales[$1]} | sort) <(printf '%s\n' ${RunningOrgas[$1]} | sort)| sed -e ':a;N;$!ba;s/\n/ /g') + NB_SUR_NAS=$(echo ${OrgasOnNAS[$1]} | wc -w) + NB_LOCAUX=$(echo ${OrgasLocales[$1]} | wc -w) + NB_RUNNING_SUR_NAS=$(echo $RUNNING_FROM_NAS | wc -w) + NB_RUNNING_LOCALLY=$(echo $RUNNING_LOCAL | wc -w) + MAIN_RUNNING="${RED}DOWN${NC}" + if docker ps | grep -q " ${DockerServNames[$1]}" + then + MAIN_RUNNING="${GREEN}UP${NC}" + fi + + [ -n "${composeDirs[${1}]}" ] && echo "${NC}Le ${1} commun est $MAIN_RUNNING" + if [[ ${NbOrgas[$1]} -gt 0 ]]; then + ENLOCALSTR= + if [[ ${NB_RUNNING_SUR_NAS[$1]} -gt 0 ]]; then ENLOCALSTR=" en local" ; fi + echo "Orgas : $NB_RUNNING_LOCALLY / $NB_LOCAUX running ${1}$ENLOCALSTR" + echo "${NC}UP : ${GREEN}${RUNNING_LOCAL}" + echo "${NC}DOWN : ${RED}$DOWN_LOCAL${NC}" + if [[ ${NB_RUNNING_SUR_NAS[$1]} -gt 0 ]]; then + echo "${NC}Orgas : $NB_RUNNING_SUR_NAS / $NB_SUR_NAS running depuis le NAS :" + echo "${NC}UP : ${GREEN}${RUNNING_FROM_NAS}" + echo "${NC}DOWN : ${RED}$DOWN_ON_NAS${NC}" + fi + fi +} + + +######################################################### +############# FONCTIONS SPECIFIQUES ##################### +######################################################### + +################################## +############### CLOUD ############ +################################## + +UpgradeClouds() { + echo "${NC}--------------------------------------------------------" >& $QUIET + echo "UPGRADE des cloud" >& $QUIET + echo "--------------------------------------------------------" >& $QUIET + RunOCCCommand "upgrade" +} + +OptimiseClouds() { + echo "${NC}--------------------------------------------------------" >& $QUIET + echo "Optimisation des cloud" >& $QUIET + echo "--------------------------------------------------------" >& $QUIET + RunOCCCommands "db:add-missing-indices" "db:convert-filecache-bigint --no-interaction" +} + +InstallApps(){ + echo "${NC}--------------------------------------------------------" >& $QUIET + echo "INSTALL DES APPLIS sur les clouds : ${LISTE_APPS}" >& $QUIET + echo "-------------------------------------------------------------" >& $QUIET + if [ -z "${LISTE_APPS}" ]; then + echo "Aucune appli n'est précisée, j'installe les applis par défaut : ${APPLIS_PAR_DEFAUT}" >& $QUIET + LISTE_APPS="${APPLIS_PAR_DEFAUT}" + fi + PARAMS="-a \"$LISTE_APPS\"" + if [ ! "$QUIET" = "1" ]; then PARAMS="${PARAMS} -q" ; fi + if [ -n "$SIMU" ]; then PARAMS="${PARAMS} -n" ; fi + _executeFunctionForAll "${KAZ_BIN_DIR}/${subScripts["cloud"]} -i $PARAMS" "Install des applis" "cloud" "#ORGA#" +} + +UpdateApplis() { + echo "${NC}--------------------------------------------------------" >& $QUIET + echo "UPDATE DES APPLIS des cloud : ${LISTE_APPS}" >& $QUIET + echo "--------------------------------------------------------" >& $QUIET + PARAMS="-a ${LISTE_APPS}" + if [ -z "${LISTE_APPS}" ]; then + echo "Aucune appli n'est précisée, je les met toutes à jour! " >& $QUIET + PARAMS= + fi + if [ ! "$QUIET" = "1" ]; then PARAMS="${PARAMS} -q" ; fi + if [ -n "$SIMU" ]; then PARAMS="${PARAMS} -n" ; fi + _executeFunctionForAll "${KAZ_BIN_DIR}/${subScripts["cloud"]} -u $PARAMS" "Maj des applis" "cloud" "#ORGA#" +} + + +################################## +############### AGORA ############ +################################## + + +PostMessages(){ + echo "${NC}--------------------------------------------------------" >& $QUIET + echo "Envoi de messages sur mattermost" >& $QUIET + echo "--------------------------------------------------------" >& $QUIET + + for TEAM in "${!Posts[@]}" + do + MSG=${Posts[$TEAM]/\"/\\\"} + PARAMS="-p \"$TEAM\" \"$MSG\"" + if [ ! "$QUIET" = "1" ]; then PARAMS="${PARAMS} -q" ; fi + if [ -n "$SIMU" ]; then PARAMS="${PARAMS} -n" ; fi + _executeFunctionForAll "${KAZ_BIN_DIR}/${subScripts["agora"]} $PARAMS" "Post vers $TEAM sur l'agora" "agora" "#ORGA#" + done +} + + +########## LANCEMENT COMMANDES OCC / MMCTL ############ + +RunCommands() { + echo "${NC}--------------------------------------------------------" >& $QUIET + echo "Envoi de commandes en direct" >& $QUIET + echo "--------------------------------------------------------" >& $QUIET + # $1 OCC / MMCTL / EXEC + # $ suivants : les commandes + for command in "${@:2}" + do + if [ $1 = "OCC" ]; then RunOCCCommand "$command" ; fi + if [ $1 = "MMCTL" ]; then RunMMCTLCommand "$command" ; fi + if [ $1 = "EXEC" ]; then RunEXECCommand "$command" ; fi + done +} + +_runSingleOccCommand(){ + # $1 Command + # $2 Dockername + ${SIMU} docker exec -u 33 $2 /var/www/html/occ $1 +} + +_runSingleMmctlCommand(){ + # $1 Command + # $2 Dockername + ${SIMU} docker exec $2 bin/mmctl $1 +} + +_runSingleExecCommand(){ + # $1 Command + # $2 Dockername + ${SIMU} ssh -p 2201 root@${SITE_DST}.${domain} docker exec $2 $1 +} + +RunOCCCommand() { + # $1 Command + _executeFunctionForAll "_runSingleOccCommand \"${1}\"" "OCC $1" "cloud" "#DOCKERSERVNAME#" +} + +RunMMCTLCommand() { + # $1 Command + _executeFunctionForAll "_runSingleMmctlCommand \"${1}\"" "MMCTL $1" "agora" "#DOCKERSERVNAME#" +} + +RunEXECCommand() { + # $1 Command + _executeFunctionForAll "_runSingleExecCommand \"${1}\"" "docker exec $1" "${CONTAINERS_TYPES[@]}" "#DOCKERSERVNAME#" +} + + + +########## Contrôle ################# +for ARG in "$@"; do + # Seul PROD1 peut attaquer tous les autres serveurs kaz sinon un serveur kaz peut juste s'attaquer lui-même (aie!) + if [ "${ARG}" == "--all-srv" -a "${site}" != "prod1" ]; then + echo "${RED}--all-srv choisi alors qu'on n'est pas sur prod1 : impossible, on quitte${NC}" +# mais pour l'instant on autorise pour les tests +# exit + fi +done + +########## Main ################# +for ARG in "$@"; do + +#echo "${ARG}" + + if [ -n "${GETOCCCOMAND}" ]; then # après un -occ + OCCCOMANDS+=("${ARG}") + GETOCCCOMAND= + elif [ -n "${GETEXECCOMAND}" ]; then # après un -exec + EXECCOMANDS+=("${ARG}") + GETEXECCOMAND= + elif [ -n "${GETAPPS}" ]; then # après un -a + LISTE_APPS="${LISTE_APPS} ${ARG}" + GETAPPS="" + elif [ -n "${GETMMCTLCOMAND}" ]; then # après un -mmctl + MMCTLCOMANDS+=("${ARG}") + GETMMCTLCOMAND= + elif [ -n "${GETTEAM}" ]; then # après un --post + GETMESSAGE="now" + GETTEAM="" + TEAM="${ARG}" + elif [ -n "${GETMESSAGE}" ]; then # après un --post "team:channel" + if [[ $TEAM == "-*" && ${#TEAM} -le 5 ]]; then echo "J'envoie mon message à \"${TEAM}\" ?? Arf, ça me plait pas j'ai l'impression que tu t'es planté sur la commande."; usage ; exit 1 ; fi + if [[ $ARG == "-*" && ${#ARG} -le 5 ]]; then echo "J'envoie le message \"${ARG}\" ?? Arf, ça me plait pas j'ai l'impression que tu t'es planté sur la commande."; usage ; exit 1 ; fi + if [[ ! $TEAM =~ .*:.+ ]]; then echo "Il faut mettre un destinataire sous la forme team:channel. Recommence !"; usage ; exit 1 ; fi + Posts+=( ["${TEAM}"]="$ARG" ) + GETMESSAGE="" + TEAM="" + else + case "${ARG}" in + '-h' | '--help' ) + usage && exit ;; + '-n' | '--simu') + SIMU="echo" ;; + '-q' ) + QUIET="/dev/null" ;; + '-m' | '--main' ) + OPERATE_ON_MAIN="OUI-OUI" ;; + '-M' ) + AVAILABLE_ORGAS= && OPERATE_ON_MAIN="OUI-OUI" ;; #pas d'orgas + '--nas' | '-nas' ) + OPERATE_LOCAL_ORGA= ;; # pas les locales + '--local' | '-local' ) + OPERATE_ON_NAS_ORGA= ;; # pas celles sur NAS + '-cloud'|'--cloud') + CONTAINERS_TYPES="${CONTAINERS_TYPES} cloud" ;; + '-agora'|'--agora') + CONTAINERS_TYPES="${CONTAINERS_TYPES} agora" ;; + '-wiki'|'--wiki') + CONTAINERS_TYPES="${CONTAINERS_TYPES} wiki" ;; + '-wp'|'--wp') + CONTAINERS_TYPES="${CONTAINERS_TYPES} wp" ;; + '-office'|'--office') + CONTAINERS_TYPES="${CONTAINERS_TYPES} office" ;; + '-t' ) + COMMANDS="${COMMANDS} RESTART-COMPOSE" ;; + '-r' ) + COMMANDS="${COMMANDS} RESTART-DOCKER" ;; + '-l' | '--list' ) + COMMANDS="$(echo "${COMMANDS} LIST" | sed "s/\s/\n/g" | sort | uniq)" ;; + '-v' | '--version') + COMMANDS="$(echo "${COMMANDS} VERSION" | sed "s/\s/\n/g" | sort | uniq)" ;; + '-I' | '--install' ) + COMMANDS="$(echo "${COMMANDS} INIT" | sed "s/\s/\n/g" | sort | uniq)" ;; # le sed sort uniq, c'est pour pas l'avoir en double + '-U' | '--upgrade') + COMMANDS="$(echo "${COMMANDS} UPGRADE" | sed "s/\s/\n/g" | sort | uniq)" ;; + '--optim' ) + COMMANDS="$(echo "${COMMANDS} OPTIMISE-CLOUD" | sed "s/\s/\n/g" | sort | uniq)" ;; + '-u' ) + COMMANDS="$(echo "${COMMANDS} UPDATE-CLOUD-APP" | sed "s/\s/\n/g" | sort | uniq)" ;; + '-i' ) + COMMANDS="$(echo "${COMMANDS} INSTALL-CLOUD-APP" | sed "s/\s/\n/g" | sort | uniq)" ;; + '-a' ) + GETAPPS="now" ;; + '-occ' ) + COMMANDS="$(echo "${COMMANDS} RUN-CLOUD-OCC" | sed "s/\s/\n/g" | sort | uniq)" + GETOCCCOMAND="now" ;; + '-mmctl' ) + COMMANDS="$(echo "${COMMANDS} RUN-AGORA-MMCTL" | sed "s/\s/\n/g" | sort | uniq)" + GETMMCTLCOMAND="now" ;; + '-exec' ) + COMMANDS="$(echo "${COMMANDS} RUN-DOCKER-EXEC" | sed "s/\s/\n/g" | sort | uniq)" + GETEXECCOMAND="now" ;; + '-p' | '--post' ) + COMMANDS="$(echo "${COMMANDS} POST-AGORA" | sed "s/\s/\n/g" | sort | uniq)" + GETTEAM="now" ;; + '-*' ) # ignore + ;; + *) + GIVEN_ORGA="${GIVEN_ORGA} ${ARG%-orga}" + ;; + esac + fi +done + + +if [[ "${COMMANDS[*]}" =~ "RESTART-COMPOSE" && "${COMMANDS[*]}" =~ "RESTART-TYPE" ]]; then + echo "Je restarte via docker-compose ou via docker mais pas les deux !" + usage + exit 1 +fi +if [ -z "${COMMANDS}" ]; then + usage && exit +fi +if [ -n "${GIVEN_ORGA}" ]; then + # intersection des 2 listes : quelle commande de ouf !! + AVAILABLE_ORGAS=$(comm -12 <(printf '%s\n' ${AVAILABLE_ORGAS} | sort) <(printf '%s\n' ${GIVEN_ORGA} | sort)) +fi + +NB_ORGAS=$(echo "${AVAILABLE_ORGAS}" | wc -w ) + +if [[ $NB_ORGAS = 0 && -z "${OPERATE_ON_MAIN}" ]]; then + echo "Aucune orga trouvée." + exit 1 +fi + +NB_ORGAS_STR="$NB_ORGAS orgas" +[ -n "${OPERATE_ON_MAIN}" ] && NB_ORGAS_STR="$NB_ORGAS_STR + les communs" + +_populate_lists # on récupère les clouds / agora / wiki / wp correspondants aux orga + +if [[ $NB_ORGAS -gt 2 && "${COMMANDS[*]}" =~ 'INIT' ]]; then + ETLECLOUDCOMMUN= + [ -n "${OPERATE_ON_MAIN}" ] && ETLECLOUDCOMMUN=" ainsi que les containers commun" + echo "On s'apprête à initialiser les ${CONTAINERS_TYPES} suivants : ${AVAILABLE_ORGAS}${ETLECLOUDCOMMUN}" + checkContinue +fi + +for COMMAND in ${COMMANDS}; do + case "${COMMAND}" in + 'LIST' ) + listContainers && exit ;; + 'VERSION' ) + version && exit ;; + 'OPTIMISE-CLOUD' ) + OptimiseClouds ;; + 'RESTART-COMPOSE' ) + restart-compose ;; + 'RESTART-DOCKER' ) + restart ;; + 'UPDATE-CLOUD-APP' ) + UpdateApplis ;; + 'UPGRADE' ) + UpgradeClouds ;; + 'INIT' ) + Init ;; + 'INSTALL-CLOUD-APP' ) + InstallApps ;; + 'RUN-CLOUD-OCC' ) + RunCommands "OCC" "${OCCCOMANDS[@]}" ;; + 'RUN-AGORA-MMCTL' ) + RunCommands "MMCTL" "${MMCTLCOMANDS[@]}" ;; + 'RUN-DOCKER-EXEC' ) + RunCommands "EXEC" "${EXECCOMANDS[@]}" ;; + 'POST-AGORA' ) + PostMessages ${Posts} ;; + esac +done diff --git a/bin/gestUsers.sh b/bin/gestUsers.sh new file mode 100755 index 0000000..1fb9d27 --- /dev/null +++ b/bin/gestUsers.sh @@ -0,0 +1,1151 @@ +#!/bin/bash +# gestion des utilisateurs de kaz ( mail, cloud général, mattermost ) +#KAZ_ROOT=$(cd "$(dirname $0)"/..; pwd) +KAZ_ROOT=/kaz +. $KAZ_ROOT/bin/.commonFunctions.sh +setKazVars + +. $DOCKERS_ENV +. $KAZ_ROOT/secret/SetAllPass.sh + +VERSION="13-12-2023" +PRG=$(basename $0) +RACINE=$(echo $PRG | awk '{print $1}') +IFS=' ' +BLINK='\033[0;5m' + +LOG=$RACINE".log" +URL_NC=$(echo $cloudHost).$(echo $domain) +URL_AGORA=$(echo $matterHost).$(echo $domain) +URL_LISTE=$(echo $sympaHost).$(echo $domain) +URL_PAHEKO="$httpProto://${paheko_API_USER}:${paheko_API_PASSWORD}@kaz-paheko.$(echo $domain)" +SETUP_MAIL="docker exec -ti mailServ setup" +NL_LIST=infos@listes.kaz.bzh +URL_AGORA_API=${URL_AGORA}/api/v4 +EQUIPE=kaz +LISTMASTER=$(echo ${sympa_LISTMASTERS} | cut -d',' -f1) + + +#### Test du serveur sur lequel s' execute le script #### +echo ${site} | grep -i prod2 && { echo "Le script ne fonctionne que sur Prod1 et Dev ";exit;} +############################## + +TFILE_EMAILS=$(mktemp /tmp/$RACINE.XXXXXXXXX.TFILE_EMAILS) +TFILE_EMAILS_LIST=$(mktemp /tmp/$RACINE.XXXXXXXXX.TFILE_EMAILS_LIST) +TFILE_MAILS_TROUVE=$(mktemp /tmp/$RACINE.XXXXXXXXX.TFILE_MAILS_TROUVE) +TFILE_MAILS_MATTERMOST=$(mktemp /tmp/$RACINE.XXXXXXXXTFILE_MAILS_MATTERMOST) +TFILE_CREATE_MAIL=$(mktemp /tmp/$RACINE.XXXXXXXXTFILE_CREATE_MAIL.sh) +LDAP_IP=$(docker inspect -f '{{.NetworkSettings.Networks.ldapNet.IPAddress}}' ldapServ) + +regexMail="^(([A-Za-z0-9]+((\.|\-|\_|\+)?[A-Za-z0-9]?)*[A-Za-z0-9]+)|[A-Za-z0-9]+)@(([A-Za-z0-9]+)+((\.|\-|\_)?([A-Za-z0-9]+)+)*)+\.([A-Za-z]{2,})+$" + + +rm -rf /tmp/$RACINE* +rm -rf /tmp/*.json + +############################################ Fonctions ####################################################### + +ExpMail() { + MAIL_DEST=$1 + MAIL_SUJET=$2 + MAIL_TEXTE=$3 + # a mettre ailleurs + mailexp=${service_mail} + mailpassword=${service_password} + mailserveur=${smtpHost}.${domain} + printf "Subject:${MAIL_SUJET}\n${MAIL_TEXTE}" | msmtp ${MAIL_DEST} +} + +PostMattermost() { + PostM=$1 + CHANNEL=$2 + TEAMID=$(curl -s -H "Authorization: Bearer ${mattermost_token}" "${URL_AGORA_API}/teams/name/${EQUIPE}" | jq .id | sed -e 's/"//g') + CHANNELID=$(curl -s -H "Authorization: Bearer ${mattermost_token}" ${URL_AGORA_API}/teams/${TEAMID}/channels/name/${CHANNEL} | jq .id | sed -e 's/"//g') + curl -s i-X POST -i -H "Authorization: Bearer ${mattermost_token}" -d "{\"channel_id\":\"${CHANNELID}\",\"message\":\"${PostM}\"}" "${URL_AGORA_API}/posts" >/dev/null 2>&1 +} + + +searchEmail() { + # on peut appeler cette fonction avec un paramêtre + # qui doit être une adresse email + CHOIX_MAIL="" + SEARCH_OBJECT_CLASS="inetOrgPerson" + [ "$1" = "alias" ] && SEARCH_OBJECT_CLASS="PostfixBookMailForward" + rm -rf $TFILE_MAILS_TROUVE + rm -rf $TFILE_MAILS + fait=0 + while [ ${fait} = 0 ] + do + clear + echo "----------------------------------------------------------------------" + printKazMsg "${ACTION_EN_COURS}" + echo "----------------------------------------------------------------------" + read -p "Adresse ou caractere contenu dans cette adresse (r ou R pour retour ) ? : " RMAIL + [[ ${RMAIL} =~ ^[rRqQ]$ ]] && Main + if [ "${RMAIL}" == "" ] + then + fait=0 + else + fait=1 + fi + done + ldapsearch -H ldap://${LDAP_IP} \ + -x -D "cn=${ldap_LDAP_ADMIN_USERNAME},${ldap_root}" \ + -w "${ldap_LDAP_ADMIN_PASSWORD}" \ + -b "${ldap_root}" "(&(objectclass=${SEARCH_OBJECT_CLASS})(cn=*${RMAIL}*))" cn | grep ^cn | sed -e 's/^cn: //' >$TFILE_EMAILS + COMPTEUR_LIGNE=0 + while read LIGNE + do + COMPTEUR_LIGNE=$(expr $COMPTEUR_LIGNE + 1) + printf "%d - %s\n" $COMPTEUR_LIGNE $LIGNE >>$TFILE_MAILS_TROUVE + done <$TFILE_EMAILS + if [ $COMPTEUR_LIGNE -gt 0 ] + then + fait=0 + while [ ${fait} = 0 ] + do + echo "----------------------------------------------------------------------" + cat $TFILE_MAILS_TROUVE + echo "----------------------------------------------------------------------" + read -p "Choisir le numéro correspondant ( R ou r ou 0 pour retour ): " NB_LIGNE_MAIL + # si on tape r ou r ou Q ou q ou 0 on relance le menu de recherche de mail + [[ $NB_LIGNE_MAIL =~ [rRqQ0] ]] && searchEmail $1 + CHOIX_MAIL=$(cat ${TFILE_MAILS_TROUVE} | grep "^${NB_LIGNE_MAIL}\b" | awk '{print $3}' | tr -d '[:space:]') + + # si on répond par entrée nb_ligne_mail sera vide + # si on donne une réponse qui est une lettre ou un mauvais chiffre choix_mail sera vide + # alors on reboucle sur la liste des mails + + if [ "$CHOIX_MAIL" == "" ] || [ "$NB_LIGNE_MAIL" == "" ] + then + CHOIX_MAIL="" + fait=0 + else + fait=1 + fi + done + else + searchEmail $1 + fi +} + +searchMattermost() { + #Ici $1 est une adresse email + docker exec -ti ${mattermostServName} bin/mmctl --suppress-warnings auth login $httpProto://$URL_AGORA --name local-server --username $mattermost_user --password $mattermost_pass >/dev/null 2>&1 + docker exec -ti ${mattermostServName} bin/mmctl --suppress-warnings config set ServiceSettings.EnableAPIUserDeletion "true" >/dev/null 2>&1 + #on créé la list des mails dans mattermost + docker exec -ti ${mattermostServName} bin/mmctl --suppress-warnings user list --all >${TFILE_MAILS_MATTERMOST} 2>/dev/null + REP_SEARCH_MATTERMOST=$(cat ${TFILE_MAILS_MATTERMOST} | grep $1 | awk '{print $2}' | tr -d '[:space:]') + if [ ! -z ${REP_SEARCH_MATTERMOST} ] + then + if [ "$2" = "DETAILS" ] + then + touch /tmp/$1-mattermost.json + docker exec -ti ${mattermostServName} bin/mmctl --suppress-warnings --format json user search $1 >/tmp/$1-mattermost.json 2>/dev/null + echo -e "${RED} Compte : ${GREEN} " ;jq .username /tmp/$1-mattermost.json + echo -e "${RED} Mail : ${GREEN} " ; jq .email /tmp/$1-mattermost.json + echo -e "${RED} Role : ${GREEN} " ; jq .roles /tmp/$1-mattermost.json + else + echo "${REP_SEARCH_MATTERMOST}" + fi + else + echo "" + fi +} + + +infoEmail() { + ACTION_EN_COURS="Information sur un compte" + while : + do + clear + echo "------------------------------------------------" + printKazMsg "${ACTION_EN_COURS}" + echo "------------------------------------------------" + read -p "Alias ou Mail ? (R pour retour ou M/A [M] :" RINFOMAIL + case ${RINFOMAIL} in + "" | M | m ) + infofait=O + searchEmail + clear + echo "------------------------------------------------" + printKazMsg " ADRESSE DE MESSAGERIE : $CHOIX_MAIL" + echo "------------------------------------------------" + printKazMsg " DETAILS DU COMPTE DANS MATTERMOST" + searchMattermost $CHOIX_MAIL DETAILS + echo " ------------------------------------------------" + printKazMsg " DETAILS DU COMPTE DANS NEXTCLOUD PRINCIPAL" + echo -e "" + #TEMP_USER_NC=$(mktemp /tmp/$RACINE.XXXXXXXXX.TEMP_USER_NC) + #curl -s -o $TEMP_USER_NC -X GET -H 'OCS-APIRequest:true' $httpProto://admin:$nextcloud_NEXTCLOUD_ADMIN_PASSWORD@$URL_NC/ocs/v1.php/cloud/users?search=$CHOIX_MAIL + #cat $TEMP_USER_NC | grep -i "element" | sed -e s/[\<\>\/]//g | sed -e s/element//g + echo -ne "${NC}" + echo -ne " - Nextcloud enable : " + echo -ne "${GREEN}" + ldapsearch -H ldap://${LDAP_IP} -D "cn=${ldap_LDAP_ADMIN_USERNAME},${ldap_root}" -x -w "${ldap_LDAP_ADMIN_PASSWORD}" -b "cn=${CHOIX_MAIL},ou=users,${ldap_root}" | grep -i nextcloudEnabled | cut -c 18-30 + echo -ne "${NC}" + echo -e "${NC} ------------------------------------------------" + printKazMsg " DETAILS DU COMPTE DANS LDAP ET PAHEKO" + echo "" + curl -s ${URL_PAHEKO}/api/sql -d "SELECT nom,adresse,code_postal,ville,email,email_secours,admin_orga,nom_orga,quota_disque FROM users where email='${CHOIX_MAIL}' LIMIT 1;" >/tmp/$CHOIX_MAIL-paheko.json + jq .results[].nom /tmp/$CHOIX_MAIL-paheko.json + jq .results[].adresse /tmp/$CHOIX_MAIL-paheko.json + jq .results[].code_postal /tmp/$CHOIX_MAIL-paheko.json + jq .results[].ville /tmp/$CHOIX_MAIL-paheko.json + echo -n " - Quota (Paheko) : " + echo -ne "${GREEN}" + jq .results[].quota_disque /tmp/$CHOIX_MAIL-paheko.json + echo -ne "${NC}" + echo -n " - Quota Mail (Ldap) : " + echo -ne "${GREEN}" + ldapsearch -H ldap://${LDAP_IP} -D "cn=${ldap_LDAP_ADMIN_USERNAME},${ldap_root}" -x -w "${ldap_LDAP_ADMIN_PASSWORD}" -b "cn=${CHOIX_MAIL},ou=users,${ldap_root}" | grep -i mailquota | cut -c 11-60 + echo -ne "${NC}" + echo -n " - Quota Nextcloud (Ldap) : " + echo -ne "${GREEN}" + ldapsearch -H ldap://${LDAP_IP} -D "cn=${ldap_LDAP_ADMIN_USERNAME},${ldap_root}" -x -w "${ldap_LDAP_ADMIN_PASSWORD}" -b "cn=${CHOIX_MAIL},ou=users,${ldap_root}" | grep -i nextcloudquota | cut -c 17-60 + echo -ne "${NC}" + echo -n " - Mail de secours (Paheko ): " + echo -ne "${GREEN}" + jq .results[].email_secours /tmp/$CHOIX_MAIL-paheko.json | sed -e 's/"//g' + echo -ne "${NC}" + echo -n " - Mail de secours (Ldap): " + echo -ne "${GREEN}" + ldapsearch -H ldap://${LDAP_IP} -D "cn=${ldap_LDAP_ADMIN_USERNAME},${ldap_root}" -x -w "${ldap_LDAP_ADMIN_PASSWORD}" -b "cn=${CHOIX_MAIL},ou=users,${ldap_root}" | grep -i maildeSecours | sed -e 's/mailDeSecours://' + echo -ne "${NC}" + echo -n " - Alias (Ldap) : " + echo -ne "${GREEN}" + LDAP_ALIAS=$(ldapsearch -H ldap://${LDAP_IP} -D "cn=${ldap_LDAP_ADMIN_USERNAME},${ldap_root}" -x -w "${ldap_LDAP_ADMIN_PASSWORD}" -b "cn=${CHOIX_MAIL},ou=users,${ldap_root}" | grep -i alias | cut -c 11-60) + echo -ne "${NC}" + echo -ne "${GREEN}" + for ldap_alias in ${LDAP_ALIAS} + do + echo "${ldap_alias}" | tr -d [:space:] + echo -n " " + done + echo -e "${NC}" + echo " - L' adresse apparait dans :" + echo -n -e "${GREEN}" + curl -s ${URL_PAHEKO}/api/sql -d "SELECT * FROM users where emails_Rattaches like '%$CHOIX_MAIL%' ;" | jq .results[].nom + echo -e "${NC}" + read -p " ==> Entrée pour continuer <==" + ;; + A | a ) + searchEmail alias + echo "------------------------------------------------" + echo " Alias : ${CHOIX_MAIL} " + echo "" + for INFOALIAS in $(ldapsearch -H ldap://${LDAP_IP} -x -D "cn=${ldap_LDAP_ADMIN_USERNAME},${ldap_root}" \ + -w "${ldap_LDAP_ADMIN_PASSWORD}" -b "${ldap_root}" "(&(objectclass=PostfixBookMailForward)(cn=*${CHOIX_MAIL}*))" mail \ + | grep ^mail: | sed -e 's/^mail://') + do + echo -ne "=====> ${GREEN} " + echo "${INFOALIAS}" | tr -d [:space:] + echo "${NC}" + done + echo "" + read -p "==> Entrée pour continuer <==" + ;; + r | R ) + Main + ;; + * ) + echo "" + ;; + esac + done +} + +searchDestroy() { + + ACTION_EN_COURS="Suppression d'un compte" + clear + #TODO pourquoi REP_SEARCH_DESTROY=$(searchEmail) ne marche pas et fait déconner la fonction search_mail, j' en suis la à m' esbaudir + searchEmail + REP_SEARCH_DESTROY=$CHOIX_MAIL + echo "CHOIX=$REP_SEARCH_DESTROY" + echo "--------------------------------- SUPPRESION ----------------------------------------" + while : + do + echo "----------------------------------------------------------------------" + printKazMsg "${GREEN}${ACTION_EN_COURS}${NC}" + echo "----------------------------------------------------------------------" + echo -e "${BLINK} TOUT RETOUR EN ARRIERE EST IMPOSSIBLE ${NC}" + read -p "ON CONTINUE ? [ o / n ]: " SEARCH_DESTROY_INPUT + if [ "$SEARCH_DESTROY_INPUT" = "n" ] || [ "$SEARCH_DESTROY_INPUT" = "N" ] + then + searchDestroy + fi + if [ "$SEARCH_DESTROY_INPUT" = "o" ] || [ "$SEARCH_DESTROY_INPUT" = "O" ] + then + REP_SEARCH=$(searchMattermost $REP_SEARCH_DESTROY) + printKazMsg "réponse de mattermost : ${REP_SEARCH_DESTROY}" + if [ ! -z ${REP_SEARCH} ] + then + echo -e "${RED} suppression de ${REP_SEARCH_DESTROY} dans mattermost ${NC}" + docker exec -ti ${mattermostServName} bin/mmctl user delete ${REP_SEARCH_DESTROY} --confirm >/dev/null 2>&1 + if [ "$?" -eq "0" ] + then + printKazMsg "Suppresion ok" + else + printKazError -e "Erreur de suppression" + fi + echo " ----------------- " + else + echo "Rien a supprimer dans mattermost" + fi + echo -e "${NC}" + echo -e "Recherche de ${GREEN} ${REP_SEARCH_DESTROY} ${NC} dans nextcloud" + USER_NEXTCLOUD_SUPPR=$(curl -s -X GET -H 'OCS-APIRequest:true' $httpProto://admin:$nextcloud_NEXTCLOUD_ADMIN_PASSWORD@$URL_NC/ocs/v1.php/cloud/users?search=${REP_SEARCH_DESTROY} | grep element | sed -s 's/[ \<\>\/]//g' | sed 's/element//g') + if [ ! -z ${USER_NEXTCLOUD_SUPPR} ] + then + printKazMsg "le user trouvé est : ${USER_NEXTCLOUD_SUPPR}" + echo -e "${RED} Suppresion de ${USER_NEXTCLOUD_SUPPR}" + curl -H 'OCS-APIREQUEST: true' -X DELETE $httpProto://admin:$nextcloud_NEXTCLOUD_ADMIN_PASSWORD@$URL_NC/ocs/v1.php/cloud/users/${USER_NEXTCLOUD_SUPPR} >/dev/null 2>&1 + if [ "$?" -eq "0" ] + then + printKazMsg "Suppresion ok" + else + printKazError "Erreur de suppression" + fi + else:w + + printKazMsg "Rien à supprimer dans Nextcloud" + fi + echo -e "${NC}" + echo "" + echo -e "${RED} suppression de ${REP_SEARCH_DESTROY} dans la liste info de sympa" + echo -e "${NC}" + echo "" + docker exec -ti sympaServ /usr/lib/sympa/bin/sympa_soap_client.pl --soap_url=${httpProto}://${URL_LISTE}/sympasoap --trusted_application=${sympa_SOAP_USER} --trusted_application_password=${sympa_SOAP_PASSWORD} --proxy_vars=USER_EMAIL=${LISTMASTER} --service=del --service_parameters="${NL_LIST},${REP_SEARCH_DESTROY}" + echo -e "${NC}" + echo "" + echo -e "${RED} suppression de ${REP_SEARCH_DESTROY} dans le serveur de mail" + echo -e "${NC}" + echo "" + ${SETUP_MAIL} email del -y ${REP_SEARCH_DESTROY} >/dev/null 2>&1 + echo -e "${NC}" + echo "" + echo -e "${RED} suppression de ${REP_SEARCH_DESTROY} dans le ldap" + echo -e "${NC}" + echo "" + ldapdelete -H ldap://${LDAP_IP} -D "cn=${ldap_LDAP_ADMIN_USERNAME},${ldap_root}" -x -w "${ldap_LDAP_ADMIN_PASSWORD}" "cn=${REP_SEARCH_DESTROY},ou=users,${ldap_root}" + if [ "$?" -eq "0" ] + then + printKazMsg "Suppresion ok" + else + printKazError "Erreur de suppression" + fi + printKazMsg "Envoi d'un message dans mattermost pour la suppression du compte" + docker exec -ti mattermostServ bin/mmctl post create kaz:Creation-Comptes --message "Le compte ${REP_SEARCH_DESTROY} est supprimé" >/dev/null 2>&1 + MAIL_SUPPR="Suppression du compte ${REP_SEARCH_DESTROY}" + docker exec -i mailServ mailx -a 'Content-Type: text/plain; charset="UTF-8"' -r contact@${domain} -s "Suppression de mail" contact@${domain} << EOF + ${MAIL_SUPPR} +EOF + echo -e "${NC}" + read -p " ---------------------- Appuyer sur une touche pour continuer -------------------------" + searchDestroy + fi + done +} + +gestPassword() { + ACTION_EN_COURS="Gestion du mot de passe d' un compte" + searchEmail + #cree un mdp acceptable par postfix/nc/mattermost + PASSWORD=_`apg -n 1 -m 10 -M NCL -d`_ + COMPTE_A_MODIFIER=${CHOIX_MAIL} + + # On considère le mot de passe de secours de ldap + # FICMAILSECOURS=$(mktemp /tmp/FICMAILSECOURSXXXXX.json) + # curl -s ${URL_PAHEKO}/api/sql -d "SELECT email_secours FROM users where email='${COMPTE_A_MODIFIER}' LIMIT 1 ;" >$FICMAILSECOURS + # MAIL_SECOURS=$(jq .results[].email_secours $FICMAILSECOURS | sed -e 's/\"//g') + + MAIL_SECOURS=$(ldapsearch -H ldap://${LDAP_IP} \ + -x -D "cn=${ldap_LDAP_ADMIN_USERNAME},${ldap_root}" \ + -w "${ldap_LDAP_ADMIN_PASSWORD}" \ + -b "${ldap_root}" "(&(objectclass=inetOrgPerson)(cn=*${CHOIX_MAIL}*))" | grep ^mailDeSecours | sed -e 's/^mailDeSecours: //') + if [ "$MAIL_SECOURS" = "" ] + then + ADRESSE_SEC="NON" + else + ADRESSE_SEC="OUI" + fi + ############################################################################## + # on cherche le moyen de savoir si ce mail est le mail dans une orga + # si c' est le cas ça veut dire qu' il faut changer le mot de passe dans le cloud de l' orga + rm -rf /tmp/FICMAILORGA* + EMAIL_ORGA_TROUVE=0 + EMAIL_ORGA_TROUVE=$(curl -s ${URL_PAHEKO}/api/sql -d "SELECT id FROM users where emails_Rattaches like '%$CHOIX_MAIL%' ;" | jq .results[].id | wc -l) + if [ $EMAIL_ORGA_TROUVE -ge 1 ] + then + printKazError "L' adresse apparait dans plusieurs orga comme mails rattaché IL EST PREFERABLE DE REPONDRE NON !!!" + fi + ########################################################################## + read -p "ON CONTINUE ? [ o / n ]: " SEARCH_RESET_INPUT + if [ "$SEARCH_RESET_INPUT" = "n" ] || [ "$SEARCH_RESET_INPUT" = "N" ] + then + clear + gestPassword + fi + if [ "$SEARCH_RESET_INPUT" = "o" ] || [ "$SEARCH_RESET_INPUT" = "O" ] + then + USER_NEXTCLOUD_MODIF=$(curl -s -X GET -H 'OCS-APIRequest:true' $httpProto://admin:$nextcloud_NEXTCLOUD_ADMIN_PASSWORD@$URL_NC/ocs/v1.php/cloud/users?search=${COMPTE_A_MODIFIER} | grep element | sed -e 's/[ \<\>\/]//g' -e 's/element//g') + echo -e "$GREEN Compte à modifier = $RED ${COMPTE_A_MODIFIER} ${NC}" + echo -e "$GREEN Mail de secours = $RED ${MAIL_SECOURS} ${NC}" + echo -e "$GREEN Compte $RED $(searchMattermost $COMPTE_A_MODIFIER) ${NC}" + echo -e "$GREEN Compte Nextcloud $RED ${USER_NEXTCLOUD_MODIF} ${NC}" + echo -e "$GREEN Le mot de passe sera = $RED ${PASSWORD} ${NC}" + docker exec -ti mattermostServ bin/mmctl user change-password $(searchMattermost $COMPTE_A_MODIFIER) -p $PASSWORD >/dev/null 2>&1 + curl -H 'OCS-APIREQUEST: true' -X PUT $httpProto://admin:$nextcloud_NEXTCLOUD_ADMIN_PASSWORD@$URL_NC/ocs/v1.php/cloud/users/${USER_NEXTCLOUD_MODIF} -d key=password -d value=${PASSWORD} >/dev/null 2>&1 + ${SETUP_MAIL} email update ${COMPTE_A_MODIFIER} ${PASSWORD} + pass=$(mkpasswd -m sha512crypt ${PASSWORD}) + echo -e "\n\ndn: cn=${COMPTE_A_MODIFIER},ou=users,${ldap_root}\n\ +changeType: modify\n\ +replace: userPassword\n\ +userPassword: {CRYPT}${pass}\n\n" | ldapmodify -c -H ldap://${LDAP_IP} -D "cn=${ldap_LDAP_ADMIN_USERNAME},${ldap_root}" -x -w "${ldap_LDAP_ADMIN_PASSWORD}" + echo -e "Envoi d'un message dans mattermost pour la modification du mot de passe" + docker exec -ti mattermostServ bin/mmctl post create kaz:Creation-Comptes --message "Le mot de passe du compte ${COMPTE_A_MODIFIER} a été modifié" >/dev/null 2>&1 + if [ $ADRESSE_SEC == "OUI" ] + then + echo -e "Envoi d'un message à l' adresse de secours : $GREEN${MAIL_SECOURS}${NC}" + MAIL_CHANG=" +Hello, + +le nouveau mot de passe de ${COMPTE_A_MODIFIER} est : ${PASSWORD} + +le webmail est disponible à https://webmail.kaz.bzh +Le site d' aide pour la configuration des services : https://wiki.kaz.bzh +Le site Web de Kaz : https://kaz.bzh +Le mail de la collégiale : contact@kaz.bzh + +A bientôt" + docker exec -i mailServ mailx -a 'Content-Type: text/plain; charset="UTF-8"' -r admin@${domain} -s "Modification du compte" $MAIL_SECOURS << EOF + ${MAIL_CHANG} +EOF + fi + if [ $ADRESSE_SEC == "NON" ] + then + echo -e "${RED} Pas d adresse de secours ${NC}" + fi + fi + +} + +createMail() { + ACTION_EN_COURS="Création d' une adresse de messagerie" + QUOTA=1 + TRUE_KAZ=TRUE + fait=0 + # On demande le mail souhaite on regarde si c' est valide et si ça existe déjà + while [ $fait -eq 0 ] + do + clear + echo "----------------------------------------------------------------------" + printKazMsg "${ACTION_EN_COURS}" + echo "----------------------------------------------------------------------" + read -p "Mail souhaité (r ou R pour quitter ) : " EMAIL_SOUHAITE + [[ ${EMAIL_SOUHAITE} =~ ^[rRqQ]$ ]] && Main + if [[ ${EMAIL_SOUHAITE} =~ ${regexMail} ]] + then + ldapsearch -H ldap://${LDAP_IP} \ + -x -D "cn=${ldap_LDAP_ADMIN_USERNAME},${ldap_root}" \ + -w "${ldap_LDAP_ADMIN_PASSWORD}" \ + -b "${ldap_root}" "(&(objectclass=inetOrgPerson)(cn=${EMAIL_SOUHAITE}))" cn | grep ^cn | sed -e 's/^cn: //' >$TFILE_EMAILS + if grep -q "^${EMAIL_SOUHAITE}$" "${TFILE_EMAILS}" + then + printKazError " - Le mail ${EMAIL_SOUHAITE} existe déjà" + fait=0 + elif ! grep $(echo ${EMAIL_SOUHAITE} | sed -e 's/^.*@//') ${KAZ_CONF_DIR}/autorized-domains.txt >/dev/null + then + printKazMsg "Le domaine n' est pas dans la liste autorisée" + sleep 2 + fait=0 + else + fait=1 + fi + else + printKazError " - Mail invalide !" + sleep 2 + fait=0 + fi + done + # On demande le mail de secours et on teste si c' est un mail valide + fait=0 + while [ $fait -eq 0 ] + do + read -p "Mail de secours : " EMAIL_SECOURS + if [[ ${EMAIL_SECOURS} =~ ${regexMail} ]] + then + fait=1 + else + printKazError "Mail invalide" + fait=0 + fi + done + # On demande le prenom + read -p "Prénom : " PRENOM + # On demande le nom + read -p "Nom : " NOM + # On constitue l' ident kaz pour le ldap + IDENT_KAZ=$(unaccent utf8 "${PRENOM,,}.${NOM,,}") + # on créé le mot de passe + PASSWORD=_`apg -n 1 -m 10 -M NCL -d`_ + # on constitue le user,domain et pass crypté pour le ldap + LDAPUSER=$(echo ${EMAIL_SOUHAITE} | awk -F '@' '{print $1}') + LDAPDOMAIN=$(echo ${EMAIL_SOUHAITE} | awk -F '@' '{print $2}') + LDAPPASS=$(mkpasswd -m sha512crypt ${PASSWORD}) + [ "$(echo ${EMAIL_SOUHAITE} | sed -e 's/^.*@//')" != "${domain}" ] && TRUE_KAZ=FALSE + echo "${GREEN}Mail souhaité : ${NC}${EMAIL_SOUHAITE}" + echo "${GREEN}Mail secours : ${NC}${EMAIL_SECOURS}" + echo "${GREEN}Prénom : ${NC}${PRENOM}" + echo "${GREEN}Nom : ${NC}${NOM}" + echo "${GREEN}Mot de passe : ${NC}${PASSWORD}" + RCREATION="" + repRegEx='^[oOnN]$' + while ! [[ ${RCREATION} =~ ${repRegEx} ]] + do + read -p "Création ? o/n : " RCREATION + done + if [ ${RCREATION} = "n" ] || [ ${RCREATION} = "N" ] + then + Main + fi + # on créé le mail et on met les quotas avec le setup du docker mail pour que + # le contenu des fichiers à plat soient idem que l' annuaire ldap + ${SETUP_MAIL} email add ${EMAIL_SOUHAITE} ${PASSWORD} + ${SETUP_MAIL} quota set ${EMAIL_SOUHAITE} ${QUOTA}G + echo "echo -e '\n\ndn: cn=${EMAIL_SOUHAITE},ou=users,${ldap_root}\n\ +changeType: add\n\ +objectclass: inetOrgPerson\n\ +objectClass: PostfixBookMailAccount\n\ +objectClass: nextcloudAccount\n\ +objectClass: kaznaute\n\ +sn: ${PRENOM} ${NOM}\n\ +mail: ${EMAIL_SOUHAITE}\n\ +mailEnabled: TRUE\n\ +mailGidNumber: 5000\n\ +mailHomeDirectory: /var/mail/${LDAPDOMAIN}/${LDAPUSER}/\n\ +mailQuota: ${QUOTA}G\n\ +mailStorageDirectory: maildir:/var/mail/${LDAPDOMAIN}/${LDAPUSER}/\n\ +mailUidNumber: 5000\n\ +mailDeSecours: ${EMAIL_SECOURS}\n\ +identifiantKaz: ${IDENT_KAZ}\n\ +quota: ${QUOTA}\n\ +nextcloudEnabled: ${TRUE_KAZ}\n\ +nextcloudQuota: ${QUOTA} GB\n\ +mobilizonEnabled: ${TRUE_KAZ}\n\ +agoraEnabled: ${TRUE_KAZ}\n\ +userPassword: {CRYPT}${LDAPPASS}\n\n' | ldapmodify -c -H ldap://${LDAP_IP} -D \"cn=${ldap_LDAP_ADMIN_USERNAME},${ldap_root}\" -x -w ${ldap_LDAP_ADMIN_PASSWORD}" >${TFILE_CREATE_MAIL} +# on execute le fichier avec les données ldap pour créer l' entrée dans l' annuaire +bash ${TFILE_CREATE_MAIL} >/dev/null +# on colle le compte et le mot de passe dans le fichier +echo "Création de : ${EMAIL_SOUHAITE} avec le mot de passe : ${PASSWORD}" >>${TFILE_CREATE_MAIL} +OLDIFS=${IFS} +IFS='' +MSG_CREATION_MAIL="Bonjour + +Le compte de messagerie ${EMAIL_SOUHAITE} a été créé avec le mot de passe ${PASSWORD} + +Le mot de passe peut être changé à l' adresse https://mdp.${domain} + +Bonne journée" +ExpMail ${EMAIL_SECOURS} "Creation de ${EMAIL_SOUHAITE}" ${MSG_CREATION_MAIL} +docker exec -ti mattermostServ bin/mmctl post create kaz:Creation-Comptes --message "création du compte ${EMAIL_SOUHAITE} avec l' adresse de secours ${EMAIL_SECOURS}" >/dev/null 2>&1 +IFS=${OLDIFS} +echo " --------------------------------------------------------------------" +echo "! INFO : !" +echo "! Le Mot de passe !" +echo "! Est dans le fichier !" +echo "! ${TFILE_CREATE_MAIL} !" +echo " --------------------------------------------------------------------" +echo +read -p " - ENTREE pour continuer - " +# Retour au menu principal +Main +} + +createAlias() { + ACTION_EN_COURS="création d' un alias de messagerie" + fait=0 + # On demande alias souhaite on regarde si c' est valide et si ça existe déjà + while [ $fait -eq 0 ] + do + clear + echo "-------------------------------------------------" + printKazMsg "${ACTION_EN_COURS}" + echo "-------------------------------------------------" + read -p "Alias souhaité (r ou q pour quitter ) : " AMAIL + [[ ${AMAIL} =~ ^[rRqQ]$ ]] && Main + if [[ ${AMAIL} =~ ${regexMail} ]] + then + RESU_ALIAS=$(ldapsearch -H ldap://${LDAP_IP} \ + -x -D "cn=${ldap_LDAP_ADMIN_USERNAME},${ldap_root}" \ + -w "${ldap_LDAP_ADMIN_PASSWORD}" \ + -b "${ldap_root}" "(&(objectclass=PostfixBookMailForward)(cn=*${AMAIL}*))" | grep ^cn | sed -e 's/^cn: //') + RESU_ALIAS_IS_MAIL=$(ldapsearch -H ldap://${LDAP_IP} \ + -x -D "cn=${ldap_LDAP_ADMIN_USERNAME},${ldap_root}" \ + -w "${ldap_LDAP_ADMIN_PASSWORD}" \ + -b "${ldap_root}" "(&(objectclass=inetOrgPerson)(cn=*${AMAIL}*))" cn | grep ^cn | sed -e 's/^cn: //') + + if echo ${RESU_ALIAS} | grep -q "^${AMAIL}$" || echo ${RESU_ALIAS_IS_MAIL} | grep -q "^${AMAIL}$" + then + echo "${GREEN} - alias ou mail ${AMAIL} déjà dans présent l' annuaire${NC}" + sleep 2 + fait=0 + elif ! grep $(echo ${AMAIL} | sed -e 's/^.*@//') ${KAZ_CONF_DIR}/autorized-domains.txt >/dev/null + then + echo -e "${GREEN}Le domaine n' est pas dans la liste autorisée${NC}" + sleep 2 + fait=0 + else + fait=1 + fi + else + echo -e "${GREEN} - Mail invalide !${NC}" + sleep 2 + fait=0 + fi + done + echo "Alias : ${AMAIL} peut être créé" + declare -a TABAMAIL + fait=0 + while [ $fait -eq 0 ] + do + clear + echo "-----------------------------------------------------------------" + echo "Ajout de mail pour cet alias" + echo "-----------------------------------------------------------------" + [ "${TABAMAIL[*]}" == "" ] || echo -e "Liste des mails dans cet alias: [ ${GREEN}${TABAMAIL[*]}${NC} ]" + read -p "ENTREE ou F ( pour fin :-) pour une liste vide ou saisir une adresse et enfin saisir F pour arrêter : " RALIAS + if [[ ${RALIAS} =~ ^[Ff]$ ]] || [ "${RALIAS}" == "" ] + then + fait=1 + else + if echo ${TABAMAIL[*]} | grep -i ${RALIAS} >/dev/null 2>&1 + then + echo -e "${GREEN}l' alias est déjà dans la liste ${NC}" + sleep 2 + else + if [[ ${RALIAS} =~ ${regexMail} ]] + then + TABAMAIL+=(${RALIAS}) + else + echo -e "${GREEN} - Mail invalide !${NC}" + sleep 2 + fi + fi + fi + done + fait=0 + while [ "$fait" = 0 ] + do + clear + echo "--------------------------------------------------" + echo -e "${GREEN}résumé de la situation${NC}" + echo "--------------------------------------------------" + echo -e "création d' un alias : ${GREEN}${AMAIL}${NC}" + echo -e "avec le(s) Mail suivant : ${GREEN}${TABAMAIL[*]}${NC}" + echo "--------------------------------------------------" + RCREATONALIAS="" + read -p "OK pour la création ?(o/n) : " RCREATIONALIAS + case "$RCREATIONALIAS" in + o | O ) + for ALIASMAIL in ${TABAMAIL[*]} + do + LDAPALAISMAIL="${LDAPALAISMAIL}$(echo mail :${ALIASMAIL})\n" + done + echo -e "\n\ndn: cn=${AMAIL},ou=mailForwardings,${ldap_root}\n\ +changeType: add\n\ +objectClass: organizationalRole\n\ +objectClass: PostfixBookMailForward\n\ +mailAlias: ${AMAIL}\n\ +${LDAPALAISMAIL}\n\n" | ldapmodify -c -H ldap://${LDAP_IP} -D "cn=${ldap_LDAP_ADMIN_USERNAME},${ldap_root}" -x -w ${ldap_LDAP_ADMIN_PASSWORD} + fait=1 + printKazMsg "Création de ${AMAIL}" + sleep 3 + createAlias + ;; + n | N ) + printKazMsg "abandon de création" + sleep 2 + createAlias + ;; + * ) + fait=0 + ;; + esac + done +} + +delAlias() { + ACTION_EN_COURS="Suppresion d' un alias de messagerie" + fait=0 + while [ $fait -eq 0 ] + do + RALIAS="" + CHOIX_MAIL="" + RESU_ALIAS="" + searchEmail alias + RALIAS=${CHOIX_MAIL} + [[ ${RALIAS} =~ ^[rRqQ]$ ]] && Main + if [[ ${RALIAS} =~ ${regexMail} ]] + then + RESU_ALIAS=$(ldapsearch -H ldap://${LDAP_IP} \ + -x -D "cn=${ldap_LDAP_ADMIN_USERNAME},${ldap_root}" \ + -w "${ldap_LDAP_ADMIN_PASSWORD}" \ + -b "${ldap_root}" "(&(objectclass=PostfixBookMailForward)(cn=${RALIAS}))" cn | grep ^cn | sed -e 's/^cn: //') + if [ ! -z ${RESU_ALIAS} ] + then + faitdel=0 + while [ ${faitdel} = 0 ] + do + read -p "suppression de ${RESU_ALIAS} ? (o/n): " REPDELALIAS + case "${REPDELALIAS}" in + o | O ) + ldapdelete -H ldap://${LDAP_IP} -D "cn=${ldap_LDAP_ADMIN_USERNAME},${ldap_root}" -x -w "${ldap_LDAP_ADMIN_PASSWORD}" "cn=${RESU_ALIAS},ou=mailForwardings,${ldap_root}" + printKazMsg "suppression ${RESU_ALIAS} effectuée" + sleep 2 + faitdel=1 + ;; + n | N ) + faitdel=1 + ;; + "" | * ) + faitdel=0 + ;; + + esac + done + else + fait=0 + fi + + else + printKazError " - format alias invalide !" + sleep 2 + fait=0 + fi + done + delAlias +} + +modifyAlias() +{ + ACTION_EN_COURS="Modfication d' un alias de messagerie" + MRESU_ALIAS="" + LISTE_MAIL_ALIAS="" + NEW_LISTE_MAIL_ALIAS="" + ACHANGE=0 + searchEmail alias + LISTE_MAIL_ALIAS=$(ldapsearch -H ldap://${LDAP_IP} \ + -x -D "cn=${ldap_LDAP_ADMIN_USERNAME},${ldap_root}" \ + -w "${ldap_LDAP_ADMIN_PASSWORD}" \ + -b "${ldap_root}" "(&(objectclass=PostfixBookMailForward)(cn=*${CHOIX_MAIL}*))" \ + | grep -i ^mail: | sed -e 's/^mail: /_/' | tr -d [:space:] | sed -s 's/_/ /g') + echo "-------------------------------------------------------------------" + echo "-------------------------------------------------------------------" + echo -e "${GREEN} [ ${LISTE_MAIL_ALIAS} ] ${NC} " + echo "-------------------------------------------------------------------" + BOUCLE_M_MAIL=0 + NEW_LISTE_MAIL_ALIAS="" + for M_MAIL in ${LISTE_MAIL_ALIAS} + do + read -p " - On garde l' alias ${M_MAIL} (o/n) ? [o] : " READM_MAIL + case ${READM_MAIL} in + "" | o | O ) + NEW_LISTE_MAIL_ALIAS="${NEW_LISTE_MAIL_ALIAS} ${M_MAIL}" + ;; + n | N ) + ACHANGE=$(expr ${ACHANGE} + 1) + NEW_LISTE_MAIL_ALIAS=${NEW_LISTE_MAIL_ALIAS} + ;; + * ) + NEW_LISTE_MAIL_ALIAS="${NEW_LISTE_MAIL_ALIAS} ${M_MAIL}" + ;; + esac + done + TALIAS_SUPP="" + BOUCLE_ADD_MAIL=0 + BOUCLE_ALIAS=0 + read -p "Ajouter un alias ? (o/n) [n] :" RALIAS_SUPP + while [ $BOUCLE_ADD_MAIL = 0 ] + do + case ${RALIAS_SUPP} in + o | O ) + while [ ${BOUCLE_ADD_MAIL} = 0 ] + do + read -p " - Nouvel Alias: ( F pour finir ) : " ALIAS_SUPP + if [[ ${ALIAS_SUPP} =~ ${regexMail} ]] + then + if echo ${NEW_LISTE_MAIL_ALIAS} | grep -w ${ALIAS_SUPP} >/dev/null + then + printKazMsg "Alias déjà existant" + BOUCLE_ADD_MAIL=0 + else + NEW_LISTE_MAIL_ALIAS="${NEW_LISTE_MAIL_ALIAS} ${ALIAS_SUPP}" + ACHANGE=$(expr ${ACHANGE} + 1) + fi + elif [[ ${ALIAS_SUPP} =~ ^[[fF]+$ ]] + then + BOUCLE_ADD_MAIL=1 + else + printKazMsg "erreur de mail" + fi + done + ;; + "" | n | N ) + BOUCLE_ADD_MAIL=1 + ;; + * ) + BOUCLE_ADD_MAIL=1 + ;; + esac + done + if [ "${ACHANGE}" -gt "0" ] + then + printKazMsg "Changement à effectuer : ${NEW_LISTE_MAIL_ALIAS}" + sleep 3 + FIC_MODIF_LDIF=$(mktemp /tmp/$RACINE.XXXXXXXXX.TFIC_MODIF_LDIF.ldif) + echo "dn: cn=${CHOIX_MAIL},ou=mailForwardings,${ldap_root}" >${FIC_MODIF_LDIF} + echo "changeType: modify" >>${FIC_MODIF_LDIF} + echo "replace: mail" >>${FIC_MODIF_LDIF} + for key in ${NEW_LISTE_MAIL_ALIAS} + do + echo "mail: ${key}" >>${FIC_MODIF_LDIF} + done + echo "-" >>${FIC_MODIF_LDIF} + ldapmodify -c -H ldap://${LDAP_IP} -D "cn=${ldap_LDAP_ADMIN_USERNAME},${ldap_root}" \ + -x -w ${ldap_LDAP_ADMIN_PASSWORD} \ + -f ${FIC_MODIF_LDIF} >/dev/null + else + printKazMsg "Pas de changement" + sleep 3 + fi +} + +updateUser() { + rm -rf /tmp/*attributs.txt + ACTION_EN_COURS="Modification d'un compte" + ATTRIB_MAILS="mailDeSecours mailAlias" + ATTRIB_QUOTA="mailQuota nextcloudQuota" + rm -rf /tmp/*fic_attributs.txt + while : + do + FIC_ATTRIBUTS=$(mktemp /tmp/XXXXfic_attributs.txt) + CHOIX_MAIL="" + declare -a ATTRIB + searchEmail + MAILDESECOURSACTUEL="" + MAILDESECOURS="" + Ucompteur=1 + for attribut in mailDeSecours mailAlias mailQuota nextcloudQuota + do + ATTRIB+=([${attribut}]=$(ldapsearch -H ldap://${LDAP_IP} \ + -x -D "cn=${ldap_LDAP_ADMIN_USERNAME},${ldap_root}" \ + -w "${ldap_LDAP_ADMIN_PASSWORD}" \ + -b "${ldap_root}" "(&(objectclass=inetOrgPerson)(cn=*${CHOIX_MAIL}*))" \ + | grep ^"${attribut}": | sed -e 's/^'${attribut}': //' | tr -s '[:space:]' ' ' )) + # si l' attribut est mailDesecours on l' attrape et on on le stocke pour pouvoir l' enlever de sympa + [ ${attribut} = mailDeSecours ] && MAILDESECOURSACTUEL=${ATTRIB[${attribut}]} + echo -e "${Ucompteur} - ${attribut} = ${ATTRIB[${attribut}]}" >>${FIC_ATTRIBUTS} + Ucompteur=$(expr ${Ucompteur} + 1) + done + faitAttrib=0 + declare -A CHANGED + while [ ${faitAttrib} = 0 ] + do + clear + echo "---------------------------------------------------" + printKazMsg "${ACTION_EN_COURS} ${CHOIX_MAIL}" + echo "---------------------------------------------------" + [ "${#CHANGED[@]}" -gt "0" ] && echo "------ Les Modification en cours -------------" + for key in "${!CHANGED[@]}" + do + echo "${GREEN}${key}${NC}: ${CHANGED[$key]}" + done + [ "${#CHANGED[@]}" -gt "0" ] && echo "---------------------------------------------------" + cat ${FIC_ATTRIBUTS} + read -p "Quel attribut est à changer ? (R ou r pour retour F pour Finir ): " REP_ATTRIBUT + case "${REP_ATTRIBUT}" in + r | R ) + if [ "${#CHANGED[@]}" -gt "0" ] + then + echo "------------------------------------------------------------------------" + read -p "=====> ATTENTION : il y a des modifs en cours abandonner ?(o ou n) ? <===== : " RABANDON + case "${RABANDON}" in + o | O ) + faitAttrib=1 + ;; + n | N ) + faitAttrib=0 + ;; + * ) + echo "" + ;; + esac + else + faitAttrib=1 + fi + ;; + [0-9] ) + if [ "${REP_ATTRIBUT}" -gt "$(expr ${Ucompteur} - 1 )" ] + then + printKazMsg "Chiffre invalide" + sleep 3 + else + # pour être sur de virer tous les espaces et les tab etc on utilise [:space:] + # on affiche tout les arguments ( les $1 $2 $2 avec la boucle for) + # on ajoute de ___ pour pouvoir le remplace par un espace entre chaque variable + # afin d' afficher un beau contenu1 contenu2 contenu 3 + + ATTRIBUT_EN_COURS=$(cat ${FIC_ATTRIBUTS} | grep "^${REP_ATTRIBUT}\b" | awk '{print $3}') + CONTENU_ATTRIBUT=$(cat ${FIC_ATTRIBUTS} | grep "^${REP_ATTRIBUT}\b" \ + | awk '{for (i=5; i<=NF; i++) print $i"___"}'\ + | tr -d [:space:] | sed -e 's/___/ /g') + + case ${ATTRIBUT_EN_COURS} in + mailQuota | nextcloudQuota ) + echo "------------------------------------------------" + read -p " - Nouveau ${ATTRIBUT_EN_COURS} (R ou r pour retour) : " RCHANGE + if [[ ${RCHANGE} =~ ^[0-9]+$ ]] + then + [ "${ATTRIBUT_EN_COURS}" = "mailQuota" ] && CHANGED+=([mailQuota]="${RCHANGE}G") + [ "${ATTRIBUT_EN_COURS}" = "nextcloudQuota" ] && CHANGED+=([nextcloudQuota]="${RCHANGE} GB") + else + printKazError "Valeur incorecte" + sleep 3 + fi + faitAttrib=0 + ;; + mailDeSecours ) + echo "------------------------------------------------" + read -p " - Nouveau Mail de Secours : " RCHANGE + if [[ ${RCHANGE} =~ ${regexMail} ]] + then + CHANGED+=([mailDeSecours]=${RCHANGE}) + else + printKazMsg "Mail invalide" + sleep 3 + fi + faitAttrib=0 + ;; + mailAlias ) + NEW_CONTENU_ATTRIBUT="" + READVALMAIL="" + ALIAS_SUPP="" + RALIAS_SUPP="" + TALIAS_SUPP="" + BOUCLE_ADD_MAIL=0 + MAILALIAS_CHANGE=0 + for VALMAIL in ${CONTENU_ATTRIBUT} + do + read -p " - On garde ${VALMAIL} (o/n) ? [o] : " READVALMAIL + case ${READVALMAIL} in + * | "" | o | O ) + NEW_CONTENU_ATTRIBUT="${NEW_CONTENU_ATTRIBUT} ${VALMAIL}" + ;; + n | N ) + NEW_CONTENU_ATTRIBUT="${NEW_CONTENU_ATTRIBUT}" + MAILALIAS_CHANGE=$(expr ${MAILALIAS_INCHANGE} + 1) + ;; + * ) + printKazError "Erreur" + sleep 2 + esac + done + read -p "Ajouter un alias ? (o/n) [n] :" RALIAS_SUPP + case ${RALIAS_SUPP} in + o | O ) + while [ ${BOUCLE_ADD_MAIL} = 0 ] + do + read -p " - ${GREEN}Nouvel Alias: ( F pour finir ) :${NC} " ALIAS_SUPP + BOUCLE_ADD_MAIL=0 + if [[ ${ALIAS_SUPP} =~ ${regexMail} ]] + then + if echo "${CONTENU_ATTRIBUT}" | grep "^${ALIAS_SUPP}$" + then + printKazMsg "Alias déjà existant" + sleep 3 + else + TALIAS_SUPP="${TALIAS_SUPP} ${ALIAS_SUPP}" + MAILALIAS_CHANGE=$(expr ${MAILALIAS_INCHANGE} + 1) + fi + fi + if [[ ${ALIAS_SUPP} =~ ^[[fF]+$ ]] + then + BOUCLE_ADD_MAIL=1 + fi + done + ;; + "" | n | N ) + #CHANGED+=([mailAlias]="${NEW_CONTENU_ATTRIBUT}") + ;; + * ) + printKazMsg "Erreur" + sleep 2 + ;; + esac + [ "${MAILALIAS_CHANGE}" -gt "0" ] && CHANGED+=([mailAlias]="${NEW_CONTENU_ATTRIBUT} ${TALIAS_SUPP}") + faitattrib=0 + ;; + esac + fi + ;; + f | F ) + if [ "${#CHANGED[@]}" -eq "0" ] + then + printKazMsg "Il n'y a pas eu de modif" + sleep 3 + updateUser + else + echo "Départ des modifs" + LISTE_MODIF_USER="" + FIC_MODIF_LDIF=$(mktemp /tmp/$RACINE.XXXXXXXXX.TFIC_MODIF_LDIF.ldif) + echo "dn: cn=${CHOIX_MAIL},ou=users,${ldap_root}" >${FIC_MODIF_LDIF} + echo "changeType: modify" >>${FIC_MODIF_LDIF} + for key in "${!CHANGED[@]}" + do + if [ ${key} = "mailDeSecours" ] + then + MAILDESECOURS=${CHANGED[mailDeSecours]} + fi + if [ ${key} = "mailAlias" ] + then + echo "replace: ${key}" >>${FIC_MODIF_LDIF} + for KEY_MAILALIAS in ${CHANGED[$key]} + do + echo "${key}: ${KEY_MAILALIAS}" >>${FIC_MODIF_LDIF} + done + echo "-" >>${FIC_MODIF_LDIF} + else + echo "replace: ${key}" >>${FIC_MODIF_LDIF} + echo "${key}: ${CHANGED[$key]}" >>${FIC_MODIF_LDIF} + echo "-" >>${FIC_MODIF_LDIF} + fi + done + cat ${FIC_MODIF_LDIF} + sleep 3 + ldapmodify -c -H ldap://${LDAP_IP} -D "cn=${ldap_LDAP_ADMIN_USERNAME},${ldap_root}" \ + -x -w ${ldap_LDAP_ADMIN_PASSWORD} \ + -f ${FIC_MODIF_LDIF} + if [ ! -z ${MAILDESECOURS} ] + then + # suppression du mail de secours de la liste infos + docker exec -ti sympaServ /usr/lib/sympa/bin/sympa_soap_client.pl --soap_url=${httpProto}://${URL_LISTE}/sympasoap --trusted_application=${sympa_SOAP_USER} --trusted_application_password=${sympa_SOAP_PASSWORD} --proxy_vars=USER_EMAIL=${LISTMASTER} --service=del --service_parameters="${NL_LIST},${MAILDESECOURSACTUEL}" + # ajout de l' adresse de la nouvelle adresse de secours + docker exec -ti sympaServ /usr/lib/sympa/bin/sympa_soap_client.pl --soap_url=${httpProto}://${URL_LISTE}/sympasoap --trusted_application=${sympa_SOAP_USER} --trusted_application_password=${sympa_SOAP_PASSWORD} --proxy_vars=USER_EMAIL=${LISTMASTER} --service=add --service_parameters="${NL_LIST},${MAILDESECOURS}" + fi + updateUser + fi + ;; + "" | * ) + faitAttrib=0 + ;; + esac + done + done + +} +############################################################################################################## +############################################################################################################## +usage () { + printKazMsg "$PRG [-h] [-v]" + printKazMsg "script de gestion des kaznautes" + printKazMsg " -h aide sur ce script" + printKazMsg " -v version du script." +} +############################################################################################################## +# test des dépendances +type -P jq | grep -i jq >/dev/null || { echo "erreur jq n' est pas installé ( apt install jq ? ) " ; exit;} +################ Main loop ############################################ +Main() { + +echo -e "${NC}" +while true +do +clear +echo "-------------------------------------------------" +echo "$PRG, version : $VERSION ($KAZ_ROOT)" +echo "-------------------------------------------------" +echo -e "${BOLD}COMPTES : " +echo -e "${NC}*********" +echo -e "${NC}" +echo -e " ${NC}1 - Information sur un compte (alias ou Mail )" +echo -e " ${YELLOW}2 - Modification d' un compte" +echo -e " ${NC}3 - Changement de mot de passe d' un compte" +echo -e " ${GREEN}4 - Suppression d' un compte " +echo -e " ${NC}5 - Création d' une adresse de messagerie" +echo -e "${NC}" +echo -e "${BOLD}ALIAS : " +echo -e "${NC}*******" +echo -e "${NC}" +echo -e " ${NC}6 - Création d' un alias Général pour mails Externes" +echo -e " ${YELLOW}7 - Modification d' un alias Général pour mails Externes" +echo -e " ${GREEN}8 - Suppression d' un alias Général pour mails Externes" +echo -e "${NC}" +echo "-------------------------------------------------" +echo -e "${NC}" +read -p "Choix ? (Q pour quitter) ==> : " CHOICE +case "$CHOICE" in + '1' ) + infoEmail + ;; + '2' ) + updateUser + ;; + '3' ) + gestPassword + ;; + '4' ) + searchDestroy + ;; + '5' ) + createMail + ;; + '6' ) + createAlias + ;; + '7' ) + modifyAlias + ;; + '8' ) + delAlias + ;; + 'h'| "H" ) + clear + echo "--------------------------------------------" + usage + echo "--------------------------------------------" + ;; + 'q'| "Q" ) + exit + ;; + ""|'*' ) + clear + ;; +esac +done +} + + +################################################################################################################################## + +# Lancement de la boucle principale +#traitement des options ( usage ) + +case "$1" in + '-h' ) + usage + exit + ;; + '-v' ) + printKazMsg "$PRG version $VERSION" + exit + ;; +esac + +# on teste la présence du domaine du site dans le fichier autorized-domains.txt +[ ! -e ${KAZ_CONF_DIR}/autorized-domains.txt ] && { echo "création de ${KAZ_CONF_DIR}/autorized-domains.txt" ; touch ${KAZ_CONF_DIR}/autorized-domains.txt;} +! grep $domain ${KAZ_CONF_DIR}/autorized-domains.txt && echo $domain >> ${KAZ_CONF_DIR}/autorized-domains.txt +Main + diff --git a/bin/indicateurs.sh b/bin/indicateurs.sh new file mode 100755 index 0000000..d871b6e --- /dev/null +++ b/bin/indicateurs.sh @@ -0,0 +1,77 @@ +#!/bin/bash +# 23/04/2021 +# script de mise a jour du fichier de collecte pour future intégration dans la base de donneyyy +# did + +KAZ_ROOT=$(cd $(dirname $0)/..; pwd) +. "${KAZ_ROOT}/bin/.commonFunctions.sh" +setKazVars + +FIC_COLLECTE=${KAZ_STATE_DIR}/collecte.csv +FIC_ACTIVITE_MAILBOX=${KAZ_STATE_DIR}/activites_mailbox.csv + +mkdir -p ${KAZ_STATE_DIR} +mkdir -p ${KAZ_STATE_DIR}/metro + +#Jirafeau +echo "$(date +%Y-%m-%d-%H-%M-%S);" \ + "depot-count;" \ + "$(find ${DOCK_VOL}/jirafeau_fileData/_data/files/ -name \*count| wc -l)" >> "${FIC_COLLECTE}" +echo "$(date +%Y-%m-%d-%H-%M-%S);" \ + "depot-size;" \ + "$(du -ks ${DOCK_VOL}/jirafeau_fileData/_data/files/ | awk -F " " '{print $1}')" >> "${FIC_COLLECTE}" + +#PLACE DISQUE sur serveur +echo "$(date +%Y-%m-%d-%H-%M-%S);" \ + "disk-system-size-used;" \ + "$(df | grep sda | awk -F " " '{print $3}')" >> "${FIC_COLLECTE}" +echo "$(date +%Y-%m-%d-%H-%M-%S);" \ + "disk-system-size-used-human;" \ + "$(df -h | grep sda | awk -F " " '{print $3}')" >> "${FIC_COLLECTE}" + +#nombre de mails kaz: +echo "$(date +%Y-%m-%d-%H-%M-%S);" \ + "mailboxes;" \ + "$(cat ${KAZ_COMP_DIR}/postfix/config/postfix-accounts.cf | wc -l)" >> "${FIC_COLLECTE}" + +#nombre d'alias kaz: +echo "$(date +%Y-%m-%d-%H-%M-%S);" \ + "mail_alias;" \ + "$(cat ${KAZ_COMP_DIR}/postfix/config/postfix-virtual.cf | wc -l)" >> "${FIC_COLLECTE}" +#Nombre d' orgas +echo "$(date +%Y-%m-%d-%H-%M-%S);" \ + "Orgas;" \ + "$(ls -l /kaz/dockers/ | grep orga | wc -l)" >> "${FIC_COLLECTE}" + +#stats des 2 postfix (mail+sympa) +EXP=$(/usr/bin/hostname -s) + +STATS1=$(cat ${DOCK_VOL}/sympa_sympaLog/_data/mail.log.1 | /usr/sbin/pflogsumm) +#docker exec -i mailServ mailx -r $EXP -s "stats Sympa" root < "${FIC_ACTIVITE_MAILBOX}" +done + +#pour pister les fuites mémoires +docker stats --no-stream --format "table {{.Name}}\t{{.Container}}\t{{.MemUsage}}" | sort -k 3 -h > "${KAZ_STATE_DIR}/metro/$(date +"%Y%m%d")_docker_memory_kaz.log" +ps aux --sort -rss > "${KAZ_STATE_DIR}/metro/$(date +"%Y%m%d")_ps_kaz.log" +free -hlt > "${KAZ_STATE_DIR}/metro/$(date +"%Y%m%d")_mem_kaz.log" +systemd-cgls --no-pager > "${KAZ_STATE_DIR}/metro/$(date +"%Y%m%d")_cgls_kaz.log" +for i in $(docker container ls --format "{{.ID}}"); do docker inspect -f '{{.State.Pid}} {{.Name}}' $i; done > "${KAZ_STATE_DIR}/metro/$(date +"%Y%m%d")_dockerpid_kaz.log" + +#on piste cette saloperie d'ethercalc +#echo $(date +"%Y%m%d") >> "${KAZ_STATE_DIR}/metro/docker_stats_ethercalc.log" +#docker stats --no-stream ethercalcServ ethercalcDB >> "${KAZ_STATE_DIR}/metro/docker_stats_ethercalc.log" + +#fab le 04/10/2022 +#taille des dockers +docker system df -v > "${KAZ_STATE_DIR}/metro/$(date +"%Y%m%d")_docker_size_kaz.log" diff --git a/bin/init.sh b/bin/init.sh new file mode 100755 index 0000000..e0e395e --- /dev/null +++ b/bin/init.sh @@ -0,0 +1,220 @@ +#!/bin/bash + +KAZ_ROOT=$(cd "$(dirname $0)/.."; pwd) +. "${KAZ_ROOT}/bin/.commonFunctions.sh" +setKazVars + +cd "${KAZ_ROOT}" + +MY_MAIN_IP=$(ip a | grep "inet " | head -2 | tail -1 | sed "s%.*inet *\([0-9.]*\)/.*%\1%") +MY_SECOND_IP=$(ip a | grep "inet " | head -3 | tail -1 | sed "s%.*inet *\([0-9.]*\)/.*%\1%") + +DOMAIN="kaz.local" +DOMAIN_SYMPA="kaz.local" +HTTP_PROTO="https" +MAIN_IP="${MY_MAIN_IP}" +SYMPA_IP="MY_SECOND_IP" +RESTART_POLICY="no" +JIRAFEAU_DIR="/var/jirafeauData/$(apg -n 1 -m 16 -M NCL)/" + +DOCKERS_TMPL_ENV="${KAZ_CONF_DIR}/dockers.tmpl.env" + +RESET_ENV="true" +if [ -f "${DOCKERS_ENV}" ]; then + DOMAIN=$(getValInFile "${DOCKERS_ENV}" "domain") + DOMAIN_SYMPA=$(getValInFile "${DOCKERS_ENV}" "domain_sympa") + HTTP_PROTO=$(getValInFile "${DOCKERS_ENV}" "httpProto") + MAIN_IP=$(getValInFile "${DOCKERS_ENV}" "MAIN_IP") + SYMPA_IP=$(getValInFile "${DOCKERS_ENV}" "SYMPA_IP") + RESTART_POLICY=$(getValInFile "${DOCKERS_ENV}" "restartPolicy") + JIRAFEAU_DIR=$(getValInFile "${DOCKERS_ENV}" "jirafeauDir") + while : ; do + read -p "Change '${DOCKERS_ENV}'? " resetEnv + case "${resetEnv}" in + [yYoO]* ) + break + ;; + ""|[Nn]* ) + RESET_ENV="" + break + ;; + * ) + echo "Please answer yes no." + ;; + esac + done +fi + +[ -n "${RESET_ENV}" ] && { + echo "Reset '${DOCKERS_ENV}'" + read -p " * domain (kaz.bzh / dev.kaz.bzh / kaz.local)? [${YELLOW}${DOMAIN}${NC}] " domain + case "${domain}" in + "" ) + DOMAIN="${DOMAIN}" + ;; + * ) + # XXX ne conserver que .-0-9a-z + DOMAIN=$(sed 's/[^a-z0-9.-]//g' <<< "${domain}") + ;; + esac + + read -p " * lists domain (kaz.bzh / kaz2.ovh / kaz.local)? [${YELLOW}${DOMAIN_SYMPA}${NC}] " domain + case "${domain}" in + "" ) + DOMAIN_SYMPA="${DOMAIN_SYMPA}" + ;; + * ) + DOMAIN_SYMPA="${domain}" + ;; + esac + + while : ; do + read -p " * protocol (https / http)? [${YELLOW}${HTTP_PROTO}${NC}] " proto + case "${proto}" in + "" ) + HTTP_PROTO="${HTTP_PROTO}" + break + ;; + "https"|"http" ) + HTTP_PROTO="${proto}" + break + ;; + * ) echo "Please answer joe, emacs, vim or no." + ;; + esac + done + + while : ; do + read -p " * main IP (ip)? [${YELLOW}${MAIN_IP}${NC}] " ip + case "${ip}" in + "" ) + MAIN_IP="${MAIN_IP}" + break + ;; + * ) + if testValidIp "${ip}" ; then + MAIN_IP="${ip}" + break + else + echo "Please answer x.x.x.x format." + fi + ;; + esac + done + + while : ; do + read -p " * lists IP (ip)? [${YELLOW}${SYMPA_IP}${NC}] " ip + case "${ip}" in + "" ) + SYMPA_IP="${SYMPA_IP}" + break + ;; + * ) + if testValidIp "${ip}" ; then + SYMPA_IP="${ip}" + break + else + echo "Please answer x.x.x.x format." + fi + ;; + esac + done + + while : ; do + read -p " * restart policy (always / unless-stopped / no)? [${YELLOW}${RESTART_POLICY}${NC}] " policy + case "${policy}" in + "" ) + RESTART_POLICY="${RESTART_POLICY}" + break + ;; + "always"|"unless-stopped"|"no") + RESTART_POLICY="${policy}" + break + ;; + * ) echo "Please answer always, unless-stopped or no." + ;; + esac + done + + while : ; do + read -p " * Jirafeau dir? [${YELLOW}${JIRAFEAU_DIR}${NC}] " jirafeauDir + case "${jirafeauDir}" in + "" ) + JIRAFEAU_DIR="${JIRAFEAU_DIR}" + break + ;; + * ) + if [[ "${jirafeauDir}" =~ ^/var/jirafeauData/[0-9A-Za-z]{1,16}/$ ]]; then + JIRAFEAU_DIR="${jirafeauDir}" + break + else + echo "Please give dir name (/var/jirafeauData/[0-9A-Za-z]{1,3}/)." + fi + ;; + esac + done + + [ -f "${DOCKERS_ENV}" ] || cp "${DOCKERS_TMPL_ENV}" "${DOCKERS_ENV}" + + sed -i "${DOCKERS_ENV}" \ + -e "s%^\s*domain\s*=.*$%domain=${DOMAIN}%" \ + -e "s%^\s*domain_sympa\s*=.*$%domain_sympa=${DOMAIN_SYMPA}%" \ + -e "s%^\s*httpProto\s*=.*$%httpProto=${HTTP_PROTO}%" \ + -e "s%^\s*MAIN_IP\s*=.*$%MAIN_IP=${MAIN_IP}%" \ + -e "s%^\s*SYMPA_IP\s*=.*$%SYMPA_IP=${SYMPA_IP}%" \ + -e "s%^\s*restartPolicy\s*=.*$%restartPolicy=${RESTART_POLICY}%" \ + -e "s%^\s*ldapRoot\s*=.*$%ldapRoot=dc=${DOMAIN_SYMPA/\./,dc=}%" \ + -e "s%^\s*jirafeauDir\s*=.*$%jirafeauDir=${JIRAFEAU_DIR}%" +} + +if [ ! -f "${KAZ_CONF_DIR}/container-mail.list" ]; then + cat > "${KAZ_CONF_DIR}/container-mail.list" < "${KAZ_CONF_DIR}/container-orga.list" < "${KAZ_CONF_DIR}/container-proxy.list" < "${KAZ_CONF_DIR}/container-withMail.list" < "${KAZ_CONF_DIR}/container-withoutMail.list" < >(tee ${DebugLog}stdout.log) 2> >(tee ${DebugLog}stderr.log >&2) diff --git a/bin/installDepollueur.sh b/bin/installDepollueur.sh new file mode 100755 index 0000000..e99489f --- /dev/null +++ b/bin/installDepollueur.sh @@ -0,0 +1,27 @@ +#!/bin/bash + +SRC_DEP=https://git.kaz.bzh/KAZ/depollueur.git + +KAZ_ROOT=$(cd "$(dirname $0)/.."; pwd) +. "${KAZ_ROOT}/bin/.commonFunctions.sh" +setKazVars + +if [[ -f "${KAZ_GIT_DIR}/depollueur/test-running" ]]; then + exit +fi + +printKazMsg "\n *** Installation du dépollueur" + +sudo apt-get install -y --fix-missing build-essential make g++ libboost-program-options-dev libboost-system-dev libboost-filesystem-dev libcurl4-gnutls-dev libssl-dev + +mkdir -p "${KAZ_GIT_DIR}" +cd "${KAZ_GIT_DIR}" +if [ ! -d "depollueur" ]; then + git clone "${SRC_DEP}" +fi +cd depollueur +git reset --hard && git pull +make + +. "${DOCKERS_ENV}" +echo "${domain}" > "src/bash/domainname" diff --git a/bin/interoPaheko.sh b/bin/interoPaheko.sh new file mode 100755 index 0000000..9fdadde --- /dev/null +++ b/bin/interoPaheko.sh @@ -0,0 +1,166 @@ +#!/bin/bash + +KAZ_ROOT=$(cd "$(dirname $0)"/..; pwd) +KAZ_ROOT=/kaz +. $KAZ_ROOT/bin/.commonFunctions.sh +setKazVars + +. $DOCKERS_ENV +. $KAZ_ROOT/secret/SetAllPass.sh + +URL_PAHEKO="$httpProto://${paheko_API_USER}:${paheko_API_PASSWORD}@kaz-paheko.$(echo $domain)" + +PRG=$(basename $0) +RACINE=$(echo $PRG | awk '{print $1}') + +TFILE_INT_PAHEKO_ACTION=$(mktemp /tmp/XXXXXXXX_INT_PAHEKO_ACTION.json) +TFILE_INT_PAHEKO_IDFILE=$(mktemp /tmp/XXXXXXXX_TFILE_INT_PAHEKO_IDFILE.json) +FILE_CREATEUSER="$KAZ_ROOT/tmp/createUser.txt" + +sep=' ' + +#trap "rm -f ${TFILE_INT_PAHEKO_IDFILE} ${TFILE_INT_PAHEKO_ACTION} " 0 1 2 3 15 + +############################################ Fonctions ####################################################### +TEXTE=" +# -- fichier de création des comptes KAZ +# -- +# -- 1 ligne par compte +# -- champs séparés par ;. les espaces en début et en fin sont enlevés +# -- laisser vide si pas de donnée +# -- pas d'espace dans les variables +# -- +# -- ORGA: nom de l'organisation (max 15 car), vide sinon +# -- ADMIN_ORGA: O/N indique si le user est admin de l'orga (va le créer comme admin du NC de l'orga et admin de l'équipe agora) +# -- NC_ORGA: O/N indique si l'orga a demandé un NC +# -- PAHEKO_ORGA: O/N indique si l'orga a demandé un paheko +# -- WP_ORGA: O/N indique si l'orga a demandé un wp +# -- AGORA_ORGA: O/N indique si l'orga a demandé un mattermost +# -- WIKI_ORGA: O/N indique si l'orga a demandé un wiki +# -- NC_BASE: O/N indique si le user doit être inscrit dans le NC de base +# -- GROUPE_NC_BASE: soit null soit le groupe dans le NC de base +# -- EQUIPE_AGORA: soit null soit equipe agora (max 15 car) +# -- QUOTA=(1/10/20/...) en GB +# -- +# NOM ; PRENOM ; EMAIL_SOUHAITE ; EMAIL_SECOURS ; ORGA ; ADMIN_ORGA ; NC_ORGA ; PAHEKO_ORGA ; WP_ORGA ; AGORA_ORGA ; WIKI_ORGA ; NC_BASE ; GROUPE_NC_BASE ; EQUIPE_AGORA ; QUOTA +# +# exemple pour un compte découverte: +# dupont ; jean-louis; jean-louis.dupont@kaz.bzh ; gregomondo@kaz.bzh; ; N; N; N; N; N; N; O; ; ;1 +# +# exemple pour un compte asso de l'orga gogol avec le service dédié NC uniquement + une équipe dans l'agora +# dupont ; jean-louis; jean-louis.dupont@kaz.bzh ; gregomondo@kaz.bzh; gogol ; O; O; N; N; N; N;N;;gogol_team; 10 +" + +Int_paheko_Action() { + # $1 est une action; + ACTION=$1 + OPTION=$2 + # on envoie la requête sur le serveur paheko avec la clause à créer + # problème de gestion de remontée de données dans la table services_users quand le compte a plus de 2 activités + #curl -s ${URL_PAHEKO}/api/sql -d "SELECT * from users cross join services_users on users.id = services_users.id_user where users.action_auto='${ACTION}';" >>${TFILE_INT_PAHEKO_ACTION} + curl -s ${URL_PAHEKO}/api/sql -d "SELECT * from users where action_auto='${ACTION}';" >>${TFILE_INT_PAHEKO_ACTION} + [ ! -z ${TFILE_INT_PAHEKO_ACTION} ] || { echo "probleme de fichier ${TFILE_INT_PAHEKO_ACTION}" ; exit 1;} + REP_ID=$(jq -c '.results[].id ' ${TFILE_INT_PAHEKO_ACTION} 2>/dev/null) + if [ ! -z "${REP_ID}" ] + then + [ "$OPTION" = "silence" ] || echo -e "${RED}Nombre de compte ${ACTION} ${NC}= ${GREEN} $(echo ${REP_ID} | wc -w) ${NC}" + if [ -f "$FILE_CREATEUSER" ] + then + mv $FILE_CREATEUSER $FILE_CREATEUSER.$(date +%d-%m-%Y-%H:%M:%S) + fi + echo "# -------- Fichier généré le $(date +%d-%m-%Y-%H:%M:%S) ----------">${FILE_CREATEUSER} + echo "${TEXTE}" >>${FILE_CREATEUSER} + for VAL_ID in ${REP_ID} + do + jq -c --argjson val "${VAL_ID}" '.results[] | select (.id == $val)' ${TFILE_INT_PAHEKO_ACTION} > ${TFILE_INT_PAHEKO_IDFILE} + for VAL_GAR in id_category action_auto nom email email_secours quota_disque admin_orga nom_orga responsable_organisation responsable_email agora cloud wordpress garradin docuwiki id_service + do + eval $VAL_GAR=$(jq .$VAL_GAR ${TFILE_INT_PAHEKO_IDFILE}) + done + #comme tout va bien on continue + #on compte le nom de champs dans la zone nom pour gérer les noms et prénoms composés + # si il y a 3 champs, on associe les 2 premieres valeurs avec un - et on laisse le 3ème identique + # si il y a 4 champs on associe les 1 et le 2 avec un tiret et le 3 et 4 avec un tiret + # on met les champs nom_ok et prenom_ok à blanc + nom_ok="" + prenom_ok="" + # on regarde si le nom de l' orga est renseigné ou si le nom de l' orga est null et l' activité de membre est 7 (membre rattaché) + # si c' est le cas alors le nom est le nom de l' orga et le prénom est forcé à la valeur Organisation + if [[ "$nom_orga" = null ]] || [[ "$nom_orga" != null && "$id_service" = "7" ]] + then + [ "$OPTION" = "silence" ] || echo -e "${NC}Abonné ${GREEN}${nom}${NC}" + #si lactivité est membre rattaché on affiche a quelle orga il est rattaché + if [ "$id_service" = "7" ] && [ "$OPTION" != "silence" ] && [ "$nom_orga" != null ] + then + echo -e "${NC}Orga Rattachée : ${GREEN}${nom_orga}${NC}" + fi + COMPTE_NOM=$(echo $nom | awk -F' ' '{for (i=1; i != NF; i++); print i;}') + case "${COMPTE_NOM}" in + 0|1) + echo "Il faut corriger le champ nom (il manque un nom ou prénom) de paheko" + echo "je quitte et supprime le fichier ${FILE_CREATEUSER}" + rm -f $FILE_CREATEUSER + exit 2 + ;; + 2) + nom_ok=$(echo $nom | awk -F' ' '{print $1}') + prenom_ok=$(echo $nom | awk -F' ' '{print $2}') + ;; + *) + nom_ok= + prenom_ok= + for i in ${nom}; do grep -q '^[A-Z]*$' <<<"${i}" && nom_ok="${nom_ok}${sep}${i}" || prenom_ok="${prenom_ok}${sep}${i}"; done + nom_ok="${nom_ok#${sep}}" + prenom_ok="${prenom_ok#${sep}}" + if [ -z "${nom_ok}" ] || [ -z "${prenom_ok}" ]; then + echo "Il faut corriger le champ nom (peut être un nom de famille avec une particule ?) de paheko" + echo "je quitte et supprime le fichier ${FILE_CREATEUSER}" + rm -f $FILE_CREATEUSER + exit + fi + esac + # comme l' orga est à null nom orga est a vide, pas d' admin orga, on met dans l' agora générale + # pas d' équipe agora et de groupe nextcloud spécifique + + nom_orga=" " + admin_orga="N" + nc_base="O" + equipe_agora=" " + groupe_nc_base=" " + else + # L' orga est renseigné dans paheko donc les nom et prenoms sont forcé a nom_orga et Organisation + # un équipe agora portera le nom de l' orga, le compte ne sera pas créé dans le nextcloud général + # et le compte est admin de son orga + nom_orga=$(echo $nom_orga | tr [:upper:] [:lower:]) + [ "$OPTION" = "silence" ] || echo -e "${NC}Orga : ${GREEN}${nom_orga}${NC}" + nom_ok=$nom_orga + # test des caractères autorisés dans le nom d' orga: lettres, chiffres et/ou le tiret + if ! [[ "${nom_ok}" =~ ^[[:alnum:]-]+$ ]]; then + echo "Erreur : l' orga doit être avec des lettres et/ou des chiffres. Le séparateur doit être le tiret" + rm -f $FILE_CREATEUSER� + exit 2 + fi + prenom_ok=organisation + equipe_agora=$nom_orga + groupe_nc_base=" " + nc_base="N" + admin_orga="O" + fi + # Pour le reste on renomme les null en N ( non ) et les valeurs 1 en O ( Oui) + cloud=$(echo $cloud | sed -e 's/0/N/g' | sed -e 's/1/O/g') + paheko=$(echo $garradin | sed -e 's/0/N/g' | sed -e 's/1/O/g') + wordpress=$(echo $wordpress | sed -e 's/0/N/g' | sed -e 's/1/O/g') + agora=$(echo $agora | sed -e 's/0/N/g' | sed -e 's/1/O/g') + docuwiki=$(echo $docuwiki | sed -e 's/0/N/g' | sed -e 's/1/O/g') + # et enfin on écrit dans le fichier + echo "$nom_ok;$prenom_ok;$email;$email_secours;$nom_orga;$admin_orga;$cloud;$paheko;$wordpress;$agora;$docuwiki;$nc_base;$groupe_nc_base;$equipe_agora;$quota_disque">>${FILE_CREATEUSER} + done + else + echo "Rien à créer" + exit 2 + fi +} +#Int_paheko_Action "A créer" "silence" +Int_paheko_Action "A créer" +exit 0 + diff --git a/bin/iptables.sh b/bin/iptables.sh new file mode 100755 index 0000000..9ad9dfa --- /dev/null +++ b/bin/iptables.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +#cleaning, may throw errors at first launch +#iptables -t nat -D POSTROUTING -o ens18 -j ipbis +#iptables -t nat -F ipbis +#iptables -t nat -X ipbis + +iptables -t nat -N ipbis +iptables -t nat -F ipbis +iptables -t nat -I ipbis -o ens18 -p tcp --source `docker inspect -f '{{.NetworkSettings.Networks.sympaNet.IPAddress}}' sympaServ` -j SNAT --to `ifconfig ens18:0 | grep "inet" | awk '{print $2}'` +iptables -t nat -I ipbis -o ens18 -p tcp --source `docker inspect -f '{{.NetworkSettings.Networks.jirafeauNet.IPAddress}}' sympaServ` -j SNAT --to `ifconfig ens18:0 | grep "inet" | awk '{print $2}'` +iptables -t nat -A ipbis -j RETURN +iptables -t nat -D POSTROUTING -o ens18 -j ipbis +iptables -t nat -I POSTROUTING -o ens18 -j ipbis diff --git a/bin/kazDockerNet.sh b/bin/kazDockerNet.sh new file mode 100755 index 0000000..c41c565 --- /dev/null +++ b/bin/kazDockerNet.sh @@ -0,0 +1,110 @@ +#!/bin/bash + +# faire un completion avec les composant dispo + +PRG=$(basename $0) + +KAZ_ROOT=$(cd "$(dirname $0)/.."; pwd) +. "${KAZ_ROOT}/bin/.commonFunctions.sh" +setKazVars + +usage () { + echo "Usage: ${PRG} [-n] [-h] list|add [netName]..." + echo " -n : simulation" + echo " -h|--help : help" + echo + echo " create all net : ${PRG} add $(${KAZ_BIN_DIR}/kazList.sh compose validate)" + exit 1 +} + +allNetName="" +export CMD="" +for ARG in $@; do + case "${ARG}" in + '-h' | '-help' ) + usage + ;; + '-n' ) + shift + export SIMU="echo" + ;; + -*) + usage + ;; + list|add) + CMD="${ARG}" + shift; + ;; + *) + allNetName="${allNetName} ${ARG}" + shift + ;; + esac +done + +if [ -z "${CMD}" ] ; then + usage +fi + +# running composes +export allBridgeName="$(docker network list | grep bridge | awk '{print $2}')" +# running network +export allBridgeNet=$(for net in ${allBridgeName} ; do docker inspect ${net} | grep Subnet | sed 's#.*"\([0-9]*\.[0-9]*\.[0-9]*\.[0-9]*/[0-9]*\)".*# \1#'; done) + +minB=0 +minC=0 +minD=0 + +getNet() { + netName="$1Net" + + if [[ "${allBridgeName}" =~ "${netName}" ]]; then + echo "${netName} already created" + return + fi + # echo "start 10.${minB}.${minC}.$((${minD}*16))" + + find="" + for b in $(eval echo {${minB}..255}); do + for c in $(eval echo {${minC}..255}); do + for d in $(eval echo {${minD}..15}); do + if [ ! -z "${find}" ]; then + minB=${b} + minC=${c} + minD=${d} + return + fi + # to try + subnet="10.${b}.${c}.$((d*16))" + if [[ "${allBridgeNet}" =~ " ${subnet}/" ]]; + then + # used + # XXX check netmask + continue + fi + # the winner is... + echo "${netName} => ${subnet}/28" + ${SIMU} docker network create --subnet "${subnet}/28" "${netName}" + find="ok" + done + minD=0 + done + minC=0 + done +} + +list () { + echo "name: " ${allBridgeName} + echo "net: " ${allBridgeNet} +} + +add () { + if [ -z "${allNetName}" ] ; then + usage + fi + for netName in ${allNetName}; do + getNet "${netName}" + done +} + +"${CMD}" diff --git a/bin/kazList.sh b/bin/kazList.sh new file mode 100755 index 0000000..2816f2b --- /dev/null +++ b/bin/kazList.sh @@ -0,0 +1,181 @@ +#!/bin/bash + +PRG=$(basename $0) +SIMU="" +KAZ_ROOT=$(cd "$(dirname $0)"/..; pwd) +. "${KAZ_ROOT}/bin/.commonFunctions.sh" +setKazVars +. "${DOCKERS_ENV}" + +cd "$(dirname $0)" + +ALL_STATUS=$(docker ps -a --format "{{.ID}} {{.Names}} {{.Status}}") +SERVICES_CHOICE="$(getAvailableServices | tr "\n" "|")" +SERVICES_CHOICE="${SERVICES_CHOICE%|}" + +usage () { + echo "${RED}${BOLD}" \ + "Usage: $0 [-h] {compose|orga|service} {available|validate|enable|disable|status} [names]...${NL}" \ + " -h help${NL}" \ + " compose {available|validate|enable|disable} : list docker-compose name${NL}" \ + " compose status : status of docker-compose (default available)${NL}" \ + " service {available|validate} : list services name${NL}" \ + " service {enable|disable} : list services in orga${NL}" \ + " service status : status of services in orga${NL}" \ + " service {${SERVICES_CHOICE}}${NL}" \ + " : list of orga enable a service ${NL}" \ + " [compose] in${NL}" \ + " ${CYAN}$((getAvailableComposes;getAvailableOrgas) | tr "\n" " ")${NC}${NL}" + exit 1 +} + +# ======================================== +compose_available () { + echo $* +} + +getComposeEnableByProxy () { + onList=$( + for type in ${KAZ_CONF_DIR}/container-*.list ; do + getList "${type}" + done) + local compose + for compose in ${onList} ; do + composeFlag="proxy_${compose//-/_}" + [[ "${!composeFlag}" == "on" ]] && echo ${compose} + done +} + +compose_validate () { + echo $( + for type in ${KAZ_CONF_DIR}/container-*.list ; do + getList "${type}" + done | filterInList $*) +} + +compose_enable () { + echo $(getComposeEnableByProxy | filterInList $*) +} + +compose_disable () { + echo $(getAvailableComposes | filterNotInList $(getComposeEnableByProxy) | filterInList $*) +} + +compose_status () { + for compose in $*; do + cd "${KAZ_COMP_DIR}/${compose}" + echo "${compose}:" + for service in $(docker-compose ps --services 2>/dev/null); do + id=$(docker-compose ps -q "${service}" | cut -c 1-12) + if [ -z "${id}" ]; then + echo " - ${RED}${BOLD}[Down]${NC} ${service}" + else + status=$(grep "^${id}\b" <<< "${ALL_STATUS}" | sed "s/.*${id}\s\s*\S*\s\s*\(\S*.*\)/\1/") + COLOR=$([[ "${status}" =~ Up ]] && echo "${GREEN}" || echo "${RED}") + echo " - ${COLOR}${BOLD}[${status}]${NC} ${service}" + fi + done + done +} + +# ======================================== +service_available () { + echo $(getAvailableServices) +} + +service_validate () { + echo $(getAvailableServices) +} + +getServiceInOrga () { + for orga in $*; do + [[ "${orga}" = *"-orga" ]] || continue + local ORGA_DIR="${KAZ_COMP_DIR}/${orga}" + ORGA_COMPOSE="${ORGA_DIR}/docker-compose.yml" + [[ -f "${ORGA_COMPOSE}" ]] || continue + for service in $(getAvailableServices); do + case "${service}" in + paheko) + [ -f "${ORGA_DIR}/usePaheko" ] && echo "${service}" + ;; + wiki) + grep -q "\s*dokuwiki:" "${ORGA_COMPOSE}" 2>/dev/null && echo "${service}" + ;; + wp) + grep -q "\s*wordpress:" "${ORGA_COMPOSE}" 2>/dev/null && echo "${service}" + ;; + *) + grep -q "\s*${service}:" "${ORGA_COMPOSE}" 2>/dev/null && echo "${service}" + esac + done + done +} + +getOrgaWithService() { + service="$1" + shift + case "${service}" in + wiki) keyword="dokuwiki" ;; + wp) keyword="wordpress" ;; + *) keyword="${service}" ;; + esac + for orga in $*; do + [[ "${orga}" = *"-orga" ]] || continue + local ORGA_DIR="${KAZ_COMP_DIR}/${orga}" + ORGA_COMPOSE="${ORGA_DIR}/docker-compose.yml" + [[ -f "${ORGA_COMPOSE}" ]] || continue + if [ "${service}" = "paheko" ]; then + [ -f "${ORGA_DIR}/usePaheko" ] && echo "${orga}" + else + grep -q "\s*${keyword}:" "${ORGA_COMPOSE}" 2>/dev/null && echo "${orga}" + fi + done +} + +service_enable () { + echo $(getServiceInOrga $* | sort -u) +} + +service_disable () { + echo $(getAvailableServices | filterNotInList $(getServiceInOrga $*)) +} + +service_status () { + # ps per enable + echo "*** TODO ***" +} + +# ======================================== + +KAZ_CMD="" +case "$1" in + '-h' | '-help' ) + usage + ;; + compose|service) + KAZ_CMD="$1" + shift + ;; + *) + usage + ;; +esac + +KAZ_OPT="" +case "$1" in + available|validate|enable|disable|status) + KAZ_OPT="$1" + shift + ;; + *) + if [ "${KAZ_CMD}" = "service" ] && [[ $1 =~ ^(${SERVICES_CHOICE})$ ]]; then + KAZ_OPT="$1" + shift + getOrgaWithService "${KAZ_OPT}" $(filterAvailableComposes $*) + exit + fi + usage + ;; +esac + +${KAZ_CMD}_${KAZ_OPT} $(filterAvailableComposes $*) diff --git a/bin/ldap/ldap_sauve.sh b/bin/ldap/ldap_sauve.sh new file mode 100755 index 0000000..ca138be --- /dev/null +++ b/bin/ldap/ldap_sauve.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +KAZ_ROOT=/kaz +. $KAZ_ROOT/bin/.commonFunctions.sh +setKazVars + +FILE_LDIF=/home/sauve/ldap.ldif + +. $DOCKERS_ENV +. $KAZ_ROOT/secret/SetAllPass.sh + +docker exec -u 0 -i ${ldapServName} slapcat -F /opt/bitnami/openldap/etc/slapd.d -b ${ldap_root} | gzip >${FILE_LDIF}.gz diff --git a/bin/ldap/ldapvi.sh b/bin/ldap/ldapvi.sh new file mode 100755 index 0000000..d557b0b --- /dev/null +++ b/bin/ldap/ldapvi.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +KAZ_ROOT=/kaz +. $KAZ_ROOT/bin/.commonFunctions.sh +setKazVars + +. $DOCKERS_ENV +. $KAZ_ROOT/secret/SetAllPass.sh + +LDAP_IP=$(docker inspect -f '{{.NetworkSettings.Networks.ldapNet.IPAddress}}' ldapServ) + +read -p "quel éditeur ? [vi] " EDITOR +EDITOR=${EDITOR:-vi} + +# if [ ${EDITOR} = 'emacs' ]; then +# echo "ALERTE ALERTE !!! quelqu'un a voulu utiliser emacs :) :) :)" +# exit +# fi + +EDITOR=${EDITOR:-vi} +export EDITOR=${EDITOR} + +ldapvi -h $LDAP_IP -D "cn=${ldap_LDAP_ADMIN_USERNAME},${ldap_root}" -w ${ldap_LDAP_ADMIN_PASSWORD} --discover diff --git a/bin/ldap/migrate_to_ldap.sh b/bin/ldap/migrate_to_ldap.sh new file mode 100755 index 0000000..ff339e7 --- /dev/null +++ b/bin/ldap/migrate_to_ldap.sh @@ -0,0 +1,222 @@ +#!/bin/bash + +echo "ATTENTION ! Il ne faut plus utiliser ce script, il est probable qu'il commence à mettre la grouille avec le LDAP qui vit sa vie..." +exit 1 + +KAZ_ROOT=/kaz +. $KAZ_ROOT/bin/.commonFunctions.sh +setKazVars + +. $DOCKERS_ENV +. $KAZ_ROOT/secret/SetAllPass.sh + +ACCOUNTS=/kaz/dockers/postfix/config/postfix-accounts.cf + +LDAP_IP=$(docker inspect -f '{{.NetworkSettings.Networks.ldapNet.IPAddress}}' ldapServ) +URL_GARRADIN="$httpProto://${paheko_API_USER}:${paheko_API_PASSWORD}@kaz-paheko.$(echo $domain)" +# docker exec -i nextcloudDB mysql --user=${nextcloud_MYSQL_USER} --password=${nextcloud_MYSQL_PASSWORD} ${nextcloud_MYSQL_DATABASE} <<< "select * from oc_accounts;" > /tmp/oc_accounts + +ERRORS="/tmp/ldap-errors.log" +> ${ERRORS} + +mkdir -p /tmp/ldap/ + +# curl -s ${URL_GARRADIN}/api/sql -d "SELECT * from membres where emails_rattaches LIKE '%mailrattache%';" + +for line in `cat ${ACCOUNTS}` +do + mail=$(echo $line | awk -F '|' '{print $1}') + user=$(echo $mail | awk -F '@' '{print $1}') + domain=$(echo $mail | awk -F '@' '{print $2}') + pass=$(echo $line | awk -F '|' '{print $2}' | sed -e "s/SHA512-//") + IDENT_KAZ= + if [ ${mode} = "prod" ]; then + ficheGarradin=$(curl -s ${URL_GARRADIN}/api/sql -d "SELECT * from membres where email='${mail}';") + mailDeSecours=$(echo ${ficheGarradin} | jq .results[0].email_secours | sed -e "s/\"//g") + quota=$(echo ${ficheGarradin} | jq .results[0].quota_disque | sed -e "s/\"//g") + nom=$(echo ${ficheGarradin} | jq .results[0].nom | sed -e "s/\"//g") + nom_orga=$(echo ${ficheGarradin} | jq .results[0].nom_orga | sed -e "s/\"//g") + else + mailDeSecours=${mail} + quota=1 + nom=${mail} + nom_orga="null" + fi + + if [ "${quota}" = "null" ]; then + quota=1 + fi + + # nextcloudEnabled=MAYBE + # IDENT_KAZ=$(grep -i \"${mail}\" /tmp/oc_accounts | cut -f1) + # + # if [ ! -z "${IDENT_KAZ}" ]; then # ident Kaz trouvé avec le mail Kaz + # nextcloudEnabled=TRUE + # else # pas trouvé avec le mail Kaz + # if [ "${nom_orga}" != "null" ]; then # c'est une orga, pas de NC + # IDENT_KAZ="null" + # nextcloudEnabled=FALSE + # else # pas trouvé avec le mail Kaz, pas une orga, on retente avec le mail de secours + # IDENT_KAZ=$(grep -i \"${mailDeSecours}\" /tmp/oc_accounts | cut -f1 | head -n1) + # if [ ! -z "${IDENT_KAZ}" ]; then # on a trouvé l'ident kaz chez NC avec le mail de secours + # nextcloudEnabled=TRUE + # else # pas trouvé avec le mail Kaz, pas une orga, pas trouvé avec le mail de secours + # ficheRattache=$(curl -s ${URL_GARRADIN}/api/sql -d "SELECT * from membres where emails_rattaches LIKE '%${mail}%';" | jq ".results | length") + # if [ $ficheRattache != "0" ]; then # c'est un mail rattaché, pas de NC c'est normal + # IDENT_KAZ="null" + # nextcloudEnabled=FALSE + # else # pas trouvé, pas une orga, pas mail rattaché donc souci + # echo "Pas trouvé l'identifiant Kaz nextcloud pour ${mail} / ${mailDeSecours}, on désactive nextcloud pour ce compte" >> ${ERRORS} + # IDENT_KAZ="null" + # nextcloudEnabled=FALSE + # fi + # fi + # fi + # fi + + + echo -e "\n\ndn: cn=${mail},ou=users,${ldap_root}\n\ +changeType: add\n\ +objectClass: inetOrgPerson\n\ +sn: ${nom}\n\ +userPassword: ${pass}\n\ +\n\n\ +dn: cn=${mail},ou=users,${ldap_root}\n\ +changeType: modify\n\ +replace: objectClass\n\ +objectClass: inetOrgPerson\n\ +objectClass: kaznaute\n\ +objectClass: PostfixBookMailAccount\n\ +objectClass: nextcloudAccount\n\ +-\n\ +replace: sn\n\ +sn: ${nom}\n\ +-\n\ +replace: mail\n\ +mail: ${mail}\n\ +-\n\ +replace: mailEnabled\n\ +mailEnabled: TRUE\n\ +-\n\ +replace: mailGidNumber\n\ +mailGidNumber: 5000\n\ +-\n\ +replace: mailHomeDirectory\n\ +mailHomeDirectory: /var/mail/${domain}/${user}/\n\ +-\n\ +replace: mailQuota\n\ +mailQuota: ${quota}G\n\ +-\n\ +replace: mailStorageDirectory\n\ +mailStorageDirectory: maildir:/var/mail/${domain}/${user}/\n\ +-\n\ +replace: mailUidNumber\n\ +mailUidNumber: 5000\n\ +-\n\ +replace: nextcloudQuota\n\ +nextcloudQuota: ${quota} GB\n\ +-\n\ +replace: mailDeSecours\n\ +mailDeSecours: ${mailDeSecours}\n\ +-\n\ +replace: quota\n\ +quota: ${quota}\n\ +-\n\ +replace: agoraEnabled\n\ +agoraEnabled: TRUE\n\ +-\n\ +replace: mobilizonEnabled\n\ +mobilizonEnabled: TRUE\n\n" | tee /tmp/ldap/${mail}.ldif | ldapmodify -c -H ldap://${LDAP_IP} -D "cn=${ldap_LDAP_ADMIN_USERNAME},${ldap_root}" -x -w ${ldap_LDAP_ADMIN_PASSWORD} +done + +#replace: nextcloudEnabled\n\ +#nextcloudEnabled: ${nextcloudEnabled}\n\ +#-\n\ +#replace: identifiantKaz\n\ +#identifiantKaz: ${IDENT_KAZ}\n\ +#-\n\ + +OLDIFS=${IFS} +IFS=$'\n' +# ALIASES est le fichier d'entrée +ALIASES="/kaz/dockers/postfix/config/postfix-virtual.cf" +# ALIASES_WITHLDAP est le fichier de sortie des forwards qu'on ne met pas dans le ldap +ALIASES_WITHLDAP="/kaz/dockers/postfix/config/postfix-virtual-withldap.cf" +# On vide le fichier de sortie avant de commencer +> ${ALIASES_WITHLDAP} + + +for line in `cat ${ALIASES}` +do + echo "Virtual line is $line" + if [ `grep -v "," <<< $line` ] + then + echo "Alias simple" + mail=$(echo $line | awk -F '[[:space:]]*' '{print $2}') + if [ `grep $mail ${ACCOUNTS}` ] + then + echo "Alias vers un mail local, go ldap" + LIST="" + for alias in `grep ${mail} ${ALIASES} | grep -v "," | cut -d' ' -f1` + do + LIST=${LIST}"mailAlias: $alias\n" + done + echo -e "dn: cn=${mail},ou=users,${ldap_root}\n\ +changeType: modify +replace: mailAlias\n\ +$LIST\n\n" | ldapmodify -c -H ldap://${LDAP_IP} -D "cn=${ldap_LDAP_ADMIN_USERNAME},${ldap_root}" -x -w ${ldap_LDAP_ADMIN_PASSWORD} + else + echo "Alias vers un mail externe, go fichier" + echo $line >> ${ALIASES_WITHLDAP} + echo " + intégration LDAP" + src=$(echo $line | awk -F '[[:space:]]*' '{print $1}') + dst=$(echo $line | awk -F '[[:space:]]*' '{print $2}') + echo -e "\n\ndn: cn=${src},ou=mailForwardings,${ldap_root}\n\ +changeType: add\n\ +objectClass: organizationalRole\n\ +\n\n\ +dn: cn=${src},ou=mailForwardings,${ldap_root}\n\ +changeType: modify\n\ +replace: objectClass\n\ +objectClass: organizationalRole\n\ +objectClass: PostfixBookMailForward\n\ +-\n\ +replace: mailAlias\n\ +mailAlias: ${src}\n\ +-\n\ +replace: mail\n\ +mail: ${dst}\n\n" | ldapmodify -c -H ldap://${LDAP_IP} -D "cn=${ldap_LDAP_ADMIN_USERNAME},${ldap_root}" -x -w ${ldap_LDAP_ADMIN_PASSWORD} + fi + else + echo "Forward vers plusieurs adresses, on met dans le fichier" + echo $line >> ${ALIASES_WITHLDAP} + echo " + intégration LDAP" + src=$(echo $line | awk -F '[[:space:]]*' '{print $1}') + dst=$(echo $line | awk -F '[[:space:]]*' '{print $2}') + OOLDIFS=${IFS} + IFS="," + LIST="" + for alias in ${dst} + do + LIST=${LIST}"mail: $alias\n" + done + IFS=${OOLDIFS} + echo -e "\n\ndn: cn=${src},ou=mailForwardings,${ldap_root}\n\ +changeType: add\n\ +objectClass: organizationalRole\n\ +\n\n\ +dn: cn=${src},ou=mailForwardings,${ldap_root}\n\ +changeType: modify\n\ +replace: objectClass\n\ +objectClass: organizationalRole\n\ +objectClass: PostfixBookMailForward\n\ +-\n\ +replace: mailAlias\n\ +mailAlias: ${src}\n\ +-\n\ +replace: mail\n\ +${LIST}\n\n" | ldapmodify -c -H ldap://${LDAP_IP} -D "cn=${ldap_LDAP_ADMIN_USERNAME},${ldap_root}" -x -w ${ldap_LDAP_ADMIN_PASSWORD} + + fi +done +IFS=${OLDIFS} diff --git a/bin/ldap/tests/nc_orphans.sh b/bin/ldap/tests/nc_orphans.sh new file mode 100755 index 0000000..c4e97d6 --- /dev/null +++ b/bin/ldap/tests/nc_orphans.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +KAZ_ROOT=/kaz +. $KAZ_ROOT/bin/.commonFunctions.sh +setKazVars + +. $DOCKERS_ENV +. $KAZ_ROOT/secret/SetAllPass.sh + +LDAP_IP=$(docker inspect -f '{{.NetworkSettings.Networks.ldapNet.IPAddress}}' ldapServ) + +docker exec -i nextcloudDB mysql --user=${nextcloud_MYSQL_USER} --password=${nextcloud_MYSQL_PASSWORD} ${nextcloud_MYSQL_DATABASE} <<< "select uid from oc_users;" > /tmp/nc_users.txt + +OLDIFS=${IFS} +IFS=$'\n' +for line in `cat /tmp/nc_users.txt`; do + result=$(ldapsearch -h $LDAP_IP -D "cn=${ldap_LDAP_ADMIN_USERNAME},${ldap_root}" -w ${ldap_LDAP_ADMIN_PASSWORD} -b $ldap_root -x "(identifiantKaz=${line})" | grep numEntries) + echo "${line} ${result}" | grep -v "numEntries: 1" | grep -v "^uid" +done +IFS=${OLDIFS} diff --git a/bin/look/feminin/kaz-entier.png b/bin/look/feminin/kaz-entier.png new file mode 100644 index 0000000000000000000000000000000000000000..3956823328fe89dc41fba33a26087f3ffc42e7b6 GIT binary patch literal 26978 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 + + + + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/bin/look/feminin/kaz-signature.png b/bin/look/feminin/kaz-signature.png new file mode 100644 index 0000000000000000000000000000000000000000..8d027dbcd1ceabc083c3f1fa02d92cc2e1a0c194 GIT binary patch literal 2505 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 literal 0 HcmV?d00001 diff --git a/bin/look/feminin/kaz-tete.png b/bin/look/feminin/kaz-tete.png new file mode 100644 index 0000000000000000000000000000000000000000..8bb18f213661bd3866298fa9140f94dc17048f90 GIT binary patch literal 2284 zcmVh($8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H12zg0F zK~!jg?V5XRRL2#@e{*-&Yp*dF2oy?4Ai^rNw5n2pjMN76Xq8eai5h|`Y9Ijt5-KDu zQd+6%Lj_d|qR=QrNtHh!RH@X6q@;lcN^G9u=|X@AV<2^sVge)>V-gG)->-knz3au^ zy?b}P236`O9qrwDoH^gjnKN@|E`-A{-~zA?C<$lA5+557T}IxO5U;m4}Y*a2)4fbP|>^<`UM;vqET23aRr#(}m)mJRx*0 zCI3e`nDq^y&9}+3@1MDHONWRtrn#)FOv{!n)5M7r6^TTIoi|xsTWFt0#uchelmDPl zhgW8INSk87LC0}aSy`zSD^_U0fB^#jofESR7y!7FCQXtgojrTjSMCBcgDQINe8VGi zAfVhHugrC!Xb78r4!H4nT*r?emn6-YF+;#LvIJEpgz}+)X>V@_VA!x>ELpOISS)6M z=Yb&sbz><8TnH%lnfDt9vJw6Y@N36$%&b|n7&~?>0Iur-6a)RT)X$14Kou1g^3mMf ztbqdu`t#-u0$f|@V{i1%3B}Un7YQ|aWj@SeO9a?yj8P(yP-A0b(x!3a#`#`1I$xqb z9tYH?Paoa5aUNg;Jm=vP9&5!#=U-XatqBZEfvs9v6?teOn&xDdEMQBGuH?BpbPP>y}DOOZ`R!)M8yO9qI22eIBs!Xc2lS zz=mSrfZtXu7SrX+mos(#j36>$f>x>jP+wo4OpHV#l$DjGma77OL7Mq&Fz*1Bz$WYz z@NoKT9SpXpFdQ4DOMpbm^S7~aq_ZJo zXlZF-(4av{yCnJ9f&1w&giHK~y1Ke#;^xhp@t3L;I9QiTy46sIQqrk{ZET~8Dgd^y z<=F>#;8{QK8rL|_`K$(Ybab$B<3{GspAW!+0|!DA4udtM%$*%Oc4+V3y&5@kWa^eK zqikn;rg_^2@8_gDL#OBS znt2=EB&3^#k@?I|y6{})7EEu7tP3I&W?2e!kz641LIO* z2uD0MkqDTaj;u4o7^~OU>4Y&A=vBgw10(K(d9$H74$~ijCuc%wA{PVGv>1WW6)2T95%295UmBEuq@7zW zuAdiEt3_4?@CRuXMRC}zY}2AqadeN>`^2N!bYwb60K&O+`ZC5q!x{VBzuf*e2FlC96*QdAP|Z+M zQbJKt2KRJbTeqkP{8J6bKv^HSc@sWp%w}(ogY1W->q%ood0+9;AH)kQM8pwOCW=#a z;;A27mrqk~{P^*@bm@{dY}lZpqN4PpG-R+?|C0FSQjsVTPtFu44~oa8+0hOj;e94KN)z!Rxd{9g7=yj$ zX-dia6#LD`y`&kSIIt498KRLL*n6S{z@@uH^b|I>d>wo5)EZE>6_}SwItsj%V}rM_ z?lT7XJ(+f6g%cNO1YQItd-8h70rKlF!h3oBDfxZwBj76b_U~_4_p0=anFtKbL#GcH zXuw{|?Zv*peA&}G4Y;4&Fx z!AAd};8O^h4fb2B-duMJ?Q^8T*oa@~c5ec&Vn3-!3kR6$8F<+1k)F7_3v9%0 zOS9nz*o}Wr2zCJHfX&!1ZHISlB->gR#kyL;`z`Z~_n%9=PKtQ{i?G|$tJrmUA9g)e z2*P;!CSj*oUpkkw>p%;3#QZicV7HI0*n#=nd-k`*Y4Kmwjn`dm#h0uA0000 + + + + + image/svg+xml + + + + + + + + + + + + + + + + + diff --git a/bin/look/feminin/kazdate.png b/bin/look/feminin/kazdate.png new file mode 100644 index 0000000000000000000000000000000000000000..309ac06a1dcfa1624da81c4420d7de1a07c12933 GIT binary patch literal 10795 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 literal 0 HcmV?d00001 diff --git a/bin/look/greve/kaz-entier.svg b/bin/look/greve/kaz-entier.svg new file mode 100644 index 0000000..253dcec --- /dev/null +++ b/bin/look/greve/kaz-entier.svg @@ -0,0 +1,100 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + GRÈVE + + + diff --git a/bin/look/greve/kaz-signature.png b/bin/look/greve/kaz-signature.png new file mode 100644 index 0000000000000000000000000000000000000000..f17cef1df93b6f11f405912683bc2e51eeae49bb GIT binary patch literal 2905 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 DQwwWV literal 0 HcmV?d00001 diff --git a/bin/look/greve/kaz-tete.png b/bin/look/greve/kaz-tete.png new file mode 100644 index 0000000000000000000000000000000000000000..3a459826d1ce14f34f92dd7b9d3a1a8812c2c885 GIT binary patch literal 3021 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 + + + + + image/svg+xml + + + + + + + + + + + + + + GRÈVE + + + + diff --git a/bin/look/greve/kazdate.png b/bin/look/greve/kazdate.png new file mode 100644 index 0000000000000000000000000000000000000000..d2838c9ab9acc96a01a5b5ba657fd4700711b321 GIT binary patch literal 11601 zcmXwfb6B14|9D*1vTZHfT3B_mT_@{=<;5*7+qP}jaxL4|vd{0lKi}*3$Ajy>p69+_ zcDSmt92znaG87aPn*3L3H7F?PG03|d;wQ-c=MaJ;Q+Kj*^E7p}g!1(CWV3O!1DTsTTe3O10y8d!iJ+h;pyZ_`G`?qEWO@1G+&@2U zO`p?It|LNA4`K`d@NUeW$76@Xp{bMqQU09*C*Gt8CQl>vha{9;k_CW4YfX3<~KnP6zj zTy_e!)o>ZqT)5Yd-NA5ddSwzh>!hWxF{GA^;+U`9EZgD75s=kPewTD6P23`Q5|zK& z$uPdZ{lst|adR|>A-_yv5cCXq+u4$!T;pzo=#@1%YJtBUN;RG&|7w4@><-dc^=-2~ zeV+1ApDi8mD3GW6!m=tk7=A0&hiM$3c?O_& zR}6*jy(Nppp;y*H#W+Kn`Ft$?HzSm=_oKkzijYy_{{Xh%ag#+#Y8dYBt*Hf?!`K(u zu=XKi=BhpGiwD?;_O!mOGi*6fbVr+6wN&5IW6~Z>vVf2+#(kY>tfmfVVj}h(Be_Qm z+q!Qyh07co8Xe$Q_GD{M%eefQ5aq}IE&Dtw%};WSC{XKCFP=5o(vYKv|1n_mD;|U= zHK}|~;&W^PbBOfrRk2rFR#Sh$H+sdzG1xL`Y9BpNU4h_4@)Q_^THHwJbGUS@wJ;IC zvhICvxg@&?Rxxo&I7?d`*RZL!3AHobM}_#wAOnt`?>E3~#Q!!|Sy}0{L=nI-*eW8eEgkuv*wF=I!#mp~J@PzW;_&AU zw~?a1+9+|>S=Oj-M>y>0Y36A+tw$dm?CJfP8tsuqvqQUl)Dh!(bJaEd7xK+$6!$+~ zenX;1K5t~Y8kofKR4dbg^C7>B)EkoxjzARFOxn_78d%gc(exr`u3ySHpLl~tWUhUT z+PIdVK{hhFZjlF1qqO}eXeDJk(g2TAfLP{dq(%NqWRjy?_L{OTn+!X#P1#u~O^L3aAc}yCdAH zA4p9G6#}qYCT7u~y5?zbH~}djSTgCLwPo>rGzYVNw~xNqcQ4f7e}^ajlLTxM7f(Dl z%{zoU;GPw{oX=ITkn*gRE>mAbbb0FOlkeatPD1}EU_pqZ5&~(pm$MCbO&<-b4S*Y3 zpF48-&i^~Lo_DJ}(4sUY0<=Q^Nskp|IB$k>+t!O~cHCQ4FH#S5}#o6)Vo0!f;d zmwtx8OVzoRG&Tpp^j*){NgxE<;AhUYihlF$QA6igGz z$sIg&TqafAf#3+R-@y_SYG~9ANYhw0=}dEaA+u;PMWdYfDI{a(!CKr0i<+4$fPqP6 zTa;u+NY8fYe;T(t-+M8DYacp?ycTF2_!(n0id%G>XS@DpPdt^AqlBS>3%G^cj+pXV z0^{_IPJ8d;JY+b#E=31XnVJMAb$ZPcd+TF<+*Rmb3i>>E*S#{Vd$GNahz{DO9d#uK zDe!i~q!HQsVkDP7&dw$EgCUzi?w@8pM|u#n6G!>!w6E0Oz0XX6_|l4XLE;GaVi>~tZVQ@QEkv{P2`Dr5RuNYG&SQcN}Ht6R?-VCbCk(m&mU*If10C# z`f|}_(_GSNhmojUVXQ$K8F}$6{cIGaO!}H`*jmRRE?^S<@Gms2M0t668d}o2q88Yz z2f_h0J-xBMU<7$(Wje|{fMktPP#1}B$Q)9rxPD4V zJ=ZN?uz#*?rzyeBOG{qD76aQ9BG0f$6sh-Bc-RY@V^nijFOU%IRr-By#Ha~4rFa&R z=lQGeUgAJ5Un_|{ID#D!jwOeJk+>y2a2{B9S7S95ac}fzmLS#2Sd7uZhmZ+qHY1h9 zCD9R_*_>h`^onJ3^mQge11aKNmI-vkpyt0R&cZ+&*C~FAzD+vQ6FK_ACG=!GJ<9N~ z&nHk|GQ)U!Ye;&TP6pxg7pfzb95IW;g7Rd?%=E4s@~KzR&s<-g-1=IzG^$m1e1qIH z8MV3fh766%`THVQm(a+)0_SVsd2{QBou0KmJPiJM(7ST$05-jjvRt?P9v6g3ev6^)Z+%J) zi1|1ELwAZt1^czI}2x zFlEy}gt=MmR+!Oj24`WTVh!Co40SD`FBFFTepKyj{lZmh~`xN-1NB; z+4Q}gw7riJy5v_Ddv}`>d0e+W5_`LrG75OV9~jx_@H}D1OUKFR^!9!?U3^*OX`4C^~}4Gl*V4~%U!*=c`5uOvG&JYV^yL~v_? zHaRWH*n42yfLO!F9v2AGO7ju<%qpB*2X~kGRcMCE4T`iZENWtE!ua=SNIz}Dt}fQ; zpS#{zeH_MK%P5m=-;gGV6zobfl9FB=jBe1lYsOilxvHs>n)lT}@MhMC>X@uk88N3% z;qr9s!K0nje|u5=!Vhiz7co%EulGK8=?Gp8tfJ}fV8#CG>Bl4pNHopy67sy@vse$t!w9Sta zONj1XX}`KHKh0f=ZEo#&7Ra%)2H zTdZ?3egfXqnhqS#mtnJa-OcT!9p=kKrgGcNVOb)e67+`CXMcZcH6MwAT8D_uPuv4+ z72&?FN3C`b(Z_X=FZliO=lB8*HFa>a!y1{W=S!PSQRc zIQKP0DdxU#)yN#m(~&RT@Qc+`^}m{QIrlO=7z3wpgycZnjW3j3&;G z+uoCU-dxu#&xtAl0JEmFD?I@LT2*?QliP>gua|ob)BPo1NO){<{9Yd{AqK?lY!U5; zis&2n0o5*d?kS#{4~<}{;=R>n%bQRx9LQ1PA8i}EPtP!m47m2Yic zRp#c<$(V*aJ-#xxN~eAi74>9|8+7yicdtX?n_S}#YWMJj-hGd=jIge^VUz8`&LfxA1R^BQShbPkBievae&#!Tmx0$PwBYM8t6ba1n!}~$(*czD3`g`3`x802~OHk8M z%*glO(A%+`81j;z#_qfoa9>4Q7!YrVT{%b@S^>iNKBJW&y8{#SC-O&>_4sGziPRiH z`SZ-mxZG$4=7kx#bsZZL{o@O-P^r!Ade5C;5kxSMMLsz9-f@21;TmuWfn1;J-}pO!65MU#o7Lpb+uR<0RK z2mJ94VT01L|KVhY_60vP^I2^^39mhY=awIzrKP3tzd>vW!aqxL{L_OGFkp64455pa zv-B>$U#_<`F^(~qM9b|Tk#RR#8Q=6Zj*w4_982hrAf{($#RZt}%&pVsv&uJez%Ch= zf1%FLig1%k)AQ^j$E3nY#lX-tkJ0@n(bIH6x2*tf*yzd3=XJ1Y$Y9Im}HhpA%G?j&ei@P8w1?RKUtl#1oMZ^;k@Nt_HMZgiX z+2!+-r@YMJcYRQXQ+HYtv-Zt~=hnXwv5&YVBOkOM#7>=91Bg9C%RfZM=vriHYU{h) zzZ6{mBWSt7>6Utiio?*nCpO9Xbt(016sIPEtr>l2rZbY488JqPm;%E_QUT3RStm0W z-7ERXH_IL8&?kg-dXI1oYsPm?c`bS!$I!80!j9Jg_h4#WMmr}PBnu;3S`m@S)ZDW* zU0aaYMhTMb%Vm7ruiV^!pfig*xm6FSR;Wh!)>YImsIkc*-J(BAIjf3nmKp8Jngm$P zydLiqUp=+a9Y^oL^2%qBiz1Mqz!cYPd%f&9nJqwo&?euo!-Lz&|L$$1O0Q`*6EwB+ zZ-l(W^J}jAqJYs02a?l;zH-wkZdA&5~4A6_=V_aH6PE`W! z-$7YC{U-m}!S0KD0#3rGIw{?#*d20sB@Z1jD~`!-|0ciQ4FcKjVyc9Rx^dolS3jOF zKHjFMv`j=$0@J#Df=dQpteXV9-I2Z*G6dWts-CzyFM3ligCi>A)wp?pE)_T4>l$Bmj0CWYTO_IyyQ|w_{b9Kqv(C zWnN}{W8Z%OU=$D9Pi4Uqn(n9FNN1UhsCApHcljxWl^oV?%<^0bORn)XKUJ9u5W zChsM-ZT_Bj#3y<*z+HOG#fTHRGmP!MGgf$pbqlhsH%W2Z(c6Z4a+Sh{z9tm96(_XI zBV>@f`-~hrud_x7f)bnL;gFmI#^qmiMxF%uXAxgUpr5yQ zx(EFpH{^bkiKKFvf6DQDAiV0lo!YUZ&++9P2t(W3Pf!uHB~~w1GW`=ztCKtwMaYG& zU&$NtpSFops9r2nr~k@N%ujScaO6&jsZNU$R5-?y`Iy`N@#ZpBE+73FzO>eQ&K%-o zC*cl-%A7(g1AC?JSp&ev^ydh|(WPeT#dUi<@&T@8G-5d8?ZqE`7YFG0e$Y2zbcu$B z9H*j0>J7Q_Ou71nZ)q4B_ z8>~_@sR^CCE+JaUUfafC$iF{9t3Cr8jU^(FnJ(oqWjk=E5Iq1cQ~SGnQDMv%xR z0nQK)X643p#bY73f1NGBPxfjL6f+w{l#L?jJIV=|(l+*!f%t%vxgtfLN0c9R<|D3W zRV^?Nb9V3$C&dei)duS;2mE7Ow)}+#!a^0jY}7$l|N2qbEg;70=kJPUV+kW8vIP2{ z;_W-8QrA=V-PlK>PLH#FEkE#=a(tZ*~YN z@lSXP8u6%!`iWMugP$XBT+ewlp;@}ITcv!bm_w^A3ISV>I+8?ph-kl4 zOf}UyhGkRN^UtZ$e_8C|8>J^o{rWfXH)t-OXL?95{X6hFW=BryPI$W}8wHnko?_r6 zL0tOMHNkyo!sy`&lFQxp#`$=<=N0Zr1K9K$f5}9iHo?1AVx4h~(J&XXA6QJab>>>< z7rciT$MoW(S7ax1nHZ^YyXsj1C(_Z0M6X@mpH}Nz%WL^tBt9{mP6>k@xa?Ncm6Lh?TVk4vxd?g!HsYZXf%|Kk~YvZ zWKEQc`huiWI$!ZKr@s;_d06?QkHNB~KZ#lpYEQGL>xebnFcIi0POA>^zW}{(=4-P( z^jcsdLrOxnh0UCyY(+g3xy-j7H*ik^f0OCV8A_{YBrPIN)~f$*f;&x+*tAKn|Anzm zgaSCDDsg9BSX~z!x#-%<^xr)j=97*SlH%&4z7AHe@T zD#|v0KETiSrlsv*R~@L$i9TSYzzJ_u@FZ5Ewo<(_aCzDZCy=^9R?m8sPXT?E88pT5 zZ+ZgkF^pjgO1ajvE{tX#($H&9ZSr;-KZkeSOc?oI==*GWa&=toP*=PzRQXPs^aVe> zYUgYyt~sKe2p!6HyE_Nr)-M7rr#gjI@7AtQbf}cH4kyS=qEcW{)f?UI{c__;!i`)x ztm8sU)003xJBYe9VF&MA`Yv#AQe zIVId9l}7@}*aQ-7Ez-TAgr$WMhRk(-pF||2B=uksp9=OGGVA?)+>2Gs2SboME?=#V^SlOfyr|!Bvap-zF~Z_5t!#gx>cgfKG~;@gl+aoYK-?2xk2+BNRYYgTO;tW3U&t&sT|hsp&S&BpIB5TM}4MA)JmV7OE> zn8Tbb@<+Iic&c&;b^s-9cwp1_nP49gVD6hL0Tdb^f`Z1rx=mN72fwcjHq&_vugW>O zW!giejsKAGaxrPSnR5XRw^9Cdlzv1o9Ntx;a1ilr{arR~c!?bQoQd$vpgziCkiK@k z9K)k(z1LXH(6RAgTvX-#N%k83FE<)jte|4-QQ&3VB^W_rtvzy(bG6-t%-~fa*P&%G zkp%O_*!{THRmlbe>}N+_emy^CrIf0d^D`{eVK5GA-a%S*_AyV*bhYq(q0(r}$DBRc zlKD@kZvbqxS3U>4-*kdj$X;EP_OR8I$MN-9RNcw57*DMB568H3zQs{8!HOEwKb)tW zvgKK^{gZ+D>q*&&g{Q)HKSATW)I-;`Ro?d{zY6Vrr72R1TaxAy!$+HCspsK!vWP+b1WbR6j}(HmJ_J=z_XILK5OXD!O=k;7gmF+57hG0( zG}(a&M3g^mQ?b5^DE0wrulTENPTQPmlldayg&HKj6%oL&!EZ*jN&_SsYri%$aNV-T z+G7x%(@+{s5hVx6Z2wvR^t!$9vvT6?%q0b*n$EJGbPh4^AeVZz9W`dzuDcm-M;ldGkBCb35h6XpjD)$pjeuQ<_O(kPK`23oS+v{ZZR zd~$L~|880*UaK>;xreww;DJfzJzY)POs{pW}5luu5a(ZJS*Vv=GZ>}Rsk95Y0K1ju1siY;t^0y z>o9glTgkd~Ssg$eyQO&fcy|UbRW{)dnMfV=M7)PmmD2S{?pomIaB-FMi9NiTwzpNp zZ56Co)-frn*(gyHmVASM|`|*yj=<9f>bw6kuJsfe6)Bd>?+8e{kJb(1R z-cD(^DXCoH;!S_7h?@t#WM7K2roquI9b4gbJ(WK7}t zw(Ir~amM^p0P%xSO3hnlV3)DGV3LBn zpco>pt~{ht@Gs(Rv*1weP3yQQbK(uuX*~fm!dStQda5B0lRLNKVwkdDIISdcFXNmI z1nm6l&tE^6Rryw&oY8d>!-f2*Ev6W>71X#Fo=DxUH|4*kU<1?o=NzVVZyS9CwO@8b zQ|i=$?o>b-$I`rpI;6Z)(7Et4&i3BTnMAL|M_;i@uJznT#P|B2MKcniDZ|Cn0!t41 zL4qoy`nIg@D?a3&_gNf}%9@8QfnA@CnI075s#TUC=jUf3AzF_Y_HF*6RN4>Y)+9=T zU3_ViA3{!>KWL;$LeH^hm;5uj)?F4qfh4^7wk=BTNgG-wTnub+T^ghf6*I#2oFgMp z@6_;0_yLKZuF;zN?JLD>%WlfQg?*i*3SNF}W0!ni$Pk|`4A19WOBhak2%Gli03Nfn z$VdGK={R|sJUfw!CDy9}n-SbIi+UNc5vCmvQ2r40K2u1!YuaQq^Woyl(VaXT&!UX~ z2oQ7>cS|iF?-5(bFj%Oef3;pA`pFE8_*azC9c^+f$OnPWXGxSqG()EA(MPl+_#brh zf_E^M>GE9BNjCFU*jz06j8olYUWxfg1osbjk_@2*++ZjaA8BjM;D7Sa$jQoM@HZkh zN;Wl2>{gaj0$Vc7%6@wW?=dt0?09rvC@9pq|7HO$srr2`3qGYyD#qUv_C^O8G5hFS zuo&>SPcXNg1|zb!m-=l+^~SoDG~FVHtwR}XcYLG&OytY=ZOG z_J5;u{^ms-Z9YsUf^N2zg0Z&C`^#?{mjg&z^3@g%S} zN@c|F#IK_1=373P%MwK#Sj#yptv5GV(VAY|Fp8j(Qs)}ENm3wTJ)6(*c@B4L@%Fsr zH;w(UFs(5N3i6VDCBL?#0_IEMkydTNYPmU)Rv)u(KMY!&n+TIs0al9kWZ$(pv|%LR}F6W;pgfVaZ~pHrGs zdT~ow_S&>=q=Wu$h(}*;b(R48#;ka|s1kc?x2KWm>0a^L3o}))A@{B0XbJ3L@_VP@TSQ5gQ{q7eJpaIl%Q`)MX@8{nI6dC2~dUg{XYoLbuFZD zJE2)VrJFLZm#V2I3b6{AH;0yMKh%rRk0{?Y?;joL5775nj`L5vt1I#hDvzndB#=KxkEf)mrRx`u{wo4heD;+vM zXc#YCowLEWN`QACh^*u+l>AOL*Ww#qZce8jQ{KrMePuIGp#<`q zVIhd84(p7%JqiwVuF!F{gET|2L(zgh^f|gEE$}Hl!t<(XX#l7d3H{+rnJi-h6hLzu zl+aHZ=vqB{Bgy60l#MdC6Mn*FrM)Xlrsh6?OfHOAv#}MEs@^7;vU5C% zPNNt$QZQ7JZW8J_OK!3Wi^pj6Po|$s);pfcatLYU5}JSL8NiUn*8VzlpNl67L;79@ z*9r;%h##R^odsh~J;tQNG2s;OPo={Ah%LQFNt8rA^8)y(hb;V>Ku|N6mxq~|ZGpA~ zHV&1Br!|;$1@W;t%t&op#UjA7;x#RXn|8s!aE9G?_U8op$PgkKY#$17{;>QmHc+7& z@u^uzKD1*K|Q$-fcdS=;!Kgc8uTj^!UU=$kit4VLl=Iu>VCc2n1Q` zhWW9}FH2-pw1Wzhj3cFEgHln7v}2`JR@)ykr&Rpk?-rlT31v9-$39c;$x;{qn zXnA26_;~E4NdB3nbyNB>K2Qi!StBNV)i0~SGa(j`MHqMy;YdZ9<6c5w{j7%dLFS-) zsu&6r{z>$_7arLcz0#8+(o7CXVNfd`&ja(m{E{S&?l94ECw~1EK<71YfHGdI1q4o1 z@)%^=q~qZglrjetdM5rFV#iz|%ln8CD2_4os417V{Zy|%OId2Jo&U+4c()Wk9PV_S zISX4*mUdel{xB8S_y>^9Jlo6)_v-IvVnpQ?Y1BZPEqu zxn-t~s#;xBu#qw(mJ=|VWP#T!Bd{ATJ_0u`enfz_)YlG*>mKtl$|xqur*>7sR1)rU%&7~ecfUJ;&aUBHuO=I*Mjw*4$ z6p1T?t>CUe2LdbCo6YTm)ZHg{T1XI64OXR_$6VP7zZFpPwo=du45ZOH^OF4%dbul*87|P>%ylUqm$7Gi$e{Am&0yy0#{$71wDu!i* z<7r4f_;qZJi4dM@SA^;|oW_V0Jn3SW8RlY-!^s&TW%Y2g^-C`$f;5>MFZ+JsOM_PT z>`IPnOzApwJ)?rzIV`Ihjp2J=qRq;912dZj!x&^VEWyll&;|Qp;@sP06ks$PJ`q5J zyV!^oiN2Q%86yBgt%u5usf8THSZ!nLaGn&wG1u{_LRC-R^{796`&os+p88*7p?Wn! zof%RC2M=APaE$>|9FsA{BxxP=7VG;URa-zE6xzsq9Wx%0mafzJFFnU>pNL}=Tj!6) zN?dg=&Isyw=wT%F2JH`X+r=Ab+vBp{@8>??5CTBve_i*C^e%eb*yS~pI5lGc)W_=z%QR1ntyFZ z)%Vwl+Ji+S`6NY2NiX%fi1^;3n*$7V5?@udh9^{poRcfA5K%9XnVJsN-=zLMW%af~ zN;|iKYk)31p-~w>DoRo)BkYADf98`vVmQAs&a}3<*TRo@hY!oHVK>7gR`t<4qNJI_&eJ4!>wPWTwTk3PxM^xp3b4&dR3)SyA4e?%io3hJ%i(Z1-~GS5m)(84 zo6O`llgVT<;op=2m}sPEFfcHf@^aE@Ffg#$&}RS&67(t{Ol<(Yp*qUxy1>8?;r>@) zM;LA-pf5>WWprHC9n4+bO`OeO+}+(dtn6)EOidikI2@cUGS5XxVPHPO$V*FVc!0oJ zo*wv`OhG{`lEHn5L2PpPeX1H^ zZ21>?a6N330x`Q>;s7UOfOJ=|%+QSC;j8Mgt!u*h$;mm)-SfYv8BMo~za(BDZuVC(b;s*#z6PyO=;F`XsHwU*u!4Rw;^xXYmzxWe~NPZ z{UXzuC!pp-RZwVX=-}ID-dVYB)p5knxKr4xTRwvueESSt64w4mImbF%&Rf$fH`HB` z@n{pH*?y?j274OZ6%3c(j_boWMXEN*g?dX$$Wo>`^@LK6-cvX-aIP-;dwWga8CLEh zM*}+HI*a{qjRO4P8D*J8j0#~&_yi@09x&1n$GRvn*Q4NjO~XN~mjBfIdF8WV4ERL< zF3SkyJ_SVxQ-I+<_85ztB_OZOjQ-alb@>GHQW*Hr4Azj$-~!z>ObsZCvt>c5g847} zRTLBO*!=tgOr{{$FWS5h zKDxgr(OmQWlHxE#d4XAeu~U?uc2^`l4DJuNs)1wldIhvkjHBi4?Ug-i z$1&#riHcTPIRA#s=glzD8%;w%uoG)_I=9MgEkaBfUA&WOH7}Q0HZjMW!gD#eK-Dwq zC};Z*@K=&#_Vm@|Wf&o2)^r}GxPalH>Q)@XVKUZgIDPEw67f^OWM-08>CHw9XZYIo z>>%qHwL&i^OU>EW3i3}4JNN4xWd~3VpxW*G$m>djGN1SQ*%x5<-TlOvSDhc14pxBo z?>$Vs?!3Ey(o4hVq ztQBXn?ik^9*PQ8b>XB?xZ?_*(=a)Sq8hhJE^uzuaUn|Bg|BqUxAh)PeN!AyVZ0s)v zt21AhJw7f?K3RN`xN)bhR*Ec><)#s+xa3F*%_l5;NwrnE`G2d^|5`-@E^y5F`PDBN zk->la!@C@M*jRai`Ae)rB;sOJc|(Y*isfxUMNT83rV&Kl|0YAUWAEh}RsI;s_#7C; zw!{g+@(W0$s4Qwr?(plDG46;0P%@__Uc{%BnI(97M0G#-gk!y$g_8+`6=jp&cw@itx{RpGQJt!`aV;G?O&(#QT@VmYJ7q>ES9q88^>2D? z;@2vV)m76!RFUHnLe>n9NhqN6Hf?8sR0S;>(pETieMIlUS=N2HHJM9f@Zl~7z1s#8 zyP*5Ect5iV4@-qV4i$*-yQ3$O-NIwrH*AfpgHCtOSAC!FKzLRW0^Z)nRbP{HCOmuT z=2vJlh$C_E>7wld|2cfe%OA_@^4}>|CEhb95D#(&G&&>rdL_EeVbY)0!&RlmP%Iid z6a%Zi9@|kPKg)=a5oY0Egz|KtZsm_uY|c{5vky&GXEy^a#F+Gd48 z`T2|=XQYy=X6Sn8SFA;adiwaz5bIxdPC_4`Cy8V#t*!g z{)x^;FDv8x>*nWcs$l--<`=y-y}s)9ujib3 z0_Zv0EJJz(OX^SQx&yNZwKwosI9l^u-ddciU&}vr6d~E3JYCLHRiXt-gEwXa{{-(Zqd4PZZi>`00>j?Yk=7(_b z03>kcmL#A++7UO_!@l*HfrR^q^qeydJr2q@1B0l|i^CX^e7|9bn606z_-Wk+b4$Lo(yOBqu8_7o4A(jAg_|?sQ2(|AJ+uZ#K6R|Yh!uv9MU`RSb~;7 zkYxX-banp+=LU3%&#v(VmttC4PKylg8D%oAqAa@ABd+mwuIDiw@ojF>RBBbenK3Aa zh-W4qV>7o=nEG%sD4i(kaWJo{VB>v=3NzDqTD87knpwtaPsTOhq0uzF|4av50lpHZ zzHfZ*uhqH1rNEh})aU@dw~e|{3Cy;CifKhse)QH+E7WgalkQd9>q}q`KZ0me_EgwM zAAGFSQ|O)(4utp)>gay_o^Eq@n2_l7RM{OFQP}W0Wq-TLd5=p-_;Rt)Ap~xmwcPyY zYnblP`I-_K z^P)z?VI-8wZ)hS*m+qMp zr36HU>m^-!O#Vx5?sG0|xaycw9!7HLQ}AQGl`)Gj(ZO@BM<*gqadU9$?_)=uG0PbQ ziIp~ND`u5An&IOXxBeDq6KlidIW)%-0BAR+5wzrb9C5v<$+}MOBHp5nGQt<96mNbK zvRI}nh$G-PKaMj-m8EvHw>=2GmD8jwyMAUQNWx-2%9^XNy*;|--{sv~k%KA+m)#04 zrRN@Qlbwz2_t}}5q2b}}ywEe>=vaw2LG|LZtk6`H z*p(zaoG0o_o9TH&gyh#kIieA;{1CoHz`D$vFdH?I~l8&L-Dlf zqMo0i+!nYTP8j$-?youaVj4JhBZi^)zdvtEEr`55>QK3y2^S-w| z$Hn8HH_s@JPksIV_w$@AAZg}x=!my4v&-f0aRgtMTWNNwmHG}AsAMwmXP!HQ*p=XS@(@>1SW)E+!{M z9Z4(Mrrb$*TyN|F7+7=sBr|C+2p_Zh>mT3SX+RTKZ?18tAB={!Hjhb9knA92)4vn2 zzO(aVAeszdZq5J$pz`Gv*zhnN0?Nja59?ZFX}Tw+rY^3dD>pyB`Vq40A$*pRLDbaL zj6N6GZ@-zap}T<&&E}kY_pJ!^q4IBORFqZ>O>7cdwqu)xD`~D?2Go1qT0r*-`V#L# z@M&iz<;YNdbY>E;7w^%iJsNDjTnjsm#>_;?k8^8v9~G9Pqd~=|IJ9)0I$>T;nZ--7E11n!pwYE4embX z3wV2db~&pXyq*S7)j@Ud`uh5$c=_q}c(#(!C`a7)gq#FM7;?E`hTnmn%9B)k#6Yd=*-N_ zdESuom&;!4pFe-rnhwUqnPpaA@g}j;xJ|%2{Ci}uS*YBu@qcB7Sv2xyr^)jrgnoWG zcUNlgT*US7#beK(RU}kdLXV2^V`izz9n(k-@6w4QlWh1Vk39Zm&pb}POSI1PLWaUj zGU@cXnpt5F)CPFy>$uU{`{1J7O6#DdDedzF$cWV^ss13rBnls;MqC(jul_p{Q8r&R zJmjx2E3YA&@yu-b)#l~JNEjn6?eG3bBKWwRoXH3RR_ID429JcXm8kN?w%wsC02W^G zW{l@}y-h&d$dlgK?~xu65mEHTanEy1Hj21AiCMjtEi#@K@I#l7X6B4nKj;iBxu%zERr=d>R$c7hM?O3Whe05S$6GBf51!Y!6BJ1vSk;h_!{Jiy) z!|Trar9M726>vCl_be#lcG5RfqngyL--$?MSkJU4sUlC8392KgPg zLZarTVXtMvDUBq8?n>Cq=6c4*&A$^KC0S?oy)+cSLpkVWzBr`px|-nA6RZc#(bPkZ6y>O zR7GcbrKzeP?tD~;qt)XiNr<*SqBoyN;G`-0dBEnIm;2LFC9Zx!Q9yFfQQsmvvZY$e zAktt8>VX`LX96T8Vf~+GbCz11XeoqUw`zN_b}Jhuc5;7Xm9vWc=tJ?lt1yP;C5V7)C-AbS07Piy)iwtp^ZYGqDG>RYsLXhb$Z9bl(S4s{TK6gpJkuFG2 z>*kd=C=#uUO)GWu$Vj^|##(J0A#e|}J&v)BKNRHtRc%JCxdo>uB_=mi_ot@UnQCjL zn~^j}p1cd&o|Hn7DT<&tHc?2-6!@;5@4JWSjDh^qmD%qfR}9 zM<01J`0KR6f4kQDI&FWyil_ls2Tj_>g9VfJ_@J^fqE>e5Ag#!sl}Tw6hWsM8s?e^G z94(08^BgGCQ_!!oVLJJ!0xHyZphhzxfoVo5!QX9FKeu<#nA8`g#DPFLOqIpu28)}$ zM3n*Q^d<%Ht^Py21709`-rJluz>LsH&xkv3T&5rzdx7X9E*Yhy;d&7z$8N~P*-3M` z5pAm@&m>U74{{_8p8i1R`mx`9%G#!bF7kunNs6k^#WRmDEzM6B_a zsiqKm7xHy9oKV(fh61a~c?sb)2I3KUegpsAKJrdl{mZmG)T;znCLK}IPuEjvE zqCU+ez>AP9ry(MDIfo^!Q?BF`q+#$D*&yyUB!n;fZCHFEY7*o$JQlH9;MB0D7$4TL z3xgomXQ!lRI!9pc?XR$p7B^df)6hQXo=iI~gQ^$^sTmehlp1WdMsj(%&OuX{?L;JZ z*wFm_7{7qaE$)~RBt@6ts2c;>03WSaTWAze1VF*=nwz=pWHunRGVB+Q<+t`&F;OvU zvY!@d3cX&Q|A|?(7nXR|-*>^^+e~FYNQDz4{s#}d(oMv{2!}!1T5_m$wTfPGu@{mU~W68QkaS z`%NAec&@E3l=HFR&Z>m({%N-DuZeWwWpm)^#^`;1BiW(a5S))L{k? zDdo2M&4Sr`Ok+mTb+8~gez{J3pHK?nXuRNX=svXiGHkic*4*6BHG=fJ|CAZ3vQ{@v>Ti_F;rp~HY znHV?5JC>%!^M3!Qo@-tZzes}xre7#0_sXAKy7eqqjC@8B(D0e*1m)b{1sRdzmU9D` zC3OQUt9y5wF)B4V#fpn9))X>PwbQG|x6D;tQTE zv)0&^0u$`*9J4|Byqy+|_|^Ly5kzR6we@Bbu-?)&ss=IC@< z1ku!J@{4;S9{Hn+;%UrQ!LF1gtU_BIV%lqp&^LWx2p|2|B@Sm=?^~uc9Zsr6*xY3h zq`1|E$W^SJhtJPsCQBkws|~T(!u{c9B&+nxb^%zokqtB<8en(1V)8uGEGpTs@7)Ie z43i?(@H>0GOe96T1Aa7++y7{2^PL%2xq-gtvW0I?Y}7)2Em}nxI4y{4_&IYEoGh&N>UxG_IJhG<&mC1!z&EvN5IglS-dV3M2H$uk6MOdql? zCJf`ezB9H`ZlqJEF4HEDBI|+PGL`A>j>g0w!6NI?Wth!x0Iarv)B#JwAx^(=n8-2&s^gD@#)Qwtd- z`g#XXl`{&CQQ`c8P{BZ{5rNIF>FDC)S;uT*bzGvRs{pQjt!>bC)iw`OlDPL^Do6P~ zA*$1^a$kE^22Dd5M1GnvHgvYIXe^s6AHaFyS zu$~F<#Ud0eh@a4$ipP5q_4i)tEDaRX-pLLkC#mPKI}>t6KXLJ2O|hCdT>MAuzc8!& zT2^b)k%7~15XbPq3Fy`eULjR!jfct;AG(ahYthcfsNwVGe(=CbCP-1e0k&A-Lql;y zaN2-n;N0F%l}?g0lHjl$wy0l4lk59aZcDVdOxL@9VtugzP!*_@a?x25mjB&^aTYTa znssZV5AL}C137?S#P#lto4S|_L^Laj(|Zfd)D+9knSFjcVi z`X@Gk#C9Sd?Ps-yD2zP#Mhrmy#=vqjLoGe-?Hv1)eOzR=T~-VW!$Kz3xh>38z6j#c zPdoWinX4+Q&$H#Pm0gQVvdG_Ao80v;DZ4amdmARz+nsGSp+remtQJ26jT+*3X3&w@ z$Qs^V23#MWJ2o*EpiZ|au~t?cuYjRD%IS%ND@|;^AN1z|zkTpk;oM@<3qJ&qr9e%e{^}S6QIhK-_pG9)Fp-7Ts z@E0LOD?&S_>cmRZDEtIXV2{~Z0ZNU5(?xI6(hQXxTzewYz;Z3&x?&HeED9?ZUtD-h@q(B)$1?yF z&3;6yb%vHI$$0Uzj-62IB{OgFesQG57m~rGRoE@azqgzVhszOiAc_jWDD+d{x8E}( zy33NoG;|FxmFyewgiOFP-BSNews1Dr?z`B~fnQf6S~fM!cgM;WryErSSY!e%|`Cd&ksAmYMLI3D|P_iFE^nQ>rl%l|1^(u>@bDWrw?(c$ zoJ&=GZlD4J+F^;cyuqA}@iHFB2(?6Kh=SzAn%-cs6MvOJF}RG7S5OSieyft_<59** z^?H5X&2n3JxcvODMTfSNUqhWyCxRY(^37(16hu}<+{-e971wFK{1n^_E>E0uTw1U( zCG;{aB|oQwbfG3}S{6qtl46m7K?%y)w308W8n1(6Nm-GUiguN7 zR?+l_k)Maymhig1`k)Jr=8?lzSBVLmUi|rs%vE;LvZ^4Sxs{X-Hwa@qXY|hKh!)TJ zjjT)d46|yYINcz>-N!ZB^OxuR0{kpK?<`_GBLdszsp%)G_Lj<>>RRXEitFkvj}@D( zUjr7A&NC&^_Ezdpg+yj(rU=jx+S%THXv6FBqo5>IM1Aic97T^Se{B$oej0TQm{6f*o(-;`KsM!J9g zx24C}^&$9c*-QA2!(C{huNQ$$nr$U`!In|qaeo9S3PX<69s@QfTYDg>vGc=RMf>i( z`-+5uWGIU3Jw8BGq}+Xkh8mk@QqA*vfOs&!2~pP6tcM!mKogys*kPZ`{MxA4q0>?$ zDjDxyx@F@3PN;=`@s;|WL5{J?uH5d!-O_@wz5&g0$w_6%RCBlY3$K@I{z4XtCY9I~ zsSrU_!D;_ko;=Ko91ftn1p&5gs{HoYA(ish@o#V`Ca#~WCPJJ`Ny%Bt8Cje^0Xrja z1h?ww1<{5F*OeP+?@MU{DD=;WPZmQa`ZhxGPL0EV0Nk*YQ5j6$xvy(W;MmW61g1%| zLg-xk@cIlap4uGB+mU|8E7HUYVAz0LY^VfF{{)6OKq5akc7JHg^@=RlVtN@Ric`Vt z)vM`2!zGEN8#(gu2nt0x=_UO|VMnhg%wj+@BE3bTVNCcCiRKWdSAmdH%7wnKEW=T- zaMWmqafWEsygBz@<{F1bmu>QFl zOD!E#rQ_|GnWta30}y=wgwNe-<;JL+UN+gm_TyvOdTX(H&I z+N(KcHEhG0g3?s?h}h)xfZ@^ei-WKU-5qe!arze4Hfs?-QyirtBct^_(d`y9VmElA znEaF{VPOx8(|V!8X2AvG!xuYMkZGK&9R-{-G7eDg?Vx)c%`%9