first commit

This commit is contained in:
2024-06-03 18:43:35 +02:00
parent 2da01a3f6e
commit f501d519af
883 changed files with 71550 additions and 2 deletions

22
dockers/apikaz/Readme.txt Normal file
View File

@ -0,0 +1,22 @@
yo, ceci est l'api de kaz !
https://apikaz.kazkouil.fr/
Je pars de ça: python api + docker-compose: https://dev.to/alissonzampietro/the-amazing-journey-of-docker-compose-17lj
Pour la doc:
module Flask + flask-restful + flasgger
https://stackoverflow.com/questions/75840827/how-to-properly-generate-a-documentation-with-swagger-for-flask
https://medium.com/@DanKaranja/building-api-documentation-in-flask-with-swagger-a-step-by-step-guide-59a453509e2f
https://apidog.com/blog/what-is-flasgger/
autre piste: abandonnée pour l'instant. trop jeune ?
Documentation (OpenApi remplace swagger)
https://pypi.org/project/flask-openapi3/
TODO:
* sécurisation de l'API : un token ? otp ?
* sécurité: comment différencier les rôles admin (tout faire) et kaznaute (changer uniquement ses coordonnées, relancer son site propre web, ...)
* pourquoi pas une seule classe pour plusieurs api ? (par exemple LDAP, pas mal de factorisation possible)
* pourquoi pas de donnée dans le body ? (au lieu des variables dans l'url). par ex, pour le message du mail.

View File

@ -0,0 +1,56 @@
version: '3.8'
services:
api-service:
build: ./source/
container_name: ${apikazServName}
# restart: ${restartPolicy}
volumes:
- ./source/:/usr/src/app/
#pour être dans la même time zone que le host
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
ports:
- 5000:5000
env_file:
- ../../secret/env-${apikazServName}
environment:
PORT: 5000
FLASK_DEBUG: 1
FLASK_ENV: development # Activation du rechargement automatique
#important sinon mmctl va aller taper sur PROD1
ENV MMCTL_SERVER: "https://${apikazHost}.${domain}"
# volumes:
# - apiKaz:/
networks:
- apikazNet
- pahekoNet
- ldapNet
- cloudNet
- postfixNet
external_links:
- ${smtpServName}:${smtpHost}.${domain}
labels:
- "traefik.enable=true"
- "traefik.http.routers.${apikazServName}.rule=Host(`${apikazHost}.${domain}`)"
- "traefik.http.routers.${apikazServName}.middlewares=test-adminipwhitelist@file"
- "traefik.docker.network=apikazNet"
#volumes:
# apiKaz:
networks:
apikazNet:
external: true
name: apikazNet
pahekoNet:
external: true
name: pahekoNet
ldapNet:
external: true
name: ldapNet
cloudNet:
external: true
name: cloudNet
postfixNet:
external: true
name: postfixNet

View File

@ -0,0 +1,31 @@
FROM python:3.11
#cette image permet d'avoir l'env python ainsi que le mode "reload on change", pratique quand on modifie les sources *.py
# maj des packages dispo
RUN apt-get update
#ldap pour le pip install python-ldap car apt-get python-ldap marche po
RUN apt-get install -y libsasl2-dev python3-dev libldap2-dev libssl-dev ldap-utils
#pour l'api soap sympa', perl est déjà installé mais il faut les modules suivant
RUN cpan App::cpanminus
RUN cpanm SOAP::Lite XML::LibXML MIME::EncWords Text::LineFold Class::Singleton Locale::Messages
#installer le truc de génération de mot de mdp
RUN apt-get -y install apg
#installer mmctl pour mattermost
RUN mkdir -p /mm/ && cd /mm/ && \
curl -vfsSL -O https://releases.mattermost.com/mmctl/v9.7.1/linux_amd64.tar && \
tar -xf linux_amd64.tar
#l'api Kaz
RUN mkdir /usr/src/app/
COPY . /usr/src/app/
WORKDIR /usr/src/app/
EXPOSE 5000
#les modules python à installer lors du build
RUN pip install -r requirements.txt
CMD ["python", "app.py"]

View File

@ -0,0 +1,75 @@
# -*- indent-tabs-mode: nil; -*-
# vim:ft=perl:et:sw=4
# $Id$
# Sympa - SYsteme de Multi-Postage Automatique
#
# Copyright (c) 1997, 1998, 1999 Institut Pasteur & Christophe Wolfhugel
# Copyright (c) 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005,
# 2006, 2007, 2008, 2009, 2010, 2011 Comite Reseau des Universites
# Copyright (c) 2011, 2012, 2013, 2014, 2015, 2016, 2017 GIP RENATER
# Copyright 2018 The Sympa Community. See the AUTHORS.md file at the
# top-level directory of this distribution and at
# <https://github.com/sympa-community/sympa.git>.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
package Sympa::Constants;
use strict;
use constant VERSION => '--VERSION--';
use constant USER => '--USER--';
use constant GROUP => '--GROUP--';
use constant CONFIG => '--CONFIG--';
use constant WWSCONFIG => '--WWSCONFIG--';
use constant SENDMAIL_ALIASES => '--SENDMAIL_ALIASES--';
use constant PIDDIR => '--piddir--';
use constant EXPLDIR => '--expldir--';
use constant SPOOLDIR => '--spooldir--';
use constant SYSCONFDIR => '--sysconfdir--';
use constant LOCALEDIR => '--localedir--';
use constant LIBEXECDIR => '--libexecdir--';
use constant SBINDIR => '--sbindir--';
use constant SCRIPTDIR => '--scriptdir--';
use constant MODULEDIR => '--modulesdir--';
use constant DEFAULTDIR => '--defaultdir--';
use constant ARCDIR => '--arcdir--';
use constant BOUNCEDIR => '--bouncedir--';
use constant EXECCGIDIR => '--execcgidir--';
use constant STATICDIR => '--staticdir--';
use constant CSSDIR => '--cssdir--';
use constant PICTURESDIR => '--picturesdir--';
use constant EMAIL_LEN => 100;
use constant FAMILY_LEN => 50;
use constant LIST_LEN => 50;
use constant ROBOT_LEN => 80;
1;
__END__
=encoding utf-8
=head1 NAME
Sympa::Constants - Definition of constants
=head1 DESCRIPTION
This module keeps definition of constants used by Sympa software.
=cut

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,128 @@
# -*- indent-tabs-mode: nil; -*-
# vim:ft=perl:et:sw=4
# Sympa - SYsteme de Multi-Postage Automatique
#
# Copyright (c) 1997, 1998, 1999 Institut Pasteur & Christophe Wolfhugel
# Copyright (c) 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005,
# 2006, 2007, 2008, 2009, 2010, 2011 Comite Reseau des Universites
# Copyright (c) 2011, 2012, 2013, 2014, 2015, 2016, 2017 GIP RENATER
# Copyright 2017, 2021, 2022, 2023 The Sympa Community. See the
# AUTHORS.md file at the top-level directory of this distribution and at
# <https://github.com/sympa-community/sympa.git>.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
package Sympa::Regexps;
use strict;
use warnings;
# domain name.
use constant domain => qr'[-\w]+(?:[.][-\w]+)+';
# These are relaxed variants of the syntax for mailbox described in RFC 5322.
# See also RFC 5322, 3.2.3 & 3.4.1 for details on format.
use constant email =>
qr{(?:[A-Za-z0-9!\#\$%\&'*+\-/=?^_`{|}~.]+|"(?:\\.|[^\\"])*")\@[-\w]+(?:[.][-\w]+)+};
# This is older definition used by 6.2.65b and earlier.
#use constant addrspec => qr{(?:[-&+'./\w=]+|".*")\@[-\w]+(?:[.][-\w]+)+};
# This is the same as above except that it gave some groups, then regexp
# using it should also be changed. By this reason it has been deprecated.
#use constant email => qr'([\w\-\_\.\/\+\=\'\&]+|\".*\")\@[\w\-]+(\.[\w\-]+)+';
use constant family_name => qr'[a-z0-9][a-z0-9\-\.\+_]*';
## Allow \s for template names
use constant template_name => qr'[a-zA-Z0-9][a-zA-Z0-9\-\.\+_\s]*';
#FIXME: Not matching with IPv6 address.
use constant host => qr'[\w\.\-]+';
use constant hostport => qr{(?:
[-.\w]+ (?::\d+)?
| [:0-9a-f]*:[:0-9a-f]*:[:0-9a-f]*
| \[ [:0-9a-f]*:[:0-9a-f]*:[:0-9a-f]* \] (?::\d+)?
)}ix;
use constant html_date =>
qr'[0-9]{4}[0-9]*-(?:0[1-9]|1[0-2])-(?:0[1-9]|[12][0-9]|3[01])';
use constant ipv6 => qr'[:0-9a-f]*:[:0-9a-f]*:[:0-9a-f]*'i;
#FIXME: Cannot contain IPv6 address.
use constant multiple_host_with_port =>
'[\w\.\-]+(:\d+)?(,[\w\.\-]+(:\d+)?)*';
#FIXME: Cannot contain IPv6 address.
use constant multiple_host_or_url =>
qr'([-\w]+://.+|[-.\w]+(:\d+)?)(,([-\w]+://.+|[-.\w]+(:\d+)?))*';
use constant listname => qr'[a-z0-9][a-z0-9\-\.\+_]*';
use constant ldap_attrdesc => qr'\w[-\w]*(?:;[-\w]+)*'; # RFC2251, 4.1.5
# "value" defined in RFC 2045, 5.1.
use constant rfc2045_parameter_value =>
qr'[^\s\x00-\x1F\x7F-\xFF()<>\@,;:\\/\[\]?=\"]+';
use constant sql_query => qr'(SELECT|select).*';
# "scenario" was deprecated. Use "scenario_name".
# "scenario_config" is used for compatibility to earlier list config files.
use constant scenario_config => qr'[-.,\w]+';
use constant scenario_name => qr'[-.\w]+';
use constant task => qr'\w+';
use constant datasource => qr'[\w-]+';
use constant uid => qr'[\w\-\.\+]+';
use constant time => qr'[012]?[0-9](?:\:[0-5][0-9])?';
use constant time_range => __PACKAGE__->time . '-' . __PACKAGE__->time;
use constant time_ranges => time_range() . '(?:\s+' . time_range() . ')*';
use constant re => qr{
(?:
Antw # Dutch
| ATB # Welsh
| ATB \. # Latvian
| AW # German
| Odp # Polish
| R # Italian
| Re (?: \s* \( \d+ \) | \s* \[ \d+ \] | \*{1,2} \d+ | \^ \d+ )?
| REF # French
| RES # Portuguese
| Rif # Italian
| SV # Scandinavian
| V\x{00E1} # Magyar, "VA"
| VS # Finnish
| YNT # Turkish
| \x{05D4}\x{05E9}\x{05D1} # Hebrew, "hashev"
| \x{0391}\x{03A0} # Greek, "AP"
| \x{03A3}\x{03A7}\x{0395}\x{03A4} # Greek, "SChET"
| \x{041D}\x{0410} # some Slavic in Cyrillic, "na"
| \x{56DE}\x{590D} # Simp. Chinese, "huifu"
| \x{56DE}\x{8986} # Trad. Chinese, "huifu"
)
\s* [:\x{FF1A}]
}ix;
1;
__END__
=encoding utf-8
=head1 NAME
Sympa::Regexps - Definition of regular expressions
=head1 DESCRIPTION
This module keeps definition of regular expressions used by Sympa software.
=cut

View File

@ -0,0 +1,506 @@
# -*- indent-tabs-mode: nil; -*-
# vim:ft=perl:et:sw=4
# $Id$
# Sympa - SYsteme de Multi-Postage Automatique
#
# Copyright (c) 1997, 1998, 1999 Institut Pasteur & Christophe Wolfhugel
# Copyright (c) 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005,
# 2006, 2007, 2008, 2009, 2010, 2011 Comite Reseau des Universites
# Copyright (c) 2011, 2012, 2013, 2014, 2015, 2016, 2017 GIP RENATER
# Copyright 2018 The Sympa Community. See the AUTHORS.md file at the
# top-level directory of this distribution and at
# <https://github.com/sympa-community/sympa.git>.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
package Sympa::Tools::Data;
use strict;
use warnings;
use Encode qw();
use English qw(-no_match_vars);
use POSIX qw();
use XML::LibXML qw();
BEGIN { eval 'use Clone qw()'; }
use Sympa::Tools::Text;
## This applies recursively to a data structure
## The transformation subroutine is passed as a ref
sub recursive_transformation {
my ($var, $subref) = @_;
return unless (ref($var));
if (ref($var) eq 'ARRAY') {
foreach my $index (0 .. $#{$var}) {
if (ref($var->[$index])) {
recursive_transformation($var->[$index], $subref);
} else {
$var->[$index] = &{$subref}($var->[$index]);
}
}
} elsif (ref($var) eq 'HASH') {
foreach my $key (keys %{$var}) {
if (ref($var->{$key})) {
recursive_transformation($var->{$key}, $subref);
} else {
$var->{$key} = &{$subref}($var->{$key});
}
}
}
return;
}
## Dump a variable's content
sub dump_var {
my ($var, $level, $fd) = @_;
return undef unless ($fd);
if (ref($var)) {
if (ref($var) eq 'ARRAY') {
foreach my $index (0 .. $#{$var}) {
print $fd "\t" x $level . $index . "\n";
dump_var($var->[$index], $level + 1, $fd);
}
} elsif (ref($var) eq 'HASH'
|| ref($var) eq 'Sympa::Scenario'
|| ref($var) eq 'Sympa::List'
|| ref($var) eq 'CGI::Fast') {
foreach my $key (sort keys %{$var}) {
print $fd "\t" x $level . '_' . $key . '_' . "\n";
dump_var($var->{$key}, $level + 1, $fd);
}
} else {
printf $fd "\t" x $level . "'%s'" . "\n", ref($var);
}
} else {
if (defined $var) {
print $fd "\t" x $level . "'$var'" . "\n";
} else {
print $fd "\t" x $level . "UNDEF\n";
}
}
}
## Dump a variable's content
sub dump_html_var {
my ($var) = shift;
my $html = '';
if (ref($var)) {
if (ref($var) eq 'ARRAY') {
$html .= '<ul>';
foreach my $index (0 .. $#{$var}) {
$html .= '<li> ' . $index . ':';
$html .= dump_html_var($var->[$index]);
$html .= '</li>';
}
$html .= '</ul>';
} elsif (ref($var) eq 'HASH'
|| ref($var) eq 'Sympa::Scenario'
|| ref($var) eq 'Sympa::List') {
$html .= '<ul>';
foreach my $key (sort keys %{$var}) {
$html .= '<li>' . $key . '=';
$html .= dump_html_var($var->{$key});
$html .= '</li>';
}
$html .= '</ul>';
} else {
$html .= 'EEEEEEEEEEEEEEEEEEEEE' . ref($var);
}
} else {
if (defined $var) {
$html .= Sympa::Tools::Text::encode_html($var);
} else {
$html .= 'UNDEF';
}
}
return $html;
}
# Duplicates a complex variable (faster).
# CAUTION: This duplicates blessed elements even if they are
# singleton/multiton; this breaks subroutine references.
sub clone_var {
return Clone::clone($_[0]) if $Clone::VERSION;
goto &dup_var; # '&' needed
}
## Duplictate a complex variable
sub dup_var {
my ($var) = @_;
if (ref($var)) {
if (ref($var) eq 'ARRAY') {
my $new_var = [];
foreach my $index (0 .. $#{$var}) {
$new_var->[$index] = dup_var($var->[$index]);
}
return $new_var;
} elsif (ref($var) eq 'HASH') {
my $new_var = {};
foreach my $key (sort keys %{$var}) {
$new_var->{$key} = dup_var($var->{$key});
}
return $new_var;
}
}
return $var;
}
####################################################
# get_array_from_splitted_string
####################################################
# return an array made on a string splited by ','.
# It removes spaces.
#
#
# IN : -$string (+): string to split
#
# OUT : -ref(ARRAY)
#
######################################################
# Note: This is used only by Sympa::List.
sub get_array_from_splitted_string {
my ($string) = @_;
my @array;
foreach my $word (split /,/, $string) {
$word =~ s/^\s+//;
$word =~ s/\s+$//;
push @array, $word;
}
return \@array;
}
####################################################
# diff_on_arrays
####################################################
# Makes set operation on arrays (seen as set, with no double) :
# - deleted : A \ B
# - added : B \ A
# - intersection : A /\ B
# - union : A \/ B
#
# IN : -$setA : ref(ARRAY) - set
# -$setB : ref(ARRAY) - set
#
# OUT : -ref(HASH) with keys :
# deleted, added, intersection, union
#
#######################################################
sub diff_on_arrays {
my ($setA, $setB) = @_;
my $result = {
'intersection' => [],
'union' => [],
'added' => [],
'deleted' => []
};
my %deleted;
my %added;
my %intersection;
my %union;
my %hashA;
my %hashB;
foreach my $eltA (@$setA) {
$hashA{$eltA} = 1;
$deleted{$eltA} = 1;
$union{$eltA} = 1;
}
foreach my $eltB (@$setB) {
$hashB{$eltB} = 1;
$added{$eltB} = 1;
if ($hashA{$eltB}) {
$intersection{$eltB} = 1;
$deleted{$eltB} = 0;
} else {
$union{$eltB} = 1;
}
}
foreach my $eltA (@$setA) {
if ($hashB{$eltA}) {
$added{$eltA} = 0;
}
}
foreach my $elt (keys %deleted) {
next unless $elt;
push @{$result->{'deleted'}}, $elt if ($deleted{$elt});
}
foreach my $elt (keys %added) {
next unless $elt;
push @{$result->{'added'}}, $elt if ($added{$elt});
}
foreach my $elt (keys %intersection) {
next unless $elt;
push @{$result->{'intersection'}}, $elt if ($intersection{$elt});
}
foreach my $elt (keys %union) {
next unless $elt;
push @{$result->{'union'}}, $elt if ($union{$elt});
}
return $result;
}
####################################################
# is_in_array
####################################################
# Test if a value is on an array
#
# IN : -$setA : ref(ARRAY) - set
# -$value : a serached value
#
# OUT : boolean
#######################################################
sub is_in_array {
my $set = shift;
die 'missing parameter "$value"' unless @_;
my $value = shift;
if (defined $value) {
foreach my $elt (@{$set || []}) {
next unless defined $elt;
return 1 if $elt eq $value;
}
} else {
foreach my $elt (@{$set || []}) {
return 1 unless defined $elt;
}
}
return undef;
}
=over
=item smart_eq ( $a, $b )
I<Function>.
Check if two strings are identical.
Parameters:
=over
=item $a, $b
Operands.
If both of them are undefined, they are equal.
If only one of them is undefined, the are not equal.
If C<$b> is a L<Regexp> object and it matches to C<$a>, they are equal.
Otherwise, they are compared as strings.
=back
Returns:
If arguments matched, true value. Otherwise false value.
=back
=cut
sub smart_eq {
die 'missing argument' if scalar @_ < 2;
my ($a, $b) = @_;
if (defined $a and defined $b) {
if (ref $b eq 'Regexp') {
return 1 if $a =~ $b;
} else {
return 1 if $a eq $b;
}
} elsif (!defined $a and !defined $b) {
return 1;
}
return undef;
}
## convert a string formated as var1="value1";var2="value2"; into a hash.
## Used when extracting from session table some session properties or when
## extracting users preference from user table
## Current encoding is NOT compatible with encoding of values with '"'
##
sub string_2_hash {
my $data = shift;
my %hash;
pos($data) = 0;
while ($data =~ /\G;?(\w+)\=\"((\\[\"\\]|[^\"])*)\"(?=(;|\z))/g) {
my ($var, $val) = ($1, $2);
$val =~ s/\\([\"\\])/$1/g;
$hash{$var} = $val;
}
return (%hash);
}
## convert a hash into a string formated as var1="value1";var2="value2"; into
## a hash
sub hash_2_string {
my $refhash = shift;
return undef unless ref $refhash eq 'HASH';
my $data_string;
foreach my $var (keys %$refhash) {
next unless length $var;
my $val = $refhash->{$var};
$val = '' unless defined $val;
$val =~ s/([\"\\])/\\$1/g;
$data_string .= ';' . $var . '="' . $val . '"';
}
return ($data_string);
}
## compare 2 scalars, string/numeric independant
sub smart_lessthan {
my ($stra, $strb) = @_;
$stra =~ s/^\s+//;
$stra =~ s/\s+$//;
$strb =~ s/^\s+//;
$strb =~ s/\s+$//;
$ERRNO = 0;
my ($numa, $unparsed) = POSIX::strtod($stra);
my $numb;
$numb = POSIX::strtod($strb)
unless ($ERRNO || $unparsed != 0);
if (($stra eq '') || ($strb eq '') || ($unparsed != 0) || $ERRNO) {
return $stra lt $strb;
} else {
return $stra < $strb;
}
}
=over
=item sort_uniq ( [ \&comp ], @items )
Returns sorted array of unique elements in the list.
Parameters:
=over
=item \&comp
Optional subroutine reference to compare each pairs of elements.
It should take two arguments and return negative, zero or positive result.
=item @items
Items to be sorted.
=back
This function was added on Sympa 6.2.16.
=back
=cut
sub sort_uniq {
my $comp;
if (ref $_[0] eq 'CODE') {
$comp = shift;
}
my %items;
@items{@_} = ();
if ($comp) {
return sort { $comp->($a, $b) } keys %items;
} else {
return sort keys %items;
}
}
# Create a custom attribute from an XML description
# IN : A string, XML formed data as stored in database
# OUT : HASH data storing custome attributes.
# Old name: Sympa::List::parseCustomAttribute().
sub decode_custom_attribute {
my $xmldoc = shift;
return undef unless defined $xmldoc and length $xmldoc;
my $parser = XML::LibXML->new();
my $tree;
## We should use eval to parse to prevent the program to crash if it fails
if (ref($xmldoc) eq 'GLOB') {
$tree = eval { $parser->parse_fh($xmldoc) };
} else {
$tree = eval { $parser->parse_string($xmldoc) };
}
return undef unless defined $tree;
my $doc = $tree->getDocumentElement;
my @custom_attr = $doc->getChildrenByTagName('custom_attribute');
my %ca;
foreach my $ca (@custom_attr) {
my $id = Encode::encode_utf8($ca->getAttribute('id'));
my $value = Encode::encode_utf8($ca->getElementsByTagName('value'));
$ca{$id} = {value => $value};
}
return \%ca;
}
# Create an XML Custom attribute to be stored into data base.
# IN : HASH data storing custome attributes
# OUT : string, XML formed data to be stored in database
# Old name: Sympa::List::createXMLCustomAttribute().
sub encode_custom_attribute {
my $custom_attr = shift;
return
'<?xml version="1.0" encoding="UTF-8" ?><custom_attributes></custom_attributes>'
if (not defined $custom_attr);
my $XMLstr = '<?xml version="1.0" encoding="UTF-8" ?><custom_attributes>';
foreach my $k (sort keys %{$custom_attr}) {
my $value = $custom_attr->{$k}{value};
$value = '' unless defined $value;
$XMLstr .=
"<custom_attribute id=\"$k\"><value>"
. Sympa::Tools::Text::encode_html($value, '\000-\037')
. "</value></custom_attribute>";
}
$XMLstr .= "</custom_attributes>";
$XMLstr =~ s/\s*\n\s*/ /g;
return $XMLstr;
}
1;

View File

@ -0,0 +1,942 @@
# -*- indent-tabs-mode: nil; -*-
# vim:ft=perl:et:sw=4
# $Id$
# Sympa - SYsteme de Multi-Postage Automatique
#
# Copyright (c) 1997, 1998, 1999 Institut Pasteur & Christophe Wolfhugel
# Copyright (c) 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005,
# 2006, 2007, 2008, 2009, 2010, 2011 Comite Reseau des Universites
# Copyright (c) 2011, 2012, 2013, 2014, 2015, 2016, 2017 GIP RENATER
# Copyright 2018, 2020 The Sympa Community. See the AUTHORS.md
# file at the top-level directory of this distribution and at
# <https://github.com/sympa-community/sympa.git>.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
package Sympa::Tools::Text;
use strict;
use warnings;
use Encode qw();
use English qw(-no_match_vars);
use Encode::MIME::Header; # 'MIME-Q' encoding.
use HTML::Entities qw();
use MIME::EncWords;
use Text::LineFold;
use Unicode::GCString;
use URI::Escape qw();
use if ($] < 5.016), qw(Unicode::CaseFold fc);
use if (5.016 <= $]), qw(feature fc);
BEGIN { eval 'use Unicode::Normalize qw()'; }
BEGIN { eval 'use Unicode::UTF8 qw()'; }
use Sympa::Language;
use Sympa::Regexps;
# Old name: tools::addrencode().
sub addrencode {
my $addr = shift;
my $phrase = (shift || '');
my $charset = (shift || 'utf8');
my $comment = (shift || '');
return undef unless $addr =~ /\S/;
if ($phrase =~ /[^\s\x21-\x7E]/) {
$phrase = MIME::EncWords::encode_mimewords(
Encode::decode('utf8', $phrase),
'Encoding' => 'A',
'Charset' => $charset,
'Replacement' => 'FALLBACK',
'Field' => 'Resent-Sender', # almost longest
'Minimal' => 'DISPNAME', # needs MIME::EncWords >= 1.012.
);
} elsif ($phrase =~ /\S/) {
$phrase =~ s/([\\\"])/\\$1/g;
$phrase = '"' . $phrase . '"';
}
if ($comment =~ /[^\s\x21-\x27\x2A-\x5B\x5D-\x7E]/) {
$comment = MIME::EncWords::encode_mimewords(
Encode::decode('utf8', $comment),
'Encoding' => 'A',
'Charset' => $charset,
'Replacement' => 'FALLBACK',
'Minimal' => 'DISPNAME',
);
} elsif ($comment =~ /\S/) {
$comment =~ s/([\\\"])/\\$1/g;
}
return
($phrase =~ /\S/ ? "$phrase " : '')
. ($comment =~ /\S/ ? "($comment) " : '')
. "<$addr>";
}
# Old names: tools::clean_email(), tools::get_canonical_email().
sub canonic_email {
my $email = shift;
return undef unless defined $email;
# Remove leading and trailing white spaces.
$email =~ s/\A\s+//;
$email =~ s/\s+\z//;
# Lower-case.
$email =~ tr/A-Z/a-z/;
return (length $email) ? $email : undef;
}
# Old name: tools::clean_msg_id().
sub canonic_message_id {
my $msg_id = shift;
return $msg_id unless defined $msg_id;
chomp $msg_id;
if ($msg_id =~ /\<(.+)\>/) {
$msg_id = $1;
}
return $msg_id;
}
sub canonic_text {
my $text = shift;
return undef unless defined $text;
# Normalize text. See also discussion on
# https://listes.renater.fr/sympa/arc/sympa-developpers/2018-03/thrd1.html
#
# N.B.: Corresponding modules are optional by now, and should be
# mandatory in the future.
my $utext;
if (Encode::is_utf8($text)) {
$utext = $text;
} elsif ($Unicode::UTF8::VERSION) {
no warnings 'utf8';
$utext = Unicode::UTF8::decode_utf8($text);
} else {
$utext = Encode::decode_utf8($text);
}
if ($Unicode::Normalize::VERSION) {
$utext = Unicode::Normalize::normalize('NFC', $utext);
}
# Remove DOS linefeeds (^M) that cause problems with Outlook 98, AOL,
# and EIMS:
$utext =~ s/\r\n|\r/\n/g;
if (Encode::is_utf8($text)) {
return $utext;
} else {
return Encode::encode_utf8($utext);
}
}
sub slurp {
my $path = shift;
my $ifh;
return undef unless open $ifh, '<', $path;
my $text = do { local $RS; <$ifh> };
close $ifh;
return canonic_text($text);
}
sub wrap_text {
my $text = shift;
my $init = shift;
my $subs = shift;
my $cols = shift;
$init //= '';
$subs //= '';
$cols //= 78;
return $text unless $cols;
my $email_re = Sympa::Regexps::email();
my $linefold = Text::LineFold->new(
Language => Sympa::Language->instance->get_lang,
Prep => 'NONBREAKURI',
prep => [$email_re, sub { shift; @_ }],
ColumnsMax => $cols,
Format => sub {
shift;
my $event = shift;
my $str = shift;
if ($event =~ /^eo/) { return "\n"; }
if ($event =~ /^so[tp]/) { return $init . $str; }
if ($event eq 'sol') { return $subs . $str; }
undef;
},
);
my $t = Encode::is_utf8($text) ? $text : Encode::decode_utf8($text);
my $ret = '';
while (1000 < length $t) {
my $s = substr $t, 0, 1000;
$ret .= $linefold->break_partial($s);
$t = substr $t, 1000;
}
$ret .= $linefold->break_partial($t) if length $t;
$ret .= $linefold->break_partial(undef);
return Encode::is_utf8($text) ? $ret : Encode::encode_utf8($ret);
}
sub decode_filesystem_safe {
my $str = shift;
return '' unless defined $str and length $str;
$str = Encode::encode_utf8($str) if Encode::is_utf8($str);
# On case-insensitive filesystem "_XX" along with "_xx" should be decoded.
$str =~ s/_([0-9A-Fa-f]{2})/chr hex "0x$1"/eg;
return $str;
}
sub decode_html {
my $str = shift;
Encode::encode_utf8(
HTML::Entities::decode_entities(Encode::decode_utf8($str)));
}
sub encode_filesystem_safe {
my $str = shift;
return '' unless defined $str and length $str;
$str = Encode::encode_utf8($str) if Encode::is_utf8($str);
$str =~ s/([^-+.0-9\@A-Za-z])/sprintf '_%02x', ord $1/eg;
return $str;
}
sub encode_html {
my $str = shift;
my $additional_unsafe = shift || '';
HTML::Entities::encode_entities($str, '<>&"' . $additional_unsafe);
}
sub encode_uri {
my $str = shift;
my %options = @_;
# Note: URI-1.35 (URI::Escape 3.28) or later is required.
return Encode::encode_utf8(
URI::Escape::uri_escape_utf8(
Encode::decode_utf8($str),
'^-A-Za-z0-9._~' . (exists $options{omit} ? $options{omit} : '')
)
);
}
# Old name: tools::escape_chars().
sub escape_chars {
my $s = shift;
my $except = shift; ## Exceptions
my $ord_except = ord $except if defined $except;
## Escape chars
## !"#$%&'()+,:;<=>?[] AND accented chars
## escape % first
foreach my $i (
0x25,
0x20 .. 0x24,
0x26 .. 0x2c,
0x3a .. 0x3f,
0x5b, 0x5d,
0x80 .. 0x9f,
0xa0 .. 0xff
) {
next if defined $ord_except and $i == $ord_except;
my $hex_i = sprintf "%lx", $i;
$s =~ s/\x$hex_i/%$hex_i/g;
}
## Special traetment for '/'
$s =~ s/\//%a5/g unless defined $except and $except eq '/';
return $s;
}
# Old name: tt2::escape_url().
# DEPRECATED. Use Sympa::Tools::Text::escape_uri() or
# Sympa::Tools::Text::mailtourl().
#sub escape_url;
sub foldcase {
my $str = shift;
return '' unless defined $str and length $str;
# Perl 5.16.0 and later have built-in fc(). Earlier uses Unicode::CaseFold.
return Encode::encode_utf8(fc(Encode::decode_utf8($str)));
}
my %legacy_charsets = (
'ar' => [qw(iso-8859-6)],
'bs' => [qw(iso-8859-2)],
'cs' => [qw(iso-8859-2)],
'eo' => [qw(iso-8859-3)],
'et' => [qw(iso-8859-4)],
'he' => [qw(iso-8859-8)],
'hr' => [qw(iso-8859-2)],
'hu' => [qw(iso-8859-2)],
'ja' => [qw(euc-jp cp932 MacJapanese)],
'kl' => [qw(iso-8859-4)],
'ko' => [qw(cp949)],
'lt' => [qw(iso-8859-4)],
'lv' => [qw(iso-8859-4)],
'mt' => [qw(iso-8859-3)],
'pl' => [qw(iso-8859-2)],
'ro' => [qw(iso-8859-2)],
'ru' => [qw(koi8-r cp1251)], # cp866? MacCyrillic?
'sk' => [qw(iso-8859-2)],
'sl' => [qw(iso-8859-2)],
'th' => [qw(iso-8859-11 cp874 MacThai)],
'tr' => [qw(iso-8859-9)],
'uk' => [qw(koi8-u)], # MacUkrainian?
'zh-CN' => [qw(euc-cn)],
'zh-TW' => [qw(big5-eten)],
);
sub guessed_to_utf8 {
my $text = shift;
my @langs = @_;
return Encode::encode_utf8($text) if Encode::is_utf8($text);
return $text
unless defined $text
and length $text
and $text =~ /[^\x00-\x7F]/;
my $utf8;
if ($Unicode::UTF8::VERSION) {
$utf8 =
eval { Unicode::UTF8::decode_utf8($text, Encode::FB_CROAK()) };
}
unless (defined $utf8) {
foreach my $charset (map { $_ ? @$_ : () } @legacy_charsets{@langs}) {
$utf8 =
eval { Encode::decode($charset, $text, Encode::FB_CROAK()) };
last if defined $utf8;
}
}
unless (defined $utf8) {
$utf8 = Encode::decode('iso-8859-1', $text);
}
# Apply NFC: e.g. for modified-NFD by Mac OS X.
$utf8 = Unicode::Normalize::normalize('NFC', $utf8)
if $Unicode::Normalize::VERSION;
return Encode::encode_utf8($utf8);
}
sub mailtourl {
my $text = shift;
my %options = @_;
my $dtext =
(not defined $text) ? ''
: $options{decode_html} ? Sympa::Tools::Text::decode_html($text)
: $text;
$dtext =~ s/\A\s+//;
$dtext =~ s/\s+\z//;
$dtext =~ s/(?:\r\n|\r|\n)(?=[ \t])//g;
$dtext =~ s/\r\n|\r|\n/ /g;
# The ``@'' in email address should not be encoded because some MUAs
# aren't able to decode ``%40'' in e-mail address of mailto: URL.
# Contrary, ``@'' in query component should be encoded because some
# MUAs take it for a delimiter to separate URL from the rest.
my ($format, $utext, $qsep);
if ($dtext =~ /[()<>\[\]:;,\"\s]/) {
# Use "to" header if source text includes any of RFC 5322
# "specials", minus ``@'' and ``\'', plus whitespaces.
$format = 'mailto:?to=%s%s';
$utext = Sympa::Tools::Text::encode_uri($dtext);
$qsep = '&';
} else {
$format = 'mailto:%s%s';
$utext = Sympa::Tools::Text::encode_uri($dtext, omit => '@');
$qsep = '?';
}
my $qstring = _url_query_string(
$options{query},
decode_html => $options{decode_html},
leadchar => $qsep,
sepchar => '&',
trim_values => 1,
);
return sprintf $format, $utext, $qstring;
}
sub _url_query_string {
my $query = shift;
my %options = @_;
unless (ref $query eq 'HASH' and %$query) {
return '';
} else {
my $decode_html = $options{decode_html};
my $trim_values = $options{trim_values};
return ($options{leadchar} || '?') . join(
($options{sepchar} || ';'),
map {
my ($dkey, $dval) = map {
(not defined $_) ? ''
: $decode_html ? Sympa::Tools::Text::decode_html($_)
: $_;
} ($_, $query->{$_});
if ($trim_values and lc $dkey ne 'body') {
$dval =~ s/\A\s+//;
$dval =~ s/\s+\z//;
$dval =~ s/(?:\r\n|\r|\n)(?=[ \t])//g;
$dval =~ s/\r\n|\r|\n/ /g;
}
sprintf '%s=%s',
Sympa::Tools::Text::encode_uri($dkey),
Sympa::Tools::Text::encode_uri($dval);
} sort keys %$query
);
}
}
sub pad {
my $str = shift;
my $width = shift;
return $str unless $width and defined $str;
my $ustr = Encode::is_utf8($str) ? $str : Encode::decode_utf8($str);
my $cols = Unicode::GCString->new($ustr)->columns;
unless ($cols < abs $width) {
return $str;
} elsif ($width < 0) {
return $str . (' ' x (-$width - $cols));
} else {
return (' ' x ($width - $cols)) . $str;
}
}
# Old name: tools::qdecode_filename().
sub qdecode_filename {
my $filename = shift;
## We don't use MIME::Words here because it does not encode properly
## Unicode
## Check if string is already Q-encoded first
#if ($filename =~ /\=\?UTF-8\?/) {
$filename = Encode::encode_utf8(Encode::decode('MIME-Q', $filename));
#}
return $filename;
}
# Old name: tools::qencode_filename().
sub qencode_filename {
my $filename = shift;
## We don't use MIME::Words here because it does not encode properly
## Unicode
## Check if string is already Q-encoded first
## Also check if the string contains 8bit chars
unless ($filename =~ /\=\?UTF-8\?/
|| $filename =~ /^[\x00-\x7f]*$/) {
## Don't encode elements such as .desc. or .url or .moderate
## or .extension
my $part = $filename;
my ($leading, $trailing);
$leading = $1 if ($part =~ s/^(\.desc\.)//); ## leading .desc
$trailing = $1 if ($part =~ s/((\.\w+)+)$//); ## trailing .xx
my $encoded_part = MIME::EncWords::encode_mimewords(
$part,
Charset => 'utf8',
Encoding => 'q',
MaxLineLen => 1000,
Minimal => 'NO'
);
$filename = $leading . $encoded_part . $trailing;
}
return $filename;
}
# Old name: tools::unescape_chars().
sub unescape_chars {
my $s = shift;
$s =~ s/%a5/\//g; ## Special traetment for '/'
foreach my $i (0x20 .. 0x2c, 0x3a .. 0x3f, 0x5b, 0x5d, 0x80 .. 0x9f,
0xa0 .. 0xff) {
my $hex_i = sprintf "%lx", $i;
my $hex_s = sprintf "%c", $i;
$s =~ s/%$hex_i/$hex_s/g;
}
return $s;
}
# Old name: tools::valid_email().
sub valid_email {
my $email = shift;
my $email_re = Sympa::Regexps::email();
return undef unless $email =~ /^${email_re}$/;
# Forbidden characters.
return undef if $email =~ /[\|\$\*\?\!]/;
return 1;
}
sub weburl {
my $base = shift;
my $paths = shift;
my %options = @_;
my @paths = map {
Sympa::Tools::Text::encode_uri(
(not defined $_) ? ''
: $options{decode_html} ? Sympa::Tools::Text::decode_html($_)
: $_
);
} @{$paths || []};
my $qstring = _url_query_string(
$options{query},
decode_html => $options{decode_html},
sepchar => '&',
);
my $fstring;
my $fragment = $options{fragment};
if (defined $fragment) {
$fstring = '#'
. Sympa::Tools::Text::encode_uri(
$options{decode_html}
? Sympa::Tools::Text::decode_html($fragment)
: $fragment
);
} else {
$fstring = '';
}
return sprintf '%s%s%s', join('/', grep { defined $_ } ($base, @paths)),
$qstring, $fstring;
}
1;
__END__
=encoding utf-8
=head1 NAME
Sympa::Tools::Text - Text-related functions
=head1 DESCRIPTION
This package provides some text-related functions.
=head2 Functions
=over
=item addrencode ( $addr, [ $phrase, [ $charset, [ $comment ] ] ] )
Returns formatted (and encoded) name-addr as RFC5322 3.4.
=item canonic_email ( $email )
I<Function>.
Returns canonical form of e-mail address.
Leading and trailing white spaces are removed.
Latin letters without accents are lower-cased.
For malformed inputs returns C<undef>.
=item canonic_message_id ( $message_id )
Returns canonical form of message ID without trailing or leading whitespaces
or C<E<lt>>, C<E<gt>>.
=item canonic_text ( $text )
Canonicalizes text.
C<$text> should be a binary string encoded by UTF-8 character set or
a Unicode string.
Forbidden sequences in binary string will be replaced by
U+FFFD REPLACEMENT CHARACTERs, and Normalization Form C (NFC) will be applied.
=item decode_filesystem_safe ( $str )
I<Function>.
Decodes a string encoded by encode_filesystem_safe().
Parameter:
=over
=item $str
String to be decoded.
=back
Returns:
Decoded string, stripped C<utf8> flag if any.
=item decode_html ( $str )
I<Function>.
Decodes HTML entities in a string encoded by UTF-8 or a Unicode string.
Parameter:
=over
=item $str
String to be decoded.
=back
Returns:
Decoded string, stripped C<utf8> flag if any.
=item encode_filesystem_safe ( $str )
I<Function>.
Encodes a string $str to be suitable for filesystem.
Parameter:
=over
=item $str
String to be encoded.
=back
Returns:
Encoded string, stripped C<utf8> flag if any.
All bytes except C<'-'>, C<'+'>, C<'.'>, C<'@'>
and alphanumeric characters are encoded to sequences C<'_'> followed by
two hexdigits.
Note that C<'/'> will also be encoded.
=item encode_html ( $str, [ $additional_unsafe ] )
I<Function>.
Encodes characters in a string $str to HTML entities.
By default
C<'E<lt>'>, C<'E<gt>'>, C<'E<amp>'> and C<'E<quot>'> are encoded.
Parameter:
=over
=item $str
String to be encoded.
=item $additional_unsafe
Character or range of characters additionally encoded as entity references.
This optional parameter was introduced on Sympa 6.2.37b.3.
=back
Returns:
Encoded string, I<not> stripping utf8 flag if any.
=item encode_uri ( $str, [ omit => $chars ] )
I<Function>.
Encodes potentially unsafe characters in the string using "percent" encoding
suitable for URIs.
Parameters:
=over
=item $str
String to be encoded.
=item omit =E<gt> $chars
By default, all characters except those defined as "unreserved" in RFC 3986
are encoded, that is, C<[^-A-Za-z0-9._~]>.
If this parameter is given, it will prevent encoding additional characters.
=back
Returns:
Encoded string, stripped C<utf8> flag if any.
=item escape_chars ( $str )
Escape weird characters.
ToDo: This should be obsoleted in the future release: Would be better to use
L</encode_filesystem_safe>.
=item escape_url ( $str )
DEPRECATED.
Would be better to use L</"encode_uri"> or L</"mailtourl">.
=item foldcase ( $str )
I<Function>.
Returns "fold-case" string suitable for case-insensitive match.
For example, a code below looks for a needle in haystack not regarding case,
even if they are non-ASCII UTF-8 strings.
$haystack = Sympa::Tools::Text::foldcase($HayStack);
$needle = Sympa::Tools::Text::foldcase($NeedLe);
if (index $haystack, $needle >= 0) {
...
}
Parameter:
=over
=item $str
A string.
=back
=item guessed_to_utf8( $text, [ lang, ... ] )
I<Function>.
Guesses text charset considering language context
and returns the text reencoded by UTF-8.
Parameters:
=over
=item $text
Text to be reencoded.
=item lang, ...
Language tag(s) which may be given by L<Sympa::Language/"implicated_langs">.
=back
Returns:
Reencoded text.
If any charsets could not be guessed, C<iso-8859-1> will be used
as the last resort, just because it covers full range of 8-bit.
=item mailtourl ( $email, [ decode_html =E<gt> 1 ],
[ query =E<gt> {key =E<gt> val, ...} ] )
I<Function>.
Constructs a C<mailto:> URL for given e-mail.
Parameters:
=over
=item $email
E-mail address.
=item decode_html =E<gt> 1
If set, arguments are assumed to include HTML entities.
=item query =E<gt> {key =E<gt> val, ...}
Optional query.
=back
Returns:
Constructed URL.
=item pad ( $str, $width )
Pads space a string so that result will not be narrower than given width.
Parameters:
=over
=item $str
A string.
=item $width
If $width is false value or width of $str is not less than $width,
does nothing.
If $width is less than C<0>, pads right.
Otherwise, pads left.
=back
Returns:
Padded string.
=item qdecode_filename ( $filename )
Q-Decodes web file name.
ToDo:
This should be obsoleted in the future release: Would be better to use
L</decode_filesystem_safe>.
=item qencode_filename ( $filename )
Q-Encodes web file name.
ToDo:
This should be obsoleted in the future release: Would be better to use
L</encode_filesystem_safe>.
=item slurp ( $file )
Get entire content of the file.
Normalization by canonic_text() is applied.
C<$file> is the path to text file.
=item unescape_chars ( $str )
Unescape weird characters.
ToDo: This should be obsoleted in the future release: Would be better to use
L</decode_filesystem_safe>.
=item valid_email ( $string )
Basic check of an email address.
=item weburl ( $base, \@paths, [ decode_html =E<gt> 1 ],
[ fragment =E<gt> $fragment ], [ query =E<gt> \%query ] )
Constructs a C<http:> or C<https:> URL under given base URI.
Parameters:
=over
=item $base
Base URI.
=item \@paths
Additional path components.
=item decode_html =E<gt> 1
If set, arguments are assumed to include HTML entities.
Exception is $base:
It is assumed not to include entities.
=item fragment =E<gt> $fragment
Optional fragment.
=item query =E<gt> \%query
Optional query.
=back
Returns:
A URI.
=item wrap_text ( $text, [ $init_tab, [ $subsequent_tab, [ $cols ] ] ] )
I<Function>.
Returns line-wrapped text.
Parameters:
=over
=item $text
The text to be folded.
=item $init_tab
Indentation prepended to the first line of paragraph.
Default is C<''>, no indentation.
=item $subsequent_tab
Indentation prepended to each subsequent line of folded paragraph.
Default is C<''>, no indentation.
=item $cols
Max number of columns of folded text.
Default is C<78>.
=back
=back
=head1 HISTORY
L<Sympa::Tools::Text> appeared on Sympa 6.2a.41.
decode_filesystem_safe() and encode_filesystem_safe() were added
on Sympa 6.2.10.
decode_html(), encode_html(), encode_uri() and mailtourl()
were added on Sympa 6.2.14, and escape_url() was deprecated.
guessed_to_utf8() and pad() were added on Sympa 6.2.17.
canonic_text() and slurp() were added on Sympa 6.2.53b.
=cut

View File

@ -0,0 +1,327 @@
#!/usr/bin/perl
# -*- indent-tabs-mode: nil; -*-
# vim:ft=perl:et:sw=4
# $Id$
# Sympa - SYsteme de Multi-Postage Automatique
#
# Copyright (c) 1997, 1998, 1999 Institut Pasteur & Christophe Wolfhugel
# Copyright (c) 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005,
# 2006, 2007, 2008, 2009, 2010, 2011 Comite Reseau des Universites
# Copyright (c) 2011, 2012, 2013, 2014, 2015, 2016, 2017 GIP RENATER
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
use lib '/usr/share/sympa/lib';
use strict;
use warnings;
use Getopt::Long;
use HTTP::Cookies;
#use SOAP::Lite +trace;
use SOAP::Lite;
use Sympa::Tools::Data;
my ($reponse, @ret, $val, %fault);
my $usage =
"$0 is a perl soap client for Sympa for TEST ONLY. Use it to illustrate how to
code access to features of Sympa soap server. Authentication can be done via
user/password or user cookie or as a trusted remote application
Usage: $0 <with the following options:>
--soap_url=<soap sympa server url>
--service=<a sympa service>
--trusted_application=<app name>
--trusted_application_password=<password>
--proxy_vars=<id=value,id2=value2>
--service_parameters=<value1,value2,value3>
OR usage: $0 <with the following options:>
--soap_url=<soap sympa server url>
--user_email=<email>
--user_password=<password>
--session_id=<sessionid>
--service=<a sympa service>
--service_parameters=<value1,value2,value3>
OR usage: $0 <with the following options:>
--soap_url=<soap sympa server url>
--cookie=<sympauser cookie string>
Example:
$0 --soap_url=<soap sympa server url> --cookie=sympauser=someone\@cru.fr
";
my %options;
unless (
GetOptions(
\%main::options, 'soap_url=s',
'service=s', 'trusted_application=s',
'trusted_application_password=s', 'user_email=s',
'user_password=s', 'cookie=s',
'proxy_vars=s', 'service_parameters=s',
'session_id=s'
)
) {
printf "";
}
my $soap_url = $main::options{'soap_url'};
unless (defined $soap_url) {
printf "error : missing soap_url parameter\n";
printf $usage;
exit 1;
}
my $user_email = $main::options{'user_email'};
my $user_password = $main::options{'user_password'};
my $session_id = $main::options{'session_id'};
my $trusted_application = $main::options{'trusted_application'};
my $trusted_application_password =
$main::options{'trusted_application_password'};
my $proxy_vars = $main::options{'proxy_vars'};
my $service = $main::options{'service'};
my $service_parameters = $main::options{'service_parameters'};
my $cookie = $main::options{'cookie'};
if (defined $trusted_application) {
unless (defined $trusted_application_password) {
printf "error : missing trusted_application_password parameter\n";
printf $usage;
exit 1;
}
unless (defined $service) {
printf "error : missing service parameter\n";
printf $usage;
exit 1;
}
unless (defined $proxy_vars) {
printf "error : missing proxy_vars parameter\n";
printf $usage;
exit 1;
}
play_soap_as_trusted($soap_url, $trusted_application,
$trusted_application_password, $service, $proxy_vars,
$service_parameters);
} elsif ($service eq 'getUserEmailByCookie') {
play_soap(
soap_url => $soap_url,
session_id => $session_id,
service => $service
);
} elsif (defined $cookie) {
printf "error : get_email_cookie\n";
get_email($soap_url, $cookie);
exit 1;
} else {
unless (defined $session_id
|| (defined $user_email && defined $user_password)) {
printf
"error : missing session_id OR user_email+user_passwors parameters\n";
printf $usage;
exit 1;
}
play_soap(
soap_url => $soap_url,
user_email => $user_email,
user_password => $user_password,
session_id => $session_id,
service => $service,
service_parameters => $service_parameters
);
}
sub play_soap_as_trusted {
my $soap_url = shift;
my $trusted_application = shift;
my $trusted_application_password = shift;
my $service = shift;
my $proxy_vars = shift;
my $service_parameters = shift;
my $soap = SOAP::Lite->new();
$soap->uri('urn:sympasoap');
$soap->proxy($soap_url);
my @parameters;
if (defined $service_parameters) {
@parameters = split /,/, $service_parameters;
} else {
@parameters = ();
}
my $p = join(',', @parameters);
printf
"calling authenticateRemoteAppAndRun( $trusted_application, $trusted_application_password, $proxy_vars,$service,$p)\n";
my $reponse =
$soap->authenticateRemoteAppAndRun($trusted_application,
$trusted_application_password, $proxy_vars, $service, \@parameters);
print_result($reponse);
}
sub get_email {
my $soap_url = shift;
my $cookie = shift;
my ($service, $reponse, @ret, $val, %fault);
## Cookies management
# my $uri = URI->new($soap_url);
# my $cookies = HTTP::Cookies->new(ignore_discard => 1,
# file => '/tmp/my_cookies' );
# $cookies->load();
printf "cookie : %s\n", $cookie;
my $soap = SOAP::Lite->new();
#$soap->on_debug(sub{print@_});
$soap->uri('urn:sympasoap');
$soap->proxy($soap_url);
#, cookie_jar =>$cookies);
print "\n\ngetEmailUserByCookie....\n";
$reponse = $soap->getUserEmailByCookie($cookie);
print_result($reponse);
exit;
}
sub play_soap {
my %param = @_;
my $soap_url = $param{'soap_url'};
my $user_email = $param{'user_email'};
my $user_password = $param{'user_password'};
my $session_id = $param{'session_id'};
my $service = $param{'service'};
my $service_parameters = $param{'service_parameters'};
my ($reponse, @ret, $val, %fault);
## Cookies management
# my $uri = URI->new($soap_url);
my $cookies = HTTP::Cookies->new(
ignore_discard => 1,
file => '/tmp/my_cookies'
);
$cookies->load();
printf "cookie : %s\n", $cookies->as_string();
my @parameters;
@parameters = split(/,/, $service_parameters)
if (defined $service_parameters);
my $p = join(',', @parameters);
foreach my $tmpParam (@parameters) {
printf "param: %s\n", $tmpParam;
}
# Change to the path of Sympa.wsdl
#$service = SOAP::Lite->service($soap_url);
#$reponse = $service->login($user_email,$user_password);
#my $soap = SOAP::Lite->service($soap_url);
my $soap = SOAP::Lite->new() || die;
#$soap->on_debug(sub{print@_});
$soap->uri('urn:sympasoap');
$soap->proxy($soap_url, cookie_jar => $cookies);
## Do the login unless a session_id is provided
if ($session_id) {
print "Using Session_id $session_id\n";
} else {
print "LOGIN....\n";
#$reponse = $soap->casLogin($soap_url);
$reponse = $soap->login($user_email, $user_password);
$cookies->save;
print_result($reponse);
$session_id = $reponse->result;
}
## Don't use authenticateAndRun for lists command
## Split parameters
if ($service_parameters && $service_parameters ne '') {
@parameters = split /,/, $service_parameters;
}
if ($service eq 'lists') {
printf "\n\nlists....\n";
$reponse = $soap->lists();
} elsif ($service eq 'subscribe') {
printf "\n\n$service....\n";
$reponse = $soap->subscribe(@parameters);
} elsif ($service eq 'signoff') {
printf "\n\n$service....\n";
$reponse = $soap->signoff(@parameters);
} elsif ($service eq 'add') {
printf "\n\n$service....\n";
$reponse = $soap->add(@parameters);
} elsif ($service eq 'del') {
printf "\n\n$service....\n";
$reponse = $soap->del(@parameters);
} elsif ($service eq 'getUserEmailByCookie') {
printf "\n\n$service....\n";
$reponse = $soap->getUserEmailByCookie($session_id);
} else {
printf "\n\nAuthenticateAndRun service=%s;(session_id=%s)....\n",
$service, $session_id;
$reponse =
$soap->authenticateAndRun($user_email, $session_id, $service,
\@parameters);
}
print_result($reponse);
}
sub print_result {
my $r = shift;
# If we get a fault
if (defined $r && $r->fault) {
print "Soap error :\n";
my %fault = %{$r->fault};
foreach $val (keys %fault) {
print "$val = $fault{$val}\n";
}
} else {
if (ref($r->result) =~ /^ARRAY/) {
#printf "R: $r->result\n";
@ret = @{$r->result};
} elsif (ref $r->result) {
print "Pb " . ($r->result) . "\n";
return undef;
} else {
@ret = $r->result;
}
Sympa::Tools::Data::dump_var(\@ret, 0, \*STDOUT);
}
return 1;
}

1851
dockers/apikaz/source/app.py Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1 @@
<!-- silence is golden -->

View File

@ -0,0 +1,9 @@
flask
flask-restful
flask-mail
requests
flasgger
passlib
unidecode
email-validator
python-ldap

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -0,0 +1,82 @@
body {
font-family: Arial, sans-serif;
background-color: #f4f4f4;
margin: 0;
padding: 0;
}
.email-content {
background-color: #f0f0f0; /* Light gray background */
margin: 20px auto;
padding: 20px;
border: 1px solid #dddddd;
max-width: 600px;
width: 90%; /* This makes the content take 90% width of its container */
text-align: left; /* Remove text justification */
}
header {
background-color: #E16969;
color: white;
text-align: center;
height: 50px; /* Fixed height for header */
line-height: 50px; /* Vertically center the text */
width: 100%; /* Make header full width */
}
footer {
background-color: #E16969;
color: white;
text-align: center;
height: 50px; /* Fixed height for footer */
line-height: 50px; /* Vertically center the text */
width: 100%; /* Make footer full width */
}
.header-container {
position: relative; /* Pour positionner le logo et le texte dans le header */
height: 50px; /* Hauteur maximale du header */
}
.logo {
position: absolute; /* Pour positionner le logo */
max-height: 100%; /* Taille maximale du logo égale à la hauteur du header */
top: 0; /* Aligner le logo en haut */
left: 0; /* Aligner le logo à gauche */
margin-right: 10px; /* Marge à droite du logo */
}
.header-container h1, .footer-container p {
margin: 0;
font-size: 24px;
}
.footer-container p {
font-size: 12px;
}
.footer-container a {
color: #FFFFFF; /* White color for links in footer */
text-decoration: none;
}
.footer-container a:hover {
text-decoration: underline; /* Optional: add underline on hover */
}
a {
color: #E16969; /* Same color as header/footer background for all other links */
text-decoration: none;
}
a:hover {
text-decoration: underline; /* Optional: add underline on hover */
}
h2 {
color: #E16969;
}
p {
line-height: 1.6;
}

View File

@ -0,0 +1,9 @@
<footer>
<div class="footer-container">
<p>
&copy; {{ now().year }} Kaz. Ici, on prend soin de vos données et on ne les vend pas !
<br>
<a href="https://kaz.bzh">https://kaz.bzh</a>
</p>
</div>
</footer>

View File

@ -0,0 +1,6 @@
<header>
<div class="header-container">
<img class="logo" src="https://kaz-cloud.kaz.bzh/apps/theming/image/logo?v=33" alt="KAZ Logo">
<h1>Kaz : Le numérique sobre, libre, éthique et local</h1>
</div>
</header>

View File

@ -0,0 +1,94 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Email d'inscription'</title>
<style>
{% include 'email.css' %}
</style>
</head>
<body>
{% include 'email_header.html' %}
<div class="email-content">
<p>
Bonjour {{NOM}}!<br><br>
Bienvenue chez KAZ!<br><br>
Vous disposez de :
<ul>
<li>une messagerie classique : <a href={{URL_WEBMAIL}}>{{URL_WEBMAIL}}</a></li>
<li>une messagerie instantanée pour discuter au sein d'équipes : <a href={{URL_AGORA}}>{{URL_AGORA}}</a></li>
</ul>
Votre email et identifiant pour ces services : {{EMAIL_SOUHAITE}}<br>
Le mot de passe : <b>{{PASSWORD}}</b><br><br>
Pour changer votre mot de passe de messagerie, c'est ici: <a href={{URL_MDP}}>{{URL_MDP}}</a><br>
Si vous avez perdu votre mot de passe, c'est ici: <a href={{URL_MDP}}/?action=sendtoken>{{URL_MDP}}/?action=sendtoken</a><br><br>
Vous pouvez accéder à votre messagerie classique:
<ul>
<li>soit depuis votre webmail : <a href={{URL_WEBMAIL}}>{{URL_WEBMAIL}}</a></li>
<li>soit depuis votre bureau virtuel : <a href={{URL_CLOUD}}>{{URL_CLOUD}}</a></li>
<li>soit depuis un client de messagerie comme thunderbird<br>
</ul>
</p>
{% if ADMIN_ORGA == '1' %}
<p>
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.<br><br>
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 ;)<br>
A quoi sert d'avoir un site web si on ne l'utilise pas, n'est-ce pas ?<br><br>
Par retour de mail, dites-nous de quoi vous avez besoin tout de suite entre:
<ul>
<li>une comptabilité : un service de gestion adhérents/clients</li>
<li>un site web de type WordPress</li>
<li>un cloud : bureau virtuel pour stocker des fichiers/calendriers/contacts et partager avec vos connaissances</li>
</ul>
Une fois que vous aurez répondu à ce mail, votre demande sera traitée manuellement.
</p>
{% endif %}
<p>
Vous avez quelques docs intéressantes sur le wiki de kaz:
<ul>
<li>Migrer son site internet wordpress vers kaz : <a href="https://wiki.kaz.bzh/wordpress/start#migrer_son_site_wordpress_vers_kaz">https://wiki.kaz.bzh/wordpress/start#migrer_son_site_wordpress_vers_kaz</a></li>
<li>Migrer sa messagerie vers kaz : <a href="https://wiki.kaz.bzh/messagerie/gmail/start">https://wiki.kaz.bzh/messagerie/gmail/start</a></li>
<li>Démarrer simplement avec son cloud : <a href="https://wiki.kaz.bzh/nextcloud/start">https://wiki.kaz.bzh/messagerie/gmail/start</a></li>
</ul>
Votre quota est de {{QUOTA}}GB. Si vous souhaitez plus de place pour vos fichiers ou la messagerie, faites-nous signe !<br><br>
Pour accéder à la messagerie instantanée et communiquer avec les membres de votre équipe ou ceux de kaz : <a href={{URL_AGORA}}/login>{{URL_AGORA}}/login</a><br>
</p>
{% if ADMIN_ORGA == '1' %}
<p>
Comme administrateur de votre organisation, vous pouvez créer des listes de diffusion en vous rendant sur <a href={{URL_LISTE}}>{{URL_LISTE}}</a><br>
</p>
{% endif %}
<p>
Enfin, vous disposez de tous les autres services KAZ où l'authentification n'est pas nécessaire : <a href={{URL_SITE}}>{{URL_SITE}}</a><br><br>
En cas de soucis, n'hésitez pas à poser vos questions sur le canal 'Une question ? un soucis' de l'agora dispo ici : <a href={{URL_AGORA}}>{{URL_AGORA}}</a><br><br>
Si vous avez besoin d'accompagnement pour votre site, votre cloud, votre compta, votre migration de messagerie,...<br>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.<br><br>
À bientôt 😉<br><br>
La collégiale de KAZ.<br>
</p>
</div> <!-- <div class="email-content"> -->
{% include 'email_footer.html' %}
</body>
</html>

1
dockers/cachet/.env Symbolic link
View File

@ -0,0 +1 @@
../../config/dockers.env

View File

@ -0,0 +1,47 @@
version: "3"
services:
cachet:
# ports:
# - 8085:8000
image: cachethq/docker
container_name: ${cachetServName}
restart: ${restartPolicy}
depends_on:
- db
networks:
- cachetNet
links:
- db:db
environment:
- DB_HOST=db
- APP_ENV=${APP_ENV:-production}
- APP_LOG=errorlog
- APP_DEBUG=false
- DEBUG=false
env_file:
- ../../secret/env-${cachetServName}
db:
image: mariadb:10.5
container_name: ${cachetDBName}
restart: ${restartPolicy}
networks:
- cachetNet
env_file:
- ../../secret/env-${cachetDBName}
volumes:
#- ./initdb.d:/docker-entrypoint-initdb.d:ro
- cachetDB:/var/lib/mysql
- /home/sauve/:/svg/
- /etc/localtime:/etc/localtime:ro
- /etc/timezone:/etc/timezone:ro
volumes:
cachetDB:
networks:
cachetNet:
external: true
name: cachetNet

1
dockers/castopod/.env Symbolic link
View File

@ -0,0 +1 @@
../../config/dockers.env

View File

@ -0,0 +1,55 @@
services:
app:
image: castopod/castopod:latest
container_name: ${castopodServName}
volumes:
- castopodMedia:/var/www/castopod/public/media
environment:
CP_BASEURL: "https://${castopodHost}.${domain}"
CP_ANALYTICS_SALT: qldsgfliuzrbhgmkjbdbmkvb
VIRTUAL_PORT: 8000
CP_CACHE_HANDLER: redis
CP_REDIS_HOST: redis
env_file:
- ../../secret/env-${castopodServName}
- ../../secret/env-${castopodDBName}
networks:
- castopodNet
expose:
- 8000
restart: unless-stopped
labels:
- "traefik.enable=true"
- "traefik.http.routers.${castopodServName}.rule=Host(`${castopodHost}.${domain}`)"
- "traefik.docker.network=castopodNet"
mariadb:
image: mariadb:10.5
container_name: ${castopodDBName}
networks:
- castopodNet
volumes:
- castopodDb:/var/lib/mysql
env_file:
- ../../secret/env-${castopodDBName}
restart: unless-stopped
redis:
image: redis:7.0-alpine
container_name: castopodCache
volumes:
- castopodCache:/data
networks:
- castopodNet
command: --requirepass ${castopodRedisPassword}
volumes:
castopodMedia:
castopodDb:
castopodCache:
networks:
castopodNet:
external: true
name: castopodNet

13
dockers/castopod/first.sh Executable file
View File

@ -0,0 +1,13 @@
#!/bin/bash
KAZ_ROOT=$(cd $(dirname $0)/../..; pwd)
. "${KAZ_ROOT}/bin/.commonFunctions.sh"
setKazVars
cd $(dirname $0)
. "${DOCKERS_ENV}"
. "${KAZ_KEY_DIR}/SetAllPass.sh"
"${KAZ_BIN_DIR}/gestContainers.sh" --install -M -castopod

1
dockers/cloud/.env Symbolic link
View File

@ -0,0 +1 @@
../../config/dockers.env

90
dockers/cloud/Readme.txt Normal file
View File

@ -0,0 +1,90 @@
Pour l'installation de Nextcloud
Documentation:
https://registry.hub.docker.com/_/nextcloud?tab=description
https://registry.hub.docker.com/_/mariadb?tab=description
https://blog.ssdnodes.com/blog/installing-nextcloud-docker/
____________________________________________________________
Contenu du répertoire
____________________________________________________________
Se placer dans le bon répertoire
# cd /docker/cloud
____________________________________________________________
Lancement de nextcloud et nextcloudDB
# docker-compose up -d
____________________________________________________________
Vérification
Il y a des containers qui tournent cloudServ cloudDB (collabraServ)
# docker ps
# docker exec -ti cloudServ bash
exit
# docker exec -ti cloudDB bash
exit
____________________________________________________________
Personalisation
Il faut attendre 2 minutes pour le lancement
Pour mettre en français
emacs /var/lib/docker/volumes/cloud_cloudConfig/_data/config.php
il faut ajouter :
"default_language" => "fr",
Création des comptes.
Application
* Tasks
* Calendar
* Desk
* Contact
* Mail
* Talk
* Draw.io
* Collabora Online - Built-in CODE Server (il faut un port d'écoute)
apt update
apt install sudo
sudo -u www-data php -d memory_limit=512M ./occ app:install richdocumentscode
sudo -u www-data php -d memory_limit=512M ./occ app:update --all
ou
installer un docker collabra et
apt update
apt install sudo
sudo -u www-data ./occ config:app:set --value http://89.234.186.106:9980/ richdocuments wopi_url
sudo -u www-data ./occ richdocuments:activate-config
https://cloud.kaz.bzh/settings/admin/richdocuments
____________________________________________________________
Mettre à jour le mot de passe dans /kaz/secret
____________________________________________________________
Test
Y a plus qu'a tester
http://kaz.bzh:8080
____________________________________________________________
Traces
https://cloud.kaz.bzh/index.php/settings/admin/logging
____________________________________________________________
Pour la sauvegarde il faut également des scripts
Didier le 11/12/2020
installation du module RainLoop pour les mails
mot passe de l' admin : voir dans /kaz/secret/SetAllPass.sh
le module a viré pas de custom_apps dans le docker-compose
proposition de modif du docker-compose.yml mis en commentaires.

View File

@ -0,0 +1,77 @@
version: '3.3'
services:
cloud:
image: nextcloud
container_name: ${nextcloudServName}
restart: ${restartPolicy}
depends_on:
- db
links:
- db
external_links:
- ${smtpServName}:${smtpHost}
# ports:
# - 8090:80
networks:
- cloudNet
- postfixNet
- ldapNet
volumes:
- cloudMain:/var/www/html
- cloudData:/var/www/html/data
- cloudConfig:/var/www/html/config
- cloudApps:/var/www/html/apps
- cloudCustomApps:/var/www/html/custom_apps
- cloudThemes:/var/www/html/themes/
- cloudPhp:/usr/local/etc/php/conf.d/
- /etc/localtime:/etc/localtime:ro
- /etc/timezone:/etc/timezone:ro
env_file:
- ../../secret/env-${nextcloudServName}
- ../../secret/env-${nextcloudDBName}
environment:
- NEXTCLOUD_TRUSTED_DOMAINS=${cloudHost}.${domain}
- SMTP_HOST=${smtpHost}
- SMTP_PORT=25
- MAIL_DOMAIN=${domain}
labels:
- "traefik.enable=true"
- "traefik.http.routers.${nextcloudServName}.rule=Host(`${cloudHost}.${domain}`)"
- "traefik.docker.network=cloudNet"
db:
image: mariadb:10.5
container_name: ${nextcloudDBName}
command: --transaction-isolation=READ-COMMITTED --binlog-format=ROW
restart: ${restartPolicy}
volumes:
- cloudDB:/var/lib/mysql
- /etc/localtime:/etc/localtime:ro
- /etc/timezone:/etc/timezone:ro
env_file:
- ../../secret/env-${nextcloudDBName}
networks:
- cloudNet
volumes:
cloudDB:
cloudMain:
cloudData:
cloudConfig:
cloudApps:
cloudCustomApps:
cloudThemes:
cloudPhp:
networks:
cloudNet:
external: true
name: cloudNet
postfixNet:
external: true
name: postfixNet
ldapNet:
external: true
name: ldapNet

10
dockers/cloud/first.sh Executable file
View File

@ -0,0 +1,10 @@
#!/bin/bash
KAZ_ROOT=$(cd $(dirname $0)/../..; pwd)
. "${KAZ_ROOT}/bin/.commonFunctions.sh"
setKazVars
. "${DOCKERS_ENV}"
. $KAZ_ROOT/secret/SetAllPass.sh
${KAZ_BIN_DIR}/gestContainers.sh --install -M -cloud

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

3
dockers/cloud/reindex.sh Executable file
View File

@ -0,0 +1,3 @@
#!/bin/bash
docker exec --user www-data -ti nextcloudServ bash -c "/var/www/html/occ db:add-missing-indices"

1
dockers/collabora/.env Symbolic link
View File

@ -0,0 +1 @@
../../config/dockers.env

View File

@ -0,0 +1,37 @@
https://help.nextcloud.com/t/socket-error-when-accessing-collabora/22486/17
https://collabora-online-for-nextcloud.readthedocs.io/en/latest/install/
https://www.collaboraoffice.com/code/nginx-reverse-proxy/
https://www.digitalocean.com/community/tutorials/understanding-nginx-server-and-location-block-selection-algorithms
https://cloud.kaz.bzh/settings/admin/richdocuments
https://office.kaz.bzh/
docker run -t -d -p 127.0.0.1:9980:9980 -e 'domain=cloud\\.kaz\\.local --restart always --cap-add MKNOD collabora/code
https://stackoverflow.com/questions/31667160/running-docker-container-iptables-no-chain-target-match-by-that-name
*nat
:PREROUTING ACCEPT [144:8072]
:INPUT ACCEPT [87:5208]
:OUTPUT ACCEPT [118:8055]
:POSTROUTING ACCEPT [118:8055]
:DOCKER - [0:0]
... your previous rules here ...
-A PREROUTING -m addrtype --dst-type LOCAL -j DOCKER
-A OUTPUT ! -d 127.0.0.0/8 -m addrtype --dst-type LOCAL -j DOCKER
-A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE
COMMIT
*filter
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [5781:5099614]
:DOCKER - [0:0]
... your previous rules here ...
-A FORWARD -o docker0 -j DOCKER
-A FORWARD -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A FORWARD -i docker0 ! -o docker0 -j ACCEPT
-A FORWARD -i docker0 -o docker0 -j ACCEPT
COMMIT

View File

@ -0,0 +1,40 @@
version: '3.3'
services:
collabora:
image: collabora/code
container_name: ${officeServName}
restart: ${restartPolicy}
cap_add:
- MKNOD
- SYS_CHROOT
- FOWNER
# ports:
# - 8091:9980
env_file:
- ../../secret/env-${officeServName}
environment:
- dictionaries=fr_FR en_GB es_ES
#- domain=${cloudHost}.${domain}
- aliasgroup1=https://.*${cloudHost}.${domain}:443
- server_name=${site}-${officeHost}.${domain}
- VIRTUAL_HOST=${site}-${officeHost}.${domain}
- VIRTUAL_PORT=9980
- VIRTUAL_PROTO=https
- extra_params=--o:ssl.enable=false --o:ssl.termination=true
volumes:
- /etc/localtime:/etc/localtime:ro
- /etc/timezone:/etc/timezone:ro
networks:
collaboraNet:
labels:
- "traefik.enable=true"
- "traefik.http.routers.${officeServName}-admin.rule=Host(`${site}-${officeHost}.${domain}`) && PathPrefix(`/(c|l)ool/adminws`)"
- "traefik.http.routers.${officeServName}-admin.middlewares=test-adminipwhitelist@file"
- "traefik.http.routers.${officeServName}.rule=Host(`${site}-${officeHost}.${domain}`) && ! PathPrefix(`/(c|l)ool/adminws`)"
networks:
collaboraNet:
external: true
name: collaboraNet

1
dockers/dokuwiki/.env Symbolic link
View File

@ -0,0 +1 @@
../../config/dockers.env

View File

@ -0,0 +1,85 @@
FROM --platform=${TARGETPLATFORM:-linux/amd64} crazymax/alpine-s6:3.12
ARG TARGETPLATFORM
ARG BUILDPLATFORM
RUN printf "I am running on ${BUILDPLATFORM:-linux/amd64}, building for ${TARGETPLATFORM:-linux/amd64}\n$(uname -a)\n"
LABEL maintainer="CrazyMax"
########################################
# APT local cache
# work around because COPY failed if no source file
COPY .dummy .apt-mirror-confi[g] .proxy-confi[g] /
RUN cp /.proxy-config /etc/profile.d/proxy.sh 2> /dev/null || true
RUN if [ -f /.apt-mirror-config ] ; then . /.apt-mirror-config && sed -i \
-e "s%s\?://deb.debian.org%://${APT_MIRROR_DEBIAN}%g" \
-e "s%s\?://security.debian.org%://${APT_MIRROR_DEBIAN_SECURITY}%g" \
-e "s%s\?://archive.ubuntu.com%://${APT_MIRROR_UBUNTU}%g" \
-e "s%s\?://security.ubuntu.com%://${APT_MIRROR_UBUNTU_SECURITY}%g" \
/etc/apt/sources.list; fi
########################################
RUN apk --update --no-cache add \
curl \
imagemagick \
inotify-tools \
libgd \
nginx \
php7 \
php7-cli \
php7-ctype \
php7-curl \
php7-fpm \
php7-gd \
php7-imagick \
php7-json \
php7-ldap \
php7-mbstring \
php7-openssl \
php7-pdo \
php7-pdo_sqlite \
php7-session \
php7-simplexml \
php7-sqlite3 \
php7-xml \
php7-zip \
php7-zlib \
shadow \
su-exec \
tar \
tzdata \
&& rm -rf /tmp/* /var/cache/apk/* /var/www/*
ENV S6_BEHAVIOUR_IF_STAGE2_FAILS="2" \
DOKUWIKI_VERSION="2020-07-29" \
DOKUWIKI_MD5="8867b6a5d71ecb5203402fe5e8fa18c9" \
TZ="UTC" \
PUID="1500" \
PGID="1500"
RUN apk --update --no-cache add -t build-dependencies \
gnupg \
wget \
&& cd /tmp \
&& wget -q "https://download.dokuwiki.org/src/dokuwiki/dokuwiki-$DOKUWIKI_VERSION.tgz" \
&& echo "$DOKUWIKI_MD5 /tmp/dokuwiki-$DOKUWIKI_VERSION.tgz" | md5sum -c - | grep OK \
&& tar -xzf "dokuwiki-$DOKUWIKI_VERSION.tgz" --strip 1 -C /var/www \
&& apk del build-dependencies \
&& rm -rf /root/.gnupg /tmp/* /var/cache/apk/*
COPY rootfs /
RUN rm -f /dokuwiki.tgz
COPY htaccess /dokuwiki/.htaccess
RUN chmod a+x /usr/local/bin/* \
&& addgroup -g ${PGID} dokuwiki \
&& adduser -D -H -u ${PUID} -G dokuwiki -s /bin/sh dokuwiki
EXPOSE 8000
WORKDIR /var/www
VOLUME [ "/data" ]
ENTRYPOINT [ "/init" ]
HEALTHCHECK --interval=10s --timeout=5s --start-period=20s \
CMD curl --fail http://127.0.0.1:12345/ping || exit 1

View File

@ -0,0 +1,40 @@
version: '2.1'
services:
dokuwiki:
image: mprasil/dokuwiki
container_name: ${dokuwikiServName}
restart: ${restartPolicy}
# ports:
# - 8087:80
networks:
- dokuwikiNet
- postfixNet
external_links:
- ${smtpServName}:${smtpHost}.${domain}
volumes:
- "dokuwikiData:/dokuwiki/data"
- "dokuwikiConf:/dokuwiki/conf"
- "dokuwikiPlugins:/dokuwiki/lib/plugins"
- "dokuwikiLibtpl:/dokuwiki/lib/tpl"
- "dokuwikiLogs:/var/log"
labels:
- "traefik.enable=true"
- "traefik.http.routers.${dokuwikiServName}.rule=Host(`${dokuwikiHost}.${domain}`)"
- "traefik.docker.network=dokuwikiNet"
volumes:
dokuwikiData:
dokuwikiConf:
dokuwikiPlugins:
dokuwikiLibtpl:
dokuwikiLogs:
networks:
dokuwikiNet:
external: true
name: dokuwikiNet
postfixNet:
external: true
name: postfixNet

19
dockers/dokuwiki/download.sh Executable file
View File

@ -0,0 +1,19 @@
#!/bin/bash
SERV_DIR=$(cd $(dirname $0); pwd)
KAZ_ROOT=$(cd $(dirname $0)/../..; pwd)
. "${KAZ_ROOT}/bin/.commonFunctions.sh"
setKazVars
mkdir -p "${KAZ_DNLD_DIR}/dokuwiki"
cd "${KAZ_DNLD_DIR}/dokuwiki"
printKazMsg "\n *** Download dokuwiki on ${KAZ_DNLD_DIR}"
downloadFile https://github.com/splitbrain/dokuwiki-plugin-captcha/zipball/master captcha.zip
downloadFile https://github.com/turnermm/ckgedit/archive/current.zip ckgedit.zip
downloadFile https://github.com/splitbrain/dokuwiki-plugin-smtp/zipball/master smtp.zip
downloadFile https://github.com/leibler/dokuwiki-plugin-todo/archive/stable.zip todo.zip
downloadFile https://github.com/selfthinker/dokuwiki_plugin_wrap/archive/stable.zip wrap.zip
downloadFile http://github.com/practical-solutions/dokuwiki-plugin-wrapadd/zipball/master wrapadd.zip
downloadFile https://github.com/Vincent31Fr/docnavwiki-template/zipball/master docnavwiki.zip

10
dockers/dokuwiki/first.sh Executable file
View File

@ -0,0 +1,10 @@
#!/bin/bash
KAZ_ROOT=$(cd $(dirname $0)/../..; pwd)
. "${KAZ_ROOT}/bin/.commonFunctions.sh"
setKazVars
cd $(dirname $0)
. "${DOCKERS_ENV}"
"${KAZ_BIN_DIR}/gestContainers.sh" -M -I -wiki

43
dockers/dokuwiki/htaccess Normal file
View File

@ -0,0 +1,43 @@
## You should disable Indexes and MultiViews either here or in the
## global config. Symlinks maybe needed for URL rewriting.
#Options -Indexes -MultiViews +FollowSymLinks
## make sure nobody gets the htaccess, README, COPYING or VERSION files
<Files ~ "^([\._]ht|README$|VERSION$|COPYING$)">
<IfModule mod_authz_core.c>
Require all denied
</IfModule>
<IfModule !mod_authz_core.c>
Order allow,deny
Deny from all
</IfModule>
</Files>
## Don't allow access to git directories
<IfModule alias_module>
RedirectMatch 404 /\.git
</IfModule>
## Uncomment these rules if you want to have nice URLs using
## $conf['userewrite'] = 1 - not needed for rewrite mode 2
RewriteEngine on
RewriteRule ^_media/(.*) lib/exe/fetch.php?media=$1 [QSA,L]
RewriteRule ^_detail/(.*) lib/exe/detail.php?media=$1 [QSA,L]
RewriteRule ^_export/([^/]+)/(.*) doku.php?do=export_$1&id=$2 [QSA,L]
RewriteRule ^$ doku.php [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule (.*) doku.php?id=$1 [QSA,L]
RewriteRule ^index.php$ doku.php
#
## Not all installations will require the following line. If you do,
## change "/dokuwiki" to the path to your dokuwiki directory relative
## to your document root.
#RewriteBase /dokuwiki
#
## If you enable DokuWikis XML-RPC interface, you should consider to
## restrict access to it over HTTPS only! Uncomment the following two
## rules if your server setup allows HTTPS.
#RewriteCond %{HTTPS} !=on
#RewriteRule ^lib/exe/xmlrpc.php$ https://%{SERVER_NAME}%{REQUEST_URI} [L,R=301]

View File

@ -0,0 +1,10 @@
# acl.auth.php
# <?php exit()?>
# Don't modify the lines above
#
# Access Control Lists
#
# Auto-generated by install script
# Date: Sat, 13 Feb 2021 17:42:28 +0000
* @ALL 1
* @user 8

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

View File

@ -0,0 +1,26 @@
<?php
/*
* Dokuwiki's Main Configuration File - Local Settings
* Auto-generated by config plugin
* Run for user: felix
* Date: Sun, 28 Feb 2021 15:56:13 +0000
*/
$conf['title'] = 'Kaz';
$conf['template'] = 'docnavwiki';
$conf['license'] = 'cc-by-sa';
$conf['useacl'] = 1;
$conf['superuser'] = '@admin';
$conf['manager'] = '@manager';
$conf['disableactions'] = 'register';
$conf['remoteuser'] = '';
$conf['mailfrom'] = 'dokuwiki@kaz.bzh';
$conf['updatecheck'] = 0;
$conf['userewrite'] = '1';
$conf['useslash'] = 1;
$conf['plugin']['ckgedit']['scayt_auto'] = 'on';
$conf['plugin']['ckgedit']['scayt_lang'] = 'French/fr_FR';
$conf['plugin']['ckgedit']['other_lang'] = 'fr';
$conf['plugin']['smtp']['smtp_host'] = 'smtp.kaz.bzh';
$conf['plugin']['todo']['CheckboxText'] = 0;
$conf['plugin']['wrap']['restrictionType'] = '1';

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

View File

@ -0,0 +1,13 @@
# users.auth.php
# <?php exit()?>
# Don't modify the lines above
#
# Userfile
#
# Auto-generated by install script
# Date: Sat, 13 Feb 2021 17:42:28 +0000
#
# Format:
# login:passwordhash:Real Name:email:groups,comma,separated
admin:$2y$10$GYvFgViXeEUmDViplHEs7eoYV8tmbfsS8wA1vfHQ.tWgW14o9aTjy:admin:contact@kaz.bzh:admin,user

1
dockers/ethercalc/.env Symbolic link
View File

@ -0,0 +1 @@
../../config/dockers.env

View File

@ -0,0 +1,9 @@
FROM node:4.8
RUN useradd ethercalc --create-home
RUN npm install -g ethercalc pm2 || true
RUN rm -rf /usr/local/lib/node_modules/ethercalc/node_modules/nodemailer/ || true
USER ethercalc
EXPOSE 8000
CMD ["sh", "-c", "REDIS_HOST=$REDIS_PORT_6379_TCP_ADDR REDIS_PORT=$REDIS_PORT_6379_TCP_PORT pm2 start -x `which ethercalc` -- --cors && pm2 logs"]

View File

@ -0,0 +1,39 @@
version: '3.3'
services:
calc:
image: audreyt/ethercalc
container_name: ${ethercalcServName}
# ports:
# - 8082:8000
networks:
- ethercalcNet
env_file:
- ../../secret/env-${ethercalcServName}
links:
- redis:redis
restart: ${restartPolicy}
labels:
- "traefik.enable=true"
- "traefik.http.routers.${ethercalcServName}.rule=Host(`${calcHost}.${domain}`)"
redis:
image: redis
container_name: ${ethercalcDBName}
networks:
- ethercalcNet
volumes:
- calcDB:/data
command: redis-server --appendonly yes
restart: ${restartPolicy}
#on autorise 40% du cpu car sinon les docs mettent un temps fou à charger
cpus: 0.4
volumes:
calcDB:
networks:
ethercalcNet:
external: true
name: ethercalcNet

1
dockers/etherpad/.env Symbolic link
View File

@ -0,0 +1 @@
../../config/dockers.env

View File

@ -0,0 +1,64 @@
version: '3.3'
services:
pad:
image: etherpad/etherpad:1.8.18
container_name: ${etherpadServName}
restart: ${restartPolicy}
depends_on:
wait-for-db:
condition: service_completed_successfully
networks:
- etherpadNet
# - postfixNet
external_links:
- ${etherpadDBName}:padDB
# - ${smtpServName}:${smtpHost}
volumes:
- etherpadPlugins:/opt/etherpad-lite/node_modules/
- ${PWD}/settings.json:/opt/etherpad-lite/settings.json
env_file:
- ../../secret/env-${etherpadServName}
environment:
- "DEFAULT_PAD_TEXT= Ce texte est à effacer (après lecture si cest votre première visite) ou à conserver en bas de votre pad \n\nBienvenue sur notre PAD !\n\n➡ Comment commencer ?\n• Renseignez votre nom ou pseudo, en cliquant sur licône « utilisateur » en haut à droite.\n• Choisissez votre couleur d'écriture au même endroit.\n• Lancez-vous : écrivez sur votre pad !\n• Les contributions de chacun se synchronisent « en temps réel » sous leur propre couleur.\n\n➡ Comment partager / collaborer ?\n• Sélectionnez et copiez l'URL (l'adresse web dans la grande barre en haut à gauche du navigateur)\n• Partagez-là à vos collaborateurs et collaboratrices (email, messagerie, etc.)\n• Attention : toute personne ayant cette adresse d'accès peut modifier le pad à sa convenance.\n• Utilisez l'onglet chat (en bas à droite) pour séparer les discussions du texte sur lequel vous travaillez.\n\n➡ Comment sauvegarder ?\n• Il n'y a rien à faire : le texte est automatiquement sauvegardé, à chaque caractère tapé.\n• Marquez une version (un état du pad) en cliquant sur licône « étoile ».\n• Retrouvez toute l'évolution du pad et vos versions marquées d'une étoile dans lhistorique (icône « horloge »).\n• Importez et exportez votre texte avec l'icône « double flèche » (formats HTML, texte brut) ou avec un copier/coller.\n\nImportant ! Noubliez pas de conserver quelque part ladresse web (URL) de votre pad.\n\nBon travail collaboratif :)\n\n Ce texte est à effacer (après lecture si cest votre première visite) \n\n**ATTENTION**\nCETTE INSTANCE PROPOSE DES PADS À EFFACEMENT AUTOMATIQUE !\n\nVOS PADS SERONT AUTOMATIQUEMENT SUPPRIMÉS AU BOUT DE 62 JOURS (2 MOIS) SANS ÉDITION !\n\nSi le contenu de votre pad bimestriel a été effacé, c'est qu'il n'avait pas été modifié depuis plus de 62 jours consécutifs.\n"
labels:
- "traefik.enable=true"
- "traefik.http.routers.pad-admin.rule=Host(`${padHost}.${domain}`) && PathPrefix(`/admin`)"
- "traefik.http.routers.pad-admin.middlewares=test-adminipwhitelist@file"
- "traefik.http.routers.pad.rule=Host(`${padHost}.${domain}`)"
- "traefik.docker.network=etherpadNet"
padDB:
image: mariadb:10.5
container_name: ${etherpadDBName}
restart: ${restartPolicy}
networks:
- etherpadNet
env_file:
- ../../secret/env-${etherpadDBName}
volumes:
- padDB:/var/lib/mysql
# wait-for-db permet d'attendre le lancement de la DB sans avoir un health_check sur le DB
# le pb du healthcheck sur la DB : ça fait du bruit dans les logs. Si délai court beaucoup de bruit, si délai long le lancement initial est long
wait-for-db:
image: atkrad/wait4x:latest
depends_on:
- padDB
networks:
- etherpadNet
command: tcp padDB:3306 -t 0 -i 1s
volumes:
padDB:
etherpadPlugins:
networks:
etherpadNet:
external: true
name: etherpadNet
# postfixNet:
# external: true
# name: postfixNet

View File

@ -0,0 +1,650 @@
/**
* THIS IS THE SETTINGS FILE THAT IS COPIED INSIDE THE DOCKER CONTAINER.
*
* By default, some runtime customizations are supported (see the
* documentation).
*
* If you need more control, edit this file and rebuild the container.
*/
/*
* This file must be valid JSON. But comments are allowed
*
* Please edit settings.json, not settings.json.template
*
* Please note that starting from Etherpad 1.6.0 you can store DB credentials in
* a separate file (credentials.json).
*
*
* ENVIRONMENT VARIABLE SUBSTITUTION
* =================================
*
* All the configuration values can be read from environment variables using the
* syntax "${ENV_VAR}" or "${ENV_VAR:default_value}".
*
* This is useful, for example, when running in a Docker container.
*
* DETAILED RULES:
* - If the environment variable is set to the string "true" or "false", the
* value becomes Boolean true or false.
* - If the environment variable is set to the string "null", the value
* becomes null.
* - If the environment variable is set to the string "undefined", the setting
* is removed entirely, except when used as the member of an array in which
* case it becomes null.
* - If the environment variable is set to a string representation of a finite
* number, the string is converted to that number.
* - If the environment variable is set to any other string, including the
* empty string, the value is that string.
* - If the environment variable is unset and a default value is provided, the
* value is as if the environment variable was set to the provided default:
* - "${UNSET_VAR:}" becomes the empty string.
* - "${UNSET_VAR:foo}" becomes the string "foo".
* - "${UNSET_VAR:true}" and "${UNSET_VAR:false}" become true and false.
* - "${UNSET_VAR:null}" becomes null.
* - "${UNSET_VAR:undefined}" causes the setting to be removed (or be set
* to null, if used as a member of an array).
* - If the environment variable is unset and no default value is provided,
* the value becomes null. THIS BEHAVIOR MAY CHANGE IN A FUTURE VERSION OF
* ETHERPAD; if you want the default value to be null, you should explicitly
* specify "null" as the default value.
*
* EXAMPLE:
* "port": "${PORT:9001}"
* "minify": "${MINIFY}"
* "skinName": "${SKIN_NAME:colibris}"
*
* Would read the configuration values for those items from the environment
* variables PORT, MINIFY and SKIN_NAME.
*
* If PORT and SKIN_NAME variables were not defined, the default values 9001 and
* "colibris" would be used.
* The configuration value "minify", on the other hand, does not have a
* designated default value. Thus, if the environment variable MINIFY were
* undefined, "minify" would be null.
*
* REMARKS:
* 1) please note that variable substitution always needs to be quoted.
*
* "port": 9001, <-- Literal values. When not using
* "minify": false substitution, only strings must be
* "skinName": "colibris" quoted. Booleans and numbers must not.
*
* "port": "${PORT:9001}" <-- CORRECT: if you want to use a variable
* "minify": "${MINIFY:true}" substitution, put quotes around its name,
* "skinName": "${SKIN_NAME}" even if the required value is a number or
* a boolean.
* Etherpad will take care of rewriting it
* to the proper type if necessary.
*
* "port": ${PORT:9001} <-- ERROR: this is not valid json. Quotes
* "minify": ${MINIFY} around variable names are missing.
* "skinName": ${SKIN_NAME}
*
* 2) Beware of undefined variables and default values: nulls and empty strings
* are different!
*
* This is particularly important for user's passwords (see the relevant
* section):
*
* "password": "${PASSW}" // if PASSW is not defined would result in password === null
* "password": "${PASSW:}" // if PASSW is not defined would result in password === ''
*
* If you want to use an empty value (null) as default value for a variable,
* simply do not set it, without putting any colons: "${ABIWORD}".
*
* 3) if you want to use newlines in the default value of a string parameter,
* use "\n" as usual.
*
* "defaultPadText" : "${DEFAULT_PAD_TEXT}Line 1\nLine 2"
*/
{
/*
* Name your instance!
*/
"title": "${TITLE:Etherpad}",
/*
* Pathname of the favicon you want to use. If null, the skin's favicon is
* used if one is provided by the skin, otherwise the default Etherpad favicon
* is used. If this is a relative path it is interpreted as relative to the
* Etherpad root directory.
*/
"favicon": "${FAVICON:null}",
/*
* Skin name.
*
* Its value has to be an existing directory under src/static/skins.
* You can write your own, or use one of the included ones:
*
* - "no-skin": an empty skin (default). This yields the unmodified,
* traditional Etherpad theme.
* - "colibris": the new experimental skin (since Etherpad 1.8), candidate to
* become the default in Etherpad 2.0
*/
"skinName": "${SKIN_NAME:colibris}",
/*
* Skin Variants
*
* Use the UI skin variants builder at /p/test#skinvariantsbuilder
*
* For the colibris skin only, you can choose how to render the three main
* containers:
* - toolbar (top menu with icons)
* - editor (containing the text of the pad)
* - background (area outside of editor, mostly visible when using page style)
*
* For each of the 3 containers you can choose 4 color combinations:
* super-light, light, dark, super-dark.
*
* For example, to make the toolbar dark, you will include "dark-toolbar" into
* skinVariants.
*
* You can provide multiple skin variants separated by spaces. Default
* skinVariant is "super-light-toolbar super-light-editor light-background".
*
* For the editor container, you can also make it full width by adding
* "full-width-editor" variant (by default editor is rendered as a page, with
* a max-width of 900px).
*/
"skinVariants": "${SKIN_VARIANTS:super-light-toolbar super-light-editor light-background}",
/*
* IP and port which Etherpad should bind at.
*
* Binding to a Unix socket is also supported: just use an empty string for
* the ip, and put the full path to the socket in the port parameter.
*
* EXAMPLE USING UNIX SOCKET:
* "ip": "", // <-- has to be an empty string
* "port" : "/somepath/etherpad.socket", // <-- path to a Unix socket
*/
"ip": "${IP:0.0.0.0}",
"port": "${PORT:9001}",
/*
* Option to hide/show the settings.json in admin page.
*
* Default option is set to true
*/
"showSettingsInAdminPage": "${SHOW_SETTINGS_IN_ADMIN_PAGE:true}",
/*
* Node native SSL support
*
* This is disabled by default.
* Make sure to have the minimum and correct file access permissions set so
* that the Etherpad server can access them
*/
/*
"ssl" : {
"key" : "/path-to-your/epl-server.key",
"cert" : "/path-to-your/epl-server.crt",
"ca": ["/path-to-your/epl-intermediate-cert1.crt", "/path-to-your/epl-intermediate-cert2.crt"]
},
*/
/*
* The type of the database.
*
* You can choose between many DB drivers, for example: dirty, postgres,
* sqlite, mysql.
*
* You shouldn't use "dirty" for for anything else than testing or
* development.
*
*
* Database specific settings are dependent on dbType, and go in dbSettings.
* Remember that since Etherpad 1.6.0 you can also store this information in
* credentials.json.
*
* For a complete list of the supported drivers, please refer to:
* https://www.npmjs.com/package/ueberdb2
*/
"dbType": "${DB_TYPE:dirty}",
"dbSettings": {
"host": "${DB_HOST:undefined}",
"port": "${DB_PORT:undefined}",
"database": "${DB_NAME:undefined}",
"user": "${DB_USER:undefined}",
"password": "${DB_PASS:undefined}",
"charset": "${DB_CHARSET:undefined}",
"filename": "${DB_FILENAME:var/dirty.db}"
},
/*
* The default text of a pad
*/
"defaultPadText" : "${DEFAULT_PAD_TEXT:Welcome to Etherpad!\n\nThis pad text is synchronized as you type, so that everyone viewing this page sees the same text. This allows you to collaborate seamlessly on documents!\n\nGet involved with Etherpad at https:\/\/etherpad.org\n}",
/*
* Default Pad behavior.
*
* Change them if you want to override.
*/
"padOptions": {
"noColors": "${PAD_OPTIONS_NO_COLORS:false}",
"showControls": "${PAD_OPTIONS_SHOW_CONTROLS:true}",
"showChat": "${PAD_OPTIONS_SHOW_CHAT:true}",
"showLineNumbers": "${PAD_OPTIONS_SHOW_LINE_NUMBERS:true}",
"useMonospaceFont": "${PAD_OPTIONS_USE_MONOSPACE_FONT:false}",
"userName": "${PAD_OPTIONS_USER_NAME:false}",
"userColor": "${PAD_OPTIONS_USER_COLOR:false}",
"rtl": "${PAD_OPTIONS_RTL:false}",
"alwaysShowChat": "${PAD_OPTIONS_ALWAYS_SHOW_CHAT:false}",
"chatAndUsers": "${PAD_OPTIONS_CHAT_AND_USERS:false}",
"lang": "${PAD_OPTIONS_LANG:en-gb}"
},
/*
* Pad Shortcut Keys
*/
"padShortcutEnabled" : {
"altF9": "${PAD_SHORTCUTS_ENABLED_ALT_F9:true}", /* focus on the File Menu and/or editbar */
"altC": "${PAD_SHORTCUTS_ENABLED_ALT_C:true}", /* focus on the Chat window */
"cmdShift2": "${PAD_SHORTCUTS_ENABLED_CMD_SHIFT_2:true}", /* shows a gritter popup showing a line author */
"delete": "${PAD_SHORTCUTS_ENABLED_DELETE:true}",
"return": "${PAD_SHORTCUTS_ENABLED_RETURN:true}",
"esc": "${PAD_SHORTCUTS_ENABLED_ESC:true}", /* in mozilla versions 14-19 avoid reconnecting pad */
"cmdS": "${PAD_SHORTCUTS_ENABLED_CMD_S:true}", /* save a revision */
"tab": "${PAD_SHORTCUTS_ENABLED_TAB:true}", /* indent */
"cmdZ": "${PAD_SHORTCUTS_ENABLED_CMD_Z:true}", /* undo/redo */
"cmdY": "${PAD_SHORTCUTS_ENABLED_CMD_Y:true}", /* redo */
"cmdI": "${PAD_SHORTCUTS_ENABLED_CMD_I:true}", /* italic */
"cmdB": "${PAD_SHORTCUTS_ENABLED_CMD_B:true}", /* bold */
"cmdU": "${PAD_SHORTCUTS_ENABLED_CMD_U:true}", /* underline */
"cmd5": "${PAD_SHORTCUTS_ENABLED_CMD_5:true}", /* strike through */
"cmdShiftL": "${PAD_SHORTCUTS_ENABLED_CMD_SHIFT_L:true}", /* unordered list */
"cmdShiftN": "${PAD_SHORTCUTS_ENABLED_CMD_SHIFT_N:true}", /* ordered list */
"cmdShift1": "${PAD_SHORTCUTS_ENABLED_CMD_SHIFT_1:true}", /* ordered list */
"cmdShiftC": "${PAD_SHORTCUTS_ENABLED_CMD_SHIFT_C:true}", /* clear authorship */
"cmdH": "${PAD_SHORTCUTS_ENABLED_CMD_H:true}", /* backspace */
"ctrlHome": "${PAD_SHORTCUTS_ENABLED_CTRL_HOME:true}", /* scroll to top of pad */
"pageUp": "${PAD_SHORTCUTS_ENABLED_PAGE_UP:true}",
"pageDown": "${PAD_SHORTCUTS_ENABLED_PAGE_DOWN:true}"
},
/*
* Should we suppress errors from being visible in the default Pad Text?
*/
"suppressErrorsInPadText": "${SUPPRESS_ERRORS_IN_PAD_TEXT:false}",
/*
* If this option is enabled, a user must have a session to access pads.
* This effectively allows only group pads to be accessed.
*/
"requireSession": "${REQUIRE_SESSION:false}",
/*
* Users may edit pads but not create new ones.
*
* Pad creation is only via the API.
* This applies both to group pads and regular pads.
*/
"editOnly": "${EDIT_ONLY:false}",
/*
* If true, all css & js will be minified before sending to the client.
*
* This will improve the loading performance massively, but makes it difficult
* to debug the javascript/css
*/
"minify": "${MINIFY:true}",
/*
* How long may clients use served javascript code (in seconds)?
*
* Not setting this may cause problems during deployment.
* Set to 0 to disable caching.
*/
"maxAge": "${MAX_AGE:21600}", // 60 * 60 * 6 = 6 hours
/*
* Absolute path to the Abiword executable.
*
* Abiword is needed to get advanced import/export features of pads. Setting
* it to null disables Abiword and will only allow plain text and HTML
* import/exports.
*/
"abiword": "${ABIWORD:null}",
/*
* This is the absolute path to the soffice executable.
*
* LibreOffice can be used in lieu of Abiword to export pads.
* Setting it to null disables LibreOffice exporting.
*/
"soffice": "${SOFFICE:null}",
/*
* Path to the Tidy executable.
*
* Tidy is used to improve the quality of exported pads.
* Setting it to null disables Tidy.
*/
"tidyHtml": "${TIDY_HTML:null}",
/*
* Allow import of file types other than the supported ones:
* txt, doc, docx, rtf, odt, html & htm
*/
"allowUnknownFileEnds": "${ALLOW_UNKNOWN_FILE_ENDS:true}",
/*
* This setting is used if you require authentication of all users.
*
* Note: "/admin" always requires authentication.
*/
"requireAuthentication": "${REQUIRE_AUTHENTICATION:false}",
/*
* Require authorization by a module, or a user with is_admin set, see below.
*/
"requireAuthorization": "${REQUIRE_AUTHORIZATION:false}",
/*
* When you use NGINX or another proxy/load-balancer set this to true.
*
* This is especially necessary when the reverse proxy performs SSL
* termination, otherwise the cookies will not have the "secure" flag.
*
* The other effect will be that the logs will contain the real client's IP,
* instead of the reverse proxy's IP.
*/
"trustProxy": "${TRUST_PROXY:false}",
/*
* Settings controlling the session cookie issued by Etherpad.
*/
"cookie": {
/*
* Value of the SameSite cookie property. "Lax" is recommended unless
* Etherpad will be embedded in an iframe from another site, in which case
* this must be set to "None". Note: "None" will not work (the browser will
* not send the cookie to Etherpad) unless https is used to access Etherpad
* (either directly or via a reverse proxy with "trustProxy" set to true).
*
* "Strict" is not recommended because it has few security benefits but
* significant usability drawbacks vs. "Lax". See
* https://stackoverflow.com/q/41841880 for discussion.
*/
"sameSite": "${COOKIE_SAME_SITE:Lax}"
},
/*
* Privacy: disable IP logging
*/
"disableIPlogging": "${DISABLE_IP_LOGGING:false}",
/*
* Time (in seconds) to automatically reconnect pad when a "Force reconnect"
* message is shown to user.
*
* Set to 0 to disable automatic reconnection.
*/
"automaticReconnectionTimeout": "${AUTOMATIC_RECONNECTION_TIMEOUT:0}",
/*
* By default, when caret is moved out of viewport, it scrolls the minimum
* height needed to make this line visible.
*/
"scrollWhenFocusLineIsOutOfViewport": {
/*
* Percentage of viewport height to be additionally scrolled.
*
* E.g.: use "percentage.editionAboveViewport": 0.5, to place caret line in
* the middle of viewport, when user edits a line above of the
* viewport
*
* Set to 0 to disable extra scrolling
*/
"percentage": {
"editionAboveViewport": "${FOCUS_LINE_PERCENTAGE_ABOVE:0}",
"editionBelowViewport": "${FOCUS_LINE_PERCENTAGE_BELOW:0}"
},
/*
* Time (in milliseconds) used to animate the scroll transition.
* Set to 0 to disable animation
*/
"duration": "${FOCUS_LINE_DURATION:0}",
/*
* Flag to control if it should scroll when user places the caret in the
* last line of the viewport
*/
"scrollWhenCaretIsInTheLastLineOfViewport": "${FOCUS_LINE_CARET_SCROLL:false}",
/*
* Percentage of viewport height to be additionally scrolled when user
* presses arrow up in the line of the top of the viewport.
*
* Set to 0 to let the scroll to be handled as default by Etherpad
*/
"percentageToScrollWhenUserPressesArrowUp": "${FOCUS_LINE_PERCENTAGE_ARROW_UP:0}"
},
/*
* User accounts. These accounts are used by:
* - default HTTP basic authentication if no plugin handles authentication
* - some but not all authentication plugins
* - some but not all authorization plugins
*
* User properties:
* - password: The user's password. Some authentication plugins will ignore
* this.
* - is_admin: true gives access to /admin. Defaults to false. If you do not
* uncomment this, /admin will not be available!
* - readOnly: If true, this user will not be able to create new pads or
* modify existing pads. Defaults to false.
* - canCreate: If this is true and readOnly is false, this user can create
* new pads. Defaults to true.
*
* Authentication and authorization plugins may define additional properties.
*
* WARNING: passwords should not be stored in plaintext in this file.
* If you want to mitigate this, please install ep_hash_auth and
* follow the section "secure your installation" in README.md
*/
"users": {
"admin": {
// 1) "password" can be replaced with "hash" if you install ep_hash_auth
// 2) please note that if password is null, the user will not be created
"password": "${ADMIN_PASSWORD:null}",
"is_admin": true
},
"user": {
// 1) "password" can be replaced with "hash" if you install ep_hash_auth
// 2) please note that if password is null, the user will not be created
"password": "${USER_PASSWORD:null}",
"is_admin": false
}
},
/*
* Restrict socket.io transport methods
*/
"socketTransportProtocols" : ["xhr-polling", "jsonp-polling", "htmlfile"],
"socketIo": {
/*
* Maximum permitted client message size (in bytes). All messages from
* clients that are larger than this will be rejected. Large values make it
* possible to paste large amounts of text, and plugins may require a larger
* value to work properly, but increasing the value increases susceptibility
* to denial of service attacks (malicious clients can exhaust memory).
*/
"maxHttpBufferSize": 10000
},
/*
* Allow Load Testing tools to hit the Etherpad Instance.
*
* WARNING: this will disable security on the instance.
*/
"loadTest": "${LOAD_TEST:false}",
/**
* Disable dump of objects preventing a clean exit
*/
"dumpOnUncleanExit": false,
/*
* Disable indentation on new line when previous line ends with some special
* chars (':', '[', '(', '{')
*/
/*
"indentationOnNewLine": false,
*/
/*
* From Etherpad 1.8.3 onwards, import and export of pads is always rate
* limited.
*
* The default is to allow at most 10 requests per IP in a 90 seconds window.
* After that the import/export request is rejected.
*
* See https://github.com/nfriedly/express-rate-limit for more options
*/
"importExportRateLimiting": {
// duration of the rate limit window (milliseconds)
"windowMs": "${IMPORT_EXPORT_RATE_LIMIT_WINDOW:90000}",
// maximum number of requests per IP to allow during the rate limit window
"max": "${IMPORT_EXPORT_MAX_REQ_PER_IP:10}"
},
/*
* From Etherpad 1.8.3 onwards, the maximum allowed size for a single imported
* file is always bounded.
*
* File size is specified in bytes. Default is 50 MB.
*/
"importMaxFileSize": "${IMPORT_MAX_FILE_SIZE:52428800}", // 50 * 1024 * 1024
/*
* From Etherpad 1.8.5 onwards, when Etherpad is in production mode commits from individual users are rate limited
*
* The default is to allow at most 10 changes per IP in a 1 second window.
* After that the change is rejected.
*
* See https://github.com/animir/node-rate-limiter-flexible/wiki/Overall-example#websocket-single-connection-prevent-flooding for more options
*/
"commitRateLimiting": {
// duration of the rate limit window (seconds)
"duration": "${COMMIT_RATE_LIMIT_DURATION:1}",
// maximum number of changes per IP to allow during the rate limit window
"points": "${COMMIT_RATE_LIMIT_POINTS:10}"
},
/*
* Toolbar buttons configuration.
*
* Uncomment to customize.
*/
/*
"toolbar": {
"left": [
["bold", "italic", "underline", "strikethrough"],
["orderedlist", "unorderedlist", "indent", "outdent"],
["undo", "redo"],
["clearauthorship"]
],
"right": [
["importexport", "timeslider", "savedrevision"],
["settings", "embed"],
["showusers"]
],
"timeslider": [
["timeslider_export", "timeslider_returnToPad"]
]
},
*/
/*
* Expose Etherpad version in the web interface and in the Server http header.
*
* Do not enable on production machines.
*/
"exposeVersion": "${EXPOSE_VERSION:false}",
/*
* The log level we are using.
*
* Valid values: DEBUG, INFO, WARN, ERROR
*/
"loglevel": "${LOGLEVEL:INFO}",
/*
* Logging configuration. See log4js documentation for further information:
* https://github.com/nomiddlename/log4js-node
*
* You can add as many appenders as you want here.
*/
"logconfig" :
{ "appenders": [
{ "type": "console"
//, "category": "access"// only logs pad access
}
/*
, { "type": "file"
, "filename": "your-log-file-here.log"
, "maxLogSize": 1024
, "backups": 3 // how many log files there're gonna be at max
//, "category": "test" // only log a specific category
}
*/
/*
, { "type": "logLevelFilter"
, "level": "warn" // filters out all log messages that have a lower level than "error"
, "appender":
{ Use whatever appender you want here }
}
*/
/*
, { "type": "logLevelFilter"
, "level": "error" // filters out all log messages that have a lower level than "error"
, "appender":
{ "type": "smtp"
, "subject": "An error occurred in your EPL instance!"
, "recipients": "bar@blurdybloop.com, baz@blurdybloop.com"
, "sendInterval": 300 // 60 * 5 = 5 minutes -- will buffer log messages; set to 0 to send a mail for every message
, "transport": "SMTP", "SMTP": { // see https://github.com/andris9/Nodemailer#possible-transport-methods
"host": "smtp.example.com", "port": 465,
"secureConnection": true,
"auth": {
"user": "foo@example.com",
"pass": "bar_foo"
}
}
}
}
*/
]
}, // logconfig
/* Override any strings found in locale directories */
"customLocaleStrings": {},
"ep_delete_after_delay": {
"delay": 32000000, // 1 ans et des poussieères, in seconds
"loop": true,
"loopDelay": 86400, // one day, in seconds
"deleteAtStart": true,
"text": "Le contenu de ce pad a été effacé car il n'a pas été modifié depuis plus d'un an."
}
}

1
dockers/framadate/.env Symbolic link
View File

@ -0,0 +1 @@
../../config/dockers.env

View File

@ -0,0 +1,67 @@
FROM php:7.4-apache
########################################
# APT local cache
# work around because COPY failed if no source file
COPY .dummy .apt-mirror-confi[g] .proxy-confi[g] /
RUN cp /.proxy-config /etc/profile.d/proxy.sh 2> /dev/null || true
RUN if [ -f /.apt-mirror-config ] ; then . /.apt-mirror-config && sed -i \
-e "s%s\?://deb.debian.org%://${APT_MIRROR_DEBIAN}%g" \
-e "s%s\?://security.debian.org%://${APT_MIRROR_DEBIAN_SECURITY}%g" \
-e "s%s\?://archive.ubuntu.com%://${APT_MIRROR_UBUNTU}%g" \
-e "s%s\?://security.ubuntu.com%://${APT_MIRROR_UBUNTU_SECURITY}%g" \
/etc/apt/sources.list; fi
########################################
RUN apt-get update --quiet && apt-get install -y \
git wget zip patch \
libicu-dev libpq-dev zlib1g-dev libicu-dev
RUN apt-get install -y locales locales-all
RUN sed -i '/fr_FR.UTF-8/s/^# //g' /etc/locale.gen && locale-gen
ENV LC_ALL fr_FR.UTF-8
ENV LANG fr_FR.UTF-8
ENV LANGUAGE fr_FR:fr
RUN update-locale LANG=fr_FR.UTF-8
# install framadate
RUN mkdir /var/framadate
WORKDIR /var/framadate
COPY --chown=www-data git/framadate/ .
RUN cp php.ini /usr/local/etc/php/
# patch bad-e-mail
COPY dockers/framadate/patch/*.patch ./
RUN patch adminstuds.php adminstuds.php.patch
RUN patch create_classic_poll.php create_classic_poll.php.patch
RUN patch create_date_poll.php create_date_poll.php.patch
RUN patch find_polls.php find_polls.php.patch
RUN patch locale/en.json en.json.patch
RUN patch locale/fr.json fr.json.patch
# install composer setup script
COPY dockers/framadate/composer-setup.sh /usr/local/bin/
COPY dockers/framadate/kazdate.png /var/framadate/images/logo-framadate.png
COPY dockers/framadate/kazclassic.png /var/framadate/images/classic.png
COPY dockers/framadate/kazdates.png /var/framadate/images/date.png
RUN chmod +x /usr/local/bin/composer-setup.sh
# install internationalization libs
RUN docker-php-ext-configure intl
RUN docker-php-ext-install intl
RUN docker-php-ext-install -j$(nproc) pdo pdo_mysql
RUN /usr/local/bin/composer-setup.sh
RUN php composer.phar install
# patch : Kaz don't use TLS
RUN sed -e 's%$tls = true;%//XXX Kaz not use TLS // $tls = true;%' -i vendor/phpmailer/phpmailer/src/PHPMailer.php
RUN chown -R www-data.www-data /var/framadate/
RUN a2enmod rewrite
RUN cp htaccess.txt .htaccess
RUN mkdir /svg
VOLUME ["/var/framadate/app/inc", "/etc/apache2/sites-available/000-default"]
EXPOSE 80

9
dockers/framadate/build.sh Executable file
View File

@ -0,0 +1,9 @@
#!/bin/bash
KAZ_ROOT=$(cd $(dirname $0)/../..; pwd)
. "${KAZ_ROOT}/bin/.commonFunctions.sh"
setKazVars
printKazMsg "\n *** Création du Dockerfile Framadate"
cd "${KAZ_ROOT}"
docker build -t datekaz . -f dockers/framadate/Dockerfile

View File

@ -0,0 +1,18 @@
#!/bin/sh
EXPECTED_SIGNATURE=$(wget -q -O - https://composer.github.io/installer.sig)
php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
ACTUAL_SIGNATURE=$(php -r "echo hash_file('SHA384', 'composer-setup.php');")
if [ "$EXPECTED_SIGNATURE" != "$ACTUAL_SIGNATURE" ]
then
>&2 echo 'ERROR: Invalid installer signature'
rm composer-setup.php
exit 1
fi
php composer-setup.php --quiet
RESULT=$?
rm composer-setup.php
exit $RESULT

View File

@ -0,0 +1,31 @@
<VirtualHost *:80>
ServerName date.kaz.bzh
DocumentRoot /var/framadate/
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined
ServerSignature Off
<Location /admin/ >
AuthType Basic
AuthName "Administration"
AuthUserFile "/var/framadate/admin/.htpasswd"
Require valid-user
</Location>
<Directory /var/framadate/ >
AllowOverride All
Require all granted
Options FollowSymLinks MultiViews
<IfModule mod_dav.c>
Dav off
</IfModule>
</Directory>
<FilesMatch "^\.ht.*">
deny from all
satisfy all
ErrorDocument 403 "Access denied."
</FilesMatch>
</VirtualHost>

View File

@ -0,0 +1,58 @@
version: '3.3'
services:
framadate:
# ports:
# - 8088:80
image: datekaz
container_name: ${framadateServName}
restart: ${restartPolicy}
depends_on:
- db
networks:
- framadateNet
- postfixNet
external_links:
- ${framadateDBName}:db
- ${smtpServName}:${smtpHost}
volumes:
- ./config/framadate.conf:/etc/apache2/sites-available/000-default.conf
- dateAdmin:/var/framadate/admin
- dateConfig:/var/framadate/app/inc
- /etc/localtime:/etc/localtime:ro
- /etc/timezone:/etc/timezone:ro
labels:
- "traefik.enable=true"
- "traefik.http.routers.${framadateServName}-admin.rule=Host(`${dateHost}.${domain}`) && PathPrefix(`/admin`)"
- "traefik.http.routers.${framadateServName}-admin.middlewares=test-adminipwhitelist@file"
- "traefik.http.routers.${framadateServName}.rule=Host(`${dateHost}.${domain}`)"
- "traefik.docker.network=framadateNet"
db:
image: mariadb:10.5
container_name: ${framadateDBName}
restart: ${restartPolicy}
networks:
- framadateNet
env_file:
- ../../secret/env-${framadateDBName}
volumes:
- dateDB:/var/lib/mysql
- /home/sauve/:/svg/
- /etc/localtime:/etc/localtime:ro
- /etc/timezone:/etc/timezone:ro
volumes:
dateDB:
dateConfig:
dateAdmin:
dateLocale:
networks:
framadateNet:
external: true
name: framadateNet
postfixNet:
external: true
name: postfixNet

24
dockers/framadate/download.sh Executable file
View File

@ -0,0 +1,24 @@
#!/bin/bash
SERV_DIR=$(cd $(dirname $0); pwd)
KAZ_ROOT=$(cd $(dirname $0)/../..; pwd)
. "${KAZ_ROOT}/bin/.commonFunctions.sh"
setKazVars
SRC_FRAMADATE="https://github.com/framasoft/framadate.git"
FRAMDATE_VER="1.1.19"
printKazMsg "\n *** Download framadate"
mkdir -p "${KAZ_GIT_DIR}"
cd "${KAZ_GIT_DIR}"
if [ ! -d "framadate" ]; then
git clone "${SRC_FRAMADATE}" --branch ${FRAMDATE_VER}
fi
cd "${KAZ_GIT_DIR}/framadate"
if [ -z "$(git branch | grep "${FRAMDATE_VER}")" ]; then
printKazMsg " checkout branch ${FRAMDATE_VER}"
git reset --hard
git checkout ${FRAMADATE_VER}
fi

44
dockers/framadate/first.sh Executable file
View File

@ -0,0 +1,44 @@
#!/bin/bash
KAZ_ROOT=$(cd $(dirname $0)/../..; pwd)
. "${KAZ_ROOT}/bin/.commonFunctions.sh"
setKazVars
cd $(dirname $0)
. "${DOCKERS_ENV}"
. "${KAZ_KEY_DIR}/env-${framadateServName}"
. "${KAZ_KEY_DIR}/env-${framadateDBName}"
FRAMADATE_URL="${httpProto}://${dateHost}.${domain}"
checkDockerRunning "${framadateServName}" "Framadate" || exit
if [ ! -f "${DOCK_LIB}/volumes/framadate_dateConfig/_data/config.php" ]; then
printKazMsg "\n *** Premier lancement de Framadate"
waitUrl "${FRAMADATE_URL}"
${SIMU} docker exec "${framadateServName}" bash -c -i "htpasswd -bc /var/framadate/admin/.htpasswd ${HTTPD_USER} ${HTTPD_PASSWORD}"
${SIMU} docker exec "${framadateServName}" bash -c -i "chown www-data: /var/framadate/.htaccess /var/framadate/admin/.htpasswd"
curl -X POST \
-u "${HTTPD_USER}:${HTTPD_PASSWORD}" \
-d "appMail=framadate@kaz.bzh" \
-d "responseMail=no-reply@kaz.bzh" \
-d "defaultLanguage=fr" \
-d "cleanUrl=on" \
-d "dbConnectionString=mysql:host=db;dbname=${MYSQL_DATABASE};port=3306" \
-d "dbUser=${MYSQL_USER}" \
-d "dbPassword=${MYSQL_PASSWORD}" \
-d "dbPrefix=fd_" \
-d "migrationTable=framadate_migration" \
"${FRAMADATE_URL}/admin/install.php"
curl -X POST \
-u "${HTTPD_USER}:${HTTPD_PASSWORD}" \
"${FRAMADATE_URL}/admin/migration.php"
sed -e "s/'host'\s*=>\s*'[^']*',/'host' => 'smtp',/" \
-e "s/const\s*NOMAPPLICATION\s*=\s*'[^']*';/const NOMAPPLICATION = 'Sondage';/" \
-i "${DOCK_LIB}/volumes/framadate_dateConfig/_data/config.php"
fi

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

View File

@ -0,0 +1,15 @@
--- adminstuds.php 2022-02-05 09:09:45.256310389 +0100
+++ adminstuds.php-new 2022-02-05 08:49:40.952269857 +0100
@@ -84,6 +84,12 @@
$message = new Message('success', __('adminstuds', 'The poll is created.'));
}
+$messageBadEmail = $sessionService->get("Framadate", "messageBadEmail", FALSE);
+if ($messageBadEmail) {
+ $sessionService->remove("Framadate", "messageBadEmail");
+ $message = new Message('danger', __('adminstuds', "Bad e-mail. Can't send links"));
+}
+
// -------------------------------
// Update poll info
// -------------------------------

View File

@ -0,0 +1,17 @@
--- create_classic_poll.php 2022-02-05 09:09:45.256310389 +0100
+++ create_classic_poll.php-new 2022-02-05 09:03:08.924297050 +0100
@@ -83,8 +83,12 @@
$message_admin .= sprintf(' :<br/><br/><a href="%1$s">%1$s</a>', Utils::getUrlSondage($admin_poll_id, true));
if ($mailService->isValidEmail($form->admin_mail)) {
- $mailService->send($form->admin_mail, '[' . NOMAPPLICATION . '][' . __('Mail', 'Author\'s message') . '] ' . __('Generic', 'Poll') . ': ' . Utils::htmlEscape($form->title), $message_admin);
- $mailService->send($form->admin_mail, '[' . NOMAPPLICATION . '][' . __('Mail', 'For sending to the polled users') . '] ' . __('Generic', 'Poll') . ': ' . Utils::htmlEscape($form->title), $message);
+ try {
+ $mailService->send($form->admin_mail, '[' . NOMAPPLICATION . '][' . __('Mail', 'Author\'s message') . '] ' . __('Generic', 'Poll') . ': ' . Utils::htmlEscape($form->title), $message_admin);
+ $mailService->send($form->admin_mail, '[' . NOMAPPLICATION . '][' . __('Mail', 'For sending to the polled users') . '] ' . __('Generic', 'Poll') . ': ' . Utils::htmlEscape($form->title), $message);
+ } catch (Exception $e) {
+ $sessionService->set("Framadate", "messageBadEmail", TRUE);
+ }
}
}

View File

@ -0,0 +1,17 @@
--- create_date_poll.php 2022-02-05 09:09:45.256310389 +0100
+++ create_date_poll.php-new 2022-02-05 08:47:40.708265810 +0100
@@ -210,8 +210,12 @@
$message_admin = sprintf($message_admin, Utils::getUrlSondage($admin_poll_id, true));
if ($mailService->isValidEmail($form->admin_mail)) {
- $mailService->send($form->admin_mail, '[' . NOMAPPLICATION . '][' . __('Mail', 'Author\'s message') . '] ' . __('Generic', 'Poll') . ': ' . Utils::htmlEscape($form->title), $message_admin);
- $mailService->send($form->admin_mail, '[' . NOMAPPLICATION . '][' . __('Mail', 'For sending to the polled users') . '] ' . __('Generic', 'Poll') . ': ' . Utils::htmlEscape($form->title), $message);
+ try {
+ $mailService->send($form->admin_mail, '[' . NOMAPPLICATION . '][' . __('Mail', 'Author\'s message') . '] ' . __('Generic', 'Poll') . ': ' . Utils::htmlEscape($form->title), $message_admin);
+ $mailService->send($form->admin_mail, '[' . NOMAPPLICATION . '][' . __('Mail', 'For sending to the polled users') . '] ' . __('Generic', 'Poll') . ': ' . Utils::htmlEscape($form->title), $message);
+ } catch (Exception $e) {
+ $sessionService->set("Framadate", "messageBadEmail", TRUE);
+ }
}
}

View File

@ -0,0 +1,10 @@
--- locale/en.json 2022-02-05 09:09:45.256310389 +0100
+++ locale/en.json-new 2022-02-05 08:52:37.688275805 +0100
@@ -439,6 +439,7 @@
"Remove the comments": "Remove the comments",
"Remove the votes": "Remove the votes",
"The poll is created.": "The poll was created.",
+ "Bad e-mail. Can't send links": "Bad e-mail. Can't send links",
"Vote added": "Vote added",
"Vote deleted": "Vote deleted",
"Vote updated": "Vote updated",

View File

@ -0,0 +1,17 @@
--- find_polls.php 2022-02-05 09:09:45.256310389 +0100
+++ find_polls.php-new 2022-02-05 09:07:28.072305772 +0100
@@ -43,8 +43,12 @@
$smarty->assign('polls', $polls);
$body = $smarty->fetch('mail/find_polls.tpl');
- $mailService->send($mail, __('FindPolls', 'List of your polls') . ' - ' . NOMAPPLICATION, $body, 'SEND_POLLS');
- $message = new Message('success', __('FindPolls', 'Polls sent'));
+ try {
+ $mailService->send($mail, __('FindPolls', 'List of your polls') . ' - ' . NOMAPPLICATION, $body, 'SEND_POLLS');
+ $message = new Message('success', __('FindPolls', 'Polls sent'));
+ } catch (Exception $e) {
+ $message = new Message('warning', __('Error', 'No polls found'));
+ }
} else {
$message = new Message('warning', __('Error', 'No polls found'));
}

View File

@ -0,0 +1,10 @@
--- locale/fr.json 2022-02-05 09:09:45.256310389 +0100
+++ locale/fr.json-new 2022-02-05 08:57:14.468285120 +0100
@@ -435,6 +435,7 @@
"Remove the comments": "Supprimer les commentaires",
"Remove the votes": "Supprimer les votes",
"The poll is created.": "Le sondage a été créé.",
+ "Bad e-mail. Can't send links": "Mauvais mèl. Les liens n'ont pu être envoyés.",
"Vote added": "Vote ajouté",
"Vote deleted": "Vote supprimé",
"Vote updated": "Vote mis à jour",

1
dockers/gitea/.env Symbolic link
View File

@ -0,0 +1 @@
../../config/dockers.env

View File

@ -0,0 +1,53 @@
version: '3'
services:
web:
image: gitea/gitea
container_name: ${gitServName}
restart: ${restartPolicy}
ports:
# - 8088:3000/tcp
- 2202:22/tcp
networks:
- giteaNet
- postfixNet
external_links:
- ${smtpServName}:${smtpHost}
volumes:
- gitData:/data
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
depends_on:
- db
environment:
- USER_UID=1000
- USER_GID=1000
labels:
- "traefik.enable=true"
- "traefik.http.routers.${gitServName}.rule=Host(`${gitHost}.${domain}`)"
- "traefik.http.services.${gitServName}.loadbalancer.server.port=3000"
- "traefik.docker.network=giteaNet"
db:
image: mariadb:10.5
container_name: ${gitDBName}
restart: ${restartPolicy}
env_file:
- ../../secret/env-${gitDBName}
volumes:
- gitDB:/var/lib/mysql
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
networks:
- giteaNet
volumes:
gitDB:
gitData:
networks:
giteaNet:
external: true
name: giteaNet
postfixNet:
external: true
name: postfixNet

72
dockers/gitea/first.sh Executable file
View File

@ -0,0 +1,72 @@
#!/bin/bash
KAZ_ROOT=$(cd $(dirname $0)/../..; pwd)
. "${KAZ_ROOT}/bin/.commonFunctions.sh"
setKazVars
cd $(dirname $0)
. "${DOCKERS_ENV}"
. "${KAZ_KEY_DIR}/env-${gitServName}"
. "${KAZ_KEY_DIR}/env-${gitDBName}"
GIT_URL="${httpProto}://${gitHost}.${domain}"
checkDockerRunning "${gitServName}" "Gitea" || exit
if ! grep -q "INSTALL_LOCK\s*=\s*true" "${DOCK_LIB}/volumes/gitea_gitData/_data/gitea/conf/app.ini" 2>/dev/null ; then
printKazMsg "\n *** Premier lancement de GIT"
waitUrl "${GIT_URL}"
curl -X POST \
--data-urlencode "admin_confirm_passwd=${pass_admin}" \
--data-urlencode "admin_email=${admin_email}" \
--data-urlencode "admin_name=${user_admin}" \
--data-urlencode "admin_passwd=${pass_admin}" \
--data-urlencode "allow_only_external_registration=" \
--data-urlencode "app_name=Gitea: Git with a cup of tea" \
--data-urlencode "app_url=${httpProto}://${gitHost}.${domain}/" \
--data-urlencode "charset=utf8" \
--data-urlencode "db_host=db:3306" \
--data-urlencode "db_name=${MYSQL_DATABASE}" \
--data-urlencode "db_passwd=${MYSQL_PASSWORD}" \
--data-urlencode "db_schema=" \
--data-urlencode "db_path=/data/gitea/gitea.db" \
--data-urlencode "db_type=mysql" \
--data-urlencode "db_user=${MYSQL_USER}" \
--data-urlencode "default_allow_create_organization=on" \
--data-urlencode "default_enable_timetracking=on" \
--data-urlencode "default_keep_email_private=" \
--data-urlencode "disable_gravatar=" \
--data-urlencode "disable_registration=on" \
--data-urlencode "domain=${gitHost}.${domain}" \
--data-urlencode "enable_captcha=" \
--data-urlencode "enable_federated_avatar=on" \
--data-urlencode "enable_open_id_sign_in=" \
--data-urlencode "enable_open_id_sign_up=" \
--data-urlencode "http_port=3000" \
--data-urlencode "lfs_root_path=/data/git/lfs" \
--data-urlencode "log_root_path=/data/gitea/log" \
--data-urlencode "mail_notify=on" \
--data-urlencode "no_reply_address=noreply.localhost" \
--data-urlencode "offline_mode=" \
--data-urlencode "password_algorithm=pbkdf2" \
--data-urlencode "register_confirm=on" \
--data-urlencode "repo_root_path=/data/git/repositories" \
--data-urlencode "require_sign_in_view=" \
--data-urlencode "run_user=git" \
--data-urlencode "smtp_from=admin@${smtpHost}.${domain}" \
--data-urlencode "smtp_host=${smtpHost}.${domain}" \
--data-urlencode "smtp_passwd=" \
--data-urlencode "smtp_user=" \
--data-urlencode "ssh_port=2202" \
--data-urlencode "ssl_mode=disable" \
"${httpProto}://${gitHost}.${domain}/"
fi
# https://docs.gitea.io/en-us/customizing-gitea/
DATA_DIR="${DOCK_VOL}/gitea_gitData/_data/gitea"
mkdir -p "${DATA_DIR}/public/img"
cp "$(dirname $0)/logo.svg" "${DATA_DIR}/public/img/logo.svg"
chown -R 1000:1000 "${DATA_DIR}/public/"

979
dockers/gitea/logo.svg Normal file
View File

@ -0,0 +1,979 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
viewBox="0 0 640 640"
style="enable-background:new 0 0 640 640"
xml:space="preserve"
width="32"
height="32"
version="1.1"
id="svg8"
sodipodi:docname="logo.svg"
inkscape:version="1.0.2 (e86c870879, 2021-01-15)"><metadata
id="metadata14"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs
id="defs12" /><sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="2159"
inkscape:window-height="1675"
id="namedview10"
showgrid="false"
inkscape:zoom="38.229526"
inkscape:cx="16.59547"
inkscape:cy="20.409433"
inkscape:window-x="960"
inkscape:window-y="288"
inkscape:window-maximized="0"
inkscape:current-layer="svg8" /><path
style="fill:#fff"
d="m395.9 484.2-126.9-61c-12.5-6-17.9-21.2-11.8-33.8l61-126.9c6-12.5 21.2-17.9 33.8-11.8 17.2 8.3 27.1 13 27.1 13l-.1-109.2 16.7-.1.1 117.1s57.4 24.2 83.1 40.1c3.7 2.3 10.2 6.8 12.9 14.4 2.1 6.1 2 13.1-1 19.3l-61 126.9c-6.2 12.7-21.4 18.1-33.9 12z"
id="path2" /><path
style="fill:#609926"
d="M622.7 149.8c-4.1-4.1-9.6-4-9.6-4s-117.2 6.6-177.9 8c-13.3.3-26.5.6-39.6.7v117.2c-5.5-2.6-11.1-5.3-16.6-7.9 0-36.4-.1-109.2-.1-109.2-29 .4-89.2-2.2-89.2-2.2s-141.4-7.1-156.8-8.5c-9.8-.6-22.5-2.1-39 1.5-8.7 1.8-33.5 7.4-53.8 26.9C-4.9 212.4 6.6 276.2 8 285.8c1.7 11.7 6.9 44.2 31.7 72.5 45.8 56.1 144.4 54.8 144.4 54.8s12.1 28.9 30.6 55.5c25 33.1 50.7 58.9 75.7 62 63 0 188.9-.1 188.9-.1s12 .1 28.3-10.3c14-8.5 26.5-23.4 26.5-23.4S547 483 565 451.5c5.5-9.7 10.1-19.1 14.1-28 0 0 55.2-117.1 55.2-231.1-1.1-34.5-9.6-40.6-11.6-42.6zM125.6 353.9c-25.9-8.5-36.9-18.7-36.9-18.7S69.6 321.8 60 295.4c-16.5-44.2-1.4-71.2-1.4-71.2s8.4-22.5 38.5-30c13.8-3.7 31-3.1 31-3.1s7.1 59.4 15.7 94.2c7.2 29.2 24.8 77.7 24.8 77.7s-26.1-3.1-43-9.1zm300.3 107.6s-6.1 14.5-19.6 15.4c-5.8.4-10.3-1.2-10.3-1.2s-.3-.1-5.3-2.1l-112.9-55s-10.9-5.7-12.8-15.6c-2.2-8.1 2.7-18.1 2.7-18.1L322 273s4.8-9.7 12.2-13c.6-.3 2.3-1 4.5-1.5 8.1-2.1 18 2.8 18 2.8L467.4 315s12.6 5.7 15.3 16.2c1.9 7.4-.5 14-1.8 17.2-6.3 15.4-55 113.1-55 113.1z"
id="path4" /><image
width="169.37718"
height="142.20097"
preserveAspectRatio="none"
xlink:href="data:image/png;base64,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
cENlaGlIenJlU3pOVGN6a2M5ZCI/Pgo8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEv
IiB4OnhtcHRrPSJYTVAgQ29yZSA0LjQuMC1FeGl2MiI+CiA8cmRmOlJERiB4bWxuczpyZGY9Imh0
dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPgogIDxyZGY6RGVzY3Jp
cHRpb24gcmRmOmFib3V0PSIiCiAgICB4bWxuczppcHRjRXh0PSJodHRwOi8vaXB0Yy5vcmcvc3Rk
L0lwdGM0eG1wRXh0LzIwMDgtMDItMjkvIgogICAgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9i
ZS5jb20veGFwLzEuMC9tbS8iCiAgICB4bWxuczpzdEV2dD0iaHR0cDovL25zLmFkb2JlLmNvbS94
YXAvMS4wL3NUeXBlL1Jlc291cmNlRXZlbnQjIgogICAgeG1sbnM6cGx1cz0iaHR0cDovL25zLnVz
ZXBsdXMub3JnL2xkZi94bXAvMS4wLyIKICAgIHhtbG5zOkdJTVA9Imh0dHA6Ly93d3cuZ2ltcC5v
cmcveG1wLyIKICAgIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIK
ICAgIHhtbG5zOnRpZmY9Imh0dHA6Ly9ucy5hZG9iZS5jb20vdGlmZi8xLjAvIgogICAgeG1sbnM6
eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIgogICB4bXBNTTpEb2N1bWVudElEPSJn
aW1wOmRvY2lkOmdpbXA6ZGVhOTA4ZDItYTJkZC00NDhlLWIxNzItM2FjNjRlM2E5ODI5IgogICB4
bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjNkZGQ0M2U0LTk5ZmUtNDYzOC04OGNkLWYwODg0ZDU2
NzgyZiIKICAgeG1wTU06T3JpZ2luYWxEb2N1bWVudElEPSJ4bXAuZGlkOjkzYjIyYTZiLWI4Mzkt
NDZjMC05MDc1LTU0NzdjYTk1NGMxYyIKICAgR0lNUDpBUEk9IjIuMCIKICAgR0lNUDpQbGF0Zm9y
bT0iTGludXgiCiAgIEdJTVA6VGltZVN0YW1wPSIxNjU0NDMwNTg2MjYzMzk1IgogICBHSU1QOlZl
cnNpb249IjIuMTAuMjIiCiAgIGRjOkZvcm1hdD0iaW1hZ2UvcG5nIgogICB0aWZmOk9yaWVudGF0
aW9uPSIxIgogICB4bXA6Q3JlYXRvclRvb2w9IkdJTVAgMi4xMCI+CiAgIDxpcHRjRXh0OkxvY2F0
aW9uQ3JlYXRlZD4KICAgIDxyZGY6QmFnLz4KICAgPC9pcHRjRXh0OkxvY2F0aW9uQ3JlYXRlZD4K
ICAgPGlwdGNFeHQ6TG9jYXRpb25TaG93bj4KICAgIDxyZGY6QmFnLz4KICAgPC9pcHRjRXh0Okxv
Y2F0aW9uU2hvd24+CiAgIDxpcHRjRXh0OkFydHdvcmtPck9iamVjdD4KICAgIDxyZGY6QmFnLz4K
ICAgPC9pcHRjRXh0OkFydHdvcmtPck9iamVjdD4KICAgPGlwdGNFeHQ6UmVnaXN0cnlJZD4KICAg
IDxyZGY6QmFnLz4KICAgPC9pcHRjRXh0OlJlZ2lzdHJ5SWQ+CiAgIDx4bXBNTTpIaXN0b3J5Pgog
ICAgPHJkZjpTZXE+CiAgICAgPHJkZjpsaQogICAgICBzdEV2dDphY3Rpb249InNhdmVkIgogICAg
ICBzdEV2dDpjaGFuZ2VkPSIvIgogICAgICBzdEV2dDppbnN0YW5jZUlEPSJ4bXAuaWlkOmMwY2Y5
ZmZkLTYwMmItNDI4YS04YjQ1LTIwYmM2MGY2MjhmNiIKICAgICAgc3RFdnQ6c29mdHdhcmVBZ2Vu
dD0iR2ltcCAyLjEwIChMaW51eCkiCiAgICAgIHN0RXZ0OndoZW49IiswMTowMCIvPgogICAgIDxy
ZGY6bGkKICAgICAgc3RFdnQ6YWN0aW9uPSJzYXZlZCIKICAgICAgc3RFdnQ6Y2hhbmdlZD0iLyIK
ICAgICAgc3RFdnQ6aW5zdGFuY2VJRD0ieG1wLmlpZDowYjE2ZGEzMy05MjU3LTRmYzEtYTBmZS00
MzQxMTBhMjEwMWUiCiAgICAgIHN0RXZ0OnNvZnR3YXJlQWdlbnQ9IkdpbXAgMi4xMCAoTGludXgp
IgogICAgICBzdEV2dDp3aGVuPSIrMDE6MDAiLz4KICAgICA8cmRmOmxpCiAgICAgIHN0RXZ0OmFj
dGlvbj0ic2F2ZWQiCiAgICAgIHN0RXZ0OmNoYW5nZWQ9Ii8iCiAgICAgIHN0RXZ0Omluc3RhbmNl
SUQ9InhtcC5paWQ6NWU3MGZiMTUtMmVlZi00Y2Q3LThmZWQtNzlmMGZiZDdiMjZhIgogICAgICBz
dEV2dDpzb2Z0d2FyZUFnZW50PSJHaW1wIDIuMTAgKExpbnV4KSIKICAgICAgc3RFdnQ6d2hlbj0i
KzAyOjAwIi8+CiAgICA8L3JkZjpTZXE+CiAgIDwveG1wTU06SGlzdG9yeT4KICAgPHBsdXM6SW1h
Z2VTdXBwbGllcj4KICAgIDxyZGY6U2VxLz4KICAgPC9wbHVzOkltYWdlU3VwcGxpZXI+CiAgIDxw
bHVzOkltYWdlQ3JlYXRvcj4KICAgIDxyZGY6U2VxLz4KICAgPC9wbHVzOkltYWdlQ3JlYXRvcj4K
ICAgPHBsdXM6Q29weXJpZ2h0T3duZXI+CiAgICA8cmRmOlNlcS8+CiAgIDwvcGx1czpDb3B5cmln
aHRPd25lcj4KICAgPHBsdXM6TGljZW5zb3I+CiAgICA8cmRmOlNlcS8+CiAgIDwvcGx1czpMaWNl
bnNvcj4KICA8L3JkZjpEZXNjcmlwdGlvbj4KIDwvcmRmOlJERj4KPC94OnhtcG1ldGE+CiAgICAg
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAg
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
ICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
ICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAK
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAg
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAg
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
ICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
ICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAg
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAg
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
ICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
ICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAg
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAg
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
ICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
ICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
IAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAg
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAg
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
ICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAKPD94cGFja2V0
IGVuZD0idyI/Pkb7dcoAAAAGYktHRADbANkA3uRVBFsAAAAJcEhZcwAACxMAAAsTAQCanBgAAAAH
dElNRQfmBgUMAwZ/A1g/AAAgAElEQVR42uy9eZgcV3X3/62enqVnVBpJlhpJbll2C8Z4DGM8ICNb
ODEOYbUDNptj1hicGAKBF17DQwgYeAOYNYQt8IMshDWscQwBh8TYxvsi24ot22N5ZEkjjdUaLaOe
fbrr/v64dapuVVdvMzU9I/X38zz1dHV1d3V19b3nfu+5555rgcRJAoDjPloAiu7xNgAzAE4GcDmA
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
"
id="image849"
x="413.95416"
y="96.540276"
transform="rotate(26.21162)"
inkscape:transform-center-x="38.617587"
inkscape:transform-center-y="-16.964991" /></svg>

After

Width:  |  Height:  |  Size: 72 KiB

1
dockers/grafana/.env Symbolic link
View File

@ -0,0 +1 @@
../../config/dockers.env

View File

@ -0,0 +1,67 @@
version: '3'
services:
prometheus:
image: prom/prometheus:v2.15.2
restart: unless-stopped
container_name: ${prometheusServName}
volumes:
- ./prometheus/:/etc/prometheus/
- prometheus:/prometheus
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
command:
- "--web.route-prefix=/"
- "--web.external-url=https://${site}.${domain}/prometheus"
- "--config.file=/etc/prometheus/prometheus.yml"
- "--storage.tsdb.path=/prometheus"
- "--web.console.libraries=/usr/share/prometheus/console_libraries"
- "--web.console.templates=/usr/share/prometheus/consoles"
networks:
- traefikNet
labels:
- "traefik.enable=true"
- "traefik.http.routers.prometheus-secure.entrypoints=websecure"
- "traefik.http.middlewares.prometheus-stripprefix.stripprefix.prefixes=/prometheus"
- "traefik.http.routers.prometheus-secure.rule=Host(`${site}.${domain}`) && PathPrefix(`/prometheus`)"
# - "traefik.http.routers.prometheus-secure.tls=true"
- "traefik.http.routers.prometheus-secure.middlewares=prometheus-stripprefix,test-adminipwhitelist@file,traefik-auth"
- "traefik.http.routers.prometheus-secure.service=prometheus"
- "traefik.http.services.prometheus.loadbalancer.server.port=9090"
- "traefik.docker.network=traefikNet"
grafana:
image: grafana/grafana:6.6.1
restart: unless-stopped
container_name: ${grafanaServName}
volumes:
- grafana:/var/lib/grafana
- ./grafana/provisioning:/etc/grafana/provisioning
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
env_file:
- grafana.env
depends_on:
- prometheus
networks:
- traefikNet
labels:
- "traefik.enable=true"
- "traefik.http.routers.grafana-secure.entrypoints=websecure"
- "traefik.http.middlewares.grafana-stripprefix.stripprefix.prefixes=/grafana"
- "traefik.http.routers.grafana-secure.rule=Host(`${site}.${domain}`) && PathPrefix(`/grafana`)"
# - "traefik.http.routers.grafana-secure.tls=true"
- "traefik.http.routers.grafana-secure.service=grafana"
- "traefik.http.routers.grafana-secure.middlewares=grafana-stripprefix,test-adminipwhitelist@file,traefik-auth"
- "traefik.http.services.grafana.loadbalancer.server.port=3000"
- "traefik.docker.network=traefikNet"
networks:
traefikNet:
external: true
name: traefikNet
volumes:
prometheus:
grafana:

View File

@ -0,0 +1,6 @@
GF_AUTH_ANONYMOUS_ENABLED=true
GF_AUTH_BASIC_ENABLED=false
GF_AUTH_PROXY_ENABLED=false
GF_USERS_ALLOW_SIGN_UP=false
GF_INSTALL_PLUGINS=grafana-piechart-panel
GF_SERVER_ROOT_URL=%(protocol)s://%(domain)s:%(http_port)s/grafana

View File

@ -0,0 +1,21 @@
apiVersion: 1
providers:
# <string> provider name
- name: 'default'
# <int> org id. will default to orgId 1 if not specified
orgId: 1
# <string, required> name of the dashboard folder. Required
folder: ''
# <string> folder UID. will be automatically generated if not specified
folderUid: ''
# <string, required> provider type. Required
type: file
# <bool> disable dashboard deletion
disableDeletion: false
# <bool> enable dashboard editing
editable: true
# <int> how often Grafana will scan for changed dashboards
updateIntervalSeconds: 10
options:
path: /etc/grafana/provisioning/dashboards

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,50 @@
# config file version
apiVersion: 1
# list of datasources that should be deleted from the database
deleteDatasources:
- name: Prometheus
orgId: 1
# list of datasources to insert/update depending
# whats available in the database
datasources:
# <string, required> name of the datasource. Required
- name: Prometheus
# <string, required> datasource type. Required
type: prometheus
# <string, required> access mode. direct or proxy. Required
access: proxy
# <int> org id. will default to orgId 1 if not specified
orgId: 1
# <string> url
url: http://prometheus:9090
# <string> database password, if used
password:
# <string> database user, if used
user:
# <string> database name, if used
database:
# <bool> enable/disable basic auth
basicAuth: false
# <string> basic auth username
basicAuthUser:
# <string> basic auth password
basicAuthPassword:
# <bool> enable/disable with credentials headers
withCredentials:
# <bool> mark as default datasource. Max one per org
isDefault: true
# <map> fields that will be converted to json and stored in json_data
jsonData:
graphiteVersion: "1.1"
tlsAuth: false
tlsAuthWithCACert: false
# <string> json object of data that will be encrypted.
secureJsonData:
tlsCACert: "..."
tlsClientCert: "..."
tlsClientKey: "..."
version: 1
# <bool> allow users to edit datasources from the UI.
editable: true

View File

@ -0,0 +1,11 @@
groups:
- name: traefik
rules:
- alert: service_down
expr: up == 0
for: 2m
labels:
severity: page
annotations:
summary: "Instance {{ $labels.instance }} down"
description: "{{ $labels.instance }} of job {{ $labels.job }} has been down for more than 2 minutes"

View File

@ -0,0 +1,12 @@
global:
scrape_interval: 15s
evaluation_interval: 15s
rule_files:
- 'alert.rules'
scrape_configs:
- job_name: 'traefik'
scrape_interval: 5s
static_configs:
- targets: ['dashboard.kaz.sns:8289','dashboard2.kaz.sns:8289']

1
dockers/imapsync/.env Symbolic link
View File

@ -0,0 +1 @@
../../config/dockers.env

View File

@ -0,0 +1,31 @@
version: '3.3'
services:
imapsync:
image: gilleslamiral/imapsync
command: /servimapsync
container_name: ${imapsyncServName}
restart: ${restartPolicy}
networks:
- imapsyncNet
env_file:
- ../../secret/env-${imapsyncServName}
expose:
- 80
volumes:
- ./imapsync_form_extra.html:/var/tmp/imapsync_form_extra.html
- ./imapsync_form.css:/var/tmp/imapsync_form.css
#on autorise 40% du cpu : si la machine s' emballe, les ressources seront atteintes et imapsync régulera
cpus: 0.4
labels:
- "traefik.enable=true"
- "traefik.http.routers.${imapsyncServName}.rule=Host(`${imapsyncHost}.${domain}`)"
- "traefik.http.services.${imapsyncServName}.loadbalancer.server.port=${imapsyncPort}"
- "traefik.docker.network=imapsyncNet"
networks:
imapsyncNet:
external: true
name: imapsyncNet

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,484 @@
<!DOCTYPE html>
<!-- $Id: imapsync_form_extra.html,v 1.29 2024/01/14 19:47:25 gilles Exp gilles $ -->
<html lang="en" id="top">
<head>
<!--
<script
data-ad-client="ca-pub-3325993554161060"
async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js">
</script>
-->
<title>Imapsync Online Unlimited</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!--
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"
integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css"
integrity="sha384-rHyoN1iRsVXV4nD0JutlnGaslCJuC7uwjduW9SVrLvRYooPp2bWYgmgJQIXwl/Sp" crossorigin="anonymous">
-->
<link rel="stylesheet" href="imapsync_form.css">
<link rel="stylesheet" href="../S/style.css" type="text/css" />
<link rel="license" href="https://imapsync.lamiral.info/NOLIMIT">
<noscript>
<link rel="stylesheet" href="noscript.css">
</noscript>
</head>
<body>
<div class="scripton">
<!-- will appear if some tests fail -->
<pre id="tests" class="collapse"></pre>
<!-- hidden stuff that must exit for the tests -->
<div class="hidden">
<input type="checkbox" id="test_checkbox">
<input type="text" id="test_text">
<input type="radio" id="test_radio1" name="test_radio" value="first" >
<input type="radio" id="test_radio2" name="test_radio" value="second" >
</div>
</div>
<div class="container-fluid" >
<div class="row">
<div class="text-center">
<a href="https://imapsync.lamiral.info/">
<img alt="Imapsync home" title="Imapsync home page" src="https://imapsync.lamiral.info/X/logo_imapsync_Xn.png" height="38" width="60">
</a>
<a href="#top" title="Top of the page" class="btn btn-info " role="button">Top</a>
<a href="#bottom" title="Bottom of the page" class="btn btn-info active" role="button">Bottom</a>
</div>
</div>
<h1 class="text-center">Imapsync Online Unlimited</h1>
<p class="text-center larger"> <strong>Copy</strong>/synchronize a <strong>complete</strong> Mailbox to another, without <strong>duplicates!</strong></p>
<form id="form" action="/cgi-bin/imapsync" method="post" autocomplete="on">
<div id="form_row" class="row">
<div id="account1" class="col-md-5" >
<fieldset>
<legend class="text-center h2">IMAP source Mailbox</legend>
<label for="user1">Login</label> (usually an email address)
<div class="input-group form-group">
<span class="input-group-addon"><i class="glyphicon glyphicon-user"> </i></span>
<input
data-toggle="tooltip" data-placement="bottom" title="It is usually an email address or its left part before @"
type="text" class="form-control input-lg" id="user1" name="user1" tabindex="1"
placeholder="Enter login name">
</div>
<label for="password1">Password</label>
<label class="checkbox-inline out">
<input type="checkbox" id="showpassword1"> show password
</label>
<div class="input-group form-group">
<span class="input-group-addon"><i class="glyphicon glyphicon-lock"></i></span>
<input
data-toggle="tooltip" data-placement="bottom" title="Passwords are not stored on the server"
type="password" class="form-control input-lg" id="password1" name="password1" tabindex="2"
placeholder="Enter password">
</div>
<label for="host1">IMAP Server hostname</label> (or its IP address)
<div class="input-group form-group">
<span class="input-group-addon"><i class="glyphicon glyphicon-cloud"></i></span>
<input
data-toggle="tooltip" data-placement="bottom" title="IMAP transfers are done with encryption if the servers support it."
list="servers1" type="text" class="form-control input-lg" id="host1" name="host1" tabindex="3"
placeholder="Enter IMAP source server name or IP address">
<datalist id="servers1">
<option value="imap.gmail.com">
<option value="outlook.office365.com">
<option value="imap.hostinger.com">
<option value="ssl0.ovh.net">
<option value="email-ssl.com.br">
<option value="imap.mail.yahoo.com">
</datalist>
</div>
<div class="form-group collapse extra_param">
<label class="checkbox-inline">
<input
data-toggle="tooltip" data-placement="bottom" title="Be careful with this option"
type="checkbox" id="delete1" name="delete1">Move sync. Deletes messages on source mailbox after a successful transfer.
</label>
</div>
<div class="form-group collapse extra_param" >
<label for="subfolder1">Sub-folder</label>
<div class="input-group form-group">
<span class="input-group-addon"><i class="glyphicon glyphicon-folder-open"></i></span>
<input
data-toggle="tooltip" data-placement="bottom" title="A subfolder where all the source mailbox comes from."
type="text" class="form-control input-lg" id="subfolder1" name="subfolder1"
placeholder="Enter sub-folder name">
</div>
</div>
<div>
<br>
</div>
</fieldset>
</div>
<div id="parameters" class="col-md-2" >
<div>
<br>
</div>
<div>
<label class="checkbox-inline">
<input
data-toggle="tooltip" data-placement="bottom" title="Shows what would be done without really doing it."
type="checkbox" id="dry" name="dry" >Just verbose, no real sync.
</label>
</div>
<div>
<label class="checkbox-inline">
<input
data-toggle="tooltip" data-placement="bottom" title="Checks credentials without syncing anything."
type="checkbox" id="justlogin" name="justlogin" >Just checks credentials.
</label>
</div>
<div>
<label class="checkbox-inline">
<input
data-toggle="tooltip" data-placement="bottom" title="Shows folders sizes and exits."
type="checkbox" id="justfoldersizes" name="justfoldersizes" >Just presents folders sizes.
</label>
</div>
<div>
<label class="checkbox-inline">
<input
data-toggle="tooltip" data-placement="bottom" title="Just create the folder hierarchy, messages are not synced."
type="checkbox" id="justfolders" name="justfolders" >Just create folders.
</label>
</div>
<div>
<br>
</div>
<div id="button_extra_param" class="text-center scripton">
<button type="button" class="btn btn-default btn-block" data-toggle="collapse"
data-target=".extra_param">Show / Hide extra parameters</button>
</div>
<div>
<br>
</div>
<div id="button_swap" class="text-center scripton">
<button type="button" class="btn btn-default btn-block" id="swap">
Swap Source <span class="glyphicon glyphicon-transfer"></span> Destination
</button>
</div>
<div>
<br>
</div>
</div>
<div id="account2" class="col-md-5" >
<fieldset>
<legend class="text-center h2">IMAP destination Mailbox</legend>
<label for="user2">Login</label> (usually an email address)
<div class="input-group form-group">
<span class="input-group-addon"><i class="glyphicon glyphicon-user"></i></span>
<input
data-toggle="tooltip" data-placement="bottom" title="It is usually an email address or its left part before @"
type="text" class="form-control input-lg" id="user2" name="user2" tabindex="6"
placeholder="Enter login name">
</div>
<label for="password2">Password</label>
<label class="checkbox-inline out">
<input type="checkbox" id="showpassword2"> show password
</label>
<div class="input-group form-group">
<span class="input-group-addon"><i class="glyphicon glyphicon-lock"></i></span>
<input
data-toggle="tooltip" data-placement="bottom" title="Passwords are not stored on the server"
type="password" class="form-control input-lg" id="password2" name="password2" tabindex="7"
placeholder="Enter password">
</div>
<label for="host2">IMAP Server hostname</label> (or its IP address)
<div class="input-group form-group">
<span class="input-group-addon"><i class="glyphicon glyphicon-cloud"></i></span>
<select data-placement="bottom" title="IMAP transfers are done with encryption if the servers support it." class="form-control input-lg" id="host2" name="host2">
<option value="mail.kaz.bzh">mail.kaz.bzh</option>
</select>
<!--
<input
data-toggle="tooltip" data-placement="bottom" title="IMAP transfers are done with encryption if the servers support it."
list="servers2" type="text" class="form-control input-lg" id="host2" name="host2" tabindex="8"
placeholder="Enter IMAP destination server name or IP address">
<datalist id="servers2">
<option value="imap.gmail.com">
<option value="outlook.office365.com">
<option value="imap.hostinger.com">
<option value="ssl0.ovh.net">
<option value="email-ssl.com.br">
<option value="imap.mail.yahoo.com">
</datalist>
-->
</div>
<!-- -->
<div class="form-group collapse extra_param">
<label class="checkbox-inline">
<input
data-toggle="tooltip" data-placement="bottom" title="Be careful with this option"
type="checkbox" id="delete2" name="delete2" tabindex="9">Strict sync. Deletes messages on destination mailbox that are not at the source mailbox.
</label>
</div>
<div class="form-group collapse extra_param" id="extra_subfolder2" >
<label for="subfolder2">Sub-folder</label>
<div class="input-group form-group">
<span class="input-group-addon"><i class="glyphicon glyphicon-folder-open"></i></span>
<input
data-toggle="tooltip" data-placement="bottom" title="A subfolder where all the source mailbox will go."
type="text" class="form-control input-lg" id="subfolder2" name="subfolder2"
placeholder="Enter sub-folder name">
</div>
</div>
<!-- -->
<div>
<br>
</div>
</fieldset>
</div>
</div>
<input type="hidden" name="automap" value="on">
<input type="hidden" name="addheader" value="on">
<!-- -#->
<input type="hidden" name="simulong" value="360">
<!-#- -->
<a id="buttons"></a>
<hr>
<div class="text-center center-block">
If you <b>close</b> this <b>window</b> (or tab) <b>during</b> the synchronization,
it will <b>stop</b> the synchronization, it's like <b>hitting</b> the red button <b>"Stop!"</b> below.
</div>
<!-- Classical button to go to the log only, when javascript is turned off -->
<noscript>
<div class="row">
<div class="col-sm-12 padd0" >
<button type="submit" class="btn btn-success btn-lg center-block btn-block">Go sync!</button>
</div>
</div>
</noscript>
<!-- Javascript buttons using xhr -->
<div class="row scripton">
<div class="col-sm-6 padd0" >
<button id="bt-sync" type="button"
class="btn btn-success btn-lg center-block btn-block"
tabindex="11"
data-toggle="tooltip" data-placement="top"
title="Launch the sync! You can stop the sync with the red Stop! button nearby or by closing the tab/window."
>
Sync or resync!<br>
<span class="glyphicon glyphicon-envelope"></span>
<span class="glyphicon glyphicon-arrow-right"></span>
<span class="glyphicon glyphicon-envelope"></span>
</button>
</div>
<div class="col-sm-6 padd0" >
<button id="bt-abort" type="button"
class="btn btn-danger btn-lg center-block btn-block" tabindex="12"
data-toggle="tooltip" data-placement="top"
title="Stop the sync! You can restart the sync later, no duplicates should happen."
>
Stop!<br>
<span class="glyphicon glyphicon-scissors"></span>
</button>
</div>
</div>
</form>
<div class="row scripton" id="consoles" >
<pre id="imapsync_current" class="center-block text-center"></pre>
<span id="imapsync_advice_hours" class="text-center collapse">
Best <a href="#local_bandwidth"><b>bandwidth available hours</b></a> are from <b>11h PM to 11h AM UTC</b> on Mondays to Fridays, <b>all hours</b> on Saturdays and <b>Sundays</b>.
</span>
<pre id="progress-txt">ETA: Estimation Time of Arrival</pre>
<div class="progress">
<div id="progress-bar-done" class="progress-bar progress-bar-success" role="progressbar">
Progress bar
</div>
<div id="progress-bar-left" class="progress-bar progress-bar-info" role="progressbar">
Progress bar
</div>
</div>
<div class="col-sm-6 well">
<h2 class="text-center">Console of imapsync launch</h2>
<pre id="console">
</pre>
</div>
<div class="col-sm-6 well">
<h2 class="text-center">Console of Stopping</h2>
<pre id="abort">
</pre>
</div>
</div>
</div>
<h2 id="imapsync_log_beginning" class="text-center scripton">Log of imapsync run</h2>
<div class="text-center scripton">
<a href="#imapsync_log_bottom">Link to the <b>bottom</b> of the imapsync log file</a>
</div>
<pre id="output" class="scripton">
</pre>
<div id="imapsync_log_bottom" class="text-center scripton">
<a href="#imapsync_log_beginning">Link to the <b>beginning</b> of the imapsync log file</a>
</div>
<div id="local_bandwidth" class="collapse">
<hr>
<p class="text-center">
<b>Local bandwidth statistics</b><br>
<a href="/vnstat/vnstati.html">
<img alt="Local bandwidth statistics" src="/vnstat/vnstat_vs.png" >
</a>
</p>
</div>
<div id="local_status_dbmon" class="collapse">
<hr>
<p class="text-center">
Imapsync <b>Online Status</b> over the <b>last 24h</b><br>
<a href="imapsync_online_status.html"><img id="status_24h" class="img-responsive center-block" alt="Imapsync Online Status over the last 24h" src="https://sup.lamiral.info/dbmon/cgi-bin/rrdview.cgi?child=yes&rrdfile=%2Fvar%2Ftmp%2Fdbmon%2Ftests%2Frrdbases%2Flocalhost~2583~LAMIRAL~Imapsync_Online~LAMIRAL%2CImapsync_Online~opstatus~300~.rrd;interval_vue=p86400;date_given_by=now;date_means=end;dsname=opstatus;width=1200;hight=70;lower=0;upper=100;rigid=on;Beautiful%20Image%21.x=128;Beautiful%20Image%21.y=30;title=Service%20Status;owner=Imapsync_Online;caption=Status%20in%20%25;monitor=https.rrdrt.monitor%20--rrdrt%20imapsync.lamiral.info%2Fcgi-bin%2Fimapsync%20%3B%3B">
</a>
</p>
</div>
<div id="local_status_hetrix" class="collapse">
<hr>
<p class="text-center">
The service is down? For how long? How often? Take also a look at the
<a href="https://hetrixtools.com/report/uptime/873a2356aea43055204b59f562b5ad52/414322.html">Imapsync Online Status</a>
monitor page powered by the <a href="https://hetrixtools.com/414322.html">HetrixTools</a> company.
</p>
</div>
<a id="bottom"></a>
<hr>
<p class="text-center">Feel free to contact
<strong><a href="https://imapsync.lamiral.info/#AUTHOR" target="_blank">Gilles LAMIRAL</a></strong>
</p>
<div class="container-fluid" >
<div class="row">
<div class="text-center">
<a href="https://imapsync.lamiral.info/">
<img alt="Imapsync home page" src="https://imapsync.lamiral.info/X/logo_imapsync_Xn.png" height="38" width="60">
</a>
<a href="#top" title="Top of the page" class="btn btn-info " role="button">Top</a>
<!-- <a href="#buttons" class="btn btn-info scripton" role="button">Consoles</a> -->
<a href="#bottom" title="Bottom of the page" class="btn btn-info active" role="button">Bottom</a>
<br>
<small> ($Id: imapsync_form_extra.html,v 1.29 2024/01/14 19:47:25 gilles Exp gilles $) </small><br>
Terms and conditions for anything: <a href="https://imapsync.lamiral.info/LICENSE">No limits to do anything with this work and this license!</a><br>
</div>
</div>
</div>
<script
src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"
integrity="sha384-xBuQ/xzmlsLoJpyjoggmTEz8OWUFM0/RC5BsqQBDX2v5cMvDHcMakNTNrHIW2I5f"
crossorigin="anonymous"
>
</script>
<script
src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"
integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa"
crossorigin="anonymous"
>
</script>
<!--
<script src="crypto-js/core.js"></script>
<script src="crypto-js/sha256.js"></script>
-->
<script
src="imapsync_form.js"
>
</script>
</body>
</html>

1
dockers/jirafeau/.env Symbolic link
View File

@ -0,0 +1 @@
../../config/dockers.env

View File

@ -0,0 +1,75 @@
FROM php:7.4-apache
########################################
# APT local cache
# work around because COPY failed if no source file
COPY .dummy .apt-mirror-confi[g] .proxy-confi[g] /
RUN cp /.proxy-config /etc/profile.d/proxy.sh 2> /dev/null || true
RUN if [ -f /.apt-mirror-config ] ; then . /.apt-mirror-config && sed -i \
-e "s%s\?://deb.debian.org%://${APT_MIRROR_DEBIAN}%g" \
-e "s%s\?://security.debian.org%://${APT_MIRROR_DEBIAN_SECURITY}%g" \
-e "s%s\?://archive.ubuntu.com%://${APT_MIRROR_UBUNTU}%g" \
-e "s%s\?://security.ubuntu.com%://${APT_MIRROR_UBUNTU_SECURITY}%g" \
/etc/apt/sources.list; fi
########################################
RUN apt-get update --quiet && apt-get install -y \
libicu-dev libpq-dev zlib1g-dev libicu-dev \
libzip-dev wget zip patch mailutils sendmail
RUN apt-get install -y emacs php-elisp telnet
RUN sed -i 's/127.0.0.1/smtp/' /etc/mail/submit.mc
RUN echo "define(\`SMART_HOST',\`smtp')" >> /etc/mail/sendmail.mc
RUN m4 /etc/mail/submit.mc > /etc/mail/submit.cf
RUN m4 /etc/mail/sendmail.mc > /etc/mail/sendmail.cf
#install composer setup script
COPY dockers/jirafeau/composer-setup.sh /usr/local/bin/
RUN chmod +x /usr/local/bin/composer-setup.sh
#install internationalization libs
RUN docker-php-ext-configure intl
RUN docker-php-ext-install intl
RUN docker-php-ext-install zip
########################################
#install jirafeau
RUN mkdir /var/jirafeau/ /var/jirafeauData/
WORKDIR /var/jirafeau
COPY --chown=www-data git/Jirafeau/ .
COPY --chown=www-data git/depollueur/src/Jirafeau/[aft].php ./
COPY --chown=www-data dockers/jirafeau/media/kaz media/kaz
RUN sed -i -e '1i\<p>La limite des t&eacute;l&eacute;versements est actuellement de <?php echo ini_get("post_max_size"); ?></p>' lib/template/footer.php
RUN sed -i -e '/<div id="jyraphe">/i\<div id="kaz">' lib/template/footer.php
COPY dockers/jirafeau/config/composer.json .
RUN /usr/local/bin/composer-setup.sh
RUN php composer.phar install
RUN echo '\n\
upload_max_filesize = 1024M\n\
post_max_size = 1024M\n\
[mail function]\n\
SMTP = smtp\n\
smtp_port = 25\n\
sendmail_path=/usr/sbin/sendmail -t -i\n\
sendmail_from = no-reply@kaz.local\n\
' > /usr/local/etc/php/php.ini
RUN chown -R www-data.www-data . /var/jirafeauData/
RUN chmod o=,ug=rwX -R . /var/jirafeauData/
RUN rm -rf .git .gitignore .gitlab-ci.yml CONTRIBUTING.md README.md Dockerfile
VOLUME ["/var/jirafeauData", "/etc/apache2/sites-available"]
RUN echo "#!/bin/sh" >> /entrypoint.sh
RUN echo "chown -R www-data: /var/jirafeauData/" >> /entrypoint.sh
RUN echo "/usr/sbin/apache2ctl -D FOREGROUND" >> /entrypoint.sh
RUN chmod u=+x /entrypoint.sh
EXPOSE 80
ENTRYPOINT ["/entrypoint.sh"]

32
dockers/jirafeau/build.sh Executable file
View File

@ -0,0 +1,32 @@
#!/bin/bash
SERV_DIR=$(cd $(dirname $0); pwd)
KAZ_ROOT=$(cd $(dirname $0)/../..; pwd)
. "${KAZ_ROOT}/bin/.commonFunctions.sh"
setKazVars
cd "${SERV_DIR}"
CONF_FILE="config/config.local.php"
touch "${CONF_FILE}"
chown 33:33 "${CONF_FILE}"
SRC_JIR="https://gitlab.com/mojo42/Jirafeau.git"
JIR_VER="4.3.0"
"${KAZ_BIN_DIR}/installDepollueur.sh"
printKazMsg "\n *** Création du Dockerfile Jirafeau"
printKazMsg "\n - GIT Jirafeau "
cd "${KAZ_GIT_DIR}"
if [ ! -d "Jirafeau" ]; then
git clone "${SRC_JIR}" --branch ${JIR_VER}
fi
cd "${KAZ_GIT_DIR}/Jirafeau" && git reset --hard && git checkout ${JIR_VER}
printKazMsg "\n - Dockefile"
cd "${KAZ_ROOT}"
# Pour permettre la copy de git il faut que le répertoire soit visible de la racine qui lance la construction
docker build -t filekaz . -f dockers/jirafeau/Dockerfile

View File

@ -0,0 +1,18 @@
#!/bin/sh
EXPECTED_SIGNATURE=$(wget -q -O - https://composer.github.io/installer.sig)
php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
ACTUAL_SIGNATURE=$(php -r "echo hash_file('SHA384', 'composer-setup.php');")
if [ "$EXPECTED_SIGNATURE" != "$ACTUAL_SIGNATURE" ]
then
>&2 echo 'ERROR: Invalid installer signature'
rm composer-setup.php
exit 1
fi
php composer-setup.php --quiet
RESULT=$?
rm composer-setup.php
exit $RESULT

View File

@ -0,0 +1,5 @@
{
"require": {
"phpmailer/phpmailer": "^6.5"
}
}

View File

@ -0,0 +1,24 @@
<VirtualHost *:80>
ServerName file.kaz.bzh
DocumentRoot /var/jirafeau/
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined
ServerSignature Off
<Location / >
AllowOverride All
Require all granted
Options FollowSymLinks MultiViews
<IfModule mod_dav.c>
Dav off
</IfModule>
</Location>
<FilesMatch "^\.ht.*">
deny from all
satisfy all
ErrorDocument 403 "Access denied."
</FilesMatch>
</VirtualHost>

View File

@ -0,0 +1,44 @@
# jirafeauDir doit être déclaré dans .env qui pointe sur ../../config/docker.env
# car les variables déclarées dans env_file: ne sont pas encore connues dans volumes:
version: '3'
services:
jirafeau:
image: filekaz
build: .
container_name: ${jirafeauServName}
restart: always
networks:
- jirafeauNet
- postfixNet
external_links:
- ${smtpServName}:${smtpHost}
# ports:
# - 8081:80
env_file:
- ../../secret/env-${jirafeauServName}
volumes:
- ./config/jirafeau.conf:/etc/apache2/sites-available/000-default.conf
- fileData:${jirafeauDir}
- ./config/config.local.php:/var/jirafeau/lib/config.local.php
- /etc/localtime:/etc/localtime:ro
- /etc/timezone:/etc/timezone:ro
labels:
- "traefik.enable=true"
- "traefik.http.routers.${jirafeauServName}-admin.rule=Host(`${fileHost}.${domain}`) && PathPrefix(`/admin.php`)"
- "traefik.http.routers.${jirafeauServName}-admin.middlewares=test-adminipwhitelist@file"
- "traefik.http.routers.${jirafeauServName}.rule=Host(`${fileHost}.${domain}`) && ! PathPrefix(`/admin.php`)"
- "traefik.docker.network=jirafeauNet"
volumes:
fileData:
config:
networks:
jirafeauNet:
external: true
name: jirafeauNet
postfixNet:
external: true
name: postfixNet

25
dockers/jirafeau/download.sh Executable file
View File

@ -0,0 +1,25 @@
#!/bin/bash
SERV_DIR=$(cd $(dirname $0); pwd)
KAZ_ROOT=$(cd $(dirname $0)/../..; pwd)
. "${KAZ_ROOT}/bin/.commonFunctions.sh"
setKazVars
SRC_JIR="https://gitlab.com/mojo42/Jirafeau.git"
JIR_VER="4.5.0"
printKazMsg "\n *** Download Jirafeau"
mkdir -p "${KAZ_GIT_DIR}"
cd "${KAZ_GIT_DIR}"
if [ ! -d "Jirafeau" ]; then
git clone "${SRC_JIR}" --branch ${JIR_VER}
fi
cd "${KAZ_GIT_DIR}/Jirafeau"
if [ -z "$(git branch | grep "${JIR_VER}")" ]; then
printKazMsg " checkout branch ${JIR_VER}"
git fetch -a
git reset --hard
git checkout ${JIR_VER}
fi

50
dockers/jirafeau/first.sh Executable file
View File

@ -0,0 +1,50 @@
#!/bin/bash
KAZ_ROOT=$(cd $(dirname $0)/../..; pwd)
. "${KAZ_ROOT}/bin/.commonFunctions.sh"
setKazVars
JIR_VER=4.5.0
cd $(dirname $0)
. "${DOCKERS_ENV}"
. "${KAZ_KEY_DIR}/env-${jirafeauServName}"
CONF_FILE="${DOCK_VOL}/jirafeau_config/_data/config.local.php"
if ! grep -q "'installation_done'\s*=>\s*true" "${CONF_FILE}" 2>/dev/null ; then
printKazMsg "\n *** Premier lancement de Jirafeau"
checkDockerRunning "${jirafeauServName}" "Jirafeau" || exit
curl -X POST \
-d "jirafeau=${JIR_VER}" \
-d "step=1" \
-d "admin_password=${HTTPD_PASSWORD}" \
-d "next=1" \
"${httpProto}://${fileHost}.${domain}/install.php"
curl -X POST -d "jirafeau=${JIR_VER}" \
-d "step=2" \
-d "web_root=${httpProto}://${fileHost}.${domain}/" \
-d "var_root=${jirafeauDir}" \
-d "next=1" \
"${httpProto}://${fileHost}.${domain}/install.php"
fi
updatePhpVar(){
# $1 key
# $2 val
# $3 file
if grep -q "$1" "$3" ; then
sed -i \
-e "s%\([\"']$1[\"']\s*=>\s*\)[^,]*,%\1$2,%" \
"$3"
fi
}
updatePhpVar "style" "'kaz'" "${CONF_FILE}"
updatePhpVar "organisation" "'KAZ'" "${CONF_FILE}"
#updatePhpVar "web_root" "'${httpProto}://${fileHost}.${domain}/'" "${CONF_FILE}"
#updatePhpVar "admin_password" "'$(echo -n "${HTTPD_PASSWORD}" | sha256sum | awk '{print $1}')'" "${CONF_FILE}"
#updatePhpVar "var_root" "'${jirafeauDir}'" "${CONF_FILE}"
#updatePhpVar "installation_done" "true" "${CONF_FILE}"

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 596 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 525 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 492 B

View File

@ -0,0 +1,205 @@
<?php
/*
* Jyraphe, your web file repository
* Copyright (C) 2008 Julien "axolotl" BERNARD <axolotl@magieeternelle.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
/*
* This stylesheet is the default stylesheet for Jyraphe.
* The content is dynamically generated for easier handling.
*/
$dark = '#8B4513';
header('Content-type: text/css');
?>
body {
font-family: sans-serif;
text-align: center;
margin: 2ex auto;
background: white;
/* border: <?php echo $dark; ?> 5px solid; */
}
fieldset {
text-align: left;
width: 40em;
margin: auto;
background: #E2f5ff;
border: 2px solid #02233f;
-moz-border-radius: 10px;
-webkit-border-radius: 10px;
border-radius: 10px;
}
fieldset legend {
color: white;
background: #02233f;
border: 2px solid #02233f;
padding: 1px 5px;
-moz-border-radius: 5px;
-webkit-border-radius: 5px;
border-radius: 5px;
}
h1 {
width: 100%;
text-align: center;
background: url('bandeau.png') left top repeat-x;
height: 70px;
padding-top: 30px;
}
h1 a {
text-decoration: none;
color: white;
}
fieldset p {
margin-left: 25%;
}
.jyraphe_info {
font-size: small;
margin-left: 30%;
}
label {
float: left;
width: 12em;
}
input[type=text], input[type=submit], select {
color: black;
width: 15em;
border: 1px #02233f solid;
background: white;
}
input:hover {
color: white;
background: #02233f;
}
#jyraphe {
background: url('jyraphe.png') right bottom no-repeat;
position: fixed;
bottom: 0;
right: 0;
height: 100px;
width: 100px;
clear:both;
}
#kaz {
background: url('kaz.png') right bottom no-repeat;
position: fixed;
top: 0;
left: 10px;
height: 64px;
width: 64px;
clear:both;
}
#copyright {
text-align: center;
}
.error, .message {
width: 50em;
margin: 5ex auto;
}
.error {
padding-bottom: 1ex;
border: red 2px solid;
background-color: #FBB;
}
.error p:before {
content: url('error.png');
padding-right: 1ex;
}
.message {
padding: 1ex;
border: green 2px solid;
background-color: #BFB;
}
.message p:before {
content: url('ok.png');
padding-right: 1ex;
}
.info {
text-align: left;
width: 40em;
margin: auto;
background: #E2f5ff;
border: 2px solid #02233f;
-moz-border-radius: 10px;
-webkit-border-radius: 10px;
border-radius: 10px;
}
.info h2 {
text-align: center;
}
.info h3 {
text-align: center;
}
.info p {
margin-left: 5%;
margin-right: 5%;
}
#upload {}
#uploading {
text-align: center;
width: 30em;
background: #E2f5ff;
border: 2px solid #02233f;
margin: auto;
}
#upload_finished {
text-align: center;
width: 60em;
background: #E2f5ff;
border: 2px solid #02233f;
margin: auto;
}
#self_destruct {
font-weight: bold;
color: red;
}
#upload_link_email {
margin-left: 10px;
}
#upload_image_email {
padding-left: 20px;
padding-bottom: 15px;
background: url(email.png) no-repeat;
}

Some files were not shown because too many files have changed in this diff Show More