feat: add hardened base and per-edition image overlays

Add the files that land in the image, organised as layered rootfs trees
(base first, then the edition layer wins on conflict).

- base: hardened kernel cmdline + sysctl, default-deny nftables,
  key-only ssh, persistent journald, module blacklist, no core dumps,
  snapshot timer units, motd.
- server: Prometheus + auto-provisioned Grafana + Loki + promtail.
- workstation: dev profile and desktop sysctl relaxations (perf,
  rootless containers).
- cloud: cloud-init provisioning config.
This commit is contained in:
Blake Ridgway
2026-08-21 13:15:43 -05:00
parent 729f191950
commit 94ab6043e7
19 changed files with 516 additions and 0 deletions

View File

@@ -0,0 +1,13 @@
# Arcline OS — Grafana dashboard provisioning (server edition)
apiVersion: 1
providers:
- name: Arcline
orgId: 1
folder: Arcline
type: file
disableDeletion: false
allowUiUpdates: true
updateIntervalSeconds: 30
options:
path: /etc/grafana/provisioning/dashboards

View File

@@ -0,0 +1,94 @@
{
"uid": "arcline-node-overview",
"title": "Arcline Node Overview",
"tags": ["arcline", "node"],
"timezone": "browser",
"schemaVersion": 39,
"version": 1,
"refresh": "30s",
"time": { "from": "now-1h", "to": "now" },
"panels": [
{
"id": 1,
"title": "System load",
"type": "timeseries",
"gridPos": { "h": 8, "w": 12, "x": 0, "y": 0 },
"datasource": { "type": "prometheus", "uid": "Prometheus" },
"targets": [
{
"expr": "node_load1",
"legendFormat": "load1",
"refId": "A"
},
{
"expr": "node_load5",
"legendFormat": "load5",
"refId": "B"
}
],
"fieldConfig": {
"defaults": { "unit": "short" },
"overrides": []
}
},
{
"id": 2,
"title": "Memory used",
"type": "timeseries",
"gridPos": { "h": 8, "w": 12, "x": 12, "y": 0 },
"datasource": { "type": "prometheus", "uid": "Prometheus" },
"targets": [
{
"expr": "(1 - node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes) * 100",
"legendFormat": "% used",
"refId": "A"
}
],
"fieldConfig": {
"defaults": { "unit": "percent", "max": 100, "min": 0 },
"overrides": []
}
},
{
"id": 3,
"title": "Disk usage",
"type": "gauge",
"gridPos": { "h": 8, "w": 12, "x": 0, "y": 8 },
"datasource": { "type": "prometheus", "uid": "Prometheus" },
"targets": [
{
"expr": "(1 - node_filesystem_avail_bytes{mountpoint=\"/\"} / node_filesystem_size_bytes{mountpoint=\"/\"}) * 100",
"legendFormat": "/",
"refId": "A"
}
],
"fieldConfig": {
"defaults": { "unit": "percent", "max": 100, "min": 0 },
"overrides": []
}
},
{
"id": 4,
"title": "Network traffic",
"type": "timeseries",
"gridPos": { "h": 8, "w": 12, "x": 12, "y": 8 },
"datasource": { "type": "prometheus", "uid": "Prometheus" },
"targets": [
{
"expr": "rate(node_network_receive_bytes_total[5m])",
"legendFormat": "rx {{ $labels.device }}",
"refId": "A"
},
{
"expr": "rate(node_network_transmit_bytes_total[5m])",
"legendFormat": "tx {{ $labels.device }}",
"refId": "B"
}
],
"fieldConfig": {
"defaults": { "unit": "Bps" },
"overrides": []
}
}
]
}

View File

@@ -0,0 +1,20 @@
# Arcline OS — Grafana datasource provisioning (server edition)
# Grafana is installed from the upstream apt repo (see configure-system.sh /
# docs/observability.md). These files auto-provision on first start.
apiVersion: 1
datasources:
- name: Prometheus
type: prometheus
access: proxy
url: http://localhost:9090
isDefault: true
editable: false
- name: Loki
type: loki
access: proxy
url: http://localhost:3100
isDefault: false
editable: false

View File

@@ -0,0 +1,35 @@
# Arcline OS — Loki configuration (server edition)
# Single-node, filesystem storage, 14-day retention. Loki is installed from
# the upstream tarball (see docs/observability.md).
auth_enabled: false
server:
http_listen_port: 3100
grpc_listen_port: 9095
common:
path_prefix: /var/lib/loki
storage:
filesystem:
chunks_directory: /var/lib/loki/chunks
rules_directory: /var/lib/loki/rules
replication_factor: 1
ring:
instance_addr: 127.0.0.1
kvstore:
store: inmemory
schema_config:
configs:
- from: 2024-01-01
store: tsdb
object_store: filesystem
schema: v13
index:
prefix: index_
period: 24h
limits_config:
retention_period: 14d
allow_structured_metadata: false

View File

@@ -0,0 +1,43 @@
# Arcline OS — pre-built alerting rules (Prometheus)
groups:
- name: arcline-node
rules:
- alert: NodeDown
expr: up == 0
for: 2m
labels:
severity: critical
annotations:
summary: "{{ $labels.instance }} is unreachable"
- alert: HighCpuLoad
expr: node_load1 / count(node_cpu_seconds_total{mode="idle"}) > 2
for: 10m
labels:
severity: warning
annotations:
summary: "sustained load average > 2 on {{ $labels.instance }}"
- alert: DiskAlmostFull
expr: (1 - node_filesystem_avail_bytes{fstype!~"tmpfs|overlay|squashfs"} / node_filesystem_size_bytes) * 100 > 85
for: 10m
labels:
severity: warning
annotations:
summary: "filesystem {{ $labels.mountpoint }} on {{ $labels.instance }} > 85%"
- alert: RootDiskCritical
expr: (1 - node_filesystem_avail_bytes{mountpoint="/"} / node_filesystem_size_bytes{mountpoint="/"}) * 100 > 95
for: 5m
labels:
severity: critical
annotations:
summary: "root filesystem on {{ $labels.instance }} > 95%"
- alert: ServiceRestarts
expr: increase(node_systemd_unit_restart_total[15m]) > 2
for: 0m
labels:
severity: warning
annotations:
summary: "unit {{ $labels.name }} restarting repeatedly on {{ $labels.instance }}"

View File

@@ -0,0 +1,25 @@
# Arcline OS — Prometheus configuration (server edition)
# Scrapes itself and node_exporter on the local host. Extension points for
# the Arcline toolchain (arcline-uptime exposes :8081/metrics) are commented.
global:
scrape_interval: 15s
evaluation_interval: 15s
external_labels:
cluster: arcline
rule_files:
- /etc/prometheus/arcline.rules.yml
scrape_configs:
- job_name: prometheus
static_configs:
- targets: ["localhost:9090"]
- job_name: node
static_configs:
- targets: ["localhost:9100"]
# - job_name: arcline-uptime
# static_configs:
# - targets: ["localhost:8081"]

View File

@@ -0,0 +1,31 @@
# Arcline OS — Promtail configuration (server edition)
# Ships systemd journal + /var/log files to Loki. Zero telemetry: this only
# talks to the local Loki instance on localhost.
server:
http_listen_port: 9080
grpc_listen_port: 0
positions:
filename: /var/lib/promtail/positions.yaml
clients:
- url: http://localhost:3100/loki/api/v1/push
scrape_configs:
- job_name: systemd-journal
journal:
path: /var/log/journal
max_age: 12h
labels:
job: systemd-journal
relabel_configs:
- source_labels: ["__journal__systemd_unit"]
target_label: "unit"
- job_name: varlogs
static_configs:
- targets: [localhost]
labels:
job: varlogs
__path__: /var/log/**/*.log