chore: initial private ActivityWatch Russian deployment project

This commit is contained in:
igor04091968
2026-04-25 15:24:04 +03:00
commit 0821de0964
26 changed files with 2915 additions and 0 deletions
+24
View File
@@ -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
+33
View File
@@ -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"
+122
View File
@@ -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 });
});
})();
+11
View File
@@ -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
+18
View File
@@ -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 () {});
+86
View File
@@ -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