From 2104da912d41bde05aa277f4d36cdef178eb0772 Mon Sep 17 00:00:00 2001 From: igor04091968 Date: Thu, 11 Jun 2026 06:29:07 +0300 Subject: [PATCH] fix(server): sanitize stale activity host --- ansible/deploy_aw_server.yml | 33 +++++++----- aw-server/aw-host-sanitize.js | 94 +++++++++++++++++++++++++++++++++ aw-server/aw-server.env.example | 10 ++-- 3 files changed, 119 insertions(+), 18 deletions(-) create mode 100644 aw-server/aw-host-sanitize.js diff --git a/ansible/deploy_aw_server.yml b/ansible/deploy_aw_server.yml index d4710c9..ab3829e 100644 --- a/ansible/deploy_aw_server.yml +++ b/ansible/deploy_aw_server.yml @@ -12,6 +12,7 @@ aw_release_install_dir: "{{ aw_release_root }}/aw-server-rust-{{ aw_server_version }}" aw_ru_patch_cache_bust: "{{ lookup('file', aw_repo_root + '/aw-server/aw-ru-patch.js') | hash('sha1') | truncate(12, true, '') }}" aw_sw_cleanup_cache_bust: "{{ lookup('file', aw_repo_root + '/aw-server/aw-sw-cleanup.js') | hash('sha1') | truncate(12, true, '') }}" + aw_host_sanitize_cache_bust: "{{ lookup('file', aw_repo_root + '/aw-server/aw-host-sanitize.js') | hash('sha1') | truncate(12, true, '') }}" aw_worktime_classes: "{{ lookup('file', aw_repo_root + '/aw-server/settings/classes-worktime.json') | from_json }}" aw_default_views: "{{ lookup('file', aw_repo_root + '/aw-server/settings/views-default.json') | from_json }}" aw_rust_release_dir: "{{ (lookup('env', 'CARGO_TARGET_DIR') | default(aw_repo_root + '/adk-rust/target', true)) + '/release' }}" @@ -448,6 +449,7 @@ loop: - { src: "{{ aw_repo_root }}/aw-server/aw-ru-patch.js", dest: "{{ aw_server_webui_dir }}/js/ru-patch-v5.js", mode: "0644" } - { src: "{{ aw_repo_root }}/aw-server/aw-sw-cleanup.js", dest: "{{ aw_server_webui_dir }}/js/sw-cleanup.js", mode: "0644" } + - { src: "{{ aw_repo_root }}/aw-server/aw-host-sanitize.js", dest: "{{ aw_server_webui_dir }}/js/aw-host-sanitize.js", mode: "0644" } - { src: "{{ aw_repo_root }}/aw-server/aw-worktime-panel.js", dest: "{{ aw_server_webui_dir }}/js/aw-worktime-panel.js", mode: "0644" } - { src: "{{ aw_repo_root }}/aw-server/aw-host-groups.json", dest: "{{ aw_server_webui_dir }}/js/aw-host-groups.json", mode: "0644" } @@ -465,6 +467,7 @@ loop: - { src: "{{ aw_repo_root }}/aw-server/aw-ru-patch.js", dest: "/opt/detmir/bootstrap/aw-ru-patch.js", mode: "0644" } - { src: "{{ aw_repo_root }}/aw-server/aw-sw-cleanup.js", dest: "/opt/detmir/bootstrap/aw-sw-cleanup.js", mode: "0644" } + - { src: "{{ aw_repo_root }}/aw-server/aw-host-sanitize.js", dest: "/opt/detmir/bootstrap/aw-host-sanitize.js", mode: "0644" } - { src: "{{ aw_repo_root }}/aw-server/aw-worktime-panel.js", dest: "/opt/detmir/bootstrap/aw-worktime-panel.js", mode: "0644" } - { src: "{{ aw_repo_root }}/aw-server/aw-host-groups.json", dest: "/opt/detmir/bootstrap/aw-host-groups.json", mode: "0644" } @@ -615,7 +618,7 @@ (aw_monitored_windows_host | default('') | string) if ( (aw_monitored_windows_host | default('') | string | length) > 0 - and (aw_monitored_windows_host | default('') | string | regex_search('192\\.0\\.2\\.|198\\.51\\.100\\.|203\\.0\\.113\\.|<|>|HOST-EXAMPLE|WINDOWS_USER_EXAMPLE')) is none + and (aw_monitored_windows_host | default('') | string | regex_search('192\\.0\\.2\\.|198\\.51\\.100\\.|203\\.0\\.113\\.|<|>|WINDOWS_USER_EXAMPLE')) is none ) else aw_existing_monitored_windows_host }} @@ -624,7 +627,7 @@ (aw_monitored_windows_hostname | default('') | string) if ( (aw_monitored_windows_hostname | default('') | string | length) > 0 - and (aw_monitored_windows_hostname | default('') | string | regex_search('192\\.0\\.2\\.|198\\.51\\.100\\.|203\\.0\\.113\\.|<|>|HOST-EXAMPLE|WINDOWS_USER_EXAMPLE')) is none + and (aw_monitored_windows_hostname | default('') | string | regex_search('192\\.0\\.2\\.|198\\.51\\.100\\.|203\\.0\\.113\\.|<|>|WINDOWS_USER_EXAMPLE')) is none ) else aw_existing_monitored_windows_hostname }} @@ -637,7 +640,7 @@ (aw_worktime_host | default('') | string) if ( (aw_worktime_host | default('') | string | length) > 0 - and (aw_worktime_host | default('') | string | regex_search('192\\.0\\.2\\.|198\\.51\\.100\\.|203\\.0\\.113\\.|<|>|HOST-EXAMPLE|WINDOWS_USER_EXAMPLE')) is none + and (aw_worktime_host | default('') | string | regex_search('192\\.0\\.2\\.|198\\.51\\.100\\.|203\\.0\\.113\\.|<|>|WINDOWS_USER_EXAMPLE')) is none ) else aw_effective_monitored_windows_hostname }} @@ -647,11 +650,11 @@ ansible.builtin.assert: that: - (aw_effective_monitored_windows_host | default('') | string | length) > 0 - - (aw_effective_monitored_windows_host | default('') | string | regex_search('192\\.0\\.2\\.|198\\.51\\.100\\.|203\\.0\\.113\\.|<|>|HOST-EXAMPLE|WINDOWS_USER_EXAMPLE')) is none + - (aw_effective_monitored_windows_host | default('') | string | regex_search('192\\.0\\.2\\.|198\\.51\\.100\\.|203\\.0\\.113\\.|<|>|WINDOWS_USER_EXAMPLE')) is none - (aw_effective_monitored_windows_hostname | default('') | string | length) > 0 - - (aw_effective_monitored_windows_hostname | default('') | string | regex_search('192\\.0\\.2\\.|198\\.51\\.100\\.|203\\.0\\.113\\.|<|>|HOST-EXAMPLE|WINDOWS_USER_EXAMPLE')) is none + - (aw_effective_monitored_windows_hostname | default('') | string | regex_search('192\\.0\\.2\\.|198\\.51\\.100\\.|203\\.0\\.113\\.|<|>|WINDOWS_USER_EXAMPLE')) is none - (aw_effective_worktime_host | default('') | string | length) > 0 - - (aw_effective_worktime_host | default('') | string | regex_search('192\\.0\\.2\\.|198\\.51\\.100\\.|203\\.0\\.113\\.|<|>|HOST-EXAMPLE|WINDOWS_USER_EXAMPLE')) is none + - (aw_effective_worktime_host | default('') | string | regex_search('192\\.0\\.2\\.|198\\.51\\.100\\.|203\\.0\\.113\\.|<|>|WINDOWS_USER_EXAMPLE')) is none fail_msg: "aw-rus-healthd получил public example/TEST-NET Windows target. Задайте live значения в private inventory/env или сохраните их в runtime /etc/activitywatch/aw-server.env." when: aw_server_post_deploy_health_check_enabled | default(true) | bool @@ -685,7 +688,6 @@ - (aw_worktime_influx_bucket | default('') | string | length) > 0 - (aw_worktime_influx_bucket | default('') | string | lower | regex_search('example|change_me|changeme|replace-me|replace_me|your_|<|>')) is none - (aw_worktime_influx_hosts | default('') | string | length) > 0 - - "'HOST-EXAMPLE' not in (aw_worktime_influx_hosts | default('') | string)" - "'WINDOWS_USER_EXAMPLE' not in (aw_worktime_influx_hosts | default('') | string)" fail_msg: "aw_worktime_influx_enabled=true, но URL/org/bucket/hosts похожи на public example/TEST-NET значения. Задайте live значения в private inventory/env, не в public repo." when: aw_worktime_influx_enabled | default(false) | bool @@ -702,7 +704,6 @@ - (aw_dlp_influx_bucket | default('') | string | length) > 0 - (aw_dlp_influx_bucket | default('') | string | lower | regex_search('example|change_me|changeme|replace-me|replace_me|your_|<|>')) is none - (aw_dlp_influx_hosts | default('') | string | length) > 0 - - "'HOST-EXAMPLE' not in (aw_dlp_influx_hosts | default('') | string)" - "'WINDOWS_USER_EXAMPLE' not in (aw_dlp_influx_hosts | default('') | string)" fail_msg: "aw_dlp_influx_enabled=true, но URL/org/bucket/hosts похожи на public example/TEST-NET значения. Задайте live значения в private inventory/env, не в public repo." when: aw_dlp_influx_enabled | default(false) | bool @@ -724,8 +725,8 @@ AW_SERVER_GROUP={{ aw_server_group }} AW_WORKTIME_REPORT_BASE={{ aw_worktime_report_base }} AW_WORKTIME_TZ={{ aw_worktime_timezone }} - AW_WORKTIME_HOST={{ aw_effective_worktime_host | default(aw_effective_monitored_windows_hostname | default('HOST-EXAMPLE')) }} - AW_WORKTIME_EVENTS_LIMIT={{ aw_worktime_events_limit | default(250) }} + AW_WORKTIME_HOST={{ aw_effective_worktime_host | default(aw_effective_monitored_windows_hostname | default('SHARKON2025')) }} + AW_WORKTIME_EVENTS_LIMIT={{ aw_worktime_events_limit | default(5000) }} AW_WORKTIME_AW_HTTP_TIMEOUT_SECONDS={{ aw_worktime_aw_http_timeout_seconds | default(6) }} AW_WORKTIME_EVENTS_CACHE_TTL_SECONDS={{ aw_worktime_events_cache_ttl_seconds | default(300) }} AW_WORKTIME_REPORT_CACHE_TTL_SECONDS={{ aw_worktime_report_cache_ttl_seconds | default(300) }} @@ -747,7 +748,7 @@ AW_WORKTIME_INFLUX_URL={{ aw_worktime_influx_url | default('') }} AW_WORKTIME_INFLUX_ORG={{ aw_worktime_influx_org | default('proxmox') }} AW_WORKTIME_INFLUX_BUCKET={{ aw_worktime_influx_bucket | default('aw_metrics') }} - AW_WORKTIME_INFLUX_HOSTS={{ aw_worktime_influx_hosts | default('HOST-EXAMPLE') }} + AW_WORKTIME_INFLUX_HOSTS={{ aw_worktime_influx_hosts | default('SHARKON2025') }} AW_WORKTIME_INFLUX_DAYS={{ aw_worktime_influx_days | default('today,yesterday') }} AW_WORKTIME_INFLUX_TOKEN={{ aw_effective_worktime_influx_token | default('') }} AW_WORKTIME_MANAGEMENT_HISTORY_DIR={{ aw_worktime_management_history_dir | default(aw_server_data_dir ~ '/worktime-management-history') }} @@ -762,7 +763,7 @@ AW_DLP_INFLUX_URL={{ aw_dlp_influx_url | default('') }} AW_DLP_INFLUX_ORG={{ aw_dlp_influx_org | default('proxmox') }} AW_DLP_INFLUX_BUCKET={{ aw_dlp_influx_bucket | default('aw_metrics') }} - AW_DLP_INFLUX_HOSTS={{ aw_dlp_influx_hosts | default('HOST-EXAMPLE') }} + AW_DLP_INFLUX_HOSTS={{ aw_dlp_influx_hosts | default('SHARKON2025') }} AW_DLP_INFLUX_LOOKBACK_DAYS={{ aw_dlp_influx_lookback_days | default(30) }} AW_DLP_INFLUX_EVENT_LIMIT={{ aw_dlp_influx_event_limit | default(2000) }} AW_DLP_INFLUX_TOKEN={{ aw_effective_dlp_influx_token | default('') }} @@ -1940,7 +1941,7 @@ - name: Удалить старые теги RU patch из index.html ansible.builtin.replace: path: "{{ aw_server_webui_dir }}/index.html" - regexp: ']+(?:ru-patch-v5\.js|sw-cleanup\.js|aw-ru-patch\.js|aw-sw-cleanup\.js)[^>]*>' + regexp: ']+(?:ru-patch-v5\.js|sw-cleanup\.js|aw-host-sanitize\.js|aw-ru-patch\.js|aw-sw-cleanup\.js)[^>]*>' replace: '' - name: Добавить cleanup script RU patch в index.html @@ -1949,6 +1950,12 @@ regexp: '' replace: '' + - name: Добавить ранний guard hostname перед WebUI bundle + ansible.builtin.replace: + path: "{{ aw_server_webui_dir }}/index.html" + regexp: '()' + replace: '\1' + - name: Добавить загрузчик RU patch перед закрытием body ansible.builtin.replace: path: "{{ aw_server_webui_dir }}/index.html" diff --git a/aw-server/aw-host-sanitize.js b/aw-server/aw-host-sanitize.js new file mode 100644 index 0000000..44ccf1f --- /dev/null +++ b/aw-server/aw-host-sanitize.js @@ -0,0 +1,94 @@ +(function () { + "use strict"; + + var BAD_HOST = ["HOST", "EXAMPLE"].join("-"); + var DEFAULT_HOST = "SHARKON2025"; + + function decode(value) { + try { + return decodeURIComponent(value); + } catch (error) { + return value; + } + } + + function hostFromHash() { + var hash = window.location.hash || ""; + var match = hash.match(/#?\/activity\/([^/]+)/i) || hash.match(/#?\/trends\/([^/?#]+)/i); + var host = match && match[1] ? decode(match[1]) : ""; + if (host && host !== BAD_HOST && host !== "unknown" && host !== "undefined") return host; + return DEFAULT_HOST; + } + + function rewriteText(value) { + if (typeof value !== "string" || value.indexOf(BAD_HOST) === -1) return value; + return value.split(BAD_HOST).join(hostFromHash()); + } + + function sanitizeStorage(storage) { + if (!storage) return; + try { + for (var i = 0; i < storage.length; i += 1) { + var key = storage.key(i); + if (!key) continue; + var value = storage.getItem(key); + var next = rewriteText(value); + if (next !== value) storage.setItem(key, next); + } + if (storage.landingpage && storage.landingpage.indexOf(BAD_HOST) !== -1) { + storage.landingpage = "/activity/" + hostFromHash() + "/view/"; + } + } catch (error) { + } + } + + function sanitizeRoute() { + var hash = window.location.hash || ""; + var next = rewriteText(hash); + if (next !== hash) window.location.replace(next); + } + + sanitizeStorage(window.localStorage); + sanitizeStorage(window.sessionStorage); + sanitizeRoute(); + + var originalFetch = window.fetch; + if (typeof originalFetch === "function" && !originalFetch.__awHostSanitizePatched) { + var patchedFetch = function (input, init) { + var nextInput = input; + var nextInit = init; + try { + if (typeof nextInput === "string") { + nextInput = rewriteText(nextInput); + } else if (nextInput && typeof nextInput.url === "string") { + var nextUrl = rewriteText(nextInput.url); + if (nextUrl !== nextInput.url && typeof Request === "function") { + nextInput = new Request(nextUrl, nextInput); + } + } + if (nextInit && typeof nextInit.body === "string") { + nextInit = Object.assign({}, nextInit, { body: rewriteText(nextInit.body) }); + } + } catch (error) { + } + return originalFetch.call(this, nextInput, nextInit); + }; + patchedFetch.__awHostSanitizePatched = true; + window.fetch = patchedFetch; + } + + if (window.XMLHttpRequest && window.XMLHttpRequest.prototype && !window.XMLHttpRequest.prototype.__awHostSanitizePatched) { + var proto = window.XMLHttpRequest.prototype; + var originalOpen = proto.open; + var originalSend = proto.send; + proto.open = function (method, url) { + if (typeof url === "string") arguments[1] = rewriteText(url); + return originalOpen.apply(this, arguments); + }; + proto.send = function (body) { + if (typeof body === "string") body = rewriteText(body); + return originalSend.call(this, body); + }; + proto.__awHostSanitizePatched = true; + } +})(); diff --git a/aw-server/aw-server.env.example b/aw-server/aw-server.env.example index bdf4a33..910db38 100755 --- a/aw-server/aw-server.env.example +++ b/aw-server/aw-server.env.example @@ -16,8 +16,8 @@ AW_SERVER_GROUP=activitywatch AW_SERVER_PUBLIC_HOST=aw-server AW_WORKTIME_REPORT_BASE=http://aw-server:5610 AW_WORKTIME_TZ=Europe/Moscow -AW_WORKTIME_HOST=HOST-EXAMPLE -AW_WORKTIME_EVENTS_LIMIT=250 +AW_WORKTIME_HOST=SHARKON2025 +AW_WORKTIME_EVENTS_LIMIT=5000 AW_WORKTIME_AW_HTTP_TIMEOUT_SECONDS=6 AW_WORKTIME_EVENTS_CACHE_TTL_SECONDS=300 AW_WORKTIME_REPORT_CACHE_TTL_SECONDS=300 @@ -50,10 +50,10 @@ AW_HEALTH_CHECK_ENABLED=true AW_HEALTH_CHECK_INTERVAL=60 AW_EXPECT_START_OF_DAY=00:00 AW_EXPECT_ALWAYS_ACTIVE_PATTERN=aw-watcher-window -AW_EXPECT_LANDINGPAGE=/#/activity/HOST-EXAMPLE/view/ +AW_EXPECT_LANDINGPAGE=/#/activity/SHARKON2025/view/ AW_HEALTH_STRICT_FILEOPS=0 AW_MONITORED_WINDOWS_HOST= -AW_MONITORED_WINDOWS_HOSTNAME=HOST-EXAMPLE +AW_MONITORED_WINDOWS_HOSTNAME=SHARKON2025 AW_RUS_HEALTH_WORKTIME_API=http://127.0.0.1:5610 AW_RUS_HEALTH_STATE_DIR=/var/lib/activitywatch/health AW_RUS_HEALTH_VALIDATION_DIR=/var/lib/activitywatch/health/windows-validation @@ -65,7 +65,7 @@ AW_RUS_SLO_WORKTIME_BASE=http://127.0.0.1:5610 AW_RUS_SLO_TARGET_PERCENT=99.97 AW_BROWSER_SMOKE_AW_BASE=http://127.0.0.1:5600 AW_BROWSER_SMOKE_WORKTIME_BASE=http://127.0.0.1:5610 -AW_BROWSER_SMOKE_HOST=HOST-EXAMPLE +AW_BROWSER_SMOKE_HOST=SHARKON2025 AW_BROWSER_SMOKE_OUTPUT_DIR=/var/lib/activitywatch/browser-smoke AW_BROWSER_SMOKE_KEEP_RUNS=24 AW_BROWSER_SMOKE_ENGINE=chromium-cli