# Mgmt # Copyright (C) James Shubin and the project contributors # Written by James Shubin and the project contributors # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # # Additional permission under GNU GPL version 3 section 7 # # If you modify this program, or any covered work, by linking or combining it # with embedded mcl code and modules (and that the embedded mcl code and # modules which link with this program, contain a copy of their source code in # the authoritative form) containing parts covered by the terms of any other # license, the licensors of this program grant you additional permission to # convey the resulting work. Furthermore, the licensors of this program grant # the original author, James Shubin, additional permission to update this # additional permission if he deems it necessary to achieve the goals of this # additional permission. import "fmt" import "golang" import "golang/strings" as golang_strings import "iter" import "local" import "net" import "os" import "strings" import "world/collect" # ssh_keygen creates an ssh key pair in the user's home directory if the private # key doesn't exist. # TODO: add more parameters such as key size and type in the future class ssh_keygen($user) { include ssh_keygen_type($user, "ed25519") } # $type is rsa or ed25519 class ssh_keygen_type($user, $type) { panic($user == "") # panic if $user is empty panic($type == "") # panic if $type is empty panic($type != "rsa" and $type != "ed25519") # panic if $type is invalid $p = os.expand_home("~${user}/") # eg: ~james/ exec "ssh-keygen-${user}" { cmd => "/usr/bin/ssh-keygen", args => [ "-t", "${type}", # type "-f", "${p}.ssh/id_${type}", # private key file "-N", "", # empty password ], creates => "${p}.ssh/id_${type}", user => $user, Before => File["${p}.ssh/id_${type}"], } # This also serves as a "handle" so that other resources can depend on # this file getting created before they run. file "${p}.ssh/" { state => "exists", mode => "u=rwx,go=", owner => $user, } file "${p}.ssh/id_${type}" { mode => "u=rw,go=", owner => $user, Depend => File["${p}.ssh/"], } line "${user}@${hostname}" { #file => "", # specified on collect #state => "exists", # specified on collect content => os.readfilewait("${p}.ssh/id_${type}.pub"), Meta:hidden => true, Meta:export => ["*",], } } # ssh_authorized_keys pulls down an export key for a $user, from a user@host. class ssh_authorized_keys($user, $from) { panic($user == "") # panic if $user is empty $p = os.expand_home("~${user}/") # eg: ~james/ $all = collect.res("line") # []struct{name str; host str;} $fn = func($st) { $st->name == "${from}" and $st->host != "${hostname}" } $filtered = iter.filter($all, $fn) collect line $filtered { # pull down everyone's public keys file => "${p}.ssh/authorized_keys", state => $const.res.file.state.exists, Meta:hidden => false, } } # network_rename takes the device with the $mac address and renames it to $dev. class network_rename($mac, $dev) { #panic(not net.is_mac("${mac}")) file "/etc/systemd/network/70-rename-${dev}.link" { state => "exists", content => " # Pick the device name based on the mac address. [Match] MACAddress=${mac} [Link] Name=${dev} ", mode => "u=rw,go=r", owner => "root", group => "root", Notify => Exec["udevadm trigger"], } # TODO: we only want to run this once, but it's harmless for now exec "udevadm trigger" { cmd => "/usr/sbin/udevadm trigger --type=all --action=add --prioritized-subsystem=net --settle", } } # networkd_dhcp sets up a dhcp client with systemd-networkd. class networkd_dhcp($dev, $st) { $dns = $st->dns || ["8.8.8.8",] $tmpl = " [Match] Name=${dev} [Network] DHCP=yes {{ range .dns -}} DNS={{ . }} {{ end -}} #UseGateway=false [DHCP] UseDNS=false RouteMetric=100 " $args = struct{ #dev => $dev, dns => $dns, } file "/etc/systemd/network/${dev}-dhcp.network" { state => "exists", content => golang.template($tmpl, $args), mode => "u=rw,go=r", owner => "root", group => "root", Notify => Svc["systemd-networkd"], } svc "systemd-networkd" { state => "running", startup => "enabled", } } # networkd_static sets up a static ip address with systemd-networkd. class networkd_static($dev, $st) { $cidr = $st->cidr # cidr $ip = net.cidr_to_ip($cidr) $prefix = net.cidr_to_prefix($cidr) #$router = $st->router || "" $dns = $st->dns || ["8.8.8.8",] #$vips []str = $st->vips || [] # []cidr $tmpl = " [Match] Name=${dev} [Network] Address=${ip}/${prefix} {{ range .dns -}} DNS={{ . }} {{ end -}} #UseGateway=false [Address] RouteMetric=101 [Route] Metric=1001 #Gateway= #Destination=0.0.0.0/0 " $args = struct{ #dev => $dev, dns => $dns, } file "/etc/systemd/network/${dev}-static.network" { state => "exists", content => golang.template($tmpl, $args), mode => "u=rw,go=r", owner => "root", group => "root", Notify => Svc["systemd-networkd"], } svc "systemd-networkd" { state => "running", startup => "enabled", } } # network_manager_dhcp sets up a dhcp client with network manager. # NOTE: To see what it's using run: `nmcli -f name,uuid,filename connection`. class network_manager_dhcp($st) { $uuid = $st->uuid || "" # 01234567-89ab-cdef-0123-456789abcdef $mac = $st->mac || "" $dev = $st->dev || "eth0" $dns = $st->dns || ["8.8.8.8",] $dns_str = golang_strings.join($dns, ";") # the line also ends with a semicolon $tmpl = " [connection] id=${dev} {{ if .uuid -}} uuid={{ .uuid }} {{ end -}} type=ethernet interface-name=${dev} autoconnect=true [ipv4] {{ if .dns -}} dns=${dns_str}; {{ end -}} dns-search= may-fail=false method=auto [ethernet] {{ if .mac -}} mac-address={{ .mac }} {{ end -}} " $args = struct{ uuid => $uuid, mac => $mac, dns => $dns, } file "/etc/NetworkManager/system-connections/${dev}.nmconnection" { state => "exists", content => golang.template($tmpl, $args), mode => "u=rw,go=", owner => "root", Notify => Svc["NetworkManager"], } svc "NetworkManager" { } } # network_manager_static sets up a static ip address with network manager. # NOTE: To see what it's using run: `nmcli -f name,uuid,filename connection`. class network_manager_static($st) { $uuid = $st->uuid || "" # 01234567-89ab-cdef-0123-456789abcdef $mac = $st->mac || "" $dev = $st->dev || "eth0" $cidr = $st->cidr # cidr $ip = net.cidr_to_ip($cidr) $prefix = net.cidr_to_prefix($cidr) $router = $st->router || "" $dns = $st->dns || ["8.8.8.8",] $vips []str = $st->vips || [] # []cidr $dns_str = golang_strings.join($dns, ";") # the line also ends with a semicolon $tmpl = " [connection] id=${dev} {{ if .uuid -}} uuid={{ .uuid }} {{ end -}} type=ethernet interface-name=${dev} autoconnect=true [ipv4] {{ if .router -}} address1=${ip}/${prefix},{{ .router }} {{ else -}} address1=${ip}/${prefix} {{ end -}} {{ if .vips -}} {{ range $index, $ip := .vips }} {{ $ix := len (printf \"xx%*s\" $index \"\") -}} address{{ $ix }}={{ $ip }} {{ end -}} {{ end -}} {{ if .dns -}} dns=${dns_str}; {{ end -}} dns-search= may-fail=false method=manual [ethernet] {{ if .mac -}} mac-address={{ .mac }} {{ end -}} " $args = struct{ uuid => $uuid, mac => $mac, router => $router, dns => $dns, vips => $vips, } file "/etc/NetworkManager/system-connections/${dev}.nmconnection" { state => "exists", content => golang.template($tmpl, $args), mode => "u=rw,go=", owner => "root", Notify => Svc["NetworkManager"], } svc "NetworkManager" { } } # systemd_daemon_reload creates an exec which runs a systemctl daemon-reload if # one is needed. The exec resource uses the input name, to make it easy to # attach an edge to it. # TODO: can we use: systemctl show foo.service --property=NeedDaemonReload class systemd_daemon_reload($name) { $vardir = local.vardir("misc/") $mtime_file = "${vardir}daemon-reload" exec "${name}" { cmd => "/usr/bin/systemctl", args => [ "daemon-reload", ], donecmd => "/usr/bin/date --utc > ${mtime_file}", doneshell => "/usr/bin/bash", watchfiles => [ "/lib/systemd/system/", "/etc/systemd/system/", "${mtime_file}", ], # If we have any file that's newer than our mtime, the run. # NOTE: We grep since find doesn't return a useful return code. ifcmd => "/usr/bin/test ! -e '${mtime_file}' || /usr/bin/find /lib/systemd/system/ /etc/systemd/system/ -type f -name '*.service' -newer '${mtime_file}' | /usr/bin/grep -q .", ifshell => "/usr/bin/bash", } } # Instead of running `dnf copr enable purpleidea/foo` you can use # `purpleidea/foo` as the $name here to accomplish the same thing. class copr_enable($name) { # example: sudo dnf copr enable gsauthof/dracut-sshd # /etc/yum.repos.d/_copr:copr.fedorainfracloud.org:gsauthof:dracut-sshd.repo $sp = strings.split($name, "/") $user = $sp[0] $repo = $sp[1] $p = fmt.printf("/etc/yum.repos.d/_copr:copr.fedorainfracloud.org:%s:%s.repo", $user, $repo) # TODO: If we wanted to be more thorough, we could template the file # instead or running this copr enable command. exec "dnf-copr-enable-${name}" { cmd => "/usr/bin/dnf", args => [ "-y", "copr", "enable", "${name}", ], creates => $p, user => "root", } }