Browse Source

Initial commit

master
Lukas Bestle 2 years ago
commit
c3200c91db
Signed by: lukas GPG Key ID: 692037D104550FC9
  1. 6
      ansible.cfg
  2. 14
      hosts
  3. 5
      requirements.yml
  4. 3
      roles/dav/files/home/.config/fish/functions/radicale.fish
  5. 54
      roles/dav/files/home/bin/radicale_to_ics
  6. 0
      roles/dav/files/home/etc/services.d/.gitkeep
  7. 24
      roles/dav/files/home/radicale/rights
  8. 3
      roles/dav/files/home/radicale/storage/.gitignore
  9. 8
      roles/dav/handlers/main.yml
  10. 63
      roles/dav/tasks/main.yml
  11. 16
      roles/dav/templates/config.j2
  12. 4
      roles/dav/templates/service.ini.j2
  13. 3
      roles/dav/templates/users.j2
  14. 3
      roles/git/files/home/.config/fish/functions/gitea.fish
  15. 0
      roles/git/files/home/etc/services.d/.gitkeep
  16. 0
      roles/git/files/home/gitea/custom/conf/.gitkeep
  17. BIN
      roles/git/files/home/gitea/custom/public/img/favicon.png
  18. BIN
      roles/git/files/home/gitea/custom/public/img/gitea-lq.png
  19. 1
      roles/git/files/home/gitea/custom/public/img/gitea-safari.svg
  20. BIN
      roles/git/files/home/gitea/custom/public/img/gitea-sm.png
  21. 24
      roles/git/files/home/gitea/custom/templates/base/footer_content.tmpl
  22. 133
      roles/git/files/home/gitea/custom/templates/base/head_navbar.tmpl
  23. 0
      roles/git/files/home/gitea/data/.gitkeep
  24. 0
      roles/git/files/home/gitea/log/.gitkeep
  25. 0
      roles/git/files/home/gitea/repos/.gitkeep
  26. 8
      roles/git/handlers/main.yml
  27. 47
      roles/git/tasks/main.yml
  28. 73
      roles/git/templates/app.ini.j2
  29. 5
      roles/git/templates/service.ini.j2
  30. 3
      roles/homebrew/handlers/main.yml
  31. 13
      roles/homebrew/tasks/main.yml
  32. 11
      roles/host-site/tasks/main.yml
  33. 127
      roles/host-site/templates/index.html.j2
  34. 15
      roles/mail-redirect/tasks/main.yml
  35. 59
      roles/mail/tasks/main.yml
  36. 32
      roles/mail/templates/muttrc.j2
  37. 3
      roles/mail/templates/qmail-dated.j2
  38. 4
      roles/terminal/files/authorized_keys
  39. 0
      roles/terminal/files/dotfiles/.composer/vendor/bin/.gitkeep
  40. 60
      roles/terminal/files/dotfiles/.config/fish/conf.d/pkgs.fish
  41. 46
      roles/terminal/files/dotfiles/.config/fish/config.fish
  42. 3
      roles/terminal/files/dotfiles/.config/fish/functions/fish_greeting.fish
  43. 21
      roles/terminal/files/dotfiles/.config/fish/functions/nsreverse.fish
  44. 21
      roles/terminal/files/dotfiles/.config/fish/functions/nsreverse6.fish
  45. 0
      roles/terminal/files/dotfiles/.config/fish/pkgs/.gitkeep
  46. 0
      roles/terminal/files/dotfiles/.config/git/.gitkeep
  47. 1136
      roles/terminal/files/dotfiles/.config/welcome/colossal.flf
  48. 6
      roles/terminal/files/dotfiles/.config/welcome/summarizers/01-date.sh
  49. 111
      roles/terminal/files/dotfiles/.config/welcome/summarizers/02-uptime.sh
  50. 12
      roles/terminal/files/dotfiles/.config/welcome/summarizers/03-quota.sh
  51. 8
      roles/terminal/files/dotfiles/.config/welcome/summarizers/04-ram.sh
  52. 0
      roles/terminal/files/dotfiles/.config/welcome/warners/.gitkeep
  53. 0
      roles/terminal/files/dotfiles/.local/bin/.gitkeep
  54. 3
      roles/terminal/handlers/main.yml
  55. 58
      roles/terminal/tasks/main.yml
  56. 17
      roles/terminal/templates/gitconfig.j2
  57. 0
      roles/web/files/home/.config/projectr/.gitkeep
  58. 5
      roles/web/files/home/bin/qdated-generate
  59. 102
      roles/web/tasks/main.yml
  60. 172
      roles/web/templates/deploy.php.j2
  61. 9
      run.sh
  62. 46
      site.yml
  63. 51
      vars/config.yml
  64. 20
      vars/installs.yml
  65. 30
      vars/vault.yml

6
ansible.cfg

@ -0,0 +1,6 @@
[defaults]
inventory = hosts
transport = ssh
[ssh_connection]
pipelining = true

14
hosts

@ -0,0 +1,14 @@
[dav]
kodos-cdddav.codesignd.net
[git]
kodos-cddgit.codesignd.net
[mail]
kodos-cddmail[1:2].codesignd.net
[misc]
kodos-cddmisc1.codesignd.net
[web]
kodos-cddweb[1:2].codesignd.net

5
requirements.yml

@ -0,0 +1,5 @@
---
- src: snapstromegon.uberspace_mail_catchall
- src: snapstromegon.uberspace_mail_user
- src: snapstromegon.uberspace_web_backend
- src: snapstromegon.uberspace_web_domain

3
roles/dav/files/home/.config/fish/functions/radicale.fish

@ -0,0 +1,3 @@
function radicale
~/.local/bin/radicale --config ~/radicale/config $argv
end

54
roles/dav/files/home/bin/radicale_to_ics

@ -0,0 +1,54 @@
#!/usr/bin/env bash
########################################################################
# 2018-11-25 Lukas Bestle <project-shell@lukasbestle.com>
########################################################################
# Concats a directory of ICS files to a single ICS file for sharing
#
# Usage: radicale_to_ics <directory> <output.ics> [<calname>]
#
# <directory> Directory with ICS files (e.g. from Radicale)
# <output.ics> Output path for the concatenated ICS file
# <calname> Optional name of the ICS file
# (displayed in clients)
########################################################################
directory="$1"
output="$2"
calname="$3"
if [[ -z "$output" ]]; then
# Print help
echo -e "\033[1mUsage:\033[0m \033[34mradicale_to_ics\033[0m <directory> <output.ics> [<calname>]"
exit 1
fi
# Place temporary file right next to the output file
# Will be moved when done with processing
tmp="$output.tmp"
# iCalendar header
echo "BEGIN:VCALENDAR" > "$tmp"
echo "VERSION:2.0" >> "$tmp"
echo "X-WR-TIMEZONE:Europe/Berlin" >> "$tmp"
echo "CALSCALE:GREGORIAN" >> "$tmp"
echo "METHOD:PUBLISH" >> "$tmp"
# Add calendar name if given
if [[ -n "$calname" ]]; then
echo "X-WR-CALNAME:$calname" >> "$tmp"
fi
# Concat all event files in the given directory
# unless the directory is empty
if [[ "$(ls "$directory")" ]]; then
for file in "$directory"/*; do
# Extract all VEVENT elements (supports multiple per input file)
sed '/^BEGIN:VEVENT/, /^END:VEVENT/!d' "$file" >> "$tmp"
done
fi
# Add iCalendar footer
echo "END:VCALENDAR" >> "$tmp"
# Move to final destination
mv "$tmp" "$output"

0
roles/dav/files/home/etc/services.d/.gitkeep

24
roles/dav/files/home/radicale/rights

@ -0,0 +1,24 @@
# The user "lukas" can read and write any collection
[admin]
user: lukas
collection: .*
permissions: RrWw
# Allow reading root collection for authenticated users (required for the web interface)
[root]
user: .+
collection:
permissions: R
# Allow reading and writing principal collection (same as user name)
[principal]
user: .+
collection: {user}
permissions: RW
# Allow reading and writing calendars and address books that are direct
# children of the principal collection
[calendars]
user: .+
collection: {user}/[^/]+
permissions: rw

3
roles/dav/files/home/radicale/storage/.gitignore

@ -0,0 +1,3 @@
.Radicale.cache
.Radicale.lock
.Radicale.tmp-*

8
roles/dav/handlers/main.yml

@ -0,0 +1,8 @@
---
- name: Reread service config
command: supervisorctl reread
- name: Restart Radicale
supervisorctl:
name: radicale
state: restarted

63
roles/dav/tasks/main.yml

@ -0,0 +1,63 @@
---
- name: Add DAV domain to Uberspace config
import_role:
name: snapstromegon.uberspace_web_domain
vars:
domain: dav.bstl.xyz
- name: Copy config to home directory
copy:
src: home/
dest: "{{ ansible_env.HOME }}/"
mode: preserve
tags: radicale-update
notify: Restart Radicale
- name: Copy service config
template:
src: service.ini.j2
dest: "{{ ansible_env.HOME }}/etc/services.d/radicale.ini"
notify: Reread service config
- name: Copy app config
template:
src: config.j2
dest: "{{ ansible_env.HOME }}/radicale/config"
tags: radicale-update
notify: Restart Radicale
- name: Set up Radicale users
template:
src: users.j2
dest: "{{ ansible_facts.env.HOME }}/radicale/users"
tags: radicale-update
notify: Restart Radicale
- name: Configure web backend
import_role:
name: snapstromegon.uberspace_web_backend
vars:
route: dav.bstl.xyz
http:
port: 8080
- name: Install and update Radicale
pip:
name: radicale[bcrypt]
state: latest
extra_args: --user
executable: pip3.8
tags: radicale-update
notify: Restart Radicale
- name: Initialize storage repository
command:
chdir: "{{ ansible_env.HOME }}/radicale/storage"
cmd: git init
creates: "{{ ansible_env.HOME }}/radicale/storage/.git"
- name: Set up garbage collection cronjob
cron:
name: "Garbage-collection"
special_time: weekly
job: git --git-dir {{ ansible_env.HOME }}/radicale/storage/.git gc

16
roles/dav/templates/config.j2

@ -0,0 +1,16 @@
[server]
hosts = 0.0.0.0:8080
[auth]
type = htpasswd
htpasswd_filename = {{ ansible_env.HOME }}/radicale/users
htpasswd_encryption = bcrypt
realm = bstl DAV - Password Required
[rights]
type = from_file
file = {{ ansible_env.HOME }}/radicale/rights
[storage]
filesystem_folder = {{ ansible_env.HOME }}/radicale/storage
hook = git add -A && (git diff --cached --quiet || git commit -m "Changes by "%(user)s)

4
roles/dav/templates/service.ini.j2

@ -0,0 +1,4 @@
[program:radicale]
command={{ ansible_env.HOME }}/.local/bin/radicale --config {{ ansible_env.HOME }}/radicale/config
autostart=yes
autorestart=yes

3
roles/dav/templates/users.j2

@ -0,0 +1,3 @@
{% for username, password in dav_users.items() %}
{{ username }}:{{ password }}
{% endfor %}

3
roles/git/files/home/.config/fish/functions/gitea.fish

@ -0,0 +1,3 @@
function gitea
env GITEA_WORK_DIR=$HOME/gitea ~/.linuxbrew/bin/gitea $argv
end

0
roles/git/files/home/etc/services.d/.gitkeep

0
roles/git/files/home/gitea/custom/conf/.gitkeep

BIN
roles/git/files/home/gitea/custom/public/img/favicon.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

BIN
roles/git/files/home/gitea/custom/public/img/gitea-lq.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

1
roles/git/files/home/gitea/custom/public/img/gitea-safari.svg

@ -0,0 +1 @@
<svg width="1000" height="1000" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd"><path d="M591.655 0a504.542 504.542 0 0178.979 21.11l-12.775 135.521a377.428 377.428 0 0160.526 35.013l110.923-78.783a511.365 511.365 0 0157.832 57.832l-78.783 110.922a377.428 377.428 0 0135.012 60.526l135.52-12.775A504.542 504.542 0 011000 408.345l-123.768 56.707A382.582 382.582 0 01877.827 500c0 11.784-.54 23.441-1.595 34.95L1000 591.654a504.542 504.542 0 01-21.11 78.979l-135.52-12.775a377.428 377.428 0 01-35.013 60.526l78.783 110.922a511.365 511.365 0 01-57.833 57.833l-110.922-78.784a377.428 377.428 0 01-60.526 35.013l12.775 135.52A504.542 504.542 0 01591.655 1000l-56.707-123.77a382.582 382.582 0 01-34.947 1.596c-11.783 0-23.44-.54-34.948-1.595L408.345 1000a504.542 504.542 0 01-78.979-21.11l12.775-135.522a377.428 377.428 0 01-60.525-35.012L170.693 887.14a511.365 511.365 0 01-57.832-57.832l78.783-110.925a377.428 377.428 0 01-35.011-60.524L21.11 670.634A504.542 504.542 0 010 591.655l123.77-56.707A382.582 382.582 0 01122.175 500c0-11.783.54-23.44 1.595-34.947L0 408.345a504.542 504.542 0 0121.11-78.979l135.523 12.775a377.428 377.428 0 0135.011-60.524L112.86 170.693a511.365 511.365 0 0157.833-57.833l110.923 78.784a377.428 377.428 0 0160.525-35.012L329.366 21.11A504.542 504.542 0 01408.345 0l56.708 123.77A382.582 382.582 0 01500 122.173c11.783 0 23.44.54 34.947 1.595L591.655 0zm-91.654 241.084c-142.995 0-258.916 115.92-258.916 258.916 0 142.995 115.92 258.916 258.916 258.916 142.995 0 258.916-115.92 258.916-258.916 0-142.995-115.92-258.916-258.916-258.916z" fill="#000"/><path stroke="#000" stroke-width="57.537" d="M500.5 347l132.935 76.75v153.5L500.5 654l-132.935-76.75v-153.5z"/></g></svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
roles/git/files/home/gitea/custom/public/img/gitea-sm.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

24
roles/git/files/home/gitea/custom/templates/base/footer_content.tmpl

@ -0,0 +1,24 @@
<footer>
<div class="ui container">
<div class="ui left">
© Lukas Bestle
</div>
<div class="ui right links">
{{if .ShowFooterBranding}}
<a target="_blank" rel="noopener noreferrer" href="https://github.com/go-gitea/gitea"><i class="fa fa-github-square"></i><span class="sr-only">GitHub</span></a>
{{end}}
<div class="ui language bottom floating slide up dropdown link item">
<i class="world icon"></i>
<div class="text">{{.LangName}}</div>
<div class="menu">
{{range .AllLangs}}
<a class="item {{if eq $.Lang .Lang}}active selected{{end}}" href="{{if eq $.Lang .Lang}}#{{else}}{{$.Link}}?lang={{.Lang}}{{end}}">{{.Name}}</a>
{{end}}
</div>
</div>
<a target="_blank" href="https://codesignd.com">{{.i18n.Tr "website"}}</a>
<a target="_blank" href="https://codesignd.com/legal">Imprint</a>
{{if (or .ShowFooterVersion .PageIsAdmin)}}<span class="version">{{GoVer}}</span>{{end}}
</div>
</div>
</footer>

133
roles/git/files/home/gitea/custom/templates/base/head_navbar.tmpl

@ -0,0 +1,133 @@
<div class="ui container" id="navbar">
<div class="item brand" style="justify-content: space-between;">
<a href="{{AppSubUrl}}/">
<img class="ui mini image" src="{{AppSubUrl}}/img/gitea-sm.png">
</a>
<div class="ui basic icon button mobile-only" id="navbar-expand-toggle">
<i class="sidebar icon"></i>
</div>
</div>
{{if .IsSigned}}
<a class="item {{if .PageIsDashboard}}active{{end}}" href="{{AppSubUrl}}/">{{.i18n.Tr "dashboard"}}</a>
<a class="item {{if .PageIsIssues}}active{{end}}" href="{{AppSubUrl}}/issues">{{.i18n.Tr "issues"}}</a>
<a class="item {{if .PageIsPulls}}active{{end}}" href="{{AppSubUrl}}/pulls">{{.i18n.Tr "pull_requests"}}</a>
<a class="item {{if .PageIsExplore}}active{{end}}" href="{{AppSubUrl}}/explore/repos">{{.i18n.Tr "explore"}}</a>
{{else if .IsLandingPageHome}}
<a class="item {{if .PageIsHome}}active{{end}}" href="{{AppSubUrl}}/">{{.i18n.Tr "home"}}</a>
<a class="item {{if .PageIsExplore}}active{{end}}" href="{{AppSubUrl}}/explore/repos">{{.i18n.Tr "explore"}}</a>
{{else if .IsLandingPageExplore}}
<a class="item {{if .PageIsExplore}}active{{end}}" href="{{AppSubUrl}}/explore/repos">{{.i18n.Tr "home"}}</a>
{{else if .IsLandingPageOrganizations}}
<a class="item {{if .PageIsExplore}}active{{end}}" href="{{AppSubUrl}}/explore/organizations">{{.i18n.Tr "home"}}</a>
{{end}}
{{template "custom/extra_links" .}}
{{/*
<div class="item">
<div class="ui icon input">
<input class="searchbox" type="text" placeholder="{{.i18n.Tr "search_project"}}">
<i class="search icon"></i>
</div>
</div>
*/}}
{{if .IsSigned}}
<div class="right stackable menu">
<a href="{{AppSubUrl}}/notifications" class="item poping up" data-content='{{.i18n.Tr "notifications"}}' data-variation="tiny inverted">
<span class="text">
<i class="fitted octicon octicon-bell"></i>
<span class="sr-mobile-only">{{.i18n.Tr "notifications"}}</span>
{{if .NotificationUnreadCount}}
<span class="ui red label">
{{.NotificationUnreadCount}}
</span>
{{end}}
</span>
</a>
<div class="ui dropdown jump item poping up" data-content="{{.i18n.Tr "create_new"}}" data-variation="tiny inverted">
<span class="text">
<i class="fitted octicon octicon-plus"></i>
<span class="sr-mobile-only">{{.i18n.Tr "create_new"}}</span>
<i class="fitted octicon octicon-triangle-down not-mobile"></i>
</span>
<div class="menu">
<a class="item" href="{{AppSubUrl}}/repo/create">
<i class="fitted octicon octicon-plus"></i> {{.i18n.Tr "new_repo"}}
</a>
<a class="item" href="{{AppSubUrl}}/repo/migrate">
<i class="fitted octicon octicon-repo-clone"></i> {{.i18n.Tr "new_migrate"}}
</a>
{{if .SignedUser.CanCreateOrganization}}
<a class="item" href="{{AppSubUrl}}/org/create">
<i class="fitted octicon octicon-organization"></i> {{.i18n.Tr "new_org"}}
</a>
{{end}}
</div><!-- end content create new menu -->
</div><!-- end dropdown menu create new -->
<div class="ui dropdown jump item poping up" tabindex="-1" data-content="{{.i18n.Tr "user_profile_and_more"}}" data-variation="tiny inverted">
<span class="text">
<img class="ui tiny avatar image" src="{{.SignedUser.RelAvatarLink}}">
<span class="sr-only">{{.i18n.Tr "user_profile_and_more"}}</span>
<span class="mobile-only">{{.SignedUser.Name}}</span>
<i class="fitted octicon octicon-triangle-down not-mobile" tabindex="-1"></i>
</span>
<div class="menu user-menu" tabindex="-1">
<div class="ui header">
{{.i18n.Tr "signed_in_as"}} <strong>{{.SignedUser.Name}}</strong>
</div>
<div class="divider"></div>
<a class="item" href="{{AppSubUrl}}/{{.SignedUser.Name}}">
<i class="octicon octicon-person"></i>
{{.i18n.Tr "your_profile"}}<!-- Your profile -->
</a>
<a class="item" href="{{AppSubUrl}}/{{.SignedUser.Name}}?tab=stars">
<i class="octicon octicon-star"></i>
{{.i18n.Tr "your_starred"}}
</a>
<a class="{{if .PageIsUserSettings}}active{{end}} item" href="{{AppSubUrl}}/user/settings">
<i class="octicon octicon-settings"></i>
{{.i18n.Tr "your_settings"}}<!-- Your settings -->
</a>
<a class="item" target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io">
<i class="octicon octicon-question"></i>
{{.i18n.Tr "help"}}<!-- Help -->
</a>
{{if .IsAdmin}}
<div class="divider"></div>
<a class="{{if .PageIsAdmin}}active{{end}} item" href="{{AppSubUrl}}/admin">
<i class="icon settings"></i>
{{.i18n.Tr "admin_panel"}}<!-- Admin Panel -->
</a>
{{end}}
<div class="divider"></div>
<a class="item" href="{{AppSubUrl}}/user/logout">
<i class="octicon octicon-sign-out"></i>
{{.i18n.Tr "sign_out"}}<!-- Sign Out -->
</a>
</div><!-- end content avatar menu -->
</div><!-- end dropdown avatar menu -->
</div><!-- end signed user right menu -->
{{else}}
<div class="right stackable menu">
{{if .ShowRegistrationButton}}
<a class="item{{if .PageIsSignUp}} active{{end}}" href="{{AppSubUrl}}/user/sign_up">
<i class="octicon octicon-person"></i> {{.i18n.Tr "register"}}
</a>
{{end}}
<a class="item{{if .PageIsSignIn}} active{{end}}" href="{{AppSubUrl}}/user/login?redirect_to={{.Link}}">
<i class="octicon octicon-sign-in"></i> {{.i18n.Tr "sign_in"}}
</a>
</div><!-- end anonymous right menu -->
{{end}}
</div>

0
roles/git/files/home/gitea/data/.gitkeep

0
roles/git/files/home/gitea/log/.gitkeep

0
roles/git/files/home/gitea/repos/.gitkeep

8
roles/git/handlers/main.yml

@ -0,0 +1,8 @@
---
- name: Reread service config
command: supervisorctl reread
- name: Restart Gitea
supervisorctl:
name: gitea
state: restarted

47
roles/git/tasks/main.yml

@ -0,0 +1,47 @@
---
- name: Add Git domain to Uberspace config
import_role:
name: snapstromegon.uberspace_web_domain
vars:
domain: git.codesignd.com
- name: Copy config to home directory
copy:
src: home/
dest: "{{ ansible_env.HOME }}/"
mode: preserve
tags: gitea-update
notify: Restart Gitea
- name: Copy service config
template:
src: service.ini.j2
dest: "{{ ansible_env.HOME }}/etc/services.d/gitea.ini"
notify: Reread service config
- name: Copy app config
template:
src: app.ini.j2
dest: "{{ ansible_env.HOME }}/gitea/custom/conf/app.ini"
tags: gitea-update
notify: Restart Gitea
- name: Configure web backend
import_role:
name: snapstromegon.uberspace_web_backend
vars:
route: git.codesignd.com
http:
port: 8080
- name: Set up Gitea Homebrew tap
homebrew_tap:
name: gitea/tap
url: https://gitea.com/gitea/homebrew-gitea.git
- name: Install and update Gitea
homebrew:
name: gitea
state: latest
notify: Restart Gitea
tags: gitea-update

73
roles/git/templates/app.ini.j2

@ -0,0 +1,73 @@
; Config last updated based on:
; commit: bcb722daec4bdd45303da97173a2b8a3c4c95dcd
; date: 2020-01-19
APP_NAME = codesignd Git
RUN_USER = {{ ansible_user_id }}
RUN_MODE = prod
; Server settings
; ===============
[server]
PROTOCOL = http
DOMAIN = git.codesignd.com
HTTP_PORT = 8080
ROOT_URL = https://%(DOMAIN)s/
SSH_PORT = 22
OFFLINE_MODE = true
ENABLE_GZIP = true
LANDING_PAGE = explore
[security]
INSTALL_LOCK = true
SECRET_KEY = {{ git_secret_key }}
INTERNAL_TOKEN = {{ git_internal_token }}
PASSWORD_COMPLEXITY = off
[oauth2]
JWT_SECRET = {{ git_jwt_secret }}
; Paths
; =====
[database]
DB_TYPE = sqlite3
PATH = /home/%(RUN_USER)s/gitea/data/gitea.db
[repository]
ROOT = /home/%(RUN_USER)s/gitea/repos
; Functionality settings
; ======================
[admin]
DISABLE_REGULAR_ORG_CREATION = true
[log]
MODE = file
LEVEL = Info
[mailer]
ENABLED = true
FROM = %(APP_NAME)s <git@codesignd.com>
MAILER_TYPE = sendmail
[service]
DISABLE_REGISTRATION = true
ENABLE_NOTIFY_MAIL = true
NO_REPLY_ADDRESS = noreply.codesignd.com
[session]
COOKIE_SECURE = true
; Branding
; ========
[ui]
THEME_COLOR_META_TAG = `#eb8e2e`
[other]
SHOW_FOOTER_BRANDING = false
SHOW_FOOTER_VERSION = false
SHOW_FOOTER_TEMPLATE_LOAD_TIME = false

5
roles/git/templates/service.ini.j2

@ -0,0 +1,5 @@
[program:gitea]
command={{ ansible_env.HOME }}/.linuxbrew/bin/gitea web
environment=GITEA_WORK_DIR={{ ansible_env.HOME }}/gitea
autostart=yes
autorestart=yes

3
roles/homebrew/handlers/main.yml

@ -0,0 +1,3 @@
---
- name: Opt out from Homebrew analytics
command: ~/.linuxbrew/bin/brew analytics off

13
roles/homebrew/tasks/main.yml

@ -0,0 +1,13 @@
---
- name: Install Homebrew
git:
repo: "https://github.com/Homebrew/brew.git"
version: master
dest: "{{ ansible_env.HOME }}/.linuxbrew"
notify: Opt out from Homebrew analytics
- name: Install Homebrew formulae
homebrew:
name: "{{ homebrew_formulae }}"
state: latest
path: "{{ ansible_env.HOME}}/.linuxbrew/bin"

11
roles/host-site/tasks/main.yml

@ -0,0 +1,11 @@
---
- name: Add host domain to Uberspace config
import_role:
name: snapstromegon.uberspace_web_domain
vars:
domain: kodos-{{ ansible_user_id }}.codesignd.net
- name: Set up host site
template:
src: index.html.j2
dest: /var/www/virtual/{{ ansible_user_id }}/html/index.html

127
roles/host-site/templates/index.html.j2

@ -0,0 +1,127 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>kodos-{{ ansible_user_id }}.codesignd.net</title>
<meta name="robots" content="noindex, nofollow">
<style>
/* Reset */
html {
box-sizing: border-box;
}
*, *::before, *::after {
margin: 0;
padding: 0;
box-sizing: inherit;
text-decoration: none;
list-style: none;
border: none;
}
/* Container styling */
html {
display: flex;
align-items: center;
justify-content: center;
min-height: 100vh;
background: #efefef;
}
body {
min-width: 20rem;
background: #fff;
box-shadow: 0 0 10px #ddd;
}
@media (max-width: 25rem) {
html {
align-items: start;
background: initial;
}
body {
min-width: initial;
width: 100%;
padding-top: 1rem;
box-shadow: initial;
}
}
main {
padding: 1.5rem;
}
footer {
border-top: 2px dotted #efefef;
padding: 1rem;
text-align: center;
}
/* General Typography */
:root {
font-family: system-ui, sans-serif;
line-height: 1.4;
}
h1 {
font-size: 1.5rem;
font-weight: 600;
line-height: 1.2;
text-align: center;
}
h1 span {
font-size: 1.8rem;
display: block;
}
svg {
width: 8rem;
height: 8rem;
display: block;
margin: 0 auto 1rem;
}
.cog {
animation: rotate 30s linear infinite;
transform-origin: 50% 50%;
}
@keyframes rotate {
100% {
transform: rotate(360deg);
}
}
a {
color: #ed8f1b;
}
footer a:not(:last-child) {
margin-right: 0.5em;
}
</style>
</head>
<body>
<main>
<svg viewBox="0 0 1000 1000" xmlns="http://www.w3.org/2000/svg"><g fill="none"><path class="cog" d="M475.28 853.557L408.45 999.43a503.966 503.966 0 0 1-78.883-21.084l15.057-159.725a354.034 354.034 0 0 1-42.828-24.777l-130.73 92.853a510.782 510.782 0 0 1-57.761-57.762l92.853-130.729a354.034 354.034 0 0 1-24.777-42.828L21.655 670.434A503.967 503.967 0 0 1 .57 591.55l145.872-66.831a359.603 359.603 0 0 1-.85-24.72c0-8.31.287-16.553.85-24.72L.57 408.45a503.966 503.966 0 0 1 21.084-78.883l159.725 15.057a354.034 354.034 0 0 1 24.777-42.828l-92.853-130.73a510.782 510.782 0 0 1 57.762-57.761l130.729 92.853a354.034 354.034 0 0 1 42.828-24.777L329.566 21.655A503.967 503.967 0 0 1 408.45.57l66.831 145.872a359.404 359.404 0 0 1 49.44 0L591.55.57a503.966 503.966 0 0 1 78.883 21.084L655.377 181.38a354.034 354.034 0 0 1 42.828 24.777l130.73-92.853a510.782 510.782 0 0 1 57.761 57.762l-92.853 130.729a354.034 354.034 0 0 1 24.777 42.828l159.725-15.057a503.967 503.967 0 0 1 21.084 78.883L853.557 475.28a359.404 359.404 0 0 1 0 49.44l145.873 66.83a503.966 503.966 0 0 1-21.084 78.883L818.62 655.377a354.034 354.034 0 0 1-24.777 42.828l92.853 130.73a510.782 510.782 0 0 1-57.762 57.761l-130.729-92.853a354.034 354.034 0 0 1-42.828 24.777l15.057 159.725a503.967 503.967 0 0 1-78.883 21.084L524.72 853.557a359.404 359.404 0 0 1-49.44 0zM787.356 500c0-158.703-128.653-287.356-287.356-287.356S212.644 341.297 212.644 500 341.297 787.356 500 787.356 787.356 658.703 787.356 500z" fill="#F4B971"/><path stroke="#0BB888" stroke-width="57.471" d="M500 346.743l132.724 76.629v153.256L500 653.257l-132.724-76.629V423.372z"/><circle stroke="#ED8F1B" stroke-width="118.774" cx="500.001" cy="500" r="318.008"/></g></svg>
<h1><span>kodos-{{ ansible_user_id }}</span>.codesignd.net</h1>
</main>
<footer>
<a href="https://codesignd.com/contact">Contact</a>
<a href="https://codesignd.com/legal">Imprint</a>
</footer>
</body>
</html>

15
roles/mail-redirect/tasks/main.yml

@ -0,0 +1,15 @@
---
- name: Set up email redirect
copy:
content: "{{ mail_redirect_to }}"
dest: "{{ ansible_env.HOME }}/.qmail"
- name: Delete catch-all address and local Maildirs
file:
path: "{{ item }}"
state: absent
loop:
- "{{ ansible_env.HOME }}/.spamfolder"
- "{{ ansible_env.HOME }}/.qmail-default"
- "{{ ansible_env.HOME }}/Maildir"
- "{{ ansible_env.HOME }}/users"

59
roles/mail/tasks/main.yml

@ -0,0 +1,59 @@
---
- name: Read existing Uberspace domains
command: uberspace mail domain list
register: uberspace_mail_domain_result
changed_when: false
- name: Add configured domains to Uberspace config
shell: "uberspace mail domain add $(idn {{ item }})"
when: item not in uberspace_mail_domain_result.stdout_lines
loop: "{{ mail_domains.get(ansible_user_id, []) }}"
- name: Delete system Maildir
file:
path: "{{ ansible_env.HOME }}/Maildir"
state: absent
- name: Set up local mail user
import_role:
name: snapstromegon.uberspace_mail_user
vars:
user: lukas
password: "{{ mail_password }}"
- name: Enable catchall mode
import_role:
name: snapstromegon.uberspace_mail_catchall
vars:
user: lukas
- name: Create qdated key
command:
cmd: qdated-makekey
creates: "{{ ansible_env.HOME }}/.qdated-key"
- name: Read qdated key
slurp:
src: "{{ ansible_env.HOME }}/.qdated-key"
register: qdated_key
no_log: yes
- name: Register qdated key as fact
set_fact:
qdated_key: "{{ qdated_key.content | b64decode }}"
no_log: yes
- name: Set up .qmail-dated-default file
template:
src: qmail-dated.j2
dest: "{{ ansible_env.HOME }}/.qmail-dated-default"
- name: Create mutt config directory
file:
path: "{{ ansible_env.HOME }}/.mutt"
state: directory
- name: Set up mutt config
template:
src: muttrc.j2
dest: "{{ ansible_env.HOME }}/.mutt/muttrc"

32
roles/mail/templates/muttrc.j2

@ -0,0 +1,32 @@
# We use Maildirs, not Mailboxes
set mbox_type=Maildir
# Cache headers
set header_cache=~/.cache/mutt_header_cache
# Directory setup
set folder="~/users/lukas"
set spoolfile="+/"
set record="+.Sent"
set postponed="+.Drafts"
# Load IMAP folders as mailboxes
mailboxes `echo -n "+ "; find ${maildir:-~/users/lukas} -maxdepth 1 -type d -name ".*" -printf "+'%f' "`
macro index c "<change-folder>?<toggle-mailboxes>" "open a different folder"
macro pager c "<change-folder>?<toggle-mailboxes>" "open a different folder"
# Sender
set from="Lukas Bestle <{{ mail_default_address[ansible_user_id] }}>"
# Sorting
set sort=threads
set sort_aux=date-sent
# Better Umlaut support
unset allow_8bit
# Display formats
set mask="!^\\.[^.]"
set date_format="%a, %d. %b %H:%M"
set index_format="%3C %Z %D %-25.25F (%?l?%4l&%4c?) %s"
set folder_format="%2C %t %d %t %f"

3
roles/mail/templates/qmail-dated.j2

@ -0,0 +1,3 @@
# Email address is valid for 7 days
|~/.linuxbrew/bin/qdated-check 604800
{{ ansible_user_id }}-lukas

4
roles/terminal/files/authorized_keys

@ -0,0 +1,4 @@
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFi5drlM5Uli7GbTZFiKPKnG8RmOyS2eu3c6XkwwJiSk luX@smithers.codesignd.net
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOBEksH8LMO5jf1QsMH3yRdxSmY2XPk8CH9HNsWfsXVt luX@bart.codesignd.net
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIETsPbHqAta4E6j9pm/9E4lLurlPoh+p5zOWiZ9GS5ml luX@maggie.codesignd.net
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIG9J5FMq9ttUcnuqWhYsiKVuols5uD2Ddv7L3Z93nD2W luX@homer.codesignd.net

0
roles/terminal/files/dotfiles/.composer/vendor/bin/.gitkeep

60
roles/terminal/files/dotfiles/.config/fish/conf.d/pkgs.fish

@ -0,0 +1,60 @@
###
# Loader for Oh My Fish! packages
# Loads all packages stored in ~/.config/fish/pkgs/
#
# Inspired by https://github.com/jorgebucaran/fisher
###
set -q pkgs_path; or set -g pkgs_path ~/.config/fish/pkgs
# also search for functions/completions/binaries/manpages within packages
set fish_function_path $fish_function_path[1] $pkgs_path/.functions $pkgs_path/*/functions $fish_function_path[2..-1]
set fish_complete_path $fish_complete_path[1] $pkgs_path/*/completions $fish_complete_path[2..-1]
set -x PATH $pkgs_path/*/bin $PATH
set -x MANPATH $pkgs_path/*/man $MANPATH
# load package key bindings when needed
if functions -q fish_user_key_bindings
functions -c fish_user_key_bindings fish_user_key_bindings_copy
end
function fish_user_key_bindings
for file in $pkgs_path/*/key_bindings.fish
builtin source $file 2>&1 > /dev/null
end
if functions -q fish_user_key_bindings_copy
fish_user_key_bindings_copy
end
end
# load custom config from packages
for file in $pkgs_path/*/conf.d/*.fish $pkgs_path/*/init.fish
builtin source $file 2> /dev/null
end
# ensure that the prompt is loaded (doesn't autoload for some reason)
if test -f $pkgs_path/.functions/fish_prompt.fish
builtin source $pkgs_path/.functions/fish_prompt.fish 2> /dev/null
end
# collect all functions on the top-level of packages into $pkgs_path/.functions
# (run by Ansible after packages have been updated)
function _pkgs_collect_functions
# clear the functions directory as we rebuild it from scratch
if test -d $pkgs_path/.functions
command rm -rf $pkgs_path/.functions
end
command mkdir -p $pkgs_path/.functions
# consider all fish files in the top-level of each package
for src in $pkgs_path/*/*.fish
set -l package (command basename (command dirname $src))
set -l filename (command basename $src)
# we don't care about uninstall.fish;
# init.fish and key_bindings.fish are dynamically handled on runtime above
if not contains $filename {init,key_bindings,uninstall}.fish
# create a relative symlink
command ln -sf ../$package/$filename $pkgs_path/.functions/$filename
end
end
end

46
roles/terminal/files/dotfiles/.config/fish/config.fish

@ -0,0 +1,46 @@
# Environment variables
# ---------------------
set -x PATH $HOME/bin \
$HOME/.composer/vendor/bin \
$HOME/.local/bin \
$HOME/.linuxbrew/bin \
$PATH
set -x MANPATH $HOME/.linuxbrew/share/man \
$MANPATH
# language
set -x LANG de_DE.UTF-8
# editor
set -x VISUAL "$HOME/.config/fish/pkgs/rmate/rmate -p 64364 -w" # Visual Studio Code (wait mode)
set -x EDITOR 'nano -w' # nano as fallback editor
set -x XDG_CONFIG_HOME $HOME/.config
# Aliases
# -------
# Edit file remotely with VS Code
alias code="$HOME/.config/fish/pkgs/rmate/rmate -p 64364"
# Short ls command
alias l='ls -la'
# General Settings
# ----------------
# enforce 24-bit color mode
# (the requried env var is not transmitted via SSH)
set -x COLORTERM truecolor
set -g fish_term24bit 1
# settings for the bobthefish theme
set -g fish_prompt_pwd_dir_length 0
set -g theme_color_scheme solarized-dark
set -g theme_date_format '+%a %d.%m.%y %H:%M:%S'
set -g theme_display_git_master_branch yes
set -g theme_show_exit_status yes
set -g theme_title_use_abbreviated_path no
set -x VIRTUAL_ENV_DISABLE_PROMPT 1

3
roles/terminal/files/dotfiles/.config/fish/functions/fish_greeting.fish

@ -0,0 +1,3 @@
function fish_greeting
welcome
end

21
roles/terminal/files/dotfiles/.config/fish/functions/nsreverse.fish

@ -0,0 +1,21 @@
###
# Looks up the IPv4 address of the given hostname and tries to find the reverse record of that IP
#
# Globals:
# None
# Arguments:
# $domain string Hostname to look up
# Returns:
# None
###
function nsreverse
# lookup IP
set ip (dig "$argv[1]" A +short); or return 1
# lookup reverse record
set reverse (dig -x "$ip" +short); or return 1
# output
echo -e "\033[1mIPv4: \033[0;34m $ip\033[0m"
echo -e "\033[1mReverse lookup:\033[0;34m $reverse\033[0m"
end

21
roles/terminal/files/dotfiles/.config/fish/functions/nsreverse6.fish

@ -0,0 +1,21 @@
###
# Looks up the IPv6 address of the given hostname and tries to find the reverse record of that IP
#
# Globals:
# None
# Arguments:
# $domain string Hostname to look up
# Returns:
# None
###
function nsreverse6
# lookup IP
set ip (dig "$argv[1]" AAAA +short); or return 1
# lookup reverse record
set reverse (dig -x "$ip" +short); or return 1
# output
echo -e "\033[1mIPv6: \033[0;34m $ip\033[0m"
echo -e "\033[1mReverse lookup:\033[0;34m $reverse\033[0m"
end

0
roles/terminal/files/dotfiles/.config/fish/pkgs/.gitkeep

0
roles/terminal/files/dotfiles/.config/git/.gitkeep

1136
roles/terminal/files/dotfiles/.config/welcome/colossal.flf

File diff suppressed because it is too large

6
roles/terminal/files/dotfiles/.config/welcome/summarizers/01-date.sh

@ -0,0 +1,6 @@
function dateSummarizer() {
echo -n "\033[34m"
date
}
registerSummarizer "Date" dateSummarizer

111
roles/terminal/files/dotfiles/.config/welcome/summarizers/02-uptime.sh

@ -0,0 +1,111 @@
function uptimeSummarizer() {
# Get the uptime in seconds depending on the kernel
if [[ "$(uname -s)" == 'Darwin' ]]; then
# OS X
# kern.boottime is an absolute timestamp
bootTime="$(sysctl -n kern.boottime | awk '{print $4}' | sed 's/,//g')"
currentTime="$(date +%s)"
uptime="$(($currentTime - $bootTime))"
elif [[ -f /proc/uptime ]]; then
# Linux
uptime="$(cat /proc/uptime)"
uptime="${uptime%%.*}"
else
# We don't have a handler for that
# If you need one, please create an issue at
# https://github.com/lukasbestle/my-welcome/issues
echo "\033[31mUnsupported environment, can't get uptime. :(\033[0m"
exit 1
fi
# Parse into units
seconds="$((uptime % 60))"
minutes="$((uptime / 60 % 60))"
hours="$(( uptime / 60 / 60 % 24))"
days="$(( uptime / 60 / 60 / 24 % 30))"
months="$(( uptime / 60 / 60 / 24 / 30 % 12))"
years="$(( uptime / 60 / 60 / 24 / 30 / 12))"
# Output depending on the scale of the uptime
if [[ years -ge 1 ]]; then
outputYears=true
outputMonths=true
outputDays=true
elif [[ months -ge 1 ]]; then
outputMonths=true
outputDays=true
elif [[ days -ge 1 ]]; then
outputDays=true
outputHours=true
outputMinutes=true
elif [[ hours -ge 1 ]]; then
outputHours=true
outputMinutes=true
elif [[ minutes -ge 1 ]]; then
outputMinutes=true
outputSeconds=true
else
outputSeconds=true
fi
# Output each element
output=''
if [[ "$outputYears" == true ]]; then
output+="\033[34m$years"
if [[ "$years" == 1 ]]; then
output+=' year\033[0m\n'
else
output+=' years\033[0m\n'
fi
fi
if [[ "$outputMonths" == true ]]; then
output+="\033[34m$months"
if [[ "$months" == 1 ]]; then
output+=' month\033[0m\n'
else
output+=' months\033[0m\n'
fi
fi
if [[ "$outputDays" == true ]]; then
output+="\033[34m$days"
if [[ "$days" == 1 ]]; then
output+=' day\033[0m\n'
else
output+=' days\033[0m\n'
fi
fi
if [[ "$outputHours" == true ]]; then
output+="\033[34m$hours"
if [[ "$hours" == 1 ]]; then
output+=' hour\033[0m\n'
else
output+=' hours\033[0m\n'
fi
fi
if [[ "$outputMinutes" == true ]]; then
output+="\033[34m$minutes"
if [[ "$minutes" == 1 ]]; then
output+=' minute\033[0m\n'
else
output+=' minutes\033[0m\n'
fi
fi
if [[ "$outputSeconds" == true ]]; then
output+="\033[34m$seconds"
if [[ "$seconds" == 1 ]]; then
output+=' second\033[0m\n'
else
output+=' seconds\033[0m\n'
fi
fi
# Replace newlines with a comma
echo "Host up for $(echo -ne "$output" | paste -s -d ';' - | sed "s/\;/\, /g")"
}
registerSummarizer "Uptime" uptimeSummarizer

12
roles/terminal/files/dotfiles/.config/welcome/summarizers/03-quota.sh

@ -0,0 +1,12 @@
function quotaSummarizer() {
# Get current quota information in MB (integers) and % of total
quota_used=$(quota -lgs | grep '/dev/' | sed 's/ \/dev\/[a-z]\+[0-9] \+\([0-9]\+\?\)M.*/\1/')
quota_total=$(quota -lgs | grep '/dev/' | sed 's/ \/dev\/[a-z]\+[0-9] \+[0-9]\+\?M \+\([0-9]\+\?\)M.*/\1/')
quota_free=$(( $quota_total - $quota_used ))
quota_percentage=$(( $quota_used * 100 / $quota_total ))
echo "Used \033[34m$quota_used MB\033[0m of \033[34m$quota_total MB\033[0m (\033[34m$quota_percentage %\033[0m)"
echo -n "\033[34m$quota_free MB\033[0m available"
}
registerSummarizer "Quota" quotaSummarizer

8
roles/terminal/files/dotfiles/.config/welcome/summarizers/04-ram.sh

@ -0,0 +1,8 @@
function ramSummarizer() {
# Get current RAM usage in MB (integer)
ram_used=$(ps -u $USER -o rss= | awk '{rss += $1} END {printf("%0.f", rss / 1000)}')
echo -n "Used \033[34m$ram_used MB\033[0m"
}
registerSummarizer "RAM" ramSummarizer

0
roles/terminal/files/dotfiles/.config/welcome/warners/.gitkeep

0
roles/terminal/files/dotfiles/.local/bin/.gitkeep

3
roles/terminal/handlers/main.yml

@ -0,0 +1,3 @@
---
- name: Collect fish functions from packages
command: fish -c _pkgs_collect_functions

58
roles/terminal/tasks/main.yml

@ -0,0 +1,58 @@
---
- name: Set authorized SSH keys
authorized_key:
user: "{{ ansible_user_id }}"
state: present
key: "{{ item }}"
with_file: authorized_keys
- name: Get current shell
shell: "getent passwd {{ ansible_user_id }} | cut -d: -f7"
register: current_shell
changed_when: no
- name: Change shell to fish
command: chsh --shell /usr/bin/fish
when: "current_shell.stdout != '/usr/bin/fish'"
- name: Copy dotfiles to home directory
copy:
src: dotfiles/
dest: "{{ ansible_env.HOME }}/"
mode: preserve
- name: Copy Git config to home directory
template:
src: gitconfig.j2
dest: "{{ ansible_env.HOME }}/.config/git/config"
- name: Remove unneeded files
file:
path: "{{ item }}"
state: absent
loop:
- "{{ ansible_env.HOME }}/.bash_history"
- "{{ ansible_env.HOME }}/.bash_logout"
- "{{ ansible_env.HOME }}/.bash_profile"
- "{{ ansible_env.HOME }}/.bashrc"
- "{{ ansible_env.HOME }}/.emacs"
- "{{ ansible_env.HOME }}/.zcompdump"
- "{{ ansible_env.HOME }}/.zshrc"
- name: Download and update iTerm2 shell integration
get_url:
url: https://iterm2.com/shell_integration/fish
dest: "{{ ansible_env.HOME }}/.config/fish/conf.d/iterm2_integration.fish"
force: yes
- name: Download and update Oh My Fish! packages
git:
repo: "{{ item.value }}"
dest: "{{ ansible_env.HOME }}/.config/fish/pkgs/{{ item.key }}"
loop: "{{ omf_pkgs | dict2items }}"
notify: Collect fish functions from packages
- name: Disable Cron MAILTO
cronvar:
name: MAILTO
value: ""

17
roles/terminal/templates/gitconfig.j2

@ -0,0 +1,17 @@
; user settings
[user]
name = kodos-{{ ansible_user_id }}.codesignd.net
email = hostmaster@codesignd.net
; core settings
[advice]
detachedHead = false
[branch]
autoSetupRebase = always
[core]
editor = code -w
quotepath = false
[help]
autocorrect = 10
[push]
default = upstream

0
roles/web/files/home/.config/projectr/.gitkeep

5
roles/web/files/home/bin/qdated-generate

@ -0,0 +1,5 @@
#!/bin/bash
for host in ~/.config/qdated/*; do
HOME="$host" qdated-now > "$host/current"
done

102
roles/web/tasks/main.yml

@ -0,0 +1,102 @@
---
- name: Copy config to home directory
copy:
src: home/
dest: "{{ ansible_env.HOME }}/"
mode: preserve
- name: Create link at ~/web
file:
src: /var/www/virtual/{{ ansible_user_id }}
dest: "{{ ansible_env.HOME }}/web"
state: link
- name: Initialize ~/web/sites
file:
path: /var/www/virtual/{{ ansible_user_id }}/sites
state: directory
- name: Download and update projectr
git:
repo: https://github.com/lukasbestle/projectr.git
dest: "{{ ansible_env.HOME }}/.config/fish/pkgs/projectr"
- name: Set up deploy script
template:
src: deploy.php.j2
dest: /var/www/virtual/{{ ansible_user_id }}/html/deploy.php
- name: Read existing Uberspace domains
command: uberspace web domain list
register: uberspace_web_domain_result
changed_when: no
- name: Add configured domains to Uberspace config
shell: "uberspace web domain add $(idn {{ item.key }})"
when: item.key not in uberspace_web_domain_result.stdout_lines
loop: "{{ web_links.get(ansible_user_id, {}) | dict2items }}"
- name: Set up sites using projectr
command:
argv:
- site_add
- "{{ item.key }}"
creates: /var/www/virtual/{{ ansible_user_id }}/sites/{{ item.key }}
loop: "{{ web_sites.get(ansible_user_id, {}) | dict2items }}"
- name: Set up site origins using projectr (sites with origin)
command:
argv:
- site_origin
- "{{ item.key }}"
- "{{ item.value }}"
creates: /var/www/virtual/{{ ansible_user_id }}/sites/{{ item.key }}/.origin
loop: "{{ web_sites.get(ansible_user_id, {}) | dict2items }}"
when: item.value != None
- name: Set up site origins using projectr (sites without origin)
command:
argv:
- site_origin
- "{{ item.key }}"
removes: /var/www/virtual/{{ ansible_user_id }}/sites/{{ item.key }}/.origin
loop: "{{ web_sites.get(ansible_user_id, {}) | dict2items }}"
when: item.value == None
- name: Deploy sites using projectr (sites with origin)
command:
argv:
- site_deploy
- "{{ item.key }}"
creates: /var/www/virtual/{{ ansible_user_id }}/sites/{{ item.key }}/current
loop: "{{ web_sites.get(ansible_user_id, {}) | dict2items }}"
when: item.value != None
- name: Link sites to domains
command:
argv:
- site_link
- "{{ item.value.site }}"
- "{{ item.key }}"
- "{{ item.value.get('path', '') }}"
creates: /var/www/virtual/{{ ansible_user_id }}/{{ item.key }}
loop: "{{ web_links.get(ansible_user_id, {}) | dict2items }}"
- name: Ensure that qdated directories for all Mail Uberspaces exist
file:
path: "{{ ansible_env.HOME }}/.config/qdated/{{ hostvars[item].ansible_user_id }}"
state: directory
loop: "{{ groups.mail }}"
- name: Copy qdated keys from all Mail Uberspaces
copy:
content: "{{ hostvars[item].qdated_key }}"
dest: "{{ ansible_env.HOME }}/.config/qdated/{{ hostvars[item].ansible_user_id }}/.qdated-key"
loop: "{{ groups.mail }}"
- name: Set up qdated address generation cronjob
cron:
name: "qdated generation"
hour: "2"
minute: "3"
job: "{{ ansible_env.HOME }}/bin/qdated-generate"

172
roles/web/templates/deploy.php.j2

@ -0,0 +1,172 @@
<?php
/**
* projectr
* Tools for deployments and project management
*
* Gitea frontend for deployments
*
* @author Lukas Bestle <project-projectr@lukasbestle.com>
* @copyright Copyright 2014 Lukas Bestle
* @license MIT
* @file webhook.gitea.php
*/
// 1. Configuration
// ====================
// Secret used as authentication (you have to set this in the Gitea project settings as well)
// ATTENTION: Make sure this is long and secure, as anyone could checkout any commit of your projects otherwise
define('SECRET', '{{ projectr_deploy_secret }}');
// Path where you installed the scripts from the "bin" directory of this repository
// This is required, as PHP doesn't automatically use the environment and therefore your $PATH from your shell
define('TOOLKIT_PATH', '/home/{{ ansible_user_id }}/.config/fish/pkgs/projectr/bin');
// 2. Setup
// ====================
/**
* This is an example webhook for Gitea projects.
* It's fairly simple to set this script up on your server/Uberspace, here's how:
*
* 1. Setup the toolkit, so you can access the tools from your SSH shell (more about that in README.md)
* 2. Take a look at the settings above.
* 2.1. Seriously, have you set a secret? Yes? Alright.
* 3. Place this script in a directory in ~/web/ (something like "~/web/hooks.example.com")
* 4. You should now be able to access https://hooks.example.com/webhook.gitea.php
* This should output "Web hook event (X-Gitea-Event header) is missing from request.".
*
* Now you can create projects and let this script deploy them for you:
*
* 5. If your project is a private repository:
* 5.1. Generate an SSH key by using `ssh-keygen`. The default values are alright for this purpose.
* 5.2. Copy the contents of `~/.ssh/id_rsa.pub` to your clipboard and add it as "Deploy Key" in your project's Gitea settings interface.
* 6. Create the project on the server by running either `project_add <path> <exact HTTPS clone url from Gitea>#<branch to deploy>` or
* `site_add <name> <exact HTTPS clone url from Gitea>#<branch to deploy>`.
* It's important to use the exact HTTPS url (not the SSH one) so this script can find the project!
* 7. Add the URL (the one you tested in 4.) of the script to your project's web hooks (choose the type "Gitea").
* Make sure to add the secret configured above and select the "POST" method, the "application/json" type and the "Push Events" trigger.
* 8. To test if it everything works, push some code to your Gitea repository.
*
* Troubleshooting:
* - Use `tail -f <path to project>/logs/*.log`. This should tell you what went wrong.
* - Check if the TOOLKIT_PATH you set above is correct.
* - Check if you set the origin of your project to the correct URL.
* - Open the web hook URL you entered in Gitea in your browser and check if the output is "Web hook event (X-Gitea-Event header) is missing from request.".
* - Click on the pen icon next to your web hook in Gitea's settings interface. You should see a list of "recent deliveries".
* You can find the exact error message from this script in the "Response" tab of the delivery.
*/
// 3. Done
// The code starts here
// ====================
// We are always returning plain text
header('Content-Type: text/plain');
// Check if a secret has been set
if (SECRET === '{{ '{{' }} projectr_deploy_secret }}') {
http_response_code(500);
die('No secret has been set in ' . basename(__FILE__) . ". This script won't work without one.");
}
// Check which event this is
if (isset($_SERVER['HTTP_X_GITEA_EVENT']) !== true) {
http_response_code(400);
die('Web hook event (X-Gitea-Event header) is missing from request.');
}
$event = $_SERVER['HTTP_X_GITEA_EVENT'];
switch ($event) {
case 'push':
echo "Received push event.\n";
break;
default:
http_response_code(400);
die("Received $event event, ignoring.");
}
// Get the request body
$input = false;
switch ($_SERVER['CONTENT_TYPE']) {
case 'application/json':
echo "Received JSON data in body.\n";
$input = file_get_contents('php://input');
break;
case 'application/x-www-form-urlencoded':
echo "Received URL-encoded form data in body.\n";
$input = (isset($_POST['payload']) === true)? $_POST['payload'] : '';
break;
default:
http_response_code(400);
die("Don't know what to do with {$_SERVER['CONTENT_TYPE']} content type.");
}
if (!$input) {
http_response_code(400);
die('No POST body sent.');
}
// Check if the authentication is valid
if (isset($_SERVER['HTTP_X_GITEA_SIGNATURE']) !== true) {
http_response_code(401);
die("Secret (X-Gitea-Signature header) is missing from request. Have you set a secret in Gitea's project settings?");
}
if (hash_equals(hash_hmac('sha256', $input, SECRET, false), $_SERVER['HTTP_X_GITEA_SIGNATURE']) !== true) {
http_response_code(403);
die('Secret (X-Gitea-Signature header) is wrong or does not match request body.');
}
// Parse payload
$payload = json_decode($input, true);
if (is_array($payload) !== true) {
http_response_code(400);
die('Invalid payload (no JSON?).');
}
// Get some interesting information from the payload
$url = $payload['repository']['clone_url'];
$commit = $payload['after'];
$ref = $payload['ref'];
if (preg_match('{(?:.*/){2}(.*)}', $ref, $matches) !== 1) {
http_response_code(400);
die('Invalid ref field (does not match regular expression "(?:.*/){2}(.*)").');
}
$branch = $matches[1];
// Debug
echo "Received commit hash \"$commit\" for repository URL \"$url\" (branch \"$branch\").\n";
// Determine the path to the projects file
$xdgProjectsFile = ($_ENV['XDG_CONFIG_HOME'] ?? ($_SERVER['HOME'] . '/.config')) . '/projectr/projects';
$projectsFile = is_file($xdgProjectsFile) === true ? $xdgProjectsFile : $_SERVER['HOME'] . '/.projects';
// Open the projects file and iterate through every project
$listPointer = fopen($projectsFile, 'r');
$exitCode = 0;
while (($project = fgets($listPointer)) !== false) {
// Trim whitespace
$project = trim($project);
// Only deployable projects are interesting for us
if (is_file($project . '/.origin') !== true || is_file($project . '/.branch') !== true) {
continue;
}
// If there is a .origin and .branch file, check if they match
if (trim(file_get_contents($project . '/.origin')) === $url && trim(file_get_contents($project . '/.branch')) === $branch) {
// Found the right project
echo "Found project at $project, running deploy script.\n";
// Run deploy script (in the background, because Gitea doesn't like responses > 5sec by default)
passthru('export PATH=' . escapeshellarg(TOOLKIT_PATH) . ':$PATH; project_deploy ' . escapeshellarg($project) . ' ' . escapeshellarg($commit) . ' &> /dev/null &', $exitCode);
// If it didn't work, add debug statement
if ($exitCode !== 0) {
echo "Something didn't work.\n";
}
}
}
// Iterated through every project, exit
http_response_code(($exitCode === 0)? 200 : 500);
die('All done.');

9
run.sh

@ -0,0 +1,9 @@
#!/bin/bash
cd "$(dirname "$0")"
if [[ -n "$1" ]]; then
ansible-playbook -v --ask-vault-pass site.yml --tags "$1"
else
ansible-playbook -v --ask-vault-pass site.yml
fi

46
site.yml

@ -0,0 +1,46 @@
---
- name: General tasks for all Uberspaces
hosts: all
vars_files:
- vars/installs.yml
roles:
- homebrew
- host-site
- terminal
- name: Redirect mail from non-mail Uberspaces
hosts: "!mail"
vars_files:
- vars/config.yml
roles:
- mail-redirect
- name: DAV Uberspaces
hosts: dav
vars_files:
- vars/vault.yml
roles:
- dav
- name: Git Uberspaces
hosts: git
vars_files:
- vars/vault.yml
roles:
- git
- name: Mail Uberspaces
hosts: mail
vars_files:
- vars/config.yml
- vars/vault.yml
roles:
- mail
- name: Web Uberspaces
hosts: web
vars_files:
- vars/config.yml
- vars/vault.yml
roles:
- web

51
vars/config.yml

@ -0,0 +1,51 @@
---
# Email domains to set up per host
mail_domains:
cddmail1:
- lukasbestle.com
- lukasbestle.de
- bstl.xyz
cddmail2:
- codesignd.com
- codesignd.de
- codesignd.net
- codesigned.de
# Default sender address per host
mail_default_address:
cddmail1: mail@lukasbestle.com
cddmail2: lukas@codesignd.de
# Destination for email redirects from non-mail Uberspaces
mail_redirect_to: hostmaster@codesignd.net
# Web sites to set up per host (name: origin)
web_sites:
cddweb1:
bachelor: null
lukasbestle: https://git.codesignd.com/sites/lukasbestle.com.git
codesignd: null
cddweb2:
shareable: null
cdn: null
# Domain links to set up per host
web_links:
cddweb1:
bachelor.lukasbestle.com:
site: bachelor
lukasbestle.com:
site: lukasbestle
path: current/public
codesignd.com:
site: codesignd
codesignd.de:
site: codesignd
cddweb2:
bstl.xyz:
site: shareable
f.bstl.xyz:
site: shareable
path: data/files
cdn.codesignd.net:
site: cdn

20
vars/installs.yml

@ -0,0 +1,20 @@
---
# Homebrew formulae to install on all Uberspaces
homebrew_formulae:
# Helpers
- lukasbestle/tap/qdated # Timestamped email addresses for qmail
# Shell tools
- fasd # Directory jump tool
- figlet # ASCII fonts
# Package managers
- composer
# Oh My Fish! packages to install and keep updated
# Key: Short package name; Value: URL to a Git repo
omf_pkgs:
theme-bobthefish: https://github.com/oh-my-fish/theme-bobthefish.git
rmate: https://github.com/aurora/rmate.git
welcome: https://github.com/lukasbestle/welcome.git
z: https://github.com/jethrokuan/z.git

30
vars/vault.yml

@ -0,0 +1,30 @@
$ANSIBLE_VAULT;1.1;AES256
66326137613864613264363534623131343633613662326230346136623738633030336166343231
6363393961646635336163386530393630333337383432330a316634623932626163313866393861
33366438346438366639623666643661373365666430393862333839393133363063653637343863
6464663264393931360a363433333139376264613333333130343136366437643162666132373233
63383136303230643432633165383939653836313338333766626263643232626539383965363163
65356336656264333037626366663864663137336661346230306661633430616166383063356539
35396634326331343935366565386436343866643635356337366631313038306462306137623939
64306266646638313638313766323336353261366166366433336165383230303866633137653264
32346131343062396533343238343366643963323131356130643737626464323863323437393262
36346533623137353234393738643266386165666662346636666436336432656439336430653734
38326162666630633464376464323130633832626665386464306231653161326135323764366435
63613637376331346265653738373539316531316539646538356463383832323034333963666261
37373934333139393662353835316438323839316232666263383661313232613839656236623338
39333261663936626564383665623933353862323932353734393962646636616235626637373062
38656136323739353935613634653331663137613761373266633332383534333039646162623437
36333839373066333263643234633462323635643738643731333139633263643335396636396239
30343964633964353264373437353463613766343033386362663130633030303362646139393563
36363635343861636564646266626631313262623664363063393231343838306538393263326633
66343831346438616665666165353661663131373935353438653061373463623266313832656132
65653561383732653638373638363036626166633838313736313133656438353334396364333964
62346265636266663239316432313463346536633762343135366233396331313662653263643036
30353761656662633862346464353631323465613037623332326664343533656439623236343535
62323831323139653039353237653832646336363832383139393137386531336230383837343038
38643761643833643231643731323239643734653532626366313033633563383966376537313735
62653231343830386430653461366437646261623331313734623036353839306539633364613264
62636637363363393166313035333835353962303263386633316231353362343665303566326332
62386339646532343766373537623435653462323566386262393832616563333638353933376364
31653034373964366134356536393935613931326663323335336437386132613733643964643434
62643265663632663261393836336561623561333333303161646466346332313239
Loading…
Cancel
Save