diff options
author | Ismaël Bouya <ismael.bouya@normalesup.org> | 2018-03-19 16:21:53 +0100 |
---|---|---|
committer | Ismaël Bouya <ismael.bouya@normalesup.org> | 2018-03-19 16:21:53 +0100 |
commit | 98311fc2ea91cc2a5f00e9fa85a30f50fde77e79 (patch) | |
tree | 58ff0ffad8cbe0230eff1791360bc7cc45501c34 /modules | |
parent | f4c9ed4c0a32082d8f7b60cee1eb33cb05c85a1c (diff) | |
parent | d87a489f9585d10f0a185beb59ae16a10f27a7bd (diff) | |
download | Puppet-98311fc2ea91cc2a5f00e9fa85a30f50fde77e79.tar.gz Puppet-98311fc2ea91cc2a5f00e9fa85a30f50fde77e79.tar.zst Puppet-98311fc2ea91cc2a5f00e9fa85a30f50fde77e79.zip |
Merge branch 'backup' into dev
Diffstat (limited to 'modules')
18 files changed, 360 insertions, 5 deletions
diff --git a/modules/base_installation/files/cronie/puppet-post-merge b/modules/base_installation/files/cronie/puppet-post-merge index ac5e3ff..f5c21a7 100644 --- a/modules/base_installation/files/cronie/puppet-post-merge +++ b/modules/base_installation/files/cronie/puppet-post-merge | |||
@@ -1,7 +1,7 @@ | |||
1 | #!/bin/bash | 1 | #!/bin/bash |
2 | ## Run Puppet locally using puppet apply | 2 | ## Run Puppet locally using puppet_apply |
3 | git submodule update --init | 3 | git submodule update --init |
4 | /usr/bin/puppet apply `pwd`/manifests/site.pp | 4 | /usr/local/sbin/puppet_apply `pwd`/manifests/site.pp |
5 | 5 | ||
6 | ## Log status of the Puppet run | 6 | ## Log status of the Puppet run |
7 | if [ $? -eq 0 ] | 7 | if [ $? -eq 0 ] |
diff --git a/modules/base_installation/files/scripts/puppet_apply b/modules/base_installation/files/scripts/puppet_apply new file mode 100644 index 0000000..69673cc --- /dev/null +++ b/modules/base_installation/files/scripts/puppet_apply | |||
@@ -0,0 +1,23 @@ | |||
1 | #!/bin/bash | ||
2 | |||
3 | lockfile=/var/run/puppet-apply.lock | ||
4 | path=`dirname $0` | ||
5 | path=`cd $path/..; pwd` | ||
6 | |||
7 | if [ $(id -u) -gt 0 ]; then | ||
8 | echo "You must be root to run this script." >&2 | ||
9 | exit 2 | ||
10 | fi | ||
11 | |||
12 | if (set -o noclobber; echo "$$" > "$lockfile") 2> /dev/null; then | ||
13 | trap 'rm -f "$lockfile"; exit $?' INT TERM EXIT | ||
14 | |||
15 | puppet apply "$@" | ||
16 | |||
17 | rm -f "$lockfile" | ||
18 | trap - INT TERM EXIT | ||
19 | else | ||
20 | echo "Failed to acquire lockfile: $lockfile." >&2 | ||
21 | echo "Held by $(cat $lockfile 2>/dev/null)" >&2 | ||
22 | exit 1 | ||
23 | fi | ||
diff --git a/modules/base_installation/files/scripts/puppet_reset_and_apply b/modules/base_installation/files/scripts/puppet_reset_and_apply index 6743044..0350e6e 100644 --- a/modules/base_installation/files/scripts/puppet_reset_and_apply +++ b/modules/base_installation/files/scripts/puppet_reset_and_apply | |||
@@ -11,4 +11,4 @@ fi | |||
11 | git reset --hard origin/$branch | 11 | git reset --hard origin/$branch |
12 | 12 | ||
13 | git submodule update --init | 13 | git submodule update --init |
14 | puppet apply --test manifests/site.pp | 14 | puppet_apply --test manifests/site.pp |
diff --git a/modules/base_installation/manifests/cronie.pp b/modules/base_installation/manifests/cronie.pp index e8d5dfd..e8f3e20 100644 --- a/modules/base_installation/manifests/cronie.pp +++ b/modules/base_installation/manifests/cronie.pp | |||
@@ -19,13 +19,13 @@ class base_installation::cronie inherits base_installation { | |||
19 | } | 19 | } |
20 | cron { 'puppet-apply': | 20 | cron { 'puppet-apply': |
21 | ensure => present, | 21 | ensure => present, |
22 | command => "cd $base_installation::puppet_code_path ; puppet apply $base_installation::puppet_code_path/manifests/site.pp", | 22 | command => "cd $base_installation::puppet_code_path ; puppet_apply $base_installation::puppet_code_path/manifests/site.pp", |
23 | user => root, | 23 | user => root, |
24 | minute => '*/20' | 24 | minute => '*/20' |
25 | } | 25 | } |
26 | cron { 'puppet-apply-reboot': | 26 | cron { 'puppet-apply-reboot': |
27 | ensure => present, | 27 | ensure => present, |
28 | command => "cd $base_installation::puppet_code_path ; puppet apply $base_installation::puppet_code_path/manifests/site.pp", | 28 | command => "cd $base_installation::puppet_code_path ; puppet_apply $base_installation::puppet_code_path/manifests/site.pp", |
29 | user => root, | 29 | user => root, |
30 | special => "reboot" | 30 | special => "reboot" |
31 | } | 31 | } |
diff --git a/modules/base_installation/manifests/puppet.pp b/modules/base_installation/manifests/puppet.pp index 6f7732d..0cb43bc 100644 --- a/modules/base_installation/manifests/puppet.pp +++ b/modules/base_installation/manifests/puppet.pp | |||
@@ -39,6 +39,12 @@ class base_installation::puppet ( | |||
39 | source => "puppet:///modules/base_installation/scripts/report_print.rb" | 39 | source => "puppet:///modules/base_installation/scripts/report_print.rb" |
40 | } | 40 | } |
41 | 41 | ||
42 | file { '/usr/local/sbin/puppet_apply': | ||
43 | mode => "0755", | ||
44 | ensure => present, | ||
45 | source => "puppet:///modules/base_installation/scripts/puppet_apply", | ||
46 | } | ||
47 | |||
42 | unless empty(find_file($password_seed)) { | 48 | unless empty(find_file($password_seed)) { |
43 | $ldap_password = generate_password(24, $password_seed, "ldap") | 49 | $ldap_password = generate_password(24, $password_seed, "ldap") |
44 | $ssha_ldap_seed = generate_password(5, $password_seed, "ldap_seed") | 50 | $ssha_ldap_seed = generate_password(5, $password_seed, "ldap_seed") |
diff --git a/modules/profile/manifests/known_hosts.pp b/modules/profile/manifests/known_hosts.pp new file mode 100644 index 0000000..ed9ec8e --- /dev/null +++ b/modules/profile/manifests/known_hosts.pp | |||
@@ -0,0 +1,11 @@ | |||
1 | class profile::known_hosts ( | ||
2 | Optional[Array] $hosts = [] | ||
3 | ) { | ||
4 | $hosts.each |$host| { | ||
5 | sshkey { $host["name"]: | ||
6 | ensure => "present", | ||
7 | key => $host["key"], | ||
8 | type => $host["type"], | ||
9 | } | ||
10 | } | ||
11 | } | ||
diff --git a/modules/profile/manifests/mail.pp b/modules/profile/manifests/mail.pp new file mode 100644 index 0000000..cc47b77 --- /dev/null +++ b/modules/profile/manifests/mail.pp | |||
@@ -0,0 +1,14 @@ | |||
1 | class profile::mail ( | ||
2 | String $mailhub, | ||
3 | Optional[Integer] $mailhub_port = 25, | ||
4 | ) { | ||
5 | ensure_packages(["s-nail", "ssmtp"]) | ||
6 | |||
7 | $hostname = lookup("base_installation::real_hostname") | ||
8 | |||
9 | file { "/etc/ssmtp/ssmtp.conf": | ||
10 | ensure => "present", | ||
11 | content => template("profile/mail/ssmtp.conf.erb"), | ||
12 | } | ||
13 | } | ||
14 | |||
diff --git a/modules/profile/templates/mail/ssmtp.conf.erb b/modules/profile/templates/mail/ssmtp.conf.erb new file mode 100644 index 0000000..e7a0410 --- /dev/null +++ b/modules/profile/templates/mail/ssmtp.conf.erb | |||
@@ -0,0 +1,14 @@ | |||
1 | # | ||
2 | # /etc/ssmtp.conf -- a config file for sSMTP sendmail. | ||
3 | # | ||
4 | # The person who gets all mail for userids < 1000 | ||
5 | # Make this empty to disable rewriting. | ||
6 | root=postmaster | ||
7 | # The place where the mail goes. The actual machine name is required | ||
8 | # no MX records are consulted. Commonly mailhosts are named mail.domain.com | ||
9 | # The example will fit if you are in domain.com and you mailhub is so named. | ||
10 | mailhub=<%= @mailhub %>:<%= @mailhub_port %> | ||
11 | # Where will the mail seem to come from? | ||
12 | #rewriteDomain=y | ||
13 | # The full hostname | ||
14 | hostname=<%= @hostname %> | ||
diff --git a/modules/role/manifests/backup.pp b/modules/role/manifests/backup.pp new file mode 100644 index 0000000..edfd5e0 --- /dev/null +++ b/modules/role/manifests/backup.pp | |||
@@ -0,0 +1,122 @@ | |||
1 | class role::backup ( | ||
2 | String $user, | ||
3 | String $group, | ||
4 | String $mailto, | ||
5 | Optional[Array] $backups = [], | ||
6 | Optional[String] $mountpoint = "/backup1", | ||
7 | Optional[String] $backup_script = "/usr/local/bin/backup.sh", | ||
8 | ) { | ||
9 | include "base_installation" | ||
10 | |||
11 | include "profile::mail" | ||
12 | include "profile::tools" | ||
13 | include "profile::xmr_stak" | ||
14 | include "profile::known_hosts" | ||
15 | |||
16 | ssh_keygen { $user: | ||
17 | notify => Notify_refresh["notify-backup-sshkey-change"] | ||
18 | } | ||
19 | |||
20 | $hosts = $backups.map |$backup| { $backup["host"] } | ||
21 | |||
22 | notify_refresh { "notify-backup-sshkey-change": | ||
23 | message => template("role/backup/ssh_key_changed.info.erb"), | ||
24 | refreshonly => true | ||
25 | } | ||
26 | |||
27 | $hosts.each |$host| { | ||
28 | notify_refresh { "notify-backup-sshhost-$host-changed": | ||
29 | message => template("role/backup/ssh_host_changed.info.erb"), | ||
30 | refreshonly => true, | ||
31 | subscribe => Sshkey[$host], | ||
32 | } | ||
33 | } | ||
34 | |||
35 | concat { $backup_script: | ||
36 | ensure => "present", | ||
37 | ensure_newline => true, | ||
38 | mode => "0755", | ||
39 | } | ||
40 | |||
41 | cron { "backup": | ||
42 | ensure => present, | ||
43 | command => $backup_script, | ||
44 | user => $user, | ||
45 | minute => 25, | ||
46 | hour => 3, | ||
47 | require => Concat[$backup_script], | ||
48 | } | ||
49 | |||
50 | concat::fragment { "backup_head": | ||
51 | target => $backup_script, | ||
52 | content => template("role/backup/backup_head.sh.erb"), | ||
53 | order => "01-50", | ||
54 | } | ||
55 | |||
56 | concat::fragment { "backup_tail": | ||
57 | target => $backup_script, | ||
58 | content => template("role/backup/backup_tail.sh.erb"), | ||
59 | order => "99-50", | ||
60 | } | ||
61 | |||
62 | $backups.each |$infos| { | ||
63 | $dirname = $infos["name"] | ||
64 | $login = $infos["login"] | ||
65 | $host = $infos["host"] | ||
66 | $dest = "$login@$host" | ||
67 | $base = "$mountpoint/$dirname" | ||
68 | $nbr = $infos["nbr"] | ||
69 | $order_dirname = $infos["order"] | ||
70 | |||
71 | file { $base: | ||
72 | ensure => "directory", | ||
73 | owner => $user, | ||
74 | group => $group, | ||
75 | require => Mount[$mountpoint], | ||
76 | } -> | ||
77 | file { "$base/older": | ||
78 | ensure => "directory", | ||
79 | owner => $user, | ||
80 | group => $group, | ||
81 | } -> | ||
82 | file { "$base/rsync_output": | ||
83 | ensure => "directory", | ||
84 | owner => $user, | ||
85 | group => $group, | ||
86 | } | ||
87 | |||
88 | concat::fragment { "backup_${dirname}_head": | ||
89 | target => $backup_script, | ||
90 | content => template("role/backup/backup_dirname_head.sh.erb"), | ||
91 | order => "$order_dirname-01", | ||
92 | } | ||
93 | |||
94 | concat::fragment { "backup_${dirname}_tail": | ||
95 | target => $backup_script, | ||
96 | content => template("role/backup/backup_dirname_tail.sh.erb"), | ||
97 | order => "$order_dirname-99", | ||
98 | } | ||
99 | |||
100 | $infos["parts"].each |$part| { | ||
101 | $local_folder = $part["local_folder"] | ||
102 | $remote_folder = $part["remote_folder"] | ||
103 | $exclude_from = $part["exclude_from"] | ||
104 | $files_from = $part["files_from"] | ||
105 | $args = $part["args"] | ||
106 | $order_part = $part["order"] | ||
107 | |||
108 | file { "$base/$local_folder": | ||
109 | ensure => "directory", | ||
110 | owner => $user, | ||
111 | group => $group, | ||
112 | require => File[$base], | ||
113 | } | ||
114 | |||
115 | concat::fragment { "backup_${dirname}_${local_folder}": | ||
116 | target => $backup_script, | ||
117 | content => template("role/backup/backup_dirname_part.sh.erb"), | ||
118 | order => "$order_dirname-$order_part", | ||
119 | } | ||
120 | } | ||
121 | } | ||
122 | } | ||
diff --git a/modules/role/templates/backup/backup_dirname_head.sh.erb b/modules/role/templates/backup/backup_dirname_head.sh.erb new file mode 100644 index 0000000..e20cfd3 --- /dev/null +++ b/modules/role/templates/backup/backup_dirname_head.sh.erb | |||
@@ -0,0 +1,27 @@ | |||
1 | ##### <%= @dirname %> ##### | ||
2 | DEST="<%= @dest %>" | ||
3 | BASE="<%= @base %>" | ||
4 | OLD_BAK_BASE=$BASE/older/j | ||
5 | BAK_BASE=${OLD_BAK_BASE}0 | ||
6 | RSYNC_OUTPUT=$BASE/rsync_output | ||
7 | NBR=<%= @nbr %> | ||
8 | |||
9 | if ! ssh \ | ||
10 | -o PreferredAuthentications=publickey \ | ||
11 | -o StrictHostKeyChecking=yes \ | ||
12 | -o ClearAllForwardings=yes \ | ||
13 | $DEST backup; then | ||
14 | echo "Fichier de verrouillage backup sur $DEST ou impossible de se connecter" >&2 | ||
15 | skip=$DEST | ||
16 | fi | ||
17 | |||
18 | rm -rf ${OLD_BAK_BASE}${NBR} | ||
19 | for j in `seq -w $(($NBR-1)) -1 0`; do | ||
20 | [ ! -d ${OLD_BAK_BASE}$j ] && continue | ||
21 | mv ${OLD_BAK_BASE}$j ${OLD_BAK_BASE}$(($j+1)) | ||
22 | done | ||
23 | mkdir $BAK_BASE | ||
24 | mv $RSYNC_OUTPUT $BAK_BASE | ||
25 | mkdir $RSYNC_OUTPUT | ||
26 | |||
27 | if [ "$skip" != "$DEST" ]; then | ||
diff --git a/modules/role/templates/backup/backup_dirname_part.sh.erb b/modules/role/templates/backup/backup_dirname_part.sh.erb new file mode 100644 index 0000000..ec662c4 --- /dev/null +++ b/modules/role/templates/backup/backup_dirname_part.sh.erb | |||
@@ -0,0 +1,26 @@ | |||
1 | ### <%= @dirname %> <%= @local_folder %> ### | ||
2 | LOCAL="<%= @local_folder %>" | ||
3 | REMOTE="<%= @remote_folder %>" | ||
4 | |||
5 | cd $BASE/$LOCAL | ||
6 | cat > $EXCL_FROM <<EOF | ||
7 | <%= @exclude_from.join("\n") %> | ||
8 | EOF | ||
9 | cat > $FILES_FROM <<EOF | ||
10 | <%= @files_from.join("\n") %> | ||
11 | EOF | ||
12 | |||
13 | OUT=$RSYNC_OUTPUT/$LOCAL | ||
14 | rsync -XAavbrz --fake-super -e ssh --numeric-ids --delete \ | ||
15 | --backup-dir=$BAK_BASE/$LOCAL \ | ||
16 | <%- unless @args.empty? -%> | ||
17 | <%= @args %>\ | ||
18 | <% end -%> | ||
19 | <%- unless @exclude_from.empty? -%> | ||
20 | --exclude-from=$EXCL_FROM \ | ||
21 | <% end -%> | ||
22 | <%- unless @files_from.empty? -%> | ||
23 | --files-from=$FILES_FROM \ | ||
24 | <% end -%> | ||
25 | $DEST:$REMOTE . > $OUT || true | ||
26 | ### End <%= @dirname %> <%= @local_folder %> ### | ||
diff --git a/modules/role/templates/backup/backup_dirname_tail.sh.erb b/modules/role/templates/backup/backup_dirname_tail.sh.erb new file mode 100644 index 0000000..6b16c9d --- /dev/null +++ b/modules/role/templates/backup/backup_dirname_tail.sh.erb | |||
@@ -0,0 +1,4 @@ | |||
1 | |||
2 | ssh $DEST sh -c "date > .last_backup" | ||
3 | fi # [ "$skip" != "$DEST" ] | ||
4 | ##### End <%= @dirname %> ##### | ||
diff --git a/modules/role/templates/backup/backup_head.sh.erb b/modules/role/templates/backup/backup_head.sh.erb new file mode 100644 index 0000000..be9f5bf --- /dev/null +++ b/modules/role/templates/backup/backup_head.sh.erb | |||
@@ -0,0 +1,20 @@ | |||
1 | #!/bin/bash | ||
2 | MAILTO="<%= @mailto %>" | ||
3 | |||
4 | EXCL_FROM=`mktemp` | ||
5 | FILES_FROM=`mktemp` | ||
6 | TMP_STDERR=`mktemp` | ||
7 | |||
8 | on_exit() { | ||
9 | if [ -s "$TMP_STDERR" ]; then | ||
10 | cat "$TMP_STDERR" | mail -Ssendwait -s "save_distant rsync error" "$MAILTO" | ||
11 | fi | ||
12 | rm -f $TMP_STDERR $EXCL_FROM $FILES_FROM | ||
13 | } | ||
14 | |||
15 | trap "on_exit" EXIT | ||
16 | |||
17 | exec 2> "$TMP_STDERR" | ||
18 | exec < /dev/null | ||
19 | |||
20 | set -e | ||
diff --git a/modules/role/templates/backup/backup_immae_eu.sh.erb b/modules/role/templates/backup/backup_immae_eu.sh.erb new file mode 100644 index 0000000..4fab30e --- /dev/null +++ b/modules/role/templates/backup/backup_immae_eu.sh.erb | |||
@@ -0,0 +1,79 @@ | |||
1 | #!/bin/bash | ||
2 | DEST="<%= @dest %>" | ||
3 | MAILTO="<%= @mailto %>" | ||
4 | BASE="<%= @base %>" | ||
5 | OLD_BAK_BASE=$BASE/older/j | ||
6 | BAK_BASE=${OLD_BAK_BASE}0 | ||
7 | RSYNC_OUTPUT=$BASE/rsync_output | ||
8 | NBR=7 | ||
9 | |||
10 | TMP=`mktemp` | ||
11 | TMP_STDERR=`mktemp` | ||
12 | |||
13 | trap "rm -f $TMP $TMP_STDERR" EXIT | ||
14 | |||
15 | exec 2> "$TMP_STDERR" | ||
16 | |||
17 | set -e | ||
18 | if ! `ssh -o ClearAllForwardings=yes $DEST backup`; then | ||
19 | echo "Fichier de verrouillage backup sur $DEST" | ||
20 | exit 1 | ||
21 | fi | ||
22 | |||
23 | rm -rf ${OLD_BAK_BASE}${NBR} | ||
24 | for j in `seq -w $(($NBR-1)) -1 0`; do | ||
25 | [ ! -d ${OLD_BAK_BASE}$j ] && continue | ||
26 | mv ${OLD_BAK_BASE}$j ${OLD_BAK_BASE}$(($j+1)) | ||
27 | done | ||
28 | mkdir $BAK_BASE | ||
29 | mv $RSYNC_OUTPUT $BAK_BASE | ||
30 | mkdir $RSYNC_OUTPUT | ||
31 | |||
32 | ############## | ||
33 | NAME="home" | ||
34 | FOLDER="/home/immae" | ||
35 | |||
36 | cd $BASE/$NAME | ||
37 | cat > $TMP <<EOF | ||
38 | /.no_backup/ | ||
39 | /hosts/florian/nobackup/ | ||
40 | /hosts/connexionswing.com/ | ||
41 | /hosts/connexionswing.immae.eu/ | ||
42 | /hosts/ludivine.immae.eu/ | ||
43 | /hosts/ludivinecassal.com/ | ||
44 | /hosts/piedsjaloux.fr/ | ||
45 | /hosts/piedsjaloux.immae.eu/ | ||
46 | /hosts/spip/sites/*/ | ||
47 | /hosts/spip/spip* | ||
48 | EOF | ||
49 | OUT=$RSYNC_OUTPUT/$NAME | ||
50 | rsync -XAavbrz --fake-super -e ssh --numeric-ids --delete \ | ||
51 | --backup-dir=$BAK_BASE/$NAME --exclude-from=$TMP \ | ||
52 | $DEST:$FOLDER . > $OUT || true | ||
53 | |||
54 | ############## | ||
55 | NAME="system" | ||
56 | FOLDER="/" | ||
57 | |||
58 | cd $BASE/$NAME | ||
59 | cat > $TMP <<EOF | ||
60 | /etc/ | ||
61 | /srv/ | ||
62 | /var/lib/ | ||
63 | /var/spool/ | ||
64 | /var/named/ | ||
65 | /usr/local/ | ||
66 | EOF | ||
67 | OUT=$RSYNC_OUTPUT/$NAME | ||
68 | rsync -XAavbrz -R --fake-super -e ssh --numeric-ids --delete \ | ||
69 | --rsync-path='sudo rsync' \ | ||
70 | --backup-dir=$BAK_BASE/$NAME \ | ||
71 | --files-from=$TMP \ | ||
72 | $DEST:$FOLDER . > $OUT || true | ||
73 | |||
74 | ############## | ||
75 | ssh $DEST sh -c "date > .last_backup" | ||
76 | |||
77 | if [ -s "$TMP_STDERR" ]; then | ||
78 | cat "$TMP_STDERR" | mail -Ssendwait -s "save_distant rsync error" "$MAILTO" | ||
79 | fi | ||
diff --git a/modules/role/templates/backup/backup_tail.sh.erb b/modules/role/templates/backup/backup_tail.sh.erb new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/modules/role/templates/backup/backup_tail.sh.erb | |||
diff --git a/modules/role/templates/backup/ssh_host_changed.info.erb b/modules/role/templates/backup/ssh_host_changed.info.erb new file mode 100644 index 0000000..ebf202e --- /dev/null +++ b/modules/role/templates/backup/ssh_host_changed.info.erb | |||
@@ -0,0 +1,4 @@ | |||
1 | Host <%= @host %> added, please send <%= @user %> key if necessary. | ||
2 | <%- if File.exist?("/home/#{@user}/.ssh/id_rsa.pub") %> | ||
3 | <%= File.read("/home/#{@user}/.ssh/id_rsa.pub") %> | ||
4 | <% end -%> | ||
diff --git a/modules/role/templates/backup/ssh_key_changed.info.erb b/modules/role/templates/backup/ssh_key_changed.info.erb new file mode 100644 index 0000000..43fd2ec --- /dev/null +++ b/modules/role/templates/backup/ssh_key_changed.info.erb | |||
@@ -0,0 +1,5 @@ | |||
1 | ssh key of <%= @user %> changed, | ||
2 | please update hosts: | ||
3 | <%- @hosts.each do |host| %> | ||
4 | - <%= host %> | ||
5 | <% end -%> | ||
diff --git a/modules/ssh_keygen b/modules/ssh_keygen new file mode 160000 | |||
Subproject ca53363249b58af96f90cb810c7c51dda8ba803 | |||