Добавлены исходники пакетов saikyo-*

This commit is contained in:
Saikyo OS Team 2026-01-21 19:46:35 +03:00
parent d11da1fafd
commit 5820108829
369 changed files with 10516 additions and 0 deletions

View 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}"

View 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

View File

@ -0,0 +1 @@
./bin/saikyo-audit-report

View 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

View 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.

View 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.

View File

@ -0,0 +1 @@
saikyo-audit-report

View 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

View File

@ -0,0 +1 @@
bin/saikyo-audit-report usr/sbin/saikyo-audit-report

View File

@ -0,0 +1,8 @@
#!/usr/bin/make -f
%:
dh $@
override_dh_auto_build:
override_dh_auto_test:

View File

@ -0,0 +1,2 @@
misc:Depends=
misc:Pre-Depends=

View File

@ -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.

View File

@ -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

View 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}"

View 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.

View File

@ -0,0 +1 @@
3.0 (native)

View 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

View 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

View 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())

View 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

View File

@ -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

View 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

View 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.

View 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.

View File

@ -0,0 +1 @@
saikyo-av-gui

View 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

View 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
View 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

View File

@ -0,0 +1 @@
dh_fixperms

View 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

View 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

View File

@ -0,0 +1,4 @@
#!/usr/bin/env bash
set -euo pipefail
exit 0

View File

@ -0,0 +1,2 @@
misc:Depends=
misc:Pre-Depends=

View File

@ -0,0 +1 @@
/etc/xdg/autostart/saikyo-av-gui.desktop

View 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.

View 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

View 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

View 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

View File

@ -0,0 +1,4 @@
#!/usr/bin/env bash
set -euo pipefail
exit 0

View 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

View 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())

View 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

View 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;

View 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.

View 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

View 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>

View File

@ -0,0 +1 @@
3.0 (native)

View 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;

View 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

View 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
View 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

View 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

View File

@ -0,0 +1,3 @@
./bin/saikyo-avd
./systemd/saikyo-avd.service
./systemd/saikyo-avd.timer

View File

@ -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

View File

@ -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

View 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
View 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.

View 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.

View File

@ -0,0 +1 @@
saikyo-av

2
saikyo-av/debian/files Normal file
View 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
View 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
View 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

View File

@ -0,0 +1 @@
dh_fixperms

View 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

View 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

View 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

View 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

View File

@ -0,0 +1,2 @@
misc:Depends=
misc:Pre-Depends=

View 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.

View 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

View 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

View 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

View 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

View 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

View 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

View File

@ -0,0 +1,11 @@
[Unit]
Description=Saikyo Antivirus Agent timer
[Timer]
OnBootSec=2min
OnUnitActiveSec=5min
AccuracySec=30s
Persistent=true
[Install]
WantedBy=timers.target

View 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

View 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.

View File

@ -0,0 +1 @@
3.0 (native)

View 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

View File

@ -0,0 +1,11 @@
[Unit]
Description=Saikyo Antivirus Agent timer
[Timer]
OnBootSec=2min
OnUnitActiveSec=5min
AccuracySec=30s
Persistent=true
[Install]
WantedBy=timers.target

View File

@ -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

View 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

View 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

View File

@ -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-----

View File

@ -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-----

View File

@ -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-----

View File

@ -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-----

View File

@ -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-----

View File

@ -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

View 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

View File

@ -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