chore: initial private ActivityWatch Russian deployment project
This commit is contained in:
Executable
+24
@@ -0,0 +1,24 @@
|
||||
[Unit]
|
||||
Description=ActivityWatch Server (Rust)
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
EnvironmentFile=/etc/activitywatch/aw-server.env
|
||||
User=__AW_SERVER_USER__
|
||||
Group=__AW_SERVER_GROUP__
|
||||
WorkingDirectory=__AW_SERVER_DATA_DIR__
|
||||
ExecStart=/bin/sh -lc 'exec /opt/activitywatch/bin/aw-server-rust --host "$AW_SERVER_BIND_HOST" --port "$AW_SERVER_PORT"'
|
||||
Restart=on-failure
|
||||
RestartSec=5s
|
||||
StateDirectory=activitywatch
|
||||
LogsDirectory=activitywatch
|
||||
NoNewPrivileges=true
|
||||
PrivateTmp=true
|
||||
ProtectSystem=full
|
||||
ProtectHome=true
|
||||
LimitNOFILE=65535
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
Executable
+33
@@ -0,0 +1,33 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
ENV_FILE="/etc/activitywatch/aw-server.env"
|
||||
if [[ ! -f "$ENV_FILE" ]]; then
|
||||
echo "missing env file: $ENV_FILE" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
source "$ENV_FILE"
|
||||
|
||||
WEBUI_DIR="${AW_SERVER_WEBUI_DIR:-/opt/activitywatch/webui-ru}"
|
||||
PATCH_JS_SRC="/root/bootstrap/aw-ru-patch.js"
|
||||
SW_CLEANUP_SRC="/root/bootstrap/aw-sw-cleanup.js"
|
||||
INDEX_HTML="$WEBUI_DIR/index.html"
|
||||
SERVICE_WORKER="$WEBUI_DIR/service-worker.js"
|
||||
TS=$(date +%Y%m%d%H%M%S)
|
||||
|
||||
[[ -f "$PATCH_JS_SRC" ]] || { echo "missing $PATCH_JS_SRC" >&2; exit 1; }
|
||||
[[ -f "$SW_CLEANUP_SRC" ]] || { echo "missing $SW_CLEANUP_SRC" >&2; exit 1; }
|
||||
[[ -f "$INDEX_HTML" ]] || { echo "missing $INDEX_HTML" >&2; exit 1; }
|
||||
|
||||
install -d "$WEBUI_DIR/js"
|
||||
install -m 0644 "$PATCH_JS_SRC" "$WEBUI_DIR/js/aw-ru-patch.js"
|
||||
install -m 0644 "$SW_CLEANUP_SRC" "$WEBUI_DIR/js/aw-sw-cleanup.js"
|
||||
cp "$INDEX_HTML" "$INDEX_HTML.bak.$TS"
|
||||
|
||||
sed -i '/aw-ru-patch.js/d;/aw-sw-cleanup.js/d' "$INDEX_HTML"
|
||||
sed -i 's#</head>#<script src="/js/aw-sw-cleanup.js"></script></head>#' "$INDEX_HTML"
|
||||
sed -i 's#</body>#<script defer="defer" src="/js/aw-ru-patch.js"></script></body>#' "$INDEX_HTML"
|
||||
cp "$SW_CLEANUP_SRC" "$SERVICE_WORKER"
|
||||
|
||||
echo "RU patch applied to $WEBUI_DIR"
|
||||
Executable
+122
@@ -0,0 +1,122 @@
|
||||
(function () {
|
||||
window.__awRuPatchVersion = "template-v1";
|
||||
document.documentElement.setAttribute("data-aw-ru-patch", "template-v1");
|
||||
|
||||
const exact = new Map([
|
||||
["ActivityWatch", "АктивВотч"],
|
||||
["Home", "Главная"],
|
||||
["Activity", "Активность"],
|
||||
["Timeline", "Таймлайн"],
|
||||
["Trends", "Тренды"],
|
||||
["Report", "Отчеты"],
|
||||
["Settings", "Настройки"],
|
||||
["Search", "Поиск"],
|
||||
["Buckets", "Бакеты"],
|
||||
["Stopwatch", "Секундомер"],
|
||||
["Tools", "Инструменты"],
|
||||
["Raw Data", "Сырые данные"],
|
||||
["Summary", "Сводка"],
|
||||
["All", "Все"],
|
||||
["None", "Нет"],
|
||||
["Date", "Дата"],
|
||||
["Time", "Время"],
|
||||
["Start", "Начало"],
|
||||
["End", "Конец"],
|
||||
["Download", "Скачать"],
|
||||
["Upload", "Загрузить"],
|
||||
["Show", "Показать"],
|
||||
["Hide", "Скрыть"],
|
||||
["Close", "Закрыть"],
|
||||
["Week", "Неделя"],
|
||||
["Month", "Месяц"],
|
||||
["Year", "Год"],
|
||||
["Today", "Сегодня"],
|
||||
["Yesterday", "Вчера"],
|
||||
["No data", "Нет данных"],
|
||||
["Category", "Категория"],
|
||||
["Categories", "Категории"],
|
||||
["Duration", "Длительность"],
|
||||
["Applications", "Приложения"],
|
||||
["Hosts", "Хосты"],
|
||||
["Date Range", "Диапазон дат"],
|
||||
["Generate", "Сформировать"],
|
||||
["Loading", "Загрузка"],
|
||||
["Loading...", "Загрузка..."],
|
||||
["Dark", "Темная"],
|
||||
["Light", "Светлая"],
|
||||
["Save", "Сохранить"],
|
||||
["Cancel", "Отмена"],
|
||||
["Delete", "Удалить"],
|
||||
["API Browser", "API-браузер"],
|
||||
["Documentation", "Документация"]
|
||||
]);
|
||||
|
||||
const partial = [
|
||||
["Top apps", "Топ приложений"],
|
||||
["Top titles", "Топ заголовков"],
|
||||
["Top URLs", "Топ URL"],
|
||||
["Top domains", "Топ доменов"],
|
||||
["Top Browser Domains", "Топ доменов браузера"],
|
||||
["Top Browser URLs", "Топ URL браузера"],
|
||||
["Top Browser Titles", "Топ заголовков браузера"],
|
||||
["Top Categories", "Топ категорий"],
|
||||
["Category Tree", "Дерево категорий"],
|
||||
["Timeline (barchart)", "Таймлайн (гистограмма)"],
|
||||
["Calculate Work Time", "Рассчитать рабочее время"],
|
||||
["Export CSV", "Экспорт CSV"],
|
||||
["Export JSON", "Экспорт JSON"],
|
||||
["No duplicate events found.", "Дубликаты событий не найдены."],
|
||||
["No overlapping events found.", "Пересекающиеся события не найдены."],
|
||||
["No zero-duration events found.", "События нулевой длительности не найдены."]
|
||||
];
|
||||
|
||||
function replaceText(text) {
|
||||
if (!text) return text;
|
||||
if (exact.has(text.trim())) {
|
||||
return text.replace(text.trim(), exact.get(text.trim()));
|
||||
}
|
||||
let result = text;
|
||||
for (const [en, ru] of partial) {
|
||||
result = result.split(en).join(ru);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function walk(root) {
|
||||
const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, null);
|
||||
const nodes = [];
|
||||
while (walker.nextNode()) nodes.push(walker.currentNode);
|
||||
for (const node of nodes) {
|
||||
const nextValue = replaceText(node.nodeValue);
|
||||
if (nextValue !== node.nodeValue) {
|
||||
node.nodeValue = nextValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function translateAttributes(root) {
|
||||
const elements = root.querySelectorAll("[title],[placeholder],[aria-label]");
|
||||
for (const element of elements) {
|
||||
["title", "placeholder", "aria-label"].forEach(function (attr) {
|
||||
const value = element.getAttribute(attr);
|
||||
if (value) {
|
||||
element.setAttribute(attr, replaceText(value));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function applyPatch() {
|
||||
walk(document.body);
|
||||
translateAttributes(document.body);
|
||||
}
|
||||
|
||||
const observer = new MutationObserver(function () {
|
||||
applyPatch();
|
||||
});
|
||||
|
||||
window.addEventListener("load", function () {
|
||||
applyPatch();
|
||||
observer.observe(document.body, { childList: true, subtree: true, characterData: true });
|
||||
});
|
||||
})();
|
||||
Executable
+11
@@ -0,0 +1,11 @@
|
||||
# Copy to /etc/activitywatch/aw-server.env and fill with real values.
|
||||
|
||||
AW_SERVER_VERSION=0.13.2
|
||||
AW_SERVER_DOWNLOAD_URL=https://github.com/ActivityWatch/aw-server-rust/releases/download/v0.13.2/aw-server-rust-linux-x86_64.zip
|
||||
AW_SERVER_BIND_HOST=0.0.0.0
|
||||
AW_SERVER_PORT=5600
|
||||
AW_SERVER_WEBUI_DIR=/opt/activitywatch/webui-ru
|
||||
AW_SERVER_DATA_DIR=/var/lib/activitywatch
|
||||
AW_SERVER_LOG_DIR=/var/log/activitywatch
|
||||
AW_SERVER_USER=activitywatch
|
||||
AW_SERVER_GROUP=activitywatch
|
||||
Executable
+18
@@ -0,0 +1,18 @@
|
||||
self.addEventListener("install", function (event) {
|
||||
self.skipWaiting();
|
||||
event.waitUntil((async function () {
|
||||
const keys = await caches.keys();
|
||||
await Promise.all(keys.map(function (key) { return caches.delete(key); }));
|
||||
})());
|
||||
});
|
||||
|
||||
self.addEventListener("activate", function (event) {
|
||||
event.waitUntil((async function () {
|
||||
const keys = await caches.keys();
|
||||
await Promise.all(keys.map(function (key) { return caches.delete(key); }));
|
||||
await self.clients.claim();
|
||||
await self.registration.unregister();
|
||||
})());
|
||||
});
|
||||
|
||||
self.addEventListener("fetch", function () {});
|
||||
Executable
+86
@@ -0,0 +1,86 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
ENV_FILE="/etc/activitywatch/aw-server.env"
|
||||
if [[ ! -f "$ENV_FILE" ]]; then
|
||||
echo "missing env file: $ENV_FILE" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
source "$ENV_FILE"
|
||||
|
||||
required_vars=(
|
||||
AW_SERVER_VERSION
|
||||
AW_SERVER_DOWNLOAD_URL
|
||||
AW_SERVER_BIND_HOST
|
||||
AW_SERVER_PORT
|
||||
AW_SERVER_WEBUI_DIR
|
||||
AW_SERVER_DATA_DIR
|
||||
AW_SERVER_LOG_DIR
|
||||
AW_SERVER_USER
|
||||
AW_SERVER_GROUP
|
||||
)
|
||||
|
||||
for var_name in "${required_vars[@]}"; do
|
||||
if [[ -z "${!var_name:-}" ]]; then
|
||||
echo "missing required variable: $var_name" >&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
apt-get update
|
||||
apt-get install -y curl ca-certificates unzip jq
|
||||
|
||||
if ! getent group "$AW_SERVER_GROUP" >/dev/null; then
|
||||
groupadd --system "$AW_SERVER_GROUP"
|
||||
fi
|
||||
|
||||
if ! id "$AW_SERVER_USER" >/dev/null 2>&1; then
|
||||
useradd --system --gid "$AW_SERVER_GROUP" --home-dir "$AW_SERVER_DATA_DIR" --shell /usr/sbin/nologin "$AW_SERVER_USER"
|
||||
fi
|
||||
|
||||
install -d -o "$AW_SERVER_USER" -g "$AW_SERVER_GROUP" /opt/activitywatch/bin
|
||||
install -d -o "$AW_SERVER_USER" -g "$AW_SERVER_GROUP" /opt/activitywatch/releases
|
||||
install -d -o "$AW_SERVER_USER" -g "$AW_SERVER_GROUP" "$AW_SERVER_WEBUI_DIR"
|
||||
install -d -o "$AW_SERVER_USER" -g "$AW_SERVER_GROUP" "$AW_SERVER_DATA_DIR"
|
||||
install -d -o "$AW_SERVER_USER" -g "$AW_SERVER_GROUP" "$AW_SERVER_LOG_DIR"
|
||||
|
||||
tmp_dir=$(mktemp -d)
|
||||
trap 'rm -rf "$tmp_dir"' EXIT
|
||||
|
||||
curl -fL "$AW_SERVER_DOWNLOAD_URL" -o "$tmp_dir/aw-server.zip"
|
||||
unzip -q "$tmp_dir/aw-server.zip" -d "$tmp_dir/unpacked"
|
||||
|
||||
server_bin=$(find "$tmp_dir/unpacked" -type f \( -name 'aw-server-rust' -o -name 'aw-server' \) | head -n 1)
|
||||
webui_dir=$(find "$tmp_dir/unpacked" -type d \( -name 'webui' -o -name 'aw-webui' \) | head -n 1 || true)
|
||||
|
||||
if [[ -z "$server_bin" || ! -f "$server_bin" ]]; then
|
||||
echo "aw-server binary not found in archive" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
release_dir="/opt/activitywatch/releases/aw-server-rust-v${AW_SERVER_VERSION}"
|
||||
rm -rf "$release_dir"
|
||||
install -d -o "$AW_SERVER_USER" -g "$AW_SERVER_GROUP" "$release_dir"
|
||||
install -m 0755 -o "$AW_SERVER_USER" -g "$AW_SERVER_GROUP" "$server_bin" "$release_dir/aw-server-rust"
|
||||
ln -sfn "$release_dir/aw-server-rust" /opt/activitywatch/bin/aw-server-rust
|
||||
|
||||
if [[ -n "$webui_dir" && -d "$webui_dir" ]]; then
|
||||
rm -rf "$AW_SERVER_WEBUI_DIR"
|
||||
mkdir -p "$AW_SERVER_WEBUI_DIR"
|
||||
cp -a "$webui_dir"/. "$AW_SERVER_WEBUI_DIR"/
|
||||
chown -R "$AW_SERVER_USER:$AW_SERVER_GROUP" "$AW_SERVER_WEBUI_DIR"
|
||||
fi
|
||||
|
||||
sed \
|
||||
-e "s#__AW_SERVER_USER__#$AW_SERVER_USER#g" \
|
||||
-e "s#__AW_SERVER_GROUP__#$AW_SERVER_GROUP#g" \
|
||||
-e "s#__AW_SERVER_DATA_DIR__#$AW_SERVER_DATA_DIR#g" \
|
||||
/root/bootstrap/activitywatch-server.service > /etc/systemd/system/activitywatch-server.service
|
||||
chmod 0644 /etc/systemd/system/activitywatch-server.service
|
||||
|
||||
systemctl daemon-reload
|
||||
systemctl enable activitywatch-server.service
|
||||
systemctl restart activitywatch-server.service
|
||||
systemctl --no-pager --full status activitywatch-server.service || true
|
||||
Reference in New Issue
Block a user