|
|
@ -495,13 +495,110 @@ mailcow:get_default_backup_host_ident() { |
|
|
|
echo "$dest $ident" |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
compose:service:containers() { |
|
|
|
local project="$1" service="$2" |
|
|
|
|
|
|
|
docker ps \ |
|
|
|
--filter label="com.docker.compose.project=$project" \ |
|
|
|
--filter label="compose.master-service=$service" \ |
|
|
|
--format="{{.ID}}" |
|
|
|
} |
|
|
|
export -f compose:service:containers |
|
|
|
|
|
|
|
|
|
|
|
compose:service:container_one() { |
|
|
|
local project="$1" service="$2" container_id |
|
|
|
{ |
|
|
|
read-0a container_id || { |
|
|
|
err "service ${DARKYELLOW}$service${NORMAL} has no running container." |
|
|
|
return 1 |
|
|
|
} |
|
|
|
if read-0a _; then |
|
|
|
err "service ${DARKYELLOW}$service${NORMAL} has more than one running container." |
|
|
|
return 1 |
|
|
|
fi |
|
|
|
} < <(compose:service:containers "$project" "$service") |
|
|
|
echo "$container_id" |
|
|
|
} |
|
|
|
export -f compose:service:container_one |
|
|
|
|
|
|
|
|
|
|
|
compose:service:container_first() { |
|
|
|
local project="$1" service="$2" container_id |
|
|
|
{ |
|
|
|
read-0a container_id || { |
|
|
|
err "service ${DARKYELLOW}$service${NORMAL} has no running container." |
|
|
|
return 1 |
|
|
|
} |
|
|
|
if read-0a _; then |
|
|
|
warn "service ${DARKYELLOW}$service${NORMAL} has more than one running container." |
|
|
|
fi |
|
|
|
} < <(compose:service:containers "$project" "$service") |
|
|
|
echo "$container_id" |
|
|
|
} |
|
|
|
export -f compose:service:container_first |
|
|
|
|
|
|
|
|
|
|
|
compose:charm:containers() { |
|
|
|
local project="$1" charm="$2" |
|
|
|
|
|
|
|
docker ps \ |
|
|
|
--filter label="com.docker.compose.project=$project" \ |
|
|
|
--filter label="compose.charm=$charm" \ |
|
|
|
--format="{{.ID}}" |
|
|
|
} |
|
|
|
export -f compose:charm:containers |
|
|
|
|
|
|
|
|
|
|
|
compose:charm:container_one() { |
|
|
|
local project="$1" charm="$2" container_id |
|
|
|
{ |
|
|
|
read-0a container_id || { |
|
|
|
err "charm ${DARKPINK}$charm${NORMAL} has no running container in project '$project'." |
|
|
|
return 1 |
|
|
|
} |
|
|
|
if read-0a _; then |
|
|
|
err "charm ${DARKPINK}$charm${NORMAL} has more than one running container." |
|
|
|
return 1 |
|
|
|
fi |
|
|
|
} < <(compose:charm:containers "$project" "$charm") |
|
|
|
echo "$container_id" |
|
|
|
} |
|
|
|
export -f compose:charm:container_one |
|
|
|
|
|
|
|
|
|
|
|
compose:charm:container_first() { |
|
|
|
local project="$1" charm="$2" container_id |
|
|
|
{ |
|
|
|
read-0a container_id || { |
|
|
|
warn "charm ${DARKYELLOW}$charm${NORMAL} has no running container in project '$project'." |
|
|
|
} |
|
|
|
if read-0a _; then |
|
|
|
warn "charm ${DARKYELLOW}$charm${NORMAL} has more than one running container." |
|
|
|
fi |
|
|
|
} < <(compose:charm:containers "$project" "$charm") |
|
|
|
echo "$container_id" |
|
|
|
} |
|
|
|
export -f compose:charm:container_first |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
compose:get_url() { |
|
|
|
local service="$1" |
|
|
|
( |
|
|
|
set -o pipefail |
|
|
|
cat "/var/lib/compose/relations/myc/${service}-frontend/web-proxy/data" | |
|
|
|
shyaml get-value url |
|
|
|
) || { |
|
|
|
local project_name="$1" service="$2" data_file network ip |
|
|
|
data_file="/var/lib/compose/relations/${project_name}/${service}-frontend/web-proxy/data" |
|
|
|
if [ -e "$data_file" ]; then |
|
|
|
( |
|
|
|
set -o pipefail |
|
|
|
cat "$data_file" | shyaml get-value url |
|
|
|
) |
|
|
|
else |
|
|
|
## Assume there are no frontend relation here, the url is direct IP |
|
|
|
container_id=$(compose:service:container_one "${project_name}" "${service}") || return 1 |
|
|
|
network_ip=$(docker:container:network_ip_one "${container_id}") || return 1 |
|
|
|
IFS=":" read -r network ip <<<"$network_ip" |
|
|
|
echo "http://$ip" |
|
|
|
fi || { |
|
|
|
err "Failed querying ${service} to frontend relation to get url." |
|
|
|
return 1 |
|
|
|
} |
|
|
@ -509,19 +606,90 @@ compose:get_url() { |
|
|
|
export -f compose:get_url |
|
|
|
|
|
|
|
|
|
|
|
compose:container:service() { |
|
|
|
local container="$1" service |
|
|
|
if ! service=$(docker:container:label "$container" "compose.service"); then |
|
|
|
err "Failed to get service name from container ${container}." |
|
|
|
return 1 |
|
|
|
fi |
|
|
|
if [ -z "$service" ]; then |
|
|
|
err "No service found for container ${container}." |
|
|
|
return 1 |
|
|
|
fi |
|
|
|
echo "$service" |
|
|
|
} |
|
|
|
export -f compose:container:service |
|
|
|
|
|
|
|
compose:psql() { |
|
|
|
local dbname="$1" |
|
|
|
docker exec -i myc_postgres_1 psql -U postgres "$dbname" |
|
|
|
local project_name="$1" dbname="$2" |
|
|
|
container_id=$(compose:charm:container_one "$project_name" "postgres") || return 1 |
|
|
|
docker exec -i "${container_id}" psql -U postgres "$dbname" |
|
|
|
} |
|
|
|
export -f compose:psql |
|
|
|
|
|
|
|
|
|
|
|
compose:pgm() { |
|
|
|
local project_name="$1" container_network_ip container_ip container_network |
|
|
|
shift |
|
|
|
|
|
|
|
container_id=$(compose:charm:container_one "$project_name" "postgres") || return 1 |
|
|
|
service_name=$(compose:container:service "$container_id") || return 1 |
|
|
|
image_id=$(docker:container:image "$container_id") || return 1 |
|
|
|
container_network_ip=$(docker:container:network_ip_one "$container_id") || return 1 |
|
|
|
IFS=":" read -r container_network container_ip <<<"$container_network_ip" |
|
|
|
|
|
|
|
pgpass="/srv/datastore/data/${service_name}/var/lib/postgresql/data/pgpass" |
|
|
|
|
|
|
|
local final_pgm_docker_run_opts+=( |
|
|
|
-u 0 -e prefix_pg_local_command=" " |
|
|
|
--network "${container_network}" |
|
|
|
-e PGHOST="$container_ip" |
|
|
|
-e PGUSER=postgres |
|
|
|
-v "$pgpass:/root/.pgpass" |
|
|
|
"${pgm_docker_run_opts[@]}" |
|
|
|
) |
|
|
|
|
|
|
|
cmd=(docker run --rm \ |
|
|
|
"${final_pgm_docker_run_opts[@]}" \ |
|
|
|
"${image_id}" pgm "$@" |
|
|
|
) |
|
|
|
echo "${cmd[@]}" |
|
|
|
"${cmd[@]}" |
|
|
|
} |
|
|
|
export -f compose:pgm |
|
|
|
|
|
|
|
|
|
|
|
postgres:dump() { |
|
|
|
local project_name="$1" src="$2" dst="$3" |
|
|
|
|
|
|
|
( |
|
|
|
settmpdir PGM_TMP_LOCATION |
|
|
|
|
|
|
|
pgm_docker_run_opts=('-v' "${PGM_TMP_LOCATION}:/tmp/dump") |
|
|
|
compose:pgm "$project_name" cp -f "$src" "/tmp/dump/dump.gz" && |
|
|
|
mv "$PGM_TMP_LOCATION/dump.gz" "$dst" |
|
|
|
) || return 1 |
|
|
|
} |
|
|
|
export -f postgres:dump |
|
|
|
|
|
|
|
|
|
|
|
postgres:restore() { |
|
|
|
local project_name="$1" src="$2" dst="$3" |
|
|
|
|
|
|
|
full_src_path=$(readlink -e "$src") || exit 1 |
|
|
|
( |
|
|
|
pgm_docker_run_opts=('-v' "${full_src_path}:/tmp/dump.gz") |
|
|
|
compose:pgm "$project_name" cp -f "/tmp/dump.gz" "$dst" |
|
|
|
) || return 1 |
|
|
|
} |
|
|
|
export -f postgres:restore |
|
|
|
|
|
|
|
|
|
|
|
cyclos:set_root_url() { |
|
|
|
local dbname="$1" cyclos_service="$2" url |
|
|
|
local project_name="$1" dbname="$2" url="$3" |
|
|
|
|
|
|
|
url=$(compose:get_url "${cyclos_service}") || return 1 |
|
|
|
echo "UPDATE configurations SET root_url = '$url';" | |
|
|
|
compose:psql "$dbname" || { |
|
|
|
compose:psql "$project_name" "$dbname" || { |
|
|
|
err "Failed to set cyclos url value in '$dbname' database." |
|
|
|
return 1 |
|
|
|
} |
|
|
@ -529,9 +697,30 @@ cyclos:set_root_url() { |
|
|
|
export -f cyclos:set_root_url |
|
|
|
|
|
|
|
|
|
|
|
compose:project_name() { |
|
|
|
if [ -z "$PROJECT_NAME" ]; then |
|
|
|
PROJECT_NAME=$(compose --get-project-name) || { |
|
|
|
err "Couldn't get project name." |
|
|
|
return 1 |
|
|
|
} |
|
|
|
if [ -z "$PROJECT_NAME" -o "$PROJECT_NAME" == "orphan" ]; then |
|
|
|
err "Couldn't get project name, probably because 'compose.yml' wasn't found." |
|
|
|
echo " Please ensure to either configure a global 'compose.yml' or run this command" >&2 |
|
|
|
echo " in a compose project (with 'compose.yml' on the top level directory)." >&2 |
|
|
|
return 1 |
|
|
|
fi |
|
|
|
export PROJECT_NAME |
|
|
|
fi |
|
|
|
echo "$PROJECT_NAME" |
|
|
|
} |
|
|
|
export -f compose:project_name |
|
|
|
|
|
|
|
|
|
|
|
compose:get_cron_docker_cmd() { |
|
|
|
local cron_line cmd_line docker_cmd |
|
|
|
if ! cron_line=$(docker exec myc_cron_1 cat /etc/cron.d/rsync-backup | grep "\* \* \*"); then |
|
|
|
project_name=$(compose:project_name) || return 1 |
|
|
|
|
|
|
|
if ! cron_line=$(docker exec "${project_name}"_cron_1 cat /etc/cron.d/rsync-backup | grep "\* \* \*"); then |
|
|
|
err "Can't find cron_line in cron container." |
|
|
|
echo " Have you forgotten to run 'compose up' ?" >&2 |
|
|
|
return 1 |
|
|
@ -559,9 +748,10 @@ compose:get_cron_docker_cmd() { |
|
|
|
|
|
|
|
|
|
|
|
compose:recover-target() { |
|
|
|
local backup_host="$1" ident="$2" src="$3" dst="$4" service_name="${5:-rsync-backup}" |
|
|
|
local backup_host="$1" ident="$2" src="$3" dst="$4" service_name="${5:-rsync-backup}" project_name |
|
|
|
project_name=$(compose:project_name) || return 1 |
|
|
|
|
|
|
|
docker_image="myc_${service_name}" |
|
|
|
docker_image="${project_name}_${service_name}" |
|
|
|
if ! docker_has_image "$docker_image"; then |
|
|
|
compose build "${service_name}" || { |
|
|
|
err "Couldn't find nor build image for service '$service_name'." |
|
|
@ -820,13 +1010,14 @@ set_errlvl() { return "${1:-1}"; } |
|
|
|
cmdline.spec:backup:cmd:compose:run() { |
|
|
|
|
|
|
|
local cron_line args |
|
|
|
project_name=$(compose:project_name) || return 1 |
|
|
|
docker_cmd=$(compose:get_cron_docker_cmd) || return 1 |
|
|
|
|
|
|
|
echo "${WHITE}Launching:${NORMAL} docker exec -i myc_cron_1 $docker_cmd" |
|
|
|
echo "${WHITE}Launching:${NORMAL} docker exec -i "${project_name}_cron_1" $docker_cmd" |
|
|
|
|
|
|
|
{ |
|
|
|
{ |
|
|
|
eval "docker exec -i myc_cron_1 $docker_cmd" | sed -r "s/^/ ${GRAY}|${NORMAL} /g" |
|
|
|
eval "docker exec -i \"${project_name}_cron_1\" $docker_cmd" | sed -r "s/^/ ${GRAY}|${NORMAL} /g" |
|
|
|
set_errlvl "${PIPESTATUS[0]}" |
|
|
|
} 3>&1 1>&2 2>&3 | sed -r "s/^/ $DARKRED\!$NORMAL /g" |
|
|
|
set_errlvl "${PIPESTATUS[0]}" |
|
|
@ -888,22 +1079,23 @@ cmdline.spec:odoo:cmd:restart:run() { |
|
|
|
local out odoo_service |
|
|
|
|
|
|
|
odoo_service="${opt_service:-odoo}" |
|
|
|
project_name=$(compose:project_name) || return 1 |
|
|
|
|
|
|
|
if ! out=$(docker restart "myc_${odoo_service}_1" 2>&1); then |
|
|
|
if ! out=$(docker restart "${project_name}_${odoo_service}_1" 2>&1); then |
|
|
|
if [[ "$out" == *"no matching entries in passwd file" ]]; then |
|
|
|
warn "Catched docker bug. Restarting once more." |
|
|
|
if ! out=$(docker restart "myc_${odoo_service}_1"); then |
|
|
|
err "Can't restart container myc_${odoo_service}_1 (restarted twice)." |
|
|
|
if ! out=$(docker restart "${project_name}_${odoo_service}_1"); then |
|
|
|
err "Can't restart container ${project_name}_${odoo_service}_1 (restarted twice)." |
|
|
|
echo " output:" >&2 |
|
|
|
echo "$out" | prefix " ${GRAY}|${NORMAL} " >&2 |
|
|
|
exit 1 |
|
|
|
fi |
|
|
|
else |
|
|
|
err "Couldn't restart container myc_${odoo_service}_1 (and no restart bug detected)." |
|
|
|
err "Couldn't restart container ${project_name}_${odoo_service}_1 (and no restart bug detected)." |
|
|
|
exit 1 |
|
|
|
fi |
|
|
|
fi |
|
|
|
info "Container myc_${odoo_service}_1 was ${DARKGREEN}successfully${NORMAL} restarted." |
|
|
|
info "Container ${project_name}_${odoo_service}_1 was ${DARKGREEN}successfully${NORMAL} restarted." |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
@ -921,7 +1113,8 @@ cmdline.spec:odoo:cmd:restore:run() { |
|
|
|
|
|
|
|
odoo_service="${opt_service:-odoo}" |
|
|
|
|
|
|
|
if [[ "$ZIP_DUMP_LOCATION" == "http://"* ]]; then |
|
|
|
if [[ "$ZIP_DUMP_LOCATION" == "http://"* ]] || |
|
|
|
[[ "$ZIP_DUMP_LOCATION" == "https://"* ]]; then |
|
|
|
settmpdir ZIP_TMP_LOCATION |
|
|
|
tmp_location="$ZIP_TMP_LOCATION/dump.zip" |
|
|
|
curl -k -s -L "$ZIP_DUMP_LOCATION" > "$tmp_location" || { |
|
|
@ -1015,11 +1208,11 @@ cmdline.spec:odoo:cmd:set-cyclos-url:run() { |
|
|
|
|
|
|
|
dbname=${opt_database:-odoo} |
|
|
|
cyclos_service="${opt_service:-cyclos}" |
|
|
|
|
|
|
|
URL=$(compose:get_url "${cyclos_service}") || exit 1 |
|
|
|
project_name=$(compose:project_name) || exit 1 |
|
|
|
URL=$(compose:get_url "${project_name}" "${cyclos_service}") || exit 1 |
|
|
|
|
|
|
|
echo "UPDATE res_company SET cyclos_server_url = '$URL/api' WHERE id=1;" | |
|
|
|
compose:psql "$dbname" || { |
|
|
|
compose:psql "$project_name" "$dbname" || { |
|
|
|
err "Failed to set cyclos url value in '$dbname' database." |
|
|
|
exit 1 |
|
|
|
} |
|
|
@ -1043,13 +1236,18 @@ cmdline.spec:cyclos:cmd:dump:run() { |
|
|
|
|
|
|
|
cyclos_service="${opt_service:-cyclos}" |
|
|
|
cyclos_database="${opt_database:-cyclos}" |
|
|
|
project_name=$(compose:project_name) || exit 1 |
|
|
|
container_id=$(compose:service:container_one "$project_name" "${cyclos_service}") || exit 1 |
|
|
|
|
|
|
|
|
|
|
|
Wrap -d "stop ${DARKYELLOW}${cyclos_service}${NORMAL}'s container" -- \ |
|
|
|
docker stop "$container_id" || exit 1 |
|
|
|
|
|
|
|
Wrap -d "stop container 'myc_${cyclos_service}_1'" -- \ |
|
|
|
docker stop "myc_${cyclos_service}_1" || exit 1 |
|
|
|
Wrap -d "Dump postgres database '${cyclos_database}'." -- \ |
|
|
|
pgm cp "$cyclos_database" "$DUMP_GZFILE" || exit 1 |
|
|
|
Wrap -d "start container 'myc_${cyclos_service}_1'." -- \ |
|
|
|
docker start "myc_${cyclos_service}_1" || exit 1 |
|
|
|
postgres:dump "${project_name}" "$cyclos_database" "$DUMP_GZFILE" || exit 1 |
|
|
|
|
|
|
|
Wrap -d "start ${DARKYELLOW}${cyclos_service}${NORMAL}'s container" -- \ |
|
|
|
docker start "${container_id}" || exit 1 |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
@ -1066,13 +1264,23 @@ cmdline.spec:cyclos:cmd:restore:run() { |
|
|
|
|
|
|
|
cyclos_service="${opt_service:-cyclos}" |
|
|
|
cyclos_database="${opt_database:-cyclos}" |
|
|
|
project_name=$(compose:project_name) || exit 1 |
|
|
|
url=$(compose:get_url "${project_name}" "${cyclos_service}") || return 1 |
|
|
|
container_id=$(compose:service:container_one "$project_name" "${cyclos_service}") || exit 1 |
|
|
|
|
|
|
|
if [[ "$GZ_DUMP_LOCATION" == "http://"* ]]; then |
|
|
|
if [[ "$GZ_DUMP_LOCATION" == "http://"* ]] || |
|
|
|
[[ "$GZ_DUMP_LOCATION" == "https://"* ]]; then |
|
|
|
settmpdir GZ_TMP_LOCATION |
|
|
|
tmp_location="$GZ_TMP_LOCATION/dump.gz" |
|
|
|
Wrap -d "get '$GZ_DUMP_LOCATION'" <<EOF |
|
|
|
|
|
|
|
curl -k -s -L "$GZ_DUMP_LOCATION" > "$tmp_location" || exit 1 |
|
|
|
Wrap -d "get '$GZ_DUMP_LOCATION'" <<EOF || exit 1 |
|
|
|
|
|
|
|
## Note that curll version before 7.76.0 do not have |
|
|
|
curl -k -s -L "$GZ_DUMP_LOCATION" --fail \\ |
|
|
|
> "$tmp_location" || { |
|
|
|
echo "Error fetching ressource. Is url correct ?" >&2 |
|
|
|
exit 1 |
|
|
|
} |
|
|
|
|
|
|
|
if [[ "\$(dd if="$tmp_location" count=2 bs=1 2>/dev/null | |
|
|
|
hexdump -v -e "/1 \"%02x\"")" != "1f8b" ]]; then |
|
|
@ -1089,27 +1297,27 @@ EOF |
|
|
|
exit 1 |
|
|
|
} |
|
|
|
|
|
|
|
Wrap -d "stop container 'myc_${cyclos_service}_1'" -- \ |
|
|
|
docker stop "myc_${cyclos_service}_1" || exit 1 |
|
|
|
Wrap -d "stop ${DARKYELLOW}${cyclos_service}${NORMAL}'s container" -- \ |
|
|
|
docker stop "$container_id" || exit 1 |
|
|
|
|
|
|
|
## XXXvlab: making the assumption that the postgres username should |
|
|
|
## be the same as the cyclos service selected (which is the default, |
|
|
|
## but not always the case). |
|
|
|
Wrap -d "restore postgres database '${cyclos_database}'." -- \ |
|
|
|
pgm cp -f "$GZ_DUMP_LOCATION" "${cyclos_service}@${cyclos_database}" || exit 1 |
|
|
|
postgres:restore "$project_name" "$GZ_DUMP_LOCATION" "${cyclos_service}@${cyclos_database}" || exit 1 |
|
|
|
## ensure that the database is not locked |
|
|
|
|
|
|
|
## XXXvlab: 70 is uid of user postgres, this avoids the docker bug |
|
|
|
## but introduce hardwritten value |
|
|
|
Wrap -d "check and remove database lock if any" <<EOF |
|
|
|
echo "delete from database_lock;" | compose:psql "${cyclos_database}" || exit 1 |
|
|
|
Wrap -d "check and remove database lock if any" <<EOF || exit 1 |
|
|
|
echo "delete from database_lock;" | compose:psql "${project_name}" "${cyclos_database}" |
|
|
|
EOF |
|
|
|
|
|
|
|
Wrap -d "set root url" -- \ |
|
|
|
cyclos:set_root_url "$cyclos_database" "$cyclos_service" || exit 1 |
|
|
|
Wrap -d "set root url to '$url'" -- \ |
|
|
|
cyclos:set_root_url "${project_name}" "${cyclos_database}" "${url}" || exit 1 |
|
|
|
|
|
|
|
Wrap -d "start container 'myc_${cyclos_service}_1'." -- \ |
|
|
|
docker start "myc_${cyclos_service}_1" || exit 1 |
|
|
|
Wrap -d "start ${DARKYELLOW}${cyclos_service}${NORMAL}'s container" -- \ |
|
|
|
docker start "${container_id}" || exit 1 |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
@ -1124,19 +1332,20 @@ cmdline.spec:cyclos:cmd:set-root-url:run() { |
|
|
|
|
|
|
|
cyclos_database=${opt_database:-cyclos} |
|
|
|
cyclos_service="${opt_service:-cyclos}" |
|
|
|
project_name=$(compose:project_name) || exit 1 |
|
|
|
url=$(compose:get_url "${project_name}" "${cyclos_service}") || exit 1 |
|
|
|
container_id=$(compose:service:container_one "${project_name}" "${cyclos_service}") || exit 1 |
|
|
|
|
|
|
|
Wrap -d "stop container 'myc_${cyclos_service}_1'" -- \ |
|
|
|
docker stop "myc_${cyclos_service}_1" || exit 1 |
|
|
|
Wrap -d "stop ${DARKYELLOW}${cyclos_service}${NORMAL}'s container" -- \ |
|
|
|
docker stop "$container_id" || exit 1 |
|
|
|
|
|
|
|
Wrap -d "set root url" -- \ |
|
|
|
cyclos:set_root_url "$cyclos_database" "$cyclos_service" || exit 1 |
|
|
|
Wrap -d "set root url to '$url'" -- \ |
|
|
|
cyclos:set_root_url "${project_name}" "${cyclos_database}" "${url}" || exit 1 |
|
|
|
|
|
|
|
Wrap -d "start container 'myc_${cyclos_service}_1'." -- \ |
|
|
|
docker start "myc_${cyclos_service}_1" || exit 1 |
|
|
|
Wrap -d "start ${DARKYELLOW}${cyclos_service}${NORMAL}'s container" -- \ |
|
|
|
docker start "${container_id}" || exit 1 |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
cmdline::parse "$@" |