veilor-os v0.1 scaffold — kickstart + hardening + 3-mode power + DuckSans-ready KDE black theme
This commit is contained in:
commit
1822005df1
37 changed files with 1733 additions and 0 deletions
13
.gitignore
vendored
Normal file
13
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
build/out/
|
||||
build/cache/
|
||||
*.iso
|
||||
*.img
|
||||
*.log
|
||||
*.pp
|
||||
*.mod
|
||||
.DS_Store
|
||||
.idea/
|
||||
.vscode/
|
||||
secrets/
|
||||
*.key
|
||||
*.pem
|
||||
21
LICENSE
Normal file
21
LICENSE
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2026 veilor-os contributors
|
||||
|
||||
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.
|
||||
49
README.md
Normal file
49
README.md
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
# veilor-os
|
||||
|
||||
> Hardened minimal Fedora KDE remix. Black-on-black. Locked down by default.
|
||||
|
||||
veilor-os is a Fedora 43 KDE spin built for operators who want a clean, fast,
|
||||
opinionated desktop with serious hardening already in place. No prompts at
|
||||
install beyond the LUKS passphrase. Boot, set admin password, work.
|
||||
|
||||
## Highlights
|
||||
|
||||
- **Single-prompt install** — only LUKS passphrase. No account wizard, no
|
||||
initial-setup screen. `admin` account is created automatically; password
|
||||
is set on first boot.
|
||||
- **Hardened by default** — SELinux enforcing, USBGuard, fail2ban, firewalld
|
||||
drop zone, kernel sysctl lockdown, NTS-authenticated NTP, DNS-over-TLS.
|
||||
- **3-mode power management** — `veilor-power save | mid | perf`, with
|
||||
AC/battery auto-switching via udev. Backed by tuned profiles.
|
||||
- **DuckSans system font** — variable font, single binary, low cache
|
||||
footprint.
|
||||
- **Pure-black KDE color scheme** — `veilor-black` theme system-wide.
|
||||
- **LUKS2 + Secure Boot** — argon2id, aes-xts, btrfs subvolumes, zram swap
|
||||
(no disk swap, no cold-boot leak).
|
||||
- **Reproducible build** — kickstart + podman + livemedia-creator. ISO
|
||||
output is deterministic given pinned base.
|
||||
|
||||
## Repo layout
|
||||
|
||||
```
|
||||
kickstart/ veilor-os.ks full kickstart definition
|
||||
build/ Containerfile + build-iso.sh reproducible ISO builder
|
||||
overlay/ files dropped into installed root via %post
|
||||
scripts/ hardening, SELinux policy, theme apply, firstboot
|
||||
assets/ fonts, KDE color scheme, branding, plymouth theme
|
||||
docs/ HARDENING / POWER / BUILD / INSTALL
|
||||
test/ boot-checklist + findings log
|
||||
```
|
||||
|
||||
See `docs/BUILD.md` for build instructions, `docs/INSTALL.md` for install,
|
||||
`docs/HARDENING.md` for what's locked down and why.
|
||||
|
||||
## Status
|
||||
|
||||
Pre-release. v0.x. Repo private until first green ISO boots clean on test
|
||||
hardware.
|
||||
|
||||
## License
|
||||
|
||||
MIT — see [LICENSE](LICENSE). DuckSans font ships under its own license; see
|
||||
`assets/fonts/ducksans/README.md`.
|
||||
40
assets/fonts/ducksans/README.md
Normal file
40
assets/fonts/ducksans/README.md
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
# DuckSans
|
||||
|
||||
DuckSans is a variable font commissioned by DuckDuckGo from Fontwerk
|
||||
(designer: Christoph Koeberlin, based on the Pangea typeface, 2026).
|
||||
|
||||
## Why this font
|
||||
|
||||
- **Variable font** — single binary covers full weight + width axis.
|
||||
Smaller font cache, less I/O, fewer files for fontconfig to scan.
|
||||
- **Designed for text-heavy UIs** — high readability, good hinting.
|
||||
- **Recognizable but unbranded look** — distinctive without being kitsch.
|
||||
|
||||
## Vendor instructions
|
||||
|
||||
Drop the font files here:
|
||||
|
||||
```
|
||||
assets/fonts/ducksans/DuckSans-VF.ttf
|
||||
```
|
||||
|
||||
(plus optional italic axis if shipped separately)
|
||||
|
||||
The build pipeline copies this directory to
|
||||
`/usr/share/fonts/ducksans/` in the installed system and runs
|
||||
`fc-cache -f`.
|
||||
|
||||
## License
|
||||
|
||||
DuckSans license terms TBD (Fontwerk commercial license vs SIL OFL).
|
||||
**Do not commit the .ttf to a public repo until license is verified.**
|
||||
|
||||
If license forbids redistribution, the kickstart `%post` should fetch
|
||||
the font from an authenticated source at build time. See
|
||||
`build/build-iso.sh` for the pull point.
|
||||
|
||||
## Fallback
|
||||
|
||||
If DuckSans is not present, fontconfig falls through to the system
|
||||
default sans-serif. veilor-os will still install and run; the system
|
||||
font will just not be DuckSans.
|
||||
105
assets/kde/veilor-black.colors
Normal file
105
assets/kde/veilor-black.colors
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
[ColorEffects:Disabled]
|
||||
Color=0,0,0
|
||||
ColorAmount=0
|
||||
ColorEffect=0
|
||||
ContrastAmount=0.65
|
||||
ContrastEffect=1
|
||||
IntensityAmount=0.1
|
||||
IntensityEffect=2
|
||||
|
||||
[ColorEffects:Inactive]
|
||||
ChangeSelectionColor=true
|
||||
Color=112,111,110
|
||||
ColorAmount=0.025
|
||||
ColorEffect=2
|
||||
ContrastAmount=0.1
|
||||
ContrastEffect=2
|
||||
Enable=false
|
||||
IntensityAmount=0
|
||||
IntensityEffect=0
|
||||
|
||||
[Colors:Button]
|
||||
BackgroundAlternate=20,20,20
|
||||
BackgroundNormal=10,10,10
|
||||
DecorationFocus=255,255,255
|
||||
DecorationHover=200,200,200
|
||||
ForegroundActive=255,255,255
|
||||
ForegroundInactive=140,140,140
|
||||
ForegroundLink=200,200,255
|
||||
ForegroundNegative=237,21,21
|
||||
ForegroundNeutral=176,128,0
|
||||
ForegroundNormal=232,232,232
|
||||
ForegroundPositive=128,210,128
|
||||
ForegroundVisited=180,180,220
|
||||
|
||||
[Colors:Selection]
|
||||
BackgroundAlternate=40,40,40
|
||||
BackgroundNormal=60,60,60
|
||||
DecorationFocus=255,255,255
|
||||
DecorationHover=200,200,200
|
||||
ForegroundActive=255,255,255
|
||||
ForegroundInactive=180,180,180
|
||||
ForegroundLink=200,200,255
|
||||
ForegroundNegative=237,21,21
|
||||
ForegroundNeutral=176,128,0
|
||||
ForegroundNormal=255,255,255
|
||||
ForegroundPositive=128,210,128
|
||||
ForegroundVisited=180,180,220
|
||||
|
||||
[Colors:Tooltip]
|
||||
BackgroundAlternate=20,20,20
|
||||
BackgroundNormal=8,8,8
|
||||
DecorationFocus=255,255,255
|
||||
DecorationHover=200,200,200
|
||||
ForegroundActive=255,255,255
|
||||
ForegroundInactive=140,140,140
|
||||
ForegroundLink=200,200,255
|
||||
ForegroundNegative=237,21,21
|
||||
ForegroundNeutral=176,128,0
|
||||
ForegroundNormal=232,232,232
|
||||
ForegroundPositive=128,210,128
|
||||
ForegroundVisited=180,180,220
|
||||
|
||||
[Colors:View]
|
||||
BackgroundAlternate=10,10,10
|
||||
BackgroundNormal=0,0,0
|
||||
DecorationFocus=255,255,255
|
||||
DecorationHover=200,200,200
|
||||
ForegroundActive=255,255,255
|
||||
ForegroundInactive=140,140,140
|
||||
ForegroundLink=200,200,255
|
||||
ForegroundNegative=237,21,21
|
||||
ForegroundNeutral=176,128,0
|
||||
ForegroundNormal=232,232,232
|
||||
ForegroundPositive=128,210,128
|
||||
ForegroundVisited=180,180,220
|
||||
|
||||
[Colors:Window]
|
||||
BackgroundAlternate=8,8,8
|
||||
BackgroundNormal=0,0,0
|
||||
DecorationFocus=255,255,255
|
||||
DecorationHover=200,200,200
|
||||
ForegroundActive=255,255,255
|
||||
ForegroundInactive=140,140,140
|
||||
ForegroundLink=200,200,255
|
||||
ForegroundNegative=237,21,21
|
||||
ForegroundNeutral=176,128,0
|
||||
ForegroundNormal=232,232,232
|
||||
ForegroundPositive=128,210,128
|
||||
ForegroundVisited=180,180,220
|
||||
|
||||
[General]
|
||||
ColorScheme=veilor-black
|
||||
Name=veilor-black
|
||||
shadeSortColumn=true
|
||||
|
||||
[KDE]
|
||||
contrast=4
|
||||
|
||||
[WM]
|
||||
activeBackground=0,0,0
|
||||
activeBlend=255,255,255
|
||||
activeForeground=255,255,255
|
||||
inactiveBackground=10,10,10
|
||||
inactiveBlend=180,180,180
|
||||
inactiveForeground=140,140,140
|
||||
16
assets/kde/veilor-default.kdeglobals
Normal file
16
assets/kde/veilor-default.kdeglobals
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
[General]
|
||||
ColorScheme=veilor-black
|
||||
Name=veilor-black
|
||||
font=DuckSans,11,-1,5,400,0,0,0,0,0,0,0,0,0,0,1
|
||||
fixed=DuckSans Mono,10,-1,5,400,0,0,0,0,0,0,0,0,0,0,1
|
||||
menuFont=DuckSans,11,-1,5,400,0,0,0,0,0,0,0,0,0,0,1
|
||||
smallestReadableFont=DuckSans,9,-1,5,400,0,0,0,0,0,0,0,0,0,0,1
|
||||
toolBarFont=DuckSans,10,-1,5,400,0,0,0,0,0,0,0,0,0,0,1
|
||||
|
||||
[Icons]
|
||||
Theme=breeze-dark
|
||||
|
||||
[KDE]
|
||||
LookAndFeelPackage=org.kde.breezedark.desktop
|
||||
SingleClick=false
|
||||
contrast=4
|
||||
22
build/Containerfile
Normal file
22
build/Containerfile
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
FROM registry.fedoraproject.org/fedora:43
|
||||
|
||||
LABEL org.opencontainers.image.title="veilor-os build env"
|
||||
LABEL org.opencontainers.image.source="https://github.com/veilor-uk/veilor-os"
|
||||
|
||||
RUN dnf install -y \
|
||||
lorax \
|
||||
livecd-tools \
|
||||
pykickstart \
|
||||
anaconda-tui \
|
||||
squashfs-tools \
|
||||
xorriso \
|
||||
genisoimage \
|
||||
syslinux \
|
||||
rsync \
|
||||
git \
|
||||
which \
|
||||
&& dnf clean all
|
||||
|
||||
WORKDIR /work
|
||||
|
||||
ENTRYPOINT ["/bin/bash"]
|
||||
54
build/build-iso.sh
Executable file
54
build/build-iso.sh
Executable file
|
|
@ -0,0 +1,54 @@
|
|||
#!/usr/bin/env bash
|
||||
# veilor-os — ISO builder
|
||||
# Wraps livemedia-creator inside a podman container for reproducibility.
|
||||
# Run from repo root.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
OUT_DIR="$REPO_ROOT/build/out"
|
||||
KS="$REPO_ROOT/kickstart/veilor-os.ks"
|
||||
RELEASEVER="${RELEASEVER:-43}"
|
||||
DATE="$(date +%Y%m%d)"
|
||||
ISO_NAME="veilor-os-${RELEASEVER}-${DATE}.iso"
|
||||
|
||||
mkdir -p "$OUT_DIR"
|
||||
|
||||
# ── Validate kickstart ──
|
||||
if command -v ksvalidator &>/dev/null; then
|
||||
ksvalidator "$KS"
|
||||
fi
|
||||
|
||||
# ── Build container ──
|
||||
podman build -t veilor-build:latest "$REPO_ROOT/build"
|
||||
|
||||
# ── Build ISO ──
|
||||
# --make-iso requires --privileged (loop devices, mount).
|
||||
podman run --rm --privileged \
|
||||
-v "$REPO_ROOT:/work:Z" \
|
||||
-v "$OUT_DIR:/out:Z" \
|
||||
veilor-build:latest -c "
|
||||
set -e
|
||||
livemedia-creator \
|
||||
--make-iso \
|
||||
--no-virt \
|
||||
--ks /work/kickstart/veilor-os.ks \
|
||||
--resultdir /out/build-${DATE} \
|
||||
--project veilor-os \
|
||||
--releasever ${RELEASEVER} \
|
||||
--title 'veilor-os' \
|
||||
--tmp /tmp/lmc \
|
||||
--logfile /out/build-${DATE}.log
|
||||
cp /out/build-${DATE}/*.iso /out/${ISO_NAME}
|
||||
sha256sum /out/${ISO_NAME} > /out/${ISO_NAME}.sha256
|
||||
"
|
||||
|
||||
echo
|
||||
echo "════════════════════════════════════════════════════════"
|
||||
echo " ISO ready: $OUT_DIR/$ISO_NAME"
|
||||
echo " Checksum: $OUT_DIR/$ISO_NAME.sha256"
|
||||
echo " Build log: $OUT_DIR/build-${DATE}.log"
|
||||
echo "════════════════════════════════════════════════════════"
|
||||
echo
|
||||
echo " Write to USB: sudo dd if=$OUT_DIR/$ISO_NAME of=/dev/sdX bs=4M status=progress conv=fsync"
|
||||
echo " (replace /dev/sdX with your USB device — use lsblk to identify)"
|
||||
76
docs/BUILD.md
Normal file
76
docs/BUILD.md
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
# Building veilor-os
|
||||
|
||||
## Requirements
|
||||
|
||||
- **Host:** Fedora 43+ or RHEL/CentOS 9+ (anything with podman + KVM bits)
|
||||
- **podman** with rootless or rootful — privileged mode required
|
||||
- **Disk:** ~15GB free for build cache + ISO
|
||||
- **Network:** internet (pulls Fedora repos, base container)
|
||||
|
||||
## One-shot build
|
||||
|
||||
From repo root:
|
||||
|
||||
```bash
|
||||
./build/build-iso.sh
|
||||
```
|
||||
|
||||
Output: `build/out/veilor-os-43-YYYYMMDD.iso` and `.sha256`.
|
||||
|
||||
## What the build does
|
||||
|
||||
1. `ksvalidator` checks `kickstart/veilor-os.ks` syntax.
|
||||
2. Builds `veilor-build:latest` container from `build/Containerfile`
|
||||
(Fedora 43 base + lorax + livemedia-creator + pykickstart).
|
||||
3. Runs `livemedia-creator --make-iso --no-virt` inside the container
|
||||
with `--privileged` (loop devices and chroot mounts required).
|
||||
4. Anaconda runs the kickstart in a tmpfs root, packages are pulled,
|
||||
`%post` executes (hardening + theme + branding), root is squashed
|
||||
into a Live ISO.
|
||||
5. ISO + sha256 + build log dropped in `build/out/`.
|
||||
|
||||
## Custom builds
|
||||
|
||||
Environment variables:
|
||||
|
||||
```bash
|
||||
RELEASEVER=43 ./build/build-iso.sh # default
|
||||
RELEASEVER=44 ./build/build-iso.sh # rebase to Fedora 44 when released
|
||||
```
|
||||
|
||||
Edit `kickstart/veilor-os.ks` to:
|
||||
|
||||
- Change locale / timezone (`lang`, `keyboard`, `timezone` lines)
|
||||
- Add/remove packages (`%packages` section)
|
||||
- Adjust LUKS parameters (`part pv.veilor` line)
|
||||
|
||||
## Writing to USB
|
||||
|
||||
```bash
|
||||
sudo dd if=build/out/veilor-os-43-YYYYMMDD.iso of=/dev/sdX bs=4M status=progress conv=fsync
|
||||
sync
|
||||
```
|
||||
|
||||
Replace `/dev/sdX` with your USB device. **Triple-check** with `lsblk`
|
||||
before running — `dd` will overwrite without warning.
|
||||
|
||||
Ventoy is **not** supported for hardened-install ISOs because Anaconda
|
||||
expects to find the kickstart at the ISO root. Use `dd` directly.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
- **`livemedia-creator` fails inside container:** ensure `--privileged`
|
||||
is set (the script already passes it). On hosts with strict SELinux,
|
||||
set `setsebool -P container_manage_cgroup on` once.
|
||||
- **Packages not found:** the Fedora mirror may have moved. Update
|
||||
`url --mirrorlist=` in the kickstart.
|
||||
- **Kickstart syntax errors:** run `ksvalidator kickstart/veilor-os.ks`
|
||||
directly. Errors point to a line number in the .ks file.
|
||||
- **Build hangs at "Setting up Install Process":** Fedora mirror
|
||||
timeouts. Pin a specific mirror with `url --url=https://...`.
|
||||
|
||||
## Reproducibility
|
||||
|
||||
The same kickstart + same Fedora release version + same overlay tree
|
||||
should produce ISOs with identical package sets. Bit-for-bit identical
|
||||
ISOs require pinning Fedora compose IDs (planned for v1).
|
||||
128
docs/HARDENING.md
Normal file
128
docs/HARDENING.md
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
# Hardening Reference
|
||||
|
||||
What veilor-os locks down and why. Each item is applied by either the
|
||||
kickstart `%post` or the overlay tree shipped in `/etc`.
|
||||
|
||||
## Boot chain
|
||||
|
||||
| Item | State | Source |
|
||||
|------|-------|--------|
|
||||
| Secure Boot | Required (bootloader signed) | `bootloader` kickstart line |
|
||||
| Kernel lockdown | `lockdown=integrity` | bootloader kernel args |
|
||||
| Slab hardening | `slab_nomerge`, `init_on_alloc=1`, `init_on_free=1` | bootloader |
|
||||
| Stack offset | `randomize_kstack_offset=on` | bootloader |
|
||||
| vsyscall | `vsyscall=none` | bootloader |
|
||||
| LUKS2 | aes-xts-plain64 / argon2id, mem=1GB, time=9 | `part pv.veilor` |
|
||||
| Module loading | Locked 30s after graphical boot | `veilor-modules-lock.service` |
|
||||
|
||||
## Kernel sysctl
|
||||
|
||||
`/etc/sysctl.d/99-veilor-hardening.conf`:
|
||||
|
||||
| Key | Value | Why |
|
||||
|-----|-------|-----|
|
||||
| `kernel.kptr_restrict` | 2 | hide kernel pointers from /proc |
|
||||
| `kernel.dmesg_restrict` | 1 | dmesg root-only |
|
||||
| `kernel.yama.ptrace_scope` | 2 | ptrace = parent only |
|
||||
| `kernel.perf_event_paranoid` | 3 | unprivileged perf disabled |
|
||||
| `net.core.bpf_jit_harden` | 2 | BPF JIT constant blinding |
|
||||
| `kernel.randomize_va_space` | 2 | full ASLR |
|
||||
| `fs.suid_dumpable` | 0 | no SUID core dumps |
|
||||
| `dev.tty.ldisc_autoload` | 0 | block tty LPE vector |
|
||||
| `net.ipv4.tcp_syncookies` | 1 | SYN flood mitigation |
|
||||
| `net.ipv4.conf.all.rp_filter` | 1 | reverse-path filter |
|
||||
| `accept_source_route` | 0 (v4+v6) | ignore source routing |
|
||||
| `accept_redirects` | 0 (v4+v6) | ignore ICMP redirects |
|
||||
|
||||
## SELinux
|
||||
|
||||
- Enforcing, targeted policy.
|
||||
- Custom module `veilor-systemd` grants `systemd_modules_load_t` the
|
||||
`sys_admin` and `perfmon` capabilities required by the modules-lock
|
||||
service. Source: `scripts/selinux/veilor-systemd.te`.
|
||||
|
||||
## Network surface
|
||||
|
||||
- **firewalld** default zone = `drop`.
|
||||
- **Inbound:** ssh only.
|
||||
- **systemd-resolved:** LLMNR off, DNSSEC `allow-downgrade`,
|
||||
DNS-over-TLS opportunistic. Resolvers: Cloudflare (1.1.1.1, 1.0.0.1),
|
||||
fallback Quad9 (9.9.9.9, 149.112.112.112).
|
||||
- **chrony:** NTS-authenticated time from `time.cloudflare.com` and
|
||||
`nts.sth1/2.ntp.se`. Pool fallback only.
|
||||
|
||||
## SSH
|
||||
|
||||
`/etc/ssh/sshd_config.d/10-veilor-hardening.conf`:
|
||||
|
||||
- `PasswordAuthentication no`
|
||||
- `PermitRootLogin no`
|
||||
- `AllowUsers admin`
|
||||
- `X11Forwarding no`
|
||||
- `MaxAuthTries 3`
|
||||
- `ClientAliveInterval 300`
|
||||
- `LogLevel VERBOSE`
|
||||
|
||||
## Auth / accounts
|
||||
|
||||
- Root account **locked** (`passwd -l root`). No interactive root login.
|
||||
- Single `admin` user, `wheel` group, full sudo.
|
||||
- `pwquality.conf`: minlen=14, 4 character classes required, dictcheck.
|
||||
- **First-boot password flow:** `chage -d 0 admin` expires the empty
|
||||
password immediately. `veilor-firstboot.service` runs on TTY1 before
|
||||
SDDM, prompts for new password, then starts the graphical session.
|
||||
|
||||
## Audit
|
||||
|
||||
`/etc/audit/rules.d/99-veilor-hardening.rules` watches:
|
||||
|
||||
- `/etc/passwd`, `/etc/shadow`, `/etc/group`, `/etc/gshadow`
|
||||
- `/etc/sudoers`, `/etc/sudoers.d/`
|
||||
- `/etc/ssh/sshd_config*`, `/etc/selinux/`, `/etc/firewalld/`
|
||||
- `/etc/cron.*`, `/var/spool/cron/`
|
||||
- `/etc/sysctl.*`, `/etc/systemd/system/`, `/usr/lib/systemd/system/`
|
||||
- All privileged binaries (sudo, su, passwd, mount, pkexec, etc.)
|
||||
- Kernel module load/unload syscalls
|
||||
- Permission/ownership changes by uid≥1000
|
||||
|
||||
## Intrusion detection
|
||||
|
||||
`fail2ban` jails:
|
||||
|
||||
- `sshd` — aggressive mode, 3 retries, 24h ban
|
||||
- `pam-generic` — 5 retries, 1h ban (catches XDM, su, sudo failures)
|
||||
|
||||
Backend: systemd journal. Action: firewalld rich rules.
|
||||
|
||||
## USB
|
||||
|
||||
`USBGuard` daemon, `ImplicitPolicyTarget=block`.
|
||||
|
||||
Ships with **empty allowlist**. On first boot, admin runs:
|
||||
|
||||
```bash
|
||||
sudo usbguard generate-policy > /etc/usbguard/rules.conf
|
||||
sudo systemctl restart usbguard
|
||||
```
|
||||
|
||||
This snapshots all currently-connected devices into the allowlist.
|
||||
Anything plugged in afterward is blocked unless explicitly allowed:
|
||||
|
||||
```bash
|
||||
sudo usbguard list-devices
|
||||
sudo usbguard allow-device <id>
|
||||
```
|
||||
|
||||
## Disabled services
|
||||
|
||||
`abrt*`, `cups`, `cups-browsed`, `geoclue`, `avahi-daemon`,
|
||||
`bluetooth`, `ModemManager`, `gssproxy`, `atd`, `pcscd.socket`,
|
||||
`pcscd.service`, `kdeconnectd` (removed at package level).
|
||||
|
||||
## What's *not* enabled by default
|
||||
|
||||
- **Disk swap** — replaced by zram (RAM-only, no key leak risk).
|
||||
- **Bluetooth** — disabled. Enable with `systemctl enable --now bluetooth`.
|
||||
- **Printing** — CUPS removed. Reinstall if needed: `dnf install cups`.
|
||||
- **Snapd, Flatpak** — not installed (Flatpak optional add-on).
|
||||
- **PackageKit** — removed; updates manual via `dnf`.
|
||||
106
docs/INSTALL.md
Normal file
106
docs/INSTALL.md
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
# Installing veilor-os
|
||||
|
||||
## What you need
|
||||
|
||||
- USB drive (8GB+) flashed with the veilor-os ISO
|
||||
- Target machine with UEFI (BIOS legacy works but Secure Boot is the
|
||||
whole point — use UEFI)
|
||||
- ~30GB free disk
|
||||
|
||||
## Install flow
|
||||
|
||||
The installer is **fully scripted**. The only thing it asks you for
|
||||
is the **LUKS passphrase**.
|
||||
|
||||
1. Boot from USB.
|
||||
2. Pick "Install veilor-os" from the boot menu.
|
||||
3. Anaconda runs the kickstart automatically.
|
||||
4. When prompted, **set a strong LUKS passphrase**. This is the only
|
||||
prompt. Choose well — losing it = losing the disk.
|
||||
5. Wait. Install + `%post` hardening takes ~10–15 min depending on
|
||||
network speed.
|
||||
6. Reboot. Pull out the USB.
|
||||
|
||||
## First boot
|
||||
|
||||
1. **LUKS prompt** — enter your passphrase to unlock the disk.
|
||||
2. **TTY1 banner appears:**
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────┐
|
||||
│ veilor-os │
|
||||
│ first boot — admin password │
|
||||
└──────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
3. Type a password for the local admin account. Must meet:
|
||||
- ≥ 14 characters
|
||||
- 1 digit, 1 upper, 1 lower, 1 special
|
||||
4. Once accepted, SDDM starts.
|
||||
5. Log in as `admin` with the password you just set.
|
||||
6. Shell prompt: `admin@veilor-os`.
|
||||
|
||||
## Post-install hygiene
|
||||
|
||||
### Set USBGuard allowlist
|
||||
|
||||
USBGuard ships with an empty allowlist — every USB device you plug in
|
||||
will be blocked until you whitelist your trusted set.
|
||||
|
||||
Plug in everything you trust (keyboard, mouse, dock, yubikey, etc.),
|
||||
then run:
|
||||
|
||||
```bash
|
||||
sudo usbguard generate-policy > /etc/usbguard/rules.conf
|
||||
sudo systemctl restart usbguard
|
||||
```
|
||||
|
||||
To allow a new device after that:
|
||||
|
||||
```bash
|
||||
sudo usbguard list-devices
|
||||
sudo usbguard allow-device <id>
|
||||
```
|
||||
|
||||
### Verify hardening
|
||||
|
||||
```bash
|
||||
getenforce # Enforcing
|
||||
mokutil --sb-state # SecureBoot enabled
|
||||
sysctl kernel.yama.ptrace_scope # = 2
|
||||
sysctl fs.suid_dumpable # = 0
|
||||
firewall-cmd --get-default-zone # drop
|
||||
fail2ban-client status sshd # active, jail loaded
|
||||
veilor-power status # current profile + governor
|
||||
```
|
||||
|
||||
### Check `/etc/os-release`
|
||||
|
||||
```bash
|
||||
cat /etc/os-release
|
||||
# NAME="veilor-os"
|
||||
# PRETTY_NAME="veilor-os 0.1 (Fedora 43 base)"
|
||||
# ID=veilor
|
||||
# ID_LIKE=fedora
|
||||
```
|
||||
|
||||
### Add additional users
|
||||
|
||||
The kickstart only creates `admin`. Add more users from there:
|
||||
|
||||
```bash
|
||||
sudo useradd -m -s /bin/bash <name>
|
||||
sudo passwd <name>
|
||||
```
|
||||
|
||||
Don't add anyone to `wheel` unless they need root.
|
||||
|
||||
## Known caveats
|
||||
|
||||
- **Bluetooth disabled by default** — `sudo systemctl enable --now bluetooth`
|
||||
if you need it.
|
||||
- **Printing disabled** — CUPS removed; `sudo dnf install cups cups-browsed`
|
||||
if you need a printer.
|
||||
- **No PackageKit** — updates manual via `sudo dnf upgrade`. Run weekly.
|
||||
- **Battery cap at 80%** — udev rule. Edit
|
||||
`/etc/udev/rules.d/91-veilor-battery-threshold.rules` to change.
|
||||
71
docs/POWER.md
Normal file
71
docs/POWER.md
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
# Power Management
|
||||
|
||||
veilor-os ships a 3-mode power profile system backed by `tuned`.
|
||||
|
||||
## Profiles
|
||||
|
||||
| Profile | Governor | EPP | Boost | ASUS TTP | Use |
|
||||
|---------|----------|-----|-------|----------|-----|
|
||||
| `veilor-powersave` | powersave | power | off | 2 (silent) | max battery |
|
||||
| `veilor-balanced` | powersave | balance_performance | on | 1 (mid) | on the go |
|
||||
| `veilor-performance` | performance | performance | on | 0 (full) | plugged in |
|
||||
|
||||
`ASUS TTP` (throttle_thermal_policy) only applies to ASUS laptops with
|
||||
`asus-nb-wmi`. On other hardware those writes are silently skipped.
|
||||
|
||||
## Switching
|
||||
|
||||
```bash
|
||||
veilor-power save # max battery (aliases: powersave, s)
|
||||
veilor-power mid # balanced (aliases: balanced, b)
|
||||
veilor-power perf # performance (aliases: performance, p)
|
||||
veilor-power # status: profile, governor, EPP, boost, freq
|
||||
```
|
||||
|
||||
`veilor-power` calls `tuned-adm` via a NOPASSWD sudoers drop-in
|
||||
locked to `veilor-*` profiles only (`/etc/sudoers.d/veilor-power`).
|
||||
|
||||
## Auto-switch on AC plug/unplug
|
||||
|
||||
`/etc/udev/rules.d/90-veilor-ac-switch.rules`:
|
||||
|
||||
```
|
||||
SUBSYSTEM=="power_supply", ATTR{online}=="0", RUN+="/usr/bin/tuned-adm profile veilor-powersave"
|
||||
SUBSYSTEM=="power_supply", ATTR{online}=="1", RUN+="/usr/bin/tuned-adm profile veilor-performance"
|
||||
```
|
||||
|
||||
Override anytime with `veilor-power mid`.
|
||||
|
||||
## Battery longevity
|
||||
|
||||
`/etc/udev/rules.d/91-veilor-battery-threshold.rules` caps charge at
|
||||
80% on supported hardware. Adjust by editing the rule or:
|
||||
|
||||
```bash
|
||||
echo 100 | sudo tee /sys/class/power_supply/BAT0/charge_control_end_threshold
|
||||
```
|
||||
|
||||
## What each profile actually does
|
||||
|
||||
`/etc/tuned/profiles/veilor-<profile>/script.sh` writes:
|
||||
|
||||
- `/sys/devices/system/cpu/cpufreq/boost`
|
||||
- `/sys/devices/platform/asus-nb-wmi/throttle_thermal_policy` (ASUS only)
|
||||
- `/sys/bus/pci/devices/*/power/control` (NVMe autosuspend)
|
||||
- `/sys/class/drm/card*/device/power_dpm_force_performance_level` (AMD iGPU)
|
||||
- `usb_autosuspend` enable/disable
|
||||
|
||||
All writes are guarded with `[ -w ... ]` so non-applicable hardware
|
||||
silently no-ops.
|
||||
|
||||
## Persistence
|
||||
|
||||
`tuned.service` starts at boot and loads the last active profile from
|
||||
`/var/lib/tuned/save.conf`. No GRUB params needed.
|
||||
|
||||
## Caveat: `platform_profile` vs `throttle_thermal_policy`
|
||||
|
||||
On some ASUS laptops the `platform_profile` sysfs key maps to TTP in
|
||||
non-obvious order (e.g. `quiet`→TTP2, `balanced`→TTP0,
|
||||
`performance`→TTP1). veilor profiles write TTP directly and never
|
||||
touch `platform_profile` to avoid the second-write override race.
|
||||
163
kickstart/veilor-os.ks
Normal file
163
kickstart/veilor-os.ks
Normal file
|
|
@ -0,0 +1,163 @@
|
|||
#version=DEVEL
|
||||
# veilor-os kickstart — Fedora 43 KDE base, hardened, minimal.
|
||||
# Build with livemedia-creator inside build/Containerfile.
|
||||
|
||||
# ── Install source ──
|
||||
url --mirrorlist="https://mirrors.fedoraproject.org/mirrorlist?repo=fedora-$releasever&arch=$basearch"
|
||||
repo --name=updates --mirrorlist="https://mirrors.fedoraproject.org/mirrorlist?repo=updates-released-f$releasever&arch=$basearch"
|
||||
|
||||
# ── Locale / keyboard / time (template — adjust per build) ──
|
||||
keyboard --xlayouts='us'
|
||||
lang en_GB.UTF-8
|
||||
timezone Europe/London --utc
|
||||
|
||||
# ── Install mode ──
|
||||
text
|
||||
firstboot --disable
|
||||
eula --agreed
|
||||
selinux --enforcing
|
||||
services --enabled=sshd,fail2ban,usbguard,tuned,auditd,firewalld,chronyd,sddm,veilor-firstboot,veilor-modules-lock
|
||||
|
||||
# ── Network / hostname ──
|
||||
network --bootproto=dhcp --device=link --activate --hostname=veilor-os
|
||||
firewall --enabled --service=ssh
|
||||
|
||||
# ── Identity (zero-prompt; only LUKS passphrase asked at install) ──
|
||||
rootpw --lock
|
||||
user --name=admin --groups=wheel --gecos="veilor admin" --password="" --plaintext
|
||||
auth --useshadow --passalgo=sha512
|
||||
|
||||
# ── Bootloader: kernel hardening flags ──
|
||||
bootloader --location=mbr --append="lockdown=integrity slab_nomerge init_on_alloc=1 init_on_free=1 randomize_kstack_offset=on vsyscall=none"
|
||||
|
||||
# ── Disk: BIOS+UEFI, LUKS2, btrfs subvols, zram swap (no disk swap) ──
|
||||
zerombr
|
||||
clearpart --all --initlabel
|
||||
reqpart --add-boot
|
||||
part /boot --fstype=ext4 --size=1024 --asprimary
|
||||
part pv.veilor --size=1 --grow --encrypted --luks-version=luks2 \
|
||||
--pbkdf=argon2id --pbkdf-memory=1048576 --pbkdf-iterations=9 \
|
||||
--cipher=aes-xts-plain64 --hash=sha512
|
||||
volgroup veilor pv.veilor
|
||||
logvol / --vgname=veilor --name=root --fstype=btrfs --size=1 --grow \
|
||||
--mkfsoptions="--mixed"
|
||||
|
||||
# ── Packages ──
|
||||
%packages --excludedocs
|
||||
@^kde-desktop-environment
|
||||
@kde-apps
|
||||
@core
|
||||
@hardware-support
|
||||
@standard
|
||||
|
||||
# core hardening tools
|
||||
fail2ban
|
||||
fail2ban-firewalld
|
||||
usbguard
|
||||
usbguard-tools
|
||||
audit
|
||||
policycoreutils-python-utils
|
||||
tuned
|
||||
chrony
|
||||
firewalld
|
||||
plymouth
|
||||
|
||||
# admin essentials
|
||||
git
|
||||
vim-enhanced
|
||||
tmux
|
||||
htop
|
||||
podman
|
||||
skopeo
|
||||
NetworkManager
|
||||
NetworkManager-wifi
|
||||
|
||||
# fonts
|
||||
fontconfig
|
||||
freetype
|
||||
|
||||
# remove fluff
|
||||
-cups
|
||||
-cups-browsed
|
||||
-abrt*
|
||||
-snapd
|
||||
-geoclue2
|
||||
-avahi
|
||||
-avahi-libs
|
||||
-kde-connect
|
||||
-open-vm-tools-desktop
|
||||
-PackageKit
|
||||
-PackageKit-command-not-found
|
||||
-mlocate
|
||||
-ModemManager
|
||||
-pcsc-lite
|
||||
-rsync-daemon
|
||||
|
||||
%end
|
||||
|
||||
# ── Post-install (nochroot): copy overlay tree into installed root ──
|
||||
%post --nochroot
|
||||
set -eu
|
||||
SRC=/run/install/repo/veilor
|
||||
DEST=/mnt/sysimage
|
||||
if [[ -d $SRC/overlay ]]; then
|
||||
cp -a $SRC/overlay/. $DEST/
|
||||
fi
|
||||
mkdir -p $DEST/usr/share/veilor-os
|
||||
cp -a $SRC/assets $DEST/usr/share/veilor-os/
|
||||
cp -a $SRC/scripts $DEST/usr/share/veilor-os/
|
||||
%end
|
||||
|
||||
# ── Post-install (chroot): apply hardening, theme, branding ──
|
||||
%post
|
||||
set -uo pipefail
|
||||
exec > >(tee -a /var/log/veilor-install.log) 2>&1
|
||||
|
||||
echo "════════════════════════════════════════════════════════"
|
||||
echo " veilor-os install — %post"
|
||||
echo "════════════════════════════════════════════════════════"
|
||||
|
||||
REPO=/usr/share/veilor-os
|
||||
chmod +x $REPO/scripts/*.sh $REPO/scripts/selinux/*.sh /usr/local/bin/veilor-power /usr/local/sbin/veilor-firstboot
|
||||
|
||||
# Apply hardening
|
||||
bash $REPO/scripts/10-harden-base.sh
|
||||
bash $REPO/scripts/20-harden-kernel.sh
|
||||
|
||||
# Build SELinux module
|
||||
bash $REPO/scripts/selinux/build-policy.sh || echo "[WARN] SELinux build failed; load on first boot"
|
||||
|
||||
# Apply KDE theme + DuckSans + os-release branding
|
||||
bash $REPO/scripts/kde-theme-apply.sh
|
||||
|
||||
# Force admin password set on first boot (chage expires immediately)
|
||||
chage -d 0 admin
|
||||
|
||||
# zram swap (no disk swap; keys never leak to platter)
|
||||
dnf install -y zram-generator || true
|
||||
cat > /etc/systemd/zram-generator.conf << 'EOF'
|
||||
[zram0]
|
||||
zram-size = min(ram, 8192)
|
||||
compression-algorithm = zstd
|
||||
EOF
|
||||
|
||||
# Enable services
|
||||
systemctl enable veilor-firstboot.service
|
||||
systemctl enable veilor-modules-lock.service
|
||||
systemctl enable sshd fail2ban usbguard tuned auditd firewalld chronyd
|
||||
|
||||
# Default tuned profile = balanced (AC/battery udev rule will override)
|
||||
tuned-adm profile veilor-balanced 2>/dev/null || true
|
||||
|
||||
# Lock root explicitly (kickstart --lock should already do this)
|
||||
passwd -l root
|
||||
|
||||
# Sanity: zero references to onyx / personal IPs in installed system
|
||||
if grep -rqi 'onyx\|192\.168\.0\.\|fedora\.local' /etc/veilor* /etc/tuned/profiles/veilor-* 2>/dev/null; then
|
||||
echo "[ERR] brand leak detected in /etc — investigate"
|
||||
fi
|
||||
|
||||
echo "════════════════════════════════════════════════════════"
|
||||
echo " veilor-os install complete"
|
||||
echo "════════════════════════════════════════════════════════"
|
||||
%end
|
||||
11
overlay/etc/os-release.d/veilor
Normal file
11
overlay/etc/os-release.d/veilor
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
NAME="veilor-os"
|
||||
PRETTY_NAME="veilor-os 0.1 (Fedora 43 base)"
|
||||
ID=veilor
|
||||
ID_LIKE=fedora
|
||||
VERSION="0.1"
|
||||
VERSION_ID="0.1"
|
||||
HOME_URL="https://github.com/veilor-uk/veilor-os"
|
||||
DOCUMENTATION_URL="https://github.com/veilor-uk/veilor-os/tree/main/docs"
|
||||
BUG_REPORT_URL="https://github.com/veilor-uk/veilor-os/issues"
|
||||
ANSI_COLOR="0;30;47"
|
||||
LOGO=veilor-logo
|
||||
15
overlay/etc/sddm.conf.d/veilor.conf
Normal file
15
overlay/etc/sddm.conf.d/veilor.conf
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
[Theme]
|
||||
Current=breeze
|
||||
CursorTheme=breeze_cursors
|
||||
|
||||
[Users]
|
||||
HideUsers=root
|
||||
HideShells=/sbin/nologin,/bin/false
|
||||
MaximumUid=60000
|
||||
MinimumUid=1000
|
||||
|
||||
[General]
|
||||
Numlock=on
|
||||
|
||||
[Autologin]
|
||||
Relogin=false
|
||||
16
overlay/etc/ssh/sshd_config.d/10-veilor-hardening.conf
Normal file
16
overlay/etc/ssh/sshd_config.d/10-veilor-hardening.conf
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
# veilor-os — sshd hardening drop-in
|
||||
# Loaded last (10-* prefix sorts after distro 50-*)
|
||||
X11Forwarding no
|
||||
AllowUsers admin
|
||||
PasswordAuthentication no
|
||||
PermitRootLogin no
|
||||
PermitEmptyPasswords no
|
||||
ChallengeResponseAuthentication no
|
||||
KbdInteractiveAuthentication no
|
||||
UsePAM yes
|
||||
ClientAliveInterval 300
|
||||
ClientAliveCountMax 2
|
||||
LoginGraceTime 30
|
||||
MaxAuthTries 3
|
||||
MaxSessions 4
|
||||
LogLevel VERBOSE
|
||||
3
overlay/etc/sudoers.d/veilor-power
Normal file
3
overlay/etc/sudoers.d/veilor-power
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# veilor-power — allow wheel to switch tuned profiles without password
|
||||
# Locked to veilor-* profiles only.
|
||||
%wheel ALL=(root) NOPASSWD: /usr/bin/tuned-adm profile veilor-powersave, /usr/bin/tuned-adm profile veilor-balanced, /usr/bin/tuned-adm profile veilor-performance
|
||||
25
overlay/etc/sysctl.d/99-veilor-hardening.conf
Normal file
25
overlay/etc/sysctl.d/99-veilor-hardening.conf
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
# veilor-os — kernel sysctl hardening
|
||||
# Mirrors scripts/20-harden-kernel.sh; ships in overlay so values present
|
||||
# at first boot before scripts run.
|
||||
|
||||
kernel.kptr_restrict = 2
|
||||
kernel.dmesg_restrict = 1
|
||||
net.core.bpf_jit_harden = 2
|
||||
kernel.perf_event_paranoid = 3
|
||||
kernel.yama.ptrace_scope = 2
|
||||
kernel.randomize_va_space = 2
|
||||
kernel.modules_disabled = 0
|
||||
net.ipv4.conf.all.rp_filter = 1
|
||||
net.ipv4.conf.default.rp_filter = 1
|
||||
net.ipv4.conf.all.log_martians = 1
|
||||
net.ipv4.conf.default.log_martians = 1
|
||||
fs.suid_dumpable = 0
|
||||
dev.tty.ldisc_autoload = 0
|
||||
kernel.sched_schedstats = 0
|
||||
net.ipv4.tcp_syncookies = 1
|
||||
net.ipv4.icmp_echo_ignore_broadcasts = 1
|
||||
net.ipv4.conf.all.accept_source_route = 0
|
||||
net.ipv6.conf.all.accept_source_route = 0
|
||||
net.ipv4.conf.all.accept_redirects = 0
|
||||
net.ipv4.conf.all.send_redirects = 0
|
||||
net.ipv6.conf.all.accept_redirects = 0
|
||||
21
overlay/etc/systemd/system/veilor-firstboot.service
Normal file
21
overlay/etc/systemd/system/veilor-firstboot.service
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
[Unit]
|
||||
Description=veilor-os first-boot admin password setup
|
||||
Documentation=https://github.com/veilor-uk/veilor-os
|
||||
ConditionPathExists=!/var/lib/veilor-firstboot.done
|
||||
Before=sddm.service display-manager.service
|
||||
After=systemd-user-sessions.service plymouth-quit-wait.service
|
||||
Conflicts=sddm.service
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
RemainAfterExit=no
|
||||
ExecStart=/usr/local/sbin/veilor-firstboot
|
||||
StandardInput=tty
|
||||
StandardOutput=tty
|
||||
StandardError=tty
|
||||
TTYPath=/dev/tty1
|
||||
TTYReset=yes
|
||||
TTYVHangup=yes
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
16
overlay/etc/systemd/system/veilor-modules-lock.service
Normal file
16
overlay/etc/systemd/system/veilor-modules-lock.service
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
[Unit]
|
||||
Description=Lock kernel module loading after graphical boot (veilor-os)
|
||||
Documentation=https://www.kernel.org/doc/html/latest/admin-guide/sysctl/kernel.html
|
||||
After=graphical.target network.target local-fs.target
|
||||
ConditionKernelCommandLine=!module.sig_enforce=1
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
RemainAfterExit=yes
|
||||
ExecStartPre=/bin/sleep 30
|
||||
ExecStart=/usr/bin/sysctl -w kernel.modules_disabled=1
|
||||
StandardOutput=journal
|
||||
StandardError=journal
|
||||
|
||||
[Install]
|
||||
WantedBy=graphical.target
|
||||
32
overlay/etc/tuned/profiles/veilor-balanced/script.sh
Executable file
32
overlay/etc/tuned/profiles/veilor-balanced/script.sh
Executable file
|
|
@ -0,0 +1,32 @@
|
|||
#!/usr/bin/bash
|
||||
# veilor-balanced — on-the-go
|
||||
. /usr/lib/tuned/functions
|
||||
|
||||
start() {
|
||||
[ -w /sys/devices/system/cpu/cpufreq/boost ] && echo 1 > /sys/devices/system/cpu/cpufreq/boost
|
||||
[ -w /sys/devices/platform/asus-nb-wmi/throttle_thermal_policy ] && \
|
||||
echo 1 > /sys/devices/platform/asus-nb-wmi/throttle_thermal_policy
|
||||
for nvme in /sys/bus/pci/devices/*/nvme; do
|
||||
ctl="${nvme%/nvme}/power/control"
|
||||
[ -w "$ctl" ] && echo auto > "$ctl"
|
||||
done
|
||||
for card in /sys/class/drm/card*/device/power_dpm_force_performance_level; do
|
||||
[ -w "$card" ] && echo auto > "$card"
|
||||
done
|
||||
enable_usb_autosuspend
|
||||
return 0
|
||||
}
|
||||
|
||||
stop() {
|
||||
for nvme in /sys/bus/pci/devices/*/nvme; do
|
||||
ctl="${nvme%/nvme}/power/control"
|
||||
[ -w "$ctl" ] && echo on > "$ctl"
|
||||
done
|
||||
for card in /sys/class/drm/card*/device/power_dpm_force_performance_level; do
|
||||
[ -w "$card" ] && echo auto > "$card"
|
||||
done
|
||||
disable_usb_autosuspend
|
||||
return 0
|
||||
}
|
||||
|
||||
process $@
|
||||
10
overlay/etc/tuned/profiles/veilor-balanced/tuned.conf
Normal file
10
overlay/etc/tuned/profiles/veilor-balanced/tuned.conf
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
[main]
|
||||
summary=veilor balanced — on-the-go, boost enabled, mid PPT
|
||||
|
||||
[cpu]
|
||||
governor=powersave
|
||||
energy_performance_preference=balance_performance
|
||||
force_latency=cstate.id_no_zero:1
|
||||
|
||||
[script]
|
||||
script=${i:PROFILE_DIR}/script.sh
|
||||
31
overlay/etc/tuned/profiles/veilor-performance/script.sh
Executable file
31
overlay/etc/tuned/profiles/veilor-performance/script.sh
Executable file
|
|
@ -0,0 +1,31 @@
|
|||
#!/usr/bin/bash
|
||||
# veilor-performance — full power
|
||||
. /usr/lib/tuned/functions
|
||||
|
||||
start() {
|
||||
[ -w /sys/devices/system/cpu/cpufreq/boost ] && echo 1 > /sys/devices/system/cpu/cpufreq/boost
|
||||
[ -w /sys/devices/platform/asus-nb-wmi/throttle_thermal_policy ] && \
|
||||
echo 0 > /sys/devices/platform/asus-nb-wmi/throttle_thermal_policy
|
||||
for nvme in /sys/bus/pci/devices/*/nvme; do
|
||||
ctl="${nvme%/nvme}/power/control"
|
||||
[ -w "$ctl" ] && echo on > "$ctl"
|
||||
done
|
||||
for card in /sys/class/drm/card*/device/power_dpm_force_performance_level; do
|
||||
[ -w "$card" ] && echo auto > "$card"
|
||||
done
|
||||
disable_usb_autosuspend
|
||||
return 0
|
||||
}
|
||||
|
||||
stop() {
|
||||
for nvme in /sys/bus/pci/devices/*/nvme; do
|
||||
ctl="${nvme%/nvme}/power/control"
|
||||
[ -w "$ctl" ] && echo on > "$ctl"
|
||||
done
|
||||
for card in /sys/class/drm/card*/device/power_dpm_force_performance_level; do
|
||||
[ -w "$card" ] && echo auto > "$card"
|
||||
done
|
||||
return 0
|
||||
}
|
||||
|
||||
process $@
|
||||
10
overlay/etc/tuned/profiles/veilor-performance/tuned.conf
Normal file
10
overlay/etc/tuned/profiles/veilor-performance/tuned.conf
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
[main]
|
||||
summary=veilor performance — full PPT, max boost, no artificial cap
|
||||
|
||||
[cpu]
|
||||
governor=performance
|
||||
energy_performance_preference=performance
|
||||
force_latency=1
|
||||
|
||||
[script]
|
||||
script=${i:PROFILE_DIR}/script.sh
|
||||
43
overlay/etc/tuned/profiles/veilor-powersave/script.sh
Executable file
43
overlay/etc/tuned/profiles/veilor-powersave/script.sh
Executable file
|
|
@ -0,0 +1,43 @@
|
|||
#!/usr/bin/bash
|
||||
# veilor-powersave — max battery
|
||||
. /usr/lib/tuned/functions
|
||||
|
||||
start() {
|
||||
# CPU boost off
|
||||
[ -w /sys/devices/system/cpu/cpufreq/boost ] && echo 0 > /sys/devices/system/cpu/cpufreq/boost
|
||||
|
||||
# ASUS throttle_thermal_policy: 2 = silent (~5W PPT). No-op on non-ASUS.
|
||||
[ -w /sys/devices/platform/asus-nb-wmi/throttle_thermal_policy ] && \
|
||||
echo 2 > /sys/devices/platform/asus-nb-wmi/throttle_thermal_policy
|
||||
|
||||
# NVMe PCI autosuspend (first NVMe device)
|
||||
for nvme in /sys/bus/pci/devices/*/nvme; do
|
||||
ctl="${nvme%/nvme}/power/control"
|
||||
[ -w "$ctl" ] && echo auto > "$ctl"
|
||||
done
|
||||
|
||||
# AMD iGPU — minimum clocks (no-op on non-AMD)
|
||||
for card in /sys/class/drm/card*/device/power_dpm_force_performance_level; do
|
||||
[ -w "$card" ] && echo low > "$card"
|
||||
done
|
||||
|
||||
# USB autosuspend
|
||||
enable_usb_autosuspend
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
stop() {
|
||||
[ -w /sys/devices/system/cpu/cpufreq/boost ] && echo 1 > /sys/devices/system/cpu/cpufreq/boost
|
||||
for nvme in /sys/bus/pci/devices/*/nvme; do
|
||||
ctl="${nvme%/nvme}/power/control"
|
||||
[ -w "$ctl" ] && echo on > "$ctl"
|
||||
done
|
||||
for card in /sys/class/drm/card*/device/power_dpm_force_performance_level; do
|
||||
[ -w "$card" ] && echo auto > "$card"
|
||||
done
|
||||
disable_usb_autosuspend
|
||||
return 0
|
||||
}
|
||||
|
||||
process $@
|
||||
12
overlay/etc/tuned/profiles/veilor-powersave/tuned.conf
Normal file
12
overlay/etc/tuned/profiles/veilor-powersave/tuned.conf
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
[main]
|
||||
summary=veilor powersave — max battery, CPU capped, boost off
|
||||
|
||||
[cpu]
|
||||
governor=powersave
|
||||
energy_performance_preference=power
|
||||
force_latency=cstate.id_no_zero:1
|
||||
min_perf_pct=0
|
||||
max_perf_pct=30
|
||||
|
||||
[script]
|
||||
script=${i:PROFILE_DIR}/script.sh
|
||||
3
overlay/etc/udev/rules.d/90-veilor-ac-switch.rules
Normal file
3
overlay/etc/udev/rules.d/90-veilor-ac-switch.rules
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# veilor-os — auto-switch tuned profile on AC plug/unplug
|
||||
SUBSYSTEM=="power_supply", ATTR{online}=="0", RUN+="/usr/bin/tuned-adm profile veilor-powersave"
|
||||
SUBSYSTEM=="power_supply", ATTR{online}=="1", RUN+="/usr/bin/tuned-adm profile veilor-performance"
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
# veilor-os — cap battery charge at 80% for longevity
|
||||
ACTION=="add", SUBSYSTEM=="power_supply", ATTR{type}=="Battery", ATTR{charge_control_end_threshold}=="*", ATTR{charge_control_end_threshold}="80"
|
||||
35
overlay/usr/local/bin/veilor-power
Executable file
35
overlay/usr/local/bin/veilor-power
Executable file
|
|
@ -0,0 +1,35 @@
|
|||
#!/usr/bin/bash
|
||||
# veilor-power — power profile switcher
|
||||
# Usage: veilor-power [save|mid|perf|status]
|
||||
|
||||
_switch() {
|
||||
sudo /usr/bin/tuned-adm profile "$1"
|
||||
}
|
||||
|
||||
_status() {
|
||||
local profile gov epp boost asus freq
|
||||
profile=$(tuned-adm active 2>/dev/null | awk '{print $NF}')
|
||||
gov=$(cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor 2>/dev/null)
|
||||
epp=$(cat /sys/devices/system/cpu/cpu0/cpufreq/energy_performance_preference 2>/dev/null)
|
||||
boost=$(cat /sys/devices/system/cpu/cpufreq/boost 2>/dev/null)
|
||||
asus=$(cat /sys/devices/platform/asus-nb-wmi/throttle_thermal_policy 2>/dev/null || echo "n/a")
|
||||
freq=$(awk '{printf "%.0f MHz", $1/1000}' /sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq 2>/dev/null)
|
||||
|
||||
echo "Profile : $profile"
|
||||
echo "Governor: $gov | EPP: $epp | Boost: $boost"
|
||||
echo "ASUS TTP: $asus (0=perf 1=balanced 2=silent) | Cur freq: $freq"
|
||||
}
|
||||
|
||||
case "$1" in
|
||||
save|powersave|s) _switch veilor-powersave ;;
|
||||
mid|balanced|b) _switch veilor-balanced ;;
|
||||
perf|performance|p) _switch veilor-performance ;;
|
||||
status|"") _status ;;
|
||||
*)
|
||||
echo "Usage: veilor-power [save|mid|perf|status]"
|
||||
echo " save — max battery (boost off, lowest PPT)"
|
||||
echo " mid — balanced (boost on, mid PPT)"
|
||||
echo " perf — performance (boost on, full PPT)"
|
||||
echo " status / no arg — show current state"
|
||||
;;
|
||||
esac
|
||||
47
overlay/usr/local/sbin/veilor-firstboot
Executable file
47
overlay/usr/local/sbin/veilor-firstboot
Executable file
|
|
@ -0,0 +1,47 @@
|
|||
#!/usr/bin/bash
|
||||
# veilor-firstboot — set admin password on first boot, then self-disable.
|
||||
# Runs on TTY1 before SDDM. Only fires while admin password is empty/expired.
|
||||
|
||||
set -uo pipefail
|
||||
|
||||
STATE=/var/lib/veilor-firstboot.done
|
||||
[[ -f $STATE ]] && exit 0
|
||||
|
||||
# Branded banner
|
||||
clear
|
||||
cat << 'EOF'
|
||||
|
||||
┌──────────────────────────────────────────────────────────┐
|
||||
│ │
|
||||
│ veilor-os │
|
||||
│ first boot — admin password │
|
||||
│ │
|
||||
└──────────────────────────────────────────────────────────┘
|
||||
|
||||
Set a password for the local admin account.
|
||||
|
||||
Requirements: minimum 14 characters, at least one digit,
|
||||
one uppercase, one lowercase, one special character.
|
||||
|
||||
EOF
|
||||
|
||||
# Loop until passwd succeeds (pwquality enforces complexity)
|
||||
until passwd admin; do
|
||||
echo
|
||||
echo " Password not accepted. Try again."
|
||||
echo
|
||||
sleep 1
|
||||
done
|
||||
|
||||
# Mark done so service doesn't fire again
|
||||
touch "$STATE"
|
||||
|
||||
# Disable self for next boots
|
||||
systemctl disable veilor-firstboot.service >/dev/null 2>&1 || true
|
||||
|
||||
echo
|
||||
echo " Password set. Starting graphical session..."
|
||||
sleep 2
|
||||
|
||||
# Start SDDM (was held back by service ordering)
|
||||
systemctl start sddm.service
|
||||
175
scripts/10-harden-base.sh
Executable file
175
scripts/10-harden-base.sh
Executable file
|
|
@ -0,0 +1,175 @@
|
|||
#!/usr/bin/env bash
|
||||
# veilor-os — base hardening (services, DNS, fail2ban, auditd)
|
||||
# Idempotent. Run as root during kickstart %post or post-install.
|
||||
|
||||
set -uo pipefail
|
||||
|
||||
GREEN='\033[0;32m'; YELLOW='\033[1;33m'; RED='\033[0;31m'; NC='\033[0m'
|
||||
ok() { echo -e "${GREEN}[OK]${NC} $*"; }
|
||||
info() { echo -e "${YELLOW}[INFO]${NC} $*"; }
|
||||
err() { echo -e "${RED}[ERR]${NC} $*"; }
|
||||
|
||||
[[ $EUID -eq 0 ]] || { err "Must run as root"; exit 1; }
|
||||
|
||||
echo "════════════════════════════════════════════════════════"
|
||||
echo " veilor-os :: 10-harden-base"
|
||||
echo "════════════════════════════════════════════════════════"
|
||||
|
||||
# ── Remove KDE Connect (network exposure, DBus surface) ──
|
||||
info "Removing kdeconnectd"
|
||||
dnf remove -y kdeconnectd 2>/dev/null && ok "kdeconnectd removed" || info "kdeconnectd absent"
|
||||
|
||||
# ── systemd-resolved: LLMNR off, DNSSEC, DNS-over-TLS ──
|
||||
info "Hardening systemd-resolved"
|
||||
mkdir -p /etc/systemd/resolved.conf.d
|
||||
cat > /etc/systemd/resolved.conf.d/veilor-hardening.conf << 'EOF'
|
||||
[Resolve]
|
||||
LLMNR=no
|
||||
DNSSEC=allow-downgrade
|
||||
DNS=1.1.1.1#cloudflare-dns.com 1.0.0.1#cloudflare-dns.com 2606:4700:4700::1111#cloudflare-dns.com
|
||||
FallbackDNS=9.9.9.9#dns.quad9.net 149.112.112.112#dns.quad9.net
|
||||
DNSOverTLS=opportunistic
|
||||
EOF
|
||||
systemctl restart systemd-resolved 2>/dev/null || true
|
||||
ok "systemd-resolved hardened (LLMNR off, DNSSEC, DoT)"
|
||||
|
||||
# ── fail2ban ──
|
||||
info "Installing fail2ban"
|
||||
rpm -q fail2ban &>/dev/null || dnf install -y fail2ban fail2ban-firewalld
|
||||
cat > /etc/fail2ban/jail.local << 'EOF'
|
||||
[DEFAULT]
|
||||
backend = systemd
|
||||
bantime = 3600
|
||||
findtime = 600
|
||||
maxretry = 5
|
||||
banaction = firewallcmd-rich-rules
|
||||
banaction_allports = firewallcmd-rich-rules
|
||||
|
||||
[sshd]
|
||||
enabled = true
|
||||
mode = aggressive
|
||||
port = ssh
|
||||
maxretry = 3
|
||||
bantime = 86400
|
||||
|
||||
[pam-generic]
|
||||
enabled = true
|
||||
filter = pam-generic
|
||||
bantime = 3600
|
||||
maxretry = 5
|
||||
EOF
|
||||
systemctl enable fail2ban
|
||||
ok "fail2ban configured + enabled"
|
||||
|
||||
# ── auditd rules ──
|
||||
info "Deploying auditd rules"
|
||||
mkdir -p /etc/audit/rules.d
|
||||
cat > /etc/audit/rules.d/99-veilor-hardening.rules << 'EOF'
|
||||
## veilor-os audit ruleset
|
||||
-D
|
||||
-b 8192
|
||||
-f 1
|
||||
|
||||
## time & clock
|
||||
-a always,exit -F arch=b64 -S adjtimex,settimeofday -k time-change
|
||||
-a always,exit -F arch=b32 -S adjtimex,settimeofday,stime -k time-change
|
||||
-a always,exit -F arch=b64 -S clock_settime -k time-change
|
||||
-a always,exit -F arch=b32 -S clock_settime -k time-change
|
||||
-w /etc/localtime -p wa -k time-change
|
||||
|
||||
## identity
|
||||
-w /etc/group -p wa -k identity
|
||||
-w /etc/passwd -p wa -k identity
|
||||
-w /etc/gshadow -p wa -k identity
|
||||
-w /etc/shadow -p wa -k identity
|
||||
-w /etc/security/opasswd -p wa -k identity
|
||||
|
||||
## hostname / network
|
||||
-a always,exit -F arch=b64 -S sethostname,setdomainname -k system-locale
|
||||
-a always,exit -F arch=b32 -S sethostname,setdomainname -k system-locale
|
||||
-w /etc/hosts -p wa -k system-locale
|
||||
-w /etc/hostname -p wa -k system-locale
|
||||
|
||||
## SELinux
|
||||
-w /etc/selinux/ -p wa -k MAC-policy
|
||||
|
||||
## logins
|
||||
-w /var/log/lastlog -p wa -k logins
|
||||
-w /var/run/faillock/ -p wa -k logins
|
||||
-w /var/run/utmp -p wa -k session
|
||||
-w /var/log/wtmp -p wa -k logins
|
||||
-w /var/log/btmp -p wa -k logins
|
||||
|
||||
## perm changes
|
||||
-a always,exit -F arch=b64 -S chmod,fchmod,fchmodat -F auid>=1000 -F auid!=unset -k perm_mod
|
||||
-a always,exit -F arch=b32 -S chmod,fchmod,fchmodat -F auid>=1000 -F auid!=unset -k perm_mod
|
||||
-a always,exit -F arch=b64 -S chown,fchown,fchownat,lchown -F auid>=1000 -F auid!=unset -k perm_mod
|
||||
-a always,exit -F arch=b32 -S chown,fchown,fchownat,lchown -F auid>=1000 -F auid!=unset -k perm_mod
|
||||
-a always,exit -F arch=b64 -S setxattr,lsetxattr,fsetxattr,removexattr,lremovexattr,fremovexattr -F auid>=1000 -F auid!=unset -k perm_mod
|
||||
-a always,exit -F arch=b32 -S setxattr,lsetxattr,fsetxattr,removexattr,lremovexattr,fremovexattr -F auid>=1000 -F auid!=unset -k perm_mod
|
||||
|
||||
## access denials
|
||||
-a always,exit -F arch=b64 -S creat,open,openat,truncate,ftruncate -F exit=-EACCES -F auid>=1000 -F auid!=unset -k access
|
||||
-a always,exit -F arch=b32 -S creat,open,openat,truncate,ftruncate -F exit=-EACCES -F auid>=1000 -F auid!=unset -k access
|
||||
-a always,exit -F arch=b64 -S creat,open,openat,truncate,ftruncate -F exit=-EPERM -F auid>=1000 -F auid!=unset -k access
|
||||
-a always,exit -F arch=b32 -S creat,open,openat,truncate,ftruncate -F exit=-EPERM -F auid>=1000 -F auid!=unset -k access
|
||||
|
||||
## privileged commands
|
||||
-a always,exit -F path=/usr/bin/sudo -F perm=x -F auid>=1000 -F auid!=unset -k privileged
|
||||
-a always,exit -F path=/usr/bin/su -F perm=x -F auid>=1000 -F auid!=unset -k privileged
|
||||
-a always,exit -F path=/usr/bin/newgrp -F perm=x -F auid>=1000 -F auid!=unset -k privileged
|
||||
-a always,exit -F path=/usr/bin/chsh -F perm=x -F auid>=1000 -F auid!=unset -k privileged
|
||||
-a always,exit -F path=/usr/bin/chfn -F perm=x -F auid>=1000 -F auid!=unset -k privileged
|
||||
-a always,exit -F path=/usr/bin/gpasswd -F perm=x -F auid>=1000 -F auid!=unset -k privileged
|
||||
-a always,exit -F path=/usr/bin/passwd -F perm=x -F auid>=1000 -F auid!=unset -k privileged
|
||||
-a always,exit -F path=/usr/bin/chage -F perm=x -F auid>=1000 -F auid!=unset -k privileged
|
||||
-a always,exit -F path=/usr/bin/pkexec -F perm=x -F auid>=1000 -F auid!=unset -k privileged
|
||||
-a always,exit -F path=/usr/bin/mount -F perm=x -F auid>=1000 -F auid!=unset -k privileged
|
||||
-a always,exit -F path=/usr/bin/umount -F perm=x -F auid>=1000 -F auid!=unset -k privileged
|
||||
|
||||
## kernel modules
|
||||
-w /sbin/insmod -p x -k modules
|
||||
-w /sbin/rmmod -p x -k modules
|
||||
-w /sbin/modprobe -p x -k modules
|
||||
-a always,exit -F arch=b64 -S init_module,delete_module,finit_module -k modules
|
||||
-a always,exit -F arch=b32 -S init_module,delete_module,finit_module -k modules
|
||||
|
||||
## sudoers
|
||||
-w /etc/sudoers -p wa -k scope
|
||||
-w /etc/sudoers.d/ -p wa -k scope
|
||||
|
||||
## sshd
|
||||
-w /etc/ssh/sshd_config -p wa -k sshd
|
||||
-w /etc/ssh/sshd_config.d/ -p wa -k sshd
|
||||
|
||||
## cron
|
||||
-w /etc/crontab -p wa -k cron
|
||||
-w /etc/cron.d/ -p wa -k cron
|
||||
-w /etc/cron.daily/ -p wa -k cron
|
||||
-w /etc/cron.hourly/ -p wa -k cron
|
||||
-w /etc/cron.monthly/ -p wa -k cron
|
||||
-w /etc/cron.weekly/ -p wa -k cron
|
||||
-w /var/spool/cron/ -p wa -k cron
|
||||
|
||||
## sysctl
|
||||
-w /etc/sysctl.conf -p wa -k sysctl
|
||||
-w /etc/sysctl.d/ -p wa -k sysctl
|
||||
|
||||
## firewall
|
||||
-w /etc/firewalld/ -p wa -k firewall
|
||||
|
||||
## boot
|
||||
-w /etc/default/grub -p wa -k grub
|
||||
-w /etc/grub.d/ -p wa -k grub
|
||||
|
||||
## systemd
|
||||
-w /etc/systemd/system/ -p wa -k systemd
|
||||
-w /usr/lib/systemd/system/ -p wa -k systemd
|
||||
EOF
|
||||
augenrules --load 2>/dev/null || true
|
||||
systemctl enable auditd
|
||||
ok "auditd rules deployed"
|
||||
|
||||
echo "════════════════════════════════════════════════════════"
|
||||
echo " 10-harden-base complete"
|
||||
echo "════════════════════════════════════════════════════════"
|
||||
145
scripts/20-harden-kernel.sh
Executable file
145
scripts/20-harden-kernel.sh
Executable file
|
|
@ -0,0 +1,145 @@
|
|||
#!/usr/bin/env bash
|
||||
# veilor-os — kernel + service hardening (sysctl, USBGuard, NTS chrony, pwquality, service prune)
|
||||
# Idempotent. Run as root.
|
||||
|
||||
set -uo pipefail
|
||||
|
||||
GREEN='\033[0;32m'; YELLOW='\033[1;33m'; RED='\033[0;31m'; NC='\033[0m'
|
||||
ok() { echo -e "${GREEN}[OK]${NC} $*"; }
|
||||
info() { echo -e "${YELLOW}[INFO]${NC} $*"; }
|
||||
err() { echo -e "${RED}[ERR]${NC} $*"; }
|
||||
|
||||
[[ $EUID -eq 0 ]] || { err "Must run as root"; exit 1; }
|
||||
|
||||
echo "════════════════════════════════════════════════════════"
|
||||
echo " veilor-os :: 20-harden-kernel"
|
||||
echo "════════════════════════════════════════════════════════"
|
||||
|
||||
# ── sysctl hardening ──
|
||||
info "Writing /etc/sysctl.d/99-veilor-hardening.conf"
|
||||
cat > /etc/sysctl.d/99-veilor-hardening.conf << 'EOF'
|
||||
# kernel pointer hiding
|
||||
kernel.kptr_restrict = 2
|
||||
kernel.dmesg_restrict = 1
|
||||
# BPF JIT constant blinding
|
||||
net.core.bpf_jit_harden = 2
|
||||
# unprivileged perf events disabled
|
||||
kernel.perf_event_paranoid = 3
|
||||
# ptrace restricted to parent
|
||||
kernel.yama.ptrace_scope = 2
|
||||
# full ASLR
|
||||
kernel.randomize_va_space = 2
|
||||
# modules unlocked at boot; veilor-modules-lock locks 30s after graphical
|
||||
kernel.modules_disabled = 0
|
||||
# reverse path filter
|
||||
net.ipv4.conf.all.rp_filter = 1
|
||||
net.ipv4.conf.default.rp_filter = 1
|
||||
# log martians
|
||||
net.ipv4.conf.all.log_martians = 1
|
||||
net.ipv4.conf.default.log_martians = 1
|
||||
# no SUID core dumps
|
||||
fs.suid_dumpable = 0
|
||||
# block unprivileged tty line discipline loading (LPE vector)
|
||||
dev.tty.ldisc_autoload = 0
|
||||
# /proc/sched_debug hardened
|
||||
kernel.sched_schedstats = 0
|
||||
# TCP SYN cookies
|
||||
net.ipv4.tcp_syncookies = 1
|
||||
# ignore ICMP broadcast
|
||||
net.ipv4.icmp_echo_ignore_broadcasts = 1
|
||||
# no source routing
|
||||
net.ipv4.conf.all.accept_source_route = 0
|
||||
net.ipv6.conf.all.accept_source_route = 0
|
||||
# no ICMP redirects
|
||||
net.ipv4.conf.all.accept_redirects = 0
|
||||
net.ipv4.conf.all.send_redirects = 0
|
||||
net.ipv6.conf.all.accept_redirects = 0
|
||||
EOF
|
||||
sysctl --system >/dev/null 2>&1 || true
|
||||
ok "sysctl hardening written"
|
||||
|
||||
# ── kernel module lock service ──
|
||||
info "Enabling veilor-modules-lock.service"
|
||||
systemctl enable veilor-modules-lock.service 2>/dev/null || \
|
||||
info "veilor-modules-lock.service not yet installed (overlay step)"
|
||||
|
||||
# ── chrony with NTS ──
|
||||
info "Configuring chrony with NTS"
|
||||
[[ -f /etc/chrony.conf ]] && cp /etc/chrony.conf /etc/chrony.conf.bak.veilor 2>/dev/null
|
||||
cat > /etc/chrony.conf << 'EOF'
|
||||
# NTS-authenticated time sources
|
||||
server time.cloudflare.com iburst nts
|
||||
server nts.sth1.ntp.se iburst nts
|
||||
server nts.sth2.ntp.se iburst nts
|
||||
|
||||
# unauthenticated pool fallback
|
||||
pool 2.fedora.pool.ntp.org iburst
|
||||
|
||||
sourcedir /run/chrony-dhcp
|
||||
ntsdumpdir /var/lib/chrony
|
||||
driftfile /var/lib/chrony/drift
|
||||
makestep 1.0 3
|
||||
rtcsync
|
||||
leapseclist /usr/share/zoneinfo/leap-seconds.list
|
||||
logdir /var/log/chrony
|
||||
EOF
|
||||
systemctl enable chronyd
|
||||
ok "chrony NTS configured (Cloudflare + NETNOD)"
|
||||
|
||||
# ── password complexity ──
|
||||
info "Writing /etc/security/pwquality.conf"
|
||||
cat > /etc/security/pwquality.conf << 'EOF'
|
||||
minlen = 14
|
||||
dcredit = -1
|
||||
ucredit = -1
|
||||
lcredit = -1
|
||||
ocredit = -1
|
||||
minclass = 3
|
||||
maxrepeat = 3
|
||||
maxclassrepeat = 4
|
||||
difok = 3
|
||||
dictcheck = 1
|
||||
usercheck = 1
|
||||
enforce_for_root
|
||||
retry = 3
|
||||
EOF
|
||||
ok "pwquality: minlen=14, 4 classes required"
|
||||
|
||||
# ── disable unneeded services ──
|
||||
for svc in gssproxy atd pcscd.socket pcscd.service cups cups-browsed abrtd \
|
||||
abrt-journal-core abrt-xorg abrt-oops abrt-ccpp geoclue avahi-daemon \
|
||||
bluetooth ModemManager; do
|
||||
systemctl disable --now "$svc" 2>/dev/null && ok "disabled $svc" || true
|
||||
done
|
||||
|
||||
# ── USBGuard ──
|
||||
info "Setting up USBGuard"
|
||||
rpm -q usbguard &>/dev/null || dnf install -y usbguard usbguard-tools
|
||||
|
||||
# At install time no devices are connected — ship empty allowlist.
|
||||
# First boot, admin runs: usbguard generate-policy > /etc/usbguard/rules.conf
|
||||
mkdir -p /etc/usbguard
|
||||
[[ -f /etc/usbguard/rules.conf ]] || : > /etc/usbguard/rules.conf
|
||||
chmod 600 /etc/usbguard/rules.conf
|
||||
chown root:root /etc/usbguard/rules.conf
|
||||
|
||||
cat > /etc/usbguard/usbguard-daemon.conf << 'EOF'
|
||||
ImplicitPolicyTarget=block
|
||||
AuditBackend=LinuxAudit
|
||||
IPCAllowedUsers=root
|
||||
IPCAllowedGroups=wheel
|
||||
RuleFile=/etc/usbguard/rules.conf
|
||||
EOF
|
||||
systemctl enable usbguard
|
||||
ok "USBGuard configured (generate-policy on first boot to allowlist your devices)"
|
||||
|
||||
# ── firewalld drop zone ──
|
||||
info "Setting firewalld default zone to drop"
|
||||
systemctl enable firewalld
|
||||
firewall-offline-cmd --set-default-zone=drop 2>/dev/null || true
|
||||
firewall-offline-cmd --zone=drop --add-service=ssh 2>/dev/null || true
|
||||
ok "firewalld: default drop, ssh allowed"
|
||||
|
||||
echo "════════════════════════════════════════════════════════"
|
||||
echo " 20-harden-kernel complete"
|
||||
echo "════════════════════════════════════════════════════════"
|
||||
19
scripts/firstboot.sh
Executable file
19
scripts/firstboot.sh
Executable file
|
|
@ -0,0 +1,19 @@
|
|||
#!/usr/bin/env bash
|
||||
# veilor-os — first user-login touch-ups (runs once via XDG autostart)
|
||||
# Currently a placeholder for per-user setup that can't be done at install time.
|
||||
|
||||
set -uo pipefail
|
||||
|
||||
DONE=$HOME/.config/veilor-firstboot.done
|
||||
[[ -f $DONE ]] && exit 0
|
||||
|
||||
# ── Generate USBGuard allowlist for currently connected devices ──
|
||||
# Requires sudo; admin runs this manually:
|
||||
# sudo usbguard generate-policy > /etc/usbguard/rules.conf
|
||||
# sudo systemctl restart usbguard
|
||||
|
||||
# ── Refresh font cache for user ──
|
||||
fc-cache -f "$HOME/.local/share/fonts" 2>/dev/null || true
|
||||
|
||||
mkdir -p "$(dirname "$DONE")"
|
||||
touch "$DONE"
|
||||
62
scripts/kde-theme-apply.sh
Executable file
62
scripts/kde-theme-apply.sh
Executable file
|
|
@ -0,0 +1,62 @@
|
|||
#!/usr/bin/env bash
|
||||
# veilor-os — apply system-wide KDE theme + DuckSans font default
|
||||
# Run during %post (chroot) or post-install. Idempotent.
|
||||
|
||||
set -uo pipefail
|
||||
|
||||
GREEN='\033[0;32m'; YELLOW='\033[1;33m'; NC='\033[0m'
|
||||
ok() { echo -e "${GREEN}[OK]${NC} $*"; }
|
||||
info() { echo -e "${YELLOW}[INFO]${NC} $*"; }
|
||||
|
||||
REPO="${VEILOR_REPO:-/usr/share/veilor-os}"
|
||||
|
||||
# ── Install color scheme system-wide ──
|
||||
info "Installing veilor-black color scheme"
|
||||
install -d -m 0755 /usr/share/color-schemes
|
||||
install -m 0644 "$REPO/assets/kde/veilor-black.colors" /usr/share/color-schemes/veilor-black.colors
|
||||
ok "color scheme installed"
|
||||
|
||||
# ── KDE system defaults ──
|
||||
info "Setting system kdedefaults"
|
||||
install -d -m 0755 /etc/xdg/kdedefaults
|
||||
install -m 0644 "$REPO/assets/kde/veilor-default.kdeglobals" /etc/xdg/kdedefaults/kdeglobals
|
||||
ok "kdedefaults written"
|
||||
|
||||
# ── DuckSans fontconfig default ──
|
||||
info "Setting DuckSans as default sans-serif"
|
||||
install -d -m 0755 /etc/fonts/conf.d
|
||||
cat > /etc/fonts/conf.d/55-veilor-ducksans.conf << 'EOF'
|
||||
<?xml version="1.0"?>
|
||||
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
|
||||
<fontconfig>
|
||||
<alias>
|
||||
<family>sans-serif</family>
|
||||
<prefer><family>DuckSans</family></prefer>
|
||||
</alias>
|
||||
<alias>
|
||||
<family>system-ui</family>
|
||||
<prefer><family>DuckSans</family></prefer>
|
||||
</alias>
|
||||
</fontconfig>
|
||||
EOF
|
||||
fc-cache -f /usr/share/fonts/ducksans 2>/dev/null || true
|
||||
ok "fontconfig: DuckSans = default sans-serif"
|
||||
|
||||
# ── /etc/os-release branding ──
|
||||
info "Branding /etc/os-release"
|
||||
if [[ -f "$REPO/overlay/etc/os-release.d/veilor" ]]; then
|
||||
install -m 0644 "$REPO/overlay/etc/os-release.d/veilor" /etc/os-release
|
||||
ln -sf /etc/os-release /usr/lib/os-release 2>/dev/null || true
|
||||
ok "os-release set to veilor-os"
|
||||
fi
|
||||
|
||||
# ── Plymouth theme (optional) ──
|
||||
if [[ -d "$REPO/assets/plymouth/veilor" ]] && command -v plymouth-set-default-theme &>/dev/null; then
|
||||
info "Installing plymouth theme"
|
||||
install -d -m 0755 /usr/share/plymouth/themes/veilor
|
||||
cp -r "$REPO/assets/plymouth/veilor/." /usr/share/plymouth/themes/veilor/
|
||||
plymouth-set-default-theme -R veilor 2>/dev/null || true
|
||||
ok "plymouth theme set to veilor"
|
||||
fi
|
||||
|
||||
ok "kde-theme-apply complete"
|
||||
10
scripts/selinux/build-policy.sh
Executable file
10
scripts/selinux/build-policy.sh
Executable file
|
|
@ -0,0 +1,10 @@
|
|||
#!/usr/bin/env bash
|
||||
# Build + load veilor-systemd SELinux policy module.
|
||||
set -euo pipefail
|
||||
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
checkmodule -M -m -o veilor-systemd.mod veilor-systemd.te
|
||||
semodule_package -o veilor-systemd.pp -m veilor-systemd.mod
|
||||
semodule -i veilor-systemd.pp
|
||||
echo "[OK] veilor-systemd SELinux module loaded"
|
||||
11
scripts/selinux/veilor-systemd.te
Normal file
11
scripts/selinux/veilor-systemd.te
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
module veilor-systemd 1.0;
|
||||
|
||||
require {
|
||||
type systemd_modules_load_t;
|
||||
class capability2 perfmon;
|
||||
class capability sys_admin;
|
||||
}
|
||||
|
||||
#============= systemd_modules_load_t ==============
|
||||
allow systemd_modules_load_t self:capability sys_admin;
|
||||
allow systemd_modules_load_t self:capability2 perfmon;
|
||||
115
test/boot-checklist.md
Normal file
115
test/boot-checklist.md
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
# Spare-laptop validation checklist
|
||||
|
||||
Run after installing a fresh veilor-os ISO. Each item should pass
|
||||
before the build is considered green.
|
||||
|
||||
## Install flow
|
||||
|
||||
- [ ] Anaconda **only** prompts for LUKS passphrase — no account wizard,
|
||||
no initial-setup screen
|
||||
- [ ] Install completes without `%post` errors (check `/var/log/veilor-install.log`)
|
||||
- [ ] Reboot succeeds, USB removed cleanly
|
||||
|
||||
## First boot
|
||||
|
||||
- [ ] LUKS prompt appears at boot
|
||||
- [ ] TTY1 shows veilor-os banner + password prompt
|
||||
- [ ] Password rejection on weak input (try `password123` — should fail)
|
||||
- [ ] Password set succeeds with strong input
|
||||
- [ ] SDDM starts after password set
|
||||
- [ ] `admin@veilor-os` shell prompt visible after first login
|
||||
- [ ] `veilor-firstboot.service` shows `inactive (dead)` and `disabled`
|
||||
after first run
|
||||
|
||||
## Identity
|
||||
|
||||
- [ ] `passwd -S root` reports `L` (locked)
|
||||
- [ ] `getent passwd | wc -l` shows base + admin only
|
||||
- [ ] `id admin` shows `groups=...,wheel`
|
||||
|
||||
## Branding
|
||||
|
||||
- [ ] `hostnamectl` reports `veilor-os`
|
||||
- [ ] `cat /etc/os-release` shows `NAME="veilor-os"` and `ID=veilor`
|
||||
- [ ] `grep -ri onyx /etc /usr/local /usr/share/fonts` returns zero
|
||||
- [ ] `grep -ri '192\.168\.0\.\|admin@gmail\|fedora\.local' /etc /usr/local` returns zero
|
||||
|
||||
## Theme
|
||||
|
||||
- [ ] KDE color scheme shows `veilor-black` in System Settings
|
||||
- [ ] Konsole renders in DuckSans (`fc-match sans-serif` returns
|
||||
`DuckSans` if the font was vendored)
|
||||
- [ ] Background is pure black (#000000), not Breeze dark grey
|
||||
|
||||
## Power
|
||||
|
||||
- [ ] `veilor-power status` runs without sudo, shows current profile
|
||||
- [ ] `veilor-power save` switches to `veilor-powersave`
|
||||
- [ ] `veilor-power perf` switches to `veilor-performance`
|
||||
- [ ] Unplugging AC auto-switches to `veilor-powersave` (udev rule)
|
||||
- [ ] Plugging AC auto-switches to `veilor-performance`
|
||||
|
||||
## Hardening — services
|
||||
|
||||
- [ ] `systemctl is-active fail2ban` → active
|
||||
- [ ] `systemctl is-active usbguard` → active
|
||||
- [ ] `systemctl is-active auditd` → active
|
||||
- [ ] `systemctl is-active firewalld` → active
|
||||
- [ ] `systemctl is-active tuned` → active
|
||||
- [ ] `systemctl is-active chronyd` → active
|
||||
- [ ] `systemctl is-active sshd` → active
|
||||
- [ ] `systemctl is-active cups` → inactive / not-found
|
||||
- [ ] `systemctl is-active avahi-daemon` → inactive / not-found
|
||||
- [ ] `systemctl is-active bluetooth` → inactive
|
||||
- [ ] `systemctl is-active veilor-modules-lock` (after 30s) → active
|
||||
|
||||
## Hardening — kernel/sysctl
|
||||
|
||||
- [ ] `getenforce` → `Enforcing`
|
||||
- [ ] `mokutil --sb-state` → `SecureBoot enabled`
|
||||
- [ ] `sysctl kernel.yama.ptrace_scope` → `2`
|
||||
- [ ] `sysctl kernel.kptr_restrict` → `2`
|
||||
- [ ] `sysctl fs.suid_dumpable` → `0`
|
||||
- [ ] `sysctl dev.tty.ldisc_autoload` → `0`
|
||||
- [ ] `sysctl kernel.modules_disabled` (after 30s post graphical) → `1`
|
||||
|
||||
## Hardening — network
|
||||
|
||||
- [ ] `firewall-cmd --get-default-zone` → `drop`
|
||||
- [ ] `firewall-cmd --zone=drop --list-services` → `ssh`
|
||||
- [ ] `resolvectl status` shows DNSSEC + DoT, LLMNR off
|
||||
- [ ] `chronyc sources -v` shows NTS-authenticated peers
|
||||
|
||||
## Hardening — SSH
|
||||
|
||||
- [ ] `sshd -T | grep -E 'permitrootlogin|passwordauth|allowusers|x11forwarding'`
|
||||
shows: `permitrootlogin no`, `passwordauthentication no`,
|
||||
`allowusers admin`, `x11forwarding no`
|
||||
|
||||
## Disk
|
||||
|
||||
- [ ] `lsblk -f` shows LUKS2 on the main partition
|
||||
- [ ] `cryptsetup luksDump /dev/...` shows argon2id, aes-xts-plain64
|
||||
- [ ] `swapon` shows `zram` device, no disk swap
|
||||
|
||||
## SELinux module
|
||||
|
||||
- [ ] `semodule -l | grep veilor-systemd` → present
|
||||
- [ ] No SELinux denials in `ausearch -m AVC -ts boot` related to
|
||||
`systemd_modules_load_t`
|
||||
|
||||
## USBGuard
|
||||
|
||||
- [ ] `systemctl status usbguard` → active
|
||||
- [ ] `wc -l /etc/usbguard/rules.conf` → 0 (empty allowlist by design)
|
||||
- [ ] After `sudo usbguard generate-policy > /etc/usbguard/rules.conf`
|
||||
and restart, all currently-connected USB devices remain
|
||||
functional
|
||||
|
||||
## Findings
|
||||
|
||||
Log issues and fixes here:
|
||||
|
||||
| Date | Item | Issue | Fix in kickstart? |
|
||||
|------|------|-------|-------------------|
|
||||
| | | | |
|
||||
Loading…
Reference in a new issue