Добавлены исходники пакетов saikyo-*
This commit is contained in:
parent
d11da1fafd
commit
5820108829
121
saikyo-audit-report/bin/saikyo-audit-report
Normal file
121
saikyo-audit-report/bin/saikyo-audit-report
Normal file
|
|
@ -0,0 +1,121 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
OUT_BASE="/var/lib/saikyo-audit-report"
|
||||||
|
TS="$(date -u +%Y%m%dT%H%M%SZ)"
|
||||||
|
OUT_DIR="${OUT_BASE}/${TS}"
|
||||||
|
|
||||||
|
mkdir -p "${OUT_DIR}" "${OUT_DIR}/crypto" "${OUT_DIR}/apt" "${OUT_DIR}/system" "${OUT_DIR}/licenses" "${OUT_DIR}/network"
|
||||||
|
chmod 0755 "${OUT_BASE}" "${OUT_DIR}" || true
|
||||||
|
|
||||||
|
{
|
||||||
|
echo "timestamp_utc=${TS}"
|
||||||
|
echo "hostname=$(hostname)"
|
||||||
|
echo "kernel=$(uname -srmo)"
|
||||||
|
echo "arch=$(dpkg --print-architecture 2>/dev/null || true)"
|
||||||
|
} > "${OUT_DIR}/system/summary.env"
|
||||||
|
|
||||||
|
# Saikyo subscription/license status (if present)
|
||||||
|
if command -v saikyo-license >/dev/null 2>&1; then
|
||||||
|
(saikyo-license status 2>&1 || true) > "${OUT_DIR}/system/saikyo-license.status"
|
||||||
|
(saikyo-license verify 2>&1 || true) > "${OUT_DIR}/system/saikyo-license.verify"
|
||||||
|
else
|
||||||
|
echo "saikyo-license not installed" > "${OUT_DIR}/system/saikyo-license.status"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -f /etc/os-release ]; then
|
||||||
|
cp -f /etc/os-release "${OUT_DIR}/system/os-release" || true
|
||||||
|
fi
|
||||||
|
if [ -f /usr/lib/os-release ]; then
|
||||||
|
cp -f /usr/lib/os-release "${OUT_DIR}/system/os-release.lib" || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Package inventory
|
||||||
|
(dpkg-query -W -f='${Package}\t${Version}\n' 2>/dev/null || true) > "${OUT_DIR}/packages.tsv"
|
||||||
|
|
||||||
|
# APT sources + keyrings
|
||||||
|
mkdir -p "${OUT_DIR}/apt/sources.list.d" || true
|
||||||
|
if [ -f /etc/apt/sources.list ]; then
|
||||||
|
cp -f /etc/apt/sources.list "${OUT_DIR}/apt/sources.list" || true
|
||||||
|
fi
|
||||||
|
if [ -d /etc/apt/sources.list.d ]; then
|
||||||
|
cp -a /etc/apt/sources.list.d/. "${OUT_DIR}/apt/sources.list.d/" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
(ls -la /usr/share/keyrings 2>/dev/null || true) > "${OUT_DIR}/apt/keyrings.ls"
|
||||||
|
|
||||||
|
(grep -RhsE '^(deb|deb-src)\s' /etc/apt/sources.list /etc/apt/sources.list.d 2>/dev/null || true) > "${OUT_DIR}/apt/sources.lines"
|
||||||
|
|
||||||
|
# Hashes for installed Saikyo keyring/list (if present)
|
||||||
|
for f in \
|
||||||
|
/usr/share/keyrings/saikyo-archive-keyring.gpg \
|
||||||
|
/etc/apt/sources.list.d/saikyo-os.list \
|
||||||
|
/etc/os-release \
|
||||||
|
/usr/lib/os-release
|
||||||
|
do
|
||||||
|
if [ -f "$f" ]; then
|
||||||
|
sha256sum "$f" >> "${OUT_DIR}/sha256sum.files" || true
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# Crypto / GOST checks
|
||||||
|
{
|
||||||
|
echo "openssl_version=$(openssl version 2>/dev/null || true)"
|
||||||
|
echo "openssl_engines="
|
||||||
|
openssl engine -t -c 2>/dev/null || true
|
||||||
|
} > "${OUT_DIR}/crypto/openssl-engine.txt"
|
||||||
|
|
||||||
|
if command -v gostsum >/dev/null 2>&1; then
|
||||||
|
echo test | gostsum > "${OUT_DIR}/crypto/gostsum.txt" 2>&1 || true
|
||||||
|
else
|
||||||
|
echo "gostsum not installed" > "${OUT_DIR}/crypto/gostsum.txt"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if command -v lsmod >/dev/null 2>&1; then
|
||||||
|
lsmod | grep -i gost > "${OUT_DIR}/crypto/lsmod-gost.txt" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
(grep -RhsE '(pool\.ntp\.org|ntp\.org|time\.google\.com|time\.windows\.com|geoip|telemetry)' /etc 2>/dev/null || true) > "${OUT_DIR}/network/external-indicators.txt"
|
||||||
|
|
||||||
|
(systemctl list-unit-files 2>/dev/null || true) > "${OUT_DIR}/system/unit-files.txt"
|
||||||
|
|
||||||
|
# Secure Boot / MOK / TPM
|
||||||
|
{
|
||||||
|
echo "mokutil_sb_state:"
|
||||||
|
(mokutil --sb-state 2>/dev/null || true)
|
||||||
|
echo
|
||||||
|
echo "mokutil_list_enrolled:"
|
||||||
|
(mokutil --list-enrolled 2>/dev/null || true)
|
||||||
|
echo
|
||||||
|
echo "tpm_devices:"
|
||||||
|
(ls -la /dev/tpm* 2>/dev/null || true)
|
||||||
|
echo
|
||||||
|
echo "saikyo_mok_der_present:"
|
||||||
|
if [ -f /usr/share/saikyo-os/secure-boot/saikyo-mok.der ]; then
|
||||||
|
sha256sum /usr/share/saikyo-os/secure-boot/saikyo-mok.der || true
|
||||||
|
else
|
||||||
|
echo "missing"
|
||||||
|
fi
|
||||||
|
} > "${OUT_DIR}/system/secure-boot.txt"
|
||||||
|
|
||||||
|
LICENSE_TSV="${OUT_DIR}/licenses/licenses.tsv"
|
||||||
|
PROBLEM_TSV="${OUT_DIR}/licenses/problematic-licenses.tsv"
|
||||||
|
|
||||||
|
echo -e "package\tlicense" > "${LICENSE_TSV}"
|
||||||
|
echo -e "package\tmatched" > "${PROBLEM_TSV}"
|
||||||
|
|
||||||
|
while IFS=$'\t' read -r pkg ver; do
|
||||||
|
[ -n "${pkg}" ] || continue
|
||||||
|
cfile="/usr/share/doc/${pkg}/copyright"
|
||||||
|
lic="UNKNOWN"
|
||||||
|
if [ -f "${cfile}" ]; then
|
||||||
|
lic=$(awk -F': ' 'BEGIN{l=""} $1=="License" && l=="" {l=$2} END{if(l=="") print "UNKNOWN"; else print l}' "${cfile}" 2>/dev/null || echo "UNKNOWN")
|
||||||
|
fi
|
||||||
|
echo -e "${pkg}\t${lic}" >> "${LICENSE_TSV}"
|
||||||
|
case "${lic}" in
|
||||||
|
*SSPL*|*Elastic*|*RSAL*|*Redis*Source*Available*|*Server*Side*Public*License*|*AGPL*)
|
||||||
|
echo -e "${pkg}\t${lic}" >> "${PROBLEM_TSV}"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done < "${OUT_DIR}/packages.tsv"
|
||||||
|
|
||||||
|
echo "Report created: ${OUT_DIR}"
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
saikyo-audit-report (1.0.0) stable; urgency=medium
|
||||||
|
|
||||||
|
* Initial release.
|
||||||
|
|
||||||
|
-- SAIKYO OS <support@saikyo-os.ru> Tue, 07 Jan 2026 00:00:00 +0000
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
./bin/saikyo-audit-report
|
||||||
5
saikyo-audit-report/debian/changelog
Normal file
5
saikyo-audit-report/debian/changelog
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
saikyo-audit-report (1.0.0) stable; urgency=medium
|
||||||
|
|
||||||
|
* Initial release.
|
||||||
|
|
||||||
|
-- SAIKYO OS <support@saikyo-os.ru> Tue, 07 Jan 2026 00:00:00 +0000
|
||||||
14
saikyo-audit-report/debian/control
Normal file
14
saikyo-audit-report/debian/control
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
Source: saikyo-audit-report
|
||||||
|
Section: admin
|
||||||
|
Priority: optional
|
||||||
|
Maintainer: SAIKYO OS <support@saikyo-os.ru>
|
||||||
|
Build-Depends: debhelper-compat (= 13)
|
||||||
|
Standards-Version: 4.6.2
|
||||||
|
Rules-Requires-Root: no
|
||||||
|
|
||||||
|
Package: saikyo-audit-report
|
||||||
|
Architecture: all
|
||||||
|
Depends: ${misc:Depends}, bash, coreutils, dpkg, ca-certificates, gnupg, openssl, findutils, grep, sed, gawk
|
||||||
|
Description: Saikyo OS audit and registry report generator
|
||||||
|
Generates a local report directory with package inventory, repositories/keys,
|
||||||
|
OS identity, hashes, and crypto/GOST checks.
|
||||||
24
saikyo-audit-report/debian/copyright
Normal file
24
saikyo-audit-report/debian/copyright
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
||||||
|
Upstream-Name: saikyo-audit-report
|
||||||
|
Source: https://saikyo-os.ru
|
||||||
|
|
||||||
|
Files: *
|
||||||
|
Copyright: 2026 SAIKYO OS <support@saikyo-os.ru>
|
||||||
|
License: MIT
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
.
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
.
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
1
saikyo-audit-report/debian/debhelper-build-stamp
Normal file
1
saikyo-audit-report/debian/debhelper-build-stamp
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
saikyo-audit-report
|
||||||
2
saikyo-audit-report/debian/files
Normal file
2
saikyo-audit-report/debian/files
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
saikyo-audit-report_1.0.0_all.deb admin optional
|
||||||
|
saikyo-audit-report_1.0.0_amd64.buildinfo admin optional
|
||||||
1
saikyo-audit-report/debian/install
Normal file
1
saikyo-audit-report/debian/install
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
bin/saikyo-audit-report usr/sbin/saikyo-audit-report
|
||||||
8
saikyo-audit-report/debian/rules
Executable file
8
saikyo-audit-report/debian/rules
Executable file
|
|
@ -0,0 +1,8 @@
|
||||||
|
#!/usr/bin/make -f
|
||||||
|
|
||||||
|
%:
|
||||||
|
dh $@
|
||||||
|
|
||||||
|
override_dh_auto_build:
|
||||||
|
|
||||||
|
override_dh_auto_test:
|
||||||
2
saikyo-audit-report/debian/saikyo-audit-report.substvars
Normal file
2
saikyo-audit-report/debian/saikyo-audit-report.substvars
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
misc:Depends=
|
||||||
|
misc:Pre-Depends=
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
Package: saikyo-audit-report
|
||||||
|
Version: 1.0.0
|
||||||
|
Architecture: all
|
||||||
|
Maintainer: SAIKYO OS <support@saikyo-os.ru>
|
||||||
|
Installed-Size: 15
|
||||||
|
Depends: bash, coreutils, dpkg, ca-certificates, gnupg, openssl, findutils, grep, sed, gawk
|
||||||
|
Section: admin
|
||||||
|
Priority: optional
|
||||||
|
Description: Saikyo OS audit and registry report generator
|
||||||
|
Generates a local report directory with package inventory, repositories/keys,
|
||||||
|
OS identity, hashes, and crypto/GOST checks.
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
c95795694790aeed9d166d5eaf2765da usr/sbin/saikyo-audit-report/saikyo-audit-report
|
||||||
|
a97380b9477d5cbeab831599b3321dfe usr/share/doc/saikyo-audit-report/changelog.gz
|
||||||
|
3a820ad7cb163259d2f91549750a52ed usr/share/doc/saikyo-audit-report/copyright
|
||||||
|
|
@ -0,0 +1,121 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
OUT_BASE="/var/lib/saikyo-audit-report"
|
||||||
|
TS="$(date -u +%Y%m%dT%H%M%SZ)"
|
||||||
|
OUT_DIR="${OUT_BASE}/${TS}"
|
||||||
|
|
||||||
|
mkdir -p "${OUT_DIR}" "${OUT_DIR}/crypto" "${OUT_DIR}/apt" "${OUT_DIR}/system" "${OUT_DIR}/licenses" "${OUT_DIR}/network"
|
||||||
|
chmod 0755 "${OUT_BASE}" "${OUT_DIR}" || true
|
||||||
|
|
||||||
|
{
|
||||||
|
echo "timestamp_utc=${TS}"
|
||||||
|
echo "hostname=$(hostname)"
|
||||||
|
echo "kernel=$(uname -srmo)"
|
||||||
|
echo "arch=$(dpkg --print-architecture 2>/dev/null || true)"
|
||||||
|
} > "${OUT_DIR}/system/summary.env"
|
||||||
|
|
||||||
|
# Saikyo subscription/license status (if present)
|
||||||
|
if command -v saikyo-license >/dev/null 2>&1; then
|
||||||
|
(saikyo-license status 2>&1 || true) > "${OUT_DIR}/system/saikyo-license.status"
|
||||||
|
(saikyo-license verify 2>&1 || true) > "${OUT_DIR}/system/saikyo-license.verify"
|
||||||
|
else
|
||||||
|
echo "saikyo-license not installed" > "${OUT_DIR}/system/saikyo-license.status"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -f /etc/os-release ]; then
|
||||||
|
cp -f /etc/os-release "${OUT_DIR}/system/os-release" || true
|
||||||
|
fi
|
||||||
|
if [ -f /usr/lib/os-release ]; then
|
||||||
|
cp -f /usr/lib/os-release "${OUT_DIR}/system/os-release.lib" || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Package inventory
|
||||||
|
(dpkg-query -W -f='${Package}\t${Version}\n' 2>/dev/null || true) > "${OUT_DIR}/packages.tsv"
|
||||||
|
|
||||||
|
# APT sources + keyrings
|
||||||
|
mkdir -p "${OUT_DIR}/apt/sources.list.d" || true
|
||||||
|
if [ -f /etc/apt/sources.list ]; then
|
||||||
|
cp -f /etc/apt/sources.list "${OUT_DIR}/apt/sources.list" || true
|
||||||
|
fi
|
||||||
|
if [ -d /etc/apt/sources.list.d ]; then
|
||||||
|
cp -a /etc/apt/sources.list.d/. "${OUT_DIR}/apt/sources.list.d/" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
(ls -la /usr/share/keyrings 2>/dev/null || true) > "${OUT_DIR}/apt/keyrings.ls"
|
||||||
|
|
||||||
|
(grep -RhsE '^(deb|deb-src)\s' /etc/apt/sources.list /etc/apt/sources.list.d 2>/dev/null || true) > "${OUT_DIR}/apt/sources.lines"
|
||||||
|
|
||||||
|
# Hashes for installed Saikyo keyring/list (if present)
|
||||||
|
for f in \
|
||||||
|
/usr/share/keyrings/saikyo-archive-keyring.gpg \
|
||||||
|
/etc/apt/sources.list.d/saikyo-os.list \
|
||||||
|
/etc/os-release \
|
||||||
|
/usr/lib/os-release
|
||||||
|
do
|
||||||
|
if [ -f "$f" ]; then
|
||||||
|
sha256sum "$f" >> "${OUT_DIR}/sha256sum.files" || true
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# Crypto / GOST checks
|
||||||
|
{
|
||||||
|
echo "openssl_version=$(openssl version 2>/dev/null || true)"
|
||||||
|
echo "openssl_engines="
|
||||||
|
openssl engine -t -c 2>/dev/null || true
|
||||||
|
} > "${OUT_DIR}/crypto/openssl-engine.txt"
|
||||||
|
|
||||||
|
if command -v gostsum >/dev/null 2>&1; then
|
||||||
|
echo test | gostsum > "${OUT_DIR}/crypto/gostsum.txt" 2>&1 || true
|
||||||
|
else
|
||||||
|
echo "gostsum not installed" > "${OUT_DIR}/crypto/gostsum.txt"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if command -v lsmod >/dev/null 2>&1; then
|
||||||
|
lsmod | grep -i gost > "${OUT_DIR}/crypto/lsmod-gost.txt" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
(grep -RhsE '(pool\.ntp\.org|ntp\.org|time\.google\.com|time\.windows\.com|geoip|telemetry)' /etc 2>/dev/null || true) > "${OUT_DIR}/network/external-indicators.txt"
|
||||||
|
|
||||||
|
(systemctl list-unit-files 2>/dev/null || true) > "${OUT_DIR}/system/unit-files.txt"
|
||||||
|
|
||||||
|
# Secure Boot / MOK / TPM
|
||||||
|
{
|
||||||
|
echo "mokutil_sb_state:"
|
||||||
|
(mokutil --sb-state 2>/dev/null || true)
|
||||||
|
echo
|
||||||
|
echo "mokutil_list_enrolled:"
|
||||||
|
(mokutil --list-enrolled 2>/dev/null || true)
|
||||||
|
echo
|
||||||
|
echo "tpm_devices:"
|
||||||
|
(ls -la /dev/tpm* 2>/dev/null || true)
|
||||||
|
echo
|
||||||
|
echo "saikyo_mok_der_present:"
|
||||||
|
if [ -f /usr/share/saikyo-os/secure-boot/saikyo-mok.der ]; then
|
||||||
|
sha256sum /usr/share/saikyo-os/secure-boot/saikyo-mok.der || true
|
||||||
|
else
|
||||||
|
echo "missing"
|
||||||
|
fi
|
||||||
|
} > "${OUT_DIR}/system/secure-boot.txt"
|
||||||
|
|
||||||
|
LICENSE_TSV="${OUT_DIR}/licenses/licenses.tsv"
|
||||||
|
PROBLEM_TSV="${OUT_DIR}/licenses/problematic-licenses.tsv"
|
||||||
|
|
||||||
|
echo -e "package\tlicense" > "${LICENSE_TSV}"
|
||||||
|
echo -e "package\tmatched" > "${PROBLEM_TSV}"
|
||||||
|
|
||||||
|
while IFS=$'\t' read -r pkg ver; do
|
||||||
|
[ -n "${pkg}" ] || continue
|
||||||
|
cfile="/usr/share/doc/${pkg}/copyright"
|
||||||
|
lic="UNKNOWN"
|
||||||
|
if [ -f "${cfile}" ]; then
|
||||||
|
lic=$(awk -F': ' 'BEGIN{l=""} $1=="License" && l=="" {l=$2} END{if(l=="") print "UNKNOWN"; else print l}' "${cfile}" 2>/dev/null || echo "UNKNOWN")
|
||||||
|
fi
|
||||||
|
echo -e "${pkg}\t${lic}" >> "${LICENSE_TSV}"
|
||||||
|
case "${lic}" in
|
||||||
|
*SSPL*|*Elastic*|*RSAL*|*Redis*Source*Available*|*Server*Side*Public*License*|*AGPL*)
|
||||||
|
echo -e "${pkg}\t${lic}" >> "${PROBLEM_TSV}"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done < "${OUT_DIR}/packages.tsv"
|
||||||
|
|
||||||
|
echo "Report created: ${OUT_DIR}"
|
||||||
Binary file not shown.
|
|
@ -0,0 +1,24 @@
|
||||||
|
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
||||||
|
Upstream-Name: saikyo-audit-report
|
||||||
|
Source: https://saikyo-os.ru
|
||||||
|
|
||||||
|
Files: *
|
||||||
|
Copyright: 2026 SAIKYO OS <support@saikyo-os.ru>
|
||||||
|
License: MIT
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
.
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
.
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
1
saikyo-audit-report/debian/source/format
Normal file
1
saikyo-audit-report/debian/source/format
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
3.0 (native)
|
||||||
8
saikyo-av-gui/autostart/saikyo-av-gui.desktop
Normal file
8
saikyo-av-gui/autostart/saikyo-av-gui.desktop
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
[Desktop Entry]
|
||||||
|
Type=Application
|
||||||
|
Name=Saikyo Antivirus (Tray)
|
||||||
|
Comment=Start Saikyo Antivirus tray app
|
||||||
|
Exec=/usr/bin/saikyo-av-gui --tray
|
||||||
|
Icon=saikyo-av
|
||||||
|
Terminal=false
|
||||||
|
X-GNOME-Autostart-enabled=true
|
||||||
323
saikyo-av-gui/bin/saikyo-av-admin
Normal file
323
saikyo-av-gui/bin/saikyo-av-admin
Normal file
|
|
@ -0,0 +1,323 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
REPORTS_DIR="/var/lib/saikyo-av/reports"
|
||||||
|
LOG_DIR="/var/log/saikyo-av"
|
||||||
|
QUAR_DIR="/var/lib/saikyo-av/quarantine"
|
||||||
|
|
||||||
|
mkdir -p "${REPORTS_DIR}" "${LOG_DIR}" "${QUAR_DIR}" || true
|
||||||
|
chmod 0755 /var/lib/saikyo-av "${REPORTS_DIR}" "${LOG_DIR}" 2>/dev/null || true
|
||||||
|
|
||||||
|
cmd="${1:-}"
|
||||||
|
shift || true
|
||||||
|
|
||||||
|
utc_ts() {
|
||||||
|
date -u +%Y-%m-%dT%H:%M:%SZ
|
||||||
|
}
|
||||||
|
|
||||||
|
write_report() {
|
||||||
|
local id="$1"
|
||||||
|
local summary="$2"
|
||||||
|
local severity="$3"
|
||||||
|
local outfile="${REPORTS_DIR}/${id}.json"
|
||||||
|
local created
|
||||||
|
created="$(utc_ts)"
|
||||||
|
|
||||||
|
# shellcheck disable=SC2129
|
||||||
|
{
|
||||||
|
echo "{"
|
||||||
|
echo " \"created_utc\": \"${created}\","
|
||||||
|
echo " \"severity\": \"${severity}\","
|
||||||
|
echo " \"summary\": \"${summary}\","
|
||||||
|
echo " \"details\": {"
|
||||||
|
echo " \"action\": \"${cmd}\","
|
||||||
|
echo " \"argv\": \"$*\""
|
||||||
|
echo " },"
|
||||||
|
echo " \"artifacts\": {},"
|
||||||
|
echo " \"suggested_fixes\": []"
|
||||||
|
echo "}"
|
||||||
|
} > "${outfile}"
|
||||||
|
|
||||||
|
echo "${outfile}"
|
||||||
|
}
|
||||||
|
|
||||||
|
run_to_file() {
|
||||||
|
local out="$1"
|
||||||
|
shift
|
||||||
|
("$@" 2>&1 || true) | sed -e 's/\r$//' > "${out}"
|
||||||
|
}
|
||||||
|
|
||||||
|
json_escape() {
|
||||||
|
# Minimal JSON string escape (no unicode handling needed for our file paths)
|
||||||
|
sed -e 's/\\/\\\\/g' -e 's/"/\\"/g' -e 's/\t/\\t/g' -e 's/\r/\\r/g' -e 's/\n/\\n/g'
|
||||||
|
}
|
||||||
|
|
||||||
|
write_scan_report() {
|
||||||
|
local id="$1"
|
||||||
|
local summary="$2"
|
||||||
|
local severity="$3"
|
||||||
|
local log_file="$4"
|
||||||
|
local detections_tsv="$5"
|
||||||
|
local outfile="${REPORTS_DIR}/${id}.json"
|
||||||
|
local created
|
||||||
|
created="$(utc_ts)"
|
||||||
|
|
||||||
|
local scanned_count infected_count
|
||||||
|
scanned_count="$(grep -E '^Scanned files:' "${log_file}" 2>/dev/null | awk -F': ' '{print $2}' | tail -n 1 || true)"
|
||||||
|
infected_count="$(grep -E '^Infected files:' "${log_file}" 2>/dev/null | awk -F': ' '{print $2}' | tail -n 1 || true)"
|
||||||
|
scanned_count="${scanned_count:-0}"
|
||||||
|
infected_count="${infected_count:-0}"
|
||||||
|
|
||||||
|
{
|
||||||
|
echo "{"
|
||||||
|
echo " \"created_utc\": \"${created}\","
|
||||||
|
echo " \"severity\": \"${severity}\","
|
||||||
|
echo " \"summary\": \"${summary}\","
|
||||||
|
echo " \"details\": {"
|
||||||
|
echo " \"action\": \"${cmd}\","
|
||||||
|
echo " \"argv\": \"$*\","
|
||||||
|
echo " \"scanned_files\": ${scanned_count},"
|
||||||
|
echo " \"infected_files\": ${infected_count},"
|
||||||
|
echo " \"detections\": ["
|
||||||
|
|
||||||
|
if [[ -s "${detections_tsv}" ]]; then
|
||||||
|
first=1
|
||||||
|
while IFS=$'\t' read -r pth sig; do
|
||||||
|
[[ -n "${pth}" ]] || continue
|
||||||
|
pth_esc="$(printf '%s' "${pth}" | json_escape)"
|
||||||
|
sig_esc="$(printf '%s' "${sig}" | json_escape)"
|
||||||
|
if [[ "${first}" -eq 1 ]]; then
|
||||||
|
first=0
|
||||||
|
else
|
||||||
|
echo ","
|
||||||
|
fi
|
||||||
|
printf ' {"path":"%s","name":"%s"}' "${pth_esc}" "${sig_esc}"
|
||||||
|
done < "${detections_tsv}"
|
||||||
|
echo
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo " ]"
|
||||||
|
echo " },"
|
||||||
|
echo " \"artifacts\": {"
|
||||||
|
echo " \"clamav_log\": \"${log_file}\","
|
||||||
|
echo " \"detections_tsv\": \"${detections_tsv}\""
|
||||||
|
echo " },"
|
||||||
|
echo " \"suggested_fixes\": ["
|
||||||
|
echo " {\"id\":\"quarantine\",\"title\":\"Quarantine infected file\",\"description\":\"Move selected file to local quarantine.\",\"requires_consent\":true},"
|
||||||
|
echo " {\"id\":\"delete\",\"title\":\"Delete infected file\",\"description\":\"Delete selected infected file (dangerous).\",\"requires_consent\":true}"
|
||||||
|
echo " ]"
|
||||||
|
echo "}"
|
||||||
|
} > "${outfile}"
|
||||||
|
|
||||||
|
echo "${outfile}"
|
||||||
|
}
|
||||||
|
|
||||||
|
svc_is_enabled() {
|
||||||
|
systemctl is-enabled saikyo-avd.timer 2>/dev/null || true
|
||||||
|
}
|
||||||
|
|
||||||
|
svc_is_active() {
|
||||||
|
systemctl is-active saikyo-avd.timer 2>/dev/null || true
|
||||||
|
}
|
||||||
|
|
||||||
|
case "${cmd}" in
|
||||||
|
health)
|
||||||
|
echo "ok"
|
||||||
|
;;
|
||||||
|
|
||||||
|
status-protection)
|
||||||
|
echo "enabled=$(svc_is_enabled) active=$(svc_is_active)"
|
||||||
|
;;
|
||||||
|
|
||||||
|
run-evidence)
|
||||||
|
outdir="${REPORTS_DIR}/artifacts"
|
||||||
|
mkdir -p "${outdir}" || true
|
||||||
|
ts="$(date -u +%Y%m%dT%H%M%SZ)"
|
||||||
|
evidence_out="${outdir}/saikyo-evidence-${ts}.log"
|
||||||
|
|
||||||
|
if command -v saikyo-evidence >/dev/null 2>&1; then
|
||||||
|
run_to_file "${evidence_out}" saikyo-evidence
|
||||||
|
elif [ -x /usr/bin/saikyo-evidence ]; then
|
||||||
|
run_to_file "${evidence_out}" /usr/bin/saikyo-evidence
|
||||||
|
else
|
||||||
|
echo "saikyo-evidence not found" > "${evidence_out}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
rpt="$(write_report "evidence-${ts}" "Evidence report generated" "info" "${cmd}")"
|
||||||
|
# Append artifacts into report JSON (minimal, without jq dependency).
|
||||||
|
sed -i "s#\"artifacts\": {}#\"artifacts\": {\"evidence_log\": \"${evidence_out}\"}#" "${rpt}" || true
|
||||||
|
echo "${rpt}"
|
||||||
|
;;
|
||||||
|
|
||||||
|
scan)
|
||||||
|
outdir="${REPORTS_DIR}/artifacts"
|
||||||
|
mkdir -p "${outdir}" || true
|
||||||
|
ts="$(date -u +%Y%m%dT%H%M%SZ)"
|
||||||
|
log_file="${outdir}/clamav-scan-${ts}.log"
|
||||||
|
det_tsv="${outdir}/clamav-detections-${ts}.tsv"
|
||||||
|
|
||||||
|
if ! command -v clamscan >/dev/null 2>&1; then
|
||||||
|
echo "clamscan not found (install clamav)" > "${log_file}"
|
||||||
|
rpt="$(write_report "scan-${ts}" "ClamAV scan failed: clamscan not found" "warn" "${cmd}")"
|
||||||
|
sed -i "s#\"artifacts\": {}#\"artifacts\": {\"clamav_log\": \"${log_file}\"}#" "${rpt}" || true
|
||||||
|
echo "${rpt}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Full scan of / with exclusions.
|
||||||
|
# - exclude hidden paths (/.*/)
|
||||||
|
# - exclude virtual/system dirs
|
||||||
|
# - keep a log we can parse
|
||||||
|
(
|
||||||
|
clamscan \
|
||||||
|
-r \
|
||||||
|
--infected \
|
||||||
|
--no-summary \
|
||||||
|
--exclude-dir='^/proc/' \
|
||||||
|
--exclude-dir='^/sys/' \
|
||||||
|
--exclude-dir='^/dev/' \
|
||||||
|
--exclude-dir='^/run/' \
|
||||||
|
--exclude-dir='^/tmp/' \
|
||||||
|
--exclude-dir='/.*/' \
|
||||||
|
/ \
|
||||||
|
2>&1
|
||||||
|
echo
|
||||||
|
clamscan -r --infected --exclude-dir='^/proc/' --exclude-dir='^/sys/' --exclude-dir='^/dev/' --exclude-dir='^/run/' --exclude-dir='^/tmp/' --exclude-dir='/.*/' / --summary 2>/dev/null || true
|
||||||
|
) | sed -e 's/\r$//' > "${log_file}"
|
||||||
|
|
||||||
|
: > "${det_tsv}"
|
||||||
|
# Parse "PATH: SIGNATURE FOUND" lines.
|
||||||
|
grep -E ' FOUND$' "${log_file}" | sed -E 's/: (.+) FOUND$//;t;d' >/dev/null 2>&1 || true
|
||||||
|
while IFS= read -r line; do
|
||||||
|
# Example: /path/file: Eicar-Test-Signature FOUND
|
||||||
|
pth="${line%%:*}"
|
||||||
|
rest="${line#*: }"
|
||||||
|
sig="${rest% FOUND}"
|
||||||
|
printf '%s\t%s\n' "${pth}" "${sig}" >> "${det_tsv}"
|
||||||
|
done < <(grep -E ' FOUND$' "${log_file}" || true)
|
||||||
|
|
||||||
|
infected_count="$(wc -l < "${det_tsv}" 2>/dev/null || echo 0)"
|
||||||
|
severity="info"
|
||||||
|
summary="ClamAV scan completed: no threats"
|
||||||
|
if [[ "${infected_count}" -gt 0 ]]; then
|
||||||
|
severity="bad"
|
||||||
|
summary="ClamAV scan completed: ${infected_count} threat(s) found"
|
||||||
|
fi
|
||||||
|
|
||||||
|
rpt="$(write_scan_report "scan-${ts}" "${summary}" "${severity}" "${log_file}" "${det_tsv}" "${cmd}")"
|
||||||
|
echo "${rpt}"
|
||||||
|
;;
|
||||||
|
|
||||||
|
quarantine)
|
||||||
|
target="${1:-}"
|
||||||
|
if [[ -z "${target}" ]]; then
|
||||||
|
echo "missing file path" >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
ts="$(date -u +%Y%m%dT%H%M%SZ)"
|
||||||
|
bn="$(basename -- "${target}" 2>/dev/null || echo file)"
|
||||||
|
dest="${QUAR_DIR}/${ts}-${bn}"
|
||||||
|
if [[ ! -f "${target}" ]]; then
|
||||||
|
rpt="$(write_report "quarantine-${ts}" "Quarantine failed: file not found" "warn" "${cmd}")"
|
||||||
|
echo "${rpt}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
mv -f -- "${target}" "${dest}" 2>>"${LOG_DIR}/saikyo-av-admin.log" || {
|
||||||
|
rpt="$(write_report "quarantine-${ts}" "Quarantine failed: move error" "warn" "${cmd}")"
|
||||||
|
echo "${rpt}"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
rpt="$(write_report "quarantine-${ts}" "File quarantined" "warn" "${cmd}")"
|
||||||
|
sed -i "s#\"artifacts\": {}#\"artifacts\": {\"from\": \"${target}\", \"to\": \"${dest}\"}#" "${rpt}" || true
|
||||||
|
echo "${rpt}"
|
||||||
|
;;
|
||||||
|
|
||||||
|
delete)
|
||||||
|
target="${1:-}"
|
||||||
|
if [[ -z "${target}" ]]; then
|
||||||
|
echo "missing file path" >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
ts="$(date -u +%Y%m%dT%H%M%SZ)"
|
||||||
|
if [[ ! -e "${target}" ]]; then
|
||||||
|
rpt="$(write_report "delete-${ts}" "Delete failed: path not found" "warn" "${cmd}")"
|
||||||
|
echo "${rpt}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
rm -rf -- "${target}" 2>>"${LOG_DIR}/saikyo-av-admin.log" || {
|
||||||
|
rpt="$(write_report "delete-${ts}" "Delete failed" "warn" "${cmd}")"
|
||||||
|
echo "${rpt}"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
rpt="$(write_report "delete-${ts}" "File deleted" "warn" "${cmd}")"
|
||||||
|
sed -i "s#\"artifacts\": {}#\"artifacts\": {\"path\": \"${target}\"}#" "${rpt}" || true
|
||||||
|
echo "${rpt}"
|
||||||
|
;;
|
||||||
|
|
||||||
|
run-audit)
|
||||||
|
outdir="${REPORTS_DIR}/artifacts"
|
||||||
|
mkdir -p "${outdir}" || true
|
||||||
|
ts="$(date -u +%Y%m%dT%H%M%SZ)"
|
||||||
|
audit_out="${outdir}/saikyo-audit-report-${ts}.txt"
|
||||||
|
|
||||||
|
if command -v saikyo-audit-report >/dev/null 2>&1; then
|
||||||
|
run_to_file "${audit_out}" saikyo-audit-report
|
||||||
|
else
|
||||||
|
echo "saikyo-audit-report not installed" > "${audit_out}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
rpt="$(write_report "audit-${ts}" "Audit report executed" "info" "${cmd}")"
|
||||||
|
sed -i "s#\"artifacts\": {}#\"artifacts\": {\"audit_stdout\": \"${audit_out}\"}#" "${rpt}" || true
|
||||||
|
echo "${rpt}"
|
||||||
|
;;
|
||||||
|
|
||||||
|
collect-artifacts)
|
||||||
|
outdir="${REPORTS_DIR}/artifacts"
|
||||||
|
mkdir -p "${outdir}" || true
|
||||||
|
ts="$(date -u +%Y%m%dT%H%M%SZ)"
|
||||||
|
collect_out="${outdir}/collect-artifacts-${ts}.log"
|
||||||
|
|
||||||
|
if [ -x /usr/share/saikyo-os/forensics/collect-artifacts.sh ]; then
|
||||||
|
run_to_file "${collect_out}" /usr/share/saikyo-os/forensics/collect-artifacts.sh
|
||||||
|
else
|
||||||
|
echo "collect-artifacts.sh not found" > "${collect_out}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
rpt="$(write_report "collect-${ts}" "Artifacts collection executed" "info" "${cmd}")"
|
||||||
|
sed -i "s#\"artifacts\": {}#\"artifacts\": {\"collector_log\": \"${collect_out}\"}#" "${rpt}" || true
|
||||||
|
echo "${rpt}"
|
||||||
|
;;
|
||||||
|
|
||||||
|
enable-protection)
|
||||||
|
ts="$(date -u +%Y%m%dT%H%M%SZ)"
|
||||||
|
(systemctl daemon-reload 2>&1 || true) >> "${LOG_DIR}/saikyo-av-admin.log" || true
|
||||||
|
if systemctl enable --now saikyo-avd.timer >/dev/null 2>&1; then
|
||||||
|
rpt="$(write_report "enable-${ts}" "Protection enabled" "info" "${cmd}")"
|
||||||
|
echo "${rpt}"
|
||||||
|
exit 0
|
||||||
|
else
|
||||||
|
rpt="$(write_report "enable-${ts}" "Protection enable failed" "warn" "${cmd}")"
|
||||||
|
echo "${rpt}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
|
||||||
|
disable-protection)
|
||||||
|
ts="$(date -u +%Y%m%dT%H%M%SZ)"
|
||||||
|
(systemctl daemon-reload 2>&1 || true) >> "${LOG_DIR}/saikyo-av-admin.log" || true
|
||||||
|
if systemctl disable --now saikyo-avd.timer >/dev/null 2>&1; then
|
||||||
|
rpt="$(write_report "disable-${ts}" "Protection disabled" "warn" "${cmd}")"
|
||||||
|
echo "${rpt}"
|
||||||
|
exit 0
|
||||||
|
else
|
||||||
|
rpt="$(write_report "disable-${ts}" "Protection disable failed" "warn" "${cmd}")"
|
||||||
|
echo "${rpt}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
|
||||||
|
*)
|
||||||
|
echo "Usage: saikyo-av-admin {health|status-protection|run-evidence|run-audit|collect-artifacts|scan|quarantine <file>|delete <file>|enable-protection|disable-protection}" >&2
|
||||||
|
exit 2
|
||||||
|
;;
|
||||||
|
esac
|
||||||
532
saikyo-av-gui/bin/saikyo-av-gui
Normal file
532
saikyo-av-gui/bin/saikyo-av-gui
Normal file
|
|
@ -0,0 +1,532 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
import pathlib
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
from datetime import datetime, timezone
|
||||||
|
|
||||||
|
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||||
|
|
||||||
|
REPORTS_DIR_DEFAULT = "/var/lib/saikyo-av/reports"
|
||||||
|
|
||||||
|
|
||||||
|
def utc_now() -> str:
|
||||||
|
return datetime.now(tz=timezone.utc).isoformat(timespec="seconds")
|
||||||
|
|
||||||
|
|
||||||
|
def load_reports(reports_dir: pathlib.Path):
|
||||||
|
items = []
|
||||||
|
if not reports_dir.exists():
|
||||||
|
return items
|
||||||
|
for p in sorted(reports_dir.glob("*.json"), key=lambda x: x.stat().st_mtime, reverse=True):
|
||||||
|
try:
|
||||||
|
data = json.loads(p.read_text(encoding="utf-8"))
|
||||||
|
except Exception:
|
||||||
|
data = {}
|
||||||
|
items.append(
|
||||||
|
{
|
||||||
|
"id": p.stem,
|
||||||
|
"path": str(p),
|
||||||
|
"summary": data.get("summary") or "(no summary)",
|
||||||
|
"severity": data.get("severity") or "unknown",
|
||||||
|
"created_utc": data.get("created_utc") or "",
|
||||||
|
"raw": data,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return items
|
||||||
|
|
||||||
|
|
||||||
|
class ReportsModel(QtCore.QAbstractTableModel):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
self.items = []
|
||||||
|
|
||||||
|
def rowCount(self, parent=QtCore.QModelIndex()):
|
||||||
|
return len(self.items)
|
||||||
|
|
||||||
|
def columnCount(self, parent=QtCore.QModelIndex()):
|
||||||
|
return 3
|
||||||
|
|
||||||
|
def headerData(self, section, orientation, role=QtCore.Qt.DisplayRole):
|
||||||
|
if role != QtCore.Qt.DisplayRole:
|
||||||
|
return None
|
||||||
|
if orientation == QtCore.Qt.Horizontal:
|
||||||
|
return ["Summary", "Severity", "Created (UTC)"][section]
|
||||||
|
return str(section + 1)
|
||||||
|
|
||||||
|
def data(self, index, role=QtCore.Qt.DisplayRole):
|
||||||
|
if not index.isValid():
|
||||||
|
return None
|
||||||
|
item = self.items[index.row()]
|
||||||
|
col = index.column()
|
||||||
|
|
||||||
|
if role == QtCore.Qt.DisplayRole:
|
||||||
|
if col == 0:
|
||||||
|
return item.get("summary")
|
||||||
|
if col == 1:
|
||||||
|
return item.get("severity")
|
||||||
|
if col == 2:
|
||||||
|
return item.get("created_utc")
|
||||||
|
|
||||||
|
if role == QtCore.Qt.ForegroundRole and col == 1:
|
||||||
|
sev = (item.get("severity") or "").lower()
|
||||||
|
if sev in {"ok", "info", "low"}:
|
||||||
|
return QtGui.QBrush(QtGui.QColor("#2dd4bf"))
|
||||||
|
if sev in {"medium", "warn", "warning"}:
|
||||||
|
return QtGui.QBrush(QtGui.QColor("#fbbf24"))
|
||||||
|
if sev in {"high", "critical", "bad"}:
|
||||||
|
return QtGui.QBrush(QtGui.QColor("#fb7185"))
|
||||||
|
return QtGui.QBrush(QtGui.QColor("#9bb0d1"))
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def set_items(self, items):
|
||||||
|
self.beginResetModel()
|
||||||
|
self.items = items
|
||||||
|
self.endResetModel()
|
||||||
|
|
||||||
|
|
||||||
|
class MainWindow(QtWidgets.QMainWindow):
|
||||||
|
def __init__(self, reports_dir: pathlib.Path, start_in_tray: bool):
|
||||||
|
super().__init__()
|
||||||
|
self.reports_dir = reports_dir
|
||||||
|
self.start_in_tray = start_in_tray
|
||||||
|
|
||||||
|
self.setWindowTitle("Saikyo Antivirus")
|
||||||
|
self.setWindowIcon(QtGui.QIcon.fromTheme("saikyo-av", QtGui.QIcon.fromTheme("security-high")))
|
||||||
|
self.setMinimumSize(980, 620)
|
||||||
|
|
||||||
|
self.model = ReportsModel()
|
||||||
|
self.table = QtWidgets.QTableView()
|
||||||
|
self.table.setModel(self.model)
|
||||||
|
self.table.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
|
||||||
|
self.table.setSelectionMode(QtWidgets.QAbstractItemView.SingleSelection)
|
||||||
|
self.table.horizontalHeader().setStretchLastSection(True)
|
||||||
|
self.table.horizontalHeader().setSectionResizeMode(0, QtWidgets.QHeaderView.Stretch)
|
||||||
|
self.table.horizontalHeader().setSectionResizeMode(1, QtWidgets.QHeaderView.ResizeToContents)
|
||||||
|
self.table.horizontalHeader().setSectionResizeMode(2, QtWidgets.QHeaderView.ResizeToContents)
|
||||||
|
|
||||||
|
self.details = QtWidgets.QPlainTextEdit()
|
||||||
|
self.details.setReadOnly(True)
|
||||||
|
self.details.setFont(QtGui.QFontDatabase.systemFont(QtGui.QFontDatabase.FixedFont))
|
||||||
|
|
||||||
|
self.det_list = QtWidgets.QListWidget()
|
||||||
|
self.det_list.setSelectionMode(QtWidgets.QAbstractItemView.SingleSelection)
|
||||||
|
|
||||||
|
self.btn_quarantine = QtWidgets.QPushButton("Quarantine")
|
||||||
|
self.btn_delete = QtWidgets.QPushButton("Delete")
|
||||||
|
for b in [self.btn_quarantine, self.btn_delete]:
|
||||||
|
b.setCursor(QtCore.Qt.PointingHandCursor)
|
||||||
|
b.setMinimumHeight(32)
|
||||||
|
self.btn_delete.setStyleSheet("background:#2b1420;color:#ffd4df;border:1px solid #5b2031;")
|
||||||
|
self.btn_quarantine.setEnabled(False)
|
||||||
|
self.btn_delete.setEnabled(False)
|
||||||
|
|
||||||
|
self.scan_status = QtWidgets.QLabel("")
|
||||||
|
self.scan_status.setStyleSheet("color:#9bb0d1")
|
||||||
|
self.scan_progress = QtWidgets.QProgressBar()
|
||||||
|
self.scan_progress.setRange(0, 0)
|
||||||
|
self.scan_progress.setVisible(False)
|
||||||
|
self.btn_stop_scan = QtWidgets.QPushButton("Stop scan")
|
||||||
|
self.btn_stop_scan.setCursor(QtCore.Qt.PointingHandCursor)
|
||||||
|
self.btn_stop_scan.setMinimumHeight(32)
|
||||||
|
self.btn_stop_scan.setEnabled(False)
|
||||||
|
|
||||||
|
self._scan_proc: QtCore.QProcess | None = None
|
||||||
|
self._scan_timer = QtCore.QElapsedTimer()
|
||||||
|
self._scan_tick = QtCore.QTimer(self)
|
||||||
|
self._scan_tick.setInterval(500)
|
||||||
|
self._scan_tick.timeout.connect(self._update_scan_elapsed)
|
||||||
|
|
||||||
|
self.status = QtWidgets.QLabel("local")
|
||||||
|
self.status.setStyleSheet("color:#9bb0d1")
|
||||||
|
|
||||||
|
btn_evidence = QtWidgets.QPushButton("Evidence")
|
||||||
|
btn_audit = QtWidgets.QPushButton("Audit")
|
||||||
|
btn_collect = QtWidgets.QPushButton("Collect")
|
||||||
|
btn_scan = QtWidgets.QPushButton("SCAN")
|
||||||
|
btn_enable = QtWidgets.QPushButton("Enable")
|
||||||
|
btn_disable = QtWidgets.QPushButton("Disable…")
|
||||||
|
btn_refresh = QtWidgets.QPushButton("Refresh")
|
||||||
|
|
||||||
|
for b in [btn_evidence, btn_audit, btn_collect, btn_scan, btn_enable, btn_disable, btn_refresh]:
|
||||||
|
b.setCursor(QtCore.Qt.PointingHandCursor)
|
||||||
|
b.setMinimumHeight(36)
|
||||||
|
|
||||||
|
btn_scan.setStyleSheet("background:rgba(45,212,191,0.18);border:1px solid rgba(45,212,191,0.35);")
|
||||||
|
|
||||||
|
btn_disable.setStyleSheet("background:#2b1420;color:#ffd4df;border:1px solid #5b2031;")
|
||||||
|
|
||||||
|
left = QtWidgets.QFrame()
|
||||||
|
left.setFrameShape(QtWidgets.QFrame.StyledPanel)
|
||||||
|
ll = QtWidgets.QVBoxLayout(left)
|
||||||
|
ll.setContentsMargins(12, 12, 12, 12)
|
||||||
|
ll.setSpacing(10)
|
||||||
|
|
||||||
|
title = QtWidgets.QLabel("Admin checks")
|
||||||
|
title.setStyleSheet("font-weight:800;font-size:16px")
|
||||||
|
hint = QtWidgets.QLabel("Кнопки запускают проверки с подтверждением (pkexec).")
|
||||||
|
hint.setWordWrap(True)
|
||||||
|
hint.setStyleSheet("color:#9bb0d1;font-size:12px")
|
||||||
|
|
||||||
|
ll.addWidget(title)
|
||||||
|
ll.addWidget(hint)
|
||||||
|
ll.addSpacing(4)
|
||||||
|
ll.addWidget(btn_evidence)
|
||||||
|
ll.addWidget(btn_audit)
|
||||||
|
ll.addWidget(btn_collect)
|
||||||
|
ll.addSpacing(8)
|
||||||
|
ll.addWidget(btn_scan)
|
||||||
|
ll.addSpacing(8)
|
||||||
|
ll.addWidget(btn_enable)
|
||||||
|
ll.addWidget(btn_disable)
|
||||||
|
ll.addStretch(1)
|
||||||
|
ll.addWidget(btn_refresh)
|
||||||
|
ll.addWidget(self.status)
|
||||||
|
|
||||||
|
splitter = QtWidgets.QSplitter()
|
||||||
|
splitter.setOrientation(QtCore.Qt.Vertical)
|
||||||
|
top = QtWidgets.QWidget()
|
||||||
|
tl = QtWidgets.QVBoxLayout(top)
|
||||||
|
tl.setContentsMargins(0, 0, 0, 0)
|
||||||
|
tl.addWidget(self.table)
|
||||||
|
splitter.addWidget(top)
|
||||||
|
details_box = QtWidgets.QWidget()
|
||||||
|
dl = QtWidgets.QVBoxLayout(details_box)
|
||||||
|
dl.setContentsMargins(0, 0, 0, 0)
|
||||||
|
dl.setSpacing(8)
|
||||||
|
|
||||||
|
det_head = QtWidgets.QHBoxLayout()
|
||||||
|
det_title = QtWidgets.QLabel("Detections")
|
||||||
|
det_title.setStyleSheet("font-weight:800;")
|
||||||
|
det_head.addWidget(det_title)
|
||||||
|
det_head.addStretch(1)
|
||||||
|
det_head.addWidget(self.scan_status)
|
||||||
|
det_head.addWidget(self.scan_progress)
|
||||||
|
det_head.addWidget(self.btn_stop_scan)
|
||||||
|
det_head.addWidget(self.btn_quarantine)
|
||||||
|
det_head.addWidget(self.btn_delete)
|
||||||
|
dl.addLayout(det_head)
|
||||||
|
dl.addWidget(self.det_list)
|
||||||
|
|
||||||
|
raw_title = QtWidgets.QLabel("Report JSON")
|
||||||
|
raw_title.setStyleSheet("font-weight:800;")
|
||||||
|
dl.addWidget(raw_title)
|
||||||
|
dl.addWidget(self.details)
|
||||||
|
|
||||||
|
splitter.addWidget(details_box)
|
||||||
|
splitter.setStretchFactor(0, 2)
|
||||||
|
splitter.setStretchFactor(1, 1)
|
||||||
|
|
||||||
|
central = QtWidgets.QWidget()
|
||||||
|
root = QtWidgets.QHBoxLayout(central)
|
||||||
|
root.setContentsMargins(16, 16, 16, 16)
|
||||||
|
root.setSpacing(12)
|
||||||
|
left.setFixedWidth(300)
|
||||||
|
root.addWidget(left)
|
||||||
|
root.addWidget(splitter)
|
||||||
|
self.setCentralWidget(central)
|
||||||
|
|
||||||
|
self.setStyleSheet(
|
||||||
|
"QMainWindow{background:#0b1220;color:#e7eefc;}"
|
||||||
|
"QFrame{background:#0f1a2e;border:1px solid rgba(255,255,255,0.08);border-radius:14px;}"
|
||||||
|
"QTableView{background:#0f1a2e;border:1px solid rgba(255,255,255,0.08);border-radius:14px;}"
|
||||||
|
"QHeaderView::section{background:#0c1628;color:#9bb0d1;border:none;padding:8px;font-weight:700;}"
|
||||||
|
"QPushButton{background:rgba(96,165,250,0.15);border:1px solid rgba(96,165,250,0.35);border-radius:10px;padding:8px 10px;font-weight:700;}"
|
||||||
|
"QPushButton:hover{background:rgba(96,165,250,0.22);}"
|
||||||
|
"QPlainTextEdit{background:#0c1628;border:1px solid rgba(255,255,255,0.08);border-radius:14px;}"
|
||||||
|
)
|
||||||
|
|
||||||
|
self.table.selectionModel().selectionChanged.connect(self._on_select)
|
||||||
|
|
||||||
|
btn_refresh.clicked.connect(self.refresh)
|
||||||
|
btn_evidence.clicked.connect(lambda: self._run_admin("run-evidence"))
|
||||||
|
btn_audit.clicked.connect(lambda: self._run_admin("run-audit"))
|
||||||
|
btn_collect.clicked.connect(lambda: self._run_admin("collect-artifacts"))
|
||||||
|
btn_scan.clicked.connect(self._scan)
|
||||||
|
btn_enable.clicked.connect(lambda: self._run_admin("enable-protection"))
|
||||||
|
btn_disable.clicked.connect(self._disable)
|
||||||
|
|
||||||
|
self.btn_quarantine.clicked.connect(self._quarantine_selected)
|
||||||
|
self.btn_delete.clicked.connect(self._delete_selected)
|
||||||
|
self.det_list.itemSelectionChanged.connect(self._on_det_select)
|
||||||
|
self.btn_stop_scan.clicked.connect(self._stop_scan)
|
||||||
|
|
||||||
|
self._setup_tray()
|
||||||
|
self.refresh()
|
||||||
|
if self.start_in_tray:
|
||||||
|
QtCore.QTimer.singleShot(50, self.hide)
|
||||||
|
|
||||||
|
def _setup_tray(self):
|
||||||
|
self.tray = QtWidgets.QSystemTrayIcon(self.windowIcon(), self)
|
||||||
|
menu = QtWidgets.QMenu()
|
||||||
|
act_open = menu.addAction("Open")
|
||||||
|
act_refresh = menu.addAction("Refresh")
|
||||||
|
menu.addSeparator()
|
||||||
|
act_quit = menu.addAction("Quit")
|
||||||
|
act_open.triggered.connect(self._show)
|
||||||
|
act_refresh.triggered.connect(self.refresh)
|
||||||
|
act_quit.triggered.connect(QtWidgets.QApplication.quit)
|
||||||
|
self.tray.setContextMenu(menu)
|
||||||
|
self.tray.setToolTip("Saikyo Antivirus")
|
||||||
|
self.tray.activated.connect(lambda r: self._show() if r == QtWidgets.QSystemTrayIcon.Trigger else None)
|
||||||
|
self.tray.show()
|
||||||
|
|
||||||
|
def _show(self):
|
||||||
|
self.showNormal()
|
||||||
|
self.raise_()
|
||||||
|
self.activateWindow()
|
||||||
|
|
||||||
|
def closeEvent(self, event: QtGui.QCloseEvent):
|
||||||
|
event.ignore()
|
||||||
|
self.hide()
|
||||||
|
self.tray.showMessage(
|
||||||
|
"Saikyo Antivirus",
|
||||||
|
"Свернуто в трей. Откройте из значка.",
|
||||||
|
QtWidgets.QSystemTrayIcon.Information,
|
||||||
|
2000,
|
||||||
|
)
|
||||||
|
|
||||||
|
def refresh(self):
|
||||||
|
self.reports_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
items = load_reports(self.reports_dir)
|
||||||
|
self.model.set_items(items)
|
||||||
|
# systemctl status doesn't require root for is-enabled/is-active.
|
||||||
|
enabled = "unknown"
|
||||||
|
active = "unknown"
|
||||||
|
try:
|
||||||
|
enabled = subprocess.run(
|
||||||
|
["systemctl", "is-enabled", "saikyo-avd.timer"],
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
).stdout.strip() or enabled
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
active = subprocess.run(
|
||||||
|
["systemctl", "is-active", "saikyo-avd.timer"],
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
).stdout.strip() or active
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
self.status.setText(f"protection: {enabled}/{active} | reports: {len(items)} | {utc_now()}")
|
||||||
|
if items:
|
||||||
|
self.table.selectRow(0)
|
||||||
|
else:
|
||||||
|
self.details.setPlainText("No reports yet. Use the buttons to generate reports.")
|
||||||
|
|
||||||
|
def _selected(self):
|
||||||
|
rows = self.table.selectionModel().selectedRows()
|
||||||
|
if not rows:
|
||||||
|
return None
|
||||||
|
i = rows[0].row()
|
||||||
|
if i < 0 or i >= len(self.model.items):
|
||||||
|
return None
|
||||||
|
return self.model.items[i]
|
||||||
|
|
||||||
|
def _on_select(self, *_):
|
||||||
|
it = self._selected()
|
||||||
|
if not it:
|
||||||
|
return
|
||||||
|
raw = it.get("raw") or {}
|
||||||
|
self.details.setPlainText(json.dumps(raw, ensure_ascii=False, indent=2))
|
||||||
|
|
||||||
|
self.det_list.clear()
|
||||||
|
self.btn_quarantine.setEnabled(False)
|
||||||
|
self.btn_delete.setEnabled(False)
|
||||||
|
|
||||||
|
dets = []
|
||||||
|
try:
|
||||||
|
dets = (raw.get("details") or {}).get("detections") or []
|
||||||
|
except Exception:
|
||||||
|
dets = []
|
||||||
|
|
||||||
|
for d in dets:
|
||||||
|
p = d.get("path")
|
||||||
|
n = d.get("name")
|
||||||
|
if not p:
|
||||||
|
continue
|
||||||
|
item = QtWidgets.QListWidgetItem(f"{p} [{n or 'unknown'}]")
|
||||||
|
item.setData(QtCore.Qt.UserRole, {"path": p, "name": n})
|
||||||
|
self.det_list.addItem(item)
|
||||||
|
|
||||||
|
def _on_det_select(self):
|
||||||
|
sel = self.det_list.selectedItems()
|
||||||
|
ok = bool(sel)
|
||||||
|
self.btn_quarantine.setEnabled(ok)
|
||||||
|
self.btn_delete.setEnabled(ok)
|
||||||
|
|
||||||
|
def _scan(self):
|
||||||
|
if self._scan_proc is not None:
|
||||||
|
QtWidgets.QMessageBox.information(self, "Scan running", "Scan is already running.")
|
||||||
|
return
|
||||||
|
|
||||||
|
r = QtWidgets.QMessageBox.question(
|
||||||
|
self,
|
||||||
|
"Full system scan",
|
||||||
|
"Run full ClamAV scan of '/'? This may take a long time.\n\nProceed?",
|
||||||
|
)
|
||||||
|
if r != QtWidgets.QMessageBox.Yes:
|
||||||
|
return
|
||||||
|
|
||||||
|
self.scan_progress.setVisible(True)
|
||||||
|
self.btn_stop_scan.setEnabled(True)
|
||||||
|
self.scan_status.setText("Scanning… 0s")
|
||||||
|
self._scan_timer.start()
|
||||||
|
self._scan_tick.start()
|
||||||
|
|
||||||
|
proc = QtCore.QProcess(self)
|
||||||
|
proc.setProgram("pkexec")
|
||||||
|
proc.setArguments(["/usr/sbin/saikyo-av-admin", "scan"])
|
||||||
|
proc.setProcessChannelMode(QtCore.QProcess.MergedChannels)
|
||||||
|
|
||||||
|
buf: list[str] = []
|
||||||
|
|
||||||
|
def on_ready():
|
||||||
|
data = bytes(proc.readAllStandardOutput()).decode("utf-8", errors="replace")
|
||||||
|
if data:
|
||||||
|
buf.append(data)
|
||||||
|
|
||||||
|
def on_finished(exit_code, _status):
|
||||||
|
self._scan_tick.stop()
|
||||||
|
self.scan_progress.setVisible(False)
|
||||||
|
self.btn_stop_scan.setEnabled(False)
|
||||||
|
|
||||||
|
out = "".join(buf).strip()
|
||||||
|
self._scan_proc = None
|
||||||
|
self.scan_status.setText("")
|
||||||
|
|
||||||
|
# Refresh reports list after scan.
|
||||||
|
self.refresh()
|
||||||
|
|
||||||
|
if exit_code == 0:
|
||||||
|
if out:
|
||||||
|
QtWidgets.QMessageBox.information(self, "Scan complete", out)
|
||||||
|
else:
|
||||||
|
msg = out or "Scan failed"
|
||||||
|
QtWidgets.QMessageBox.warning(self, "Scan failed", msg)
|
||||||
|
|
||||||
|
proc.readyReadStandardOutput.connect(on_ready)
|
||||||
|
proc.finished.connect(on_finished)
|
||||||
|
|
||||||
|
self._scan_proc = proc
|
||||||
|
proc.start()
|
||||||
|
|
||||||
|
def _selected_detection_path(self) -> str | None:
|
||||||
|
sel = self.det_list.selectedItems()
|
||||||
|
if not sel:
|
||||||
|
return None
|
||||||
|
data = sel[0].data(QtCore.Qt.UserRole) or {}
|
||||||
|
p = data.get("path")
|
||||||
|
if not p:
|
||||||
|
return None
|
||||||
|
return str(p)
|
||||||
|
|
||||||
|
def _quarantine_selected(self):
|
||||||
|
p = self._selected_detection_path()
|
||||||
|
if not p:
|
||||||
|
return
|
||||||
|
r = QtWidgets.QMessageBox.question(
|
||||||
|
self,
|
||||||
|
"Quarantine file",
|
||||||
|
f"Move file to quarantine?\n\n{p}",
|
||||||
|
)
|
||||||
|
if r != QtWidgets.QMessageBox.Yes:
|
||||||
|
return
|
||||||
|
self._run_admin_with_arg("quarantine", p)
|
||||||
|
|
||||||
|
def _delete_selected(self):
|
||||||
|
p = self._selected_detection_path()
|
||||||
|
if not p:
|
||||||
|
return
|
||||||
|
r1 = QtWidgets.QMessageBox.warning(
|
||||||
|
self,
|
||||||
|
"Delete infected file",
|
||||||
|
f"This will permanently delete the file:\n\n{p}\n\nContinue?",
|
||||||
|
QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No,
|
||||||
|
)
|
||||||
|
if r1 != QtWidgets.QMessageBox.Yes:
|
||||||
|
return
|
||||||
|
r2 = QtWidgets.QMessageBox.warning(
|
||||||
|
self,
|
||||||
|
"Confirm delete",
|
||||||
|
"Are you absolutely sure? This cannot be undone.",
|
||||||
|
QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No,
|
||||||
|
)
|
||||||
|
if r2 != QtWidgets.QMessageBox.Yes:
|
||||||
|
return
|
||||||
|
self._run_admin_with_arg("delete", p)
|
||||||
|
|
||||||
|
def _disable(self):
|
||||||
|
r = QtWidgets.QMessageBox.question(
|
||||||
|
self,
|
||||||
|
"Disable protection",
|
||||||
|
"Disable protection requires explicit operator consent. Action will be logged.",
|
||||||
|
)
|
||||||
|
if r != QtWidgets.QMessageBox.Yes:
|
||||||
|
return
|
||||||
|
self._run_admin("disable-protection")
|
||||||
|
|
||||||
|
def _run_admin(self, action: str):
|
||||||
|
cmd = ["pkexec", "/usr/sbin/saikyo-av-admin", action]
|
||||||
|
proc = subprocess.run(cmd, capture_output=True, text=True)
|
||||||
|
if proc.returncode == 0:
|
||||||
|
self.refresh()
|
||||||
|
out = (proc.stdout or "").strip()
|
||||||
|
if out:
|
||||||
|
QtWidgets.QMessageBox.information(self, "Done", out)
|
||||||
|
else:
|
||||||
|
msg = (proc.stderr or proc.stdout or "failed").strip()
|
||||||
|
QtWidgets.QMessageBox.warning(self, "Failed", msg)
|
||||||
|
|
||||||
|
def _update_scan_elapsed(self):
|
||||||
|
if self._scan_proc is None:
|
||||||
|
return
|
||||||
|
ms = self._scan_timer.elapsed()
|
||||||
|
self.scan_status.setText(f"Scanning… {int(ms/1000)}s")
|
||||||
|
|
||||||
|
def _stop_scan(self):
|
||||||
|
if self._scan_proc is None:
|
||||||
|
return
|
||||||
|
r = QtWidgets.QMessageBox.question(
|
||||||
|
self,
|
||||||
|
"Stop scan",
|
||||||
|
"Stop the running scan?",
|
||||||
|
)
|
||||||
|
if r != QtWidgets.QMessageBox.Yes:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
self._scan_proc.kill()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def _run_admin_with_arg(self, action: str, arg: str):
|
||||||
|
cmd = ["pkexec", "/usr/sbin/saikyo-av-admin", action, arg]
|
||||||
|
proc = subprocess.run(cmd, capture_output=True, text=True)
|
||||||
|
if proc.returncode == 0:
|
||||||
|
self.refresh()
|
||||||
|
out = (proc.stdout or "").strip()
|
||||||
|
if out:
|
||||||
|
QtWidgets.QMessageBox.information(self, "Done", out)
|
||||||
|
else:
|
||||||
|
msg = (proc.stderr or proc.stdout or "failed").strip()
|
||||||
|
QtWidgets.QMessageBox.warning(self, "Failed", msg)
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
ap = argparse.ArgumentParser(prog="saikyo-av-gui")
|
||||||
|
ap.add_argument("--tray", action="store_true")
|
||||||
|
ap.add_argument("--reports-dir", default=REPORTS_DIR_DEFAULT)
|
||||||
|
args = ap.parse_args()
|
||||||
|
|
||||||
|
app = QtWidgets.QApplication(sys.argv)
|
||||||
|
w = MainWindow(pathlib.Path(args.reports_dir), start_in_tray=args.tray)
|
||||||
|
w.show()
|
||||||
|
return app.exec_()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
saikyo-av-gui (0.1.4) stable; urgency=medium
|
||||||
|
|
||||||
|
* Make scan asynchronous: progress indicator and stop button.
|
||||||
|
|
||||||
|
-- SAIKYO OS <support@saikyo-os.ru> Mon, 20 Jan 2026 17:40:00 +0000
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
./bin/saikyo-av-gui
|
||||||
|
./bin/saikyo-av-admin
|
||||||
|
./desktop/saikyo-av-gui.desktop
|
||||||
|
./autostart/saikyo-av-gui.desktop
|
||||||
|
./polkit/org.saikyo.av.admin.policy
|
||||||
|
./icons/saikyo-av.svg
|
||||||
5
saikyo-av-gui/debian/changelog
Normal file
5
saikyo-av-gui/debian/changelog
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
saikyo-av-gui (0.1.4) stable; urgency=medium
|
||||||
|
|
||||||
|
* Make scan asynchronous: progress indicator and stop button.
|
||||||
|
|
||||||
|
-- SAIKYO OS <support@saikyo-os.ru> Mon, 20 Jan 2026 17:40:00 +0000
|
||||||
14
saikyo-av-gui/debian/control
Normal file
14
saikyo-av-gui/debian/control
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
Source: saikyo-av-gui
|
||||||
|
Section: admin
|
||||||
|
Priority: optional
|
||||||
|
Maintainer: SAIKYO OS <support@saikyo-os.ru>
|
||||||
|
Build-Depends: debhelper-compat (= 13)
|
||||||
|
Standards-Version: 4.6.2
|
||||||
|
Rules-Requires-Root: no
|
||||||
|
|
||||||
|
Package: saikyo-av-gui
|
||||||
|
Architecture: all
|
||||||
|
Depends: ${misc:Depends}, python3, python3-pyqt5, xdg-utils, polkitd, pkexec
|
||||||
|
Recommends: saikyo-audit-report
|
||||||
|
Description: Saikyo Antivirus desktop admin panel (local)
|
||||||
|
Native desktop UI (system tray) for Saikyo Antivirus and registry verification actions.
|
||||||
26
saikyo-av-gui/debian/copyright
Normal file
26
saikyo-av-gui/debian/copyright
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
||||||
|
Upstream-Name: saikyo-av-gui
|
||||||
|
Source: https://saikyo-os.ru/
|
||||||
|
|
||||||
|
Files: *
|
||||||
|
Copyright: 2026 SAIKYO OS
|
||||||
|
License: MIT
|
||||||
|
|
||||||
|
License: MIT
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
.
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
.
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
1
saikyo-av-gui/debian/debhelper-build-stamp
Normal file
1
saikyo-av-gui/debian/debhelper-build-stamp
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
saikyo-av-gui
|
||||||
2
saikyo-av-gui/debian/files
Normal file
2
saikyo-av-gui/debian/files
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
saikyo-av-gui_0.1.4_all.deb admin optional
|
||||||
|
saikyo-av-gui_0.1.4_amd64.buildinfo admin optional
|
||||||
6
saikyo-av-gui/debian/install
Normal file
6
saikyo-av-gui/debian/install
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
bin/saikyo-av-gui usr/bin/
|
||||||
|
bin/saikyo-av-admin usr/sbin/
|
||||||
|
desktop/saikyo-av-gui.desktop usr/share/applications/
|
||||||
|
autostart/saikyo-av-gui.desktop etc/xdg/autostart/
|
||||||
|
polkit/org.saikyo.av.admin.policy usr/share/polkit-1/actions/
|
||||||
|
icons/saikyo-av.svg usr/share/icons/hicolor/scalable/apps/
|
||||||
16
saikyo-av-gui/debian/rules
Executable file
16
saikyo-av-gui/debian/rules
Executable file
|
|
@ -0,0 +1,16 @@
|
||||||
|
#!/usr/bin/make -f
|
||||||
|
|
||||||
|
%:
|
||||||
|
dh $@
|
||||||
|
|
||||||
|
override_dh_auto_build:
|
||||||
|
|
||||||
|
override_dh_auto_test:
|
||||||
|
|
||||||
|
override_dh_fixperms:
|
||||||
|
dh_fixperms
|
||||||
|
chmod 0755 debian/saikyo-av-gui/usr/bin/saikyo-av-gui
|
||||||
|
chmod 0755 debian/saikyo-av-gui/usr/sbin/saikyo-av-admin
|
||||||
|
chmod 0755 debian/saikyo-av-gui.preinst
|
||||||
|
chmod 0755 debian/saikyo-av-gui.postinst
|
||||||
|
chmod 0755 debian/saikyo-av-gui.prerm
|
||||||
1
saikyo-av-gui/debian/saikyo-av-gui.debhelper.log
Normal file
1
saikyo-av-gui/debian/saikyo-av-gui.debhelper.log
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
dh_fixperms
|
||||||
11
saikyo-av-gui/debian/saikyo-av-gui.postinst
Executable file
11
saikyo-av-gui/debian/saikyo-av-gui.postinst
Executable file
|
|
@ -0,0 +1,11 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
case "$1" in
|
||||||
|
configure)
|
||||||
|
mkdir -p /var/lib/saikyo-av/reports /var/log/saikyo-av || true
|
||||||
|
chmod 0755 /var/lib/saikyo-av /var/lib/saikyo-av/reports /var/log/saikyo-av 2>/dev/null || true
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
exit 0
|
||||||
20
saikyo-av-gui/debian/saikyo-av-gui.preinst
Executable file
20
saikyo-av-gui/debian/saikyo-av-gui.preinst
Executable file
|
|
@ -0,0 +1,20 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Fixup for packaging mistake (0.1.0/0.1.1) where dh_install created directories:
|
||||||
|
# /usr/sbin/saikyo-av-admin/saikyo-av-admin
|
||||||
|
# /etc/xdg/autostart/saikyo-av-gui.desktop/saikyo-av-gui.desktop
|
||||||
|
# Remove them before unpacking corrected package.
|
||||||
|
|
||||||
|
if [[ "$1" == "upgrade" || "$1" == "install" ]]; then
|
||||||
|
for d in \
|
||||||
|
/usr/sbin/saikyo-av-admin \
|
||||||
|
/etc/xdg/autostart/saikyo-av-gui.desktop
|
||||||
|
do
|
||||||
|
if [[ -d "$d" ]]; then
|
||||||
|
rm -rf "$d" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
exit 0
|
||||||
4
saikyo-av-gui/debian/saikyo-av-gui.prerm
Executable file
4
saikyo-av-gui/debian/saikyo-av-gui.prerm
Executable file
|
|
@ -0,0 +1,4 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
exit 0
|
||||||
2
saikyo-av-gui/debian/saikyo-av-gui.substvars
Normal file
2
saikyo-av-gui/debian/saikyo-av-gui.substvars
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
misc:Depends=
|
||||||
|
misc:Pre-Depends=
|
||||||
1
saikyo-av-gui/debian/saikyo-av-gui/DEBIAN/conffiles
Normal file
1
saikyo-av-gui/debian/saikyo-av-gui/DEBIAN/conffiles
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
/etc/xdg/autostart/saikyo-av-gui.desktop
|
||||||
11
saikyo-av-gui/debian/saikyo-av-gui/DEBIAN/control
Normal file
11
saikyo-av-gui/debian/saikyo-av-gui/DEBIAN/control
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
Package: saikyo-av-gui
|
||||||
|
Version: 0.1.4
|
||||||
|
Architecture: all
|
||||||
|
Maintainer: SAIKYO OS <support@saikyo-os.ru>
|
||||||
|
Installed-Size: 59
|
||||||
|
Depends: python3, python3-pyqt5, xdg-utils, polkitd, pkexec
|
||||||
|
Recommends: saikyo-audit-report
|
||||||
|
Section: admin
|
||||||
|
Priority: optional
|
||||||
|
Description: Saikyo Antivirus desktop admin panel (local)
|
||||||
|
Native desktop UI (system tray) for Saikyo Antivirus and registry verification actions.
|
||||||
7
saikyo-av-gui/debian/saikyo-av-gui/DEBIAN/md5sums
Normal file
7
saikyo-av-gui/debian/saikyo-av-gui/DEBIAN/md5sums
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
df448873f75684e84718315a3036d104 usr/bin/saikyo-av-gui
|
||||||
|
2cf2d7c3465f90e05e9e3c34a8d1ede2 usr/sbin/saikyo-av-admin
|
||||||
|
5bdebcd69cfeabc37b34f22edb72c851 usr/share/applications/saikyo-av-gui.desktop
|
||||||
|
390713466d3468f6733a8e1b3f20a231 usr/share/doc/saikyo-av-gui/changelog.gz
|
||||||
|
7b9b58e66ef873f2c38b3a148fa3e513 usr/share/doc/saikyo-av-gui/copyright
|
||||||
|
5161807a723b0a180d3d0e29fee68753 usr/share/icons/hicolor/scalable/apps/saikyo-av.svg
|
||||||
|
1522d63a46b6d96da78f1742361b6b8b usr/share/polkit-1/actions/org.saikyo.av.admin.policy
|
||||||
11
saikyo-av-gui/debian/saikyo-av-gui/DEBIAN/postinst
Executable file
11
saikyo-av-gui/debian/saikyo-av-gui/DEBIAN/postinst
Executable file
|
|
@ -0,0 +1,11 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
case "$1" in
|
||||||
|
configure)
|
||||||
|
mkdir -p /var/lib/saikyo-av/reports /var/log/saikyo-av || true
|
||||||
|
chmod 0755 /var/lib/saikyo-av /var/lib/saikyo-av/reports /var/log/saikyo-av 2>/dev/null || true
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
exit 0
|
||||||
20
saikyo-av-gui/debian/saikyo-av-gui/DEBIAN/preinst
Executable file
20
saikyo-av-gui/debian/saikyo-av-gui/DEBIAN/preinst
Executable file
|
|
@ -0,0 +1,20 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Fixup for packaging mistake (0.1.0/0.1.1) where dh_install created directories:
|
||||||
|
# /usr/sbin/saikyo-av-admin/saikyo-av-admin
|
||||||
|
# /etc/xdg/autostart/saikyo-av-gui.desktop/saikyo-av-gui.desktop
|
||||||
|
# Remove them before unpacking corrected package.
|
||||||
|
|
||||||
|
if [[ "$1" == "upgrade" || "$1" == "install" ]]; then
|
||||||
|
for d in \
|
||||||
|
/usr/sbin/saikyo-av-admin \
|
||||||
|
/etc/xdg/autostart/saikyo-av-gui.desktop
|
||||||
|
do
|
||||||
|
if [[ -d "$d" ]]; then
|
||||||
|
rm -rf "$d" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
exit 0
|
||||||
4
saikyo-av-gui/debian/saikyo-av-gui/DEBIAN/prerm
Executable file
4
saikyo-av-gui/debian/saikyo-av-gui/DEBIAN/prerm
Executable file
|
|
@ -0,0 +1,4 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
exit 0
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
[Desktop Entry]
|
||||||
|
Type=Application
|
||||||
|
Name=Saikyo Antivirus (Tray)
|
||||||
|
Comment=Start Saikyo Antivirus tray app
|
||||||
|
Exec=/usr/bin/saikyo-av-gui --tray
|
||||||
|
Icon=saikyo-av
|
||||||
|
Terminal=false
|
||||||
|
X-GNOME-Autostart-enabled=true
|
||||||
532
saikyo-av-gui/debian/saikyo-av-gui/usr/bin/saikyo-av-gui
Executable file
532
saikyo-av-gui/debian/saikyo-av-gui/usr/bin/saikyo-av-gui
Executable file
|
|
@ -0,0 +1,532 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
import pathlib
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
from datetime import datetime, timezone
|
||||||
|
|
||||||
|
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||||
|
|
||||||
|
REPORTS_DIR_DEFAULT = "/var/lib/saikyo-av/reports"
|
||||||
|
|
||||||
|
|
||||||
|
def utc_now() -> str:
|
||||||
|
return datetime.now(tz=timezone.utc).isoformat(timespec="seconds")
|
||||||
|
|
||||||
|
|
||||||
|
def load_reports(reports_dir: pathlib.Path):
|
||||||
|
items = []
|
||||||
|
if not reports_dir.exists():
|
||||||
|
return items
|
||||||
|
for p in sorted(reports_dir.glob("*.json"), key=lambda x: x.stat().st_mtime, reverse=True):
|
||||||
|
try:
|
||||||
|
data = json.loads(p.read_text(encoding="utf-8"))
|
||||||
|
except Exception:
|
||||||
|
data = {}
|
||||||
|
items.append(
|
||||||
|
{
|
||||||
|
"id": p.stem,
|
||||||
|
"path": str(p),
|
||||||
|
"summary": data.get("summary") or "(no summary)",
|
||||||
|
"severity": data.get("severity") or "unknown",
|
||||||
|
"created_utc": data.get("created_utc") or "",
|
||||||
|
"raw": data,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return items
|
||||||
|
|
||||||
|
|
||||||
|
class ReportsModel(QtCore.QAbstractTableModel):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
self.items = []
|
||||||
|
|
||||||
|
def rowCount(self, parent=QtCore.QModelIndex()):
|
||||||
|
return len(self.items)
|
||||||
|
|
||||||
|
def columnCount(self, parent=QtCore.QModelIndex()):
|
||||||
|
return 3
|
||||||
|
|
||||||
|
def headerData(self, section, orientation, role=QtCore.Qt.DisplayRole):
|
||||||
|
if role != QtCore.Qt.DisplayRole:
|
||||||
|
return None
|
||||||
|
if orientation == QtCore.Qt.Horizontal:
|
||||||
|
return ["Summary", "Severity", "Created (UTC)"][section]
|
||||||
|
return str(section + 1)
|
||||||
|
|
||||||
|
def data(self, index, role=QtCore.Qt.DisplayRole):
|
||||||
|
if not index.isValid():
|
||||||
|
return None
|
||||||
|
item = self.items[index.row()]
|
||||||
|
col = index.column()
|
||||||
|
|
||||||
|
if role == QtCore.Qt.DisplayRole:
|
||||||
|
if col == 0:
|
||||||
|
return item.get("summary")
|
||||||
|
if col == 1:
|
||||||
|
return item.get("severity")
|
||||||
|
if col == 2:
|
||||||
|
return item.get("created_utc")
|
||||||
|
|
||||||
|
if role == QtCore.Qt.ForegroundRole and col == 1:
|
||||||
|
sev = (item.get("severity") or "").lower()
|
||||||
|
if sev in {"ok", "info", "low"}:
|
||||||
|
return QtGui.QBrush(QtGui.QColor("#2dd4bf"))
|
||||||
|
if sev in {"medium", "warn", "warning"}:
|
||||||
|
return QtGui.QBrush(QtGui.QColor("#fbbf24"))
|
||||||
|
if sev in {"high", "critical", "bad"}:
|
||||||
|
return QtGui.QBrush(QtGui.QColor("#fb7185"))
|
||||||
|
return QtGui.QBrush(QtGui.QColor("#9bb0d1"))
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def set_items(self, items):
|
||||||
|
self.beginResetModel()
|
||||||
|
self.items = items
|
||||||
|
self.endResetModel()
|
||||||
|
|
||||||
|
|
||||||
|
class MainWindow(QtWidgets.QMainWindow):
|
||||||
|
def __init__(self, reports_dir: pathlib.Path, start_in_tray: bool):
|
||||||
|
super().__init__()
|
||||||
|
self.reports_dir = reports_dir
|
||||||
|
self.start_in_tray = start_in_tray
|
||||||
|
|
||||||
|
self.setWindowTitle("Saikyo Antivirus")
|
||||||
|
self.setWindowIcon(QtGui.QIcon.fromTheme("saikyo-av", QtGui.QIcon.fromTheme("security-high")))
|
||||||
|
self.setMinimumSize(980, 620)
|
||||||
|
|
||||||
|
self.model = ReportsModel()
|
||||||
|
self.table = QtWidgets.QTableView()
|
||||||
|
self.table.setModel(self.model)
|
||||||
|
self.table.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
|
||||||
|
self.table.setSelectionMode(QtWidgets.QAbstractItemView.SingleSelection)
|
||||||
|
self.table.horizontalHeader().setStretchLastSection(True)
|
||||||
|
self.table.horizontalHeader().setSectionResizeMode(0, QtWidgets.QHeaderView.Stretch)
|
||||||
|
self.table.horizontalHeader().setSectionResizeMode(1, QtWidgets.QHeaderView.ResizeToContents)
|
||||||
|
self.table.horizontalHeader().setSectionResizeMode(2, QtWidgets.QHeaderView.ResizeToContents)
|
||||||
|
|
||||||
|
self.details = QtWidgets.QPlainTextEdit()
|
||||||
|
self.details.setReadOnly(True)
|
||||||
|
self.details.setFont(QtGui.QFontDatabase.systemFont(QtGui.QFontDatabase.FixedFont))
|
||||||
|
|
||||||
|
self.det_list = QtWidgets.QListWidget()
|
||||||
|
self.det_list.setSelectionMode(QtWidgets.QAbstractItemView.SingleSelection)
|
||||||
|
|
||||||
|
self.btn_quarantine = QtWidgets.QPushButton("Quarantine")
|
||||||
|
self.btn_delete = QtWidgets.QPushButton("Delete")
|
||||||
|
for b in [self.btn_quarantine, self.btn_delete]:
|
||||||
|
b.setCursor(QtCore.Qt.PointingHandCursor)
|
||||||
|
b.setMinimumHeight(32)
|
||||||
|
self.btn_delete.setStyleSheet("background:#2b1420;color:#ffd4df;border:1px solid #5b2031;")
|
||||||
|
self.btn_quarantine.setEnabled(False)
|
||||||
|
self.btn_delete.setEnabled(False)
|
||||||
|
|
||||||
|
self.scan_status = QtWidgets.QLabel("")
|
||||||
|
self.scan_status.setStyleSheet("color:#9bb0d1")
|
||||||
|
self.scan_progress = QtWidgets.QProgressBar()
|
||||||
|
self.scan_progress.setRange(0, 0)
|
||||||
|
self.scan_progress.setVisible(False)
|
||||||
|
self.btn_stop_scan = QtWidgets.QPushButton("Stop scan")
|
||||||
|
self.btn_stop_scan.setCursor(QtCore.Qt.PointingHandCursor)
|
||||||
|
self.btn_stop_scan.setMinimumHeight(32)
|
||||||
|
self.btn_stop_scan.setEnabled(False)
|
||||||
|
|
||||||
|
self._scan_proc: QtCore.QProcess | None = None
|
||||||
|
self._scan_timer = QtCore.QElapsedTimer()
|
||||||
|
self._scan_tick = QtCore.QTimer(self)
|
||||||
|
self._scan_tick.setInterval(500)
|
||||||
|
self._scan_tick.timeout.connect(self._update_scan_elapsed)
|
||||||
|
|
||||||
|
self.status = QtWidgets.QLabel("local")
|
||||||
|
self.status.setStyleSheet("color:#9bb0d1")
|
||||||
|
|
||||||
|
btn_evidence = QtWidgets.QPushButton("Evidence")
|
||||||
|
btn_audit = QtWidgets.QPushButton("Audit")
|
||||||
|
btn_collect = QtWidgets.QPushButton("Collect")
|
||||||
|
btn_scan = QtWidgets.QPushButton("SCAN")
|
||||||
|
btn_enable = QtWidgets.QPushButton("Enable")
|
||||||
|
btn_disable = QtWidgets.QPushButton("Disable…")
|
||||||
|
btn_refresh = QtWidgets.QPushButton("Refresh")
|
||||||
|
|
||||||
|
for b in [btn_evidence, btn_audit, btn_collect, btn_scan, btn_enable, btn_disable, btn_refresh]:
|
||||||
|
b.setCursor(QtCore.Qt.PointingHandCursor)
|
||||||
|
b.setMinimumHeight(36)
|
||||||
|
|
||||||
|
btn_scan.setStyleSheet("background:rgba(45,212,191,0.18);border:1px solid rgba(45,212,191,0.35);")
|
||||||
|
|
||||||
|
btn_disable.setStyleSheet("background:#2b1420;color:#ffd4df;border:1px solid #5b2031;")
|
||||||
|
|
||||||
|
left = QtWidgets.QFrame()
|
||||||
|
left.setFrameShape(QtWidgets.QFrame.StyledPanel)
|
||||||
|
ll = QtWidgets.QVBoxLayout(left)
|
||||||
|
ll.setContentsMargins(12, 12, 12, 12)
|
||||||
|
ll.setSpacing(10)
|
||||||
|
|
||||||
|
title = QtWidgets.QLabel("Admin checks")
|
||||||
|
title.setStyleSheet("font-weight:800;font-size:16px")
|
||||||
|
hint = QtWidgets.QLabel("Кнопки запускают проверки с подтверждением (pkexec).")
|
||||||
|
hint.setWordWrap(True)
|
||||||
|
hint.setStyleSheet("color:#9bb0d1;font-size:12px")
|
||||||
|
|
||||||
|
ll.addWidget(title)
|
||||||
|
ll.addWidget(hint)
|
||||||
|
ll.addSpacing(4)
|
||||||
|
ll.addWidget(btn_evidence)
|
||||||
|
ll.addWidget(btn_audit)
|
||||||
|
ll.addWidget(btn_collect)
|
||||||
|
ll.addSpacing(8)
|
||||||
|
ll.addWidget(btn_scan)
|
||||||
|
ll.addSpacing(8)
|
||||||
|
ll.addWidget(btn_enable)
|
||||||
|
ll.addWidget(btn_disable)
|
||||||
|
ll.addStretch(1)
|
||||||
|
ll.addWidget(btn_refresh)
|
||||||
|
ll.addWidget(self.status)
|
||||||
|
|
||||||
|
splitter = QtWidgets.QSplitter()
|
||||||
|
splitter.setOrientation(QtCore.Qt.Vertical)
|
||||||
|
top = QtWidgets.QWidget()
|
||||||
|
tl = QtWidgets.QVBoxLayout(top)
|
||||||
|
tl.setContentsMargins(0, 0, 0, 0)
|
||||||
|
tl.addWidget(self.table)
|
||||||
|
splitter.addWidget(top)
|
||||||
|
details_box = QtWidgets.QWidget()
|
||||||
|
dl = QtWidgets.QVBoxLayout(details_box)
|
||||||
|
dl.setContentsMargins(0, 0, 0, 0)
|
||||||
|
dl.setSpacing(8)
|
||||||
|
|
||||||
|
det_head = QtWidgets.QHBoxLayout()
|
||||||
|
det_title = QtWidgets.QLabel("Detections")
|
||||||
|
det_title.setStyleSheet("font-weight:800;")
|
||||||
|
det_head.addWidget(det_title)
|
||||||
|
det_head.addStretch(1)
|
||||||
|
det_head.addWidget(self.scan_status)
|
||||||
|
det_head.addWidget(self.scan_progress)
|
||||||
|
det_head.addWidget(self.btn_stop_scan)
|
||||||
|
det_head.addWidget(self.btn_quarantine)
|
||||||
|
det_head.addWidget(self.btn_delete)
|
||||||
|
dl.addLayout(det_head)
|
||||||
|
dl.addWidget(self.det_list)
|
||||||
|
|
||||||
|
raw_title = QtWidgets.QLabel("Report JSON")
|
||||||
|
raw_title.setStyleSheet("font-weight:800;")
|
||||||
|
dl.addWidget(raw_title)
|
||||||
|
dl.addWidget(self.details)
|
||||||
|
|
||||||
|
splitter.addWidget(details_box)
|
||||||
|
splitter.setStretchFactor(0, 2)
|
||||||
|
splitter.setStretchFactor(1, 1)
|
||||||
|
|
||||||
|
central = QtWidgets.QWidget()
|
||||||
|
root = QtWidgets.QHBoxLayout(central)
|
||||||
|
root.setContentsMargins(16, 16, 16, 16)
|
||||||
|
root.setSpacing(12)
|
||||||
|
left.setFixedWidth(300)
|
||||||
|
root.addWidget(left)
|
||||||
|
root.addWidget(splitter)
|
||||||
|
self.setCentralWidget(central)
|
||||||
|
|
||||||
|
self.setStyleSheet(
|
||||||
|
"QMainWindow{background:#0b1220;color:#e7eefc;}"
|
||||||
|
"QFrame{background:#0f1a2e;border:1px solid rgba(255,255,255,0.08);border-radius:14px;}"
|
||||||
|
"QTableView{background:#0f1a2e;border:1px solid rgba(255,255,255,0.08);border-radius:14px;}"
|
||||||
|
"QHeaderView::section{background:#0c1628;color:#9bb0d1;border:none;padding:8px;font-weight:700;}"
|
||||||
|
"QPushButton{background:rgba(96,165,250,0.15);border:1px solid rgba(96,165,250,0.35);border-radius:10px;padding:8px 10px;font-weight:700;}"
|
||||||
|
"QPushButton:hover{background:rgba(96,165,250,0.22);}"
|
||||||
|
"QPlainTextEdit{background:#0c1628;border:1px solid rgba(255,255,255,0.08);border-radius:14px;}"
|
||||||
|
)
|
||||||
|
|
||||||
|
self.table.selectionModel().selectionChanged.connect(self._on_select)
|
||||||
|
|
||||||
|
btn_refresh.clicked.connect(self.refresh)
|
||||||
|
btn_evidence.clicked.connect(lambda: self._run_admin("run-evidence"))
|
||||||
|
btn_audit.clicked.connect(lambda: self._run_admin("run-audit"))
|
||||||
|
btn_collect.clicked.connect(lambda: self._run_admin("collect-artifacts"))
|
||||||
|
btn_scan.clicked.connect(self._scan)
|
||||||
|
btn_enable.clicked.connect(lambda: self._run_admin("enable-protection"))
|
||||||
|
btn_disable.clicked.connect(self._disable)
|
||||||
|
|
||||||
|
self.btn_quarantine.clicked.connect(self._quarantine_selected)
|
||||||
|
self.btn_delete.clicked.connect(self._delete_selected)
|
||||||
|
self.det_list.itemSelectionChanged.connect(self._on_det_select)
|
||||||
|
self.btn_stop_scan.clicked.connect(self._stop_scan)
|
||||||
|
|
||||||
|
self._setup_tray()
|
||||||
|
self.refresh()
|
||||||
|
if self.start_in_tray:
|
||||||
|
QtCore.QTimer.singleShot(50, self.hide)
|
||||||
|
|
||||||
|
def _setup_tray(self):
|
||||||
|
self.tray = QtWidgets.QSystemTrayIcon(self.windowIcon(), self)
|
||||||
|
menu = QtWidgets.QMenu()
|
||||||
|
act_open = menu.addAction("Open")
|
||||||
|
act_refresh = menu.addAction("Refresh")
|
||||||
|
menu.addSeparator()
|
||||||
|
act_quit = menu.addAction("Quit")
|
||||||
|
act_open.triggered.connect(self._show)
|
||||||
|
act_refresh.triggered.connect(self.refresh)
|
||||||
|
act_quit.triggered.connect(QtWidgets.QApplication.quit)
|
||||||
|
self.tray.setContextMenu(menu)
|
||||||
|
self.tray.setToolTip("Saikyo Antivirus")
|
||||||
|
self.tray.activated.connect(lambda r: self._show() if r == QtWidgets.QSystemTrayIcon.Trigger else None)
|
||||||
|
self.tray.show()
|
||||||
|
|
||||||
|
def _show(self):
|
||||||
|
self.showNormal()
|
||||||
|
self.raise_()
|
||||||
|
self.activateWindow()
|
||||||
|
|
||||||
|
def closeEvent(self, event: QtGui.QCloseEvent):
|
||||||
|
event.ignore()
|
||||||
|
self.hide()
|
||||||
|
self.tray.showMessage(
|
||||||
|
"Saikyo Antivirus",
|
||||||
|
"Свернуто в трей. Откройте из значка.",
|
||||||
|
QtWidgets.QSystemTrayIcon.Information,
|
||||||
|
2000,
|
||||||
|
)
|
||||||
|
|
||||||
|
def refresh(self):
|
||||||
|
self.reports_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
items = load_reports(self.reports_dir)
|
||||||
|
self.model.set_items(items)
|
||||||
|
# systemctl status doesn't require root for is-enabled/is-active.
|
||||||
|
enabled = "unknown"
|
||||||
|
active = "unknown"
|
||||||
|
try:
|
||||||
|
enabled = subprocess.run(
|
||||||
|
["systemctl", "is-enabled", "saikyo-avd.timer"],
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
).stdout.strip() or enabled
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
active = subprocess.run(
|
||||||
|
["systemctl", "is-active", "saikyo-avd.timer"],
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
).stdout.strip() or active
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
self.status.setText(f"protection: {enabled}/{active} | reports: {len(items)} | {utc_now()}")
|
||||||
|
if items:
|
||||||
|
self.table.selectRow(0)
|
||||||
|
else:
|
||||||
|
self.details.setPlainText("No reports yet. Use the buttons to generate reports.")
|
||||||
|
|
||||||
|
def _selected(self):
|
||||||
|
rows = self.table.selectionModel().selectedRows()
|
||||||
|
if not rows:
|
||||||
|
return None
|
||||||
|
i = rows[0].row()
|
||||||
|
if i < 0 or i >= len(self.model.items):
|
||||||
|
return None
|
||||||
|
return self.model.items[i]
|
||||||
|
|
||||||
|
def _on_select(self, *_):
|
||||||
|
it = self._selected()
|
||||||
|
if not it:
|
||||||
|
return
|
||||||
|
raw = it.get("raw") or {}
|
||||||
|
self.details.setPlainText(json.dumps(raw, ensure_ascii=False, indent=2))
|
||||||
|
|
||||||
|
self.det_list.clear()
|
||||||
|
self.btn_quarantine.setEnabled(False)
|
||||||
|
self.btn_delete.setEnabled(False)
|
||||||
|
|
||||||
|
dets = []
|
||||||
|
try:
|
||||||
|
dets = (raw.get("details") or {}).get("detections") or []
|
||||||
|
except Exception:
|
||||||
|
dets = []
|
||||||
|
|
||||||
|
for d in dets:
|
||||||
|
p = d.get("path")
|
||||||
|
n = d.get("name")
|
||||||
|
if not p:
|
||||||
|
continue
|
||||||
|
item = QtWidgets.QListWidgetItem(f"{p} [{n or 'unknown'}]")
|
||||||
|
item.setData(QtCore.Qt.UserRole, {"path": p, "name": n})
|
||||||
|
self.det_list.addItem(item)
|
||||||
|
|
||||||
|
def _on_det_select(self):
|
||||||
|
sel = self.det_list.selectedItems()
|
||||||
|
ok = bool(sel)
|
||||||
|
self.btn_quarantine.setEnabled(ok)
|
||||||
|
self.btn_delete.setEnabled(ok)
|
||||||
|
|
||||||
|
def _scan(self):
|
||||||
|
if self._scan_proc is not None:
|
||||||
|
QtWidgets.QMessageBox.information(self, "Scan running", "Scan is already running.")
|
||||||
|
return
|
||||||
|
|
||||||
|
r = QtWidgets.QMessageBox.question(
|
||||||
|
self,
|
||||||
|
"Full system scan",
|
||||||
|
"Run full ClamAV scan of '/'? This may take a long time.\n\nProceed?",
|
||||||
|
)
|
||||||
|
if r != QtWidgets.QMessageBox.Yes:
|
||||||
|
return
|
||||||
|
|
||||||
|
self.scan_progress.setVisible(True)
|
||||||
|
self.btn_stop_scan.setEnabled(True)
|
||||||
|
self.scan_status.setText("Scanning… 0s")
|
||||||
|
self._scan_timer.start()
|
||||||
|
self._scan_tick.start()
|
||||||
|
|
||||||
|
proc = QtCore.QProcess(self)
|
||||||
|
proc.setProgram("pkexec")
|
||||||
|
proc.setArguments(["/usr/sbin/saikyo-av-admin", "scan"])
|
||||||
|
proc.setProcessChannelMode(QtCore.QProcess.MergedChannels)
|
||||||
|
|
||||||
|
buf: list[str] = []
|
||||||
|
|
||||||
|
def on_ready():
|
||||||
|
data = bytes(proc.readAllStandardOutput()).decode("utf-8", errors="replace")
|
||||||
|
if data:
|
||||||
|
buf.append(data)
|
||||||
|
|
||||||
|
def on_finished(exit_code, _status):
|
||||||
|
self._scan_tick.stop()
|
||||||
|
self.scan_progress.setVisible(False)
|
||||||
|
self.btn_stop_scan.setEnabled(False)
|
||||||
|
|
||||||
|
out = "".join(buf).strip()
|
||||||
|
self._scan_proc = None
|
||||||
|
self.scan_status.setText("")
|
||||||
|
|
||||||
|
# Refresh reports list after scan.
|
||||||
|
self.refresh()
|
||||||
|
|
||||||
|
if exit_code == 0:
|
||||||
|
if out:
|
||||||
|
QtWidgets.QMessageBox.information(self, "Scan complete", out)
|
||||||
|
else:
|
||||||
|
msg = out or "Scan failed"
|
||||||
|
QtWidgets.QMessageBox.warning(self, "Scan failed", msg)
|
||||||
|
|
||||||
|
proc.readyReadStandardOutput.connect(on_ready)
|
||||||
|
proc.finished.connect(on_finished)
|
||||||
|
|
||||||
|
self._scan_proc = proc
|
||||||
|
proc.start()
|
||||||
|
|
||||||
|
def _selected_detection_path(self) -> str | None:
|
||||||
|
sel = self.det_list.selectedItems()
|
||||||
|
if not sel:
|
||||||
|
return None
|
||||||
|
data = sel[0].data(QtCore.Qt.UserRole) or {}
|
||||||
|
p = data.get("path")
|
||||||
|
if not p:
|
||||||
|
return None
|
||||||
|
return str(p)
|
||||||
|
|
||||||
|
def _quarantine_selected(self):
|
||||||
|
p = self._selected_detection_path()
|
||||||
|
if not p:
|
||||||
|
return
|
||||||
|
r = QtWidgets.QMessageBox.question(
|
||||||
|
self,
|
||||||
|
"Quarantine file",
|
||||||
|
f"Move file to quarantine?\n\n{p}",
|
||||||
|
)
|
||||||
|
if r != QtWidgets.QMessageBox.Yes:
|
||||||
|
return
|
||||||
|
self._run_admin_with_arg("quarantine", p)
|
||||||
|
|
||||||
|
def _delete_selected(self):
|
||||||
|
p = self._selected_detection_path()
|
||||||
|
if not p:
|
||||||
|
return
|
||||||
|
r1 = QtWidgets.QMessageBox.warning(
|
||||||
|
self,
|
||||||
|
"Delete infected file",
|
||||||
|
f"This will permanently delete the file:\n\n{p}\n\nContinue?",
|
||||||
|
QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No,
|
||||||
|
)
|
||||||
|
if r1 != QtWidgets.QMessageBox.Yes:
|
||||||
|
return
|
||||||
|
r2 = QtWidgets.QMessageBox.warning(
|
||||||
|
self,
|
||||||
|
"Confirm delete",
|
||||||
|
"Are you absolutely sure? This cannot be undone.",
|
||||||
|
QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No,
|
||||||
|
)
|
||||||
|
if r2 != QtWidgets.QMessageBox.Yes:
|
||||||
|
return
|
||||||
|
self._run_admin_with_arg("delete", p)
|
||||||
|
|
||||||
|
def _disable(self):
|
||||||
|
r = QtWidgets.QMessageBox.question(
|
||||||
|
self,
|
||||||
|
"Disable protection",
|
||||||
|
"Disable protection requires explicit operator consent. Action will be logged.",
|
||||||
|
)
|
||||||
|
if r != QtWidgets.QMessageBox.Yes:
|
||||||
|
return
|
||||||
|
self._run_admin("disable-protection")
|
||||||
|
|
||||||
|
def _run_admin(self, action: str):
|
||||||
|
cmd = ["pkexec", "/usr/sbin/saikyo-av-admin", action]
|
||||||
|
proc = subprocess.run(cmd, capture_output=True, text=True)
|
||||||
|
if proc.returncode == 0:
|
||||||
|
self.refresh()
|
||||||
|
out = (proc.stdout or "").strip()
|
||||||
|
if out:
|
||||||
|
QtWidgets.QMessageBox.information(self, "Done", out)
|
||||||
|
else:
|
||||||
|
msg = (proc.stderr or proc.stdout or "failed").strip()
|
||||||
|
QtWidgets.QMessageBox.warning(self, "Failed", msg)
|
||||||
|
|
||||||
|
def _update_scan_elapsed(self):
|
||||||
|
if self._scan_proc is None:
|
||||||
|
return
|
||||||
|
ms = self._scan_timer.elapsed()
|
||||||
|
self.scan_status.setText(f"Scanning… {int(ms/1000)}s")
|
||||||
|
|
||||||
|
def _stop_scan(self):
|
||||||
|
if self._scan_proc is None:
|
||||||
|
return
|
||||||
|
r = QtWidgets.QMessageBox.question(
|
||||||
|
self,
|
||||||
|
"Stop scan",
|
||||||
|
"Stop the running scan?",
|
||||||
|
)
|
||||||
|
if r != QtWidgets.QMessageBox.Yes:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
self._scan_proc.kill()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def _run_admin_with_arg(self, action: str, arg: str):
|
||||||
|
cmd = ["pkexec", "/usr/sbin/saikyo-av-admin", action, arg]
|
||||||
|
proc = subprocess.run(cmd, capture_output=True, text=True)
|
||||||
|
if proc.returncode == 0:
|
||||||
|
self.refresh()
|
||||||
|
out = (proc.stdout or "").strip()
|
||||||
|
if out:
|
||||||
|
QtWidgets.QMessageBox.information(self, "Done", out)
|
||||||
|
else:
|
||||||
|
msg = (proc.stderr or proc.stdout or "failed").strip()
|
||||||
|
QtWidgets.QMessageBox.warning(self, "Failed", msg)
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
ap = argparse.ArgumentParser(prog="saikyo-av-gui")
|
||||||
|
ap.add_argument("--tray", action="store_true")
|
||||||
|
ap.add_argument("--reports-dir", default=REPORTS_DIR_DEFAULT)
|
||||||
|
args = ap.parse_args()
|
||||||
|
|
||||||
|
app = QtWidgets.QApplication(sys.argv)
|
||||||
|
w = MainWindow(pathlib.Path(args.reports_dir), start_in_tray=args.tray)
|
||||||
|
w.show()
|
||||||
|
return app.exec_()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
323
saikyo-av-gui/debian/saikyo-av-gui/usr/sbin/saikyo-av-admin
Executable file
323
saikyo-av-gui/debian/saikyo-av-gui/usr/sbin/saikyo-av-admin
Executable file
|
|
@ -0,0 +1,323 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
REPORTS_DIR="/var/lib/saikyo-av/reports"
|
||||||
|
LOG_DIR="/var/log/saikyo-av"
|
||||||
|
QUAR_DIR="/var/lib/saikyo-av/quarantine"
|
||||||
|
|
||||||
|
mkdir -p "${REPORTS_DIR}" "${LOG_DIR}" "${QUAR_DIR}" || true
|
||||||
|
chmod 0755 /var/lib/saikyo-av "${REPORTS_DIR}" "${LOG_DIR}" 2>/dev/null || true
|
||||||
|
|
||||||
|
cmd="${1:-}"
|
||||||
|
shift || true
|
||||||
|
|
||||||
|
utc_ts() {
|
||||||
|
date -u +%Y-%m-%dT%H:%M:%SZ
|
||||||
|
}
|
||||||
|
|
||||||
|
write_report() {
|
||||||
|
local id="$1"
|
||||||
|
local summary="$2"
|
||||||
|
local severity="$3"
|
||||||
|
local outfile="${REPORTS_DIR}/${id}.json"
|
||||||
|
local created
|
||||||
|
created="$(utc_ts)"
|
||||||
|
|
||||||
|
# shellcheck disable=SC2129
|
||||||
|
{
|
||||||
|
echo "{"
|
||||||
|
echo " \"created_utc\": \"${created}\","
|
||||||
|
echo " \"severity\": \"${severity}\","
|
||||||
|
echo " \"summary\": \"${summary}\","
|
||||||
|
echo " \"details\": {"
|
||||||
|
echo " \"action\": \"${cmd}\","
|
||||||
|
echo " \"argv\": \"$*\""
|
||||||
|
echo " },"
|
||||||
|
echo " \"artifacts\": {},"
|
||||||
|
echo " \"suggested_fixes\": []"
|
||||||
|
echo "}"
|
||||||
|
} > "${outfile}"
|
||||||
|
|
||||||
|
echo "${outfile}"
|
||||||
|
}
|
||||||
|
|
||||||
|
run_to_file() {
|
||||||
|
local out="$1"
|
||||||
|
shift
|
||||||
|
("$@" 2>&1 || true) | sed -e 's/\r$//' > "${out}"
|
||||||
|
}
|
||||||
|
|
||||||
|
json_escape() {
|
||||||
|
# Minimal JSON string escape (no unicode handling needed for our file paths)
|
||||||
|
sed -e 's/\\/\\\\/g' -e 's/"/\\"/g' -e 's/\t/\\t/g' -e 's/\r/\\r/g' -e 's/\n/\\n/g'
|
||||||
|
}
|
||||||
|
|
||||||
|
write_scan_report() {
|
||||||
|
local id="$1"
|
||||||
|
local summary="$2"
|
||||||
|
local severity="$3"
|
||||||
|
local log_file="$4"
|
||||||
|
local detections_tsv="$5"
|
||||||
|
local outfile="${REPORTS_DIR}/${id}.json"
|
||||||
|
local created
|
||||||
|
created="$(utc_ts)"
|
||||||
|
|
||||||
|
local scanned_count infected_count
|
||||||
|
scanned_count="$(grep -E '^Scanned files:' "${log_file}" 2>/dev/null | awk -F': ' '{print $2}' | tail -n 1 || true)"
|
||||||
|
infected_count="$(grep -E '^Infected files:' "${log_file}" 2>/dev/null | awk -F': ' '{print $2}' | tail -n 1 || true)"
|
||||||
|
scanned_count="${scanned_count:-0}"
|
||||||
|
infected_count="${infected_count:-0}"
|
||||||
|
|
||||||
|
{
|
||||||
|
echo "{"
|
||||||
|
echo " \"created_utc\": \"${created}\","
|
||||||
|
echo " \"severity\": \"${severity}\","
|
||||||
|
echo " \"summary\": \"${summary}\","
|
||||||
|
echo " \"details\": {"
|
||||||
|
echo " \"action\": \"${cmd}\","
|
||||||
|
echo " \"argv\": \"$*\","
|
||||||
|
echo " \"scanned_files\": ${scanned_count},"
|
||||||
|
echo " \"infected_files\": ${infected_count},"
|
||||||
|
echo " \"detections\": ["
|
||||||
|
|
||||||
|
if [[ -s "${detections_tsv}" ]]; then
|
||||||
|
first=1
|
||||||
|
while IFS=$'\t' read -r pth sig; do
|
||||||
|
[[ -n "${pth}" ]] || continue
|
||||||
|
pth_esc="$(printf '%s' "${pth}" | json_escape)"
|
||||||
|
sig_esc="$(printf '%s' "${sig}" | json_escape)"
|
||||||
|
if [[ "${first}" -eq 1 ]]; then
|
||||||
|
first=0
|
||||||
|
else
|
||||||
|
echo ","
|
||||||
|
fi
|
||||||
|
printf ' {"path":"%s","name":"%s"}' "${pth_esc}" "${sig_esc}"
|
||||||
|
done < "${detections_tsv}"
|
||||||
|
echo
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo " ]"
|
||||||
|
echo " },"
|
||||||
|
echo " \"artifacts\": {"
|
||||||
|
echo " \"clamav_log\": \"${log_file}\","
|
||||||
|
echo " \"detections_tsv\": \"${detections_tsv}\""
|
||||||
|
echo " },"
|
||||||
|
echo " \"suggested_fixes\": ["
|
||||||
|
echo " {\"id\":\"quarantine\",\"title\":\"Quarantine infected file\",\"description\":\"Move selected file to local quarantine.\",\"requires_consent\":true},"
|
||||||
|
echo " {\"id\":\"delete\",\"title\":\"Delete infected file\",\"description\":\"Delete selected infected file (dangerous).\",\"requires_consent\":true}"
|
||||||
|
echo " ]"
|
||||||
|
echo "}"
|
||||||
|
} > "${outfile}"
|
||||||
|
|
||||||
|
echo "${outfile}"
|
||||||
|
}
|
||||||
|
|
||||||
|
svc_is_enabled() {
|
||||||
|
systemctl is-enabled saikyo-avd.timer 2>/dev/null || true
|
||||||
|
}
|
||||||
|
|
||||||
|
svc_is_active() {
|
||||||
|
systemctl is-active saikyo-avd.timer 2>/dev/null || true
|
||||||
|
}
|
||||||
|
|
||||||
|
case "${cmd}" in
|
||||||
|
health)
|
||||||
|
echo "ok"
|
||||||
|
;;
|
||||||
|
|
||||||
|
status-protection)
|
||||||
|
echo "enabled=$(svc_is_enabled) active=$(svc_is_active)"
|
||||||
|
;;
|
||||||
|
|
||||||
|
run-evidence)
|
||||||
|
outdir="${REPORTS_DIR}/artifacts"
|
||||||
|
mkdir -p "${outdir}" || true
|
||||||
|
ts="$(date -u +%Y%m%dT%H%M%SZ)"
|
||||||
|
evidence_out="${outdir}/saikyo-evidence-${ts}.log"
|
||||||
|
|
||||||
|
if command -v saikyo-evidence >/dev/null 2>&1; then
|
||||||
|
run_to_file "${evidence_out}" saikyo-evidence
|
||||||
|
elif [ -x /usr/bin/saikyo-evidence ]; then
|
||||||
|
run_to_file "${evidence_out}" /usr/bin/saikyo-evidence
|
||||||
|
else
|
||||||
|
echo "saikyo-evidence not found" > "${evidence_out}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
rpt="$(write_report "evidence-${ts}" "Evidence report generated" "info" "${cmd}")"
|
||||||
|
# Append artifacts into report JSON (minimal, without jq dependency).
|
||||||
|
sed -i "s#\"artifacts\": {}#\"artifacts\": {\"evidence_log\": \"${evidence_out}\"}#" "${rpt}" || true
|
||||||
|
echo "${rpt}"
|
||||||
|
;;
|
||||||
|
|
||||||
|
scan)
|
||||||
|
outdir="${REPORTS_DIR}/artifacts"
|
||||||
|
mkdir -p "${outdir}" || true
|
||||||
|
ts="$(date -u +%Y%m%dT%H%M%SZ)"
|
||||||
|
log_file="${outdir}/clamav-scan-${ts}.log"
|
||||||
|
det_tsv="${outdir}/clamav-detections-${ts}.tsv"
|
||||||
|
|
||||||
|
if ! command -v clamscan >/dev/null 2>&1; then
|
||||||
|
echo "clamscan not found (install clamav)" > "${log_file}"
|
||||||
|
rpt="$(write_report "scan-${ts}" "ClamAV scan failed: clamscan not found" "warn" "${cmd}")"
|
||||||
|
sed -i "s#\"artifacts\": {}#\"artifacts\": {\"clamav_log\": \"${log_file}\"}#" "${rpt}" || true
|
||||||
|
echo "${rpt}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Full scan of / with exclusions.
|
||||||
|
# - exclude hidden paths (/.*/)
|
||||||
|
# - exclude virtual/system dirs
|
||||||
|
# - keep a log we can parse
|
||||||
|
(
|
||||||
|
clamscan \
|
||||||
|
-r \
|
||||||
|
--infected \
|
||||||
|
--no-summary \
|
||||||
|
--exclude-dir='^/proc/' \
|
||||||
|
--exclude-dir='^/sys/' \
|
||||||
|
--exclude-dir='^/dev/' \
|
||||||
|
--exclude-dir='^/run/' \
|
||||||
|
--exclude-dir='^/tmp/' \
|
||||||
|
--exclude-dir='/.*/' \
|
||||||
|
/ \
|
||||||
|
2>&1
|
||||||
|
echo
|
||||||
|
clamscan -r --infected --exclude-dir='^/proc/' --exclude-dir='^/sys/' --exclude-dir='^/dev/' --exclude-dir='^/run/' --exclude-dir='^/tmp/' --exclude-dir='/.*/' / --summary 2>/dev/null || true
|
||||||
|
) | sed -e 's/\r$//' > "${log_file}"
|
||||||
|
|
||||||
|
: > "${det_tsv}"
|
||||||
|
# Parse "PATH: SIGNATURE FOUND" lines.
|
||||||
|
grep -E ' FOUND$' "${log_file}" | sed -E 's/: (.+) FOUND$//;t;d' >/dev/null 2>&1 || true
|
||||||
|
while IFS= read -r line; do
|
||||||
|
# Example: /path/file: Eicar-Test-Signature FOUND
|
||||||
|
pth="${line%%:*}"
|
||||||
|
rest="${line#*: }"
|
||||||
|
sig="${rest% FOUND}"
|
||||||
|
printf '%s\t%s\n' "${pth}" "${sig}" >> "${det_tsv}"
|
||||||
|
done < <(grep -E ' FOUND$' "${log_file}" || true)
|
||||||
|
|
||||||
|
infected_count="$(wc -l < "${det_tsv}" 2>/dev/null || echo 0)"
|
||||||
|
severity="info"
|
||||||
|
summary="ClamAV scan completed: no threats"
|
||||||
|
if [[ "${infected_count}" -gt 0 ]]; then
|
||||||
|
severity="bad"
|
||||||
|
summary="ClamAV scan completed: ${infected_count} threat(s) found"
|
||||||
|
fi
|
||||||
|
|
||||||
|
rpt="$(write_scan_report "scan-${ts}" "${summary}" "${severity}" "${log_file}" "${det_tsv}" "${cmd}")"
|
||||||
|
echo "${rpt}"
|
||||||
|
;;
|
||||||
|
|
||||||
|
quarantine)
|
||||||
|
target="${1:-}"
|
||||||
|
if [[ -z "${target}" ]]; then
|
||||||
|
echo "missing file path" >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
ts="$(date -u +%Y%m%dT%H%M%SZ)"
|
||||||
|
bn="$(basename -- "${target}" 2>/dev/null || echo file)"
|
||||||
|
dest="${QUAR_DIR}/${ts}-${bn}"
|
||||||
|
if [[ ! -f "${target}" ]]; then
|
||||||
|
rpt="$(write_report "quarantine-${ts}" "Quarantine failed: file not found" "warn" "${cmd}")"
|
||||||
|
echo "${rpt}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
mv -f -- "${target}" "${dest}" 2>>"${LOG_DIR}/saikyo-av-admin.log" || {
|
||||||
|
rpt="$(write_report "quarantine-${ts}" "Quarantine failed: move error" "warn" "${cmd}")"
|
||||||
|
echo "${rpt}"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
rpt="$(write_report "quarantine-${ts}" "File quarantined" "warn" "${cmd}")"
|
||||||
|
sed -i "s#\"artifacts\": {}#\"artifacts\": {\"from\": \"${target}\", \"to\": \"${dest}\"}#" "${rpt}" || true
|
||||||
|
echo "${rpt}"
|
||||||
|
;;
|
||||||
|
|
||||||
|
delete)
|
||||||
|
target="${1:-}"
|
||||||
|
if [[ -z "${target}" ]]; then
|
||||||
|
echo "missing file path" >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
ts="$(date -u +%Y%m%dT%H%M%SZ)"
|
||||||
|
if [[ ! -e "${target}" ]]; then
|
||||||
|
rpt="$(write_report "delete-${ts}" "Delete failed: path not found" "warn" "${cmd}")"
|
||||||
|
echo "${rpt}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
rm -rf -- "${target}" 2>>"${LOG_DIR}/saikyo-av-admin.log" || {
|
||||||
|
rpt="$(write_report "delete-${ts}" "Delete failed" "warn" "${cmd}")"
|
||||||
|
echo "${rpt}"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
rpt="$(write_report "delete-${ts}" "File deleted" "warn" "${cmd}")"
|
||||||
|
sed -i "s#\"artifacts\": {}#\"artifacts\": {\"path\": \"${target}\"}#" "${rpt}" || true
|
||||||
|
echo "${rpt}"
|
||||||
|
;;
|
||||||
|
|
||||||
|
run-audit)
|
||||||
|
outdir="${REPORTS_DIR}/artifacts"
|
||||||
|
mkdir -p "${outdir}" || true
|
||||||
|
ts="$(date -u +%Y%m%dT%H%M%SZ)"
|
||||||
|
audit_out="${outdir}/saikyo-audit-report-${ts}.txt"
|
||||||
|
|
||||||
|
if command -v saikyo-audit-report >/dev/null 2>&1; then
|
||||||
|
run_to_file "${audit_out}" saikyo-audit-report
|
||||||
|
else
|
||||||
|
echo "saikyo-audit-report not installed" > "${audit_out}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
rpt="$(write_report "audit-${ts}" "Audit report executed" "info" "${cmd}")"
|
||||||
|
sed -i "s#\"artifacts\": {}#\"artifacts\": {\"audit_stdout\": \"${audit_out}\"}#" "${rpt}" || true
|
||||||
|
echo "${rpt}"
|
||||||
|
;;
|
||||||
|
|
||||||
|
collect-artifacts)
|
||||||
|
outdir="${REPORTS_DIR}/artifacts"
|
||||||
|
mkdir -p "${outdir}" || true
|
||||||
|
ts="$(date -u +%Y%m%dT%H%M%SZ)"
|
||||||
|
collect_out="${outdir}/collect-artifacts-${ts}.log"
|
||||||
|
|
||||||
|
if [ -x /usr/share/saikyo-os/forensics/collect-artifacts.sh ]; then
|
||||||
|
run_to_file "${collect_out}" /usr/share/saikyo-os/forensics/collect-artifacts.sh
|
||||||
|
else
|
||||||
|
echo "collect-artifacts.sh not found" > "${collect_out}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
rpt="$(write_report "collect-${ts}" "Artifacts collection executed" "info" "${cmd}")"
|
||||||
|
sed -i "s#\"artifacts\": {}#\"artifacts\": {\"collector_log\": \"${collect_out}\"}#" "${rpt}" || true
|
||||||
|
echo "${rpt}"
|
||||||
|
;;
|
||||||
|
|
||||||
|
enable-protection)
|
||||||
|
ts="$(date -u +%Y%m%dT%H%M%SZ)"
|
||||||
|
(systemctl daemon-reload 2>&1 || true) >> "${LOG_DIR}/saikyo-av-admin.log" || true
|
||||||
|
if systemctl enable --now saikyo-avd.timer >/dev/null 2>&1; then
|
||||||
|
rpt="$(write_report "enable-${ts}" "Protection enabled" "info" "${cmd}")"
|
||||||
|
echo "${rpt}"
|
||||||
|
exit 0
|
||||||
|
else
|
||||||
|
rpt="$(write_report "enable-${ts}" "Protection enable failed" "warn" "${cmd}")"
|
||||||
|
echo "${rpt}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
|
||||||
|
disable-protection)
|
||||||
|
ts="$(date -u +%Y%m%dT%H%M%SZ)"
|
||||||
|
(systemctl daemon-reload 2>&1 || true) >> "${LOG_DIR}/saikyo-av-admin.log" || true
|
||||||
|
if systemctl disable --now saikyo-avd.timer >/dev/null 2>&1; then
|
||||||
|
rpt="$(write_report "disable-${ts}" "Protection disabled" "warn" "${cmd}")"
|
||||||
|
echo "${rpt}"
|
||||||
|
exit 0
|
||||||
|
else
|
||||||
|
rpt="$(write_report "disable-${ts}" "Protection disable failed" "warn" "${cmd}")"
|
||||||
|
echo "${rpt}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
|
||||||
|
*)
|
||||||
|
echo "Usage: saikyo-av-admin {health|status-protection|run-evidence|run-audit|collect-artifacts|scan|quarantine <file>|delete <file>|enable-protection|disable-protection}" >&2
|
||||||
|
exit 2
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
[Desktop Entry]
|
||||||
|
Type=Application
|
||||||
|
Name=Saikyo Antivirus
|
||||||
|
Comment=Saikyo Antivirus admin panel
|
||||||
|
Exec=/usr/bin/saikyo-av-gui
|
||||||
|
Icon=saikyo-av
|
||||||
|
Terminal=false
|
||||||
|
Categories=System;Security;
|
||||||
Binary file not shown.
|
|
@ -0,0 +1,26 @@
|
||||||
|
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
||||||
|
Upstream-Name: saikyo-av-gui
|
||||||
|
Source: https://saikyo-os.ru/
|
||||||
|
|
||||||
|
Files: *
|
||||||
|
Copyright: 2026 SAIKYO OS
|
||||||
|
License: MIT
|
||||||
|
|
||||||
|
License: MIT
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
.
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
.
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="256" height="256" viewBox="0 0 256 256">
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="g" x1="0" x2="1" y1="0" y2="1">
|
||||||
|
<stop offset="0" stop-color="#60a5fa"/>
|
||||||
|
<stop offset="1" stop-color="#2dd4bf"/>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<rect x="24" y="24" width="208" height="208" rx="48" fill="#0b1220"/>
|
||||||
|
<path d="M128 40c40 20 72 16 88 24v72c0 52-36 88-88 104C76 224 40 188 40 136V64c16-8 48-4 88-24z" fill="url(#g)" opacity="0.95"/>
|
||||||
|
<path d="M94 132l22 22 50-58" fill="none" stroke="#0b1220" stroke-width="16" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 616 B |
|
|
@ -0,0 +1,15 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE policyconfig PUBLIC "-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN" "http://www.freedesktop.org/standards/PolicyKit/1.0/policyconfig.dtd">
|
||||||
|
<policyconfig>
|
||||||
|
<action id="org.saikyo.av.admin">
|
||||||
|
<description>Run Saikyo Antivirus administrative actions</description>
|
||||||
|
<message>Authentication is required to run Saikyo Antivirus administrative actions.</message>
|
||||||
|
<defaults>
|
||||||
|
<allow_any>no</allow_any>
|
||||||
|
<allow_inactive>no</allow_inactive>
|
||||||
|
<allow_active>auth_admin</allow_active>
|
||||||
|
</defaults>
|
||||||
|
<annotate key="org.freedesktop.policykit.exec.path">/usr/sbin/saikyo-av-admin</annotate>
|
||||||
|
<annotate key="org.freedesktop.policykit.exec.allow_gui">true</annotate>
|
||||||
|
</action>
|
||||||
|
</policyconfig>
|
||||||
1
saikyo-av-gui/debian/source/format
Normal file
1
saikyo-av-gui/debian/source/format
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
3.0 (native)
|
||||||
8
saikyo-av-gui/desktop/saikyo-av-gui.desktop
Normal file
8
saikyo-av-gui/desktop/saikyo-av-gui.desktop
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
[Desktop Entry]
|
||||||
|
Type=Application
|
||||||
|
Name=Saikyo Antivirus
|
||||||
|
Comment=Saikyo Antivirus admin panel
|
||||||
|
Exec=/usr/bin/saikyo-av-gui
|
||||||
|
Icon=saikyo-av
|
||||||
|
Terminal=false
|
||||||
|
Categories=System;Security;
|
||||||
11
saikyo-av-gui/icons/saikyo-av.svg
Normal file
11
saikyo-av-gui/icons/saikyo-av.svg
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="256" height="256" viewBox="0 0 256 256">
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="g" x1="0" x2="1" y1="0" y2="1">
|
||||||
|
<stop offset="0" stop-color="#60a5fa"/>
|
||||||
|
<stop offset="1" stop-color="#2dd4bf"/>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<rect x="24" y="24" width="208" height="208" rx="48" fill="#0b1220"/>
|
||||||
|
<path d="M128 40c40 20 72 16 88 24v72c0 52-36 88-88 104C76 224 40 188 40 136V64c16-8 48-4 88-24z" fill="url(#g)" opacity="0.95"/>
|
||||||
|
<path d="M94 132l22 22 50-58" fill="none" stroke="#0b1220" stroke-width="16" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 616 B |
15
saikyo-av-gui/polkit/org.saikyo.av.admin.policy
Normal file
15
saikyo-av-gui/polkit/org.saikyo.av.admin.policy
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE policyconfig PUBLIC "-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN" "http://www.freedesktop.org/standards/PolicyKit/1.0/policyconfig.dtd">
|
||||||
|
<policyconfig>
|
||||||
|
<action id="org.saikyo.av.admin">
|
||||||
|
<description>Run Saikyo Antivirus administrative actions</description>
|
||||||
|
<message>Authentication is required to run Saikyo Antivirus administrative actions.</message>
|
||||||
|
<defaults>
|
||||||
|
<allow_any>no</allow_any>
|
||||||
|
<allow_inactive>no</allow_inactive>
|
||||||
|
<allow_active>auth_admin</allow_active>
|
||||||
|
</defaults>
|
||||||
|
<annotate key="org.freedesktop.policykit.exec.path">/usr/sbin/saikyo-av-admin</annotate>
|
||||||
|
<annotate key="org.freedesktop.policykit.exec.allow_gui">true</annotate>
|
||||||
|
</action>
|
||||||
|
</policyconfig>
|
||||||
52
saikyo-av/bin/saikyo-avd
Normal file
52
saikyo-av/bin/saikyo-avd
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
REPORTS_DIR="/var/lib/saikyo-av/reports"
|
||||||
|
ART_DIR="${REPORTS_DIR}/artifacts"
|
||||||
|
|
||||||
|
mkdir -p "${REPORTS_DIR}" "${ART_DIR}" || true
|
||||||
|
chmod 0755 /var/lib/saikyo-av "${REPORTS_DIR}" 2>/dev/null || true
|
||||||
|
|
||||||
|
TS="$(date -u +%Y%m%dT%H%M%SZ)"
|
||||||
|
REPORT_ID="agent-${TS}"
|
||||||
|
OUT_JSON="${REPORTS_DIR}/${REPORT_ID}.json"
|
||||||
|
|
||||||
|
FAILED_UNITS_FILE="${ART_DIR}/systemctl-failed-${TS}.txt"
|
||||||
|
JOURNAL_FILE="${ART_DIR}/journal-warn-${TS}.txt"
|
||||||
|
|
||||||
|
(systemctl --failed 2>&1 || true) > "${FAILED_UNITS_FILE}"
|
||||||
|
(journalctl -b -p warning..alert --no-pager 2>&1 | tail -n 400 || true) > "${JOURNAL_FILE}"
|
||||||
|
|
||||||
|
FAILED_COUNT="$(grep -cE '^[^\s].*\.service' "${FAILED_UNITS_FILE}" 2>/dev/null || echo 0)"
|
||||||
|
|
||||||
|
SEVERITY="info"
|
||||||
|
SUMMARY="Saikyo AV: periodic health report"
|
||||||
|
if [[ "${FAILED_COUNT}" -gt 0 ]]; then
|
||||||
|
SEVERITY="warn"
|
||||||
|
SUMMARY="Saikyo AV: detected failed systemd units (${FAILED_COUNT})"
|
||||||
|
fi
|
||||||
|
|
||||||
|
cat > "${OUT_JSON}" <<EOF
|
||||||
|
{
|
||||||
|
"created_utc": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
|
||||||
|
"severity": "${SEVERITY}",
|
||||||
|
"summary": "${SUMMARY}",
|
||||||
|
"details": {
|
||||||
|
"failed_units_count": ${FAILED_COUNT}
|
||||||
|
},
|
||||||
|
"artifacts": {
|
||||||
|
"systemctl_failed": "${FAILED_UNITS_FILE}",
|
||||||
|
"journal_warn": "${JOURNAL_FILE}"
|
||||||
|
},
|
||||||
|
"suggested_fixes": [
|
||||||
|
{
|
||||||
|
"id": "run_evidence",
|
||||||
|
"title": "Run evidence collection",
|
||||||
|
"description": "Generate /var/log/saikyo-evidence/latest.log using saikyo-evidence.",
|
||||||
|
"requires_consent": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
exit 0
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
saikyo-av (0.1.1) stable; urgency=medium
|
||||||
|
|
||||||
|
* Fix packaging: install saikyo-avd and systemd units to correct paths; cleanup broken dirs on upgrade.
|
||||||
|
|
||||||
|
-- SAIKYO OS <support@saikyo-os.ru> Mon, 20 Jan 2026 16:55:00 +0000
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
./bin/saikyo-avd
|
||||||
|
./systemd/saikyo-avd.service
|
||||||
|
./systemd/saikyo-avd.timer
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
# Automatically added by dh_installsystemd/13.24.2
|
||||||
|
if [ "$1" = "configure" ] || [ "$1" = "abort-upgrade" ] || [ "$1" = "abort-deconfigure" ] || [ "$1" = "abort-remove" ] ; then
|
||||||
|
# The following line should be removed in trixie or trixie+1
|
||||||
|
deb-systemd-helper unmask 'saikyo-avd.timer' >/dev/null || true
|
||||||
|
|
||||||
|
# was-enabled defaults to true, so new installations run enable.
|
||||||
|
if deb-systemd-helper --quiet was-enabled 'saikyo-avd.timer'; then
|
||||||
|
# Enables the unit on first installation, creates new
|
||||||
|
# symlinks on upgrades if the unit file has changed.
|
||||||
|
deb-systemd-helper enable 'saikyo-avd.timer' >/dev/null || true
|
||||||
|
else
|
||||||
|
# Update the statefile to add new symlinks (if any), which need to be
|
||||||
|
# cleaned up on purge. Also remove old symlinks.
|
||||||
|
deb-systemd-helper update-state 'saikyo-avd.timer' >/dev/null || true
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
# End automatically added section
|
||||||
|
# Automatically added by dh_installsystemd/13.24.2
|
||||||
|
if [ "$1" = "configure" ] || [ "$1" = "abort-upgrade" ] || [ "$1" = "abort-deconfigure" ] || [ "$1" = "abort-remove" ] ; then
|
||||||
|
if [ -d /run/systemd/system ]; then
|
||||||
|
systemctl --system daemon-reload >/dev/null || true
|
||||||
|
if [ -n "$2" ]; then
|
||||||
|
_dh_action=restart
|
||||||
|
else
|
||||||
|
_dh_action=start
|
||||||
|
fi
|
||||||
|
deb-systemd-invoke $_dh_action 'saikyo-avd.service' 'saikyo-avd.timer' >/dev/null || true
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
# End automatically added section
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
# Automatically added by dh_installsystemd/13.24.2
|
||||||
|
if [ -z "$DPKG_ROOT" ] && [ "$1" = remove ] && [ -d /run/systemd/system ] ; then
|
||||||
|
deb-systemd-invoke stop 'saikyo-avd.service' 'saikyo-avd.timer' >/dev/null || true
|
||||||
|
fi
|
||||||
|
# End automatically added section
|
||||||
5
saikyo-av/debian/changelog
Normal file
5
saikyo-av/debian/changelog
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
saikyo-av (0.1.1) stable; urgency=medium
|
||||||
|
|
||||||
|
* Fix packaging: install saikyo-avd and systemd units to correct paths; cleanup broken dirs on upgrade.
|
||||||
|
|
||||||
|
-- SAIKYO OS <support@saikyo-os.ru> Mon, 20 Jan 2026 16:55:00 +0000
|
||||||
13
saikyo-av/debian/control
Normal file
13
saikyo-av/debian/control
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
Source: saikyo-av
|
||||||
|
Section: admin
|
||||||
|
Priority: optional
|
||||||
|
Maintainer: SAIKYO OS <support@saikyo-os.ru>
|
||||||
|
Build-Depends: debhelper-compat (= 13)
|
||||||
|
Standards-Version: 4.6.2
|
||||||
|
Rules-Requires-Root: no
|
||||||
|
|
||||||
|
Package: saikyo-av
|
||||||
|
Architecture: all
|
||||||
|
Depends: ${misc:Depends}, bash, coreutils, systemd | systemd-sysv, util-linux, grep, sed, gawk
|
||||||
|
Description: Saikyo Antivirus agent (local)
|
||||||
|
Local always-on agent for collecting security signals and producing local reports.
|
||||||
26
saikyo-av/debian/copyright
Normal file
26
saikyo-av/debian/copyright
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
||||||
|
Upstream-Name: saikyo-av
|
||||||
|
Source: https://saikyo-os.ru/
|
||||||
|
|
||||||
|
Files: *
|
||||||
|
Copyright: 2026 SAIKYO OS
|
||||||
|
License: MIT
|
||||||
|
|
||||||
|
License: MIT
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
.
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
.
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
1
saikyo-av/debian/debhelper-build-stamp
Normal file
1
saikyo-av/debian/debhelper-build-stamp
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
saikyo-av
|
||||||
2
saikyo-av/debian/files
Normal file
2
saikyo-av/debian/files
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
saikyo-av_0.1.1_all.deb admin optional
|
||||||
|
saikyo-av_0.1.1_amd64.buildinfo admin optional
|
||||||
3
saikyo-av/debian/install
Normal file
3
saikyo-av/debian/install
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
bin/saikyo-avd usr/sbin/
|
||||||
|
systemd/saikyo-avd.service lib/systemd/system/
|
||||||
|
systemd/saikyo-avd.timer lib/systemd/system/
|
||||||
15
saikyo-av/debian/rules
Executable file
15
saikyo-av/debian/rules
Executable file
|
|
@ -0,0 +1,15 @@
|
||||||
|
#!/usr/bin/make -f
|
||||||
|
|
||||||
|
%:
|
||||||
|
dh $@
|
||||||
|
|
||||||
|
override_dh_auto_build:
|
||||||
|
|
||||||
|
override_dh_auto_test:
|
||||||
|
|
||||||
|
override_dh_fixperms:
|
||||||
|
dh_fixperms
|
||||||
|
chmod 0755 debian/saikyo-av/usr/sbin/saikyo-avd
|
||||||
|
chmod 0755 debian/saikyo-av.preinst
|
||||||
|
chmod 0755 debian/saikyo-av.postinst
|
||||||
|
chmod 0755 debian/saikyo-av.prerm
|
||||||
1
saikyo-av/debian/saikyo-av.debhelper.log
Normal file
1
saikyo-av/debian/saikyo-av.debhelper.log
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
dh_fixperms
|
||||||
13
saikyo-av/debian/saikyo-av.postinst
Executable file
13
saikyo-av/debian/saikyo-av.postinst
Executable file
|
|
@ -0,0 +1,13 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
case "$1" in
|
||||||
|
configure)
|
||||||
|
mkdir -p /var/lib/saikyo-av/reports /var/lib/saikyo-av/reports/artifacts || true
|
||||||
|
chmod 0755 /var/lib/saikyo-av /var/lib/saikyo-av/reports 2>/dev/null || true
|
||||||
|
systemctl daemon-reload >/dev/null 2>&1 || true
|
||||||
|
systemctl enable --now saikyo-avd.timer >/dev/null 2>&1 || true
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
exit 0
|
||||||
12
saikyo-av/debian/saikyo-av.postrm.debhelper
Normal file
12
saikyo-av/debian/saikyo-av.postrm.debhelper
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
# Automatically added by dh_installsystemd/13.24.2
|
||||||
|
if [ "$1" = remove ] && [ -d /run/systemd/system ] ; then
|
||||||
|
systemctl --system daemon-reload >/dev/null || true
|
||||||
|
fi
|
||||||
|
# End automatically added section
|
||||||
|
# Automatically added by dh_installsystemd/13.24.2
|
||||||
|
if [ "$1" = "purge" ]; then
|
||||||
|
if [ -x "/usr/bin/deb-systemd-helper" ]; then
|
||||||
|
deb-systemd-helper purge 'saikyo-avd.timer' >/dev/null || true
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
# End automatically added section
|
||||||
22
saikyo-av/debian/saikyo-av.preinst
Executable file
22
saikyo-av/debian/saikyo-av.preinst
Executable file
|
|
@ -0,0 +1,22 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Fixup for 0.1.0 packaging mistake where dh_install created directories:
|
||||||
|
# /usr/sbin/saikyo-avd/saikyo-avd
|
||||||
|
# /lib/systemd/system/saikyo-avd.service/saikyo-avd.service
|
||||||
|
# /lib/systemd/system/saikyo-avd.timer/saikyo-avd.timer
|
||||||
|
# Remove them before unpacking the corrected package.
|
||||||
|
|
||||||
|
if [[ "$1" == "upgrade" || "$1" == "install" ]]; then
|
||||||
|
for d in \
|
||||||
|
/usr/sbin/saikyo-avd \
|
||||||
|
/lib/systemd/system/saikyo-avd.service \
|
||||||
|
/lib/systemd/system/saikyo-avd.timer
|
||||||
|
do
|
||||||
|
if [[ -d "$d" ]]; then
|
||||||
|
rm -rf "$d" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
exit 0
|
||||||
10
saikyo-av/debian/saikyo-av.prerm
Executable file
10
saikyo-av/debian/saikyo-av.prerm
Executable file
|
|
@ -0,0 +1,10 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
case "$1" in
|
||||||
|
remove|deconfigure)
|
||||||
|
systemctl disable --now saikyo-avd.timer >/dev/null 2>&1 || true
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
exit 0
|
||||||
2
saikyo-av/debian/saikyo-av.substvars
Normal file
2
saikyo-av/debian/saikyo-av.substvars
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
misc:Depends=
|
||||||
|
misc:Pre-Depends=
|
||||||
10
saikyo-av/debian/saikyo-av/DEBIAN/control
Normal file
10
saikyo-av/debian/saikyo-av/DEBIAN/control
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
Package: saikyo-av
|
||||||
|
Version: 0.1.1
|
||||||
|
Architecture: all
|
||||||
|
Maintainer: SAIKYO OS <support@saikyo-os.ru>
|
||||||
|
Installed-Size: 21
|
||||||
|
Depends: bash, coreutils, systemd | systemd-sysv, util-linux, grep, sed, gawk
|
||||||
|
Section: admin
|
||||||
|
Priority: optional
|
||||||
|
Description: Saikyo Antivirus agent (local)
|
||||||
|
Local always-on agent for collecting security signals and producing local reports.
|
||||||
5
saikyo-av/debian/saikyo-av/DEBIAN/md5sums
Normal file
5
saikyo-av/debian/saikyo-av/DEBIAN/md5sums
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
170cbb33110db84791e38ef0d265367b lib/systemd/system/saikyo-avd.service
|
||||||
|
706f24a7d93f1588f6130236a0cc30dc lib/systemd/system/saikyo-avd.timer
|
||||||
|
428c2feb89dbdabeb07df9c07a55931e usr/sbin/saikyo-avd
|
||||||
|
1d3486cb4986868f799ac11f32b7c69f usr/share/doc/saikyo-av/changelog.gz
|
||||||
|
ba541e7320b5e3963eae2a7ad3e12502 usr/share/doc/saikyo-av/copyright
|
||||||
13
saikyo-av/debian/saikyo-av/DEBIAN/postinst
Executable file
13
saikyo-av/debian/saikyo-av/DEBIAN/postinst
Executable file
|
|
@ -0,0 +1,13 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
case "$1" in
|
||||||
|
configure)
|
||||||
|
mkdir -p /var/lib/saikyo-av/reports /var/lib/saikyo-av/reports/artifacts || true
|
||||||
|
chmod 0755 /var/lib/saikyo-av /var/lib/saikyo-av/reports 2>/dev/null || true
|
||||||
|
systemctl daemon-reload >/dev/null 2>&1 || true
|
||||||
|
systemctl enable --now saikyo-avd.timer >/dev/null 2>&1 || true
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
exit 0
|
||||||
14
saikyo-av/debian/saikyo-av/DEBIAN/postrm
Executable file
14
saikyo-av/debian/saikyo-av/DEBIAN/postrm
Executable file
|
|
@ -0,0 +1,14 @@
|
||||||
|
#!/bin/sh
|
||||||
|
set -e
|
||||||
|
# Automatically added by dh_installsystemd/13.24.2
|
||||||
|
if [ "$1" = remove ] && [ -d /run/systemd/system ] ; then
|
||||||
|
systemctl --system daemon-reload >/dev/null || true
|
||||||
|
fi
|
||||||
|
# End automatically added section
|
||||||
|
# Automatically added by dh_installsystemd/13.24.2
|
||||||
|
if [ "$1" = "purge" ]; then
|
||||||
|
if [ -x "/usr/bin/deb-systemd-helper" ]; then
|
||||||
|
deb-systemd-helper purge 'saikyo-avd.timer' >/dev/null || true
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
# End automatically added section
|
||||||
22
saikyo-av/debian/saikyo-av/DEBIAN/preinst
Executable file
22
saikyo-av/debian/saikyo-av/DEBIAN/preinst
Executable file
|
|
@ -0,0 +1,22 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Fixup for 0.1.0 packaging mistake where dh_install created directories:
|
||||||
|
# /usr/sbin/saikyo-avd/saikyo-avd
|
||||||
|
# /lib/systemd/system/saikyo-avd.service/saikyo-avd.service
|
||||||
|
# /lib/systemd/system/saikyo-avd.timer/saikyo-avd.timer
|
||||||
|
# Remove them before unpacking the corrected package.
|
||||||
|
|
||||||
|
if [[ "$1" == "upgrade" || "$1" == "install" ]]; then
|
||||||
|
for d in \
|
||||||
|
/usr/sbin/saikyo-avd \
|
||||||
|
/lib/systemd/system/saikyo-avd.service \
|
||||||
|
/lib/systemd/system/saikyo-avd.timer
|
||||||
|
do
|
||||||
|
if [[ -d "$d" ]]; then
|
||||||
|
rm -rf "$d" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
exit 0
|
||||||
10
saikyo-av/debian/saikyo-av/DEBIAN/prerm
Executable file
10
saikyo-av/debian/saikyo-av/DEBIAN/prerm
Executable file
|
|
@ -0,0 +1,10 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
case "$1" in
|
||||||
|
remove|deconfigure)
|
||||||
|
systemctl disable --now saikyo-avd.timer >/dev/null 2>&1 || true
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
exit 0
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
[Unit]
|
||||||
|
Description=Saikyo Antivirus Agent (report generator)
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=oneshot
|
||||||
|
ExecStart=/usr/sbin/saikyo-avd
|
||||||
|
NoNewPrivileges=yes
|
||||||
|
PrivateTmp=yes
|
||||||
|
ProtectSystem=strict
|
||||||
|
ProtectHome=yes
|
||||||
|
ProtectKernelTunables=yes
|
||||||
|
ProtectKernelModules=yes
|
||||||
|
ProtectControlGroups=yes
|
||||||
|
RestrictSUIDSGID=yes
|
||||||
|
LockPersonality=yes
|
||||||
|
MemoryDenyWriteExecute=yes
|
||||||
|
SystemCallArchitectures=native
|
||||||
|
SystemCallFilter=@system-service
|
||||||
|
ReadWritePaths=/var/lib/saikyo-av/reports
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
[Unit]
|
||||||
|
Description=Saikyo Antivirus Agent timer
|
||||||
|
|
||||||
|
[Timer]
|
||||||
|
OnBootSec=2min
|
||||||
|
OnUnitActiveSec=5min
|
||||||
|
AccuracySec=30s
|
||||||
|
Persistent=true
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=timers.target
|
||||||
52
saikyo-av/debian/saikyo-av/usr/sbin/saikyo-avd
Executable file
52
saikyo-av/debian/saikyo-av/usr/sbin/saikyo-avd
Executable file
|
|
@ -0,0 +1,52 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
REPORTS_DIR="/var/lib/saikyo-av/reports"
|
||||||
|
ART_DIR="${REPORTS_DIR}/artifacts"
|
||||||
|
|
||||||
|
mkdir -p "${REPORTS_DIR}" "${ART_DIR}" || true
|
||||||
|
chmod 0755 /var/lib/saikyo-av "${REPORTS_DIR}" 2>/dev/null || true
|
||||||
|
|
||||||
|
TS="$(date -u +%Y%m%dT%H%M%SZ)"
|
||||||
|
REPORT_ID="agent-${TS}"
|
||||||
|
OUT_JSON="${REPORTS_DIR}/${REPORT_ID}.json"
|
||||||
|
|
||||||
|
FAILED_UNITS_FILE="${ART_DIR}/systemctl-failed-${TS}.txt"
|
||||||
|
JOURNAL_FILE="${ART_DIR}/journal-warn-${TS}.txt"
|
||||||
|
|
||||||
|
(systemctl --failed 2>&1 || true) > "${FAILED_UNITS_FILE}"
|
||||||
|
(journalctl -b -p warning..alert --no-pager 2>&1 | tail -n 400 || true) > "${JOURNAL_FILE}"
|
||||||
|
|
||||||
|
FAILED_COUNT="$(grep -cE '^[^\s].*\.service' "${FAILED_UNITS_FILE}" 2>/dev/null || echo 0)"
|
||||||
|
|
||||||
|
SEVERITY="info"
|
||||||
|
SUMMARY="Saikyo AV: periodic health report"
|
||||||
|
if [[ "${FAILED_COUNT}" -gt 0 ]]; then
|
||||||
|
SEVERITY="warn"
|
||||||
|
SUMMARY="Saikyo AV: detected failed systemd units (${FAILED_COUNT})"
|
||||||
|
fi
|
||||||
|
|
||||||
|
cat > "${OUT_JSON}" <<EOF
|
||||||
|
{
|
||||||
|
"created_utc": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
|
||||||
|
"severity": "${SEVERITY}",
|
||||||
|
"summary": "${SUMMARY}",
|
||||||
|
"details": {
|
||||||
|
"failed_units_count": ${FAILED_COUNT}
|
||||||
|
},
|
||||||
|
"artifacts": {
|
||||||
|
"systemctl_failed": "${FAILED_UNITS_FILE}",
|
||||||
|
"journal_warn": "${JOURNAL_FILE}"
|
||||||
|
},
|
||||||
|
"suggested_fixes": [
|
||||||
|
{
|
||||||
|
"id": "run_evidence",
|
||||||
|
"title": "Run evidence collection",
|
||||||
|
"description": "Generate /var/log/saikyo-evidence/latest.log using saikyo-evidence.",
|
||||||
|
"requires_consent": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
exit 0
|
||||||
BIN
saikyo-av/debian/saikyo-av/usr/share/doc/saikyo-av/changelog.gz
Normal file
BIN
saikyo-av/debian/saikyo-av/usr/share/doc/saikyo-av/changelog.gz
Normal file
Binary file not shown.
26
saikyo-av/debian/saikyo-av/usr/share/doc/saikyo-av/copyright
Normal file
26
saikyo-av/debian/saikyo-av/usr/share/doc/saikyo-av/copyright
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
||||||
|
Upstream-Name: saikyo-av
|
||||||
|
Source: https://saikyo-os.ru/
|
||||||
|
|
||||||
|
Files: *
|
||||||
|
Copyright: 2026 SAIKYO OS
|
||||||
|
License: MIT
|
||||||
|
|
||||||
|
License: MIT
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
.
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
.
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
1
saikyo-av/debian/source/format
Normal file
1
saikyo-av/debian/source/format
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
3.0 (native)
|
||||||
19
saikyo-av/systemd/saikyo-avd.service
Normal file
19
saikyo-av/systemd/saikyo-avd.service
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
[Unit]
|
||||||
|
Description=Saikyo Antivirus Agent (report generator)
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=oneshot
|
||||||
|
ExecStart=/usr/sbin/saikyo-avd
|
||||||
|
NoNewPrivileges=yes
|
||||||
|
PrivateTmp=yes
|
||||||
|
ProtectSystem=strict
|
||||||
|
ProtectHome=yes
|
||||||
|
ProtectKernelTunables=yes
|
||||||
|
ProtectKernelModules=yes
|
||||||
|
ProtectControlGroups=yes
|
||||||
|
RestrictSUIDSGID=yes
|
||||||
|
LockPersonality=yes
|
||||||
|
MemoryDenyWriteExecute=yes
|
||||||
|
SystemCallArchitectures=native
|
||||||
|
SystemCallFilter=@system-service
|
||||||
|
ReadWritePaths=/var/lib/saikyo-av/reports
|
||||||
11
saikyo-av/systemd/saikyo-avd.timer
Normal file
11
saikyo-av/systemd/saikyo-avd.timer
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
[Unit]
|
||||||
|
Description=Saikyo Antivirus Agent timer
|
||||||
|
|
||||||
|
[Timer]
|
||||||
|
OnBootSec=2min
|
||||||
|
OnUnitActiveSec=5min
|
||||||
|
AccuracySec=30s
|
||||||
|
Persistent=true
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=timers.target
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
[Desktop Entry]
|
||||||
|
Type=Application
|
||||||
|
Name=SAIKYO OS First Login (KDE)
|
||||||
|
Exec=/usr/bin/saikyo-first-login-kde
|
||||||
|
Terminal=false
|
||||||
|
OnlyShowIn=KDE;
|
||||||
|
X-KDE-autostart-phase=2
|
||||||
102
saikyo-branding/assets/bin/saikyo-evidence
Normal file
102
saikyo-branding/assets/bin/saikyo-evidence
Normal file
|
|
@ -0,0 +1,102 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
OUT_DIR="/var/log/saikyo-evidence"
|
||||||
|
TS="$(date -u +%Y%m%dT%H%M%SZ 2>/dev/null || date +%s)"
|
||||||
|
OUT_FILE="${OUT_DIR}/saikyo-evidence-${TS}.log"
|
||||||
|
|
||||||
|
mkdir -p "${OUT_DIR}" 2>/dev/null || true
|
||||||
|
chmod 0755 "${OUT_DIR}" 2>/dev/null || true
|
||||||
|
|
||||||
|
dumpln() {
|
||||||
|
printf '\n==== %s ====\n' "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
echo "saikyo_evidence_version=1"
|
||||||
|
echo "timestamp_utc=${TS}"
|
||||||
|
|
||||||
|
dumpln "os-release"
|
||||||
|
cat /etc/os-release 2>/dev/null || true
|
||||||
|
|
||||||
|
dumpln "lsb-release"
|
||||||
|
cat /etc/lsb-release 2>/dev/null || true
|
||||||
|
|
||||||
|
dumpln "uname"
|
||||||
|
uname -a 2>/dev/null || true
|
||||||
|
|
||||||
|
dumpln "dpkg saikyo packages"
|
||||||
|
dpkg -l 2>/dev/null | grep -i saikyo || true
|
||||||
|
|
||||||
|
dumpln "apt sources"
|
||||||
|
if [ -f /etc/apt/sources.list ]; then
|
||||||
|
echo "--- /etc/apt/sources.list ---"
|
||||||
|
sed -n '1,200p' /etc/apt/sources.list 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
if [ -d /etc/apt/sources.list.d ]; then
|
||||||
|
echo "--- /etc/apt/sources.list.d ---"
|
||||||
|
ls -la /etc/apt/sources.list.d 2>/dev/null || true
|
||||||
|
for f in /etc/apt/sources.list.d/*; do
|
||||||
|
[ -e "$f" ] || continue
|
||||||
|
echo "--- $f ---"
|
||||||
|
sed -n '1,200p' "$f" 2>/dev/null || true
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
dumpln "apt sources forbidden patterns"
|
||||||
|
grep -RIn --line-number -E 'deb\.debian\.org|security\.debian\.org|cdrom:|archive\.ubuntu\.com' /etc/apt/sources.list /etc/apt/sources.list.d 2>/dev/null || true
|
||||||
|
|
||||||
|
dumpln "apt periodic config"
|
||||||
|
if [ -f /etc/apt/apt.conf.d/20saikyo-periodic ]; then
|
||||||
|
echo "20saikyo-periodic=present"
|
||||||
|
cat /etc/apt/apt.conf.d/20saikyo-periodic 2>/dev/null || true
|
||||||
|
else
|
||||||
|
echo "20saikyo-periodic=MISSING"
|
||||||
|
fi
|
||||||
|
|
||||||
|
dumpln "auto update units state"
|
||||||
|
for u in apt-daily.timer apt-daily-upgrade.timer unattended-upgrades.service packagekit-offline-update.timer packagekit-offline-update.service; do
|
||||||
|
echo "unit=${u} enabled=$(systemctl is-enabled "$u" 2>/dev/null || echo unknown) active=$(systemctl is-active "$u" 2>/dev/null || echo unknown) masked=$(systemctl is-masked "$u" 2>/dev/null || echo unknown)"
|
||||||
|
done
|
||||||
|
|
||||||
|
dumpln "packagekit state"
|
||||||
|
echo "packagekit enabled=$(systemctl is-enabled packagekit.service 2>/dev/null || echo unknown) active=$(systemctl is-active packagekit.service 2>/dev/null || echo unknown) masked=$(systemctl is-masked packagekit.service 2>/dev/null || echo unknown)"
|
||||||
|
|
||||||
|
dumpln "system timers (filtered)"
|
||||||
|
systemctl list-timers --all 2>/dev/null | grep -E 'apt|unattended|packagekit|flatpak|snap' || true
|
||||||
|
|
||||||
|
dumpln "license"
|
||||||
|
if command -v saikyo-license >/dev/null 2>&1; then
|
||||||
|
echo "saikyo-license=present"
|
||||||
|
if /usr/sbin/saikyo-license verify >/dev/null 2>&1; then
|
||||||
|
echo "license_verify=ok"
|
||||||
|
else
|
||||||
|
echo "license_verify=fail"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "saikyo-license=missing"
|
||||||
|
fi
|
||||||
|
|
||||||
|
dumpln "secure boot"
|
||||||
|
if command -v mokutil >/dev/null 2>&1; then
|
||||||
|
mokutil --sb-state 2>&1 || true
|
||||||
|
mokutil --list-enrolled 2>/dev/null || true
|
||||||
|
else
|
||||||
|
echo "mokutil=missing"
|
||||||
|
fi
|
||||||
|
|
||||||
|
dumpln "branding files"
|
||||||
|
ls -la /usr/share/saikyo-os 2>/dev/null || true
|
||||||
|
ls -la /usr/share/wallpapers/Saikyo/contents/images 2>/dev/null || true
|
||||||
|
ls -la /usr/share/sddm/themes/Saikyo 2>/dev/null || true
|
||||||
|
|
||||||
|
dumpln "visible Debian/Plasma strings (best effort)"
|
||||||
|
grep -RIn --line-number -E '\bDebian\b|\bPlasma\b' /usr/share/applications /usr/share/metainfo 2>/dev/null | head -n 50 || true
|
||||||
|
|
||||||
|
} >"${OUT_FILE}" 2>/dev/null
|
||||||
|
|
||||||
|
chmod 0644 "${OUT_FILE}" 2>/dev/null || true
|
||||||
|
ln -sfn "$(basename "${OUT_FILE}")" "${OUT_DIR}/latest.log" 2>/dev/null || true
|
||||||
|
|
||||||
|
echo "Wrote ${OUT_FILE}"
|
||||||
|
exit 0
|
||||||
185
saikyo-branding/assets/bin/saikyo-first-login-kde
Normal file
185
saikyo-branding/assets/bin/saikyo-first-login-kde
Normal file
|
|
@ -0,0 +1,185 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
MARK_DIR="${XDG_CONFIG_HOME:-${HOME}/.config}/saikyo-os"
|
||||||
|
MARK_FILE="${MARK_DIR}/first-login-kde.done"
|
||||||
|
WALLPAPER_PATH="/usr/share/wallpapers/Saikyo/contents/images/saikyo-default.svg"
|
||||||
|
LOGO_PATH="/usr/share/saikyo-os/logos/saikyo-logo.svg"
|
||||||
|
AVATAR_PNG="/usr/share/icons/hicolor/512x512/apps/distributor-logo.png"
|
||||||
|
COLOR_SCHEME="SaikyoDark"
|
||||||
|
|
||||||
|
mkdir -p "${MARK_DIR}" 2>/dev/null || true
|
||||||
|
|
||||||
|
if [[ -f "${MARK_FILE}" ]]; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ! -r "${WALLPAPER_PATH}" ]]; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
have_cmd() { command -v "$1" >/dev/null 2>&1; }
|
||||||
|
|
||||||
|
apply_look_and_feel() {
|
||||||
|
if have_cmd lookandfeeltool; then
|
||||||
|
lookandfeeltool -a org.saikyo.desktop >/dev/null 2>&1 || true
|
||||||
|
elif have_cmd lookandfeeltool5; then
|
||||||
|
lookandfeeltool5 -a org.saikyo.desktop >/dev/null 2>&1 || true
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
apply_color_scheme() {
|
||||||
|
if have_cmd plasma-apply-colorscheme; then
|
||||||
|
plasma-apply-colorscheme "${COLOR_SCHEME}" >/dev/null 2>&1 || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
if have_cmd kwriteconfig5; then
|
||||||
|
kwriteconfig5 --file kdeglobals --group General --key ColorScheme "${COLOR_SCHEME}" >/dev/null 2>&1 || true
|
||||||
|
elif have_cmd kwriteconfig6; then
|
||||||
|
kwriteconfig6 --file kdeglobals --group General --key ColorScheme "${COLOR_SCHEME}" >/dev/null 2>&1 || true
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
restart_plasma_shell() {
|
||||||
|
if have_cmd kquitapp5 && have_cmd kstart5; then
|
||||||
|
kquitapp5 plasmashell >/dev/null 2>&1 || true
|
||||||
|
nohup kstart5 plasmashell >/dev/null 2>&1 &
|
||||||
|
elif have_cmd kquitapp6 && have_cmd kstart6; then
|
||||||
|
kquitapp6 plasmashell >/dev/null 2>&1 || true
|
||||||
|
nohup kstart6 plasmashell >/dev/null 2>&1 &
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
set_wallpaper() {
|
||||||
|
local qdbus_cmd="$1"
|
||||||
|
"${qdbus_cmd}" org.kde.plasmashell /PlasmaShell org.kde.PlasmaShell.evaluateScript "
|
||||||
|
var Desktops = desktops();
|
||||||
|
for (i = 0; i < Desktops.length; i++) {
|
||||||
|
d = Desktops[i];
|
||||||
|
d.wallpaperPlugin = 'org.kde.image';
|
||||||
|
d.currentConfigGroup = ['Wallpaper', 'org.kde.image', 'General'];
|
||||||
|
d.writeConfig('Image', 'file://${WALLPAPER_PATH}');
|
||||||
|
}
|
||||||
|
" >/dev/null 2>&1 || return 1
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if have_cmd qdbus; then
|
||||||
|
set_wallpaper qdbus || true
|
||||||
|
elif have_cmd qdbus6; then
|
||||||
|
set_wallpaper qdbus6 || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
apply_color_scheme
|
||||||
|
apply_look_and_feel
|
||||||
|
restart_plasma_shell || true
|
||||||
|
|
||||||
|
add_panel_widgets_and_pins() {
|
||||||
|
local qdbus_cmd="$1"
|
||||||
|
"${qdbus_cmd}" org.kde.plasmashell /PlasmaShell org.kde.PlasmaShell.evaluateScript "
|
||||||
|
function firstPanel() {
|
||||||
|
var ps = panels();
|
||||||
|
if (!ps || ps.length === 0) return null;
|
||||||
|
return ps[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureWidget(panel, pluginId) {
|
||||||
|
try {
|
||||||
|
var ws = panel.widgets();
|
||||||
|
for (var i = 0; i < ws.length; i++) {
|
||||||
|
if (ws[i].type === pluginId) return;
|
||||||
|
}
|
||||||
|
panel.addWidget(pluginId);
|
||||||
|
} catch (e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureTaskManagerPins(panel, launchersStr) {
|
||||||
|
try {
|
||||||
|
var ws = panel.widgets();
|
||||||
|
for (var i = 0; i < ws.length; i++) {
|
||||||
|
if (ws[i].type === 'org.kde.plasma.taskmanager') {
|
||||||
|
var tm = ws[i];
|
||||||
|
tm.currentConfigGroup = ['General'];
|
||||||
|
tm.writeConfig('launchers', launchersStr);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var p = firstPanel();
|
||||||
|
if (!p) return;
|
||||||
|
|
||||||
|
// License status widget
|
||||||
|
ensureWidget(p, 'org.saikyo.license');
|
||||||
|
|
||||||
|
// Pinned launchers in task manager
|
||||||
|
var launchers = [
|
||||||
|
'applications:org.kde.discover.desktop',
|
||||||
|
'applications:systemsettings.desktop',
|
||||||
|
'applications:saikyo-license.desktop'
|
||||||
|
].join(',');
|
||||||
|
ensureTaskManagerPins(p, launchers);
|
||||||
|
" >/dev/null 2>&1 || true
|
||||||
|
}
|
||||||
|
|
||||||
|
if have_cmd qdbus; then
|
||||||
|
add_panel_widgets_and_pins qdbus || true
|
||||||
|
elif have_cmd qdbus6; then
|
||||||
|
add_panel_widgets_and_pins qdbus6 || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
desktop_dir="${HOME}/Desktop"
|
||||||
|
if [[ -r "${HOME}/.config/user-dirs.dirs" ]]; then
|
||||||
|
d=$(grep -E '^XDG_DESKTOP_DIR=' "${HOME}/.config/user-dirs.dirs" | head -n 1 | cut -d= -f2- | tr -d '"')
|
||||||
|
d=${d//\$HOME/${HOME}}
|
||||||
|
if [[ -n "${d}" ]]; then desktop_dir="${d}"; fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Ensure KWallet doesn't require a pre-existing GPG key by default.
|
||||||
|
# This prevents first-run failures in apps that request KWallet integration.
|
||||||
|
mkdir -p "${XDG_CONFIG_HOME:-${HOME}/.config}" 2>/dev/null || true
|
||||||
|
{
|
||||||
|
echo "[Wallet]"
|
||||||
|
echo "UseGpg=false"
|
||||||
|
} > "${XDG_CONFIG_HOME:-${HOME}/.config}/kwalletrc" 2>/dev/null || true
|
||||||
|
|
||||||
|
# Set a default user avatar for new sessions (shown in KDE menus/panel).
|
||||||
|
# Prefer PNG (KDE reliably picks it up); fallback to SVG.
|
||||||
|
if [[ -r "${AVATAR_PNG}" ]]; then
|
||||||
|
cp -f "${AVATAR_PNG}" "${HOME}/.face.icon" 2>/dev/null || true
|
||||||
|
chmod 0644 "${HOME}/.face.icon" 2>/dev/null || true
|
||||||
|
elif [[ -r "${LOGO_PATH}" ]]; then
|
||||||
|
cp -f "${LOGO_PATH}" "${HOME}/.face.icon" 2>/dev/null || true
|
||||||
|
chmod 0644 "${HOME}/.face.icon" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p "${desktop_dir}" 2>/dev/null || true
|
||||||
|
if [[ -r /usr/share/applications/saikyo-license.desktop ]]; then
|
||||||
|
cp -f /usr/share/applications/saikyo-license.desktop "${desktop_dir}/SAIKYO OS License.desktop" 2>/dev/null || true
|
||||||
|
chmod 0755 "${desktop_dir}/SAIKYO OS License.desktop" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -r /usr/share/applications/org.kde.discover.desktop ]]; then
|
||||||
|
cp -f /usr/share/applications/org.kde.discover.desktop "${desktop_dir}/SAIKYO Discover.desktop" 2>/dev/null || true
|
||||||
|
chmod 0755 "${desktop_dir}/SAIKYO Discover.desktop" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -r /usr/share/applications/systemsettings.desktop ]]; then
|
||||||
|
cp -f /usr/share/applications/systemsettings.desktop "${desktop_dir}/System Settings.desktop" 2>/dev/null || true
|
||||||
|
chmod 0755 "${desktop_dir}/System Settings.desktop" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
if have_cmd kdialog; then
|
||||||
|
msg="Добро пожаловать в SAIKYO OS.\n\nНа этой системе действует пробный период 7 дней.\nПосле истечения пробного периода потребуется полная активация лицензии.\n\nРекомендуется выполнить активацию заранее."
|
||||||
|
if kdialog --title "SAIKYO OS" --icon "${LOGO_PATH}" --yes-label "Открыть активацию" --no-label "Позже" --yesno "${msg}" 2>/dev/null; then
|
||||||
|
if have_cmd saikyo-license-kde; then
|
||||||
|
nohup saikyo-license-kde >/dev/null 2>&1 &
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
touch "${MARK_FILE}" 2>/dev/null || true
|
||||||
|
exit 0
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIHmDCCB0WgAwIBAgIKYqt5lQAAAAADtjAKBggqhQMHAQEDAjCCASQxHjAcBgkq
|
||||||
|
hkiG9w0BCQEWD2RpdEBtaW5zdnlhei5ydTELMAkGA1UEBhMCUlUxGDAWBgNVBAgM
|
||||||
|
Dzc3INCc0L7RgdC60LLQsDEZMBcGA1UEBwwQ0LMuINCc0L7RgdC60LLQsDEuMCwG
|
||||||
|
A1UECQwl0YPQu9C40YbQsCDQotCy0LXRgNGB0LrQsNGPLCDQtNC+0LwgNzEsMCoG
|
||||||
|
A1UECgwj0JzQuNC90LrQvtC80YHQstGP0LfRjCDQoNC+0YHRgdC40LgxGDAWBgUq
|
||||||
|
hQNkARINMTA0NzcwMjAyNjcwMTEaMBgGCCqFAwOBAwEBEgwwMDc3MTA0NzQzNzUx
|
||||||
|
LDAqBgNVBAMMI9Cc0LjQvdC60L7QvNGB0LLRj9C30Ywg0KDQvtGB0YHQuNC4MB4X
|
||||||
|
DTIwMDIwNTE0MDI0N1oXDTM1MDIwNTE0MDI0N1owggFtMSAwHgYJKoZIhvcNAQkB
|
||||||
|
FhF1Y19ma0Byb3NrYXpuYS5ydTEZMBcGA1UECAwQ0LMuINCc0L7RgdC60LLQsDEa
|
||||||
|
MBgGCCqFAwOBAwEBEgwwMDc3MTA1Njg3NjAxGDAWBgUqhQNkARINMTA0Nzc5NzAx
|
||||||
|
OTgzMDFgMF4GA1UECQxX0JHQvtC70YzRiNC+0Lkg0JfQu9Cw0YLQvtGD0YHRgtC4
|
||||||
|
0L3RgdC60LjQuSDQv9C10YDQtdGD0LvQvtC6LCDQtC4gNiwg0YHRgtGA0L7QtdC9
|
||||||
|
0LjQtSAxMRUwEwYDVQQHDAzQnNC+0YHQutCy0LAxCzAJBgNVBAYTAlJVMTgwNgYD
|
||||||
|
VQQKDC/QpNC10LTQtdGA0LDQu9GM0L3QvtC1INC60LDQt9C90LDRh9C10LnRgdGC
|
||||||
|
0LLQvjE4MDYGA1UEAwwv0KTQtdC00LXRgNCw0LvRjNC90L7QtSDQutCw0LfQvdCw
|
||||||
|
0YfQtdC50YHRgtCy0L4wZjAfBggqhQMHAQEBATATBgcqhQMCAiMBBggqhQMHAQEC
|
||||||
|
AgNDAARA/G1/DjI9Hw+IV16X9pm0vTEj7uE8rv88OAAdmD7ddmRuYD6FLl491Zqv
|
||||||
|
AxF94s7j5C2i/XC5OMkEhJQ6nKUI9KOCBAMwggP/MBIGA1UdEwEB/wQIMAYBAf8C
|
||||||
|
AQAwUgYFKoUDZG8ESQxHItCa0YDQuNC/0YLQvtCf0YDQviBDU1AiINCy0LXRgNGB
|
||||||
|
0LjRjyA0LjAgKNC40YHQv9C+0LvQvdC10L3QuNC1IDItQmFzZSkwJQYDVR0gBB4w
|
||||||
|
HDAIBgYqhQNkcQEwCAYGKoUDZHECMAYGBFUdIAAwDgYDVR0PAQH/BAQDAgHGMIIB
|
||||||
|
ZQYDVR0jBIIBXDCCAViAFMJU8bRr1Ey34G02tCOQ8f7DPJsGoYIBLKSCASgwggEk
|
||||||
|
MR4wHAYJKoZIhvcNAQkBFg9kaXRAbWluc3Z5YXoucnUxCzAJBgNVBAYTAlJVMRgw
|
||||||
|
FgYDVQQIDA83NyDQnNC+0YHQutCy0LAxGTAXBgNVBAcMENCzLiDQnNC+0YHQutCy
|
||||||
|
0LAxLjAsBgNVBAkMJdGD0LvQuNGG0LAg0KLQstC10YDRgdC60LDRjywg0LTQvtC8
|
||||||
|
IDcxLDAqBgNVBAoMI9Cc0LjQvdC60L7QvNGB0LLRj9C30Ywg0KDQvtGB0YHQuNC4
|
||||||
|
MRgwFgYFKoUDZAESDTEwNDc3MDIwMjY3MDExGjAYBggqhQMDgQMBARIMMDA3NzEw
|
||||||
|
NDc0Mzc1MSwwKgYDVQQDDCPQnNC40L3QutC+0LzRgdCy0Y/Qt9GMINCg0L7RgdGB
|
||||||
|
0LjQuIIQTm1HiybyfWV/do4CXOPTkzAdBgNVHQ4EFgQU0GSWbXJA61h9JH+7IFvP
|
||||||
|
w45setQwgZgGA1UdHwSBkDCBjTAtoCugKYYnaHR0cDovL3JlZXN0ci1wa2kucnUv
|
||||||
|
Y2RwL2d1Y19nb3N0MTIuY3JsMC2gK6AphidodHRwOi8vY29tcGFueS5ydC5ydS9j
|
||||||
|
ZHAvZ3VjX2dvc3QxMi5jcmwwLaAroCmGJ2h0dHA6Ly9yb3N0ZWxlY29tLnJ1L2Nk
|
||||||
|
cC9ndWNfZ29zdDEyLmNybDBDBggrBgEFBQcBAQQ3MDUwMwYIKwYBBQUHMAKGJ2h0
|
||||||
|
dHA6Ly9yZWVzdHItcGtpLnJ1L2NkcC9ndWNfZ29zdDEyLmNydDCB9QYFKoUDZHAE
|
||||||
|
geswgegMNNCf0JDQmtCcIMKr0JrRgNC40L/RgtC+0J/RgNC+IEhTTcK7INCy0LXR
|
||||||
|
gNGB0LjQuCAyLjAMQ9Cf0JDQmiDCq9CT0L7Qu9C+0LLQvdC+0Lkg0YPQtNC+0YHR
|
||||||
|
gtC+0LLQtdGA0Y/RjtGJ0LjQuSDRhtC10L3RgtGAwrsMNdCX0LDQutC70Y7Rh9C1
|
||||||
|
0L3QuNC1IOKEliAxNDkvMy8yLzIvMjMg0L7RgiAwMi4wMy4yMDE4DDTQl9Cw0LrQ
|
||||||
|
u9GO0YfQtdC90LjQtSDihJYgMTQ5LzcvNi8xMDUg0L7RgiAyNy4wNi4yMDE4MAoG
|
||||||
|
CCqFAwcBAQMCA0EAYO7/gtED6JgCLK7ffDDrnggByLOMGafo2r5TakOU/aBSidSD
|
||||||
|
a17cnrTflUw6REP8SrOa7o/tfCwpkbLJF2l6CQ==
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIEizCCA/egAwIBAgIVALl/bbS8diQY7sdeMXod8B1oPUzeMAoGCCqFAwcBAQMD
|
||||||
|
MIIBMDEVMBMGBSqFA2QEEgo3NzEwNDc0Mzc1MRgwFgYFKoUDZAESDTEwNDc3MDIw
|
||||||
|
MjY3MDExUzBRBgNVBAkMStCf0YDQtdGB0L3QtdC90YHQutCw0Y8g0L3QsNCx0LXR
|
||||||
|
gNC10LbQvdCw0Y8sINC00L7QvCAxMCwg0YHRgtGA0L7QtdC90LjQtSAyMRkwFwYD
|
||||||
|
VQQHDBDQsy4g0JzQvtGB0LrQstCwMRgwFgYDVQQIDA83NyDQnNC+0YHQutCy0LAx
|
||||||
|
CzAJBgNVBAYTAlJVMSYwJAYDVQQKDB3QnNC40L3RhtC40YTRgNGLINCg0L7RgdGB
|
||||||
|
0LjQuDE+MDwGA1UEAww10JzQuNC90YbQuNGE0YDRiyDQoNC+0YHRgdC40Lgg0J3Q
|
||||||
|
o9CmINC60L7RgNC90LXQstC+0LkwHhcNMjUwNTI4MTU0MTQxWhcNNDAwNTI0MTU0
|
||||||
|
MTQxWjCCATAxFTATBgUqhQNkBBIKNzcxMDQ3NDM3NTEYMBYGBSqFA2QBEg0xMDQ3
|
||||||
|
NzAyMDI2NzAxMVMwUQYDVQQJDErQn9GA0LXRgdC90LXQvdGB0LrQsNGPINC90LDQ
|
||||||
|
sdC10YDQtdC20L3QsNGPLCDQtNC+0LwgMTAsINGB0YLRgNC+0LXQvdC40LUgMjEZ
|
||||||
|
MBcGA1UEBwwQ0LMuINCc0L7RgdC60LLQsDEYMBYGA1UECAwPNzcg0JzQvtGB0LrQ
|
||||||
|
stCwMQswCQYDVQQGEwJSVTEmMCQGA1UECgwd0JzQuNC90YbQuNGE0YDRiyDQoNC+
|
||||||
|
0YHRgdC40LgxPjA8BgNVBAMMNdCc0LjQvdGG0LjRhNGA0Ysg0KDQvtGB0YHQuNC4
|
||||||
|
INCd0KPQpiDQutC+0YDQvdC10LLQvtC5MIGqMCEGCCqFAwcBAQECMBUGCSqFAwcB
|
||||||
|
AgECAQYIKoUDBwEBAgMDgYQABIGAdCxt4xzP9ZCep1FqKXymiUBX5GVp9KNV6bCV
|
||||||
|
sw+nV+E2/i2QKoTXkSrZwNdTAGfHaxRj0dNZjwljPyGRARTmBSG1CLvZvuwrla4O
|
||||||
|
ki2HXmLtLmdZCmk0MVG/0qyK2Hg/cG9VU/YvHNSxbdWgfBpaIKEprR3511tD1Sui
|
||||||
|
xEPhknSjgZcwgZQwEgYDVR0TAQH/BAgwBgEB/wIBBDAdBgNVHQ4EFgQU2EhWFsjb
|
||||||
|
gUKVDHySi+o35xGlveowDgYDVR0PAQH/BAQDAgEGME8GA1UdIARIMEYwCgYIKoUD
|
||||||
|
AhkBDgMwBgYEVR0gADAIBgYqhQNkcQEwCAYGKoUDZHECMAgGBiqFA2RxAzAIBgYq
|
||||||
|
hQNkcQQwCAYGKoUDZHEFMAoGCCqFAwcBAQMDA4GBAG6v+ir5U37uMA9IEkhNMl95
|
||||||
|
PSa6X0XsLrmxWFjHVQcPMGP5niDjF0pNDqxps40Ig2qqzGZXlFLxuu0w+xeuDfMn
|
||||||
|
/AokEYCkWsMRhmzNEmVkLWggEaqBfHO+JEmAjwvyo6ggyvBdX8xQRpbcw9/Ugxx5
|
||||||
|
h7F9deB2Q1ZcoFgcZmFl
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIHhjCCBzOgAwIBAgILALXxMtMAAAAAAVowCgYIKoUDBwEBAwIwggEkMR4wHAYJ
|
||||||
|
KoZIhvcNAQkBFg9kaXRAbWluc3Z5YXoucnUxCzAJBgNVBAYTAlJVMRgwFgYDVQQI
|
||||||
|
DA83NyDQnNC+0YHQutCy0LAxGTAXBgNVBAcMENCzLiDQnNC+0YHQutCy0LAxLjAs
|
||||||
|
BgNVBAkMJdGD0LvQuNGG0LAg0KLQstC10YDRgdC60LDRjywg0LTQvtC8IDcxLDAq
|
||||||
|
BgNVBAoMI9Cc0LjQvdC60L7QvNGB0LLRj9C30Ywg0KDQvtGB0YHQuNC4MRgwFgYF
|
||||||
|
KoUDZAESDTEwNDc3MDIwMjY3MDExGjAYBggqhQMDgQMBARIMMDA3NzEwNDc0Mzc1
|
||||||
|
MSwwKgYDVQQDDCPQnNC40L3QutC+0LzRgdCy0Y/Qt9GMINCg0L7RgdGB0LjQuDAe
|
||||||
|
Fw0xODExMTkxNTU2MDFaFw0zMzExMTkxNTU2MDFaMIIBbTEgMB4GCSqGSIb3DQEJ
|
||||||
|
ARYRdWNfZmtAcm9za2F6bmEucnUxGTAXBgNVBAgMENCzLiDQnNC+0YHQutCy0LAx
|
||||||
|
GjAYBggqhQMDgQMBARIMMDA3NzEwNTY4NzYwMRgwFgYFKoUDZAESDTEwNDc3OTcw
|
||||||
|
MTk4MzAxYDBeBgNVBAkMV9CR0L7Qu9GM0YjQvtC5INCX0LvQsNGC0L7Rg9GB0YLQ
|
||||||
|
uNC90YHQutC40Lkg0L/QtdGA0LXRg9C70L7Quiwg0LQuIDYsINGB0YLRgNC+0LXQ
|
||||||
|
vdC40LUgMTEVMBMGA1UEBwwM0JzQvtGB0LrQstCwMQswCQYDVQQGEwJSVTE4MDYG
|
||||||
|
A1UECgwv0KTQtdC00LXRgNCw0LvRjNC90L7QtSDQutCw0LfQvdCw0YfQtdC50YHR
|
||||||
|
gtCy0L4xODA2BgNVBAMML9Ck0LXQtNC10YDQsNC70YzQvdC+0LUg0LrQsNC30L3Q
|
||||||
|
sNGH0LXQudGB0YLQstC+MGYwHwYIKoUDBwEBAQEwEwYHKoUDAgIjAQYIKoUDBwEB
|
||||||
|
AgIDQwAEQBHF4BLi2ABpI2cZNBIql/rippw/0qqoi4S2NvJHSGbC694tvfJqOmGG
|
||||||
|
UCD0b1kUlEq6ueFAtF7gOICquPj+DcmjggPwMIID7DASBgNVHRMBAf8ECDAGAQH/
|
||||||
|
AgEAMD8GBSqFA2RvBDYMNNCh0JrQl9CYICLQmtGA0LjQv9GC0L7Qn9GA0L4gQ1NQ
|
||||||
|
IiAo0LLQtdGA0YHQuNGPIDQuMCkwJQYDVR0gBB4wHDAIBgYqhQNkcQEwCAYGKoUD
|
||||||
|
ZHECMAYGBFUdIAAwDgYDVR0PAQH/BAQDAgHGMIIBZQYDVR0jBIIBXDCCAViAFMJU
|
||||||
|
8bRr1Ey34G02tCOQ8f7DPJsGoYIBLKSCASgwggEkMR4wHAYJKoZIhvcNAQkBFg9k
|
||||||
|
aXRAbWluc3Z5YXoucnUxCzAJBgNVBAYTAlJVMRgwFgYDVQQIDA83NyDQnNC+0YHQ
|
||||||
|
utCy0LAxGTAXBgNVBAcMENCzLiDQnNC+0YHQutCy0LAxLjAsBgNVBAkMJdGD0LvQ
|
||||||
|
uNGG0LAg0KLQstC10YDRgdC60LDRjywg0LTQvtC8IDcxLDAqBgNVBAoMI9Cc0LjQ
|
||||||
|
vdC60L7QvNGB0LLRj9C30Ywg0KDQvtGB0YHQuNC4MRgwFgYFKoUDZAESDTEwNDc3
|
||||||
|
MDIwMjY3MDExGjAYBggqhQMDgQMBARIMMDA3NzEwNDc0Mzc1MSwwKgYDVQQDDCPQ
|
||||||
|
nNC40L3QutC+0LzRgdCy0Y/Qt9GMINCg0L7RgdGB0LjQuIIQTm1HiybyfWV/do4C
|
||||||
|
XOPTkzAdBgNVHQ4EFgQUwNbWCn1rfsmOObzaifqvlCxYWo0wgZgGA1UdHwSBkDCB
|
||||||
|
jTAtoCugKYYnaHR0cDovL3JlZXN0ci1wa2kucnUvY2RwL2d1Y19nb3N0MTIuY3Js
|
||||||
|
MC2gK6AphidodHRwOi8vY29tcGFueS5ydC5ydS9jZHAvZ3VjX2dvc3QxMi5jcmww
|
||||||
|
LaAroCmGJ2h0dHA6Ly9yb3N0ZWxlY29tLnJ1L2NkcC9ndWNfZ29zdDEyLmNybDBD
|
||||||
|
BggrBgEFBQcBAQQ3MDUwMwYIKwYBBQUHMAKGJ2h0dHA6Ly9yZWVzdHItcGtpLnJ1
|
||||||
|
L2NkcC9ndWNfZ29zdDEyLmNydDCB9QYFKoUDZHAEgeswgegMNNCf0JDQmtCcIMKr
|
||||||
|
0JrRgNC40L/RgtC+0J/RgNC+IEhTTcK7INCy0LXRgNGB0LjQuCAyLjAMQ9Cf0JDQ
|
||||||
|
miDCq9CT0L7Qu9C+0LLQvdC+0Lkg0YPQtNC+0YHRgtC+0LLQtdGA0Y/RjtGJ0LjQ
|
||||||
|
uSDRhtC10L3RgtGAwrsMNdCX0LDQutC70Y7Rh9C10L3QuNC1IOKEliAxNDkvMy8y
|
||||||
|
LzIvMjMg0L7RgiAwMi4wMy4yMDE4DDTQl9Cw0LrQu9GO0YfQtdC90LjQtSDihJYg
|
||||||
|
MTQ5LzcvNi8xMDUg0L7RgiAyNy4wNi4yMDE4MAoGCCqFAwcBAQMCA0EALtT076gA
|
||||||
|
jyE3aXhaCfJUQCH8xyUy4/It0btS24K3tKJZYLaYahNjbNNqcEFptDfn6I0c89xz
|
||||||
|
oKhFJDPHvjy/sg==
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIFwjCCA6qgAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwcDELMAkGA1UEBhMCUlUx
|
||||||
|
PzA9BgNVBAoMNlRoZSBNaW5pc3RyeSBvZiBEaWdpdGFsIERldmVsb3BtZW50IGFu
|
||||||
|
ZCBDb21tdW5pY2F0aW9uczEgMB4GA1UEAwwXUnVzc2lhbiBUcnVzdGVkIFJvb3Qg
|
||||||
|
Q0EwHhcNMjIwMzAxMjEwNDE1WhcNMzIwMjI3MjEwNDE1WjBwMQswCQYDVQQGEwJS
|
||||||
|
VTE/MD0GA1UECgw2VGhlIE1pbmlzdHJ5IG9mIERpZ2l0YWwgRGV2ZWxvcG1lbnQg
|
||||||
|
YW5kIENvbW11bmljYXRpb25zMSAwHgYDVQQDDBdSdXNzaWFuIFRydXN0ZWQgUm9v
|
||||||
|
dCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMfFOZ8pUAL3+r2n
|
||||||
|
qqE0Zp52selXsKGFYoG0GM5bwz1bSFtCt+AZQMhkWQheI3poZAToYJu69pHLKS6Q
|
||||||
|
XBiwBC1cvzYmUYKMYZC7jE5YhEU2bSL0mX7NaMxMDmH2/NwuOVRj8OImVa5s1F4U
|
||||||
|
zn4Kv3PFlDBjjSjXKVY9kmjUBsXQrIHeaqmUIsPIlNWUnimXS0I0abExqkbdrXbX
|
||||||
|
YwCOXhOO2pDUx3ckmJlCMUGacUTnylyQW2VsJIyIGA8V0xzdaeUXg0VZ6ZmNUr5Y
|
||||||
|
Ber/EAOLPb8NYpsAhJe2mXjMB/J9HNsoFMBFJ0lLOT/+dQvjbdRZoOT8eqJpWnVD
|
||||||
|
U+QL/qEZnz57N88OWM3rabJkRNdU/Z7x5SFIM9FrqtN8xewsiBWBI0K6XFuOBOTD
|
||||||
|
4V08o4TzJ8+Ccq5XlCUW2L48pZNCYuBDfBh7FxkB7qDgGDiaftEkZZfApRg2E+M9
|
||||||
|
G8wkNKTPLDc4wH0FDTijhgxR3Y4PiS1HL2Zhw7bD3CbslmEGgfnnZojNkJtcLeBH
|
||||||
|
BLa52/dSwNU4WWLubaYSiAmA9IUMX1/RpfpxOxd4Ykmhz97oFbUaDJFipIggx5sX
|
||||||
|
ePAlkTdWnv+RWBxlJwMQ25oEHmRguNYf4Zr/Rxr9cS93Y+mdXIZaBEE0KS2iLRqa
|
||||||
|
OiWBki9IMQU4phqPOBAaG7A+eP8PAgMBAAGjZjBkMB0GA1UdDgQWBBTh0YHlzlpf
|
||||||
|
BKrS6badZrHF+qwshzAfBgNVHSMEGDAWgBTh0YHlzlpfBKrS6badZrHF+qwshzAS
|
||||||
|
BgNVHRMBAf8ECDAGAQH/AgEEMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsF
|
||||||
|
AAOCAgEAALIY1wkilt/urfEVM5vKzr6utOeDWCUczmWX/RX4ljpRdgF+5fAIS4vH
|
||||||
|
tmXkqpSCOVeWUrJV9QvZn6L227ZwuE15cWi8DCDal3Ue90WgAJJZMfTshN4OI8cq
|
||||||
|
W9E4EG9wglbEtMnObHlms8F3CHmrw3k6KmUkWGoa+/ENmcVl68u/cMRl1JbW2bM+
|
||||||
|
/3A+SAg2c6iPDlehczKx2oa95QW0SkPPWGuNA/CE8CpyANIhu9XFrj3RQ3EqeRcS
|
||||||
|
AQQod1RNuHpfETLU/A2gMmvn/w/sx7TB3W5BPs6rprOA37tutPq9u6FTZOcG1Oqj
|
||||||
|
C/B7yTqgI7rbyvox7DEXoX7rIiEqyNNUguTk/u3SZ4VXE2kmxdmSh3TQvybfbnXV
|
||||||
|
4JbCZVaqiZraqc7oZMnRoWrXRG3ztbnbes/9qhRGI7PqXqeKJBztxRTEVj8ONs1d
|
||||||
|
WN5szTwaPIvhkhO3CO5ErU2rVdUr89wKpNXbBODFKRtgxUT70YpmJ46VVaqdAhOZ
|
||||||
|
D9EUUn4YaeLaS8AjSF/h7UkjOibNc4qVDiPP+rkehFWM66PVnP1Msh93tc+taIfC
|
||||||
|
EYVMxjh8zNbFuoc7fzvvrFILLe7ifvEIUqSVIC/AzplM/Jxw7buXFeGP1qVCBEHq
|
||||||
|
391d/9RAfaZ12zkwFsl+IKwE/OZxW8AHa9i1p4GO0YSNuczzEm4=
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIFFDCCBMGgAwIBAgIQTm1HiybyfWV/do4CXOPTkzAKBggqhQMHAQEDAjCCASQx
|
||||||
|
HjAcBgkqhkiG9w0BCQEWD2RpdEBtaW5zdnlhei5ydTELMAkGA1UEBhMCUlUxGDAW
|
||||||
|
BgNVBAgMDzc3INCc0L7RgdC60LLQsDEZMBcGA1UEBwwQ0LMuINCc0L7RgdC60LLQ
|
||||||
|
sDEuMCwGA1UECQwl0YPQu9C40YbQsCDQotCy0LXRgNGB0LrQsNGPLCDQtNC+0Lwg
|
||||||
|
NzEsMCoGA1UECgwj0JzQuNC90LrQvtC80YHQstGP0LfRjCDQoNC+0YHRgdC40Lgx
|
||||||
|
GDAWBgUqhQNkARINMTA0NzcwMjAyNjcwMTEaMBgGCCqFAwOBAwEBEgwwMDc3MTA0
|
||||||
|
NzQzNzUxLDAqBgNVBAMMI9Cc0LjQvdC60L7QvNGB0LLRj9C30Ywg0KDQvtGB0YHQ
|
||||||
|
uNC4MB4XDTE4MDcwNjEyMTgwNloXDTM2MDcwMTEyMTgwNlowggEkMR4wHAYJKoZI
|
||||||
|
hvcNAQkBFg9kaXRAbWluc3Z5YXoucnUxCzAJBgNVBAYTAlJVMRgwFgYDVQQIDA83
|
||||||
|
NyDQnNC+0YHQutCy0LAxGTAXBgNVBAcMENCzLiDQnNC+0YHQutCy0LAxLjAsBgNV
|
||||||
|
BAkMJdGD0LvQuNGG0LAg0KLQstC10YDRgdC60LDRjywg0LTQvtC8IDcxLDAqBgNV
|
||||||
|
BAoMI9Cc0LjQvdC60L7QvNGB0LLRj9C30Ywg0KDQvtGB0YHQuNC4MRgwFgYFKoUD
|
||||||
|
ZAESDTEwNDc3MDIwMjY3MDExGjAYBggqhQMDgQMBARIMMDA3NzEwNDc0Mzc1MSww
|
||||||
|
KgYDVQQDDCPQnNC40L3QutC+0LzRgdCy0Y/Qt9GMINCg0L7RgdGB0LjQuDBmMB8G
|
||||||
|
CCqFAwcBAQEBMBMGByqFAwICIwEGCCqFAwcBAQICA0MABEB1OSpFp7milX33EP0i
|
||||||
|
kge6HbZacYp9fVj8sUa5RWFXrB27SKX5SvtIGepqKev69RSYeHHKR+jT9YX2NuSK
|
||||||
|
9wONo4IBwjCCAb4wgfUGBSqFA2RwBIHrMIHoDDTQn9CQ0JrQnCDCq9Ca0YDQuNC/
|
||||||
|
0YLQvtCf0YDQviBIU03CuyDQstC10YDRgdC40LggMi4wDEPQn9CQ0JogwqvQk9C+
|
||||||
|
0LvQvtCy0L3QvtC5INGD0LTQvtGB0YLQvtCy0LXRgNGP0Y7RidC40Lkg0YbQtdC9
|
||||||
|
0YLRgMK7DDXQl9Cw0LrQu9GO0YfQtdC90LjQtSDihJYgMTQ5LzMvMi8yLzIzINC+
|
||||||
|
0YIgMDIuMDMuMjAxOAw00JfQsNC60LvRjtGH0LXQvdC40LUg4oSWIDE0OS83LzYv
|
||||||
|
MTA1INC+0YIgMjcuMDYuMjAxODA/BgUqhQNkbwQ2DDTQn9CQ0JrQnCDCq9Ca0YDQ
|
||||||
|
uNC/0YLQvtCf0YDQviBIU03CuyDQstC10YDRgdC40LggMi4wMEMGA1UdIAQ8MDow
|
||||||
|
CAYGKoUDZHEBMAgGBiqFA2RxAjAIBgYqhQNkcQMwCAYGKoUDZHEEMAgGBiqFA2Rx
|
||||||
|
BTAGBgRVHSAAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud
|
||||||
|
DgQWBBTCVPG0a9RMt+BtNrQjkPH+wzybBjAKBggqhQMHAQEDAgNBAJr6/eI7rHL7
|
||||||
|
+FsQnoH2i6DVxqalbIxLKj05edpZGPLLb6B2PTAMya7pSt9hb8QnFABgsR4IE5gT
|
||||||
|
4VVkDWbX/n4=
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"DisableQUIC": true,
|
||||||
|
"SSLVersionMin": "tls1",
|
||||||
|
"SSLErrorOverrideAllowed": false,
|
||||||
|
"EnableOnlineRevocationChecks": true,
|
||||||
|
"NetworkPredictionOptions": 2
|
||||||
|
}
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 130 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 130 KiB |
99
saikyo-branding/assets/kde/SaikyoDark.colors
Normal file
99
saikyo-branding/assets/kde/SaikyoDark.colors
Normal file
|
|
@ -0,0 +1,99 @@
|
||||||
|
[General]
|
||||||
|
ColorScheme=SAIKYO Dark
|
||||||
|
Name=SAIKYO Dark
|
||||||
|
shadeSortColumn=true
|
||||||
|
|
||||||
|
[Colors:Button]
|
||||||
|
BackgroundAlternate=29,85,184
|
||||||
|
BackgroundNormal=11,29,68
|
||||||
|
DecorationFocus=30,85,184
|
||||||
|
DecorationHover=36,110,220
|
||||||
|
ForegroundActive=232,241,255
|
||||||
|
ForegroundInactive=183,208,255
|
||||||
|
ForegroundLink=110,160,255
|
||||||
|
ForegroundNegative=255,138,138
|
||||||
|
ForegroundNeutral=255,210,110
|
||||||
|
ForegroundNormal=232,241,255
|
||||||
|
ForegroundPositive=120,230,170
|
||||||
|
ForegroundVisited=150,120,255
|
||||||
|
|
||||||
|
[Colors:Complementary]
|
||||||
|
BackgroundAlternate=16,34,68
|
||||||
|
BackgroundNormal=9,18,35
|
||||||
|
DecorationFocus=30,85,184
|
||||||
|
DecorationHover=36,110,220
|
||||||
|
ForegroundActive=232,241,255
|
||||||
|
ForegroundInactive=183,208,255
|
||||||
|
ForegroundLink=110,160,255
|
||||||
|
ForegroundNegative=255,138,138
|
||||||
|
ForegroundNeutral=255,210,110
|
||||||
|
ForegroundNormal=232,241,255
|
||||||
|
ForegroundPositive=120,230,170
|
||||||
|
ForegroundVisited=150,120,255
|
||||||
|
|
||||||
|
[Colors:Selection]
|
||||||
|
BackgroundAlternate=30,85,184
|
||||||
|
BackgroundNormal=30,85,184
|
||||||
|
DecorationFocus=30,85,184
|
||||||
|
DecorationHover=36,110,220
|
||||||
|
ForegroundActive=255,255,255
|
||||||
|
ForegroundInactive=255,255,255
|
||||||
|
ForegroundLink=255,255,255
|
||||||
|
ForegroundNegative=255,255,255
|
||||||
|
ForegroundNeutral=255,255,255
|
||||||
|
ForegroundNormal=255,255,255
|
||||||
|
ForegroundPositive=255,255,255
|
||||||
|
ForegroundVisited=255,255,255
|
||||||
|
|
||||||
|
[Colors:Tooltip]
|
||||||
|
BackgroundAlternate=16,34,68
|
||||||
|
BackgroundNormal=11,29,68
|
||||||
|
DecorationFocus=30,85,184
|
||||||
|
DecorationHover=36,110,220
|
||||||
|
ForegroundActive=232,241,255
|
||||||
|
ForegroundInactive=183,208,255
|
||||||
|
ForegroundLink=110,160,255
|
||||||
|
ForegroundNegative=255,138,138
|
||||||
|
ForegroundNeutral=255,210,110
|
||||||
|
ForegroundNormal=232,241,255
|
||||||
|
ForegroundPositive=120,230,170
|
||||||
|
ForegroundVisited=150,120,255
|
||||||
|
|
||||||
|
[Colors:View]
|
||||||
|
BackgroundAlternate=9,18,35
|
||||||
|
BackgroundNormal=5,10,20
|
||||||
|
DecorationFocus=30,85,184
|
||||||
|
DecorationHover=36,110,220
|
||||||
|
ForegroundActive=232,241,255
|
||||||
|
ForegroundInactive=183,208,255
|
||||||
|
ForegroundLink=110,160,255
|
||||||
|
ForegroundNegative=255,138,138
|
||||||
|
ForegroundNeutral=255,210,110
|
||||||
|
ForegroundNormal=232,241,255
|
||||||
|
ForegroundPositive=120,230,170
|
||||||
|
ForegroundVisited=150,120,255
|
||||||
|
|
||||||
|
[Colors:Window]
|
||||||
|
BackgroundAlternate=16,34,68
|
||||||
|
BackgroundNormal=7,26,58
|
||||||
|
DecorationFocus=30,85,184
|
||||||
|
DecorationHover=36,110,220
|
||||||
|
ForegroundActive=232,241,255
|
||||||
|
ForegroundInactive=183,208,255
|
||||||
|
ForegroundLink=110,160,255
|
||||||
|
ForegroundNegative=255,138,138
|
||||||
|
ForegroundNeutral=255,210,110
|
||||||
|
ForegroundNormal=232,241,255
|
||||||
|
ForegroundPositive=120,230,170
|
||||||
|
ForegroundVisited=150,120,255
|
||||||
|
|
||||||
|
[KDE]
|
||||||
|
contrast=4
|
||||||
|
|
||||||
|
[WM]
|
||||||
|
activeBackground=7,26,58
|
||||||
|
activeBlend=7,26,58
|
||||||
|
activeForeground=232,241,255
|
||||||
|
inactiveBackground=5,10,20
|
||||||
|
inactiveBlend=5,10,20
|
||||||
|
inactiveForeground=183,208,255
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
[kdeglobals][General]
|
||||||
|
ColorScheme=SaikyoDark
|
||||||
|
|
||||||
|
[kdeglobals][KDE]
|
||||||
|
SingleClick=false
|
||||||
|
|
||||||
|
[plasmarc][Theme]
|
||||||
|
name=breeze-dark
|
||||||
|
|
||||||
|
[ksplashrc][KSplash]
|
||||||
|
Engine=KSplashQML
|
||||||
|
Theme=Saikyo
|
||||||
|
|
||||||
|
[sddm.conf][Theme]
|
||||||
|
Current=Saikyo
|
||||||
|
|
||||||
|
[Wallpaper][org.kde.image][General]
|
||||||
|
Image=file:///usr/share/wallpapers/Saikyo/contents/images/saikyo-default.svg
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user