Initial commit
commit
c3200c91db
@ -0,0 +1,6 @@
|
||||
[defaults]
|
||||
inventory = hosts
|
||||
transport = ssh
|
||||
|
||||
[ssh_connection]
|
||||
pipelining = true
|
@ -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
|
@ -0,0 +1,5 @@
|
||||
---
|
||||
- src: snapstromegon.uberspace_mail_catchall
|
||||
- src: snapstromegon.uberspace_mail_user
|
||||
- src: snapstromegon.uberspace_web_backend
|
||||
- src: snapstromegon.uberspace_web_domain
|
@ -0,0 +1,3 @@
|
||||
function radicale
|
||||
~/.local/bin/radicale --config ~/radicale/config $argv
|
||||
end
|
@ -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,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
|
@ -0,0 +1,3 @@
|
||||
.Radicale.cache
|
||||
.Radicale.lock
|
||||
.Radicale.tmp-*
|
@ -0,0 +1,8 @@
|
||||
---
|
||||
- name: Reread service config
|
||||
command: supervisorctl reread
|
||||
|
||||
- name: Restart Radicale
|
||||
supervisorctl:
|
||||
name: radicale
|
||||
state: restarted
|
@ -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
|
@ -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)
|
@ -0,0 +1,4 @@
|
||||
[program:radicale]
|
||||
command={{ ansible_env.HOME }}/.local/bin/radicale --config {{ ansible_env.HOME }}/radicale/config
|
||||
autostart=yes
|
||||
autorestart=yes
|
@ -0,0 +1,3 @@
|
||||
{% for username, password in dav_users.items() %}
|
||||
{{ username }}:{{ password }}
|
||||
{% endfor %}
|
@ -0,0 +1,3 @@
|
||||
function gitea
|
||||
env GITEA_WORK_DIR=$HOME/gitea ~/.linuxbrew/bin/gitea $argv
|
||||
end
|
Binary file not shown.
After Width: | Height: | Size: 6.2 KiB |
Binary file not shown.
After Width: | Height: | Size: 26 KiB |
@ -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 |
Binary file not shown.
After Width: | Height: | Size: 2.5 KiB |
@ -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>
|
@ -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,0 +1,8 @@
|
||||
---
|
||||
- name: Reread service config
|
||||
command: supervisorctl reread
|
||||
|
||||
- name: Restart Gitea
|
||||
supervisorctl:
|
||||
name: gitea
|
||||
state: restarted
|
@ -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
|
@ -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
|
@ -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
|
@ -0,0 +1,3 @@
|
||||
---
|
||||
- name: Opt out from Homebrew analytics
|
||||
command: ~/.linuxbrew/bin/brew analytics off
|
@ -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"
|
@ -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
|
@ -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>
|
@ -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"
|
@ -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"
|
@ -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"
|
@ -0,0 +1,3 @@
|
||||
# Email address is valid for 7 days
|
||||
|~/.linuxbrew/bin/qdated-check 604800
|
||||
{{ ansible_user_id }}-lukas
|
@ -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,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
|
@ -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
|
@ -0,0 +1,3 @@
|
||||
function fish_greeting
|
||||
welcome
|
||||
end
|
@ -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
|
@ -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
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,6 @@
|
||||
function dateSummarizer() {
|
||||
echo -n "\033[34m"
|
||||
date
|
||||
}
|
||||
|
||||
registerSummarizer "Date" dateSummarizer
|
@ -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
|
@ -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
|
@ -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,0 +1,3 @@
|
||||
---
|
||||
- name: Collect fish functions from packages
|
||||
command: fish -c _pkgs_collect_functions
|
@ -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: ""
|
@ -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,0 +1,5 @@
|
||||
#!/bin/bash
|
||||
|
||||
for host in ~/.config/qdated/*; do
|
||||
HOME="$host" qdated-now > "$host/current"
|
||||
done
|
@ -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"
|
@ -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.');
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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…
Reference in New Issue