qubes-apply
python script to automate qubes saltstack
git clone https://9o.is/git/qubes-apply.git
commit 310796e315247b161421e92c924c7860ad373c5e parent 0435c974eaade17aa0506b8d6ded9edb899a3322 Author: Jul <jul@9o.is> Date: Fri, 27 Feb 2026 15:59:14 +0800 provide an example directory Diffstat:
41 files changed, 970 insertions(+), 1 deletion(-)
diff --git a/README.md b/README.md @@ -12,9 +12,13 @@ State is tracked in `/usr/local/var/qubes-apply/last-run.json`, which is just a If you'd rather not work from dom0 `/srv` directory, see the `client` directory. It includes qubes-rpc commands and policy files and a qubes-apply command that packages the files and installs it to `/srv`. It's kind of a hack but works. +## Example + +Check out the example directory for a demonstration of using formulas, pillars, and salt. `qubes-apply` will deploy several machinesand if you make a change, only the affeted machines will be reapplied. For example, changing something in `salt/package/wireguard` will only apply sys-vpn-template when you run qubes-apply. + ## Installation -In dom0, run the `sudo make install` or manually move the qubes-apply python script manually. +In dom0, run the `sudo make install` or manually move the qubes-apply python script. ## Usage diff --git a/example/formulas/qubes-sshfs-sync/qubes-sshfs-sync/config/client.sls b/example/formulas/qubes-sshfs-sync/qubes-sshfs-sync/config/client.sls @@ -0,0 +1,100 @@ +{% set sshfs = salt['pillar.get']('sshfs-sync') %} + +~user/.ssh/id_ed25519: + file.managed: + - contents_pillar: sshfs-sync:key + - user: user + - group: user + - mode: 0600 + - dir_mode: 0700 + - makedirs: True + - show_changes: False + +~user/.ssh/known_hosts: + file.managed: + - user: user + - group: user + - mode: 0600 + - dir_mode: 0700 + - makedirs: True + - contents: | + {%- for _, server in sshfs.servers.items() %} + [localhost]:{{ server.port }} ssh-ed25519 {{ server.key }} + {%- endfor %} + +{% for name, server in sshfs.servers.items() %} +{% set mount_file = server.dir | replace('/', '-') | lower | regex_replace('^-', '') + '.mount' %} + +{{ server.dir }}: + file.directory: + - user: user + - group: user + - mode: 0755 + +~user/.config/systemd/user/sshfs-{{ name }}.socket: + file.managed: + - user: user + - group: user + - makedirs: True + - contents: | + [Unit] + Description=Forward sshfs connection for {{ name }} + ConditionPathExists=/var/run/qubes-service/sshfs-{{ name }} + + [Socket] + ListenStream=127.0.0.1:{{ server.port }} + BindToDevice=lo + Accept=true + + [Install] + WantedBy=default.target + +~user/.config/systemd/user/sshfs-{{ name }}@.service + file.managed: + - user: user + - group: user + - makedirs: True + - contents: | + [Unit] + Description=Forward sshfs connection for {{ name }} + + [Service] + ExecStart=/usr/bin/qrexec-client-vm '{{ name }}' qubes.ConnectSSH + StandardInput=socket + StandardOutput=inherit + +~user/.config/systemd/user/{{ mount_file }}: + file.managed: + - user: user + - group: user + - makedirs: True + - contents: | + [Unit] + Description=Mount SSHFS {{ server.dir }} + ConditionPathExists=/var/run/qubes-service/sshfs-{{ name }} + + [Mount] + What=sync-{{ sshfs.name }}@localhost:/sync + Where={{ server.dir }} + Type=fuse.sshfs + Options=port={{ server.port }},uid=1000,gid=1000,follow_symlinks,reconnect + + [Install] + WantedBy=default.target + +~user/.config/systemd/user/default.target.wants/sshfs-{{ name }}.socket: + file.symlink: + - target: ~user/.config/systemd/user/sshfs-{{ name }}.socket + - user: user + - group: user + - makedirs: True + +~user/.config/systemd/user/default.target.wants/{{ mount_file }}: + file.symlink: + - target: ~user/.config/systemd/user/{{ mount_file }} + - user: user + - group: user + - makedirs: True + +{% endfor %} + diff --git a/example/formulas/qubes-sshfs-sync/qubes-sshfs-sync/config/server/files/clients.j2 b/example/formulas/qubes-sshfs-sync/qubes-sshfs-sync/config/server/files/clients.j2 @@ -0,0 +1,4 @@ +{%- set sync = salt['pillar.get']('sshfs-sync') -%} +{%- for name, vals in sync.get('client', {}).items() %} +{{ name }} {{ vals.uid }} {{ vals.home }} +{%- endfor %} diff --git a/example/formulas/qubes-sshfs-sync/qubes-sshfs-sync/config/server/files/sshd_config b/example/formulas/qubes-sshfs-sync/qubes-sshfs-sync/config/server/files/sshd_config @@ -0,0 +1,20 @@ +HostKey /etc/ssh/ssh_host_ed25519_key +KexAlgorithms curve25519-sha256 +Ciphers chacha20-poly1305@openssh.com +MACs hmac-sha2-512-etm@openssh.com +LogLevel VERBOSE +PermitRootLogin no +ChallengeResponseAuthentication no +PasswordAuthentication no +AuthenticationMethods publickey +PubkeyAuthentication yes +PermitEmptyPasswords no +AuthorizedKeysFile .ssh/authorized_keys +Subsystem sftp /usr/libexec/openssh/sftp-server + +Match Group sftponly + ChrootDirectory %h + ForceCommand internal-sftp -f AUTHPRIV -l INFO + AllowTcpForwarding no + X11Forwarding no + PasswordAuthentication no diff --git a/example/formulas/qubes-sshfs-sync/qubes-sshfs-sync/config/server/files/sshfs-sync.sh b/example/formulas/qubes-sshfs-sync/qubes-sshfs-sync/config/server/files/sshfs-sync.sh @@ -0,0 +1,20 @@ +#!/bin/bash +ln -s {/rw/config/qubes-rpc,/etc/qubes-rpc}/qubes.ConnectSSH +ln -sf {/rw/config/sshfs,/etc/ssh}/ssh_host_ed25519_key +ln -sf {/rw/config/sshfs,/etc/ssh}/sshd_config + +groupadd --gid 1001 sftponly +usermod -aG sftponly user + +while read -r name uid home ignore; do + useradd \ + --no-create-home \ + --uid $uid \ + --gid sftponly \ + --home-dir $home \ + --shell /usr/sbin/nologin \ + sync-$name + passwd --delete sync-$name +done < /rw/config/sshfs/clients + +systemctl restart sshd diff --git a/example/formulas/qubes-sshfs-sync/qubes-sshfs-sync/config/server/init.sls b/example/formulas/qubes-sshfs-sync/qubes-sshfs-sync/config/server/init.sls @@ -0,0 +1,59 @@ +{% set sync = salt['pillar.get']('sshfs-sync') %} + +/rw/config/sshfs/clients: + file.managed: + - source: salt://{{ slspath }}/files/clients.j2 + - makedirs: True + - template: jinja + +/rw/config/sshfs/ssh_host_ed25519_key: + file.managed: + - contents_pillar: sshfs-sync:key + - mode: 0600 + - show_changes: False + - makedirs: True + +/rw/config/sshfs/sshd_config: + file.managed: + - source: salt://{{ slspath }}/files/sshd_config + - makedirs: True + +/rw/config/rc.local.d/00-sshfs-sync.rc: + file.managed: + - source: salt://{{ slspath }}/files/sshfs-sync.sh + - mode: 0755 + - makedirs: True + +/rw/config/qubes-rpc/qubes.ConnectSSH: + file.managed: + - mode: 0755 + - makedirs: True + - contents: | + #!/bin/sh + exec socat STDIO TCP:localhost:22 + +{% for name, user in sync.get('client', {}).items() %} + +{{ user.home }}: + file.directory: + - mode: 0755 + - makedirs: True + +{{ user.home }}/sync: + file.directory: + - user: {{ user.uid }} + - group: 1001 + - mode: 0755 + - makedirs: True + - unless: sudo -u sync-{{ name }} mountpoint {{ user.home }}/sync + +{{ user.home }}/.ssh/authorized_keys: + file.managed: + - user: {{ user.uid }} + - group: 1001 + - mode: 0600 + - makedirs: True + - contents: ssh-ed25519 {{ user.key }} user@{{ name }} + +{% endfor %} + diff --git a/example/formulas/qubes-sshfs-sync/qubes-sshfs-sync/dom0.sls b/example/formulas/qubes-sshfs-sync/qubes-sshfs-sync/dom0.sls @@ -0,0 +1,28 @@ +{% set sync = salt['pillar.get']('sshfs-sync', {}) %} + +sshfs-sync-policy: + file.managed: + - name: /etc/qubes/policy.d/50-sshfs-sync.policy + - contents: | +{%- for name in sync.get('vms', {}) %} + qubes.ConnectSSH * @tag:{{ name }}-sshfs-client {{ name }} allow +{%- endfor %} + +{% for name, clients in sync.get('vms', {}).items() %} +{% for client in clients %} + +sshfs-sync-dom0-tags-{{name}}-{{client}}: + qvm.tags: + - name: {{ client }} + - add: + - {{ name }}-sshfs-client + +sshfs-sync-dom0-service-{{name}}-{{client}}: + qvm.service: + - name: {{ client }} + - enable: + - sshfs-{{ name }} + +{% endfor %} +{% endfor %} + diff --git a/example/formulas/qubes-sshfs-sync/qubes-sshfs-sync/package/client.sls b/example/formulas/qubes-sshfs-sync/qubes-sshfs-sync/package/client.sls @@ -0,0 +1,4 @@ +sshfs-sync-client: + pkg.installed: + - pkgs: + - fuse-sshfs diff --git a/example/formulas/qubes-sshfs-sync/qubes-sshfs-sync/package/server.sls b/example/formulas/qubes-sshfs-sync/qubes-sshfs-sync/package/server.sls @@ -0,0 +1,9 @@ +sshfs-sync-server: + pkg.installed: + - pkgs: + - openssh-server + - socat + service.enabled: + - name: sshd + - require: + - pkg: sshfs-sync-server diff --git a/example/pillar/dom0.sls b/example/pillar/dom0.sls @@ -0,0 +1,18 @@ +qubes: + default: + netvm: sys-vpn + template: f42 + memory: 256 + maxmem: 2048 + vcpus: 2 + winbgcolor: '#131314' + +sshfs-sync: + vms: + sys-sync: + - personal + +sys-vpn: + vms: + default: + autostart: False diff --git a/example/pillar/init.top b/example/pillar/init.top @@ -0,0 +1,22 @@ +user: + qubes:type:app: + - match: pillar + - shared.theme + + dom0: + - shared.theme + - dom0 + + f42: + - shared.theme + + personal: + - shared.sys-sync-client + - personal + + sys-sync: + - sys-sync + + sys-vpn-dvm: + - shared.wireguard + - sys-vpn diff --git a/example/pillar/personal.sls b/example/pillar/personal.sls @@ -0,0 +1,19 @@ +#!yaml|gpg + +sshfs-sync: + name: personal + key: | + -----BEGIN PGP MESSAGE----- + + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxx + xxxxx + -----END PGP MESSAGE----- diff --git a/example/pillar/shared/sys-sync-client.sls b/example/pillar/shared/sys-sync-client.sls @@ -0,0 +1,6 @@ +sshfs-sync: + servers: + sys-sync: + key: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + dir: /home/user/sync + port: 1840 diff --git a/example/pillar/shared/theme.sls b/example/pillar/shared/theme.sls @@ -0,0 +1,20 @@ +theme: + name: catppuccin-dark + color: + base00: 131314 # dark mantle + base01: 2b2b2b # base + base02: 616162 # surface 0 + base03: 848485 # overlay 0 + base04: aaaaaa # subtext 0 + base05: e3e3e3 # text + base06: f5e0dc # rosewater + base07: b4befe # lavendar + base08: f38ba8 # red + base09: fab387 # peach + base0A: f9e2af # yellow + base0B: a6e3a1 # green + base0C: 94e2d5 # teal + base0D: 89b4fa # blue + base0E: cba6f7 # muave + base0F: f2cdcd # flamingo + diff --git a/example/pillar/shared/wireguard.sls b/example/pillar/shared/wireguard.sls @@ -0,0 +1,26 @@ +#!yaml|gpg + +wireguard: + imports: [] + config: + sg-sng: + PublicKey: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + Endpoint: xx.xx.xx.xx + us-nyc: + PublicKey: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + Endpoint: xx.xx.xx.xx + default: + Address: 10.2.0.2/32 + AllowedIPs: 0.0.0.0/0 + Port: 51820 + PostUp: setup-localdns + PrivateKey: | + -----BEGIN PGP MESSAGE----- + + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxx + -----END PGP MESSAGE----- diff --git a/example/pillar/sys-sync.sls b/example/pillar/sys-sync.sls @@ -0,0 +1,58 @@ +#!yaml|gpg + +sshfs-sync: + client: + personal: + uid: 1001 + home: /rw/sync/personal + key: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + key: | + -----BEGIN PGP MESSAGE----- + + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxx + xxxxx + -----END PGP MESSAGE----- + +rclone: + config: + drive-frankfurt: + type: s3 + provider: xxxxxx + no_check_bucket: "true" + endpoint: xxxx.xxx.xxxxxxxxxxx.com + access_key_id: xxxxxxxxxxxxxxxxxxxx + secret_access_key: | + -----BEGIN PGP MESSAGE----- + + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxx + xxxxx + -----END PGP MESSAGE----- + drive-london: + type: s3 + provider: xxxxxx + no_check_bucket: "true" + endpoint: xxxx.xxx.xxxxxxxxxxx.com + access_key_id: xxxxxxxxxxxxxxxxxxxx + secret_access_key: | + -----BEGIN PGP MESSAGE----- + + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxxxxxx + xxxxx + -----END PGP MESSAGE----- diff --git a/example/pillar/sys-vpn.sls b/example/pillar/sys-vpn.sls @@ -0,0 +1,11 @@ +cloudflared: + port: 5053 + upstream: + - 'https://all.dns.mullvad.net/dns-query' + - 'https://family.adguard-dns.com/dns-query' + +wireguard: + autoconnect: sg-sng + imports: + - sg-sng + - us-nyc diff --git a/example/salt/base/preference.sls b/example/salt/base/preference.sls @@ -0,0 +1,5 @@ + +include: + - base.template + - package.fish + - package.neovim diff --git a/example/salt/base/template.sls b/example/salt/base/template.sls @@ -0,0 +1,8 @@ +include: + - package.https_proxy + - package.fonts + - package.xresources + +base-template-pkgs: + pkg.installed: + - qubes-core-agent-passwordless-root diff --git a/example/salt/config/rclone.sls b/example/salt/config/rclone.sls @@ -0,0 +1,13 @@ +{% set rclone = salt['pillar.get']('rclone') %} + +/rw/config/rclone/rclone.conf: + file.managed: + - makedirs: True + ini.options_present: + - separator: "=" + - strict: True + - sections: {{ rclone.config | yaml }} + +~user/.config/rclone/rclone.conf: + file.symlink: + - target: /rw/config/rclone/rclone.conf diff --git a/example/salt/config/wireguard/files/connect b/example/salt/config/wireguard/files/connect @@ -0,0 +1,53 @@ +#!/usr/bin/env bash + +if [ "${EUID}" -ne 0 ]; then + echo "Please run as root" + exit 1 +fi + +declare -g match=1 +declare -g active=$(wg show | awk -F ': ' '/^interface/{ print $2 }') +mapfile -t available < <(find /etc/wireguard/ -type f | \ + awk '{ match($0,/.*\/(.*)\.conf$/,a); print a[1] }') + +print_status() { + local cyan='\033[0;36m' + local nocolor='\033[0m' + + for connection in "${available[@]}"; do + if [ "$connection" == "$active" ]; then + echo -e "${cyan}$connection${nocolor}" + else + echo "$connection" + fi + done + + if [ "${#available[@]}" -eq 0 ]; then + echo "No wireguard configurations available" + fi +} + +if [ $# -ne 1 ]; then + print_status + exit 1 +fi + +for connection in "${available[@]}"; do + if [ "$connection" == "$1" ]; then + match=0 + break + fi +done + +if [ $match -eq 1 ]; then + echo "err: $1 is not an existing connection" + print_status + exit 1 +fi + + +if [ "$active" ]; then + wg-quick down "$active" +fi + +wg-quick up "$1" diff --git a/example/salt/config/wireguard/init.sls b/example/salt/config/wireguard/init.sls @@ -0,0 +1,56 @@ +{% set wg = salt['pillar.get']('wireguard') %} + +/rw/config/wireguard: + file.directory: + - dir_mode: 0700 + - file_mode: 0600 + +/rw/config/rc.local.d/290-wireguard-symlink.rc: + file.managed: + - mode: 0755 + - makedirs: True + - contents: | + #!/usr/bin/bash + rm -rf /etc/wireguard + ln -s /rw/config/wireguard /etc/wireguard + +{% for name in wg.imports %} + +{% set config = wg.config.default %} +{% set _ = config.update(wg.config[name]) %} + +/rw/config/wireguard/{{ name }}.conf: + file.managed: + - mode: 0600 + ini.options_present: + - separator: "=" + - sections: + Interface: + Address: {{ config.Address }} + PrivateKey: {{ config.PrivateKey }} + PostUp: {{ config.PostUp }} + Peer: + PublicKey: {{ config.PublicKey }} + AllowedIPs: {{ config.AllowedIPs }} + Endpoint: {{ config.Endpoint ~ ':' ~ config.Port }} + +{% endfor %} + +{% if wg.autoconnect %} + +/rw/config/rc.local.d/299-wireguard-autoconnect.rc: + file.managed: + - mode: 0755 + - makedirs: True + - contents: | + #!/usr/bin/bash + [[ "${HOSTNAME}" =~ ^.+-dvm$ ]] && exit 0 + connect {{ wg.autoconnect }} + +{% endif %} + +/usr/local/bin/connect: + file.managed: + - source: salt://{{ slspath }}/files/connect + - mode: 0755 + - makedirs: True diff --git a/example/salt/dom0/f42.sls b/example/salt/dom0/f42.sls @@ -0,0 +1,16 @@ +{% set default = salt['pillar.get']('qubes:default') %} + +fedora-42-minimal: + qvm.template_installed + +f42: + qvm.clone: + - source: fedora-42-minimal + +f42-prefs: + qvm.prefs: + - name: f42 + - label: black + - memory: {{ default.memory }} + - maxmem: {{ default.maxmem }} + - vcpus: {{ default.vcpus }} diff --git a/example/salt/dom0/init.sls b/example/salt/dom0/init.sls @@ -0,0 +1,5 @@ +include: + - .f42 + - .personal + - .sys-sync + - .sys-vpn diff --git a/example/salt/dom0/personal.sls b/example/salt/dom0/personal.sls @@ -0,0 +1,17 @@ +{% set default = salt['pillar.get']('qubes:default') %} + +personal: + qvm.vm: + - name: personal + - present: + - template: f42 + - label: green + - prefs: + - audiovm: {{ default.audiovm }} + - netvm: {{ default.netvm }} + - memory: {{ default.memory }} + - maxmem: {{ default.maxmem }} + - vcpus: {{ default.vcpus }} + - features: + - set: + - gui-window-background-color: '{{ default.winbgcolor }}' diff --git a/example/salt/dom0/sys-sync.sls b/example/salt/dom0/sys-sync.sls @@ -0,0 +1,34 @@ +{% set default = salt['pillar.get']('qubes:default') %} + +sys-sync-template: + qvm.clone: + - source: fedora-42-minimal + +sys-sync-template-prefs: + qvm.prefs: + - name: sys-sync-template + - label: black + - memory: {{ default.memory }} + - maxmem: {{ default.maxmem }} + - vcpus: {{ default.vcpus }} + +sys-sync: + qvm.vm: + - name: sys-sync + - present: + - template: sys-sync-template + - label: blue + - prefs: + - netvm: {{ default.netvm }} + - memory: {{ default.memory }} + - maxmem: {{ default.maxmem }} + - vcpus: {{ default.vcpus }} + - features: + - set: + - gui-window-background-color: '{{ default.winbgcolor }}' + +sys-sync-resize-volume: + cmd.run: + - name: qvm-volume resize sys-sync:private 70G + - onlyif: test 70000000000 -gt $(qvm-volume info sys-sync:private size) + diff --git a/example/salt/dom0/sys-vpn.sls b/example/salt/dom0/sys-vpn.sls @@ -0,0 +1,75 @@ +{% set default = salt['pillar.get']('qubes:default') %} +{% set vpn = salt['pillar.get']('sys-vpn') %} +{% set wg = salt['pillar.get']('wireguard') %} + +sys-vpn-template: + qvm.clone: + - source: fedora-42-minimal + +sys-vpn-template-prefs: + qvm.prefs: + - name: sys-vpn-template + - label: black + - memory: {{ default.memory }} + - maxmem: {{ default.maxmem }} + - vcpus: {{ default.vcpus }} + +{% for key, config in vpn.vms.items() %} +{% set name = 'sys-vpn' if key == 'default' else 'sys-vpn-' ~ key %} + +{{name}}-dvm: + qvm.vm: + - name: {{ name }}-dvm + - present: + - label: black + - template: sys-vpn-template + - prefs: + - memory: {{ default.memory }} + - maxmem: {{ default.maxmem }} + - vcpus: {{ default.vcpus }} + - template_for_dispvms: True + - tags: + - add: + - vpn + +{{name}}: + qvm.vm: + - name: {{ name }} + - present: + - class: DispVM + - template: {{ name }}-dvm + - label: blue + - prefs: + - netvm: sys-firewall + - autostart: {{ config.autostart }} + - provides-network: True + - memory: {{ default.memory }} + - maxmem: {{ default.maxmem }} + - vcpus: {{ default.vcpus }} + - features: + - set: + - gui-window-background-color: '{{ default.winbgcolor }}' + +{{name}}-qvm-firewall: + file.managed: + - name: ~user/.config/qvm-firewall/{{ name }} + - user: user + - group: user + - mode: 0755 + - makedirs: True + - contents: | + #!/bin/bash + qvm-firewall {{ name }} reset + qvm-firewall {{ name }} del --rule-no 0 +{%- for key, config in wg.config.items() %} +{%- if key != 'default' %} + qvm-firewall {{ name }} add accept proto=udp dstports=51820 dsthost={{ config.Endpoint }} +{%- endif %} +{%- endfor %} + qvm-firewall {{ name }} add drop + cmd.run: + - name: /home/user/.config/qvm-firewall/{{ name }} + - onchanges: + - file: {{ name }}-qvm-firewall + +{% endfor %} diff --git a/example/salt/f42.sls b/example/salt/f42.sls @@ -0,0 +1,10 @@ +f42-pkgs: + pkg.installed: + - pkgs: + - feh + - mpv + - mupdf + - pipewire-qubes + - qubes-img-converter + - qubes-pdf-converter + - qubes-usb-proxy diff --git a/example/salt/init.top b/example/salt/init.top @@ -0,0 +1,34 @@ +user: + dom0: + - package.fonts + - package.xresources + - package.fish + - package.neovim + - dom0 + - qubes-sshfs-sync.dom0 + + f42: + - base.preference + - qubes-sshfs-sync.package.client + - f42 + + personal: + - qubes-sshfs-sync.config.client + + sys-sync-template: + - base.template + - package.rclone + - qubes-sshfs-sync.package.server + + sys-sync: + - config.rclone + - qubes-sshfs-sync.config.server + - sys-sync + + sys-vpn-template: + - base.template + - package.wireguard + + qubes:tags:vpn: + - match: pillar + - config.wireguard diff --git a/example/salt/package/fish/files/conf.d/00-colors.fish b/example/salt/package/fish/files/conf.d/00-colors.fish @@ -0,0 +1,18 @@ +{% set color = salt['pillar.get']('theme:color') %} + +set -Ux color_base00 '{{ color.base00 }}' +set -Ux color_base01 '{{ color.base01 }}' +set -Ux color_base02 '{{ color.base02 }}' +set -Ux color_base03 '{{ color.base03 }}' +set -Ux color_base04 '{{ color.base04 }}' +set -Ux color_base05 '{{ color.base05 }}' +set -Ux color_base06 '{{ color.base06 }}' +set -Ux color_base07 '{{ color.base07 }}' +set -Ux color_base08 '{{ color.base08 }}' +set -Ux color_base09 '{{ color.base09 }}' +set -Ux color_base0A '{{ color.base0A }}' +set -Ux color_base0B '{{ color.base0B }}' +set -Ux color_base0C '{{ color.base0C }}' +set -Ux color_base0D '{{ color.base0D }}' +set -Ux color_base0E '{{ color.base0E }}' +set -Ux color_base0F '{{ color.base0F }}' diff --git a/example/salt/package/fish/files/conf.d/main.fish b/example/salt/package/fish/files/conf.d/main.fish @@ -0,0 +1,32 @@ +set fish_greeting "" + +if not contains '/home/user/.local/bin' $PATH + set -gx --prepend PATH '/home/user/.local/bin' +end + +alias ll 'ls -alhGL --group-directories-first --time-style "+%Y-%m-%d"' +alias tree 'tree -a -L 4 --filesfirst --gitignore' + +abbr df 'df -h' +abbr du 'du -h' + +set -gx LESS '-R -M -S --tabs 4 -j.5 --incsearch --mouse --wheel-lines 4' + +bind \ce 'fish_commandline_prepend $EDITOR' +bind -M insert \ce 'fish_commandline_prepend $EDITOR' + +function fish_prompt -d "Write out the prompt" + set -l exit_code $status + set -l exit_code_str "" + + if test $exit_code -ne 0 + set exit_code_str "[$exit_code] " + end + + printf '%s%s%s%s> ' (set_color red)$exit_code_str \ + (set_color $fish_color_cwd) (prompt_pwd) (set_color normal) +end + +function fish_user_key_bindings + fish_vi_key_bindings --no-erase insert +end diff --git a/example/salt/package/fish/files/conf.d/theme.fish b/example/salt/package/fish/files/conf.d/theme.fish @@ -0,0 +1,28 @@ +set fish_color_normal $color_base05 +set fish_color_command --bold +set fish_color_param $color_base05 +set fish_color_keyword --bold +set fish_color_quote $color_base0B +set fish_color_redirection --italics +set fish_color_end --bold +set fish_color_comment $color_base03 +set fish_color_error $color_base08 +set fish_color_gray $color_base04 +set fish_color_selection --background=$color_base02 +set fish_color_search_match --background=$color_base02 +set fish_color_valid_path $color_base0D +set fish_color_option --dim +set fish_color_operator $color_base0D +set fish_color_escape $color_base04 +set fish_color_autosuggestion $color_base04 +set fish_color_cancel $color_base04 +set fish_color_cwd $color_base0D +set fish_color_user $color_base0D +set fish_color_host $color_base0D +set fish_color_host_remote $color_base0D +set fish_color_status $color_base08 +set fish_pager_color_progress $color_base04 +set fish_pager_color_prefix $color_base0D +set fish_pager_color_completion $color_base05 +set fish_pager_color_description $color_base04 +set fish_pager_color_selected_background '' diff --git a/example/salt/package/fish/files/functions/sudo.fish b/example/salt/package/fish/files/functions/sudo.fish @@ -0,0 +1,8 @@ +function sudo -d "sudo wrapper that handles aliases" + if functions -q -- $argv[1] + set -l new_args (string join ' ' -- (string escape -- $argv)) + set argv fish -c "$new_args" + end + + command sudo $argv +end diff --git a/example/salt/package/fish/init.sls b/example/salt/package/fish/init.sls @@ -0,0 +1,19 @@ +fish: + pkg.installed + +fish-loginshell: + file.replace: + - name: /etc/passwd + - pattern: ^((user):.*):(.*)$ + - repl: \1:/bin/fish + +/etc/fish/conf.d: + file.recurse: + - source: salt://{{ slspath }}/files/conf.d + - makedirs: True + - template: jinja + +/etc/fish/functions: + file.recurse: + - source: salt://{{ slspath }}/files/functions + - makedirs: True diff --git a/example/salt/package/fonts.sls b/example/salt/package/fonts.sls @@ -0,0 +1,8 @@ +{% set fonts = salt['grains.filter_by'] ({ + 'Debian': ['fonts-firacode'], + 'RedHat': ['mozilla-fira-mono-fonts'], +}) %} + +base-fonts-pkgs: + pkg.installed: + - pkgs: {{ fonts }} diff --git a/example/salt/package/https_proxy.sls b/example/salt/package/https_proxy.sls @@ -0,0 +1,4 @@ +base-template-proxy: + environ.setenv: + - name: https_proxy + - value: http://127.0.0.1:8082 diff --git a/example/salt/package/neovim.sls b/example/salt/package/neovim.sls @@ -0,0 +1,19 @@ +{% if grains['os_family'] == 'RedHat' %} + +neovim: + pkg.installed + +{% elif grains['os_family'] == 'Debian' %} + +neovim: + pkg.installed: + - name: curl + cmd.run: + - cwd: /tmp + - unless: test -x /usr/bin/nvim + - name: | + curl -LO --proxy http://127.0.0.1:8082/ --tlsv1.2 --proto =https --max-time 180 \ + https://github.com/neovim/neovim/releases/download/v0.11.1/nvim-linux-x86_64.tar.gz + tar xf nvim-linux-x86_64.tar.gz -C /usr --strip-components=1 + +{% endif %} diff --git a/example/salt/package/rclone.sls b/example/salt/package/rclone.sls @@ -0,0 +1,6 @@ +rclone: + pkg.installed: + - pkgs: + - fuse + - qubes-core-agent-networking + - rclone diff --git a/example/salt/package/wireguard.sls b/example/salt/package/wireguard.sls @@ -0,0 +1,5 @@ +wireguard: + pkg.installed: + - pkgs: + - qubes-core-agent-networking + - wireguard-tools diff --git a/example/salt/package/xresources.sls b/example/salt/package/xresources.sls @@ -0,0 +1,12 @@ +xresources-config: + file.managed: + - name: /etc/X11/Xresources + - replace: False + +xresources-dpi: + file.keyvalue: + - name: /etc/X11/Xresources + - separator: ":" + - append_if_not_found: True + - key_values: + Xft.dpi: 192 diff --git a/example/salt/sys-sync.sls b/example/salt/sys-sync.sls @@ -0,0 +1,56 @@ +{% set sshfs = salt['pillar.get']('sshfs-sync') %} + +/rw/config/rc.local.d/10-rclone-log-setup.rc: + file.managed: + - mode: 0755 + - makedirs: True + - contents: | + #!/bin/sh + mkdir -p /var/log/rclone + chown root:sftponly /var/log/rclone + chmod 775 /var/log/rclone + +{% for name, user in sshfs.get('client', {}).items() %} + +sys-sync-cloud-{{name}}: +{{ user.home }}/sync: + file.directory: + - user: {{ user.uid }} + - group: 1001 + - makedirs: True + - onlyif: test -d {{ user.home }}/sync + +/rw/config/rc.local.d/30-mount-rclone-{{name}}.rc: + file.managed: + - mode: 0755 + - makedirs: True + - contents: | + #!/bin/sh + sudo -u sync-{{ name }} rclone mount \ + sync:{{ name }} {{ user.home }}/sync \ + --config=/rw/config/rclone/rclone.conf \ + --cache-dir={{ user.home }}/.cache/rclone \ + --log-file=/var/log/rclone/mount-{{name}}.log \ + --uid {{ user.uid }} \ + --gid 1001 \ + --no-modtime \ + --vfs-cache-mode=full \ + --vfs-cache-min-free-space=1G \ + --vfs-fast-fingerprint \ + --vfs-used-is-size \ + --vfs-cache-max-age=12w \ + --dir-cache-time=12w \ + --progress \ + --stats=1m \ + --stats-one-line-date \ + --log-level=DEBUG \ + --daemon & disown + +{% endfor %} + +~user/.cache/rclone: + file.directory: + - user: user + - group: user + - mode: 0755 + - makedirs: True