summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--manifests/site.pp346
-rw-r--r--modules/blog/files/footers/about.md11
-rw-r--r--modules/blog/files/footers/contact.md12
-rw-r--r--modules/blog/files/footers/legal.md28
-rw-r--r--modules/blog/files/footers/qna.md13
-rw-r--r--modules/blog/manifests/init.pp78
-rw-r--r--modules/cgit/files/filters/hugo-authentication.lua331
-rwxr-xr-xmodules/cgit/files/filters/hugo-highlighting.sh138
-rwxr-xr-xmodules/cgit/files/filters/hugo-pre.sh15
-rw-r--r--modules/cgit/files/logo.pngbin0 -> 16244 bytes
-rw-r--r--modules/cgit/files/logo_large.pngbin0 -> 105311 bytes
-rw-r--r--modules/cgit/files/root_readme17
-rw-r--r--modules/cgit/manifests/init.pp62
-rw-r--r--modules/cgit/templates/cgitrc.epp64
-rw-r--r--modules/exports/manifests/init.pp21
-rw-r--r--modules/losetup/files/dismantle-loop-device15
-rw-r--r--modules/losetup/files/loop@.service12
-rwxr-xr-xmodules/losetup/files/setup-loop-device16
-rw-r--r--modules/losetup/manifests/init.pp24
-rw-r--r--modules/nsupdate/manifests/init.pp64
-rw-r--r--modules/nsupdate/manifests/setup.pp14
-rw-r--r--modules/nsupdate/templates/nsupdate.epp18
-rw-r--r--modules/overlay/manifests/init.pp17
-rw-r--r--modules/pacman/files/mirrorlist6
-rw-r--r--modules/pacman/manifests/hook.pp80
-rw-r--r--modules/pacman/manifests/init.pp44
-rw-r--r--modules/pacman/manifests/repo.pp24
-rw-r--r--modules/pacman/templates/hook.epp31
-rw-r--r--modules/pass/lib/puppet/functions/pass.rb21
-rw-r--r--modules/profiles/files/firewall/rules.v415
-rwxr-xr-xmodules/profiles/files/passmenu29
-rw-r--r--modules/profiles/files/ssh-agent.service13
-rw-r--r--modules/profiles/lib/facter/pacman_version.rb7
-rw-r--r--modules/profiles/manifests/dolphin.pp70
-rw-r--r--modules/profiles/manifests/firewall.pp19
-rw-r--r--modules/profiles/manifests/gandalf_web.pp102
-rw-r--r--modules/profiles/manifests/group_profile.pp16
-rw-r--r--modules/profiles/manifests/imagemagick.pp17
-rw-r--r--modules/profiles/manifests/remarkable.pp31
-rw-r--r--modules/profiles/manifests/syncthing.pp28
-rw-r--r--modules/profiles/manifests/synth.pp33
-rw-r--r--modules/profiles/manifests/transmission.pp71
-rw-r--r--modules/profiles/manifests/webdav_server.pp80
-rw-r--r--modules/profiles/manifests/workstation.pp132
-rw-r--r--modules/profiles/manifests/workstation/archlinux.pp52
-rw-r--r--modules/profiles/manifests/xmonad.pp29
-rw-r--r--modules/profiles/templates/aconnect.epp8
-rw-r--r--modules/profiles/templates/imagemagick-policy.xml.epp89
-rw-r--r--modules/profiles/templates/keyvalue.epp4
-rw-r--r--modules/profiles/templates/transmission.json.epp77
-rw-r--r--modules/shiori/files/shiori.service13
-rw-r--r--modules/shiori/manifests/init.pp97
-rw-r--r--modules/systemd_mount/manifests/init.pp42
-rw-r--r--modules/systemd_mount/templates/automount.epp11
-rw-r--r--modules/systemd_mount/templates/mount.epp13
-rwxr-xr-xrun26
56 files changed, 2634 insertions, 12 deletions
diff --git a/manifests/site.pp b/manifests/site.pp
index 430e3c6..8ba8b9a 100644
--- a/manifests/site.pp
+++ b/manifests/site.pp
@@ -1,15 +1,3 @@
-node 'gandalf.adrift.space' {
- notify { 'anything':
- message => 'Will this be shown',
- }
-}
-
-node 'hornquist.se' {
- notify { 'anything':
- message => 'Hello, World!',
- }
-}
-
node 'busting.adrift.space' {
file_line { 'hosts ourself':
@@ -81,4 +69,338 @@ node 'busting.adrift.space' {
port => 80,
}
+node 'gandalf.adrift.space' {
+ profiles::remarkable { 'any name':
+ addr => '3',
+ }
+
+ include ::losetup
+ include ::profiles::xmonad
+ include ::profiles::workstation
+
+ include ::profiles::dolphin
+ include ::profiles::imagemagick
+
+ systemd_mount { '/usr/net':
+ what => 'elrond:/files',
+ where => '/usr/net',
+ wantedBy => 'remote-fs.target',
+ automount => true,
+ }
+
+ class { '::profiles::syncthing':
+ enable_for => [ 'hugo', ],
+ }
+
+ $certname = 'adrift.space'
+
+ class { '::profiles::gandalf_web':
+ certname => $certname,
+ }
+
+ nginx::resource::location { '/varselklotet':
+ server => 'gandalf',
+ location_alias => '/home/hugo/wiki/varselklotet',
+ try_files => ['$uri', '$uri/', '=404'],
+ autoindex => 'on',
+ ssl => true,
+ ssl_only => true,
+ index_files => [],
+ }
+
+ class { 'profiles::transmission':
+ nginx_server => 'gandalf',
+ }
+
+ class { 'shiori':
+ port => 8081,
+ group_members => [
+ 'hugo',
+ ],
+ nginx => {
+ server_name => "bookmark.${facts['fqdn']}",
+ certname => $certname,
+ }
+ }
+
+ profiles::webdav_server { '/dav':
+ file_path => '/var/www/webdav',
+ users => [['hugo', pass('adrift.space/gandalf/dav/hugo')]],
+ nginx_server => 'gandalf'
+ }
+
+}
+
+node 'hornquist.se' {
+
+ include ::profiles::firewall
+
+ ensure_packages([
+ 'cowsay',
+ ], { ensure => installed })
+
+ nsupdate { 'hornquist.se':
+ ensure => present,
+ nameserver => 'ns2.adrift.space',
+ iface => 'eth0',
+ records => [
+ { type => 'A', ttl => 3600, domain => 'hornquist.se' },
+ { type => 'A', ttl => 3600, domain => '*.hornquist.se' },
+ ],
+ }
+
+ $cgit_root = '/var/www/cgit'
+ $blog_root = '/var/www/blog'
+
+ class { '::cgit':
+ root => $cgit_root,
+ root_title => 'Hornquist Git Repositiories',
+ root_desc => 'ᛏᚨᚾᛞᛖᛋ᛫ᛖᚾᛞᚨᛋᛏ᛫ᛗᛟᛏ᛫ᛚᚨᛞᚨᚾᛋ᛫ᛈᛚᚨᚾ',
+ about_filter => 'hugo-pre.sh',
+ auth_filter => 'hugo-authentication.lua',
+ source_filter => 'hugo-highlighting.sh',
+ scan_path => '/home/git/git',
+ clone_url => [
+ 'https://git.hornquist.se/$CGIT_REPO_URL',
+ 'git@hornquist.se:git/$CGIT_REPO_URL.git',
+ '(HTTP_only_for_public_repos)',
+ ],
+ }
+
+ # https://buddy.works/blog/how-deploy-projects-with-git
+ blog { 'Hugos blog':
+ root => "${blog_root}/hugo",
+ }
+
+ service { 'php7.4-fpm':
+ ensure => running,
+ enable => true,
+ }
+
+ service { 'fcgiwrap.socket':
+ ensure => running,
+ enable => true,
+ }
+
+ file { '/etc/systemd/system/php7.4-fpm.service.d':
+ ensure => directory,
+ }
+
+ file { '/etc/systemd/system/php7.4-fpm.service.d/override.conf':
+ ensure => file,
+ notify => Service['php7.4-fpm'],
+ content => @(EOF)
+ [Service]
+ RuntimeDirectory=php
+ | EOF
+ }
+
+ # include apt
+ class { '::nginx':
+ manage_repo => false,
+ server_purge => true,
+ service_config_check => true,
+ }
+
+ $certname = 'hornquist'
+
+ class { '::letsencrypt':
+ config => {
+ email => 'hugo.hornquist@gmail.com',
+ }
+ # renew_cron_ensure => present,
+ }
+
+ ensure_packages(['python3-certbot-nginx'],
+ { ensure => installed })
+
+ letsencrypt::certonly { $certname:
+ ensure => present,
+ manage_cron => true,
+ plugin => 'nginx',
+ additional_args => [ '--quiet', ],
+ post_hook_commands => [ 'systemctl reload nginx.service', ],
+ domains => [
+ 'blog.hornquist.se',
+ 'blogg.hornquist.se',
+ 'www.hornquist.se',
+ 'hornquist.se',
+ 'git.hornquist.se',
+ 'wiki.hornquist.se',
+ ],
+ }
+
+ nginx::resource::server { 'blogg':
+ server_name => [ 'blogg.hornquist.se', ],
+ access_log => 'absent',
+ error_log => 'absent',
+ ssl => true,
+ ssl_cert => "/etc/letsencrypt/live/${certname}/fullchain.pem",
+ ssl_key => "/etc/letsencrypt/live/${certname}/privkey.pem",
+ use_default_location => false,
+ server_cfg_append => {
+ 'return' => '301 $scheme://blog.hornquist.se$request_uri',
+ }
+ }
+
+ nginx::resource::server { 'blog':
+ server_name => [
+ 'blog.hornquist.se',
+ ],
+ access_log => 'absent',
+ error_log => 'absent',
+ index_files => [ 'index.php', 'index.html', 'index.htm', ],
+ ssl => true,
+ ssl_cert => "/etc/letsencrypt/live/${certname}/fullchain.pem",
+ ssl_key => "/etc/letsencrypt/live/${certname}/privkey.pem",
+ use_default_location => false,
+ www_root => $blog_root,
+ }
+
+ nginx::resource::server { 'hornquist':
+ server_name => [
+ 'hornquist.se',
+ 'www.hornquist.se',
+ '_',
+ ],
+ access_log => 'absent',
+ error_log => 'absent',
+ index_files => [ 'index.php', 'index.html', 'index.htm', ],
+ listen_options => 'default_server',
+ ssl => true,
+ ssl_cert => '/etc/letsencrypt/live/hornquist/fullchain.pem',
+ ssl_key => '/etc/letsencrypt/live/hornquist/privkey.pem',
+ use_default_location => false,
+ www_root => '/var/www/html',
+ # autoindex => 'on',
+ }
+
+ nginx::resource::server { 'userdir':
+ server_name => [
+ '~^(?P<uname>[a-z][-a-z0-9]*)\.hornquist\.se',
+ ],
+ access_log => 'absent',
+ error_log => 'absent',
+ index_files => [ 'index.cgi', 'index.php', 'index.html', 'index.htm', ],
+ # SSL sites for userdir lack a cert due to wildcard certificates being
+ # "problematic". However, it's enabled here since a ::location with
+ # ssl => true WILL generate locations, even when server is missing.
+ ssl => true,
+ ssl_cert => "/etc/letsencrypt/live/${certname}/fullchain.pem",
+ ssl_key => "/etc/letsencrypt/live/${certname}/privkey.pem",
+ use_default_location => false,
+ www_root => '/home/$uname/.public',
+ # autoindex => 'on',
+ }
+
+
+ nginx::resource::server { 'wiki':
+ server_name => [
+ 'wiki.hornquist.se',
+ ],
+ access_log => 'absent',
+ error_log => 'absent',
+ index_files => [ 'index.html', ],
+ ssl => true,
+ ssl_cert => "/etc/letsencrypt/live/${certname}/fullchain.pem",
+ ssl_key => "/etc/letsencrypt/live/${certname}/privkey.pem",
+ use_default_location => true,
+ www_root => '/var/www/wiki/public/html',
+ }
+
+ nginx::resource::server { 'cgit':
+ server_name => [
+ 'git.hornquist.se',
+ ],
+ access_log => 'absent',
+ error_log => 'absent',
+ index_files => [],
+ try_files => [ '$uri', '@cgit' ],
+ ssl => true,
+ ssl_cert => "/etc/letsencrypt/live/${certname}/fullchain.pem",
+ ssl_key => "/etc/letsencrypt/live/${certname}/privkey.pem",
+ use_default_location => true,
+ www_root => $cgit_root,
+ ssl_redirect => true,
+ # /usr/lib/cgit/cgit.cgi
+ # /usr/share/cgit/cgit.css
+ }
+
+ nginx::resource::location { '@cgit':
+ fastcgi_params => 'fastcgi_params',
+ fastcgi_param => {
+ 'SCRIPT_FILENAME' => '/usr/lib/cgit/cgit.cgi',
+ 'PATH_INFO' => '$fastcgi_script_name',
+ 'QUERY_STRING' => '$args',
+ },
+ ssl_only => true,
+ fastcgi => 'unix:/run/fcgiwrap.socket',
+ server => [
+ 'cgit',
+ ],
+ }
+
+ # TODO extra locations for dumb git clones?
+
+ nginx::resource::location { '/':
+ try_files => ['$uri', '$uri/', '=404'],
+ index_files => [],
+ ssl => true,
+ autoindex => on,
+ server => [
+ 'blog',
+ 'hornquist',
+ 'userdir',
+ ]
+ }
+
+ nginx::resource::location { '/nyar':
+ location_custom_cfg => { return => '307 http://www.lysator.liu.se/~hugo/song/2018.html' },
+ ssl => true,
+ index_files => [],
+ server => [ 'hornquist', ],
+ }
+
+ nginx::resource::location { '= /':
+ # temprory redirect
+ location_custom_cfg => { return => '307 /hugo' },
+ ssl => true,
+ index_files => [],
+ server => [ 'blog', ],
+ }
+
+ nginx::resource::location { '~ \.php$':
+ fastcgi_params => 'snippets/fastcgi-php.conf',
+ fastcgi => 'unix:/run/php/php-fpm.sock',
+ ssl => true,
+ server => [
+ 'blog',
+ 'hornquist',
+ 'userdir',
+ ],
+ }
+
+ nginx::resource::location { '~ \.cgi$':
+ fastcgi_params => 'fastcgi_params',
+ fastcgi_param => {
+ # 'SCRIPT_FILENAME' => '$document_root/*.cgi',
+ 'PATH_INFO' => '$fastcgi_script_name',
+ 'QUERY_STRING' => '$args',
+ },
+ fastcgi => 'unix:/run/fcgiwrap.socket',
+ server => [
+ 'userdir',
+ ],
+ }
+
+ nginx::resource::location { '~ /\.ht':
+ location_cfg_append => { deny => 'all' },
+ index_files => [],
+ ssl => true,
+ server => [
+ 'blog',
+ 'hornquist',
+ 'userdir',
+ ],
+ }
}
diff --git a/modules/blog/files/footers/about.md b/modules/blog/files/footers/about.md
new file mode 100644
index 0000000..ffec622
--- /dev/null
+++ b/modules/blog/files/footers/about.md
@@ -0,0 +1,11 @@
+#About
+
+This is a blog and website written and maintained by Hugo Hornquist (me).
+
+I will primarily post computer related information, and mostly in Swedish.
+Some entries might however be written in English to allow for a temporary larger audience.
+The about pages are written in English for that reason (Hello! Non Swedes!).
+
+---
+
+A (hopefully) up to date version of the source code for the website is available [here](//github.com/HugoNikanor/website-blog-2). However, be aware that any blog entries, the four files in the bottom bar as well as some other parts of the website are not included in that repository.
diff --git a/modules/blog/files/footers/contact.md b/modules/blog/files/footers/contact.md
new file mode 100644
index 0000000..651fec4
--- /dev/null
+++ b/modules/blog/files/footers/contact.md
@@ -0,0 +1,12 @@
+#Contact
+
+There are many ways to contact me, and most of them serve different purposes.
+
+All regular blog entries should have a comment field where you are more than welcome to write your thoughts. I do however reserve the full right to moderate the comments, and remove comments of an unacceptable nature (you are however free to disagree with me).
+
+I have an email address for other general contact at <hugo.hornquist@gmail.com>.
+
+A Twitter account is also present for twittery stuff. [@hugonikanor](//twitter.com/hugonikanor)
+
+Feel free to contact me in another way if you find out how, I can't however guarantee that I will answer your queries regardless of communications method.
+
diff --git a/modules/blog/files/footers/legal.md b/modules/blog/files/footers/legal.md
new file mode 100644
index 0000000..87966c7
--- /dev/null
+++ b/modules/blog/files/footers/legal.md
@@ -0,0 +1,28 @@
+#Legal
+
+Website built by Hugo Hornquist (me), however, some parts are made by other people with their permission.
+
+###[Parsedown](//parsedown.org)
+Parsedown is used for tracing the markdown files the entries are written in into proper html.
+It is used under the MIT License, readable [here](https://raw.githubusercontent.com/erusev/parsedown/master/LICENSE.txt)
+
+###[imgur](//imgur.com)
+Most images are hosted by imgur. You can read the appropriate terms of service [here](//imgur.com/tos).
+
+### [RSS Logo](//www.mozilla.org/en-US/foundation/feed-icon-guidelines/)
+This is my public statement that I try to follow these guidelines.
+
+---
+
+##Disclaimer
+
+Any text on this website is subject to change at any time. Everything that I write is in some way shape or form connected to me. Please refrain from being majorly offended by my opinions and remember that I have moderation capabilities.
+
+---
+
+Upon pressing the following button you agree to give your soul to whatever form of devil you believe in. If a deity of required sort isn't available then you instead surrender your legally owned land to the former communist state.
+
+<form action="">
+<input type="button" name="btn" value="I accept" onclick="alert('You have made a terrible choice')"></input>
+</form>
+<br>
diff --git a/modules/blog/files/footers/qna.md b/modules/blog/files/footers/qna.md
new file mode 100644
index 0000000..baa611a
--- /dev/null
+++ b/modules/blog/files/footers/qna.md
@@ -0,0 +1,13 @@
+#Questions and Answers
+
+**Q: Who are you?**
+A: *Who knows, but I have a blog that you can read [here](https://www.youtube.com/watch?v=dQw4w9WgXcQ).*
+
+**Q: What comment system are you using?**
+A: *My own comment system, available [here](//github.com/hugonikanor/website-comment-system)*
+
+**Q: Do you have an RSS feed?**
+A: *[I do now!](./rss.php)*
+
+##Have a question?
+If you have a question of your own you would like to see answered, [contact me](./?filename=contact.md).
diff --git a/modules/blog/manifests/init.pp b/modules/blog/manifests/init.pp
new file mode 100644
index 0000000..a639311
--- /dev/null
+++ b/modules/blog/manifests/init.pp
@@ -0,0 +1,78 @@
+define blog (
+ String $root,
+) {
+ vcsrepo { $root:
+ ensure => latest,
+ provider => git,
+ source => 'https://github.com/HugoNikanor/website-blog-2.git',
+ revision => 'master',
+ # keep_local_changes => true,
+ owner => 'hugo',
+ group => 'www-data',
+ }
+
+ vcsrepo { "${root}/entries":
+ ensure => latest,
+ provider => git,
+ source => '/home/git/git/blog-entries.git',
+ revision => 'master',
+ owner => 'hugo',
+ group => 'www-data',
+ }
+
+ file { "${root}/settings.php":
+ ensure => file,
+ content => @(EOF)
+ <?php
+ # FILE MANAGED BY PUPPET
+ $author = "Hugo Hornquist";
+ $blog_title = "HugoNikanors blogg‽";
+ $blog_subtitle = "A blog about nothing, but mostly itself.";
+ $http_host = $_SERVER["HTTP_HOST"];
+ $urlbase = "http://$http_host/hugo";
+ $has_comments = false;
+ | EOF
+ }
+
+ file { "${root}/footnote":
+ ensure => directory,
+ recurse => true,
+ }
+
+ $foot_files = [
+ ['about.md', 'About'],
+ ['contact.md', 'Contact'],
+ ['legal.md', 'Legal'],
+ ['qna.md', '"Q&amp;A"'],
+ ]
+
+ $foot_files.each |$item| {
+ file { "${root}/footnote/${item[0]}":
+ source => "puppet:///modules/blog/footers/${item[0]}",
+ }
+ }
+
+
+ $files_ini = join($foot_files.map |$item| {
+ @("EOF")
+ files[] = ${item[0]}
+ title[] = ${item[1]}
+ | EOF
+ })
+
+ file { "${root}/special-files.ini":
+ ensure => file,
+ content => @("EOF")
+ ; FILE MANAGED BY PUPPET
+ ;
+ ; Which files on the website that are "special"
+ ; This basicly means that they shouldn't have comments
+ [footnote]
+ ${files_ini}
+
+ [other]
+ files[] = list
+ files[] = entry-not-found.md
+ | EOF
+ }
+}
diff --git a/modules/cgit/files/filters/hugo-authentication.lua b/modules/cgit/files/filters/hugo-authentication.lua
new file mode 100644
index 0000000..df97579
--- /dev/null
+++ b/modules/cgit/files/filters/hugo-authentication.lua
@@ -0,0 +1,331 @@
+-- This script may be used with the auth-filter. Be sure to configure it as you wish.
+--
+-- Requirements:
+-- luaossl
+-- <http://25thandclement.com/~william/projects/luaossl.html>
+-- luaposix
+-- <https://github.com/luaposix/luaposix>
+--
+local sysstat = require("posix.sys.stat")
+local unistd = require("posix.unistd")
+local rand = require("openssl.rand")
+local hmac = require("openssl.hmac")
+
+--
+--
+-- Configure these variables for your settings.
+--
+--
+
+-- A list of password protected repositories along with the users who can access them.
+local protected_repos = {
+ glouglou = { laurent = true, jason = true },
+ qt = { jason = true, bob = true }
+}
+
+local public_repos = {}
+public_repos["wiki-public" ] = true
+public_repos["lyslib" ] = true
+public_repos["calp" ] = true
+public_repos["texttv" ] = true
+public_repos["scheme-monad" ] = true
+public_repos["scheme/math-parse" ] = true
+public_repos["file-descriptor-graph"] = true
+
+-- A list of users and hashes, generated with `mkpasswd -m sha-512 -R 300000`.
+local users = {
+ hugo = "$6$13z1Pf.U8itrCRX6$g2BZpaMk1CLiT6117paWXB2qdQFRc3rsGWL4iF5h5QbHo27oljTdHk69oQWAvqlVf13aLTUF3nYw65lEp88r/1"
+}
+
+-- Set this to a path this script can write to for storing a persistent
+-- cookie secret, which should be guarded.
+local secret_filename = "/var/cache/cgit/auth-secret"
+
+--
+--
+-- Authentication functions follow below. Swap these out if you want different authentication semantics.
+--
+--
+
+-- Sets HTTP cookie headers based on post and sets up redirection.
+function authenticate_post()
+ local hash = users[post["username"]]
+ local redirect = validate_value("redirect", post["redirect"])
+
+ if redirect == nil then
+ not_found()
+ return 0
+ end
+
+ redirect_to(redirect)
+
+ if hash == nil or hash ~= unistd.crypt(post["password"], hash) then
+ set_cookie("cgitauth", "")
+ else
+ -- One week expiration time
+ local username = secure_value("username", post["username"], os.time() + 604800)
+ set_cookie("cgitauth", username)
+ end
+
+ html("\n")
+ return 0
+end
+
+
+-- Returns 1 if the cookie is valid and 0 if it is not.
+function authenticate_cookie()
+
+ -- Everyone has access to the index page.
+ -- printenv(os.getenv("CGIT_REPO_NAME"));
+ if cgit["repo"] == "" then
+ return 1
+ end
+
+ ispublic = public_repos[cgit["repo"]]
+ -- accepted_users = protected_repos[cgit["repo"]]
+ if ispublic == true then
+ -- We return as valid if the repo is public
+ return 1
+ end
+
+ local username = validate_value("username", get_cookie(http["cookie"], "cgitauth"))
+ if username == nil then
+ return 0
+ else
+ return 1
+ end
+end
+
+-- Prints the html for the login form.
+function body()
+ html("<h2>Authentication Required</h2>")
+ -- html("HTML = ")
+ -- html(tostring(cgit["repo"] == ""))
+ html("<form method='post' action='")
+ html_attr(cgit["login"])
+ html("'>")
+ html("<input type='hidden' name='redirect' value='")
+ html_attr(secure_value("redirect", cgit["url"], 0))
+ html("' />")
+ html("<table>")
+ html("<tr><td><label for='username'>Username:</label></td><td><input id='username' name='username' autofocus /></td></tr>")
+ html("<tr><td><label for='password'>Password:</label></td><td><input id='password' name='password' type='password' /></td></tr>")
+ html("<tr><td colspan='2'><input value='Login' type='submit' /></td></tr>")
+ html("</table></form>")
+
+ return 0
+end
+
+
+
+--
+--
+-- Wrapper around filter API, exposing the http table, the cgit table, and the post table to the above functions.
+--
+--
+
+local actions = {}
+actions["authenticate-post"] = authenticate_post
+actions["authenticate-cookie"] = authenticate_cookie
+actions["body"] = body
+
+function filter_open(...)
+ action = actions[select(1, ...)]
+
+ http = {}
+ http["cookie"] = select(2, ...)
+ http["method"] = select(3, ...)
+ http["query"] = select(4, ...)
+ http["referer"] = select(5, ...)
+ http["path"] = select(6, ...)
+ http["host"] = select(7, ...)
+ http["https"] = select(8, ...)
+
+ cgit = {}
+ cgit["repo"] = select(9, ...)
+ cgit["page"] = select(10, ...)
+ cgit["url"] = select(11, ...)
+ cgit["login"] = select(12, ...)
+
+end
+
+function filter_close()
+ return action()
+end
+
+function filter_write(str)
+ post = parse_qs(str)
+end
+
+
+--
+--
+-- Utility functions based on keplerproject/wsapi.
+--
+--
+
+function url_decode(str)
+ if not str then
+ return ""
+ end
+ str = string.gsub(str, "+", " ")
+ str = string.gsub(str, "%%(%x%x)", function(h) return string.char(tonumber(h, 16)) end)
+ str = string.gsub(str, "\r\n", "\n")
+ return str
+end
+
+function url_encode(str)
+ if not str then
+ return ""
+ end
+ str = string.gsub(str, "\n", "\r\n")
+ str = string.gsub(str, "([^%w ])", function(c) return string.format("%%%02X", string.byte(c)) end)
+ str = string.gsub(str, " ", "+")
+ return str
+end
+
+function parse_qs(qs)
+ local tab = {}
+ for key, val in string.gmatch(qs, "([^&=]+)=([^&=]*)&?") do
+ tab[url_decode(key)] = url_decode(val)
+ end
+ return tab
+end
+
+function get_cookie(cookies, name)
+ cookies = string.gsub(";" .. cookies .. ";", "%s*;%s*", ";")
+ return url_decode(string.match(cookies, ";" .. name .. "=(.-);"))
+end
+
+function tohex(b)
+ local x = ""
+ for i = 1, #b do
+ x = x .. string.format("%.2x", string.byte(b, i))
+ end
+ return x
+end
+
+--
+--
+-- Cookie construction and validation helpers.
+--
+--
+
+local secret = nil
+
+-- Loads a secret from a file, creates a secret, or returns one from memory.
+function get_secret()
+ if secret ~= nil then
+ return secret
+ end
+ local secret_file = io.open(secret_filename, "r")
+ if secret_file == nil then
+ local old_umask = sysstat.umask(63)
+ local temporary_filename = secret_filename .. ".tmp." .. tohex(rand.bytes(16))
+ local temporary_file = io.open(temporary_filename, "w")
+ if temporary_file == nil then
+ os.exit(177)
+ end
+ temporary_file:write(tohex(rand.bytes(32)))
+ temporary_file:close()
+ unistd.link(temporary_filename, secret_filename) -- Intentionally fails in the case that another process is doing the same.
+ unistd.unlink(temporary_filename)
+ sysstat.umask(old_umask)
+ secret_file = io.open(secret_filename, "r")
+ end
+ if secret_file == nil then
+ os.exit(177)
+ end
+ secret = secret_file:read()
+ secret_file:close()
+ if secret:len() ~= 64 then
+ os.exit(177)
+ end
+ return secret
+end
+
+-- Returns value of cookie if cookie is valid. Otherwise returns nil.
+function validate_value(expected_field, cookie)
+ local i = 0
+ local value = ""
+ local field = ""
+ local expiration = 0
+ local salt = ""
+ local chmac = ""
+
+ if cookie == nil or cookie:len() < 3 or cookie:sub(1, 1) == "|" then
+ return nil
+ end
+
+ for component in string.gmatch(cookie, "[^|]+") do
+ if i == 0 then
+ field = component
+ elseif i == 1 then
+ value = component
+ elseif i == 2 then
+ expiration = tonumber(component)
+ if expiration == nil then
+ expiration = -1
+ end
+ elseif i == 3 then
+ salt = component
+ elseif i == 4 then
+ chmac = component
+ else
+ break
+ end
+ i = i + 1
+ end
+
+ if chmac == nil or chmac:len() == 0 then
+ return nil
+ end
+
+ -- Lua hashes strings, so these comparisons are time invariant.
+ if chmac ~= tohex(hmac.new(get_secret(), "sha256"):final(field .. "|" .. value .. "|" .. tostring(expiration) .. "|" .. salt)) then
+ return nil
+ end
+
+ if expiration == -1 or (expiration ~= 0 and expiration <= os.time()) then
+ return nil
+ end
+
+ if url_decode(field) ~= expected_field then
+ return nil
+ end
+
+ return url_decode(value)
+end
+
+function secure_value(field, value, expiration)
+ if value == nil or value:len() <= 0 then
+ return ""
+ end
+
+ local authstr = ""
+ local salt = tohex(rand.bytes(16))
+ value = url_encode(value)
+ field = url_encode(field)
+ authstr = field .. "|" .. value .. "|" .. tostring(expiration) .. "|" .. salt
+ authstr = authstr .. "|" .. tohex(hmac.new(get_secret(), "sha256"):final(authstr))
+ return authstr
+end
+
+function set_cookie(cookie, value)
+ html("Set-Cookie: " .. cookie .. "=" .. value .. "; HttpOnly")
+ if http["https"] == "yes" or http["https"] == "on" or http["https"] == "1" then
+ html("; secure")
+ end
+ html("\n")
+end
+
+function redirect_to(url)
+ html("Status: 302 Redirect\n")
+ html("Cache-Control: no-cache, no-store\n")
+ html("Location: " .. url .. "\n")
+end
+
+function not_found()
+ html("Status: 404 Not Found\n")
+ html("Cache-Control: no-cache, no-store\n\n")
+end
diff --git a/modules/cgit/files/filters/hugo-highlighting.sh b/modules/cgit/files/filters/hugo-highlighting.sh
new file mode 100755
index 0000000..603b8ab
--- /dev/null
+++ b/modules/cgit/files/filters/hugo-highlighting.sh
@@ -0,0 +1,138 @@
+#!/bin/bash
+# This script can be used to implement syntax highlighting in the cgit
+# tree-view by refering to this file with the source-filter or repo.source-
+# filter options in cgitrc.
+#
+# This script requires a shell supporting the ${var##pattern} syntax.
+# It is supported by at least dash and bash, however busybox environments
+# might have to use an external call to sed instead.
+#
+# Note: the highlight command (http://www.andre-simon.de/) uses css for syntax
+# highlighting, so you'll probably want something like the following included
+# in your css file:
+#
+# Style definition file generated by highlight 2.4.8, http://www.andre-simon.de/
+#
+# table.blob .num { color:#2928ff; }
+# table.blob .esc { color:#ff00ff; }
+# table.blob .str { color:#ff0000; }
+# table.blob .dstr { color:#818100; }
+# table.blob .slc { color:#838183; font-style:italic; }
+# table.blob .com { color:#838183; font-style:italic; }
+# table.blob .dir { color:#008200; }
+# table.blob .sym { color:#000000; }
+# table.blob .kwa { color:#000000; font-weight:bold; }
+# table.blob .kwb { color:#830000; }
+# table.blob .kwc { color:#000000; font-weight:bold; }
+# table.blob .kwd { color:#010181; }
+#
+#
+# Style definition file generated by highlight 2.6.14, http://www.andre-simon.de/
+#
+# body.hl { background-color:#ffffff; }
+# pre.hl { color:#000000; background-color:#ffffff; font-size:10pt; font-family:'Courier New';}
+# .hl.num { color:#2928ff; }
+# .hl.esc { color:#ff00ff; }
+# .hl.str { color:#ff0000; }
+# .hl.dstr { color:#818100; }
+# .hl.slc { color:#838183; font-style:italic; }
+# .hl.com { color:#838183; font-style:italic; }
+# .hl.dir { color:#008200; }
+# .hl.sym { color:#000000; }
+# .hl.line { color:#555555; }
+# .hl.mark { background-color:#ffffbb;}
+# .hl.kwa { color:#000000; font-weight:bold; }
+# .hl.kwb { color:#830000; }
+# .hl.kwc { color:#000000; font-weight:bold; }
+# .hl.kwd { color:#010181; }
+#
+#
+# Style definition file generated by highlight 3.8, http://www.andre-simon.de/
+#
+# body.hl { background-color:#e0eaee; }
+# pre.hl { color:#000000; background-color:#e0eaee; font-size:10pt; font-family:'Courier New';}
+# .hl.num { color:#b07e00; }
+# .hl.esc { color:#ff00ff; }
+# .hl.str { color:#bf0303; }
+# .hl.pps { color:#818100; }
+# .hl.slc { color:#838183; font-style:italic; }
+# .hl.com { color:#838183; font-style:italic; }
+# .hl.ppc { color:#008200; }
+# .hl.opt { color:#000000; }
+# .hl.lin { color:#555555; }
+# .hl.kwa { color:#000000; font-weight:bold; }
+# .hl.kwb { color:#0057ae; }
+# .hl.kwc { color:#000000; font-weight:bold; }
+# .hl.kwd { color:#010181; }
+#
+#
+# Style definition file generated by highlight 3.13, http://www.andre-simon.de/
+#
+# body.hl { background-color:#e0eaee; }
+# pre.hl { color:#000000; background-color:#e0eaee; font-size:10pt; font-family:'Courier New',monospace;}
+# .hl.num { color:#b07e00; }
+# .hl.esc { color:#ff00ff; }
+# .hl.str { color:#bf0303; }
+# .hl.pps { color:#818100; }
+# .hl.slc { color:#838183; font-style:italic; }
+# .hl.com { color:#838183; font-style:italic; }
+# .hl.ppc { color:#008200; }
+# .hl.opt { color:#000000; }
+# .hl.ipl { color:#0057ae; }
+# .hl.lin { color:#555555; }
+# .hl.kwa { color:#000000; font-weight:bold; }
+# .hl.kwb { color:#0057ae; }
+# .hl.kwc { color:#000000; font-weight:bold; }
+# .hl.kwd { color:#010181; }
+#
+#
+# The following environment variables can be used to retrieve the configuration
+# of the repository for which this script is called:
+# CGIT_REPO_URL ( = repo.url setting )
+# CGIT_REPO_NAME ( = repo.name setting )
+# CGIT_REPO_PATH ( = repo.path setting )
+# CGIT_REPO_OWNER ( = repo.owner setting )
+# CGIT_REPO_DEFBRANCH ( = repo.defbranch setting )
+# CGIT_REPO_SECTION ( = section setting )
+# CGIT_REPO_CLONE_URL ( = repo.clone-url setting )
+#
+
+# store filename and extension in local vars
+BASENAME="$1"
+EXTENSION="${BASENAME##*.}"
+
+[ "${BASENAME}" = "${EXTENSION}" ] && EXTENSION=txt
+[ -z "${EXTENSION}" ] && EXTENSION=txt
+
+# map Makefile and Makefile.* to .mk
+[ "${BASENAME%%.*}" = "Makefile" ] && EXTENSION=mk
+
+# highlight versions 2 and 3 have different commandline options. Specifically,
+# the -X option that is used for version 2 is replaced by the -O xhtml option
+# for version 3.
+#
+# Version 2 can be found (for example) on EPEL 5, while version 3 can be
+# found (for example) on EPEL 6.
+#
+# This is for version 2
+#exec highlight --force -f -I -X -S "$EXTENSION" 2>/dev/null
+
+# env
+
+extension=${REQUEST_URI: -3}
+
+case $extension in
+ org)
+ temp=$(mktemp)
+ cat - > $temp.org
+ emacs $temp.org \
+ --quick \
+ --batch \
+ --funcall org-html-export-to-html \
+ --kill
+ tail -n+10 $temp.html
+ exit 0
+ ;;
+esac
+
+exec highlight --force -f -I -O xhtml -S "$EXTENSION" # 2>/dev/null
diff --git a/modules/cgit/files/filters/hugo-pre.sh b/modules/cgit/files/filters/hugo-pre.sh
new file mode 100755
index 0000000..b716525
--- /dev/null
+++ b/modules/cgit/files/filters/hugo-pre.sh
@@ -0,0 +1,15 @@
+#!/bin/bash
+
+name=$1
+
+extension=${name: -3}
+
+case $extension in
+ .md) pandoc -f gfm -t html ;;
+ *)
+ cat <<- EOF
+ <pre>$(cat -)</pre>
+ EOF
+ ;;
+esac
+
diff --git a/modules/cgit/files/logo.png b/modules/cgit/files/logo.png
new file mode 100644
index 0000000..5c39e4a
--- /dev/null
+++ b/modules/cgit/files/logo.png
Binary files differ
diff --git a/modules/cgit/files/logo_large.png b/modules/cgit/files/logo_large.png
new file mode 100644
index 0000000..bbfa2b5
--- /dev/null
+++ b/modules/cgit/files/logo_large.png
Binary files differ
diff --git a/modules/cgit/files/root_readme b/modules/cgit/files/root_readme
new file mode 100644
index 0000000..aabf4df
--- /dev/null
+++ b/modules/cgit/files/root_readme
@@ -0,0 +1,17 @@
+ _______________________
+< Den som gör bestämmer >
+ -----------------------
+\ . .
+ \ / `. .' "
+ \ .---. < > < > .---.
+ \ | \ \ - ~ ~ - / / |
+ _____ ..-~ ~-..-~
+ | | \~~~\.' `./~~~/
+ --------- \__/ \__/
+ .' O \ / / \ "
+ (_____, `._.' | } \/~~~/
+ `----. / } | / \__/
+ `-. | / | / `. ,~~|
+ ~-.__| /_ - ~ ^| /- _ `..-'
+ | / | / ~-. `-. _ _ _
+ |_____| |_____| ~ - . _ _ _ _ _>
diff --git a/modules/cgit/manifests/init.pp b/modules/cgit/manifests/init.pp
new file mode 100644
index 0000000..c1a981b
--- /dev/null
+++ b/modules/cgit/manifests/init.pp
@@ -0,0 +1,62 @@
+class cgit (
+ String $root = '/var/www/cgit',
+ String $filterpath = '/usr/lib/cgit/extra-filters',
+ String $root_title,
+ String $root_desc,
+ String $about_filter,
+ String $auth_filter,
+ String $source_filter,
+ String $scan_path,
+ Array[String] $clone_url,
+) {
+
+ # TODO figure out where CSS comes from
+
+ ensure_packages([
+ 'cgit',
+ ], { ensure => installed })
+
+ file { '/etc/cgitrc':
+ ensure => file,
+ content => epp('cgit/cgitrc.epp'),
+ }
+
+ file { "${root}/logo":
+ ensure => directory,
+ }
+
+ file { "${root}/logo/logo.png":
+ ensure => file,
+ source => 'puppet:///modules/cgit/logo.png',
+ }
+
+ file { "${root}/logo/logo_large.png":
+ ensure => file,
+ source => 'puppet:///modules/cgit/logo_large.png',
+ }
+
+ file { "${root}/root_readme":
+ ensure => file,
+ source => 'puppet:///modules/cgit/root_readme',
+ }
+
+ file { dirname($filterpath):
+ ensure => directory,
+ }
+
+ file { $filterpath:
+ ensure => directory,
+ }
+
+ [$about_filter, $source_filter, $auth_filter].each |$f| {
+ file { "${filterpath}/${f}":
+ ensure => file,
+ source => "puppet:///modules/cgit/filters/${f}",
+ mode => stdlib::extname($f) ? {
+ '.lua' => '0444',
+ default => '0555',
+ },
+ }
+ }
+
+}
diff --git a/modules/cgit/templates/cgitrc.epp b/modules/cgit/templates/cgitrc.epp
new file mode 100644
index 0000000..9434213
--- /dev/null
+++ b/modules/cgit/templates/cgitrc.epp
@@ -0,0 +1,64 @@
+#
+# cgit config
+# see cgitrc(5) for details
+#
+# FILE MANAGED BY PUPPET
+#
+
+# css=/cgit.css
+logo=/logo/logo.png
+# favicon=/favicon.ico
+
+enable-index-owner=1
+
+root-title=<%= $cgit::root_title %>
+#root-desc=Tändes endast mot lådans plån
+root-desc=<%= $cgit::root_desc %>
+# Also causes the `about' page to exist
+# /usr/lib/cgit/readme
+root-readme=<%= $cgit::root %>/root_readme
+
+#source-filter=/srv/filters/dispatch.sh
+
+# about-filter=/usr/local/lib/cgit/filters/hugo-pre.sh
+# auth-filter=lua:/usr/local/lib/cgit/filters/hugo-authentication.lua
+# source-filter=/usr/local/lib/cgit/filters/hugo-highlighting.sh
+about-filter=<%= $cgit::filterpath %>/<%= $cgit::about_filter %>
+auth-filter=<% if stdlib::extname($cgit::auth_filter) == '.lua' {
+ -%>lua:<%
+ } -%><%= $cgit::filterpath %>/<%= $cgit::auth_filter %>
+source-filter=<%= $cgit::filterpath %>/<%= $cgit::source_filter %>
+
+enable-follow-links=1
+enable-subject-links=1 # show commit summary for parrent
+
+#side-by-side-diffs=1
+enable-commit-graph=1
+enable-index-links=1
+enable-remote-branches=1
+local-time=1
+
+case-sensative-sort=0
+
+max-repo-count=100
+
+enable-http-clone=1
+clone-url=<%= $cgit::clone_url.join(' ') %>
+
+readme=:README
+readme=:README.md
+readme=:README.txt
+readme=:readme
+readme=:readme.md
+readme=:readme.txt
+
+virtual-root=
+remove-suffix=1
+section-from-path=1
+enable-git-config=1
+
+# section=~/git
+snapshots=tar.gz tar.xz
+scan-path=<%= $cgit::scan_path %>
+
+#scan-path=/var/www/git/repositories/
diff --git a/modules/exports/manifests/init.pp b/modules/exports/manifests/init.pp
new file mode 100644
index 0000000..8280c71
--- /dev/null
+++ b/modules/exports/manifests/init.pp
@@ -0,0 +1,21 @@
+# Setup export item.
+# Should be be callable multiple times
+define exports (
+ Hash[String,Array[String]] $options,
+ String $dir = $name,
+ String $exports_file = '/etc/exports',
+) {
+
+ $fixed_opts = $options.map |$key, $val| {
+ $joined_vals = $val.join(',')
+ "${key}(${joined_vals})"
+ }.join(' ')
+
+ file_line { "Export ${exports_file} ${dir}":
+ ensure => present,
+ path => $exports_file,
+ match => "^${dir}",
+ line => "${dir} ${fixed_opts}"
+ }
+
+}
diff --git a/modules/losetup/files/dismantle-loop-device b/modules/losetup/files/dismantle-loop-device
new file mode 100644
index 0000000..be3f3a0
--- /dev/null
+++ b/modules/losetup/files/dismantle-loop-device
@@ -0,0 +1,15 @@
+#!/bin/bash
+
+target_file="$1"
+safe_name="$(systemd-escape "$target_file")"
+
+set +x
+
+loop_device=$(readlink "/dev/loop-by-name/${safe_name}")
+rm "/dev/loop-by-name/${safe_name}"
+
+for part in "/dev/loop-by-name/${safe_name}"-p*; do
+ rm "$part"
+done
+
+losetup -d $loop_device
diff --git a/modules/losetup/files/loop@.service b/modules/losetup/files/loop@.service
new file mode 100644
index 0000000..e9dc008
--- /dev/null
+++ b/modules/losetup/files/loop@.service
@@ -0,0 +1,12 @@
+[Unit]
+Description=Loopback device for %I
+
+[Service]
+ExecStart=/usr/libexec/setup-loop-device "%I"
+ExecStop=/usr/libexec/dismantle-loop-device "%I"
+#ExecStopPost=rm /dev/loop-by-name/"%i"*
+#ExecStart=echo %I
+ExecStartPre=mkdir -p /dev/loop-by-name
+# Cant't have any dashes in filename, due to escaping rules
+# ExecStartPre=/bin/sh -c "grep -vq '-' <<< "%i""
+RemainAfterExit=yes
diff --git a/modules/losetup/files/setup-loop-device b/modules/losetup/files/setup-loop-device
new file mode 100755
index 0000000..4215273
--- /dev/null
+++ b/modules/losetup/files/setup-loop-device
@@ -0,0 +1,16 @@
+#!/bin/bash
+
+target_file="$1"
+safe_name="$(systemd-escape "$target_file")"
+
+set +x
+
+loop_device=$(losetup --find --show "$target_file")
+
+ln -s $loop_device "/dev/loop-by-name/${safe_name}"
+
+for part in "${loop_device}"p*; do
+ last_two=${part:$[${#part}-2]}
+ ln -s $part \
+ "/dev/loop-by-name/${safe_name}-${last_two}"
+done
diff --git a/modules/losetup/manifests/init.pp b/modules/losetup/manifests/init.pp
new file mode 100644
index 0000000..8d7f8d8
--- /dev/null
+++ b/modules/losetup/manifests/init.pp
@@ -0,0 +1,24 @@
+class losetup {
+ file { '/etc/systemd/system/loop@.service':
+ ensure => file,
+ source => "puppet:///modules/${module_name}/loop@.service",
+ }
+
+ # ensure_resource ('file', { path => '/usr/libexec', ensure => directory })
+ file { '/usr/libexec':
+ ensure => directory,
+ }
+
+ file {
+ default:
+ ensure => file,
+ mode => '0555',
+ ;
+'/usr/libexec/setup-loop-device':
+ source => "puppet:///modules/${module_name}/setup-loop-device",
+ ;
+'/usr/libexec/dismantle-loop-device':
+ source => "puppet:///modules/${module_name}/dismantle-loop-device",
+ ;
+ }
+}
diff --git a/modules/nsupdate/manifests/init.pp b/modules/nsupdate/manifests/init.pp
new file mode 100644
index 0000000..8141f5a
--- /dev/null
+++ b/modules/nsupdate/manifests/init.pp
@@ -0,0 +1,64 @@
+# type DNSRecordType = ['A', 'AAAA', 'AFSDB', 'APL', 'CAA', 'CDNSKEY', 'CDS',
+# 'CERT', 'CNAME', 'CSYNC', 'DHCID', 'DLV', 'DNAME', 'DNSKEY', 'DS', 'EUI48',
+# 'EUI64', 'HINFO', 'HIP', 'HTTPS', 'IPSECKEY', 'KEY', 'KX', 'LOC', 'MX',
+# 'NAPTR', 'NS', 'NSEC', 'NSEC3', 'NSEC3PARAM', 'OPENPGPKEY', 'PTR', 'RRSIG',
+# 'RP', 'SIG', 'SMIMEA', 'SOA', 'SRV', 'SSHFP', 'SVCB', 'TA', 'TKEY', 'TLSA',
+# 'TSIG', 'TXT', 'URI', 'ZA', 'AAAA', 'AFSDB', 'APL', 'CAA', 'CDNSKEY', 'CDS',
+# 'CERT', 'CNAME', 'CSYNC', 'DHCID', 'DLV', 'DNAME', 'DNSKEY', 'DS', 'EUI48',
+# 'EUI64', 'HINFO', 'HIP', 'HTTPS', 'IPSECKEY', 'KEY', 'KX', 'LOC', 'MX',
+# 'NAPTR', 'NS', 'NSEC', 'NSEC3', 'NSEC3PARAM', 'OPENPGPKEY', 'PTR', 'RRSIG',
+# 'RP', 'SIG', 'SMIMEA', 'SOA', 'SRV', 'SSHFP', 'SVCB', 'TA', 'TKEY', 'TLSA',
+# 'TSIG', 'TXT', 'URI', 'ZONEMD']
+
+type DNSRecordType = Enum['A']
+
+type DNSRecord = Struct[{
+ domain => String,
+ type => DNSRecordType,
+ ttl => Integer,
+}]
+
+# Sets up a single instance of a reoccuring nsupdate.
+# Note that nsupdate::secret.$keyname needs to be made available through hiera
+# /etc/puppetlabs/code/environments/production/data/nodes/hornquist.se.yaml
+define nsupdate (
+ String $nameserver,
+ Array[DNSRecord] $records,
+ String $iface = $facts['networking']['primary'],
+ Enum['present', 'absent'] $ensure = present,
+ String $keyname = $name,
+) {
+
+ require ::nsupdate::setup
+
+ file { "/usr/libexec/nsupdate/${name}":
+ ensure => $ensure,
+ mode => '0555',
+ content => epp('nsupdate/nsupdate.epp', {
+ iface => $iface,
+ nameserver => $nameserver,
+ records => $records,
+ keyname => $keyname,
+ })
+ }
+
+ $key = lookup("nsupdate::secrets.\"${keyname}\"")
+ $secret = Sensitive($key['secret'])
+ file { "/var/lib/nsupdate/${keyname}.key":
+ ensure => file,
+ mode => '0400',
+ show_diff => false,
+ content => @("EOF")
+ key "${keyname}" {
+ algorithm ${key['algorithm']};
+ secret "${secret.unwrap}";
+ };
+ | EOF
+ }
+
+ cron { "nsupdate ${name}":
+ ensure => $ensure,
+ command => "/usr/libexec/nsupdate/${name}",
+ minute => 0,
+ }
+}
diff --git a/modules/nsupdate/manifests/setup.pp b/modules/nsupdate/manifests/setup.pp
new file mode 100644
index 0000000..9aba4ff
--- /dev/null
+++ b/modules/nsupdate/manifests/setup.pp
@@ -0,0 +1,14 @@
+class nsupdate::setup (
+) {
+ file { '/usr/libexec/nsupdate':
+ ensure => directory,
+ }
+
+ file { '/var/lib/nsupdate':
+ ensure => directory,
+ }
+
+ ensure_packages(['bind9-dnsutils'], {
+ ensure => installed,
+ })
+}
diff --git a/modules/nsupdate/templates/nsupdate.epp b/modules/nsupdate/templates/nsupdate.epp
new file mode 100644
index 0000000..66fe4b2
--- /dev/null
+++ b/modules/nsupdate/templates/nsupdate.epp
@@ -0,0 +1,18 @@
+<%- |
+ String $iface,
+ String $nameserver,
+ String $keyname,
+ Array[DNSRecord] $records,
+| -%>
+#!/bin/bash
+
+IP=$(ip -j a show dev <%= $iface %> | jq --raw-output '.[0].addr_info[] | select(.family == "inet").local')
+
+nsupdate "$@" -k '/var/lib/nsupdate/<%= $keyname %>.key' << EOF
+server <%= $nameserver %>
+<%- $records.each |$record| { -%>
+update delete <%= $record['domain'] %> <%= $record['type'] %>
+update add <%= $record['domain'] %> <%= $record['ttl'] %> <%= $record['type'] %> ${IP}
+<%- } -%>
+send
+EOF
diff --git a/modules/overlay/manifests/init.pp b/modules/overlay/manifests/init.pp
new file mode 100644
index 0000000..a85683d
--- /dev/null
+++ b/modules/overlay/manifests/init.pp
@@ -0,0 +1,17 @@
+define overlay (
+) {
+
+ {
+ lowerdir => "${dir}/root/base",
+ upperdir => "${dir}/root/overlays/${name}",
+ workdir => "${dir}/root/workdirs/${name}",
+ nfs_export => 'on',
+ }
+
+ file_line {
+ ensure => present,
+ path => "${dir}/fstab"
+ line => "overlay root/export/${name}
+ }
+
+}
diff --git a/modules/pacman/files/mirrorlist b/modules/pacman/files/mirrorlist
new file mode 100644
index 0000000..4ea5d0e
--- /dev/null
+++ b/modules/pacman/files/mirrorlist
@@ -0,0 +1,6 @@
+# File managed by puppet, local changes WILL be overwritten
+
+Server = https://ftp.lysator.liu.se/pub/archlinux/$repo/os/$arch
+Server = http://ftp.lysator.liu.se/pub/archlinux/$repo/os/$arch
+Server = https://ftp.acc.umu.se/mirror/archlinux/$repo/os/$arch
+Server = http://ftp.acc.umu.se/mirror/archlinux/$repo/os/$arch
diff --git a/modules/pacman/manifests/hook.pp b/modules/pacman/manifests/hook.pp
new file mode 100644
index 0000000..f8478e6
--- /dev/null
+++ b/modules/pacman/manifests/hook.pp
@@ -0,0 +1,80 @@
+
+type Pacman::Operation = Enum['Install', 'Upgrade', 'Remove']
+# type Variant[Type, Array[Type, 1]] = Variant[Type, Array[Type, 1]]
+
+type Pacman::Trigger = Struct[{
+ type => Enum['Path', 'Package'],
+ operation => Variant[Pacman::Operation, Array[Pacman::Operation, 1]],
+ target => Variant[String, Array[String, 1]],
+}]
+
+define pacman::hook (
+ Integer $priority = 50,
+ Optional[String] $description = undef,
+ Enum['PreTransation', 'PostTransaction'] $when,
+ String $exec,
+ Optional[Variant[String, Array[String, 1]]] $depends = undef,
+ Boolean $abortOnFail = false, # only for PreTransation
+ Boolean $needsTargets = false,
+ Variant[Pacman::Trigger, Array[Pacman::Trigger, 1]] $trigger,
+) {
+
+ require ::pacman
+
+ if ($abortOnFail and $when != 'PreTransation') {
+ fail('abortOnFail only valid when "when" => "PreTransation"')
+ }
+
+ # Normalize triggers to list
+ $triggers = ($trigger ? {
+ Array => $trigger,
+ default => [$trigger],
+ }).map |$trigger| {
+ # Normalize contents of each trigger, making
+ {
+ type => $trigger['type'],
+ operation => $trigger['operation'] ? {
+ Array => $trigger['operation'],
+ default => [$trigger['operation']],
+ },
+ target => $trigger['target'] ? {
+ Array => $trigger['target'],
+ default => [$trigger['target']],
+ }
+ }
+ }
+
+ $triggers.each |$trigger| {
+ if $trigger['type'] == 'Path' {
+ $trigger['target'].each |$target| {
+ if $target[0] == '/' {
+ fail("Target paths shouldn't start with '/' ${target} in trigger ${name}")
+ }
+ }
+ }
+ }
+
+ $str = epp('pacman/hook.epp', {
+ description => $description,
+ depends => $depends ? {
+ Optional => [],
+ Array => $depends,
+ default => [$depends],
+ },
+ triggers => $triggers,
+ exec => $exec,
+ when => $when,
+ abortOnFail => $abortOnFail,
+ needsTargets => $needsTargets,
+ })
+
+ $chksum = $str.md5()
+
+ file { $chksum:
+ ensure => 'present',
+ content => $str,
+ path => "${pacman::hooks_path}/${priority}-${name}.hook",
+ checksum => 'md5',
+ checksum_value => $chksum,
+ }
+}
diff --git a/modules/pacman/manifests/init.pp b/modules/pacman/manifests/init.pp
new file mode 100644
index 0000000..fb23328
--- /dev/null
+++ b/modules/pacman/manifests/init.pp
@@ -0,0 +1,44 @@
+class pacman (
+ String $hooks_path = '/etc/pacman.d/hooks-puppet',
+ String $conf_path = '/etc/pacman.conf',
+ Boolean $ilovecandy = false,
+) {
+
+ ini_setting { 'Pacman HookDir':
+ path => $conf_path,
+ section => 'options',
+ setting => 'HookDir',
+ value => $hooks_path,
+
+ }
+
+ ini_setting { 'Pacman ILoveCandy':
+ ensure => if ($ilovecandy) { present } else { absent },
+ path => '/etc/pacman.conf',
+ section => 'options',
+ setting => 'ILoveCandy',
+ key_val_separator => '',
+ value => '',
+ }
+
+ if versioncmp($facts['pacman-version'], '6.0.0') >= 0 {
+ ini_setting { 'Pacman parallel downloads':
+ path => '/etc/pacman.conf',
+ section => 'options',
+ setting => 'ParallelDownloads',
+ value => 8,
+ }
+ }
+
+ file { $hooks_path:
+ ensure => directory,
+ recurse => true,
+ purge => true,
+ }
+
+ file { '/etc/pacman.d/mirrorlist':
+ ensure => present,
+ backup => true,
+ source => 'puppet:///modules/pacman/mirrorlist',
+ }
+}
diff --git a/modules/pacman/manifests/repo.pp b/modules/pacman/manifests/repo.pp
new file mode 100644
index 0000000..28f92b0
--- /dev/null
+++ b/modules/pacman/manifests/repo.pp
@@ -0,0 +1,24 @@
+define pacman::repo (
+ Enum['present', 'absent'] $ensure = 'present',
+ String $repo_name = $name,
+ # String $include,
+ String $server,
+ String $sig_level,
+) {
+
+ ini_setting {
+ default:
+ ensure => $ensure,
+ path => $::pacman::conf_path,
+ section => $repo_name ,
+ ;
+ "Pacman repo [${repo_name}] server":
+ setting => 'Server',
+ value => $server ,
+ ;
+ "Pacman repo [${repo_name}] SigLevel":
+ setting => 'SigLevel',
+ value => $sig_level ,
+ ;
+ }
+}
diff --git a/modules/pacman/templates/hook.epp b/modules/pacman/templates/hook.epp
new file mode 100644
index 0000000..08377d9
--- /dev/null
+++ b/modules/pacman/templates/hook.epp
@@ -0,0 +1,31 @@
+<%- | Array[Pacman::Trigger] $triggers,
+ Optional[String] $description,
+ String $exec,
+ Enum['PreTransation', 'PostTransaction'] $when,
+ Array[String] $depends,
+ Boolean $abortOnFail,
+ Boolean $needsTargets,
+
+| -%>
+# Managed by Puppet
+
+[Trigger]
+<%- $triggers.each |$trigger| { -%>
+Type = <%= $trigger['type'] %>
+<%- $trigger['operation'].each |$op| { -%>
+Operation = <%= $op %>
+<%- } -%>
+<% $trigger['target'].each |$target| { -%>
+Target = <%= $target %>
+<%- } -%>
+<%- } %>
+
+[Action]
+<%- if ($description) { -%>Description = <%= $description %><% } %>
+Exec = <%= $exec %>
+When = <%= $when %>
+<%- $depends.each |$depend| { -%>
+Depends = <%= $depend %>
+<%- } -%>
+<%- if ($abortOnFail) { -%>AbortOnFail<% } %>
+<%- if ($needsTargets) { -%>NeedsTargets<% } %>
diff --git a/modules/pass/lib/puppet/functions/pass.rb b/modules/pass/lib/puppet/functions/pass.rb
new file mode 100644
index 0000000..176e7e9
--- /dev/null
+++ b/modules/pass/lib/puppet/functions/pass.rb
@@ -0,0 +1,21 @@
+# require 'open3'
+
+Puppet::Functions.create_function(:pass) do
+ dispatch :method do
+ param 'String', :path
+ optional_param 'String', :salt
+ end
+
+ def method(path, salt = path)
+ # Salt was an attempt to not regenerate the entry every run.
+ # This however failed, since the command is still run, and puppet
+ # doesn't diff betwen runs
+ # -salt #{salt}
+ #
+ # TODO fetch passwords in some slightly more portable way
+ `openssl passwd -apr1 $(sudo -Hu hugo pass #{path})`
+ # Open3.popen3("sudo -Hu hugo pass #{path}") do |stdin, stdout, stderr, thread|
+ # stdout.read.chomp
+ # end
+ end
+end
diff --git a/modules/profiles/files/firewall/rules.v4 b/modules/profiles/files/firewall/rules.v4
new file mode 100644
index 0000000..bdc63cc
--- /dev/null
+++ b/modules/profiles/files/firewall/rules.v4
@@ -0,0 +1,15 @@
+# Generated by iptables-save v1.8.4 on Thu Jun 3 20:27:52 2021
+*filter
+:INPUT DROP [120:97784]
+:FORWARD ACCEPT [0:0]
+:OUTPUT ACCEPT [526:114637]
+-A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
+-A INPUT -p udp -m udp --dport 67:68 -j ACCEPT
+-A INPUT -p icmp -j ACCEPT
+-A INPUT -p tcp -m tcp --dport 80 -j ACCEPT
+-A INPUT -p tcp -m tcp --dport 443 -j ACCEPT
+-A INPUT -p tcp -m tcp --dport 22 -j ACCEPT
+-A INPUT -p tcp -m tcp --dport 53 -j ACCEPT
+-A INPUT -p udp -m udp --dport 53 -j ACCEPT
+COMMIT
+# Completed on Thu Jun 3 20:27:52 2021
diff --git a/modules/profiles/files/passmenu b/modules/profiles/files/passmenu
new file mode 100755
index 0000000..653ebda
--- /dev/null
+++ b/modules/profiles/files/passmenu
@@ -0,0 +1,29 @@
+#!/usr/bin/env bash
+
+shopt -s nullglob globstar
+
+typeit=0
+if [[ $1 == "--type" ]]; then
+ typeit=1
+ shift
+fi
+
+prefix=${PASSWORD_STORE_DIR-~/.password-store}
+password_files=( "$prefix"/**/*.gpg )
+password_files=( "${password_files[@]#"$prefix"/}" )
+password_files=( "${password_files[@]%.gpg}" )
+
+password=$(printf '%s\n' "${password_files[@]}" | dmenu "$@")
+
+[[ -n $password ]] || exit
+
+if [[ "$password" == *-otp ]]; then
+ otp='otp'
+fi
+
+if [[ $typeit -eq 0 ]]; then
+ pass $otp show -c "$password" 2>/dev/null
+else
+ pass $otp show "$password" | { IFS= read -r pass; printf %s "$pass"; } |
+ xdotool type --clearmodifiers --file -
+fi
diff --git a/modules/profiles/files/ssh-agent.service b/modules/profiles/files/ssh-agent.service
new file mode 100644
index 0000000..d49edc6
--- /dev/null
+++ b/modules/profiles/files/ssh-agent.service
@@ -0,0 +1,13 @@
+# https://unix.stackexchange.com/questions/339840/how-to-start-and-use-ssh-agent-as-systemd-service
+
+[Unit]
+Description=SSH key agent
+
+[Service]
+Type=simple
+Environment=SSH_AUTH_SOCK=%t/ssh-agent.socket
+# ExecStart=/usr/bin/ssh-agent -D -a $SSH_AUTH_SOCK
+ExecStart=/usr/bin/ssh-agent -D -a $SSH_AUTH_SOCK
+
+[Install]
+WantedBy=default.target
diff --git a/modules/profiles/lib/facter/pacman_version.rb b/modules/profiles/lib/facter/pacman_version.rb
new file mode 100644
index 0000000..1d17b04
--- /dev/null
+++ b/modules/profiles/lib/facter/pacman_version.rb
@@ -0,0 +1,7 @@
+Facter.add('pacman-version') do
+ if File.exists?('/bin/pacman') then
+ setcode do
+ `pacman -Qi pacman | awk -F' : ' '/^Version/ { print $2 }'`.strip()
+ end
+ end
+end
diff --git a/modules/profiles/manifests/dolphin.pp b/modules/profiles/manifests/dolphin.pp
new file mode 100644
index 0000000..f1fdcf8
--- /dev/null
+++ b/modules/profiles/manifests/dolphin.pp
@@ -0,0 +1,70 @@
+# Configure the file manager dolphin
+class profiles::dolphin {
+ ensure_packages ([
+ 'dolphin',
+ 'kde-cli-tools',
+ 'ffmpegthumbs',
+ 'kdegraphics-thumbnailers',
+ 'konsole',
+ 'breeze-icons',
+ ], { ensure => installed })
+
+
+ $dolphin_settings = {
+ 'General' => {
+ 'BrowseThroughArchives' => 'true',
+ 'GlobalViewProps' => 'false',
+ 'HomeUrl' => '/usr/net/video',
+ 'OpenExternallyCalledFolderInNewTab' => 'false',
+ 'RememberOpenedTabs' => 'false',
+ 'ShowFullPath' => 'true',
+ },
+ 'MainWindow' => {
+ 'MenuBar' => 'Disabled',
+ 'ToolBarsMovable' => 'Disabled',
+ },
+ 'VersionControl' => {
+ 'enabledPlugins' => [
+ 'Dropbox',
+ 'Git',
+ ]
+ },
+ 'PreviewSettings' => {
+ 'Plugins' => [
+ 'appimagethumbnail',
+ 'audiothumbnail',
+ 'blenderthumbnail',
+ 'comicbookthumbnail',
+ 'djvuthumbnail',
+ 'ebookthumbnail',
+ 'exrthumbnail',
+ 'directorythumbnail',
+ 'fontthumbnail',
+ 'imagethumbnail',
+ 'jpegthumbnail',
+ 'kraorathumbnail',
+ 'windowsexethumbnail',
+ 'windowsimagethumbnail',
+ 'opendocumentthumbnail',
+ 'gsthumbnail',
+ 'svgthumbnail',
+ 'textthumbnail',
+ 'ffmpegthumbs',
+ ]
+ }
+ }
+
+ $dolphin_settings.map |$category, $group| {
+ $group.map |$setting, $value| {
+ ini_setting { "Dolphin [${category}].${setting}":
+ path => '/etc/xdg/dolphinrc',
+ section => $category,
+ setting => $setting,
+ value => $value ? {
+ Array => $value.join(','),
+ String => $value,
+ }
+ }
+ }
+ }
+}
diff --git a/modules/profiles/manifests/firewall.pp b/modules/profiles/manifests/firewall.pp
new file mode 100644
index 0000000..6c9d7e6
--- /dev/null
+++ b/modules/profiles/manifests/firewall.pp
@@ -0,0 +1,19 @@
+class profiles::firewall {
+ ensure_packages ([
+ 'iptables-persistent',
+ 'fail2ban',
+ ], { ensure => installed })
+
+ file { '/etc/iptables/rules.v4':
+ source => 'puppet:///modules/profiles/firewall/rules.v4',
+ } ~> exec { 'reload firewall':
+ command => '/usr/share/netfilter-persistent/plugins.d/15-ip4tables restart',
+ refreshonly => true,
+ }
+
+ service { 'fail2ban':
+ ensure => running,
+ enable => true,
+ }
+
+}
diff --git a/modules/profiles/manifests/gandalf_web.pp b/modules/profiles/manifests/gandalf_web.pp
new file mode 100644
index 0000000..1295d83
--- /dev/null
+++ b/modules/profiles/manifests/gandalf_web.pp
@@ -0,0 +1,102 @@
+class profiles::gandalf_web (
+ String $certname,
+) {
+
+ class { '::nginx':
+ manage_repo => false,
+ # server_purge => true,
+ package_name => 'nginx-mainline',
+ service_config_check => true,
+ http_cfg_append => {
+ 'charset' => 'utf-8',
+ },
+ mime_types_preserve_defaults => true,
+ mime_types => {
+ 'text/plain' => 'wiki txt',
+ },
+ include_modules_enabled => true,
+ server_purge => true,
+ }
+
+ file { '/etc/nginx/modules-enabled':
+ ensure => directory,
+ purge => true,
+ recurse => true,
+ }
+
+ # TODO this fails at bootstrapping, since letsencrypt requires nginx
+ # to be enabled, but nginx can't be enabled if any cert file is
+ # missing
+ # Letsencrypt::Certonly <| |> -> Nginx::Resource::Server <| |>
+
+ $domains = [
+ 'bookmark.gandalf.adrift.space',
+ 'calendar.gandalf.adrift.space',
+ 'repo.gandalf.adrift.space',
+ 'gandalf.adrift.space',
+ 'hack.adrift.space',
+ 'adrift.space',
+ ]
+
+ ensure_packages (['cronie',], { ensure => installed })
+
+ ensure_packages (['certbot', 'certbot-nginx'], { ensure => installed })
+ class { '::letsencrypt':
+ config => {
+ email => 'hugo@hornquist.se',
+ # server => 'https://acme-staging-v02.api.letsencrypt.org/directory',
+ server => 'https://acme-v02.api.letsencrypt.org/directory',
+ },
+ manage_install => false,
+ }
+
+ letsencrypt::certonly { $certname:
+ ensure => present,
+ domains => $domains,
+ manage_cron => true,
+ plugin => 'nginx',
+ additional_args => [ '--quiet', ],
+ # pre_hook_commands => [ 'systemctl stop nginx.service', ],
+ post_hook_commands => [ 'systemctl restart nginx.service', ],
+ }
+
+
+ nginx::resource::server { 'gandalf':
+ ipv6_enable => true,
+ listen_options => 'default_server',
+ ipv6_listen_options => 'default_server',
+ server_name => [ '_' ],
+ access_log => absent,
+ error_log => absent,
+ ssl => true,
+ ssl_cert => "/etc/letsencrypt/live/${certname}/fullchain.pem",
+ ssl_key => "/etc/letsencrypt/live/${certname}/privkey.pem",
+ ssl_redirect => true,
+ index_files => [ 'index.html', ],
+ www_root => '/var/www/adrift.space',
+ use_default_location => false,
+ }
+
+ nginx::resource::location { '/':
+ try_files => ['$uri', '$uri/', '=404'],
+ index_files => [],
+ ssl => true,
+ ssl_only => true,
+ autoindex => on,
+ server => 'gandalf',
+ }
+
+ nginx::resource::server { 'repo.gandalf.adrift.space':
+ ipv6_enable => true,
+ ipv6_listen_options => '',
+ server_name => [ 'repo.gandalf.adrift.space', ],
+ ssl => true,
+ ssl_cert => "/etc/letsencrypt/live/${certname}/fullchain.pem",
+ ssl_key => "/etc/letsencrypt/live/${certname}/privkey.pem",
+ ssl_redirect => true,
+ index_files => [ 'index.html', ],
+ www_root => '/usr/net/repo/',
+ use_default_location => true,
+ }
+
+}
diff --git a/modules/profiles/manifests/group_profile.pp b/modules/profiles/manifests/group_profile.pp
new file mode 100644
index 0000000..2025a4b
--- /dev/null
+++ b/modules/profiles/manifests/group_profile.pp
@@ -0,0 +1,16 @@
+class profiles::group_profile {
+ file { '/etc/profile.d/group-env.sh':
+ ensure => 'file',
+ content => @(EOF)
+ for group in $(groups $(id -nu))
+ do
+ f="/etc/profile.d/group.d/${group}"
+ test -f "$f" && . $f
+ done
+ | EOF
+ }
+
+ file { '/etc/profile.d/group.d':
+ ensure => 'directory',
+ }
+}
diff --git a/modules/profiles/manifests/imagemagick.pp b/modules/profiles/manifests/imagemagick.pp
new file mode 100644
index 0000000..7663cf8
--- /dev/null
+++ b/modules/profiles/manifests/imagemagick.pp
@@ -0,0 +1,17 @@
+class profiles::imagemagick {
+ package { 'imagemagick':
+ ensure => installed,
+ }
+
+ file { '/etc/ImageMagick-7/policy.xml':
+ content => epp('profiles/imagemagick-policy.xml', {
+ policies => [
+ {
+ domain => 'coder',
+ rights => 'read | write',
+ pattern => 'PDF'
+ },
+ ]
+ }),
+ }
+}
diff --git a/modules/profiles/manifests/remarkable.pp b/modules/profiles/manifests/remarkable.pp
new file mode 100644
index 0000000..73ee5e7
--- /dev/null
+++ b/modules/profiles/manifests/remarkable.pp
@@ -0,0 +1,31 @@
+define profiles::remarkable (
+ String $prefix = '10.11.99',
+ String $addr = '2',
+) {
+
+ file_line { 'remarkable usb':
+ ensure => present,
+ path => '/etc/hosts',
+ line => "${prefix}.1 remarkable.usb",
+ }
+
+ file_line { 'remarkable usb self':
+ ensure => present,
+ path => '/etc/hosts',
+ line => "${prefix}.${addr} host.usb",
+ }
+
+ file { '/etc/systemd/network/25-remarkable-usb.network':
+ ensure => present,
+ content => @("EOF")
+ [Match]
+ Name=enp3s0f0u4
+
+ [Network]
+
+ Description=Remarkable USB connection
+ Address=${prefix}.${addr}/29
+ | EOF
+ }
+
+}
diff --git a/modules/profiles/manifests/syncthing.pp b/modules/profiles/manifests/syncthing.pp
new file mode 100644
index 0000000..7d8183e
--- /dev/null
+++ b/modules/profiles/manifests/syncthing.pp
@@ -0,0 +1,28 @@
+class profiles::syncthing (
+ Array[String] $enable_for = []
+) {
+
+ # TODO add repo for those systems that need it
+
+ package { 'syncthing':
+ ensure => installed
+ }
+
+ systemd::dropin_file { 'nospam.conf':
+ unit => 'syncthing@.service',
+ content => @(EOF)
+ [Service]
+ ExecStart=
+ ExecStart=/bin/bash -c 'set -o pipefail; /usr/bin/syncthing -no-browser -no-restart -logflags=0 | grep -v "INFO: "'
+ | EOF
+ }
+
+ $enable_for.map |$user| {
+ service { "syncthing@${user}":
+ enable => true,
+ }
+ }
+
+ # TODO manage synced data
+
+}
diff --git a/modules/profiles/manifests/synth.pp b/modules/profiles/manifests/synth.pp
new file mode 100644
index 0000000..eb01f8f
--- /dev/null
+++ b/modules/profiles/manifests/synth.pp
@@ -0,0 +1,33 @@
+class profiles::synth {
+
+ package { 'freepats-general-midi':
+ ensure => installed,
+ }
+
+ file { '/etc/conf.d/fluidsynth':
+ content => @(EOF)
+ SOUND_FONT=/usr/share/soundfonts/freepats-general-midi.sf2
+ OTHER_OPTS='-a alsa'
+ | EOF
+ }
+
+ # TODO pull in aur package from
+ # https://git.hornquist.se/archpkg/aconnect-service/
+
+ # TODO setup the rest
+
+ # - template:
+ # dest: ~/.config/aconnect/impact
+ # source: aconnect
+ # vars:
+ # input_unit: Impact LX25
+ # output_unit: FLUID Synth
+ #
+ # - systemd:
+ # name: aconnect@{{ impact }}
+ # scope: user
+ # enabled: yes
+ # become: yes
+ # become_user: hugo
+
+}
diff --git a/modules/profiles/manifests/transmission.pp b/modules/profiles/manifests/transmission.pp
new file mode 100644
index 0000000..f79517b
--- /dev/null
+++ b/modules/profiles/manifests/transmission.pp
@@ -0,0 +1,71 @@
+class profiles::transmission (
+ Optional[String] $nginx_server = undef,
+ Enum['None', 'Error', 'Info', 'Debug'] $msg_level = 'Error',
+) {
+
+ $transmission_url = '/transmission'
+ $transmission_port = 9091
+
+ if ($nginx_server) {
+ require ::nginx
+
+ nginx::resource::location { $transmission_url:
+ proxy => "http://localhost:${transmission_port}${transmission_url}",
+ proxy_set_header => [],
+ server => $nginx_server,
+ ssl => true,
+ ssl_only => true,
+ }
+ }
+
+ ensure_packages(['transmission-cli'],
+ { ensure => installed })
+
+ systemd::dropin_file { 'transmission-after.conf':
+ unit => 'transmission.service',
+ content => @(EOF)
+ [Unit]
+ After=network-online.target
+ | EOF
+ }
+
+ systemd::dropin_file { 'transmission-flags.conf':
+ unit => 'transmission.service',
+ content => @(EOF)
+ [Service]
+ ExecStart=
+ ExecStart=/usr/bin/transmission-daemon -f
+ | EOF
+ }
+
+ # TODO whitelists are currently disabled, since they don't seem to
+ # work. Possibly turn them on again some day.
+
+ # https://github.com/transmission/transmission/wiki/Editing-Configuration-File
+ file { '/var/lib/transmission/.config/transmission-daemon/settings.json':
+ content => epp('profiles/transmission.json.epp', {
+ rpc_username => 'hugo',
+ # '{' + sha1(password + salt)
+ # But I don't know how I managed to generate it, since
+ # transmission rolls its own crypto
+ rpc_password => '{eb43101d3b9aa02223466d7f98c5329c841c7967/Zr2tFpn',
+ download_dir => '/usr/net/',
+ rpc_whitelist => ['127.0.0.1', '::1'],
+ rpc_port => $transmission_port,
+ rpc_url => "${transmission_url}/",
+ msg_level => case $msg_level {
+ 'None': { 0 }
+ 'Error': { 1 }
+ 'Info': { 2 }
+ 'Debug': { 3 }
+ },
+ }),
+ } ~> exec { '/bin/systemctl reload transmission':
+ refreshonly => true,
+ }
+
+ service { 'transmission':
+ ensure => 'running',
+ enable => true,
+ }
+}
diff --git a/modules/profiles/manifests/webdav_server.pp b/modules/profiles/manifests/webdav_server.pp
new file mode 100644
index 0000000..2cd54c1
--- /dev/null
+++ b/modules/profiles/manifests/webdav_server.pp
@@ -0,0 +1,80 @@
+define profiles::webdav_server (
+ String $nginx_server,
+ String $file_path,
+ String $location = $name,
+ String $passwd_file = "${file_path}/.htpasswd",
+ String $owner = 'http',
+ String $group = 'share',
+ Array[Array[String,2,2]] $users = [],
+ Array[String] $dav_methods = ['PUT', 'DELETE', 'MKCOL', 'COPY', 'MOVE'],
+ Array[String] $dav_ext_methods = ['PROPFIND', 'OPTIONS'],
+ Hash[String,String] $dav_access = {
+ 'user' => 'rw',
+ 'group' => 'rw',
+ }
+) {
+
+ # TODO install this module somehow
+ # AUR: nginx-mainline-mod-dav-ext
+
+ require ::nginx
+
+ $modname = 'ngx_http_dav_ext_module'
+ file { "/etc/nginx/modules-enabled/${modname}.conf":
+ ensure => file,
+ content => @("EOF")
+ load_module /usr/lib/nginx/modules/${modname}.so;
+ | EOF
+ }
+
+ file {
+ default:
+ owner => $owner,
+ group => $group,
+ ;
+ $file_path:
+ ensure => 'directory',
+ mode => '0770',
+ recurse => 'false',
+ ;
+ $passwd_file:
+ ensure => 'file',
+ mode => '0660',
+ ;
+ }
+
+ # add entries to the htpasswd file through
+ # $ echo "${user}:$(openssl passwd -apr1 $password)" >> .htpasswd
+
+
+ $users.each |$pair| {
+ $user = $pair[0]
+ $passwd = $pair[1]
+ file_line { "Add ${user} to dav passwd file":
+ ensure => present,
+ path => $passwd_file,
+ line => "${user}:${passwd}",
+ match => "^${user}:"
+ }
+ }
+
+ nginx::resource::location { $location:
+ server => $nginx_server,
+ location_alias => $file_path,
+ ssl => true,
+ ssl_only => true,
+
+ auth_basic => 'Enter password for dav access',
+ auth_basic_user_file => $passwd_file,
+
+ location_cfg_append => {
+ 'dav_methods' => $dav_methods.join(' '),
+ 'dav_ext_methods' => $dav_ext_methods.join(' '),
+ 'dav_access' => $dav_access.map |$k, $v| { "${k}:${v}" }.join(' '),
+ 'client_body_temp_path' => "${file_path}/tmp",
+ 'create_full_put_path' => 'on',
+ 'autoindex' => 'on',
+ 'allow' => 'all',
+ }
+ }
+}
diff --git a/modules/profiles/manifests/workstation.pp b/modules/profiles/manifests/workstation.pp
new file mode 100644
index 0000000..fe7e1cb
--- /dev/null
+++ b/modules/profiles/manifests/workstation.pp
@@ -0,0 +1,132 @@
+class profiles::workstation {
+ $os = $facts['os']['name'].downcase()
+ include "::profiles::workstation::${os}"
+
+ include ::profiles::group_profile
+
+ # TODO only if we use systemd
+ file { 'User ssh-agent service':
+ path => '/etc/systemd/user/ssh-agent.service',
+ source => "puppet:///modules/profiles/ssh-agent.service",
+ }
+
+ file { 'Dvorak A6 TTY keyboard layout':
+ ensure => file,
+ path => '/usr/share/kbd/keymaps/i386/dvorak/dvorak-sv-a6.map',
+ source => 'https://raw.githubusercontent.com/HugoNikanor/keymaps/master/linux-tty/dvorak-sv-a6.map',
+ }
+
+ file { 'Dvorak A6 X11 keyboard layout':
+ ensure => file,
+ path => '/usr/share/X11/xkb/symbols/planck',
+ source => 'https://raw.githubusercontent.com/HugoNikanor/keymaps/master/X11/planck',
+ }
+
+ $xkb_layout = 'planck'
+ $xkb_variant = 'dvorak_a6'
+ $xkb_options = 'compose:caps'
+
+ file { 'Default X11 keymap':
+ ensure => file,
+ path => '/etc/X11/xorg.conf.d/00-keyboard.conf',
+ content => @("EOF")
+ Section "InputClass"
+ Identifier "system-keyboard"
+ MatchIsKeyboard "on"
+ Option "XkbLayout" "${xkb_layout}"
+ Option XkbModel "pc105"
+ Option "XkbVariant" "${xkb_variant}"
+ Option "XkbOptions" "${xkb_options}"
+ EndSection
+ | EOF
+ }
+
+ file { 'Model M X11 keymap':
+ ensure => file,
+ path => '/etc/X11/xorg.conf.d/01-model-m.conf',
+ content => @(EOF)
+ Section "InputClass"
+ Identifier "Model M"
+ MathUSBID "17f6:0822"
+ Option "XkbLayout" "us"
+ Option "XkbVariant" "dvorak"
+ EndSection
+ | EOF
+ }
+
+ file { 'Setup console':
+ ensure => file,
+ path => '/etc/vconsole.conf',
+ content => epp('profiles/keyvalue.epp', { 'values' => {
+ 'KEYMAP' => 'dvorak-sv-a6',
+ 'FONT' => 'lat9v-12',
+ }}),
+ }
+
+ $cowpath = [
+ '/usr/share/cows',
+ '/usr/local/share/cows',
+ ]
+
+ file { '/etc/environment':
+ content => epp('profiles/keyvalue.epp', { values => {
+ 'COWPATH' => $cowpath.join(':'),
+ 'MANWIDTH' => 80,
+ 'MPD_HOST' => 'jukebox.lysator.liu.se',
+ 'PAGER' => 'less',
+ 'EDITOR' => '/usr/bin/vi',
+ 'VISUAL' => '/usr/bin/vim',
+ }})
+ }
+
+ service { 'systemd-resolved':
+ enable => mask,
+ }
+
+ file { 'Passmenu with OTP support':
+ path => '/usr/local/bin/passmenu',
+ mode => '0555',
+ source => 'puppet:///modules/profiles/passmenu',
+ }
+
+ file { '/etc/sudoers':
+ validate_cmd => '/usr/bin/visudo -cf %',
+ content => @(EOF)
+ Defaults insults
+ root ALL=(ALL) ALL
+ %root ALL=(ALL) ALL
+ %wheel ALL=(ALL) ALL
+
+ @includedir /etc/sudoers.d
+ | EOF
+ }
+
+ $locales = [
+ 'en_DK.UTF-8 UTF-8',
+ 'en_US.UTF-8 UTF-8',
+ 'sv_SE.UTF-8 UTF-8',
+ 'sv_SE.ISO-8859-1 ISO-8859-1',
+ '',
+ ]
+
+ file { '/etc/locale.gen':
+ content => $locales.join("\n")
+ } ~> exec { 'locale-gen':
+ path => [ '/bin', '/usr/bin', ],
+ }
+
+ file { 'Default locales':
+ path => '/etc/locale.conf',
+ content => @(EOF)
+ LANG=en_US.UTF-8
+ LC_TIME=sv_SE.UTF-8
+ | EOF
+ }
+
+ $timezone = 'Europe/Stockholm'
+
+ file { '/etc/localtime':
+ ensure => link,
+ target => "/usr/share/zoneinfo/${timezone}",
+ }
+}
diff --git a/modules/profiles/manifests/workstation/archlinux.pp b/modules/profiles/manifests/workstation/archlinux.pp
new file mode 100644
index 0000000..5274699
--- /dev/null
+++ b/modules/profiles/manifests/workstation/archlinux.pp
@@ -0,0 +1,52 @@
+class profiles::workstation::archlinux {
+
+ pacman::hook { 'systemd daemon-reload':
+ description => 'Reload systemd user daemon',
+ exec => '/bin/sudo systemctl --machine=hugo@.host --user daemon-reload',
+ when => 'PostTransaction',
+ trigger => {
+ operation => 'Upgrade',
+ type => 'Path',
+ target => 'usr/lib/systemd/user/*',
+ },
+ }
+
+ package { 'kernel-modules-hook':
+ ensure => installed,
+ } ~> service { 'linux-modules-cleanup':
+ ensure => running,
+ enable => true,
+ }
+
+ $cpus = $facts['processors']['count'] - 1
+ file_line { 'Makepkg paralell':
+ path => '/etc/makepkg.conf',
+ after => '^#-- Make flags',
+ line => "MAKEFLAGS='-j${cpus}'"
+ }
+
+ pacman::repo { 'adrift-space':
+ ensure => present,
+ server => 'http://repo.gandalf.adrift.space/arch',
+ sig_level => 'Optional',
+ }
+
+ # remove
+ # - netctl
+
+ # aur-packages
+# - pacaur
+# - ansible-aur-git
+# - cyrus-sasl-xoauth2-git
+# - todotxt
+# - effitask
+# - getmail
+# - mu
+# # - pacaur
+# - pandoc-bin
+# - tlclient
+# # backups old modules on kernel update
+# - kernel-modules-hook
+
+
+}
diff --git a/modules/profiles/manifests/xmonad.pp b/modules/profiles/manifests/xmonad.pp
new file mode 100644
index 0000000..be8d516
--- /dev/null
+++ b/modules/profiles/manifests/xmonad.pp
@@ -0,0 +1,29 @@
+# Setup xmonad, only tested on arch linux
+class profiles::xmonad {
+ ensure_packages ([
+ 'xmonad',
+ 'xmonad-contrib',
+ # apparently really needed by xmonad
+ 'xorg-fonts-misc',
+ 'ghc',
+ 'xorg-xmessage',
+ 'dzen2',
+ 'dmenu',
+ 'rofi',
+ ], { ensure => installed })
+
+ # Rebuilt my local xmonad config after an upgrade to xmonad.
+ # It's required, I think due to something with dynamic linking.
+ # It's actually pretty ugly that I'm hardcoded in here, but
+ # something had to be done.
+ pacman::hook { 'xmonad':
+ description => 'Rebuild local xmonad config.',
+ when => 'PostTransaction',
+ exec => '/bin/sudo -Hu hugo xmonad --recompile',
+ trigger => {
+ type => 'Package',
+ operation => ['Upgrade', 'Install'],
+ target => 'xmonad*',
+ },
+ }
+}
diff --git a/modules/profiles/templates/aconnect.epp b/modules/profiles/templates/aconnect.epp
new file mode 100644
index 0000000..044ada6
--- /dev/null
+++ b/modules/profiles/templates/aconnect.epp
@@ -0,0 +1,8 @@
+<%- | String $input_unit,
+ String $output_unit
+| -%>
+# Where data comes from
+INPUT_UNIT='<%= $input_unit %>'
+# Where it should go
+OUTPUT_UNIT='<%= $output_unit %>'
+
diff --git a/modules/profiles/templates/imagemagick-policy.xml.epp b/modules/profiles/templates/imagemagick-policy.xml.epp
new file mode 100644
index 0000000..cbea9e9
--- /dev/null
+++ b/modules/profiles/templates/imagemagick-policy.xml.epp
@@ -0,0 +1,89 @@
+<%- | Array[Hash] $policies | -%>
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE policymap [
+ <!ELEMENT policymap (policy)*>
+ <!ATTLIST policymap xmlns CDATA #FIXED ''>
+ <!ELEMENT policy EMPTY>
+ <!ATTLIST policy xmlns CDATA #FIXED '' domain NMTOKEN #REQUIRED
+ name NMTOKEN #IMPLIED pattern CDATA #IMPLIED rights NMTOKEN #IMPLIED
+ stealth NMTOKEN #IMPLIED value CDATA #IMPLIED>
+]>
+<!--
+ Configure ImageMagick policies.
+
+ Domains include system, delegate, coder, filter, path, or resource.
+
+ Rights include none, read, write, execute and all. Use | to combine them,
+ for example: "read | write" to permit read from, or write to, a path.
+
+ Use a glob expression as a pattern.
+
+ Suppose we do not want users to process MPEG video images:
+
+ <policy domain="delegate" rights="none" pattern="mpeg:decode" />
+
+ Here we do not want users reading images from HTTP:
+
+ <policy domain="coder" rights="none" pattern="HTTP" />
+
+ The /repository file system is restricted to read only. We use a glob
+ expression to match all paths that start with /repository:
+
+ <policy domain="path" rights="read" pattern="/repository/*" />
+
+ Lets prevent users from executing any image filters:
+
+ <policy domain="filter" rights="none" pattern="*" />
+
+ Any large image is cached to disk rather than memory:
+
+ <policy domain="resource" name="area" value="1GP"/>
+
+ Use the default system font unless overwridden by the application:
+
+ <policy domain="system" name="font" value="/usr/share/fonts/favorite.ttf"/>
+
+ Define arguments for the memory, map, area, width, height and disk resources
+ with SI prefixes (.e.g 100MB). In addition, resource policies are maximums
+ for each instance of ImageMagick (e.g. policy memory limit 1GB, -limit 2GB
+ exceeds policy maximum so memory limit is 1GB).
+
+ Rules are processed in order. Here we want to restrict ImageMagick to only
+ read or write a small subset of proven web-safe image types:
+
+ <policy domain="delegate" rights="none" pattern="*" />
+ <policy domain="filter" rights="none" pattern="*" />
+ <policy domain="coder" rights="none" pattern="*" />
+ <policy domain="coder" rights="read|write" pattern="{GIF,JPEG,PNG,WEBP}" />
+-->
+<policymap>
+ <!-- Sample policies -->
+ <!-- <policy domain="resource" name="temporary-path" value="/tmp"/> -->
+ <!-- <policy domain="resource" name="memory" value="2GiB"/> -->
+ <!-- <policy domain="resource" name="map" value="4GiB"/> -->
+ <!-- <policy domain="resource" name="width" value="10KP"/> -->
+ <!-- <policy domain="resource" name="height" value="10KP"/> -->
+ <!-- <policy domain="resource" name="list-length" value="128"/> -->
+ <!-- <policy domain="resource" name="area" value="100MP"/> -->
+ <!-- <policy domain="resource" name="disk" value="16EiB"/> -->
+ <!-- <policy domain="resource" name="file" value="768"/> -->
+ <!-- <policy domain="resource" name="thread" value="4"/> -->
+ <!-- <policy domain="resource" name="throttle" value="0"/> -->
+ <!-- <policy domain="resource" name="time" value="3600"/> -->
+ <!-- <policy domain="coder" rights="none" pattern="MVG" /> -->
+ <!-- <policy domain="module" rights="none" pattern="{PS,PDF,XPS}" /> -->
+ <!-- <policy domain="delegate" rights="none" pattern="HTTPS" /> -->
+ <!-- <policy domain="path" rights="none" pattern="@*" /> -->
+ <!-- <policy domain="cache" name="memory-map" value="anonymous"/> -->
+ <!-- <policy domain="cache" name="synchronize" value="True"/> -->
+ <!-- <policy domain="cache" name="shared-secret" value="passphrase" stealth="true"/> -->
+ <!-- <policy domain="system" name="max-memory-request" value="256MiB"/> -->
+ <!-- <policy domain="system" name="shred" value="2"/> -->
+ <!-- <policy domain="system" name="precision" value="6"/> -->
+ <!-- <policy domain="system" name="font" value="/path/to/unicode-font.ttf"/> -->
+ <!-- Below policies generated from puppet -->
+ <% $policies.map |$policy| { %>
+ <policy domain="<%= $policy['domain'] %>" rights="<%= $policy['rights'] %>" pattern="<%= $policy['pattern'] %>" />
+ <%- } %>
+</policymap>
+<!-- NOTE File managed by puppet, any manual changes will be overwritten. -->
diff --git a/modules/profiles/templates/keyvalue.epp b/modules/profiles/templates/keyvalue.epp
new file mode 100644
index 0000000..694978a
--- /dev/null
+++ b/modules/profiles/templates/keyvalue.epp
@@ -0,0 +1,4 @@
+<%- | Hash $values | -%>
+<% $values.map |$key, $value| { -%>
+<%= $key %>=<%= $value %>
+<%- } %>
diff --git a/modules/profiles/templates/transmission.json.epp b/modules/profiles/templates/transmission.json.epp
new file mode 100644
index 0000000..885ad5e
--- /dev/null
+++ b/modules/profiles/templates/transmission.json.epp
@@ -0,0 +1,77 @@
+<%- | String $rpc_username,
+ String $rpc_password,
+ String $download_dir,
+ Integer $rpc_port,
+ String $rpc_url,
+ Integer $msg_level = 1,
+ Optional[String] $incomplete_dir = undef,
+ Optional[Array[String]] $rpc_whitelist = undef,
+| -%>
+{
+ "alt-speed-down": 50,
+ "alt-speed-enabled": false,
+ "alt-speed-time-begin": 540,
+ "alt-speed-time-day": 127,
+ "alt-speed-time-enabled": false,
+ "alt-speed-time-end": 1020,
+ "alt-speed-up": 50,
+ "bind-address-ipv4": "0.0.0.0",
+ "bind-address-ipv6": "::",
+ "blocklist-enabled": false,
+ "blocklist-url": "http://www.example.com/blocklist",
+ "cache-size-mb": 4,
+ "dht-enabled": true,
+ "download-dir": "<%= $download_dir -%>",
+ "download-queue-enabled": true,
+ "download-queue-size": 5,
+ "encryption": 1,
+ "idle-seeding-limit": 30,
+ "idle-seeding-limit-enabled": false,
+ "incomplete-dir": "<%= $incomplete_dir -%>",
+ "incomplete-dir-enabled": <%= if ($incomplete_dir) { 'true' } else { 'false' } -%>,
+ "lpd-enabled": false,
+ "message-level": <%= $msg_level -%>,
+ "peer-congestion-algorithm": "",
+ "peer-id-ttl-hours": 6,
+ "peer-limit-global": 200,
+ "peer-limit-per-torrent": 50,
+ "peer-port": 51413,
+ "peer-port-random-high": 65535,
+ "peer-port-random-low": 49152,
+ "peer-port-random-on-start": false,
+ "peer-socket-tos": "default",
+ "pex-enabled": true,
+ "port-forwarding-enabled": true,
+ "preallocation": 1,
+ "prefetch-enabled": true,
+ "queue-stalled-enabled": true,
+ "queue-stalled-minutes": 30,
+ "ratio-limit": 2,
+ "ratio-limit-enabled": false,
+ "rename-partial-files": true,
+ "rpc-authentication-required": true,
+ "rpc-bind-address": "::",
+ "rpc-enabled": true,
+ "rpc-host-whitelist": "",
+ "rpc-host-whitelist-enabled": false,
+ "rpc-password": "<%= $rpc_password -%>",
+ "rpc-port": <%= $rpc_port -%>,
+ "rpc-url": "<%= $rpc_url -%>",
+ "rpc-username": "<%= $rpc_username -%>",
+ "rpc-whitelist": "<%= $rpc_whitelist.join(',') -%>",
+ "rpc-whitelist-enabled": <%= if ($rpc_whitelist) { 'false' } else { 'false' }-%>,
+ "scrape-paused-torrents-enabled": true,
+ "script-torrent-done-enabled": false,
+ "script-torrent-done-filename": "",
+ "seed-queue-enabled": false,
+ "seed-queue-size": 10,
+ "speed-limit-down": 100,
+ "speed-limit-down-enabled": false,
+ "speed-limit-up": 100,
+ "speed-limit-up-enabled": false,
+ "start-added-torrents": true,
+ "trash-original-torrent-files": false,
+ "umask": 18,
+ "upload-slots-per-torrent": 14,
+ "utp-enabled": true
+}
diff --git a/modules/shiori/files/shiori.service b/modules/shiori/files/shiori.service
new file mode 100644
index 0000000..6c8de29
--- /dev/null
+++ b/modules/shiori/files/shiori.service
@@ -0,0 +1,13 @@
+[Unit]
+Description=Bookmark server
+
+[Service]
+User=shiori
+Environment=SHIORI_DIR=/var/www/shiori
+Environment=PORT=8080
+EnvironmentFile=-/etc/conf.d/shiori
+ExecStart=shiori serve -p $PORT
+Restart=always
+
+[Install]
+WantedBy=multi-user.target
diff --git a/modules/shiori/manifests/init.pp b/modules/shiori/manifests/init.pp
new file mode 100644
index 0000000..a8622e8
--- /dev/null
+++ b/modules/shiori/manifests/init.pp
@@ -0,0 +1,97 @@
+class shiori (
+ $port = 8080,
+ Array[String] $group_members = [],
+ Optional[Hash] $nginx = undef,
+) {
+
+ # on arch this is available through the aur
+ package { 'shiori-bin':
+ ensure => installed,
+ }
+
+ user { 'shiori':
+ ensure => present,
+ system => true,
+ home => '/var/www/shiori',
+ }
+
+ group { 'shiori':
+ ensure => present,
+ members => $group_members,
+ }
+
+ file { '/var/www/shiori':
+ ensure => directory,
+ owner => shiori,
+ group => shiori,
+ mode => '0750',
+ }
+
+ file { [
+ '/var/www/shiori/archive',
+ '/var/www/shiori/thumb',
+ ] :
+ ensure => directory,
+ owner => shiori,
+ group => shiori,
+ mode => '0770',
+ }
+
+ file { '/var/www/shiori/shiori.db':
+ owner => 'shiori',
+ group => 'shiori',
+ mode => '0660',
+ }
+
+ file { '/etc/systemd/system/shiori.service':
+ ensure => file,
+ source => 'puppet:///modules/shiori/shiori.service',
+ }
+
+ file { '/etc/conf.d/shiori':
+ ensure => 'file',
+ content => @("EOF")
+ # This file is managed by Puppet.
+ # Editing it might also lead to inconsistencies with nginx
+ PORT=${port}
+ | EOF
+ }
+
+ service { 'shiori':
+ ensure => running,
+ enable => true,
+ require => [
+ File['/etc/systemd/system/shiori.service'],
+ File['/etc/conf.d/shiori'],
+ ],
+ }
+
+ # TODO only run this if Class['profiles::group_profile'] is loaded
+ file { '/etc/profile.d/group.d/shiori':
+ ensure => file,
+ content => "export SHIORI_DIR=/var/www/shiori\n",
+ }
+
+ if ($nginx) {
+ $certname = $nginx['certname']
+ nginx::resource::server { $nginx['server_name']:
+ ipv6_enable => true,
+ ipv6_listen_options => '',
+ ssl => true,
+ ssl_redirect => true,
+ ssl_cert => "/etc/letsencrypt/live/${certname}/fullchain.pem",
+ ssl_key => "/etc/letsencrypt/live/${certname}/privkey.pem",
+ www_root => '/var/www/shiori',
+ use_default_location => false,
+ }
+
+ nginx::resource::location { 'shiori /':
+ location => '/',
+ proxy => "http://[::]:$port",
+ index_files => [],
+ ssl => true,
+ ssl_only => true,
+ server => $nginx['server_name'],
+ }
+ }
+}
diff --git a/modules/systemd_mount/manifests/init.pp b/modules/systemd_mount/manifests/init.pp
new file mode 100644
index 0000000..ff081e4
--- /dev/null
+++ b/modules/systemd_mount/manifests/init.pp
@@ -0,0 +1,42 @@
+define systemd_mount (
+ String $what, # elrond:/files
+ String $where, # /usr/net
+ Boolean $automount = false,
+ String $wantedBy = 'default.target',
+) {
+
+ $mostly_fixed = regsubst($where, '/', '-', 'G')
+ $fixed = if $mostly_fixed[0] == '-' {
+ $mostly_fixed[1, -1] # drop first char
+ } else {
+ $mostly_fixed
+ }
+
+ systemd::unit_file { "${fixed}.mount":
+ content => epp('systemd_mount/mount.epp', {
+ what => $what,
+ where => $where,
+ wantedby => if ($automount) { '' } else { "WantedBy=${wantedBy}" },
+ }),
+ }
+
+ if ($automount) {
+ systemd::unit_file { "${fixed}.automount":
+ content => epp('systemd_mount/automount.epp', {
+ where => $where,
+ wantedBy => "WantedBy=${wantedBy}",
+ }),
+ }
+
+ service { "${fixed}.automount":
+ enable => true,
+ ensure => running,
+ }
+ } else {
+ service { "${fixed}.mount":
+ enable => true,
+ ensure => running,
+ }
+ }
+
+}
diff --git a/modules/systemd_mount/templates/automount.epp b/modules/systemd_mount/templates/automount.epp
new file mode 100644
index 0000000..c65f2ae
--- /dev/null
+++ b/modules/systemd_mount/templates/automount.epp
@@ -0,0 +1,11 @@
+<%- | String $where,
+ String $wantedBy,
+| -%>
+
+[Unit]
+
+[Install]
+<%= $wantedBy %>
+
+[Automount]
+Where=<%= $where %>
diff --git a/modules/systemd_mount/templates/mount.epp b/modules/systemd_mount/templates/mount.epp
new file mode 100644
index 0000000..54d191a
--- /dev/null
+++ b/modules/systemd_mount/templates/mount.epp
@@ -0,0 +1,13 @@
+<%- | String $where,
+ String $what,
+ String $wantedby,
+| -%>
+
+[Unit]
+
+[Install]
+<%= $wantedby %>
+
+[Mount]
+Where=<%= $where %>
+What=<%= $what %>
diff --git a/run b/run
new file mode 100755
index 0000000..789d1b6
--- /dev/null
+++ b/run
@@ -0,0 +1,26 @@
+#!/bin/bash
+
+# Needed on ubuntu, since this path is not set for root
+# Arch installs puppet in /usr/bin/puppet.
+export PATH="/opt/puppetlabs/bin/:$PATH"
+
+osid=$(awk -F= '/^ID=/ { print $2 }' /etc/os-release)
+
+# This is the WRONG way to do it, but it sholud work for now
+case $osid in
+ ubuntu)
+ modpath=/etc/puppetlabs/code/environments/production/modules
+ ;;
+ arch)
+ modpath=/etc/puppetlabs/code/modules/
+ ;;
+esac
+
+set -x
+
+sudo env PATH="/opt/puppetlabs/bin/:$PATH" \
+ puppet apply \
+ --modulepath="$PWD/modules:${modpath}" \
+ manifests \
+ --verbose \
+ "$@"