Skip to content

Commit f67ba20

Browse files
committed
Support Ubuntu 20.04 and localized setup messages
1 parent 5b36466 commit f67ba20

9 files changed

Lines changed: 122 additions & 65 deletions

File tree

.github/workflows/tests.yml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,14 @@ permissions:
1010
jobs:
1111
test:
1212
runs-on: ubuntu-latest
13+
strategy:
14+
matrix:
15+
python-version: ["3.8", "3.12"]
1316
steps:
1417
- uses: actions/checkout@v4
1518
- uses: actions/setup-python@v5
1619
with:
17-
python-version: "3.12"
20+
python-version: ${{ matrix.python-version }}
1821
cache: pip
1922
- name: Install dependencies
2023
run: python -m pip install -r requirements-dev.txt

README.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,8 @@ sudo bash setup.sh
5959

6060
`setup.sh` ilk kurulumun tamamını yürütür:
6161

62-
- Python 3.10+ ile `venv`/`pip` bileşenlerini denetler. Python eksik veya eskiyse otomatik kurulum yapmadan, sistem dili Türkçeyse Türkçe; diğer dillerde İngilizce örnek Ubuntu komutunu gösterip durur.
62+
- Kurulum boyunca sistem dili Türkçeyse Türkçe, diğer dillerde İngilizce mesajlar gösterir.
63+
- Ubuntu 20.04'ün varsayılan Python 3.8 sürümü dahil Python 3.8+ ile `venv`/`pip` bileşenlerini denetler. Python eksik veya eskiyse otomatik kurulum yapmadan örnek Ubuntu komutunu gösterip durur.
6364
- imapsync ve bağımsız MySQL/MariaDB bileşenlerini denetler; eksikleri kurar.
6465
- `/opt/zimbra-migration` dizini ile kısıtlı `zimbra-migrator` servis kullanıcısını oluşturur.
6566
- `zimbra_migration` veritabanını ve yalnızca bu veritabanında CRUD yetkili kullanıcıyı oluşturur.
@@ -200,7 +201,8 @@ sudo bash setup.sh
200201

201202
`setup.sh` performs the complete first-time installation:
202203

203-
- Checks for Python 3.10+ and the `venv`/`pip` components. If Python is missing or outdated, it does not install Python automatically; it stops and prints an example Ubuntu command in Turkish when the system locale is Turkish, or in English otherwise.
204+
- Displays Turkish messages throughout installation when the system locale is Turkish, and English messages for every other locale.
205+
- Supports Ubuntu 20.04's default Python 3.8 and checks for Python 3.8+ with the `venv`/`pip` components. If Python is missing or outdated, it does not install Python automatically; it stops and prints an example Ubuntu command.
204206
- Checks imapsync and the independent MySQL/MariaDB service, installing missing components.
205207
- Creates `/opt/zimbra-migration` and the restricted `zimbra-migrator` service account.
206208
- Creates the `zimbra_migration` database and a user limited to CRUD access on that database.

app.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@
1616
from contextlib import asynccontextmanager
1717
from datetime import datetime, timedelta, timezone
1818
from pathlib import Path
19-
from typing import Annotated
19+
from typing import Optional
20+
21+
from typing_extensions import Annotated
2022

2123
import uvicorn
2224
import pymysql
@@ -242,7 +244,7 @@ def add_job(payload: dict) -> int:
242244
VALUES (:source_host,:source_port,:source_security,:source_email,:source_password,
243245
:target_host,:target_port,:target_security,:target_email,:target_password,
244246
:start_date,:end_date,:lock_key,:active_lock,:created_at)""",
245-
values | {"created_at": now()},
247+
dict(values, created_at=now()),
246248
)
247249
return int(cursor.lastrowid)
248250

@@ -290,7 +292,7 @@ def __init__(self) -> None:
290292
self.wakeup = asyncio.Event()
291293
self.running: dict[int, asyncio.subprocess.Process] = {}
292294
self.tasks: set[asyncio.Task] = set()
293-
self.loop_task: asyncio.Task | None = None
295+
self.loop_task: Optional[asyncio.Task] = None
294296
self.paused = False
295297
self.last_housekeeping = 0.0
296298

@@ -602,7 +604,9 @@ def summary():
602604
SUM(CASE WHEN status='completed' THEN 1 ELSE 0 END) completed,
603605
SUM(CASE WHEN status IN ('failed','interrupted') THEN 1 ELSE 0 END) failed
604606
FROM jobs""").fetchone()
605-
return dict(row) | {"max_parallel": MAX_PARALLEL, "paused": manager.paused}
607+
result = dict(row)
608+
result.update(max_parallel=MAX_PARALLEL, paused=manager.paused)
609+
return result
606610

607611

608612
@app.post("/api/control/pause")

requirements-dev.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
-r requirements.txt
2-
pytest==8.4.1
2+
pytest==8.3.5

requirements.txt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
fastapi==0.116.1
2-
uvicorn[standard]==0.35.0
2+
uvicorn[standard]==0.33.0
33
python-multipart==0.0.20
44
cryptography==45.0.6
55
PyMySQL==1.1.1
6-
python-dotenv==1.1.1
6+
python-dotenv==1.0.1
77
itsdangerous==2.2.0
8+
typing-extensions==4.13.2

scripts/hash-password.py

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,25 @@
11
#!/usr/bin/env python3
22
import getpass
33
import hashlib
4+
import locale
5+
import os
46
import secrets
57

6-
password = getpass.getpass("Panel parolası: ")
7-
confirmation = getpass.getpass("Panel parolası (tekrar): ")
8+
language = os.getenv("INSTALL_LANGUAGE") or (locale.getlocale()[0] or "en")
9+
turkish = language.lower().startswith("tr")
10+
messages = {
11+
"password": "Panel parolası: " if turkish else "Panel password: ",
12+
"confirmation": "Panel parolası (tekrar): " if turkish else "Confirm panel password: ",
13+
"mismatch": "Parolalar eşleşmiyor" if turkish else "Passwords do not match",
14+
"length": "Parola en az 12 karakter olmalıdır" if turkish else "The password must be at least 12 characters long",
15+
}
16+
17+
password = getpass.getpass(messages["password"])
18+
confirmation = getpass.getpass(messages["confirmation"])
819
if password != confirmation:
9-
raise SystemExit("Parolalar eşleşmiyor")
20+
raise SystemExit(messages["mismatch"])
1021
if len(password) < 12:
11-
raise SystemExit("Parola en az 12 karakter olmalıdır")
22+
raise SystemExit(messages["length"])
1223

1324
n, r, p = 16384, 8, 1
1425
salt = secrets.token_bytes(16)

scripts/locales/setup.en.sh

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
MSG_ROOT_REQUIRED="Run the installer with sudo: sudo bash setup.sh"
2+
MSG_EXISTING_INSTALLATION="%s/.env already exists. setup.sh is only for the first installation."
3+
MSG_STEP_REQUIREMENTS="[1/8] Checking system requirements..."
4+
MSG_PYTHON_REQUIRED="Python 3.%s or newer is required; Python will not be installed automatically."
5+
MSG_CURRENT_UBUNTU_EXAMPLE="Example installation on current Ubuntu releases:"
6+
MSG_PYTHON_RETRY="After installation, verify the version with 'python3 --version' and run setup.sh again."
7+
MSG_VENV_REQUIRED="Python 3 is available, but the venv/pip components are missing; they will not be installed automatically."
8+
MSG_INSTALL_EXAMPLE="Example installation:"
9+
MSG_SETUP_RETRY="Run setup.sh again after installing the packages."
10+
MSG_MYSQL_REUSE="Using the existing system MySQL/MariaDB service."
11+
MSG_STEP_IMAPSYNC="[2/8] Checking imapsync..."
12+
MSG_IMAPSYNC_REUSE="Using the existing imapsync executable:"
13+
MSG_STEP_FILES="[3/8] Preparing the service account and application directory..."
14+
MSG_STEP_PYTHON="[4/8] Creating the Python environment..."
15+
MSG_STEP_DATABASE="[5/8] Preparing the application database..."
16+
MSG_MYSQL_ACCESS_ERROR="Could not obtain root socket access to the system MySQL/MariaDB service."
17+
MSG_ZIMBRA_DB_UNUSED="Zimbra's embedded database is not used for security and upgrade safety."
18+
MSG_STEP_CREDENTIALS="[6/8] Creating panel credentials..."
19+
MSG_USERNAME_PROMPT="Panel username [admin]: "
20+
MSG_USERNAME_INVALID="The username may contain only letters, numbers, dots, underscores, and hyphens."
21+
MSG_STEP_PERMISSIONS="[7/8] Configuring file permissions and the systemd service..."
22+
MSG_STEP_SERVICE="[8/8] Starting the service..."
23+
MSG_COMPLETE="Installation completed."
24+
MSG_PANEL="Panel:"
25+
MSG_USERNAME="Username:"
26+
MSG_PORT_SAFETY="Allow port 8787 only from an administrator IP or VPN network."

scripts/locales/setup.tr.sh

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
MSG_ROOT_REQUIRED="Kurulumu sudo ile çalıştırın: sudo bash setup.sh"
2+
MSG_EXISTING_INSTALLATION="%s/.env zaten var. setup.sh yalnızca ilk kurulum içindir."
3+
MSG_STEP_REQUIREMENTS="[1/8] Sistem gereksinimleri denetleniyor..."
4+
MSG_PYTHON_REQUIRED="Python 3.%s veya üzeri gerekli; Python otomatik olarak kurulmayacaktır."
5+
MSG_CURRENT_UBUNTU_EXAMPLE="Güncel Ubuntu sürümlerinde örnek kurulum:"
6+
MSG_PYTHON_RETRY="Kurulumdan sonra 'python3 --version' ile sürümü doğrulayıp setup.sh dosyasını yeniden çalıştırın."
7+
MSG_VENV_REQUIRED="Python 3 bulundu ancak venv/pip bileşenleri eksik; bunlar otomatik olarak kurulmayacaktır."
8+
MSG_INSTALL_EXAMPLE="Örnek kurulum:"
9+
MSG_SETUP_RETRY="Paketleri kurduktan sonra setup.sh dosyasını yeniden çalıştırın."
10+
MSG_MYSQL_REUSE="Mevcut sistem MySQL/MariaDB hizmeti kullanılacak."
11+
MSG_STEP_IMAPSYNC="[2/8] imapsync denetleniyor..."
12+
MSG_IMAPSYNC_REUSE="Mevcut imapsync kullanılacak:"
13+
MSG_STEP_FILES="[3/8] Servis kullanıcısı ve uygulama dizini hazırlanıyor..."
14+
MSG_STEP_PYTHON="[4/8] Python ortamı oluşturuluyor..."
15+
MSG_STEP_DATABASE="[5/8] Uygulama veritabanı hazırlanıyor..."
16+
MSG_MYSQL_ACCESS_ERROR="Sistem MySQL/MariaDB root socket erişimi kurulamadı."
17+
MSG_ZIMBRA_DB_UNUSED="Zimbra'nın gömülü veritabanı güvenlik ve güncelleme güvenliği nedeniyle kullanılmaz."
18+
MSG_STEP_CREDENTIALS="[6/8] Panel kimlik bilgileri oluşturuluyor..."
19+
MSG_USERNAME_PROMPT="Panel kullanıcı adı [admin]: "
20+
MSG_USERNAME_INVALID="Kullanıcı adı yalnızca harf, rakam, nokta, alt çizgi ve tire içerebilir."
21+
MSG_STEP_PERMISSIONS="[7/8] Dosya izinleri ve systemd servisi ayarlanıyor..."
22+
MSG_STEP_SERVICE="[8/8] Servis başlatılıyor..."
23+
MSG_COMPLETE="Kurulum tamamlandı."
24+
MSG_PANEL="Panel:"
25+
MSG_USERNAME="Kullanıcı:"
26+
MSG_PORT_SAFETY="8787 portuna yalnızca yönetici IP adresinden veya VPN ağından izin verin."

setup.sh

Lines changed: 35 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -5,66 +5,50 @@ APP_DIR=${APP_DIR:-/opt/zimbra-migration}
55
APP_USER=zimbra-migrator
66
DB_NAME=zimbra_migration
77
DB_USER=zimbra_migrator
8-
MIN_PYTHON_MINOR=10
8+
MIN_PYTHON_MINOR=8
99
SOURCE_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
10-
# Ubuntu'nun sistem dilini tercih et; dosya yoksa mevcut oturum yerel ayarına dön.
10+
# Prefer Ubuntu's system locale; fall back to the current session locale.
1111
SYSTEM_LOCALE=""
1212
if [[ -r /etc/default/locale ]]; then
1313
SYSTEM_LOCALE=$(sed -n 's/^LANG=["'"']\?\([^"'"']*\)["'"']\?$/\1/p' /etc/default/locale | head -n 1)
1414
fi
1515
SYSTEM_LOCALE=${SYSTEM_LOCALE:-${LC_ALL:-${LC_MESSAGES:-${LANG:-en}}}}
16-
IS_TURKISH=false
16+
LANGUAGE_CODE=en
1717
if [[ ${SYSTEM_LOCALE,,} == tr* ]]; then
18-
IS_TURKISH=true
18+
LANGUAGE_CODE=tr
1919
fi
20+
# shellcheck source=/dev/null
21+
source "$SOURCE_DIR/scripts/locales/setup.${LANGUAGE_CODE}.sh"
2022
2123
python_requirement_error() {
22-
if [[ "$IS_TURKISH" == true ]]; then
23-
echo "Python 3.${MIN_PYTHON_MINOR} veya üzeri gerekli; Python otomatik olarak kurulmayacaktır." >&2
24-
echo "Güncel Ubuntu sürümlerinde örnek kurulum:" >&2
25-
else
26-
echo "Python 3.${MIN_PYTHON_MINOR} or newer is required; Python will not be installed automatically." >&2
27-
echo "Example installation on current Ubuntu releases:" >&2
28-
fi
24+
printf "$MSG_PYTHON_REQUIRED\n" "$MIN_PYTHON_MINOR" >&2
25+
echo "$MSG_CURRENT_UBUNTU_EXAMPLE" >&2
2926
echo " sudo apt update" >&2
3027
echo " sudo apt install -y python3 python3-venv python3-pip" >&2
31-
if [[ "$IS_TURKISH" == true ]]; then
32-
echo "Kurulumdan sonra 'python3 --version' ile sürümü doğrulayıp setup.sh dosyasını yeniden çalıştırın." >&2
33-
else
34-
echo "After installation, verify the version with 'python3 --version' and run setup.sh again." >&2
35-
fi
28+
echo "$MSG_PYTHON_RETRY" >&2
3629
exit 1
3730
}
3831
3932
python_venv_error() {
40-
if [[ "$IS_TURKISH" == true ]]; then
41-
echo "Python 3 bulundu ancak venv/pip bileşenleri eksik; bunlar otomatik olarak kurulmayacaktır." >&2
42-
echo "Örnek kurulum:" >&2
43-
else
44-
echo "Python 3 is available, but the venv/pip components are missing; they will not be installed automatically." >&2
45-
echo "Example installation:" >&2
46-
fi
33+
echo "$MSG_VENV_REQUIRED" >&2
34+
echo "$MSG_INSTALL_EXAMPLE" >&2
4735
echo " sudo apt update" >&2
4836
echo " sudo apt install -y python3-venv python3-pip" >&2
49-
if [[ "$IS_TURKISH" == true ]]; then
50-
echo "Kurulumdan sonra setup.sh dosyasını yeniden çalıştırın." >&2
51-
else
52-
echo "Run setup.sh again after installing the packages." >&2
53-
fi
37+
echo "$MSG_SETUP_RETRY" >&2
5438
exit 1
5539
}
5640
5741
if [[ ${EUID} -ne 0 ]]; then
58-
echo "Kurulumu sudo ile çalıştırın: sudo bash setup.sh" >&2
42+
echo "$MSG_ROOT_REQUIRED" >&2
5943
exit 1
6044
fi
6145
6246
if [[ -f "$APP_DIR/.env" ]]; then
63-
echo "$APP_DIR/.env zaten var. setup.sh yalnızca ilk kurulum içindir." >&2
47+
printf "$MSG_EXISTING_INSTALLATION\n" "$APP_DIR" >&2
6448
exit 1
6549
fi
6650
67-
echo "[1/8] Sistem gereksinimleri denetleniyor..."
51+
echo "$MSG_STEP_REQUIREMENTS"
6852
NEEDED_PACKAGES=(rsync)
6953
if ! command -v python3 >/dev/null 2>&1 || ! python3 -c "import sys; raise SystemExit(sys.version_info < (3, ${MIN_PYTHON_MINOR}))"; then
7054
python_requirement_error
@@ -73,25 +57,25 @@ if ! python3 -m venv --help >/dev/null 2>&1 || ! python3 -m pip --version >/dev/
7357
python_venv_error
7458
fi
7559
76-
# Zimbra'nın /opt/zimbra altındaki gömülü veritabanına dokunulmaz. Sistemde ayrı
77-
# bir MySQL/MariaDB hizmeti varsa yeniden kurulmaz; yoksa yerel hizmet kurulur.
60+
# Never touch Zimbra's embedded database under /opt/zimbra. Reuse an independent
61+
# system MySQL/MariaDB service when available; otherwise install a local service.
7862
if command -v mysql >/dev/null 2>&1 && mysql --protocol=socket -uroot -e "SELECT 1" >/dev/null 2>&1; then
79-
echo "Mevcut sistem MySQL/MariaDB hizmeti kullanılacak."
63+
echo "$MSG_MYSQL_REUSE"
8064
else
8165
NEEDED_PACKAGES+=(default-mysql-server default-mysql-client)
8266
fi
8367
8468
apt-get update
8569
DEBIAN_FRONTEND=noninteractive apt-get install -y "${NEEDED_PACKAGES[@]}"
8670
87-
echo "[2/8] imapsync denetleniyor..."
71+
echo "$MSG_STEP_IMAPSYNC"
8872
if command -v imapsync >/dev/null 2>&1; then
89-
echo "Mevcut imapsync kullanılacak: $(command -v imapsync)"
73+
echo "$MSG_IMAPSYNC_REUSE $(command -v imapsync)"
9074
else
9175
bash "$SOURCE_DIR/scripts/install-imapsync-ubuntu.sh"
9276
fi
9377
94-
echo "[3/8] Servis kullanıcısı ve uygulama dizini hazırlanıyor..."
78+
echo "$MSG_STEP_FILES"
9579
if ! getent passwd "$APP_USER" >/dev/null; then
9680
useradd --system --home "$APP_DIR" --shell /usr/sbin/nologin "$APP_USER"
9781
fi
@@ -100,20 +84,20 @@ rsync -a --delete \
10084
--exclude='.git/' --exclude='.env' --exclude='.venv/' --exclude='data/' --exclude='logs/' \
10185
"$SOURCE_DIR/" "$APP_DIR/"
10286
103-
echo "[4/8] Python ortamı kuruluyor..."
87+
echo "$MSG_STEP_PYTHON"
10488
python3 -m venv "$APP_DIR/.venv"
10589
"$APP_DIR/.venv/bin/pip" install --upgrade pip
10690
"$APP_DIR/.venv/bin/pip" install -r "$APP_DIR/requirements.txt"
10791
108-
echo "[5/8] Uygulama veritabanı hazırlanıyor..."
92+
echo "$MSG_STEP_DATABASE"
10993
if systemctl list-unit-files mysql.service >/dev/null 2>&1; then
11094
systemctl enable --now mysql
11195
elif systemctl list-unit-files mariadb.service >/dev/null 2>&1; then
11296
systemctl enable --now mariadb
11397
fi
11498
if ! mysql --protocol=socket -uroot -e "SELECT 1" >/dev/null 2>&1; then
115-
echo "Sistem MySQL/MariaDB root socket erişimi kurulamadı." >&2
116-
echo "Zimbra'nın gömülü veritabanı güvenlik nedeniyle kullanılmaz." >&2
99+
echo "$MSG_MYSQL_ACCESS_ERROR" >&2
100+
echo "$MSG_ZIMBRA_DB_UNUSED" >&2
117101
exit 1
118102
fi
119103
DB_PASSWORD=$(python3 -c 'import secrets; print(secrets.token_urlsafe(32))')
@@ -125,14 +109,14 @@ FLUSH PRIVILEGES;
125109
SQL
126110
mysql --protocol=socket -uroot "$DB_NAME" < "$APP_DIR/migration_db.sql"
127111
128-
echo "[6/8] Panel kimlik bilgileri oluşturuluyor..."
129-
read -r -p "Panel kullanıcı adı [admin]: " PANEL_USER
112+
echo "$MSG_STEP_CREDENTIALS"
113+
read -r -p "$MSG_USERNAME_PROMPT" PANEL_USER
130114
PANEL_USER=${PANEL_USER:-admin}
131115
if [[ ! "$PANEL_USER" =~ ^[A-Za-z0-9._-]+$ ]]; then
132-
echo "Kullanıcı adı yalnızca harf, rakam, nokta, alt çizgi ve tire içerebilir." >&2
116+
echo "$MSG_USERNAME_INVALID" >&2
133117
exit 1
134118
fi
135-
PANEL_HASH=$("$APP_DIR/.venv/bin/python" "$APP_DIR/scripts/hash-password.py")
119+
PANEL_HASH=$(INSTALL_LANGUAGE="$LANGUAGE_CODE" "$APP_DIR/.venv/bin/python" "$APP_DIR/scripts/hash-password.py")
136120
SESSION_SECRET=$(python3 -c 'import secrets; print(secrets.token_urlsafe(48))')
137121
SERVER_NAME=$(hostname -f 2>/dev/null || hostname)
138122
SERVER_IP=$(hostname -I 2>/dev/null | awk '{print $1}')
@@ -161,21 +145,21 @@ MYSQL_USER=${DB_USER}
161145
MYSQL_PASSWORD='${DB_PASSWORD}'
162146
ENV
163147
164-
echo "[7/8] Dosya izinleri ve systemd servisi ayarlanıyor..."
148+
echo "$MSG_STEP_PERMISSIONS"
165149
chown -R root:root "$APP_DIR"
166150
chown -R "$APP_USER:$APP_USER" "$APP_DIR/data" "$APP_DIR/logs"
167151
chown root:"$APP_USER" "$APP_DIR/.env"
168152
chmod 640 "$APP_DIR/.env"
169153
chmod 700 "$APP_DIR/data" "$APP_DIR/data/pids" "$APP_DIR/logs"
170154
cp "$APP_DIR/deploy/zimbra-migration.service" /etc/systemd/system/zimbra-migration.service
171155
172-
echo "[8/8] Servis başlatılıyor..."
156+
echo "$MSG_STEP_SERVICE"
173157
systemctl daemon-reload
174158
systemctl enable --now zimbra-migration
175159
systemctl --no-pager --full status zimbra-migration || true
176160
177161
echo
178-
echo "Kurulum tamamlandı."
179-
echo "Panel: http://${SERVER_IP:-127.0.0.1}:8787"
180-
echo "Kullanıcı: $PANEL_USER"
181-
echo "Port 8787'yi yalnızca yönetici IP/VPN ağına açın."
162+
echo "$MSG_COMPLETE"
163+
echo "$MSG_PANEL http://${SERVER_IP:-127.0.0.1}:8787"
164+
echo "$MSG_USERNAME $PANEL_USER"
165+
echo "$MSG_PORT_SAFETY"

0 commit comments

Comments
 (0)