Compare commits
2 commits
master
...
conflectte
Author | SHA1 | Date | |
---|---|---|---|
09f085336e | |||
686fa86c3f |
185 changed files with 931 additions and 5357 deletions
|
@ -37,12 +37,3 @@ fi
|
|||
telegraf: execd for daemons
|
||||
|
||||
TEST
|
||||
|
||||
# git signing
|
||||
|
||||
git config --global gpg.format ssh
|
||||
git config --global commit.gpgsign true
|
||||
|
||||
git config user.name CroneKorkN
|
||||
git config user.email i@ckn.li
|
||||
git config user.signingkey "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILMVroYmswD4tLk6iH+2tvQiyaMe42yfONDsPDIdFv6I"
|
||||
|
|
|
@ -23,7 +23,7 @@ for node in nodes:
|
|||
print(node.run('DEBIAN_FRONTEND=noninteractive apt update').stdout.decode())
|
||||
print(node.run('DEBIAN_FRONTEND=noninteractive apt list --upgradable').stdout.decode())
|
||||
if int(node.run('DEBIAN_FRONTEND=noninteractive apt list --upgradable 2> /dev/null | grep upgradable | wc -l').stdout.decode()):
|
||||
print(node.run('DEBIAN_FRONTEND=noninteractive apt -qy full-upgrade').stdout.decode())
|
||||
print(node.run('DEBIAN_FRONTEND=noninteractive apt -y dist-upgrade').stdout.decode())
|
||||
|
||||
# REBOOT IN ORDER
|
||||
|
||||
|
|
|
@ -13,9 +13,6 @@
|
|||
'deb',
|
||||
'deb-src',
|
||||
},
|
||||
'options': { # optional
|
||||
'aarch': 'amd64',
|
||||
},
|
||||
'urls': {
|
||||
'https://deb.debian.org/debian',
|
||||
},
|
||||
|
|
|
@ -62,7 +62,6 @@ files = {
|
|||
'/usr/lib/nagios/plugins/check_apt_upgradable': {
|
||||
'mode': '0755',
|
||||
},
|
||||
# /etc/kernel/postinst.d/apt-auto-removal
|
||||
}
|
||||
|
||||
actions = {
|
||||
|
|
|
@ -1,31 +1,13 @@
|
|||
#!/bin/bash
|
||||
|
||||
set -u
|
||||
set -exu
|
||||
|
||||
# FIXME: inelegant
|
||||
% if wol_command:
|
||||
${wol_command}
|
||||
% endif
|
||||
|
||||
exit=0
|
||||
failed_paths=""
|
||||
|
||||
for path in $(jq -r '.paths | .[]' < /etc/backup/config.json)
|
||||
do
|
||||
echo backing up $path
|
||||
/opt/backup/backup_path "$path"
|
||||
# set exit to 1 if any backup fails
|
||||
if [ $? -ne 0 ]
|
||||
then
|
||||
echo ERROR: backing up $path failed >&2
|
||||
exit=5
|
||||
failed_paths="$failed_paths $path"
|
||||
fi
|
||||
done
|
||||
|
||||
if [ $exit -ne 0 ]
|
||||
then
|
||||
echo "ERROR: failed to backup paths: $failed_paths" >&2
|
||||
fi
|
||||
|
||||
exit $exit
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
#!/bin/bash
|
||||
|
||||
set -eu
|
||||
set -exu
|
||||
|
||||
path=$1
|
||||
uuid=$(jq -r .client_uuid < /etc/backup/config.json)
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
$TTL 86400
|
||||
@ IN SOA localhost. root.localhost. (
|
||||
1 ; Serial
|
||||
604800 ; Refresh
|
||||
86400 ; Retry
|
||||
2419200 ; Expire
|
||||
86400 ) ; Negative Cache TTL
|
||||
IN NS localhost.
|
|
@ -29,7 +29,6 @@ view "${view_name}" {
|
|||
|
||||
% if view_conf['is_internal']:
|
||||
recursion yes;
|
||||
include "/etc/bind/zones.rfc1918";
|
||||
% else:
|
||||
recursion no;
|
||||
rate-limit {
|
||||
|
@ -63,6 +62,9 @@ view "${view_name}" {
|
|||
file "/var/lib/bind/${view_name}/${zone_name}";
|
||||
};
|
||||
% endfor
|
||||
|
||||
include "/etc/bind/named.conf.default-zones";
|
||||
include "/etc/bind/zones.rfc1918";
|
||||
};
|
||||
|
||||
% endfor
|
||||
|
|
|
@ -10,7 +10,7 @@ options {
|
|||
|
||||
% if type == 'master':
|
||||
notify yes;
|
||||
also-notify { ${' '.join(sorted(f'{ip};' for ip in slave_ips))} };
|
||||
allow-transfer { ${' '.join(sorted(f'{ip};' for ip in slave_ips))} };
|
||||
also-notify { ${' '.join([f'{ip};' for ip in slave_ips])} };
|
||||
allow-transfer { ${' '.join([f'{ip};' for ip in slave_ips])} };
|
||||
% endif
|
||||
};
|
||||
|
|
|
@ -1,19 +0,0 @@
|
|||
zone "10.in-addr.arpa" { type master; file "/etc/bind/db.empty"; };
|
||||
zone "16.172.in-addr.arpa" { type master; file "/etc/bind/db.empty"; };
|
||||
zone "17.172.in-addr.arpa" { type master; file "/etc/bind/db.empty"; };
|
||||
zone "18.172.in-addr.arpa" { type master; file "/etc/bind/db.empty"; };
|
||||
zone "19.172.in-addr.arpa" { type master; file "/etc/bind/db.empty"; };
|
||||
zone "20.172.in-addr.arpa" { type master; file "/etc/bind/db.empty"; };
|
||||
zone "21.172.in-addr.arpa" { type master; file "/etc/bind/db.empty"; };
|
||||
zone "22.172.in-addr.arpa" { type master; file "/etc/bind/db.empty"; };
|
||||
zone "23.172.in-addr.arpa" { type master; file "/etc/bind/db.empty"; };
|
||||
zone "24.172.in-addr.arpa" { type master; file "/etc/bind/db.empty"; };
|
||||
zone "25.172.in-addr.arpa" { type master; file "/etc/bind/db.empty"; };
|
||||
zone "26.172.in-addr.arpa" { type master; file "/etc/bind/db.empty"; };
|
||||
zone "27.172.in-addr.arpa" { type master; file "/etc/bind/db.empty"; };
|
||||
zone "28.172.in-addr.arpa" { type master; file "/etc/bind/db.empty"; };
|
||||
zone "29.172.in-addr.arpa" { type master; file "/etc/bind/db.empty"; };
|
||||
zone "30.172.in-addr.arpa" { type master; file "/etc/bind/db.empty"; };
|
||||
zone "31.172.in-addr.arpa" { type master; file "/etc/bind/db.empty"; };
|
||||
zone "168.192.in-addr.arpa" { type master; file "/etc/bind/db.empty"; };
|
||||
zone "254.169.in-addr.arpa" { type master; file "/etc/bind/db.empty"; };
|
|
@ -19,7 +19,7 @@ directories[f'/var/lib/bind'] = {
|
|||
'svc_systemd:bind9',
|
||||
],
|
||||
'triggers': [
|
||||
'svc_systemd:bind9:reload',
|
||||
'svc_systemd:bind9:restart',
|
||||
],
|
||||
}
|
||||
|
||||
|
@ -29,7 +29,7 @@ files['/etc/default/bind9'] = {
|
|||
'svc_systemd:bind9',
|
||||
],
|
||||
'triggers': [
|
||||
'svc_systemd:bind9:reload',
|
||||
'svc_systemd:bind9:restart',
|
||||
],
|
||||
}
|
||||
|
||||
|
@ -43,7 +43,7 @@ files['/etc/bind/named.conf'] = {
|
|||
'svc_systemd:bind9',
|
||||
],
|
||||
'triggers': [
|
||||
'svc_systemd:bind9:reload',
|
||||
'svc_systemd:bind9:restart',
|
||||
],
|
||||
}
|
||||
|
||||
|
@ -63,7 +63,7 @@ files['/etc/bind/named.conf.options'] = {
|
|||
'svc_systemd:bind9',
|
||||
],
|
||||
'triggers': [
|
||||
'svc_systemd:bind9:reload',
|
||||
'svc_systemd:bind9:restart',
|
||||
],
|
||||
}
|
||||
|
||||
|
@ -93,7 +93,7 @@ files['/etc/bind/named.conf.local'] = {
|
|||
'svc_systemd:bind9',
|
||||
],
|
||||
'triggers': [
|
||||
'svc_systemd:bind9:reload',
|
||||
'svc_systemd:bind9:restart',
|
||||
],
|
||||
}
|
||||
|
||||
|
@ -106,7 +106,7 @@ for view_name, view_conf in master_node.metadata.get('bind/views').items():
|
|||
'svc_systemd:bind9',
|
||||
],
|
||||
'triggers': [
|
||||
'svc_systemd:bind9:reload',
|
||||
'svc_systemd:bind9:restart',
|
||||
],
|
||||
}
|
||||
|
||||
|
@ -127,7 +127,7 @@ for view_name, view_conf in master_node.metadata.get('bind/views').items():
|
|||
'svc_systemd:bind9',
|
||||
],
|
||||
'triggers': [
|
||||
'svc_systemd:bind9:reload',
|
||||
'svc_systemd:bind9:restart',
|
||||
],
|
||||
}
|
||||
|
||||
|
@ -139,24 +139,6 @@ actions['named-checkconf'] = {
|
|||
'unless': 'named-checkconf -z',
|
||||
'needs': [
|
||||
'svc_systemd:bind9',
|
||||
'svc_systemd:bind9:reload',
|
||||
'svc_systemd:bind9:restart',
|
||||
]
|
||||
}
|
||||
|
||||
# beantwortet Anfragen nach privaten IP-Adressen mit NXDOMAIN, statt sie ins Internet weiterzuleiten
|
||||
files['/etc/bind/zones.rfc1918'] = {
|
||||
'needed_by': [
|
||||
'svc_systemd:bind9',
|
||||
],
|
||||
'triggers': [
|
||||
'svc_systemd:bind9:reload',
|
||||
],
|
||||
}
|
||||
files['/etc/bind/db.empty'] = {
|
||||
'needed_by': [
|
||||
'svc_systemd:bind9',
|
||||
],
|
||||
'triggers': [
|
||||
'svc_systemd:bind9:reload',
|
||||
],
|
||||
}
|
||||
|
|
|
@ -3,7 +3,6 @@ from json import dumps
|
|||
h = repo.libs.hashable.hashable
|
||||
repo.libs.bind.repo = repo
|
||||
|
||||
|
||||
defaults = {
|
||||
'apt': {
|
||||
'packages': {
|
||||
|
@ -212,7 +211,7 @@ def generate_keys(metadata):
|
|||
'token':repo.libs.hmac.hmac_sha512(
|
||||
key,
|
||||
str(repo.vault.random_bytes_as_base64_for(
|
||||
f"{metadata.get('id')} bind key {key} 20250713",
|
||||
f"{metadata.get('id')} bind key {key}",
|
||||
length=32,
|
||||
)),
|
||||
)
|
||||
|
|
|
@ -1,165 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import os
|
||||
import datetime
|
||||
import numpy as np
|
||||
import matplotlib.pyplot as plt
|
||||
import soundfile as sf
|
||||
from scipy.fft import rfft, rfftfreq
|
||||
import shutil
|
||||
import traceback
|
||||
|
||||
|
||||
RECORDINGS_DIR = "recordings"
|
||||
PROCESSED_RECORDINGS_DIR = "recordings/processed"
|
||||
DETECTIONS_DIR = "events"
|
||||
|
||||
DETECT_FREQUENCY = 211 # Hz
|
||||
DETECT_FREQUENCY_TOLERANCE = 2 # Hz
|
||||
ADJACENCY_FACTOR = 2 # area to look for the frequency (e.g. 2 means 100Hz to 400Hz for 200Hz detection)
|
||||
BLOCK_SECONDS = 3 # seconds (longer means more frequency resolution, but less time resolution)
|
||||
DETECTION_DISTANCE_SECONDS = 30 # seconds (minimum time between detections)
|
||||
BLOCK_OVERLAP_FACTOR = 0.9 # overlap between blocks (0.2 means 20% overlap)
|
||||
MIN_SIGNAL_QUALITY = 1000.0 # maximum noise level (relative DB) to consider a detection valid
|
||||
PLOT_PADDING_START_SECONDS = 2 # seconds (padding before and after the event in the plot)
|
||||
PLOT_PADDING_END_SECONDS = 3 # seconds (padding before and after the event in the plot)
|
||||
|
||||
DETECTION_DISTANCE_BLOCKS = DETECTION_DISTANCE_SECONDS // BLOCK_SECONDS # number of blocks to skip after a detection
|
||||
DETECT_FREQUENCY_FROM = DETECT_FREQUENCY - DETECT_FREQUENCY_TOLERANCE # Hz
|
||||
DETECT_FREQUENCY_TO = DETECT_FREQUENCY + DETECT_FREQUENCY_TOLERANCE # Hz
|
||||
|
||||
|
||||
def process_recording(filename):
|
||||
print('processing', filename)
|
||||
|
||||
# get ISO 8601 nanosecond recording date from filename
|
||||
date_string_from_filename = os.path.splitext(filename)[0]
|
||||
recording_date = datetime.datetime.strptime(date_string_from_filename, "%Y-%m-%d_%H-%M-%S.%f%z")
|
||||
|
||||
# get data and metadata from recording
|
||||
path = os.path.join(RECORDINGS_DIR, filename)
|
||||
soundfile = sf.SoundFile(path)
|
||||
samplerate = soundfile.samplerate
|
||||
samples_per_block = int(BLOCK_SECONDS * samplerate)
|
||||
overlapping_samples = int(samples_per_block * BLOCK_OVERLAP_FACTOR)
|
||||
|
||||
sample_num = 0
|
||||
current_event = None
|
||||
|
||||
while sample_num < len(soundfile):
|
||||
soundfile.seek(sample_num)
|
||||
block = soundfile.read(frames=samples_per_block, dtype='float32', always_2d=False)
|
||||
|
||||
if len(block) == 0:
|
||||
break
|
||||
|
||||
# calculate FFT
|
||||
labels = rfftfreq(len(block), d=1/samplerate)
|
||||
complex_amplitudes = rfft(block)
|
||||
amplitudes = np.abs(complex_amplitudes)
|
||||
|
||||
# get the frequency with the highest amplitude within the search range
|
||||
search_amplitudes = amplitudes[(labels >= DETECT_FREQUENCY_FROM/ADJACENCY_FACTOR) & (labels <= DETECT_FREQUENCY_TO*ADJACENCY_FACTOR)]
|
||||
search_labels = labels[(labels >= DETECT_FREQUENCY_FROM/ADJACENCY_FACTOR) & (labels <= DETECT_FREQUENCY_TO*ADJACENCY_FACTOR)]
|
||||
max_amplitude = max(search_amplitudes)
|
||||
max_amplitude_index = np.argmax(search_amplitudes)
|
||||
max_freq = search_labels[max_amplitude_index]
|
||||
max_freq_detected = DETECT_FREQUENCY_FROM <= max_freq <= DETECT_FREQUENCY_TO
|
||||
|
||||
# calculate signal quality
|
||||
adjacent_amplitudes = amplitudes[(labels < DETECT_FREQUENCY_FROM) | (labels > DETECT_FREQUENCY_TO)]
|
||||
signal_quality = max_amplitude/np.mean(adjacent_amplitudes)
|
||||
good_signal_quality = signal_quality > MIN_SIGNAL_QUALITY
|
||||
|
||||
# conclude detection
|
||||
if (
|
||||
max_freq_detected and
|
||||
good_signal_quality
|
||||
):
|
||||
block_date = recording_date + datetime.timedelta(seconds=sample_num / samplerate)
|
||||
|
||||
# detecting an event
|
||||
if not current_event:
|
||||
current_event = {
|
||||
'start_at': block_date,
|
||||
'end_at': block_date,
|
||||
'start_sample': sample_num,
|
||||
'end_sample': sample_num + samples_per_block,
|
||||
'start_freq': max_freq,
|
||||
'end_freq': max_freq,
|
||||
'max_amplitude': max_amplitude,
|
||||
}
|
||||
else:
|
||||
current_event.update({
|
||||
'end_at': block_date,
|
||||
'end_freq': max_freq,
|
||||
'end_sample': sample_num + samples_per_block,
|
||||
'max_amplitude': max(max_amplitude, current_event['max_amplitude']),
|
||||
})
|
||||
print(f'- {block_date.strftime('%Y-%m-%d %H:%M:%S')}: {max_amplitude:.1f}rDB @ {max_freq:.1f}Hz (signal {signal_quality:.3f}x)')
|
||||
else:
|
||||
# not detecting an event
|
||||
if current_event:
|
||||
duration = (current_event['end_at'] - current_event['start_at']).total_seconds()
|
||||
current_event['duration'] = duration
|
||||
print(f'🔊 {current_event['start_at'].strftime('%Y-%m-%d %H:%M:%S')} ({duration:.1f}s): {current_event['start_freq']:.1f}Hz->{current_event['end_freq']:.1f}Hz @{current_event['max_amplitude']:.0f}rDB')
|
||||
|
||||
# read full audio clip again for writing
|
||||
write_event(current_event=current_event, soundfile=soundfile, samplerate=samplerate)
|
||||
|
||||
current_event = None
|
||||
sample_num += DETECTION_DISTANCE_BLOCKS * samples_per_block
|
||||
|
||||
sample_num += samples_per_block - overlapping_samples
|
||||
|
||||
# move to PROCESSED_RECORDINGS_DIR
|
||||
|
||||
os.makedirs(PROCESSED_RECORDINGS_DIR, exist_ok=True)
|
||||
shutil.move(os.path.join(RECORDINGS_DIR, filename), os.path.join(PROCESSED_RECORDINGS_DIR, filename))
|
||||
|
||||
|
||||
# write a spectrogram using the sound from start to end of the event
|
||||
def write_event(current_event, soundfile, samplerate):
|
||||
# date and filename
|
||||
event_date = current_event['start_at'] - datetime.timedelta(seconds=PLOT_PADDING_START_SECONDS)
|
||||
filename_prefix = event_date.strftime('%Y-%m-%d_%H-%M-%S.%f%z')
|
||||
|
||||
# event clip
|
||||
event_start_sample = current_event['start_sample'] - samplerate * PLOT_PADDING_START_SECONDS
|
||||
event_end_sample = current_event['end_sample'] + samplerate * PLOT_PADDING_END_SECONDS
|
||||
total_samples = event_end_sample - event_start_sample
|
||||
soundfile.seek(event_start_sample)
|
||||
event_clip = soundfile.read(frames=total_samples, dtype='float32', always_2d=False)
|
||||
|
||||
# write flac
|
||||
flac_path = os.path.join(DETECTIONS_DIR, f"{filename_prefix}.flac")
|
||||
sf.write(flac_path, event_clip, samplerate, format='FLAC')
|
||||
|
||||
# write spectrogram
|
||||
plt.figure(figsize=(8, 6))
|
||||
plt.specgram(event_clip, Fs=samplerate, NFFT=samplerate, noverlap=samplerate//2, cmap='inferno', vmin=-100, vmax=-10)
|
||||
plt.title(f"Bootshorn @{event_date.strftime('%Y-%m-%d %H:%M:%S%z')}")
|
||||
plt.xlabel(f"Time {current_event['duration']:.1f}s")
|
||||
plt.ylabel(f"Frequency {current_event['start_freq']:.1f}Hz -> {current_event['end_freq']:.1f}Hz")
|
||||
plt.colorbar(label="Intensity (rDB)")
|
||||
plt.ylim(50, 1000)
|
||||
plt.savefig(os.path.join(DETECTIONS_DIR, f"{filename_prefix}.png"))
|
||||
plt.close()
|
||||
|
||||
|
||||
def main():
|
||||
os.makedirs(RECORDINGS_DIR, exist_ok=True)
|
||||
os.makedirs(PROCESSED_RECORDINGS_DIR, exist_ok=True)
|
||||
|
||||
for filename in sorted(os.listdir(RECORDINGS_DIR)):
|
||||
if filename.endswith(".flac"):
|
||||
try:
|
||||
process_recording(filename)
|
||||
except Exception as e:
|
||||
print(f"Error processing {filename}: {e}")
|
||||
# print stacktrace
|
||||
traceback.print_exc()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
|
@ -1,25 +0,0 @@
|
|||
#!/bin/sh
|
||||
|
||||
mkdir -p recordings
|
||||
|
||||
while true
|
||||
do
|
||||
# get date in ISO 8601 format with nanoseconds
|
||||
PROGRAMM=$(test $(uname) = "Darwin" && echo "gdate" || echo "date")
|
||||
DATE=$($PROGRAMM "+%Y-%m-%d_%H-%M-%S.%6N%z")
|
||||
|
||||
# record audio using ffmpeg
|
||||
ffmpeg \
|
||||
-y \
|
||||
-f pulse \
|
||||
-i "alsa_input.usb-HANMUS_USB_AUDIO_24BIT_2I2O_1612310-00.analog-stereo" \
|
||||
-ac 1 \
|
||||
-ar 96000 \
|
||||
-sample_fmt s32 \
|
||||
-t "3600" \
|
||||
-c:a flac \
|
||||
-compression_level 12 \
|
||||
"recordings/current/$DATE.flac"
|
||||
|
||||
mv "recordings/current/$DATE.flac" "recordings/$DATE.flac"
|
||||
done
|
|
@ -1,43 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import requests
|
||||
import urllib3
|
||||
import datetime
|
||||
import csv
|
||||
urllib3.disable_warnings()
|
||||
import os
|
||||
|
||||
|
||||
HUE_IP = "${hue_ip}" # replace with your bridge IP
|
||||
HUE_APP_KEY = "${hue_app_key}" # local only
|
||||
HUE_DEVICE_ID = "31f58786-3242-4e88-b9ce-23f44ba27bbe"
|
||||
TEMPERATURE_LOG_DIR = "/opt/bootshorn/temperatures"
|
||||
|
||||
response = requests.get(
|
||||
f"https://{HUE_IP}/clip/v2/resource/temperature",
|
||||
headers={"hue-application-key": HUE_APP_KEY},
|
||||
verify=False,
|
||||
)
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
|
||||
for item in data["data"]:
|
||||
if item["id"] == HUE_DEVICE_ID:
|
||||
temperature = item["temperature"]["temperature"]
|
||||
temperature_date_string = item["temperature"]["temperature_report"]["changed"]
|
||||
temperature_date = datetime.datetime.fromisoformat(temperature_date_string).astimezone(datetime.timezone.utc)
|
||||
break
|
||||
|
||||
print(f"@{temperature_date}: {temperature}°C")
|
||||
|
||||
filename = temperature_date.strftime("%Y-%m-%d_00-00-00.000000%z") + ".log"
|
||||
logpath = os.path.join(TEMPERATURE_LOG_DIR, filename)
|
||||
now_utc = datetime.datetime.now(datetime.timezone.utc)
|
||||
|
||||
with open(logpath, "a+", newline="") as logfile:
|
||||
writer = csv.writer(logfile)
|
||||
writer.writerow([
|
||||
now_utc.strftime('%Y-%m-%d_%H-%M-%S.%f%z'), # current UTC time
|
||||
temperature_date.strftime('%Y-%m-%d_%H-%M-%S.%f%z'), # date of temperature reading
|
||||
temperature,
|
||||
])
|
|
@ -1,61 +0,0 @@
|
|||
# nano /etc/selinux/config
|
||||
# SELINUX=disabled
|
||||
# reboot
|
||||
|
||||
directories = {
|
||||
'/opt/bootshorn': {
|
||||
'owner': 'ckn',
|
||||
'group': 'ckn',
|
||||
},
|
||||
'/opt/bootshorn/temperatures': {
|
||||
'owner': 'ckn',
|
||||
'group': 'ckn',
|
||||
},
|
||||
'/opt/bootshorn/recordings': {
|
||||
'owner': 'ckn',
|
||||
'group': 'ckn',
|
||||
},
|
||||
'/opt/bootshorn/recordings/current': {
|
||||
'owner': 'ckn',
|
||||
'group': 'ckn',
|
||||
},
|
||||
'/opt/bootshorn/recordings/processed': {
|
||||
'owner': 'ckn',
|
||||
'group': 'ckn',
|
||||
},
|
||||
'/opt/bootshorn/events': {
|
||||
'owner': 'ckn',
|
||||
'group': 'ckn',
|
||||
},
|
||||
}
|
||||
|
||||
files = {
|
||||
'/opt/bootshorn/record': {
|
||||
'owner': 'ckn',
|
||||
'group': 'ckn',
|
||||
'mode': '755',
|
||||
},
|
||||
'/opt/bootshorn/temperature': {
|
||||
'content_type': 'mako',
|
||||
'context': {
|
||||
'hue_ip': repo.get_node('home.hue').hostname,
|
||||
'hue_app_key': repo.vault.decrypt('encrypt$gAAAAABoc2WxZCLbxl-Z4IrSC97CdOeFgBplr9Fp5ujpd0WCCCPNBUY_WquHN86z8hKLq5Y04dwq8TdJW0PMSOSgTFbGgdp_P1q0jOBLEKaW9IIT1YM88h-JYwLf9QGDV_5oEfvnBCtO'),
|
||||
},
|
||||
'owner': 'ckn',
|
||||
'group': 'ckn',
|
||||
'mode': '755',
|
||||
},
|
||||
'/opt/bootshorn/process': {
|
||||
'owner': 'ckn',
|
||||
'group': 'ckn',
|
||||
'mode': '755',
|
||||
},
|
||||
}
|
||||
|
||||
svc_systemd = {
|
||||
'bootshorn-record.service': {
|
||||
'needs': {
|
||||
'file:/opt/bootshorn/record',
|
||||
},
|
||||
},
|
||||
}
|
|
@ -1,44 +0,0 @@
|
|||
defaults = {
|
||||
'systemd': {
|
||||
'units': {
|
||||
'bootshorn-record.service': {
|
||||
'Unit': {
|
||||
'Description': 'Bootshorn Recorder',
|
||||
'After': 'network.target',
|
||||
},
|
||||
'Service': {
|
||||
'User': 'ckn',
|
||||
'Group': 'ckn',
|
||||
'Type': 'simple',
|
||||
'WorkingDirectory': '/opt/bootshorn',
|
||||
'ExecStart': '/opt/bootshorn/record',
|
||||
'Restart': 'always',
|
||||
'RestartSec': 5,
|
||||
'Environment': {
|
||||
"XDG_RUNTIME_DIR": "/run/user/1000",
|
||||
"PULSE_SERVER": "unix:/run/user/1000/pulse/native",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
'systemd-timers': {
|
||||
'bootshorn-temperature': {
|
||||
'command': '/opt/bootshorn/temperature',
|
||||
'when': '*:0/10',
|
||||
'working_dir': '/opt/bootshorn',
|
||||
'user': 'ckn',
|
||||
'group': 'ckn',
|
||||
},
|
||||
# 'bootshorn-process': {
|
||||
# 'command': '/opt/bootshorn/process',
|
||||
# 'when': 'hourly',
|
||||
# 'working_dir': '/opt/bootshorn',
|
||||
# 'user': 'ckn',
|
||||
# 'group': 'ckn',
|
||||
# 'after': {
|
||||
# 'bootshorn-process.service',
|
||||
# },
|
||||
# },
|
||||
},
|
||||
}
|
17
bundles/dovecot/files/dovecot-sql.conf
Normal file
17
bundles/dovecot/files/dovecot-sql.conf
Normal file
|
@ -0,0 +1,17 @@
|
|||
connect = host=${host} dbname=${name} user=${user} password=${password}
|
||||
driver = pgsql
|
||||
default_pass_scheme = ARGON2ID
|
||||
|
||||
user_query = SELECT '/var/vmail/%u' AS home, 'vmail' AS uid, 'vmail' AS gid
|
||||
|
||||
iterate_query = SELECT CONCAT(users.name, '@', domains.name) AS user \
|
||||
FROM users \
|
||||
LEFT JOIN domains ON users.domain_id = domains.id \
|
||||
WHERE redirect IS NULL
|
||||
|
||||
password_query = SELECT CONCAT(users.name, '@', domains.name) AS user, password \
|
||||
FROM users \
|
||||
LEFT JOIN domains ON users.domain_id = domains.id \
|
||||
WHERE redirect IS NULL \
|
||||
AND users.name = SPLIT_PART('%u', '@', 1) \
|
||||
AND domains.name = SPLIT_PART('%u', '@', 2)
|
|
@ -1,17 +1,13 @@
|
|||
dovecot_config_version = ${config_version}
|
||||
dovecot_storage_version = ${storage_version}
|
||||
|
||||
protocols = imap lmtp sieve
|
||||
auth_mechanisms = plain login
|
||||
mail_privileged_group = mail
|
||||
ssl = required
|
||||
ssl_server_cert_file = /var/lib/dehydrated/certs/${hostname}/fullchain.pem
|
||||
ssl_server_key_file = /var/lib/dehydrated/certs/${hostname}/privkey.pem
|
||||
ssl_server_dh_file = /etc/dovecot/dhparam.pem
|
||||
ssl_cert = </var/lib/dehydrated/certs/${node.metadata.get('mailserver/hostname')}/fullchain.pem
|
||||
ssl_key = </var/lib/dehydrated/certs/${node.metadata.get('mailserver/hostname')}/privkey.pem
|
||||
ssl_dh = </etc/dovecot/dhparam.pem
|
||||
ssl_client_ca_dir = /etc/ssl/certs
|
||||
mail_driver = maildir
|
||||
mail_path = ${maildir}/%{user}
|
||||
mail_index_path = ${maildir}/index/%{user}
|
||||
mail_plugins = fts fts_flatcurve
|
||||
mail_location = maildir:${node.metadata.get('mailserver/maildir')}/%u:INDEX=${node.metadata.get('mailserver/maildir')}/index/%u
|
||||
mail_plugins = fts fts_xapian
|
||||
|
||||
namespace inbox {
|
||||
inbox = yes
|
||||
|
@ -34,46 +30,14 @@ namespace inbox {
|
|||
}
|
||||
}
|
||||
|
||||
# postgres passdb userdb
|
||||
|
||||
sql_driver = pgsql
|
||||
|
||||
pgsql main {
|
||||
parameters {
|
||||
host = ${db_host}
|
||||
dbname = ${db_name}
|
||||
user = ${db_user}
|
||||
password = ${db_password}
|
||||
}
|
||||
passdb {
|
||||
driver = sql
|
||||
args = /etc/dovecot/dovecot-sql.conf
|
||||
}
|
||||
|
||||
passdb sql {
|
||||
passdb_default_password_scheme = ARGON2ID
|
||||
|
||||
query = SELECT \
|
||||
CONCAT(users.name, '@', domains.name) AS "user", \
|
||||
password \
|
||||
FROM users \
|
||||
LEFT JOIN domains ON users.domain_id = domains.id \
|
||||
WHERE redirect IS NULL \
|
||||
AND users.name = SPLIT_PART('%{user}', '@', 1) \
|
||||
AND domains.name = SPLIT_PART('%{user}', '@', 2)
|
||||
}
|
||||
|
||||
mail_uid = vmail
|
||||
mail_gid = vmail
|
||||
|
||||
userdb sql {
|
||||
query = SELECT \
|
||||
'/var/vmail/%{user}' AS home, \
|
||||
'vmail' AS uid, \
|
||||
'vmail' AS gid
|
||||
|
||||
iterate_query = SELECT \
|
||||
CONCAT(users.name, '@', domains.name) AS username \
|
||||
FROM users \
|
||||
LEFT JOIN domains ON users.domain_id = domains.id \
|
||||
WHERE redirect IS NULL
|
||||
# use sql for userdb too, to enable iterate_query
|
||||
userdb {
|
||||
driver = sql
|
||||
args = /etc/dovecot/dovecot-sql.conf
|
||||
}
|
||||
|
||||
service auth {
|
||||
|
@ -103,9 +67,10 @@ service stats {
|
|||
}
|
||||
}
|
||||
service managesieve-login {
|
||||
#inet_listener sieve {}
|
||||
process_min_avail = 1
|
||||
process_limit = 1
|
||||
inet_listener sieve {
|
||||
}
|
||||
process_min_avail = 0
|
||||
service_count = 1
|
||||
vsz_limit = 64 M
|
||||
}
|
||||
service managesieve {
|
||||
|
@ -113,53 +78,31 @@ service managesieve {
|
|||
}
|
||||
|
||||
protocol imap {
|
||||
mail_plugins = fts fts_flatcurve imap_sieve
|
||||
mail_plugins = $mail_plugins imap_sieve
|
||||
mail_max_userip_connections = 50
|
||||
imap_idle_notify_interval = 29 mins
|
||||
}
|
||||
protocol lmtp {
|
||||
mail_plugins = fts fts_flatcurve sieve
|
||||
mail_plugins = $mail_plugins sieve
|
||||
}
|
||||
|
||||
# Persönliches Skript (deine alte Datei /var/vmail/sieve/%u.sieve)
|
||||
sieve_script personal {
|
||||
driver = file
|
||||
# Verzeichnis mit (evtl. mehreren) Sieve-Skripten des Users
|
||||
path = /var/vmail/sieve/%{user}/
|
||||
# Aktives Skript (entspricht früher "sieve = /var/vmail/sieve/%u.sieve")
|
||||
active_path = /var/vmail/sieve/%{user}.sieve
|
||||
}
|
||||
|
||||
# Globales After-Skript (dein früheres "sieve_after = …")
|
||||
sieve_script after {
|
||||
type = after
|
||||
driver = file
|
||||
path = /var/vmail/sieve/global/spam-to-folder.sieve
|
||||
protocol sieve {
|
||||
plugin {
|
||||
sieve = /var/vmail/sieve/%u.sieve
|
||||
sieve_storage = /var/vmail/sieve/%u/
|
||||
}
|
||||
}
|
||||
|
||||
# fulltext search
|
||||
language en {
|
||||
plugin {
|
||||
fts = xapian
|
||||
fts_xapian = partial=3 full=20 verbose=0
|
||||
fts_autoindex = yes
|
||||
fts_enforced = yes
|
||||
# Index attachements
|
||||
fts_decoder = decode2text
|
||||
}
|
||||
language de {
|
||||
default = yes
|
||||
}
|
||||
language_tokenizers = generic email-address
|
||||
|
||||
fts flatcurve {
|
||||
substring_search = yes
|
||||
# rotate_count = 5000 # DB-Rotation nach X Mails
|
||||
# rotate_time = 5s # oder zeitbasiert rotieren
|
||||
# optimize_limit = 10
|
||||
# min_term_size = 3
|
||||
}
|
||||
|
||||
fts_autoindex = yes
|
||||
fts_decoder_driver = script
|
||||
fts_decoder_script_socket_path = decode2text
|
||||
|
||||
service indexer-worker {
|
||||
process_limit = ${indexer_cores}
|
||||
vsz_limit = ${indexer_ram}M
|
||||
vsz_limit = ${indexer_ram}
|
||||
}
|
||||
service decode2text {
|
||||
executable = script /usr/local/libexec/dovecot/decode2text.sh
|
||||
|
@ -169,39 +112,24 @@ service decode2text {
|
|||
}
|
||||
}
|
||||
|
||||
mailbox Junk {
|
||||
sieve_script learn_spam {
|
||||
driver = file
|
||||
type = before
|
||||
cause = copy
|
||||
path = /var/vmail/sieve/global/learn-spam.sieve
|
||||
}
|
||||
}
|
||||
# spam filter
|
||||
plugin {
|
||||
sieve_plugins = sieve_imapsieve sieve_extprograms
|
||||
sieve_dir = /var/vmail/sieve/%u/
|
||||
sieve = /var/vmail/sieve/%u.sieve
|
||||
sieve_pipe_bin_dir = /var/vmail/sieve/bin
|
||||
sieve_extensions = +vnd.dovecot.pipe
|
||||
|
||||
imapsieve_from Junk {
|
||||
sieve_script learn_ham {
|
||||
driver = file
|
||||
type = before
|
||||
cause = copy
|
||||
path = /var/vmail/sieve/global/learn-ham.sieve
|
||||
}
|
||||
}
|
||||
sieve_after = /var/vmail/sieve/global/spam-to-folder.sieve
|
||||
|
||||
# Extprograms-Plugin einschalten
|
||||
sieve_plugins {
|
||||
sieve_extprograms = yes
|
||||
}
|
||||
# From elsewhere to Spam folder
|
||||
imapsieve_mailbox1_name = Junk
|
||||
imapsieve_mailbox1_causes = COPY
|
||||
imapsieve_mailbox1_before = file:/var/vmail/sieve/global/learn-spam.sieve
|
||||
|
||||
# Welche Sieve-Erweiterungen dürfen genutzt werden?
|
||||
# Empfehlung: nur global erlauben (nicht in User-Skripten):
|
||||
sieve_global_extensions {
|
||||
vnd.dovecot.pipe = yes
|
||||
# vnd.dovecot.filter = yes # nur falls gebraucht
|
||||
# vnd.dovecot.execute = yes # nur falls gebraucht
|
||||
# From Spam folder to elsewhere
|
||||
imapsieve_mailbox2_name = *
|
||||
imapsieve_mailbox2_from = Junk
|
||||
imapsieve_mailbox2_causes = COPY
|
||||
imapsieve_mailbox2_before = file:/var/vmail/sieve/global/learn-ham.sieve
|
||||
}
|
||||
|
||||
# Verzeichnis mit deinen Skripten/Binaries für :pipe
|
||||
sieve_pipe_bin_dir = /var/vmail/sieve/bin
|
||||
# (optional, analog für :filter / :execute)
|
||||
# sieve_filter_bin_dir = /var/vmail/sieve/filter
|
||||
# sieve_execute_bin_dir = /var/vmail/sieve/execute
|
|
@ -44,16 +44,6 @@ files = {
|
|||
'context': {
|
||||
'admin_email': node.metadata.get('mailserver/admin_email'),
|
||||
'indexer_ram': node.metadata.get('dovecot/indexer_ram'),
|
||||
'config_version': node.metadata.get('dovecot/config_version'),
|
||||
'storage_version': node.metadata.get('dovecot/storage_version'),
|
||||
'maildir': node.metadata.get('mailserver/maildir'),
|
||||
'hostname': node.metadata.get('mailserver/hostname'),
|
||||
'db_host': node.metadata.get('mailserver/database/host'),
|
||||
'db_name': node.metadata.get('mailserver/database/name'),
|
||||
'db_user': node.metadata.get('mailserver/database/user'),
|
||||
'db_password': node.metadata.get('mailserver/database/password'),
|
||||
'indexer_cores': node.metadata.get('vm/cores'),
|
||||
'indexer_ram': node.metadata.get('vm/ram')//2,
|
||||
},
|
||||
'needs': {
|
||||
'pkg_apt:'
|
||||
|
@ -62,9 +52,29 @@ files = {
|
|||
'svc_systemd:dovecot:restart',
|
||||
},
|
||||
},
|
||||
'/etc/dovecot/dovecot-sql.conf': {
|
||||
'content_type': 'mako',
|
||||
'context': node.metadata.get('mailserver/database'),
|
||||
'needs': {
|
||||
'pkg_apt:'
|
||||
},
|
||||
'triggers': {
|
||||
'svc_systemd:dovecot:restart',
|
||||
},
|
||||
},
|
||||
'/etc/dovecot/dhparam.pem': {
|
||||
'content_type': 'any',
|
||||
},
|
||||
'/etc/dovecot/dovecot-sql.conf': {
|
||||
'content_type': 'mako',
|
||||
'context': node.metadata.get('mailserver/database'),
|
||||
'needs': {
|
||||
'pkg_apt:'
|
||||
},
|
||||
'triggers': {
|
||||
'svc_systemd:dovecot:restart',
|
||||
},
|
||||
},
|
||||
'/var/vmail/sieve/global/spam-to-folder.sieve': {
|
||||
'owner': 'vmail',
|
||||
'group': 'vmail',
|
||||
|
@ -121,6 +131,7 @@ svc_systemd = {
|
|||
'action:letsencrypt_update_certificates',
|
||||
'action:dovecot_generate_dhparam',
|
||||
'file:/etc/dovecot/dovecot.conf',
|
||||
'file:/etc/dovecot/dovecot-sql.conf',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ defaults = {
|
|||
'dovecot-sieve': {},
|
||||
'dovecot-managesieved': {},
|
||||
# fulltext search
|
||||
'dovecot-flatcurve': {}, # buster-backports
|
||||
'dovecot-fts-xapian': {}, # buster-backports
|
||||
'poppler-utils': {}, # pdftotext
|
||||
'catdoc': {}, # catdoc, catppt, xls2csv
|
||||
},
|
||||
|
|
|
@ -1,23 +0,0 @@
|
|||
Pg Pass workaround: set manually:
|
||||
|
||||
```
|
||||
root@freescout /ro psql freescout
|
||||
psql (15.6 (Debian 15.6-0+deb12u1))
|
||||
Type "help" for help.
|
||||
|
||||
freescout=# \password freescout
|
||||
Enter new password for user "freescout":
|
||||
Enter it again:
|
||||
freescout=#
|
||||
\q
|
||||
```
|
||||
|
||||
|
||||
# problems
|
||||
|
||||
# check if /opt/freescout/.env is resettet
|
||||
# ckeck `psql -h localhost -d freescout -U freescout -W`with pw from .env
|
||||
# chown -R www-data:www-data /opt/freescout
|
||||
# sudo su - www-data -c 'php /opt/freescout/artisan freescout:clear-cache' -s /bin/bash
|
||||
# javascript funny? `sudo su - www-data -c 'php /opt/freescout/artisan storage:link' -s /bin/bash`
|
||||
# benutzer bilder weg? aus dem backup holen: `/opt/freescout/.zfs/snapshot/zfs-auto-snap_hourly-2024-11-22-1700/storage/app/public/users` `./customers`
|
|
@ -1,66 +0,0 @@
|
|||
# https://github.com/freescout-helpdesk/freescout/wiki/Installation-Guide
|
||||
run_as = repo.libs.tools.run_as
|
||||
php_version = node.metadata.get('php/version')
|
||||
|
||||
|
||||
directories = {
|
||||
'/opt/freescout': {
|
||||
'owner': 'www-data',
|
||||
'group': 'www-data',
|
||||
# chown -R www-data:www-data /opt/freescout
|
||||
},
|
||||
}
|
||||
|
||||
actions = {
|
||||
# 'clone_freescout': {
|
||||
# 'command': run_as('www-data', 'git clone https://github.com/freescout-helpdesk/freescout.git /opt/freescout'),
|
||||
# 'unless': 'test -e /opt/freescout/.git',
|
||||
# 'needs': [
|
||||
# 'pkg_apt:git',
|
||||
# 'directory:/opt/freescout',
|
||||
# ],
|
||||
# },
|
||||
# 'pull_freescout': {
|
||||
# 'command': run_as('www-data', 'git -C /opt/freescout fetch origin dist && git -C /opt/freescout reset --hard origin/dist && git -C /opt/freescout clean -f'),
|
||||
# 'unless': run_as('www-data', 'git -C /opt/freescout fetch origin && git -C /opt/freescout status -uno | grep -q "Your branch is up to date"'),
|
||||
# 'needs': [
|
||||
# 'action:clone_freescout',
|
||||
# ],
|
||||
# 'triggers': [
|
||||
# 'action:freescout_artisan_update',
|
||||
# f'svc_systemd:php{php_version}-fpm.service:restart',
|
||||
# ],
|
||||
# },
|
||||
# 'freescout_artisan_update': {
|
||||
# 'command': run_as('www-data', 'php /opt/freescout/artisan freescout:after-app-update'),
|
||||
# 'triggered': True,
|
||||
# 'needs': [
|
||||
# f'svc_systemd:php{php_version}-fpm.service:restart',
|
||||
# 'action:pull_freescout',
|
||||
# ],
|
||||
# },
|
||||
}
|
||||
|
||||
# svc_systemd = {
|
||||
# f'freescout-cron.service': {},
|
||||
# }
|
||||
|
||||
# files = {
|
||||
# '/opt/freescout/.env': {
|
||||
# # https://github.com/freescout-helpdesk/freescout/blob/dist/.env.example
|
||||
# # Every time you are making changes in .env file, in order changes to take an effect you need to run:
|
||||
# # ´sudo su - www-data -c 'php /opt/freescout/artisan freescout:clear-cache' -s /bin/bash´
|
||||
# 'owner': 'www-data',
|
||||
# 'content': '\n'.join(
|
||||
# f'{k}={v}' for k, v in
|
||||
# sorted(node.metadata.get('freescout/env').items())
|
||||
# ) + '\n',
|
||||
# 'needs': [
|
||||
# 'directory:/opt/freescout',
|
||||
# 'action:clone_freescout',
|
||||
# ],
|
||||
# },
|
||||
# }
|
||||
|
||||
#sudo su - www-data -s /bin/bash -c 'php /opt/freescout/artisan freescout:create-user --role admin --firstName M --lastName W --email freescout@freibrief.net --password gyh.jzv2bnf6hvc.HKG --no-interaction'
|
||||
#sudo su - www-data -s /bin/bash -c 'php /opt/freescout/artisan freescout:create-user --role admin --firstName M --lastName W --email freescout@freibrief.net --password gyh.jzv2bnf6hvc.HKG --no-interaction'
|
|
@ -1,121 +0,0 @@
|
|||
from base64 import b64decode
|
||||
|
||||
# hash: SCRAM-SHA-256$4096:tQNfqQi7seqNDwJdHqCHbg==$r3ibECluHJaY6VRwpvPqrtCjgrEK7lAkgtUO8/tllTU=:+eeo4M0L2SowfyHFxT2FRqGzezve4ZOEocSIo11DATA=
|
||||
database_password = repo.vault.password_for(f'{node.name} postgresql freescout').value
|
||||
|
||||
defaults = {
|
||||
'apt': {
|
||||
'packages': {
|
||||
'git': {},
|
||||
'php': {},
|
||||
'php-pgsql': {},
|
||||
'php-fpm': {},
|
||||
'php-mbstring': {},
|
||||
'php-xml': {},
|
||||
'php-imap': {},
|
||||
'php-zip': {},
|
||||
'php-gd': {},
|
||||
'php-curl': {},
|
||||
'php-intl': {},
|
||||
},
|
||||
},
|
||||
'freescout': {
|
||||
'env': {
|
||||
'APP_TIMEZONE': 'Europe/Berlin',
|
||||
'DB_CONNECTION': 'pgsql',
|
||||
'DB_HOST': '127.0.0.1',
|
||||
'DB_PORT': '5432',
|
||||
'DB_DATABASE': 'freescout',
|
||||
'DB_USERNAME': 'freescout',
|
||||
'DB_PASSWORD': database_password,
|
||||
'APP_KEY': 'base64:' + repo.vault.random_bytes_as_base64_for(f'{node.name} freescout APP_KEY', length=32).value
|
||||
},
|
||||
},
|
||||
'php': {
|
||||
'php.ini': {
|
||||
'cgi': {
|
||||
'fix_pathinfo': '0',
|
||||
},
|
||||
},
|
||||
},
|
||||
'postgresql': {
|
||||
'roles': {
|
||||
'freescout': {
|
||||
'password_hash': repo.libs.postgres.generate_scram_sha_256(
|
||||
database_password,
|
||||
b64decode(repo.vault.random_bytes_as_base64_for(f'{node.name} postgres freescout', length=16).value.encode()),
|
||||
),
|
||||
},
|
||||
},
|
||||
'databases': {
|
||||
'freescout': {
|
||||
'owner': 'freescout',
|
||||
},
|
||||
},
|
||||
},
|
||||
# 'systemd': {
|
||||
# 'units': {
|
||||
# f'freescout-cron.service': {
|
||||
# 'Unit': {
|
||||
# 'Description': 'Freescout Cron',
|
||||
# 'After': 'network.target',
|
||||
# },
|
||||
# 'Service': {
|
||||
# 'User': 'www-data',
|
||||
# 'Nice': 10,
|
||||
# 'ExecStart': f"/usr/bin/php /opt/freescout/artisan schedule:run"
|
||||
# },
|
||||
# 'Install': {
|
||||
# 'WantedBy': {
|
||||
# 'multi-user.target'
|
||||
# }
|
||||
# },
|
||||
# }
|
||||
# },
|
||||
# },
|
||||
'systemd-timers': {
|
||||
'freescout-cron': {
|
||||
'command': '/usr/bin/php /opt/freescout/artisan schedule:run',
|
||||
'when': '*-*-* *:*:00',
|
||||
'RuntimeMaxSec': '180',
|
||||
'user': 'www-data',
|
||||
},
|
||||
},
|
||||
'zfs': {
|
||||
'datasets': {
|
||||
'tank/freescout': {
|
||||
'mountpoint': '/opt/freescout',
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@metadata_reactor.provides(
|
||||
'freescout/env/APP_URL',
|
||||
)
|
||||
def freescout(metadata):
|
||||
return {
|
||||
'freescout': {
|
||||
'env': {
|
||||
'APP_URL': 'https://' + metadata.get('freescout/domain') + '/',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@metadata_reactor.provides(
|
||||
'nginx/vhosts',
|
||||
)
|
||||
def nginx(metadata):
|
||||
return {
|
||||
'nginx': {
|
||||
'vhosts': {
|
||||
metadata.get('freescout/domain'): {
|
||||
'content': 'freescout/vhost.conf',
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
|
@ -40,7 +40,7 @@ ENABLE_OPENID_SIGNUP = false
|
|||
[service]
|
||||
REGISTER_EMAIL_CONFIRM = true
|
||||
ENABLE_NOTIFY_MAIL = true
|
||||
DISABLE_REGISTRATION = true
|
||||
DISABLE_REGISTRATION = false
|
||||
ALLOW_ONLY_EXTERNAL_REGISTRATION = false
|
||||
ENABLE_CAPTCHA = false
|
||||
REQUIRE_SIGNIN_VIEW = false
|
||||
|
|
|
@ -49,7 +49,7 @@ files['/etc/gitea/app.ini'] = {
|
|||
),
|
||||
'owner': 'git',
|
||||
'mode': '0600',
|
||||
'context': node.metadata.get('gitea'),
|
||||
'context': node.metadata['gitea'],
|
||||
'triggers': {
|
||||
'svc_systemd:gitea:restart',
|
||||
},
|
||||
|
|
|
@ -118,7 +118,7 @@ def nginx(metadata):
|
|||
'content': 'nginx/proxy_pass.conf',
|
||||
'context': {
|
||||
'target': 'http://127.0.0.1:3500',
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -26,10 +26,7 @@ actions['reset_grafana_admin_password'] = {
|
|||
|
||||
directories = {
|
||||
'/etc/grafana': {},
|
||||
'/etc/grafana/provisioning': {
|
||||
'owner': 'grafana',
|
||||
'group': 'grafana',
|
||||
},
|
||||
'/etc/grafana/provisioning': {},
|
||||
'/etc/grafana/provisioning/datasources': {
|
||||
'purge': True,
|
||||
},
|
||||
|
|
|
@ -26,15 +26,9 @@ defaults = {
|
|||
'config': {
|
||||
'server': {
|
||||
'http_port': 8300,
|
||||
'http_addr': '127.0.0.1',
|
||||
'enable_gzip': True,
|
||||
},
|
||||
'database': {
|
||||
'type': 'postgres',
|
||||
'host': '127.0.0.1:5432',
|
||||
'name': 'grafana',
|
||||
'user': 'grafana',
|
||||
'password': postgres_password,
|
||||
'url': f'postgres://grafana:{postgres_password}@localhost:5432/grafana',
|
||||
},
|
||||
'remote_cache': {
|
||||
'type': 'redis',
|
||||
|
@ -139,13 +133,11 @@ def dns(metadata):
|
|||
|
||||
|
||||
@metadata_reactor.provides(
|
||||
'nginx/has_websockets',
|
||||
'nginx/vhosts',
|
||||
)
|
||||
def nginx(metadata):
|
||||
return {
|
||||
'nginx': {
|
||||
'has_websockets': True,
|
||||
'vhosts': {
|
||||
metadata.get('grafana/hostname'): {
|
||||
'content': 'grafana/vhost.conf',
|
||||
|
|
20
bundles/homeassistant/items.py
Normal file
20
bundles/homeassistant/items.py
Normal file
|
@ -0,0 +1,20 @@
|
|||
users = {
|
||||
'homeassistant': {
|
||||
'home': '/var/lib/homeassistant',
|
||||
},
|
||||
}
|
||||
|
||||
directories = {
|
||||
'/var/lib/homeassistant': {
|
||||
'owner': 'homeassistant',
|
||||
},
|
||||
'/var/lib/homeassistant/config': {
|
||||
'owner': 'homeassistant',
|
||||
},
|
||||
'/var/lib/homeassistant/venv': {
|
||||
'owner': 'homeassistant',
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
# https://wiki.instar.com/de/Software/Linux/Home_Assistant/
|
20
bundles/homeassistant/metadata.py
Normal file
20
bundles/homeassistant/metadata.py
Normal file
|
@ -0,0 +1,20 @@
|
|||
defaults = {
|
||||
'apt': {
|
||||
'packages': {
|
||||
'python3': {},
|
||||
'python3-dev': {},
|
||||
'python3-pip': {},
|
||||
'python3-venv': {},
|
||||
'libffi-dev': {},
|
||||
'libssl-dev': {},
|
||||
'libjpeg-dev': {},
|
||||
'zlib1g-dev': {},
|
||||
'autoconf': {},
|
||||
'build-essential': {},
|
||||
'libopenjp2-7': {},
|
||||
'libtiff5': {},
|
||||
'libturbojpeg0-dev': {},
|
||||
'tzdata': {},
|
||||
},
|
||||
},
|
||||
}
|
|
@ -13,9 +13,9 @@ apply Notification "mail-icingaadmin" to Host {
|
|||
user_groups = host.vars.notification.mail.groups
|
||||
users = host.vars.notification.mail.users
|
||||
|
||||
//interval = 2h
|
||||
|
||||
|
||||
|
||||
//vars.notification_logtosyslog = true
|
||||
|
||||
assign where host.vars.notification.mail
|
||||
}
|
||||
|
@ -25,9 +25,9 @@ apply Notification "mail-icingaadmin" to Service {
|
|||
user_groups = host.vars.notification.mail.groups
|
||||
users = host.vars.notification.mail.users
|
||||
|
||||
//interval = 2h
|
||||
|
||||
|
||||
|
||||
//vars.notification_logtosyslog = true
|
||||
|
||||
assign where host.vars.notification.mail
|
||||
}
|
||||
|
|
|
@ -269,7 +269,7 @@ svc_systemd = {
|
|||
'icinga2.service': {
|
||||
'needs': [
|
||||
'pkg_apt:icinga2-ido-pgsql',
|
||||
'svc_systemd:postgresql.service',
|
||||
'svc_systemd:postgresql',
|
||||
],
|
||||
},
|
||||
}
|
||||
|
|
|
@ -179,7 +179,6 @@ def nginx(metadata):
|
|||
'context': {
|
||||
'php_version': metadata.get('php/version'),
|
||||
},
|
||||
'check_path': '/icingaweb2/index.php',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
# svc_systemd = {
|
||||
# 'ifupdown.service': {},
|
||||
# }
|
|
@ -1,21 +0,0 @@
|
|||
from json import dumps
|
||||
from bundlewrap.metadata import MetadataJSONEncoder
|
||||
|
||||
files = {
|
||||
'/etc/kea/kea-dhcp4.conf': {
|
||||
'content': dumps(node.metadata.get('kea'), indent=4, sort_keys=True, cls=MetadataJSONEncoder),
|
||||
'triggers': [
|
||||
'svc_systemd:kea-dhcp4-server:restart',
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
svc_systemd = {
|
||||
'kea-dhcp4-server': {
|
||||
'needs': [
|
||||
'pkg_apt:kea-dhcp4-server',
|
||||
'file:/etc/kea/kea-dhcp4.conf',
|
||||
'svc_systemd:systemd-networkd.service:restart',
|
||||
],
|
||||
},
|
||||
}
|
|
@ -1,97 +0,0 @@
|
|||
from ipaddress import ip_interface, ip_network
|
||||
|
||||
hashable = repo.libs.hashable.hashable
|
||||
|
||||
|
||||
defaults = {
|
||||
'apt': {
|
||||
'packages': {
|
||||
'kea-dhcp4-server': {},
|
||||
},
|
||||
},
|
||||
'kea': {
|
||||
'Dhcp4': {
|
||||
'interfaces-config': {
|
||||
'interfaces': set(),
|
||||
},
|
||||
'lease-database': {
|
||||
'type': 'memfile',
|
||||
'lfc-interval': 3600
|
||||
},
|
||||
'subnet4': set(),
|
||||
'loggers': set([
|
||||
hashable({
|
||||
'name': 'kea-dhcp4',
|
||||
'output_options': [
|
||||
{
|
||||
'output': 'syslog',
|
||||
}
|
||||
],
|
||||
'severity': 'INFO',
|
||||
}),
|
||||
]),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@metadata_reactor.provides(
|
||||
'kea/Dhcp4/interfaces-config/interfaces',
|
||||
'kea/Dhcp4/subnet4',
|
||||
)
|
||||
def subnets(metadata):
|
||||
subnet4 = set()
|
||||
interfaces = set()
|
||||
reservations = set(
|
||||
hashable({
|
||||
'hw-address': network_conf['mac'],
|
||||
'ip-address': str(ip_interface(network_conf['ipv4']).ip),
|
||||
})
|
||||
for other_node in repo.nodes
|
||||
for network_conf in other_node.metadata.get('network', {}).values()
|
||||
if 'mac' in network_conf
|
||||
)
|
||||
|
||||
for id, (network_name, network_conf) in enumerate(sorted(metadata.get('network').items())):
|
||||
dhcp_server_config = network_conf.get('dhcp_server_config', None)
|
||||
|
||||
if dhcp_server_config:
|
||||
_network = ip_network(dhcp_server_config['subnet'])
|
||||
|
||||
subnet4.add(hashable({
|
||||
'id': id + 1,
|
||||
'subnet': dhcp_server_config['subnet'],
|
||||
'pools': [
|
||||
{
|
||||
'pool': f'{dhcp_server_config['pool_from']} - {dhcp_server_config['pool_to']}',
|
||||
},
|
||||
],
|
||||
'option-data': [
|
||||
{
|
||||
'name': 'routers',
|
||||
'data': dhcp_server_config['router'],
|
||||
},
|
||||
{
|
||||
'name': 'domain-name-servers',
|
||||
'data': '10.0.0.1',
|
||||
},
|
||||
],
|
||||
'reservations': set(
|
||||
reservation
|
||||
for reservation in reservations
|
||||
if ip_interface(reservation['ip-address']).ip in _network
|
||||
),
|
||||
}))
|
||||
|
||||
interfaces.add(network_conf.get('interface', network_name))
|
||||
|
||||
return {
|
||||
'kea': {
|
||||
'Dhcp4': {
|
||||
'interfaces-config': {
|
||||
'interfaces': interfaces,
|
||||
},
|
||||
'subnet4': subnet4,
|
||||
},
|
||||
},
|
||||
}
|
|
@ -1 +1,58 @@
|
|||
https://github.com/SirPlease/L4D2-Competitive-Rework/blob/master/Dedicated%20Server%20Install%20Guide/README.md
|
||||
https://developer.valvesoftware.com/wiki/List_of_L4D2_Cvars
|
||||
|
||||
Dead Center c1m1_hotel
|
||||
Dead Center c1m2_streets
|
||||
Dead Center c1m3_mall
|
||||
Dead Center c1m4_atrium
|
||||
Dark Carnival c2m1_highway
|
||||
Dark Carnival c2m2_fairgrounds
|
||||
Dark Carnival c2m3_coaster
|
||||
Dark Carnival c2m4_barns
|
||||
Dark Carnival c2m5_concert
|
||||
Swamp Fever c3m1_plankcountry
|
||||
Swamp Fever c3m2_swamp
|
||||
Swamp Fever c3m3_shantytown
|
||||
Swamp Fever c3m4_plantation
|
||||
Hard Rain c4m1_milltown_a
|
||||
Hard Rain c4m2_sugarmill_a
|
||||
Hard Rain c4m3_sugarmill_b
|
||||
Hard Rain c4m4_milltown_b
|
||||
Hard Rain c4m5_milltown_escape
|
||||
The Parish c5m1_waterfront_sndscape
|
||||
The Parish c5m1_waterfront
|
||||
The Parish c5m2_park
|
||||
The Parish c5m3_cemetery
|
||||
The Parish c5m4_quarter
|
||||
The Parish c5m5_bridge
|
||||
The Passing c6m1_riverbank
|
||||
The Passing c6m2_bedlam
|
||||
The Passing c6m3_port
|
||||
The Sacrifice c7m1_docks
|
||||
The Sacrifice c7m2_barge
|
||||
The Sacrifice c7m3_port
|
||||
No Mercy c8m1_apartment
|
||||
No Mercy c8m2_subway
|
||||
No Mercy c8m3_sewers
|
||||
No Mercy c8m4_interior
|
||||
No Mercy c8m5_rooftop
|
||||
Crash Course c9m1_alleys
|
||||
Crash Course c9m2_lots
|
||||
Death Toll c10m1_caves
|
||||
Death Toll c10m2_drainage
|
||||
Death Toll c10m3_ranchhouse
|
||||
Death Toll c10m4_mainstreet
|
||||
Death Toll c10m5_houseboat
|
||||
Dead Air c11m1_greenhouse
|
||||
Dead Air c11m2_offices
|
||||
Dead Air c11m3_garage
|
||||
Dead Air c11m4_terminal
|
||||
Dead Air c11m5_runway
|
||||
Blood Harvest c12m1_hilltop
|
||||
Blood Harvest c12m2_traintunnel
|
||||
Blood Harvest c12m3_bridge
|
||||
Blood Harvest c12m4_barn
|
||||
Blood Harvest c12m5_cornfield
|
||||
Cold Stream c13m1_alpinecreek
|
||||
Cold Stream c13m2_southpinestream
|
||||
Cold Stream c13m3_memorialbridge
|
||||
Cold Stream c13m4_cutthroatcreek
|
||||
|
|
|
@ -1,36 +1,36 @@
|
|||
hostname "CroneKorkN : ${name}"
|
||||
sv_contact "admin@sublimity.de"
|
||||
|
||||
|
||||
// assign serevr to steam group
|
||||
sv_steamgroup "${','.join(steamgroups)}"
|
||||
|
||||
rcon_password "${rcon_password}"
|
||||
|
||||
|
||||
// no annoying message of the day
|
||||
motd_enabled 0
|
||||
|
||||
|
||||
// enable cheats
|
||||
sv_cheats 1
|
||||
|
||||
|
||||
// allow inconsistent files on clients (weapon mods for example)
|
||||
sv_consistency 0
|
||||
|
||||
|
||||
// connect from internet
|
||||
sv_lan 0
|
||||
|
||||
|
||||
// join game at any point
|
||||
sv_allow_lobby_connect_only 0
|
||||
|
||||
|
||||
// allowed modes
|
||||
sv_gametypes "coop,realism,survival,versus,teamversus,scavenge,teamscavenge"
|
||||
|
||||
|
||||
// network
|
||||
sv_minrate 30000
|
||||
sv_maxrate 60000
|
||||
sv_mincmdrate 66
|
||||
sv_maxcmdrate 101
|
||||
|
||||
|
||||
// logging
|
||||
sv_logsdir "logs-${name}" //Folder in the game directory where server logs will be stored.
|
||||
log on //Creates a logfile (on | off)
|
||||
sv_logecho 0 //default 0; Echo log information to the console.
|
|
@ -1,96 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
set -xeuo pipefail
|
||||
|
||||
getent passwd steam >/dev/null || useradd -M -d /opt/l4d2 -s /bin/bash steam
|
||||
mkdir -p /opt/l4d2 /tmp/dumps
|
||||
chown steam:steam /opt/l4d2 /tmp/dumps
|
||||
dpkg --add-architecture i386
|
||||
apt update
|
||||
DEBIAN_FRONTEND=noninteractive apt install -y libc6:i386 lib32z1
|
||||
|
||||
function steam() {
|
||||
# für systemd, damit es den prozess beenden kann
|
||||
setpriv --reuid=steam --regid=steam --init-groups "$@"
|
||||
export HOME=/opt/l4d2/steam
|
||||
}
|
||||
|
||||
# -- STEAM -- #
|
||||
|
||||
steam mkdir -p /opt/l4d2/steam
|
||||
test -f /opt/l4d2/steam/steamcmd_linux.tar.gz || \
|
||||
steam wget http://media.steampowered.com/installer/steamcmd_linux.tar.gz -P /opt/l4d2/steam
|
||||
test -f /opt/l4d2/steam/steamcmd.sh || \
|
||||
steam tar -xvzf /opt/l4d2/steam/steamcmd_linux.tar.gz -C /opt/l4d2/steam
|
||||
|
||||
# fix for: /opt/l4d2/.steam/sdk32/steamclient.so: cannot open shared object file: No such file or directory
|
||||
steam mkdir -p /opt/l4d2/steam/.steam # needs to be in steam users home dir
|
||||
readlink /opt/l4d2/steam/.steam/sdk32 | grep -q ^/opt/l4d2/steam/linux32$ || \
|
||||
steam ln -sf /opt/l4d2/steam/linux32 /opt/l4d2/steam/.steam/sdk32
|
||||
readlink /opt/l4d2/steam/.steam/sdk64 | grep -q ^/opt/l4d2/steam/linux64$ || \
|
||||
steam ln -sf /opt/l4d2/steam/linux64 /opt/l4d2/steam/.steam/sdk64
|
||||
|
||||
# -- INSTALL -- #
|
||||
|
||||
# erst die windows deps zu installieren scheint ein workaround für x64 zu sein?
|
||||
steam mkdir -p /opt/l4d2/installation
|
||||
steam /opt/l4d2/steam/steamcmd.sh \
|
||||
+force_install_dir /opt/l4d2/installation \
|
||||
+login anonymous \
|
||||
+@sSteamCmdForcePlatformType windows \
|
||||
+app_update 222860 validate \
|
||||
+quit
|
||||
steam /opt/l4d2/steam/steamcmd.sh \
|
||||
+force_install_dir /opt/l4d2/installation \
|
||||
+login anonymous \
|
||||
+@sSteamCmdForcePlatformType linux \
|
||||
+app_update 222860 validate \
|
||||
+quit
|
||||
|
||||
# -- OVERLAYS -- #
|
||||
|
||||
steam mkdir -p /opt/l4d2/overlays
|
||||
|
||||
# workshop downloader
|
||||
test -f /opt/l4d2/steam-workshop-download || \
|
||||
steam wget -4 https://git.sublimity.de/cronekorkn/steam-workshop-downloader/raw/branch/master/steam-workshop-download -P /opt/l4d2
|
||||
steam chmod +x /opt/l4d2/steam-workshop-download
|
||||
|
||||
# -- OVERLAY PVE -- #
|
||||
|
||||
steam mkdir -p /opt/l4d2/overlays/pve
|
||||
|
||||
# server config
|
||||
steam mkdir -p /opt/l4d2/overlays/pve/left4dead2/cfg
|
||||
steam cat <<'EOF' > /opt/l4d2/overlays/pve/left4dead2/cfg/server.cfg
|
||||
motd_enabled 0
|
||||
|
||||
sv_steamgroup "38347879"
|
||||
#sv_steamgroup_exclusive 0
|
||||
|
||||
sv_minrate 60000
|
||||
sv_maxrate 0
|
||||
net_splitpacket_maxrate 60000
|
||||
|
||||
#sv_cheats 1
|
||||
#sb_all_bot_game 1
|
||||
EOF
|
||||
|
||||
# admin system
|
||||
steam mkdir -p /opt/l4d2/overlays/pve/left4dead2/addons
|
||||
test -f /opt/l4d2/overlays/pve/left4dead2/addons/2524204971.vpk || \
|
||||
steam /opt/l4d2/steam-workshop-download 2524204971 --out /opt/l4d2/overlays/pve/left4dead2/addons
|
||||
steam mkdir -p "/opt/l4d2/overlays/pve/left4dead2/ems/admin system"
|
||||
steam echo "STEAM_1:0:12376499" > "/opt/l4d2/overlays/pve/left4dead2/ems/admin system/admins.txt"
|
||||
|
||||
# ions vocalizer
|
||||
test -f /opt/l4d2/overlays/pve/left4dead2/addons/698857882.vpk || \
|
||||
steam /opt/l4d2/steam-workshop-download 698857882 --out /opt/l4d2/overlays/pve/left4dead2/addons
|
||||
|
||||
test -f /opt/l4d2/overlays/pve/left4dead2/addons/1575673903.vpk || \
|
||||
steam /opt/l4d2/steam-workshop-download 1575673903 --out /opt/l4d2/overlays/pve/left4dead2/addons
|
||||
|
||||
# -- SERVERS -- #
|
||||
|
||||
#steam rm -rf /opt/l4d2/servers
|
||||
steam mkdir -p /opt/l4d2/servers
|
|
@ -1,28 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
set -xeuo pipefail
|
||||
|
||||
name=$1
|
||||
overlay=$2
|
||||
port=$3
|
||||
|
||||
function steam() {
|
||||
# für systemd, damit es den prozess beenden kann
|
||||
setpriv --reuid=steam --regid=steam --init-groups "$@"
|
||||
export HOME=/opt/l4d2/steam
|
||||
}
|
||||
|
||||
mountpoint -q "/opt/l4d2/servers/$name/merged" && umount "/opt/l4d2/servers/$name/merged"
|
||||
steam rm -rf "/opt/l4d2/servers/$name"
|
||||
|
||||
steam mkdir -p \
|
||||
"/opt/l4d2/servers/$name" \
|
||||
"/opt/l4d2/servers/$name/work" \
|
||||
"/opt/l4d2/servers/$name/upper" \
|
||||
"/opt/l4d2/servers/$name/merged"
|
||||
|
||||
mount -t overlay overlay \
|
||||
-o "lowerdir=/opt/l4d2/overlays/$overlay:/opt/l4d2/installation,upperdir=/opt/l4d2/servers/$name/upper,workdir=/opt/l4d2/servers/$name/work" \
|
||||
"/opt/l4d2/servers/$name/merged"
|
||||
|
||||
steam "/opt/l4d2/servers/$name/merged/srcds_run" -norestart -pidfile "/opt/l4d2/servers/$name/pid" -game left4dead2 -ip 0.0.0.0 -port "$port" +hostname "Crone_$name" +map c1m1_hotel
|
|
@ -1,31 +1,122 @@
|
|||
files = {
|
||||
'/opt/l4d2/setup': {
|
||||
'mode': '755',
|
||||
assert node.has_bundle('steam') and node.has_bundle('steam-workshop-download')
|
||||
|
||||
directories = {
|
||||
'/opt/steam/left4dead2-servers': {
|
||||
'owner': 'steam',
|
||||
'group': 'steam',
|
||||
'mode': '0755',
|
||||
'purge': True,
|
||||
},
|
||||
'/opt/l4d2/start': {
|
||||
'mode': '755',
|
||||
# Current zfs doesnt support zfs upperdir. The support was added in October 2022. Move upperdir - unused anyway -
|
||||
# to another dir. Also move workdir alongside it, as it has to be on same fs.
|
||||
'/opt/steam-zfs-overlay-workarounds': {
|
||||
'owner': 'steam',
|
||||
'group': 'steam',
|
||||
'mode': '0755',
|
||||
'purge': True,
|
||||
},
|
||||
}
|
||||
|
||||
svc_systemd = {
|
||||
'left4dead2-initialize.service': {
|
||||
'enabled': True,
|
||||
'running': None,
|
||||
'needs': {
|
||||
'file:/usr/local/lib/systemd/system/left4dead2-initialize.service',
|
||||
},
|
||||
},
|
||||
# /opt/steam/steam/.steam/sdk32/steamclient.so: cannot open shared object file: No such file or directory
|
||||
symlinks = {
|
||||
'/opt/steam/steam/.steam/sdk32': {
|
||||
'target': '/opt/steam/steam/linux32',
|
||||
'owner': 'steam',
|
||||
'group': 'steam',
|
||||
}
|
||||
}
|
||||
|
||||
for server_name in node.metadata.get('left4dead2').keys():
|
||||
svc_systemd[f'left4dead2-{server_name}.service'] = {
|
||||
'enabled': True,
|
||||
'running': True,
|
||||
'tags': {
|
||||
'left4dead2-servers',
|
||||
#
|
||||
# SERVERS
|
||||
#
|
||||
|
||||
for name, config in node.metadata.get('left4dead2/servers').items():
|
||||
|
||||
#overlay
|
||||
directories[f'/opt/steam/left4dead2-servers/{name}'] = {
|
||||
'owner': 'steam',
|
||||
'group': 'steam',
|
||||
}
|
||||
directories[f'/opt/steam-zfs-overlay-workarounds/{name}/upper'] = {
|
||||
'owner': 'steam',
|
||||
'group': 'steam',
|
||||
}
|
||||
directories[f'/opt/steam-zfs-overlay-workarounds/{name}/workdir'] = {
|
||||
'owner': 'steam',
|
||||
'group': 'steam',
|
||||
}
|
||||
|
||||
# conf
|
||||
files[f'/opt/steam/left4dead2-servers/{name}/left4dead2/cfg/server.cfg'] = {
|
||||
'content_type': 'mako',
|
||||
'source': 'server.cfg',
|
||||
'context': {
|
||||
'name': name,
|
||||
'steamgroups': node.metadata.get('left4dead2/steamgroups'),
|
||||
'rcon_password': config['rcon_password'],
|
||||
},
|
||||
'needs': {
|
||||
'svc_systemd:left4dead2-initialize.service',
|
||||
f'file:/usr/local/lib/systemd/system/left4dead2-{server_name}.service',
|
||||
'owner': 'steam',
|
||||
'group': 'steam',
|
||||
'triggers': [
|
||||
f'svc_systemd:left4dead2-{name}.service:restart',
|
||||
],
|
||||
}
|
||||
|
||||
# service
|
||||
svc_systemd[f'left4dead2-{name}.service'] = {
|
||||
'needs': [
|
||||
f'file:/opt/steam/left4dead2-servers/{name}/left4dead2/cfg/server.cfg',
|
||||
f'file:/usr/local/lib/systemd/system/left4dead2-{name}.service',
|
||||
],
|
||||
}
|
||||
|
||||
#
|
||||
# ADDONS
|
||||
#
|
||||
|
||||
# base
|
||||
files[f'/opt/steam/left4dead2-servers/{name}/left4dead2/addons/readme.txt'] = {
|
||||
'content_type': 'any',
|
||||
'owner': 'steam',
|
||||
'group': 'steam',
|
||||
}
|
||||
directories[f'/opt/steam/left4dead2-servers/{name}/left4dead2/addons'] = {
|
||||
'owner': 'steam',
|
||||
'group': 'steam',
|
||||
'purge': True,
|
||||
'triggers': [
|
||||
f'svc_systemd:left4dead2-{name}.service:restart',
|
||||
],
|
||||
}
|
||||
for id in [
|
||||
*config.get('workshop', []),
|
||||
*node.metadata.get('left4dead2/workshop'),
|
||||
]:
|
||||
files[f'/opt/steam/left4dead2-servers/{name}/left4dead2/addons/{id}.vpk'] = {
|
||||
'content_type': 'any',
|
||||
'owner': 'steam',
|
||||
'group': 'steam',
|
||||
'triggers': [
|
||||
f'svc_systemd:left4dead2-{name}.service:restart',
|
||||
],
|
||||
}
|
||||
|
||||
# admin system
|
||||
|
||||
directories[f'/opt/steam/left4dead2-servers/{name}/left4dead2/ems/admin system'] = {
|
||||
'owner': 'steam',
|
||||
'group': 'steam',
|
||||
'mode': '0755',
|
||||
'triggers': [
|
||||
f'svc_systemd:left4dead2-{name}.service:restart',
|
||||
],
|
||||
}
|
||||
files[f'/opt/steam/left4dead2-servers/{name}/left4dead2/ems/admin system/admins.txt'] = {
|
||||
'owner': 'steam',
|
||||
'group': 'steam',
|
||||
'mode': '0755',
|
||||
'content': '\n'.join(sorted(node.metadata.get('left4dead2/admins'))),
|
||||
'triggers': [
|
||||
f'svc_systemd:left4dead2-{name}.service:restart',
|
||||
],
|
||||
}
|
||||
|
|
|
@ -1,68 +1,102 @@
|
|||
from re import match
|
||||
assert node.has_bundle('steam')
|
||||
|
||||
from shlex import quote
|
||||
|
||||
defaults = {
|
||||
'apt': {
|
||||
'packages': {
|
||||
'libc6_i386': {}, # installs libc6:i386
|
||||
'lib32z1': {},
|
||||
'unzip': {},
|
||||
},
|
||||
},
|
||||
'left4dead2': {},
|
||||
'nftables': {
|
||||
'input': {
|
||||
'udp dport { 27005, 27020 } accept',
|
||||
},
|
||||
},
|
||||
'systemd': {
|
||||
'units': {
|
||||
'left4dead2-initialize.service': {
|
||||
'Unit': {
|
||||
'Description': 'initialize left4dead2',
|
||||
'After': 'network-online.target',
|
||||
},
|
||||
'Service': {
|
||||
'Type': 'oneshot',
|
||||
'RemainAfterExit': 'yes',
|
||||
'ExecStart': '/opt/l4d2/setup',
|
||||
'StandardOutput': 'journal',
|
||||
'StandardError': 'journal',
|
||||
},
|
||||
'Install': {
|
||||
'WantedBy': {'multi-user.target'},
|
||||
},
|
||||
'steam': {
|
||||
'games': {
|
||||
'left4dead2': 222860,
|
||||
},
|
||||
},
|
||||
'left4dead2': {
|
||||
'servers': {},
|
||||
'admins': set(),
|
||||
'workshop': set(),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@metadata_reactor.provides(
|
||||
'left4dead2/servers',
|
||||
)
|
||||
def rconn_password(metadata):
|
||||
# only works from localhost!
|
||||
return {
|
||||
'left4dead2': {
|
||||
'servers': {
|
||||
server: {
|
||||
'rcon_password': repo.vault.password_for(f'{node.name} left4dead2 {server} rcon', length=24),
|
||||
}
|
||||
for server in metadata.get('left4dead2/servers')
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@metadata_reactor.provides(
|
||||
'steam-workshop-download',
|
||||
'systemd/units',
|
||||
)
|
||||
def server_units(metadata):
|
||||
units = {}
|
||||
workshop = {}
|
||||
|
||||
for name, config in metadata.get('left4dead2').items():
|
||||
assert match(r'^[A-z0-9-_-]+$', name)
|
||||
assert config["overlay"] in {'pve'}
|
||||
assert 27000 <= config["port"] <= 27100
|
||||
for name, config in metadata.get('left4dead2/servers').items():
|
||||
# mount overlay
|
||||
mountpoint = f'/opt/steam/left4dead2-servers/{name}'
|
||||
mount_unit_name = mountpoint[1:].replace('-', '\\x2d').replace('/', '-') + '.mount'
|
||||
units[mount_unit_name] = {
|
||||
'Unit': {
|
||||
'Description': f"Mount left4dead2 server {name} overlay",
|
||||
'Conflicts': {'umount.target'},
|
||||
'Before': {'umount.target'},
|
||||
},
|
||||
'Mount': {
|
||||
'What': 'overlay',
|
||||
'Where': mountpoint,
|
||||
'Type': 'overlay',
|
||||
'Options': ','.join([
|
||||
'auto',
|
||||
'lowerdir=/opt/steam/left4dead2',
|
||||
f'upperdir=/opt/steam-zfs-overlay-workarounds/{name}/upper',
|
||||
f'workdir=/opt/steam-zfs-overlay-workarounds/{name}/workdir',
|
||||
]),
|
||||
},
|
||||
'Install': {
|
||||
'RequiredBy': {
|
||||
f'left4dead2-{name}.service',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
# individual workshop
|
||||
workshop_ids = config.get('workshop', set()) | metadata.get('left4dead2/workshop', set())
|
||||
if workshop_ids:
|
||||
workshop[f'left4dead2-{name}'] = {
|
||||
'ids': workshop_ids,
|
||||
'path': f'/opt/steam/left4dead2-servers/{name}/left4dead2/addons',
|
||||
'user': 'steam',
|
||||
'requires': {
|
||||
mount_unit_name,
|
||||
},
|
||||
'required_by': {
|
||||
f'left4dead2-{name}.service',
|
||||
},
|
||||
}
|
||||
|
||||
# left4dead2 server unit
|
||||
units[f'left4dead2-{name}.service'] = {
|
||||
'Unit': {
|
||||
'Description': f'left4dead2 server {name}',
|
||||
'After': {'left4dead2-initialize.service'},
|
||||
'Requires': {'left4dead2-initialize.service'},
|
||||
'After': {'steam-update.service'},
|
||||
'Requires': {'steam-update.service'},
|
||||
},
|
||||
'Service': {
|
||||
'Type': 'simple',
|
||||
'ExecStart': f'/opt/l4d2/start {name} {config["overlay"]} {config["port"]}',
|
||||
'User': 'steam',
|
||||
'Group': 'steam',
|
||||
'WorkingDirectory': f'/opt/steam/left4dead2-servers/{name}',
|
||||
'ExecStart': f'/opt/steam/left4dead2-servers/{name}/srcds_run -port {config["port"]} +exec server.cfg',
|
||||
'Restart': 'on-failure',
|
||||
'Nice': -10,
|
||||
'CPUWeight': 200,
|
||||
'IOSchedulingClass': 'best-effort',
|
||||
'IOSchedulingPriority': 0,
|
||||
},
|
||||
'Install': {
|
||||
'WantedBy': {'multi-user.target'},
|
||||
|
@ -70,6 +104,7 @@ def server_units(metadata):
|
|||
}
|
||||
|
||||
return {
|
||||
'steam-workshop-download': workshop,
|
||||
'systemd': {
|
||||
'units': units,
|
||||
},
|
||||
|
@ -79,13 +114,14 @@ def server_units(metadata):
|
|||
@metadata_reactor.provides(
|
||||
'nftables/input',
|
||||
)
|
||||
def nftables(metadata):
|
||||
ports = sorted(str(config["port"]) for config in metadata.get('left4dead2', {}).values())
|
||||
def firewall(metadata):
|
||||
ports = set(str(server['port']) for server in metadata.get('left4dead2/servers').values())
|
||||
|
||||
return {
|
||||
'nftables': {
|
||||
'input': {
|
||||
f'ip protocol {{ tcp, udp }} th dport {{ {", ".join(ports)} }} accept'
|
||||
f"tcp dport {{ {', '.join(sorted(ports))} }} accept",
|
||||
f"udp dport {{ {', '.join(sorted(ports))} }} accept",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -1,58 +0,0 @@
|
|||
https://developer.valvesoftware.com/wiki/List_of_L4D2_Cvars
|
||||
|
||||
Dead Center c1m1_hotel
|
||||
Dead Center c1m2_streets
|
||||
Dead Center c1m3_mall
|
||||
Dead Center c1m4_atrium
|
||||
Dark Carnival c2m1_highway
|
||||
Dark Carnival c2m2_fairgrounds
|
||||
Dark Carnival c2m3_coaster
|
||||
Dark Carnival c2m4_barns
|
||||
Dark Carnival c2m5_concert
|
||||
Swamp Fever c3m1_plankcountry
|
||||
Swamp Fever c3m2_swamp
|
||||
Swamp Fever c3m3_shantytown
|
||||
Swamp Fever c3m4_plantation
|
||||
Hard Rain c4m1_milltown_a
|
||||
Hard Rain c4m2_sugarmill_a
|
||||
Hard Rain c4m3_sugarmill_b
|
||||
Hard Rain c4m4_milltown_b
|
||||
Hard Rain c4m5_milltown_escape
|
||||
The Parish c5m1_waterfront_sndscape
|
||||
The Parish c5m1_waterfront
|
||||
The Parish c5m2_park
|
||||
The Parish c5m3_cemetery
|
||||
The Parish c5m4_quarter
|
||||
The Parish c5m5_bridge
|
||||
The Passing c6m1_riverbank
|
||||
The Passing c6m2_bedlam
|
||||
The Passing c6m3_port
|
||||
The Sacrifice c7m1_docks
|
||||
The Sacrifice c7m2_barge
|
||||
The Sacrifice c7m3_port
|
||||
No Mercy c8m1_apartment
|
||||
No Mercy c8m2_subway
|
||||
No Mercy c8m3_sewers
|
||||
No Mercy c8m4_interior
|
||||
No Mercy c8m5_rooftop
|
||||
Crash Course c9m1_alleys
|
||||
Crash Course c9m2_lots
|
||||
Death Toll c10m1_caves
|
||||
Death Toll c10m2_drainage
|
||||
Death Toll c10m3_ranchhouse
|
||||
Death Toll c10m4_mainstreet
|
||||
Death Toll c10m5_houseboat
|
||||
Dead Air c11m1_greenhouse
|
||||
Dead Air c11m2_offices
|
||||
Dead Air c11m3_garage
|
||||
Dead Air c11m4_terminal
|
||||
Dead Air c11m5_runway
|
||||
Blood Harvest c12m1_hilltop
|
||||
Blood Harvest c12m2_traintunnel
|
||||
Blood Harvest c12m3_bridge
|
||||
Blood Harvest c12m4_barn
|
||||
Blood Harvest c12m5_cornfield
|
||||
Cold Stream c13m1_alpinecreek
|
||||
Cold Stream c13m2_southpinestream
|
||||
Cold Stream c13m3_memorialbridge
|
||||
Cold Stream c13m4_cutthroatcreek
|
|
@ -1,122 +0,0 @@
|
|||
assert node.has_bundle('steam') and node.has_bundle('steam-workshop-download')
|
||||
|
||||
directories = {
|
||||
'/opt/steam/left4dead2-servers': {
|
||||
'owner': 'steam',
|
||||
'group': 'steam',
|
||||
'mode': '0755',
|
||||
'purge': True,
|
||||
},
|
||||
# Current zfs doesnt support zfs upperdir. The support was added in October 2022. Move upperdir - unused anyway -
|
||||
# to another dir. Also move workdir alongside it, as it has to be on same fs.
|
||||
'/opt/steam-zfs-overlay-workarounds': {
|
||||
'owner': 'steam',
|
||||
'group': 'steam',
|
||||
'mode': '0755',
|
||||
'purge': True,
|
||||
},
|
||||
}
|
||||
|
||||
# /opt/steam/steam/.steam/sdk32/steamclient.so: cannot open shared object file: No such file or directory
|
||||
symlinks = {
|
||||
'/opt/steam/steam/.steam/sdk32': {
|
||||
'target': '/opt/steam/steam/linux32',
|
||||
'owner': 'steam',
|
||||
'group': 'steam',
|
||||
}
|
||||
}
|
||||
|
||||
#
|
||||
# SERVERS
|
||||
#
|
||||
|
||||
for name, config in node.metadata.get('left4dead2/servers').items():
|
||||
|
||||
#overlay
|
||||
directories[f'/opt/steam/left4dead2-servers/{name}'] = {
|
||||
'owner': 'steam',
|
||||
'group': 'steam',
|
||||
}
|
||||
directories[f'/opt/steam-zfs-overlay-workarounds/{name}/upper'] = {
|
||||
'owner': 'steam',
|
||||
'group': 'steam',
|
||||
}
|
||||
directories[f'/opt/steam-zfs-overlay-workarounds/{name}/workdir'] = {
|
||||
'owner': 'steam',
|
||||
'group': 'steam',
|
||||
}
|
||||
|
||||
# conf
|
||||
files[f'/opt/steam/left4dead2-servers/{name}/left4dead2/cfg/server.cfg'] = {
|
||||
'content_type': 'mako',
|
||||
'source': 'server.cfg',
|
||||
'context': {
|
||||
'name': name,
|
||||
'steamgroups': node.metadata.get('left4dead2/steamgroups'),
|
||||
'rcon_password': config['rcon_password'],
|
||||
},
|
||||
'owner': 'steam',
|
||||
'group': 'steam',
|
||||
'triggers': [
|
||||
f'svc_systemd:left4dead2-{name}.service:restart',
|
||||
],
|
||||
}
|
||||
|
||||
# service
|
||||
svc_systemd[f'left4dead2-{name}.service'] = {
|
||||
'needs': [
|
||||
f'file:/opt/steam/left4dead2-servers/{name}/left4dead2/cfg/server.cfg',
|
||||
f'file:/usr/local/lib/systemd/system/left4dead2-{name}.service',
|
||||
],
|
||||
}
|
||||
|
||||
#
|
||||
# ADDONS
|
||||
#
|
||||
|
||||
# base
|
||||
files[f'/opt/steam/left4dead2-servers/{name}/left4dead2/addons/readme.txt'] = {
|
||||
'content_type': 'any',
|
||||
'owner': 'steam',
|
||||
'group': 'steam',
|
||||
}
|
||||
directories[f'/opt/steam/left4dead2-servers/{name}/left4dead2/addons'] = {
|
||||
'owner': 'steam',
|
||||
'group': 'steam',
|
||||
'purge': True,
|
||||
'triggers': [
|
||||
f'svc_systemd:left4dead2-{name}.service:restart',
|
||||
],
|
||||
}
|
||||
for id in [
|
||||
*config.get('workshop', []),
|
||||
*node.metadata.get('left4dead2/workshop'),
|
||||
]:
|
||||
files[f'/opt/steam/left4dead2-servers/{name}/left4dead2/addons/{id}.vpk'] = {
|
||||
'content_type': 'any',
|
||||
'owner': 'steam',
|
||||
'group': 'steam',
|
||||
'triggers': [
|
||||
f'svc_systemd:left4dead2-{name}.service:restart',
|
||||
],
|
||||
}
|
||||
|
||||
# admin system
|
||||
|
||||
directories[f'/opt/steam/left4dead2-servers/{name}/left4dead2/ems/admin system'] = {
|
||||
'owner': 'steam',
|
||||
'group': 'steam',
|
||||
'mode': '0755',
|
||||
'triggers': [
|
||||
f'svc_systemd:left4dead2-{name}.service:restart',
|
||||
],
|
||||
}
|
||||
files[f'/opt/steam/left4dead2-servers/{name}/left4dead2/ems/admin system/admins.txt'] = {
|
||||
'owner': 'steam',
|
||||
'group': 'steam',
|
||||
'mode': '0755',
|
||||
'content': '\n'.join(sorted(node.metadata.get('left4dead2/admins'))),
|
||||
'triggers': [
|
||||
f'svc_systemd:left4dead2-{name}.service:restart',
|
||||
],
|
||||
}
|
|
@ -1,127 +0,0 @@
|
|||
assert node.has_bundle('steam')
|
||||
|
||||
from shlex import quote
|
||||
|
||||
defaults = {
|
||||
'steam': {
|
||||
'games': {
|
||||
'left4dead2': 222860,
|
||||
},
|
||||
},
|
||||
'left4dead2': {
|
||||
'servers': {},
|
||||
'admins': set(),
|
||||
'workshop': set(),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@metadata_reactor.provides(
|
||||
'left4dead2/servers',
|
||||
)
|
||||
def rconn_password(metadata):
|
||||
# only works from localhost!
|
||||
return {
|
||||
'left4dead2': {
|
||||
'servers': {
|
||||
server: {
|
||||
'rcon_password': repo.vault.password_for(f'{node.name} left4dead2 {server} rcon', length=24),
|
||||
}
|
||||
for server in metadata.get('left4dead2/servers')
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@metadata_reactor.provides(
|
||||
'steam-workshop-download',
|
||||
'systemd/units',
|
||||
)
|
||||
def server_units(metadata):
|
||||
units = {}
|
||||
workshop = {}
|
||||
|
||||
for name, config in metadata.get('left4dead2/servers').items():
|
||||
# mount overlay
|
||||
mountpoint = f'/opt/steam/left4dead2-servers/{name}'
|
||||
mount_unit_name = mountpoint[1:].replace('-', '\\x2d').replace('/', '-') + '.mount'
|
||||
units[mount_unit_name] = {
|
||||
'Unit': {
|
||||
'Description': f"Mount left4dead2 server {name} overlay",
|
||||
'Conflicts': {'umount.target'},
|
||||
'Before': {'umount.target'},
|
||||
},
|
||||
'Mount': {
|
||||
'What': 'overlay',
|
||||
'Where': mountpoint,
|
||||
'Type': 'overlay',
|
||||
'Options': ','.join([
|
||||
'auto',
|
||||
'lowerdir=/opt/steam/left4dead2',
|
||||
f'upperdir=/opt/steam-zfs-overlay-workarounds/{name}/upper',
|
||||
f'workdir=/opt/steam-zfs-overlay-workarounds/{name}/workdir',
|
||||
]),
|
||||
},
|
||||
'Install': {
|
||||
'RequiredBy': {
|
||||
f'left4dead2-{name}.service',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
# individual workshop
|
||||
workshop_ids = config.get('workshop', set()) | metadata.get('left4dead2/workshop', set())
|
||||
if workshop_ids:
|
||||
workshop[f'left4dead2-{name}'] = {
|
||||
'ids': workshop_ids,
|
||||
'path': f'/opt/steam/left4dead2-servers/{name}/left4dead2/addons',
|
||||
'user': 'steam',
|
||||
'requires': {
|
||||
mount_unit_name,
|
||||
},
|
||||
'required_by': {
|
||||
f'left4dead2-{name}.service',
|
||||
},
|
||||
}
|
||||
|
||||
# left4dead2 server unit
|
||||
units[f'left4dead2-{name}.service'] = {
|
||||
'Unit': {
|
||||
'Description': f'left4dead2 server {name}',
|
||||
'After': {'steam-update.service'},
|
||||
'Requires': {'steam-update.service'},
|
||||
},
|
||||
'Service': {
|
||||
'User': 'steam',
|
||||
'Group': 'steam',
|
||||
'WorkingDirectory': f'/opt/steam/left4dead2-servers/{name}',
|
||||
'ExecStart': f'/opt/steam/left4dead2-servers/{name}/srcds_run -port {config["port"]} +exec server.cfg',
|
||||
'Restart': 'on-failure',
|
||||
},
|
||||
'Install': {
|
||||
'WantedBy': {'multi-user.target'},
|
||||
},
|
||||
}
|
||||
|
||||
return {
|
||||
'steam-workshop-download': workshop,
|
||||
'systemd': {
|
||||
'units': units,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@metadata_reactor.provides(
|
||||
'nftables/input',
|
||||
)
|
||||
def firewall(metadata):
|
||||
ports = set(str(server['port']) for server in metadata.get('left4dead2/servers').values())
|
||||
|
||||
return {
|
||||
'nftables': {
|
||||
'input': {
|
||||
f"tcp dport {{ {', '.join(sorted(ports))} }} accept",
|
||||
f"udp dport {{ {', '.join(sorted(ports))} }} accept",
|
||||
},
|
||||
},
|
||||
}
|
|
@ -1,97 +0,0 @@
|
|||
# https://github.com/SirPlease/L4D2-Competitive-Rework/blob/master/Dedicated%20Server%20Install%20Guide/README.md
|
||||
|
||||
getent passwd steam >/dev/null || useradd -M -d /opt/l4d2 -s /bin/bash steam
|
||||
mkdir -p /opt/l4d2 /tmp/dumps
|
||||
chown steam:steam /opt/l4d2 /tmp/dumps
|
||||
dpkg --add-architecture i386
|
||||
apt update
|
||||
DEBIAN_FRONTEND=noninteractive apt install -y libc6:i386 lib32z1
|
||||
|
||||
function steam() { sudo -Hiu steam $* }
|
||||
|
||||
# -- STEAM -- #
|
||||
|
||||
steam mkdir -p /opt/l4d2/steam
|
||||
test -f /opt/l4d2/steam/steamcmd_linux.tar.gz || \
|
||||
steam wget http://media.steampowered.com/installer/steamcmd_linux.tar.gz -P /opt/l4d2/steam
|
||||
test -f /opt/l4d2/steam/steamcmd.sh || \
|
||||
steam tar -xvzf /opt/l4d2/steam/steamcmd_linux.tar.gz -C /opt/l4d2/steam
|
||||
|
||||
# fix: /opt/l4d2/.steam/sdk32/steamclient.so: cannot open shared object file: No such file or directory
|
||||
steam mkdir -p /opt/l4d2/steam/.steam
|
||||
test -f /opt/l4d2/steam/.steam/sdk32/steamclient.so || \
|
||||
steam ln -s /opt/l4d2/steam/linux32 /opt/l4d2/steam/.steam/sdk32
|
||||
|
||||
# -- INSTALL -- #
|
||||
|
||||
# erst die windows deps zu installieren scheint ein workaround für x64 zu sein?
|
||||
steam mkdir -p /opt/l4d2/installation
|
||||
steam /opt/l4d2/steam/steamcmd.sh \
|
||||
+force_install_dir /opt/l4d2/installation \
|
||||
+login anonymous \
|
||||
+@sSteamCmdForcePlatformType windows \
|
||||
+app_update 222860 validate \
|
||||
+quit
|
||||
steam /opt/l4d2/steam/steamcmd.sh \
|
||||
+force_install_dir /opt/l4d2/installation \
|
||||
+login anonymous \
|
||||
+@sSteamCmdForcePlatformType linux \
|
||||
+app_update 222860 validate \
|
||||
+quit
|
||||
|
||||
# -- OVERLAYS -- #
|
||||
|
||||
steam mkdir -p /opt/l4d2/overlays
|
||||
|
||||
# workshop downloader
|
||||
steam wget -4 https://git.sublimity.de/cronekorkn/steam-workshop-downloader/raw/branch/master/steam-workshop-download -P /opt/l4d2
|
||||
steam chmod +x /opt/l4d2/steam-workshop-download
|
||||
|
||||
# -- OVERLAY PVE -- #
|
||||
|
||||
steam mkdir -p /opt/l4d2/overlays/pve
|
||||
|
||||
# admin system
|
||||
steam mkdir -p /opt/l4d2/overlays/pve/left4dead2/addons
|
||||
steam /opt/l4d2/steam-workshop-download 2524204971 --out /opt/l4d2/overlays/pve/left4dead2/addons
|
||||
steam mkdir -p "/opt/l4d2/overlays/pve/left4dead2/ems/admin system"
|
||||
echo "STEAM_1:0:12376499" | steam tee "/opt/l4d2/overlays/pve/left4dead2/ems/admin system/admins.txt"
|
||||
|
||||
# ions vocalizer
|
||||
steam /opt/l4d2/steam-workshop-download 698857882 --out /opt/l4d2/overlays/pve/left4dead2/addons
|
||||
|
||||
# -- OVERLAY ZONEMOD -- #
|
||||
|
||||
true
|
||||
|
||||
# -- SERVERS -- #
|
||||
|
||||
steam mkdir -p /opt/l4d2/servers
|
||||
|
||||
# -- SERVER PVE1 -- #
|
||||
|
||||
steam mkdir -p \
|
||||
/opt/l4d2/servers/pve1 \
|
||||
/opt/l4d2/servers/pve1/work \
|
||||
/opt/l4d2/servers/pve1/upper \
|
||||
/opt/l4d2/servers/pve1/merged
|
||||
|
||||
mount -t overlay overlay \
|
||||
-o lowerdir=/opt/l4d2/overlays/pve:/opt/l4d2/installation,upperdir=/opt/l4d2/servers/pve1/upper,workdir=/opt/l4d2/servers/pve1/work \
|
||||
/opt/l4d2/servers/pve1/merged
|
||||
|
||||
# run server
|
||||
steam cat <<'EOF' > /opt/l4d2/servers/pve1/merged/left4dead2/cfg/server.cfg
|
||||
hostname "CKNs Server"
|
||||
motd_enabled 0
|
||||
|
||||
sv_steamgroup "38347879"
|
||||
#sv_steamgroup_exclusive 0
|
||||
|
||||
sv_minrate 60000
|
||||
sv_maxrate 0
|
||||
net_splitpacket_maxrate 60000
|
||||
|
||||
sv_hibernate_when_empty 0
|
||||
EOF
|
||||
steam /opt/l4d2/servers/pve1/merged/srcds_run -game left4dead2 -ip 0.0.0.0 -port 27015 +map c1m1_hotel
|
|
@ -1,183 +0,0 @@
|
|||
from shlex import quote
|
||||
|
||||
|
||||
def steam_run(cmd):
|
||||
return f'su - steam -c {quote(cmd)}'
|
||||
|
||||
|
||||
users = {
|
||||
'steam': {
|
||||
'home': '/opt/steam',
|
||||
},
|
||||
}
|
||||
|
||||
directories = {
|
||||
'/opt/steam': {
|
||||
'owner': 'steam',
|
||||
'group': 'steam',
|
||||
},
|
||||
'/opt/steam/.steam': {
|
||||
'owner': 'steam',
|
||||
'group': 'steam',
|
||||
},
|
||||
'/opt/left4dead2': {
|
||||
'owner': 'steam',
|
||||
'group': 'steam',
|
||||
},
|
||||
'/opt/left4dead2/left4dead2/ems/admin system': {
|
||||
'owner': 'steam',
|
||||
'group': 'steam',
|
||||
},
|
||||
'/opt/left4dead2/left4dead2/addons': {
|
||||
'owner': 'steam',
|
||||
'group': 'steam',
|
||||
},
|
||||
'/tmp/dumps': {
|
||||
'owner': 'steam',
|
||||
'group': 'steam',
|
||||
'mode': '1770',
|
||||
},
|
||||
}
|
||||
|
||||
symlinks = {
|
||||
'/opt/steam/.steam/sdk32': {
|
||||
'target': '/opt/steam/linux32',
|
||||
'owner': 'steam',
|
||||
'group': 'steam',
|
||||
},
|
||||
}
|
||||
|
||||
files = {
|
||||
'/opt/steam-workshop-download': {
|
||||
'content_type': 'download',
|
||||
'source': 'https://git.sublimity.de/cronekorkn/steam-workshop-downloader/raw/branch/master/steam-workshop-download',
|
||||
'mode': '755',
|
||||
},
|
||||
'/opt/left4dead2/left4dead2/ems/admin system/admins.txt': {
|
||||
'unless': 'test -f /opt/left4dead2/left4dead2/ems/admin system/admins.txt',
|
||||
'content': 'STEAM_1:0:12376499',
|
||||
'owner': 'steam',
|
||||
'group': 'steam',
|
||||
},
|
||||
}
|
||||
|
||||
actions = {
|
||||
'dpkg_add_architecture': {
|
||||
'command': 'dpkg --add-architecture i386',
|
||||
'unless': 'dpkg --print-foreign-architectures | grep -q i386',
|
||||
'triggers': [
|
||||
'action:apt_update',
|
||||
],
|
||||
'needed_by': [
|
||||
'pkg_apt:libc6_i386',
|
||||
],
|
||||
},
|
||||
'download_steam': {
|
||||
'command': steam_run('wget http://media.steampowered.com/installer/steamcmd_linux.tar.gz -P /opt/steam'),
|
||||
'unless': steam_run('test -f /opt/steam/steamcmd_linux.tar.gz'),
|
||||
'needs': {
|
||||
'pkg_apt:libc6_i386',
|
||||
'directory:/opt/steam',
|
||||
}
|
||||
},
|
||||
'extract_steamcmd': {
|
||||
'command': steam_run('tar -xvzf /opt/steam/steamcmd_linux.tar.gz -C /opt/steam'),
|
||||
'unless': steam_run('test -f /opt/steam/steamcmd.sh'),
|
||||
'needs': {
|
||||
'action:download_steam',
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
for addon_id in [2524204971]:
|
||||
actions[f'download-left4dead2-addon-{addon_id}'] = {
|
||||
'command': steam_run(f'/opt/steam-workshop-download {addon_id} --out /opt/left4dead2/left4dead2/addons'),
|
||||
'unless': steam_run(f'test -f /opt/left4dead2/left4dead2/addons/{addon_id}.vpk'),
|
||||
'needs': {
|
||||
'directory:/opt/left4dead2/left4dead2/addons',
|
||||
},
|
||||
'needed_by': {
|
||||
'tag:left4dead2-servers',
|
||||
},
|
||||
}
|
||||
|
||||
svc_systemd = {
|
||||
'left4dead2-install.service': {
|
||||
'enabled': True,
|
||||
'running': False,
|
||||
'needs': {
|
||||
'file:/usr/local/lib/systemd/system/left4dead2-install.service',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for server_name, server_config in node.metadata.get('left4dead2/servers', {}).items():
|
||||
svc_systemd[f'left4dead2-{server_name}.service'] = {
|
||||
'enabled': True,
|
||||
'running': True,
|
||||
'tags': {
|
||||
'left4dead2-servers',
|
||||
},
|
||||
'needs': {
|
||||
'svc_systemd:left4dead2-install.service',
|
||||
f'file:/usr/local/lib/systemd/system/left4dead2-{server_name}.service',
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
# # https://github.com/SirPlease/L4D2-Competitive-Rework/blob/master/Dedicated%20Server%20Install%20Guide/README.md
|
||||
|
||||
# mkdir /opt/steam /tmp/dumps
|
||||
# useradd -M -d /opt/steam -s /bin/bash steam
|
||||
# chown steam:steam /opt/steam /tmp/dumps
|
||||
# dpkg --add-architecture i386
|
||||
# apt update
|
||||
# apt install libc6:i386 lib32z1
|
||||
# sudo su - steam -s /bin/bash
|
||||
|
||||
# #--------
|
||||
|
||||
# wget http://media.steampowered.com/installer/steamcmd_linux.tar.gz
|
||||
# tar -xvzf steamcmd_linux.tar.gz
|
||||
|
||||
# # fix: /opt/steam/.steam/sdk32/steamclient.so: cannot open shared object file: No such file or directory
|
||||
# mkdir /opt/steam/.steam && ln -s /opt/steam/linux32 /opt/steam/.steam/sdk32
|
||||
|
||||
# # erst die windows deps zu installieren scheint ein workaround für x64 zu sein?
|
||||
# ./steamcmd.sh \
|
||||
# +force_install_dir /opt/steam/left4dead2 \
|
||||
# +login anonymous \
|
||||
# +@sSteamCmdForcePlatformType windows \
|
||||
# +app_update 222860 validate \
|
||||
# +quit
|
||||
# ./steamcmd.sh \
|
||||
# +force_install_dir /opt/steam/left4dead2 \
|
||||
# +login anonymous \
|
||||
# +@sSteamCmdForcePlatformType linux \
|
||||
# +app_update 222860 validate \
|
||||
# +quit
|
||||
|
||||
# # download admin system
|
||||
# wget -4 https://git.sublimity.de/cronekorkn/steam-workshop-downloader/raw/branch/master/steam-workshop-download
|
||||
# chmod +x steam-workshop-download
|
||||
# ./steam-workshop-download 2524204971 --out /opt/steam/left4dead2/left4dead2/addons
|
||||
# mkdir -p "/opt/steam/left4dead2/left4dead2/ems/admin system"
|
||||
# echo "STEAM_1:0:12376499" > "/opt/steam/left4dead2/left4dead2/ems/admin system/admins.txt"
|
||||
|
||||
# /opt/steam/left4dead2/srcds_run -game left4dead2 -ip 0.0.0.0 -port 27015 +map c1m1_hotel
|
||||
|
||||
|
||||
# cat <<'EOF' > /opt/steam/left4dead2/left4dead2/cfg/server.cfg
|
||||
# hostname "CKNs Server"
|
||||
# motd_enabled 0
|
||||
|
||||
# sv_steamgroup "38347879"
|
||||
# #sv_steamgroup_exclusive 0
|
||||
|
||||
# sv_minrate 60000
|
||||
# sv_maxrate 0
|
||||
# net_splitpacket_maxrate 60000
|
||||
|
||||
# sv_hibernate_when_empty 0
|
||||
# EOF
|
|
@ -1,107 +0,0 @@
|
|||
from re import match
|
||||
|
||||
defaults = {
|
||||
'apt': {
|
||||
'packages': {
|
||||
'libc6_i386': {}, # installs libc6:i386
|
||||
'lib32z1': {},
|
||||
'unzip': {},
|
||||
},
|
||||
},
|
||||
'left4dead2': {
|
||||
'servers': {},
|
||||
},
|
||||
'nftables': {
|
||||
'input': {
|
||||
'udp dport { 27005, 27020 } accept',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@metadata_reactor.provides(
|
||||
'nftables/input',
|
||||
)
|
||||
def nftables(metadata):
|
||||
ports = sorted(str(config["port"]) for config in metadata.get('left4dead2/servers', {}).values())
|
||||
|
||||
return {
|
||||
'nftables': {
|
||||
'input': {
|
||||
f'ip protocol {{ tcp, udp }} th dport {{ {", ".join(ports)} }} accept'
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@metadata_reactor.provides(
|
||||
'systemd/units',
|
||||
)
|
||||
def initial_unit(metadata):
|
||||
install_command = (
|
||||
'/opt/steam/steamcmd.sh '
|
||||
'+force_install_dir /opt/left4dead2 '
|
||||
'+login anonymous '
|
||||
'+@sSteamCmdForcePlatformType {platform} '
|
||||
'+app_update 222860 validate '
|
||||
'+quit '
|
||||
)
|
||||
|
||||
return {
|
||||
'systemd': {
|
||||
'units': {
|
||||
'left4dead2-install.service': {
|
||||
'Unit': {
|
||||
'Description': 'install or update left4dead2',
|
||||
'After': 'network-online.target',
|
||||
},
|
||||
'Service': {
|
||||
'Type': 'oneshot',
|
||||
'RemainAfterExit': 'yes',
|
||||
'User': 'steam',
|
||||
'Group': 'steam',
|
||||
'WorkingDirectory': '/opt/steam',
|
||||
'ExecStartPre': install_command.format(platform='windows'),
|
||||
'ExecStart': install_command.format(platform='linux'),
|
||||
},
|
||||
'Install': {
|
||||
'WantedBy': {'multi-user.target'},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@metadata_reactor.provides(
|
||||
'systemd/units',
|
||||
)
|
||||
def server_units(metadata):
|
||||
units = {}
|
||||
|
||||
for name, config in metadata.get('left4dead2/servers').items():
|
||||
assert match(r'^[A-z0-9-_-]+$', name)
|
||||
|
||||
units[f'left4dead2-{name}.service'] = {
|
||||
'Unit': {
|
||||
'Description': f'left4dead2 server {name}',
|
||||
'After': {'left4dead2-install.service'},
|
||||
'Requires': {'left4dead2-install.service'},
|
||||
},
|
||||
'Service': {
|
||||
'User': 'steam',
|
||||
'Group': 'steam',
|
||||
'WorkingDirectory': '/opt/left4dead2',
|
||||
'ExecStart': f'/opt/left4dead2/srcds_run -port {config["port"]} +exec server_{name}.cfg',
|
||||
'Restart': 'on-failure',
|
||||
},
|
||||
'Install': {
|
||||
'WantedBy': {'multi-user.target'},
|
||||
},
|
||||
}
|
||||
|
||||
return {
|
||||
'systemd': {
|
||||
'units': units,
|
||||
},
|
||||
}
|
|
@ -5,5 +5,5 @@ printf "server 127.0.0.1
|
|||
zone acme.resolver.name.
|
||||
update add _acme-challenge.ckn.li.acme.resolver.name. 600 IN TXT "hello"
|
||||
send
|
||||
" | nsupdate -y hmac-sha512:acme:XXXXXX
|
||||
" | nsupdate -y hmac-sha512:acme:Y9BHl85l352BGZDXa/vg90hh2+5PYe4oJxpkq/oQvIODDkW8bAyQSFr0gKQQxjyIOyYlTjf0MGcdWFv46G/3Rg==
|
||||
```
|
||||
|
|
|
@ -31,12 +31,6 @@ deploy_cert() {
|
|||
% for domain, conf in sorted(domains.items()):
|
||||
<% if not conf: continue %>\
|
||||
${domain})
|
||||
% if conf.get('scp', None):
|
||||
scp "$KEYFILE" "${conf['scp']}/${conf.get('privkey_name', 'privkey.pem')}"
|
||||
scp "$CERTFILE" "${conf['scp']}/${conf.get('cert_name', 'cert.pem')}"
|
||||
scp "$FULLCHAINFILE" "${conf['scp']}/${conf.get('fullchain_name', 'fullchain.pem')}"
|
||||
scp "$CHAINFILE" "${conf['scp']}/${conf.get('chain_name', 'chain.pem')}"
|
||||
% endif
|
||||
% if conf.get('location', None):
|
||||
cat "$KEYFILE" > "${conf['location']}/${conf.get('privkey_name', 'privkey.pem')}"
|
||||
cat "$CERTFILE" > "${conf['location']}/${conf.get('cert_name', 'cert.pem')}"
|
||||
|
|
|
@ -42,7 +42,7 @@ files = {
|
|||
}
|
||||
|
||||
actions['letsencrypt_update_certificates'] = {
|
||||
'command': 'systemctl start letsencrypt.service',
|
||||
'command': 'dehydrated --cron --accept-terms --challenge dns-01',
|
||||
'triggered': True,
|
||||
'skip': delegated,
|
||||
'needs': {
|
||||
|
@ -56,7 +56,6 @@ for domain in node.metadata.get('letsencrypt/domains').keys():
|
|||
'unless': f'/etc/dehydrated/letsencrypt-ensure-some-certificate {domain} true',
|
||||
'needs': {
|
||||
'file:/etc/dehydrated/letsencrypt-ensure-some-certificate',
|
||||
'pkg_apt:dehydrated',
|
||||
},
|
||||
'needed_by': {
|
||||
'svc_systemd:nginx',
|
||||
|
|
|
@ -1,43 +0,0 @@
|
|||
from shlex import quote
|
||||
|
||||
def generate_sysctl_key_value_pairs_from_json(json_data, parents=[]):
|
||||
if isinstance(json_data, dict):
|
||||
for key, value in json_data.items():
|
||||
yield from generate_sysctl_key_value_pairs_from_json(value, [*parents, key])
|
||||
elif isinstance(json_data, list):
|
||||
raise ValueError(f"List not supported: '{json_data}'")
|
||||
else:
|
||||
# If it's a leaf node, yield the path
|
||||
yield (parents, json_data)
|
||||
|
||||
key_value_pairs = generate_sysctl_key_value_pairs_from_json(node.metadata.get('sysctl'))
|
||||
|
||||
files= {
|
||||
'/etc/sysctl.d/managed.conf': {
|
||||
'content': '\n'.join(
|
||||
sorted(
|
||||
f"{'.'.join(path)}={value}"
|
||||
for path, value in key_value_pairs
|
||||
),
|
||||
),
|
||||
'triggers': [
|
||||
'svc_systemd:systemd-sysctl.service:restart',
|
||||
],
|
||||
},
|
||||
'/etc/modules-load.d/managed.conf': {
|
||||
'content': '\n'.join(sorted(node.metadata.get('modules-load'))),
|
||||
}
|
||||
}
|
||||
|
||||
svc_systemd = {
|
||||
'systemd-sysctl.service': {},
|
||||
}
|
||||
|
||||
for path, value in key_value_pairs:
|
||||
actions[f'reload_sysctl.conf_{path}'] = {
|
||||
'command': f"sysctl --values {'.'.join(path)} | grep -q {quote('^'+value+'$')}",
|
||||
'needs': [
|
||||
f'action:systemd-sysctl.service',
|
||||
f'action:systemd-sysctl.service:restart',
|
||||
],
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
defaults = {
|
||||
'sysctl': {},
|
||||
'modules-load': set(),
|
||||
}
|
|
@ -20,19 +20,18 @@ files = {
|
|||
}
|
||||
|
||||
actions = {
|
||||
'systemd-locale': {
|
||||
'command': f'localectl set-locale LANG="{default_locale}"',
|
||||
'unless': f'localectl | grep -Fi "system locale" | grep -Fi "{default_locale}"',
|
||||
'triggers': {
|
||||
'action:locale-gen',
|
||||
},
|
||||
},
|
||||
'locale-gen': {
|
||||
'command': 'locale-gen',
|
||||
'triggered': True,
|
||||
'needs': {
|
||||
'pkg_apt:locales',
|
||||
'action:systemd-locale',
|
||||
},
|
||||
},
|
||||
'systemd-locale': {
|
||||
'command': f'localectl set-locale LANG="{default_locale}"',
|
||||
'unless': f'localectl | grep -Fi "system locale" | grep -Fi "{default_locale}"',
|
||||
'preceded_by': {
|
||||
'action:locale-gen',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -7,7 +7,12 @@ defaults = {
|
|||
'locale': {
|
||||
'default': ('en_US.UTF-8', 'UTF-8'),
|
||||
'installed': {
|
||||
('de_AT.UTF-8', 'UTF-8'),
|
||||
('de_CH.UTF-8', 'UTF-8'),
|
||||
('de_DE.UTF-8', 'UTF-8'),
|
||||
('de_LU.UTF-8', 'UTF-8'),
|
||||
('en_CA.UTF-8', 'UTF-8'),
|
||||
('en_GB.UTF-8', 'UTF-8'),
|
||||
('en_US.UTF-8', 'UTF-8'),
|
||||
},
|
||||
},
|
||||
|
|
|
@ -2,5 +2,5 @@
|
|||
|
||||
cd "$OLDPWD"
|
||||
|
||||
export BW_ITEM_WORKERS=$(expr "$(sysctl -n hw.logicalcpu)" '*' 12 '/' 10)
|
||||
export BW_ITEM_WORKERS=$(expr "$(nproc)" '*' 12 '/' 10)
|
||||
export BW_NODE_WORKERS=$(expr 320 '/' "$BW_ITEM_WORKERS")
|
||||
|
|
|
@ -2,5 +2,7 @@
|
|||
|
||||
cd "$OLDPWD"
|
||||
|
||||
PATH_add "/opt/homebrew/opt/gnu-sed/libexec/gnubin"
|
||||
PATH_add "/opt/homebrew/opt/grep/libexec/gnubin"
|
||||
GNU_PATH="$HOME/.local/gnu_bin"
|
||||
mkdir -p "$GNU_PATH"
|
||||
test -f "$GNU_PATH/sed" || ln -s "$(which gsed)" "$GNU_PATH/sed"
|
||||
PATH_add "$GNU_PATH"
|
||||
|
|
|
@ -18,7 +18,7 @@ git -C ~/.zsh/oh-my-zsh pull
|
|||
brew upgrade
|
||||
brew upgrade --cask --greedy
|
||||
|
||||
pyenv install --skip-existing
|
||||
pyenv install --keep-existing
|
||||
|
||||
sudo softwareupdate -ia --verbose
|
||||
|
||||
|
|
|
@ -2,8 +2,6 @@
|
|||
|
||||
cd "$OLDPWD"
|
||||
|
||||
pyenv install --skip-existing
|
||||
|
||||
if test -f .venv/bin/python && test "$(realpath .venv/bin/python)" != "$(realpath "$(pyenv which python)")"
|
||||
then
|
||||
echo "rebuilding venv für new python version"
|
||||
|
|
|
@ -19,9 +19,5 @@ if test "$DELTA" -gt 86400
|
|||
then
|
||||
python3 -m pip --require-virtualenv install pip wheel --upgrade
|
||||
python3 -m pip --require-virtualenv install -r requirements.txt --upgrade
|
||||
if test -e optional-requirements.txt
|
||||
then
|
||||
python3 -m pip --require-virtualenv install -r optional-requirements.txt --upgrade
|
||||
fi
|
||||
date +%s > .pip_upgrade_timestamp
|
||||
fi
|
||||
|
|
|
@ -1,26 +0,0 @@
|
|||
# Mailman
|
||||
|
||||
- django admin udner /admin
|
||||
|
||||
## Testmail
|
||||
|
||||
`echo export REST_API_PASS=$(bw metadata mseibert.mailman -k mailman/api_password | jq -r .mailman.api_password)`
|
||||
```sh
|
||||
curl -s -o /dev/null \
|
||||
-w "Status: %{http_code}\nTime: %{time_total}s\n" \
|
||||
-u restadmin:$REST_API_PASS \
|
||||
-H "Content-Type: application/json" \
|
||||
-X POST http://localhost:8001/3.1/queues/in \
|
||||
-d "{
|
||||
\"list_id\": \"testlist-2.mailman.ckn.li\",
|
||||
\"text\": \"From: i@ckn.li\nTo: testlist-2@mailman.ckn.li\nSubject: Curl Test $(date '+%Y-%m-%d %H:%M:%S')\n\nThis message was sent at $(date '+%Y-%m-%d %H:%M:%S').\"
|
||||
}"
|
||||
```
|
||||
|
||||
## Log locations
|
||||
|
||||
`tail -f /var/log/mailman3/*.log`
|
||||
|
||||
`journalctl -f | grep postfix/`
|
||||
|
||||
`mailq | head -20`
|
|
@ -1,22 +0,0 @@
|
|||
# This is the mailman extension configuration file to enable HyperKitty as an
|
||||
# archiver. Remember to add the following lines in the mailman.cfg file:
|
||||
#
|
||||
# [archiver.hyperkitty]
|
||||
# class: mailman_hyperkitty.Archiver
|
||||
# enable: yes
|
||||
# configuration: /etc/mailman3/mailman-hyperkitty.cfg
|
||||
#
|
||||
|
||||
[general]
|
||||
|
||||
# This is your HyperKitty installation, preferably on the localhost. This
|
||||
# address will be used by Mailman to forward incoming emails to HyperKitty
|
||||
# for archiving. It does not need to be publicly available, in fact it's
|
||||
# better if it is not.
|
||||
# However, if your Mailman installation is accessed via HTTPS, the URL needs
|
||||
# to match your SSL certificate (e.g. https://lists.example.com/hyperkitty).
|
||||
base_url: http://${hostname}/mailman3/hyperkitty/
|
||||
|
||||
# The shared api_key, must be identical except for quoting to the value of
|
||||
# MAILMAN_ARCHIVER_KEY in HyperKitty's settings.
|
||||
api_key: ${archiver_key}
|
|
@ -1,190 +0,0 @@
|
|||
ACCOUNT_EMAIL_VERIFICATION='none'
|
||||
|
||||
# This file is imported by the Mailman Suite. It is used to override
|
||||
# the default settings from /usr/share/mailman3-web/settings.py.
|
||||
|
||||
# SECURITY WARNING: keep the secret key used in production secret!
|
||||
SECRET_KEY = '${secret_key}'
|
||||
|
||||
ADMINS = (
|
||||
('Mailman Suite Admin', 'root@localhost'),
|
||||
)
|
||||
|
||||
# Hosts/domain names that are valid for this site; required if DEBUG is False
|
||||
# See https://docs.djangoproject.com/en/1.8/ref/settings/#allowed-hosts
|
||||
# Set to '*' per default in the Deian package to allow all hostnames. Mailman3
|
||||
# is meant to run behind a webserver reverse proxy anyway.
|
||||
ALLOWED_HOSTS = [
|
||||
'${hostname}',
|
||||
]
|
||||
|
||||
# Mailman API credentials
|
||||
MAILMAN_REST_API_URL = 'http://localhost:8001'
|
||||
MAILMAN_REST_API_USER = 'restadmin'
|
||||
MAILMAN_REST_API_PASS = '${api_password}'
|
||||
MAILMAN_ARCHIVER_KEY = '${archiver_key}'
|
||||
MAILMAN_ARCHIVER_FROM = ('127.0.0.1', '::1')
|
||||
|
||||
# Application definition
|
||||
|
||||
INSTALLED_APPS = (
|
||||
'hyperkitty',
|
||||
'postorius',
|
||||
'django_mailman3',
|
||||
# Uncomment the next line to enable the admin:
|
||||
'django.contrib.admin',
|
||||
# Uncomment the next line to enable admin documentation:
|
||||
# 'django.contrib.admindocs',
|
||||
'django.contrib.auth',
|
||||
'django.contrib.contenttypes',
|
||||
'django.contrib.sessions',
|
||||
'django.contrib.sites',
|
||||
'django.contrib.messages',
|
||||
'django.contrib.staticfiles',
|
||||
'rest_framework',
|
||||
'django_gravatar',
|
||||
'compressor',
|
||||
'haystack',
|
||||
'django_extensions',
|
||||
'django_q',
|
||||
'allauth',
|
||||
'allauth.account',
|
||||
'allauth.socialaccount',
|
||||
'django_mailman3.lib.auth.fedora',
|
||||
#'allauth.socialaccount.providers.openid',
|
||||
#'allauth.socialaccount.providers.github',
|
||||
#'allauth.socialaccount.providers.gitlab',
|
||||
#'allauth.socialaccount.providers.google',
|
||||
#'allauth.socialaccount.providers.facebook',
|
||||
#'allauth.socialaccount.providers.twitter',
|
||||
#'allauth.socialaccount.providers.stackexchange',
|
||||
)
|
||||
|
||||
|
||||
# Database
|
||||
# https://docs.djangoproject.com/en/1.8/ref/settings/#databases
|
||||
|
||||
DATABASES = {
|
||||
'default': {
|
||||
# Use 'sqlite3', 'postgresql_psycopg2', 'mysql', 'sqlite3' or 'oracle'.
|
||||
#'ENGINE': 'django.db.backends.sqlite3',
|
||||
'ENGINE': 'django.db.backends.postgresql_psycopg2',
|
||||
#'ENGINE': 'django.db.backends.mysql',
|
||||
# DB name or path to database file if using sqlite3.
|
||||
#'NAME': '/var/lib/mailman3/web/mailman3web.db',
|
||||
'NAME': 'mailman',
|
||||
# The following settings are not used with sqlite3:
|
||||
'USER': 'mailman',
|
||||
'PASSWORD': '${db_password}',
|
||||
# HOST: empty for localhost through domain sockets or '127.0.0.1' for
|
||||
# localhost through TCP.
|
||||
'HOST': '127.0.0.1',
|
||||
# PORT: set to empty string for default.
|
||||
'PORT': '5432',
|
||||
# OPTIONS: Extra parameters to use when connecting to the database.
|
||||
'OPTIONS': {
|
||||
# Set sql_mode to 'STRICT_TRANS_TABLES' for MySQL. See
|
||||
# https://docs.djangoproject.com/en/1.11/ref/
|
||||
# databases/#setting-sql-mode
|
||||
#'init_command': "SET sql_mode='STRICT_TRANS_TABLES'",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# If you're behind a proxy, use the X-Forwarded-Host header
|
||||
# See https://docs.djangoproject.com/en/1.8/ref/settings/#use-x-forwarded-host
|
||||
USE_X_FORWARDED_HOST = True
|
||||
|
||||
# And if your proxy does your SSL encoding for you, set SECURE_PROXY_SSL_HEADER
|
||||
# https://docs.djangoproject.com/en/1.8/ref/settings/#secure-proxy-ssl-header
|
||||
# SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
|
||||
# SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_SCHEME', 'https')
|
||||
|
||||
# Other security settings
|
||||
# SECURE_SSL_REDIRECT = True
|
||||
# If you set SECURE_SSL_REDIRECT to True, make sure the SECURE_REDIRECT_EXEMPT
|
||||
# contains at least this line:
|
||||
# SECURE_REDIRECT_EXEMPT = [
|
||||
# "archives/api/mailman/.*", # Request from Mailman.
|
||||
# ]
|
||||
# SESSION_COOKIE_SECURE = True
|
||||
# SECURE_CONTENT_TYPE_NOSNIFF = True
|
||||
# SECURE_BROWSER_XSS_FILTER = True
|
||||
# CSRF_COOKIE_SECURE = True
|
||||
# CSRF_COOKIE_HTTPONLY = True
|
||||
# X_FRAME_OPTIONS = 'DENY'
|
||||
|
||||
|
||||
# Internationalization
|
||||
# https://docs.djangoproject.com/en/1.8/topics/i18n/
|
||||
|
||||
LANGUAGE_CODE = 'en-us'
|
||||
|
||||
TIME_ZONE = 'UTC'
|
||||
|
||||
USE_I18N = True
|
||||
USE_L10N = True
|
||||
USE_TZ = True
|
||||
|
||||
|
||||
# Set default domain for email addresses.
|
||||
EMAILNAME = 'localhost.local'
|
||||
|
||||
# If you enable internal authentication, this is the address that the emails
|
||||
# will appear to be coming from. Make sure you set a valid domain name,
|
||||
# otherwise the emails may get rejected.
|
||||
# https://docs.djangoproject.com/en/1.8/ref/settings/#default-from-email
|
||||
# DEFAULT_FROM_EMAIL = "mailing-lists@you-domain.org"
|
||||
DEFAULT_FROM_EMAIL = 'postorius@{}'.format(EMAILNAME)
|
||||
|
||||
# If you enable email reporting for error messages, this is where those emails
|
||||
# will appear to be coming from. Make sure you set a valid domain name,
|
||||
# otherwise the emails may get rejected.
|
||||
# https://docs.djangoproject.com/en/1.8/ref/settings/#std:setting-SERVER_EMAIL
|
||||
# SERVER_EMAIL = 'root@your-domain.org'
|
||||
SERVER_EMAIL = 'root@{}'.format(EMAILNAME)
|
||||
|
||||
|
||||
# Django Allauth
|
||||
ACCOUNT_DEFAULT_HTTP_PROTOCOL = "https"
|
||||
|
||||
|
||||
#
|
||||
# Social auth
|
||||
#
|
||||
SOCIALACCOUNT_PROVIDERS = {
|
||||
#'openid': {
|
||||
# 'SERVERS': [
|
||||
# dict(id='yahoo',
|
||||
# name='Yahoo',
|
||||
# openid_url='http://me.yahoo.com'),
|
||||
# ],
|
||||
#},
|
||||
#'google': {
|
||||
# 'SCOPE': ['profile', 'email'],
|
||||
# 'AUTH_PARAMS': {'access_type': 'online'},
|
||||
#},
|
||||
#'facebook': {
|
||||
# 'METHOD': 'oauth2',
|
||||
# 'SCOPE': ['email'],
|
||||
# 'FIELDS': [
|
||||
# 'email',
|
||||
# 'name',
|
||||
# 'first_name',
|
||||
# 'last_name',
|
||||
# 'locale',
|
||||
# 'timezone',
|
||||
# ],
|
||||
# 'VERSION': 'v2.4',
|
||||
#},
|
||||
}
|
||||
|
||||
# On a production setup, setting COMPRESS_OFFLINE to True will bring a
|
||||
# significant performance improvement, as CSS files will not need to be
|
||||
# recompiled on each requests. It means running an additional "compress"
|
||||
# management command after each code upgrade.
|
||||
# http://django-compressor.readthedocs.io/en/latest/usage/#offline-compression
|
||||
COMPRESS_OFFLINE = True
|
||||
|
||||
POSTORIUS_TEMPLATE_BASE_URL = 'http://${hostname}/mailman3/'
|
|
@ -1,271 +0,0 @@
|
|||
# Copyright (C) 2008-2017 by the Free Software Foundation, Inc.
|
||||
#
|
||||
# This file is part of GNU Mailman.
|
||||
#
|
||||
# GNU Mailman is free software: you can redistribute it and/or modify it under
|
||||
# the terms of the GNU General Public License as published by the Free
|
||||
# Software Foundation, either version 3 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# GNU Mailman is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
# more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along with
|
||||
# GNU Mailman. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
# This file contains the Debian configuration for mailman. It uses ini-style
|
||||
# formats under the lazr.config regime to define all system configuration
|
||||
# options. See <https://launchpad.net/lazr.config> for details.
|
||||
|
||||
|
||||
[mailman]
|
||||
# This address is the "site owner" address. Certain messages which must be
|
||||
# delivered to a human, but which can't be delivered to a list owner (e.g. a
|
||||
# bounce from a list owner), will be sent to this address. It should point to
|
||||
# a human.
|
||||
site_owner: ${site_owner_email}
|
||||
|
||||
# This is the local-part of an email address used in the From field whenever a
|
||||
# message comes from some entity to which there is no natural reply recipient.
|
||||
# Mailman will append '@' and the host name of the list involved. This
|
||||
# address must not bounce and it must not point to a Mailman process.
|
||||
noreply_address: noreply
|
||||
|
||||
# The default language for this server.
|
||||
default_language: de
|
||||
|
||||
# Membership tests for posting purposes are usually performed by looking at a
|
||||
# set of headers, passing the test if any of their values match a member of
|
||||
# the list. Headers are checked in the order given in this variable. The
|
||||
# value From_ means to use the envelope sender. Field names are case
|
||||
# insensitive. This is a space separate list of headers.
|
||||
sender_headers: from from_ reply-to sender
|
||||
|
||||
# Mail command processor will ignore mail command lines after designated max.
|
||||
email_commands_max_lines: 10
|
||||
|
||||
# Default length of time a pending request is live before it is evicted from
|
||||
# the pending database.
|
||||
pending_request_life: 3d
|
||||
|
||||
# How long should files be saved before they are evicted from the cache?
|
||||
cache_life: 7d
|
||||
|
||||
# A callable to run with no arguments early in the initialization process.
|
||||
# This runs before database initialization.
|
||||
pre_hook:
|
||||
|
||||
# A callable to run with no arguments late in the initialization process.
|
||||
# This runs after adapters are initialized.
|
||||
post_hook:
|
||||
|
||||
# Which paths.* file system layout to use.
|
||||
# You should not change this variable.
|
||||
layout: debian
|
||||
|
||||
# Can MIME filtered messages be preserved by list owners?
|
||||
filtered_messages_are_preservable: no
|
||||
|
||||
# How should text/html parts be converted to text/plain when the mailing list
|
||||
# is set to convert HTML to plaintext? This names a command to be called,
|
||||
# where the substitution variable $filename is filled in by Mailman, and
|
||||
# contains the path to the temporary file that the command should read from.
|
||||
# The command should print the converted text to stdout.
|
||||
html_to_plain_text_command: /usr/bin/lynx -dump $filename
|
||||
|
||||
# Specify what characters are allowed in list names. Characters outside of
|
||||
# the class [-_.+=!$*{}~0-9a-z] matched case insensitively are never allowed,
|
||||
# but this specifies a subset as the only allowable characters. This must be
|
||||
# a valid character class regexp or the effect on list creation is
|
||||
# unpredictable.
|
||||
listname_chars: [-_.0-9a-z]
|
||||
|
||||
|
||||
[shell]
|
||||
# `mailman shell` (also `withlist`) gives you an interactive prompt that you
|
||||
# can use to interact with an initialized and configured Mailman system. Use
|
||||
# --help for more information. This section allows you to configure certain
|
||||
# aspects of this interactive shell.
|
||||
|
||||
# Customize the interpreter prompt.
|
||||
prompt: >>>
|
||||
|
||||
# Banner to show on startup.
|
||||
banner: Welcome to the GNU Mailman shell
|
||||
|
||||
# Use IPython as the shell, which must be found on the system. Valid values
|
||||
# are `no`, `yes`, and `debug` where the latter is equivalent to `yes` except
|
||||
# that any import errors will be displayed to stderr.
|
||||
use_ipython: no
|
||||
|
||||
# Set this to allow for command line history if readline is available. This
|
||||
# can be as simple as $var_dir/history.py to put the file in the var directory.
|
||||
history_file:
|
||||
|
||||
|
||||
[paths.debian]
|
||||
# Important directories for Mailman operation. These are defined here so that
|
||||
# different layouts can be supported. For example, a developer layout would
|
||||
# be different from a FHS layout. Most paths are based off the var_dir, and
|
||||
# often just setting that will do the right thing for all the other paths.
|
||||
# You might also have to set spool_dir though.
|
||||
#
|
||||
# Substitutions are allowed, but must be of the form $var where 'var' names a
|
||||
# configuration variable in the paths.* section. Substitutions are expanded
|
||||
# recursively until no more $-variables are present. Beware of infinite
|
||||
# expansion loops!
|
||||
#
|
||||
# This is the root of the directory structure that Mailman will use to store
|
||||
# its run-time data.
|
||||
var_dir: /var/lib/mailman3
|
||||
# This is where the Mailman queue files directories will be created.
|
||||
queue_dir: $var_dir/queue
|
||||
# This is the directory containing the Mailman 'runner' and 'master' commands
|
||||
# if set to the string '$argv', it will be taken as the directory containing
|
||||
# the 'mailman' command.
|
||||
bin_dir: /usr/lib/mailman3/bin
|
||||
# All list-specific data.
|
||||
list_data_dir: $var_dir/lists
|
||||
# Directory where log files go.
|
||||
log_dir: /var/log/mailman3
|
||||
# Directory for system-wide locks.
|
||||
lock_dir: $var_dir/locks
|
||||
# Directory for system-wide data.
|
||||
data_dir: $var_dir/data
|
||||
# Cache files.
|
||||
cache_dir: $var_dir/cache
|
||||
# Directory for configuration files and such.
|
||||
etc_dir: /etc/mailman3
|
||||
# Directory containing Mailman plugins.
|
||||
ext_dir: $var_dir/ext
|
||||
# Directory where the default IMessageStore puts its messages.
|
||||
messages_dir: $var_dir/messages
|
||||
# Directory for archive backends to store their messages in. Archivers should
|
||||
# create a subdirectory in here to store their files.
|
||||
archive_dir: $var_dir/archives
|
||||
# Root directory for site-specific template override files.
|
||||
template_dir: $var_dir/templates
|
||||
# There are also a number of paths to specific file locations that can be
|
||||
# defined. For these, the directory containing the file must already exist,
|
||||
# or be one of the directories created by Mailman as per above.
|
||||
#
|
||||
# This is where PID file for the master runner is stored.
|
||||
pid_file: /run/mailman3/master.pid
|
||||
# Lock file.
|
||||
lock_file: $lock_dir/master.lck
|
||||
|
||||
|
||||
[database]
|
||||
# The class implementing the IDatabase.
|
||||
class: mailman.database.sqlite.SQLiteDatabase
|
||||
#class: mailman.database.mysql.MySQLDatabase
|
||||
#class: mailman.database.postgresql.PostgreSQLDatabase
|
||||
|
||||
# Use this to set the Storm database engine URL. You generally have one
|
||||
# primary database connection for all of Mailman. List data and most rosters
|
||||
# will store their data in this database, although external rosters may access
|
||||
# other databases in their own way. This string supports standard
|
||||
# 'configuration' substitutions.
|
||||
url: sqlite:///$DATA_DIR/mailman.db
|
||||
#url: mysql+pymysql://mailman3:mmpass@localhost/mailman3?charset=utf8&use_unicode=1
|
||||
#url: postgresql://mailman3:mmpass@localhost/mailman3
|
||||
|
||||
debug: no
|
||||
|
||||
|
||||
[logging.debian]
|
||||
# This defines various log settings. The options available are:
|
||||
#
|
||||
# - level -- Overrides the default level; this may be any of the
|
||||
# standard Python logging levels, case insensitive.
|
||||
# - format -- Overrides the default format string
|
||||
# - datefmt -- Overrides the default date format string
|
||||
# - path -- Overrides the default logger path. This may be a relative
|
||||
# path name, in which case it is relative to Mailman's LOG_DIR,
|
||||
# or it may be an absolute path name. You cannot change the
|
||||
# handler class that will be used.
|
||||
# - propagate -- Boolean specifying whether to propagate log message from this
|
||||
# logger to the root "mailman" logger. You cannot override
|
||||
# settings for the root logger.
|
||||
#
|
||||
# In this section, you can define defaults for all loggers, which will be
|
||||
# prefixed by 'mailman.'. Use subsections to override settings for specific
|
||||
# loggers. The names of the available loggers are:
|
||||
#
|
||||
# - archiver -- All archiver output
|
||||
# - bounce -- All bounce processing logs go here
|
||||
# - config -- Configuration issues
|
||||
# - database -- Database logging (SQLAlchemy and Alembic)
|
||||
# - debug -- Only used for development
|
||||
# - error -- All exceptions go to this log
|
||||
# - fromusenet -- Information related to the Usenet to Mailman gateway
|
||||
# - http -- Internal wsgi-based web interface
|
||||
# - locks -- Lock state changes
|
||||
# - mischief -- Various types of hostile activity
|
||||
# - runner -- Runner process start/stops
|
||||
# - smtp -- Successful SMTP activity
|
||||
# - smtp-failure -- Unsuccessful SMTP activity
|
||||
# - subscribe -- Information about leaves/joins
|
||||
# - vette -- Message vetting information
|
||||
format: %(asctime)s (%(process)d) %(message)s
|
||||
datefmt: %b %d %H:%M:%S %Y
|
||||
propagate: no
|
||||
level: info
|
||||
path: mailman.log
|
||||
|
||||
[webservice]
|
||||
# The hostname at which admin web service resources are exposed.
|
||||
hostname: localhost
|
||||
|
||||
# The port at which the admin web service resources are exposed.
|
||||
port: 8001
|
||||
|
||||
# Whether or not requests to the web service are secured through SSL.
|
||||
use_https: no
|
||||
|
||||
# Whether or not to show tracebacks in an HTTP response for a request that
|
||||
# raised an exception.
|
||||
show_tracebacks: yes
|
||||
|
||||
# The API version number for the current (highest) API.
|
||||
api_version: 3.1
|
||||
|
||||
# The administrative username.
|
||||
admin_user: restadmin
|
||||
|
||||
# The administrative password.
|
||||
admin_pass: ${api_password}
|
||||
|
||||
[mta]
|
||||
# The class defining the interface to the incoming mail transport agent.
|
||||
#incoming: mailman.mta.exim4.LMTP
|
||||
incoming: mailman.mta.postfix.LMTP
|
||||
|
||||
# The callable implementing delivery to the outgoing mail transport agent.
|
||||
# This must accept three arguments, the mailing list, the message, and the
|
||||
# message metadata dictionary.
|
||||
outgoing: mailman.mta.deliver.deliver
|
||||
|
||||
# How to connect to the outgoing MTA. If smtp_user and smtp_pass is given,
|
||||
# then Mailman will attempt to log into the MTA when making a new connection.
|
||||
smtp_host: 127.0.0.1
|
||||
smtp_port: 25
|
||||
smtp_user:
|
||||
smtp_pass:
|
||||
|
||||
# Where the LMTP server listens for connections. Use 127.0.0.1 instead of
|
||||
# localhost for Postfix integration, because Postfix only consults DNS
|
||||
# (e.g. not /etc/hosts).
|
||||
lmtp_host: 127.0.0.1
|
||||
lmtp_port: 8024
|
||||
|
||||
# Where can we find the mail server specific configuration file? The path can
|
||||
# be either a file system path or a Python import path. If the value starts
|
||||
# with python: then it is a Python import path, otherwise it is a file system
|
||||
# path. File system paths must be absolute since no guarantees are made about
|
||||
# the current working directory. Python paths should not include the trailing
|
||||
# .cfg, which the file must end with.
|
||||
#configuration: python:mailman.config.exim4
|
||||
configuration: python:mailman.config.postfix
|
|
@ -1,53 +0,0 @@
|
|||
# See /usr/share/postfix/main.cf.dist for a commented, more complete version
|
||||
|
||||
# Debian specific: Specifying a file name will cause the first
|
||||
# line of that file to be used as the name. The Debian default
|
||||
# is /etc/mailname.
|
||||
#myorigin = /etc/mailname
|
||||
|
||||
smtpd_banner = $myhostname ESMTP $mail_name (Debian/GNU)
|
||||
biff = no
|
||||
|
||||
# appending .domain is the MUA's job.
|
||||
append_dot_mydomain = no
|
||||
|
||||
# Uncomment the next line to generate "delayed mail" warnings
|
||||
#delay_warning_time = 4h
|
||||
|
||||
readme_directory = no
|
||||
|
||||
# See http://www.postfix.org/COMPATIBILITY_README.html -- default to 3.6 on
|
||||
# fresh installs.
|
||||
compatibility_level = 3.6
|
||||
|
||||
# TLS parameters
|
||||
smtpd_tls_cert_file=/etc/ssl/certs/ssl-cert-snakeoil.pem
|
||||
smtpd_tls_key_file=/etc/ssl/private/ssl-cert-snakeoil.key
|
||||
smtpd_tls_security_level=may
|
||||
|
||||
smtp_tls_CApath=/etc/ssl/certs
|
||||
smtp_tls_security_level=may
|
||||
smtp_tls_session_cache_database = <%text>btree:${data_directory}/smtp_scache</%text>
|
||||
|
||||
smtpd_relay_restrictions = permit_mynetworks permit_sasl_authenticated defer_unauth_destination
|
||||
myhostname = ${hostname}
|
||||
alias_maps = hash:/etc/aliases
|
||||
alias_database = hash:/etc/aliases
|
||||
mydestination = $myhostname, localhost, localhost.localdomain, ${hostname}
|
||||
relayhost =
|
||||
mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128
|
||||
mailbox_size_limit = 0
|
||||
recipient_delimiter = +
|
||||
inet_interfaces = all
|
||||
#inet_protocols = all
|
||||
inet_protocols = ipv4
|
||||
|
||||
unknown_local_recipient_reject_code = 550
|
||||
owner_request_special = no
|
||||
|
||||
transport_maps =
|
||||
hash:/var/lib/mailman3/data/postfix_lmtp
|
||||
local_recipient_maps =
|
||||
hash:/var/lib/mailman3/data/postfix_lmtp
|
||||
relay_domains =
|
||||
hash:/var/lib/mailman3/data/postfix_domains
|
|
@ -1,50 +0,0 @@
|
|||
[uwsgi]
|
||||
# Port on which uwsgi will be listening.
|
||||
uwsgi-socket = /run/mailman3-web/uwsgi.sock
|
||||
|
||||
#Enable threading for python
|
||||
enable-threads = true
|
||||
|
||||
# Move to the directory wher the django files are.
|
||||
chdir = /usr/share/mailman3-web
|
||||
|
||||
# Use the wsgi file provided with the django project.
|
||||
wsgi-file = wsgi.py
|
||||
|
||||
# Setup default number of processes and threads per process.
|
||||
master = true
|
||||
process = 2
|
||||
threads = 2
|
||||
|
||||
# Drop privielges and don't run as root.
|
||||
uid = www-data
|
||||
gid = www-data
|
||||
|
||||
plugins = python3
|
||||
|
||||
# Setup the django_q related worker processes.
|
||||
attach-daemon = python3 manage.py qcluster
|
||||
|
||||
# Setup hyperkitty's cron jobs.
|
||||
#unique-cron = -1 -1 -1 -1 -1 ./manage.py runjobs minutely
|
||||
#unique-cron = -15 -1 -1 -1 -1 ./manage.py runjobs quarter_hourly
|
||||
#unique-cron = 0 -1 -1 -1 -1 ./manage.py runjobs hourly
|
||||
#unique-cron = 0 0 -1 -1 -1 ./manage.py runjobs daily
|
||||
#unique-cron = 0 0 1 -1 -1 ./manage.py runjobs monthly
|
||||
#unique-cron = 0 0 -1 -1 0 ./manage.py runjobs weekly
|
||||
#unique-cron = 0 0 1 1 -1 ./manage.py runjobs yearly
|
||||
|
||||
# Setup the request log.
|
||||
#req-logger = file:/var/log/mailman3/web/mailman-web.log
|
||||
|
||||
# Log cron seperately.
|
||||
#logger = cron file:/var/log/mailman3/web/mailman-web-cron.log
|
||||
#log-route = cron uwsgi-cron
|
||||
|
||||
# Log qcluster commands seperately.
|
||||
#logger = qcluster file:/var/log/mailman3/web/mailman-web-qcluster.log
|
||||
#log-route = qcluster uwsgi-daemons
|
||||
|
||||
# Last log and it logs the rest of the stuff.
|
||||
#logger = file:/var/log/mailman3/web/mailman-web-error.log
|
||||
logto = /var/log/mailman3/web/mailman-web.log
|
|
@ -1,104 +0,0 @@
|
|||
directories = {
|
||||
'/var/lib/mailman3': {
|
||||
'owner': 'list',
|
||||
'group': 'list',
|
||||
'needs': {
|
||||
'zfs_dataset:tank/mailman',
|
||||
'pkg_apt:mailman3-full',
|
||||
},
|
||||
'needed_by': {
|
||||
'svc_systemd:mailman3.service',
|
||||
'svc_systemd:mailman3-web.service',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
files = {
|
||||
'/etc/postfix/main.cf': {
|
||||
'source': 'postfix.cf',
|
||||
'content_type': 'mako',
|
||||
'mode': '0644',
|
||||
'context': {
|
||||
'hostname': node.metadata.get('mailman/hostname'),
|
||||
},
|
||||
'needs': {
|
||||
'pkg_apt:postfix',
|
||||
},
|
||||
'triggers': {
|
||||
'svc_systemd:postfix.service:restart',
|
||||
},
|
||||
},
|
||||
'/etc/mailman3/mailman.cfg': {
|
||||
'content_type': 'mako',
|
||||
'owner': 'root',
|
||||
'group': 'list',
|
||||
'mode': '0640',
|
||||
'context': node.metadata.get('mailman'),
|
||||
'needs': {
|
||||
'pkg_apt:mailman3-full',
|
||||
},
|
||||
'triggers': {
|
||||
'svc_systemd:mailman3.service:restart',
|
||||
'svc_systemd:mailman3-web.service:restart',
|
||||
},
|
||||
},
|
||||
'/etc/mailman3/mailman-web.py': {
|
||||
'content_type': 'mako',
|
||||
'owner': 'root',
|
||||
'group': 'www-data',
|
||||
'mode': '0640',
|
||||
'context': node.metadata.get('mailman'),
|
||||
'needs': {
|
||||
'pkg_apt:mailman3-full',
|
||||
},
|
||||
'triggers': {
|
||||
'svc_systemd:mailman3.service:restart',
|
||||
'svc_systemd:mailman3-web.service:restart',
|
||||
},
|
||||
},
|
||||
'/etc/mailman3/mailman-hyperkitty.cfg': {
|
||||
'content_type': 'mako',
|
||||
'owner': 'root',
|
||||
'group': 'list',
|
||||
'mode': '0640',
|
||||
'context': node.metadata.get('mailman'),
|
||||
'needs': {
|
||||
'pkg_apt:mailman3-full',
|
||||
},
|
||||
'triggers': {
|
||||
'svc_systemd:mailman3.service:restart',
|
||||
'svc_systemd:mailman3-web.service:restart',
|
||||
},
|
||||
},
|
||||
'/etc/mailman3/uwsgi.ini': {
|
||||
'content_type': 'text',
|
||||
'owner': 'root',
|
||||
'group': 'root',
|
||||
'mode': '0644',
|
||||
'needs': {
|
||||
'pkg_apt:mailman3-full',
|
||||
},
|
||||
'triggers': {
|
||||
'svc_systemd:mailman3.service:restart',
|
||||
'svc_systemd:mailman3-web.service:restart',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
svc_systemd = {
|
||||
'postfix.service': {
|
||||
'needs': {
|
||||
'pkg_apt:postfix',
|
||||
},
|
||||
},
|
||||
'mailman3.service': {
|
||||
'needs': {
|
||||
'pkg_apt:mailman3-full',
|
||||
},
|
||||
},
|
||||
'mailman3-web.service': {
|
||||
'needs': {
|
||||
'pkg_apt:mailman3-full',
|
||||
},
|
||||
},
|
||||
}
|
|
@ -1,149 +0,0 @@
|
|||
import base64
|
||||
|
||||
def derive_mailadmin_secret(metadata, salt):
|
||||
node_id = metadata.get('id')
|
||||
raw = base64.b64decode(
|
||||
repo.vault.random_bytes_as_base64_for(f'{node_id}_{salt}', length=32).value
|
||||
)
|
||||
return base64.urlsafe_b64encode(raw).rstrip(b'=').decode('ascii')
|
||||
|
||||
|
||||
defaults = {
|
||||
'apt': {
|
||||
'packages': {
|
||||
'mailman3-full': {
|
||||
'needs': {
|
||||
'postgres_db:mailman',
|
||||
'postgres_role:mailman',
|
||||
'zfs_dataset:tank/mailman',
|
||||
}
|
||||
},
|
||||
'postfix': {},
|
||||
'python3-psycopg2': {
|
||||
'needed_by': {
|
||||
'pkg_apt:mailman3-full',
|
||||
},
|
||||
},
|
||||
'apache2': {
|
||||
'installed': False,
|
||||
'needs': {
|
||||
'pkg_apt:mailman3-full',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
'zfs': {
|
||||
'datasets': {
|
||||
'tank/mailman': {
|
||||
'mountpoint': '/var/lib/mailman3',
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@metadata_reactor.provides(
|
||||
'postgresql',
|
||||
'mailman',
|
||||
)
|
||||
def postgresql(metadata):
|
||||
node_id = metadata.get('id')
|
||||
db_password = repo.vault.password_for(f'{node_id} database mailman')
|
||||
|
||||
return {
|
||||
'postgresql': {
|
||||
'databases': {
|
||||
'mailman': {
|
||||
'owner': 'mailman',
|
||||
},
|
||||
},
|
||||
'roles': {
|
||||
'mailman': {
|
||||
'password': db_password,
|
||||
},
|
||||
},
|
||||
},
|
||||
'mailman': {
|
||||
'db_password': db_password,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@metadata_reactor.provides(
|
||||
'nginx/vhosts',
|
||||
)
|
||||
def nginx(metadata):
|
||||
return {
|
||||
'nginx': {
|
||||
'vhosts': {
|
||||
metadata.get('mailman/hostname'): {
|
||||
'content': 'mailman/vhost.conf',
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@metadata_reactor.provides(
|
||||
'mailman/secret_key',
|
||||
)
|
||||
def secret_key(metadata):
|
||||
import base64
|
||||
|
||||
node_id = metadata.get('id')
|
||||
raw = base64.b64decode(
|
||||
repo.vault.random_bytes_as_base64_for(f'{node_id}_mailman_secret_key', length=32).value
|
||||
)
|
||||
secret_key = base64.urlsafe_b64encode(raw).rstrip(b'=').decode('ascii')
|
||||
|
||||
return {
|
||||
'mailman': {
|
||||
'secret_key': secret_key,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@metadata_reactor.provides(
|
||||
'mailman',
|
||||
)
|
||||
def secrets(metadata):
|
||||
return {
|
||||
'mailman': {
|
||||
'web_secret': derive_mailadmin_secret(metadata, 'secret_key'),
|
||||
'api_password': derive_mailadmin_secret(metadata, 'api_password'),
|
||||
'archiver_key': derive_mailadmin_secret(metadata, 'archiver_key'),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@metadata_reactor.provides(
|
||||
'dns',
|
||||
)
|
||||
def dns(metadata):
|
||||
report_email = metadata.get('mailman/dmarc_report_email')
|
||||
|
||||
return {
|
||||
'dns': {
|
||||
metadata.get('mailman/hostname'): {
|
||||
'MX': [f"5 {metadata.get('mailman/hostname')}."],
|
||||
'TXT': [
|
||||
'v=spf1 a mx -all',
|
||||
'; '.join(f'{k}={v}' for k, v in {
|
||||
# dmarc version
|
||||
'v': 'DMARC1',
|
||||
# reject on failure
|
||||
'p': 'reject',
|
||||
# standard reports
|
||||
'rua': f'mailto:{report_email}',
|
||||
# forensic reports
|
||||
'fo': 1,
|
||||
'ruf': f'mailto:{report_email}',
|
||||
# require alignment between the DKIM domain and the parent Header From domain
|
||||
'adkim': 's',
|
||||
# require alignment between the SPF domain (the sender) and the Header From domain
|
||||
'aspf': 's',
|
||||
}.items())
|
||||
],
|
||||
},
|
||||
},
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
|
||||
// https://raw.githubusercontent.com/Radiergummi/autodiscover/master/autodiscover/autodiscover.php
|
||||
|
||||
/********************************
|
||||
* Autodiscover responder
|
||||
|
@ -15,38 +15,38 @@
|
|||
* domain names, adjust ports and SSL.
|
||||
*/
|
||||
|
||||
|
||||
//get raw POST data so we can extract the email address
|
||||
$request = file_get_contents("php://input");
|
||||
|
||||
|
||||
// optional debug log
|
||||
# file_put_contents( 'request.log', $request, FILE_APPEND );
|
||||
|
||||
|
||||
// retrieve email address from client request
|
||||
preg_match( "/\<EMailAddress\>(.*?)\<\/EMailAddress\>/", $request, $email );
|
||||
|
||||
|
||||
// check for invalid mail, to prevent XSS
|
||||
if (filter_var($email[1], FILTER_VALIDATE_EMAIL) === false) {
|
||||
throw new Exception('Invalid E-Mail provided');
|
||||
}
|
||||
|
||||
|
||||
// get domain from email address
|
||||
$domain = substr( strrchr( $email[1], "@" ), 1 );
|
||||
|
||||
/**************************************
|
||||
* Port and server settings below *
|
||||
**************************************/
|
||||
|
||||
|
||||
// IMAP settings
|
||||
$imapServer = 'imap.' . $domain; // imap.example.com
|
||||
$imapPort = 993;
|
||||
$imapSSL = true;
|
||||
|
||||
|
||||
// SMTP settings
|
||||
$smtpServer = 'smtp.' . $domain; // smtp.example.com
|
||||
$smtpPort = 587;
|
||||
$smtpSSL = true;
|
||||
|
||||
|
||||
//set Content-Type
|
||||
header( 'Content-Type: application/xml' );
|
||||
?>
|
||||
<?php echo '<?xml version="1.0" encoding="utf-8" ?>'; ?>
|
||||
|
|
|
@ -32,14 +32,10 @@ defaults = {
|
|||
'tank/vmail': {
|
||||
'mountpoint': '/var/vmail',
|
||||
'compression': 'on',
|
||||
'atime': 'off',
|
||||
'recordsize': '16384',
|
||||
},
|
||||
'tank/vmail/index': {
|
||||
'mountpoint': '/var/vmail/index',
|
||||
'compression': 'on',
|
||||
'atime': 'off',
|
||||
'recordsize': '4096',
|
||||
'com.sun:auto-snapshot': 'false',
|
||||
'backup': False,
|
||||
},
|
||||
|
|
11
bundles/mariadb/files/override.conf
Normal file
11
bundles/mariadb/files/override.conf
Normal file
|
@ -0,0 +1,11 @@
|
|||
% for section, options in sorted(conf.items()):
|
||||
[${section}]
|
||||
% for key, value in sorted(options.items()):
|
||||
% if value is None:
|
||||
${key}
|
||||
% else:
|
||||
${key} = ${value}
|
||||
% endif
|
||||
% endfor
|
||||
|
||||
% endfor
|
|
@ -10,16 +10,19 @@ directories = {
|
|||
'group': 'mysql',
|
||||
'needs': [
|
||||
'zfs_dataset:tank/mariadb',
|
||||
],
|
||||
'needed_by': [
|
||||
'pkg_apt:mariadb-server',
|
||||
'pkg_apt:mariadb-client',
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
files = {
|
||||
'/etc/mysql/conf.d/override.conf': {
|
||||
'content': repo.libs.ini.dumps(node.metadata.get('mariadb/conf')),
|
||||
'content_type': 'text',
|
||||
'context': {
|
||||
'conf': node.metadata.get('mariadb/conf'),
|
||||
},
|
||||
'content_type': 'mako',
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -27,7 +30,6 @@ svc_systemd = {
|
|||
'mariadb.service': {
|
||||
'needs': [
|
||||
'pkg_apt:mariadb-server',
|
||||
'pkg_apt:mariadb-client',
|
||||
],
|
||||
},
|
||||
}
|
||||
|
|
|
@ -1,16 +1,7 @@
|
|||
defaults = {
|
||||
'apt': {
|
||||
'packages': {
|
||||
'mariadb-server': {
|
||||
'needs': {
|
||||
'zfs_dataset:tank/mariadb',
|
||||
},
|
||||
},
|
||||
'mariadb-client': {
|
||||
'needs': {
|
||||
'zfs_dataset:tank/mariadb',
|
||||
},
|
||||
},
|
||||
'mariadb-server': {},
|
||||
},
|
||||
},
|
||||
'mariadb': {
|
||||
|
|
|
@ -1,19 +0,0 @@
|
|||
for network_name, network_conf in node.metadata.get('network').items():
|
||||
if 'qdisc' in network_conf:
|
||||
svc_systemd[f'qdisc-{network_name}.service'] = {
|
||||
'enabled': True,
|
||||
'running': None,
|
||||
'needs': {
|
||||
f'file:/usr/local/lib/systemd/system/qdisc-{network_name}.service',
|
||||
},
|
||||
}
|
||||
actions[f'qdisc-{network_name}.service_restart_workaround'] = {
|
||||
'command': 'true',
|
||||
'triggered': True,
|
||||
'triggered_by': {
|
||||
f'file:/usr/local/lib/systemd/system/qdisc-{network_name}.service',
|
||||
},
|
||||
'triggers': {
|
||||
f'svc_systemd:qdisc-{network_name}.service:restart',
|
||||
},
|
||||
}
|
|
@ -5,141 +5,41 @@ defaults = {
|
|||
}
|
||||
|
||||
|
||||
@metadata_reactor.provides(
|
||||
'network',
|
||||
)
|
||||
def dhcp(metadata):
|
||||
networks = {}
|
||||
|
||||
for network_name, network_conf in metadata.get('network').items():
|
||||
_interface = ip_interface(network_conf['ipv4'])
|
||||
_ip = _interface.ip
|
||||
_network = _interface.network
|
||||
_hosts = list(_network.hosts())
|
||||
|
||||
if network_conf.get('dhcp_server', False):
|
||||
networks[network_name] = {
|
||||
'dhcp_server_config': {
|
||||
'subnet': str(_network),
|
||||
'pool_from': str(_hosts[len(_hosts)//2]),
|
||||
'pool_to': str(_hosts[-3]),
|
||||
'router': str(_ip),
|
||||
'domain-name-servers': str(_ip),
|
||||
}
|
||||
}
|
||||
return {
|
||||
'network': networks,
|
||||
}
|
||||
|
||||
|
||||
@metadata_reactor.provides(
|
||||
'systemd/units',
|
||||
'modules-load',
|
||||
)
|
||||
def units(metadata):
|
||||
if node.has_bundle('systemd-networkd'):
|
||||
units = {}
|
||||
modules_load = set()
|
||||
|
||||
for network_name, network_conf in metadata.get('network').items():
|
||||
interface_type = network_conf.get('type', None)
|
||||
|
||||
# network
|
||||
|
||||
units[f'{network_name}.network'] = {
|
||||
for type, network in metadata.get('network').items():
|
||||
units[f'{type}.network'] = {
|
||||
'Match': {
|
||||
'Name': network_name if interface_type == 'vlan' else network_conf['interface'],
|
||||
'Name': network['interface'],
|
||||
},
|
||||
'Network': {
|
||||
'DHCP': network_conf.get('dhcp', 'no'),
|
||||
'IPv6AcceptRA': network_conf.get('IPv6AcceptRA', 'no'),
|
||||
'VLAN': set(
|
||||
other_network_name
|
||||
for other_network_name, other_network_conf in metadata.get('network', {}).items()
|
||||
if other_network_conf.get('type') == 'vlan' and other_network_conf['vlan_interface'] == network_name
|
||||
)
|
||||
'DHCP': network.get('dhcp', 'no'),
|
||||
'IPv6AcceptRA': network.get('dhcp', 'no'),
|
||||
}
|
||||
}
|
||||
|
||||
# type
|
||||
|
||||
if interface_type:
|
||||
units[f'{network_name}.network']['Match']['Type'] = interface_type
|
||||
|
||||
# ips
|
||||
|
||||
for i in [4, 6]:
|
||||
if network_conf.get(f'ipv{i}', None):
|
||||
units[f'{network_name}.network'].update({
|
||||
if network.get(f'ipv{i}', None):
|
||||
units[f'{type}.network'].update({
|
||||
f'Address#ipv{i}': {
|
||||
'Address': network_conf[f'ipv{i}'],
|
||||
'Address': network[f'ipv{i}'],
|
||||
},
|
||||
})
|
||||
if f'gateway{i}' in network_conf:
|
||||
units[f'{network_name}.network'].update({
|
||||
if f'gateway{i}' in network:
|
||||
units[f'{type}.network'].update({
|
||||
f'Route#ipv{i}': {
|
||||
'Gateway': network_conf[f'gateway{i}'],
|
||||
'Gateway': network[f'gateway{i}'],
|
||||
'GatewayOnlink': 'yes',
|
||||
}
|
||||
})
|
||||
|
||||
# as vlan
|
||||
|
||||
if interface_type == 'vlan':
|
||||
units[f"{network_name}.netdev"] = {
|
||||
'NetDev': {
|
||||
'Name': network_name,
|
||||
'Kind': 'vlan',
|
||||
},
|
||||
'VLAN': {
|
||||
'Id': network_conf['id'],
|
||||
}
|
||||
}
|
||||
|
||||
# cake WIP
|
||||
|
||||
if 'cake' in network_conf:
|
||||
units[f'{network_name}.network']['CAKE'] = network_conf['cake']
|
||||
modules_load.add('sch_cake')
|
||||
|
||||
return {
|
||||
'systemd': {
|
||||
'units': units,
|
||||
},
|
||||
'modules-load': modules_load,
|
||||
}
|
||||
else:
|
||||
return {}
|
||||
|
||||
|
||||
@metadata_reactor.provides(
|
||||
'systemd/units',
|
||||
)
|
||||
def queuing_disciplines(metadata):
|
||||
if node.has_bundle('systemd-networkd'):
|
||||
return {
|
||||
'systemd': {
|
||||
'units': {
|
||||
f'qdisc-{network_name}.service': {
|
||||
'Unit': {
|
||||
'Description': f'setup queuing discipline for interface {network_name}',
|
||||
'Wants': 'network.target',
|
||||
'After': 'network.target',
|
||||
'BindsTo': 'network.target',
|
||||
},
|
||||
'Service': {
|
||||
'Type': 'oneshot',
|
||||
'ExecStart': f'/sbin/tc qdisc replace root dev {network_name} {network_conf["qdisc"]}',
|
||||
'RemainAfterExit': 'yes',
|
||||
},
|
||||
'Install': {
|
||||
'WantedBy': 'network-online.target',
|
||||
},
|
||||
}
|
||||
for network_name, network_conf in metadata.get('network').items()
|
||||
if 'qdisc' in network_conf
|
||||
},
|
||||
},
|
||||
}
|
||||
else:
|
||||
return {}
|
||||
|
|
|
@ -142,7 +142,6 @@ def config(metadata):
|
|||
'versions_retention_obligation': 'auto, 90',
|
||||
'simpleSignUpLink.shown': False,
|
||||
'allow_local_remote_servers': True, # FIXME?
|
||||
'maintenance_window_start': 1, # https://docs.nextcloud.com/server/29/admin_manual/configuration_server/background_jobs_configuration.html#maintenance-window-start
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -8,5 +8,4 @@ examples
|
|||
|
||||
```sh
|
||||
nft add rule inet filter input tcp dport 5201 accept
|
||||
nft add rule inet filter input udp dport 5201 accept
|
||||
```
|
||||
|
|
|
@ -2,23 +2,6 @@
|
|||
|
||||
flush ruleset
|
||||
|
||||
% if nat:
|
||||
table ip nat {
|
||||
|
||||
# NAT
|
||||
|
||||
chain postrouting {
|
||||
type nat hook postrouting priority 100
|
||||
policy accept
|
||||
|
||||
# rules
|
||||
% for rule in sorted(nat):
|
||||
${rule}
|
||||
% endfor
|
||||
}
|
||||
}
|
||||
% endif
|
||||
|
||||
table inet filter {
|
||||
|
||||
# INPUT
|
||||
|
|
|
@ -6,7 +6,6 @@ files = {
|
|||
'input': node.metadata.get('nftables/input'),
|
||||
'forward': node.metadata.get('nftables/forward'),
|
||||
'output': node.metadata.get('nftables/output'),
|
||||
'nat': node.metadata.get('nftables/nat'),
|
||||
},
|
||||
'triggers': [
|
||||
'svc_systemd:nftables.service:reload',
|
||||
|
|
|
@ -8,8 +8,7 @@ defaults = {
|
|||
'input': {
|
||||
'tcp dport 22 accept',
|
||||
},
|
||||
'forward': set(),
|
||||
'nat': set(),
|
||||
'output': set(),
|
||||
'forward': {},
|
||||
'output': {},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
pid /var/run/nginx.pid;
|
||||
user www-data;
|
||||
worker_processes ${worker_processes};
|
||||
worker_processes 10;
|
||||
|
||||
% for module in sorted(modules):
|
||||
load_module modules/ngx_${module}_module.so;
|
||||
|
@ -21,9 +21,6 @@ http {
|
|||
server_names_hash_bucket_size 128;
|
||||
tcp_nopush on;
|
||||
client_max_body_size 32G;
|
||||
ssl_dhparam "/etc/ssl/certs/dhparam.pem";
|
||||
# dont show nginx version
|
||||
server_tokens off;
|
||||
|
||||
% if node.has_bundle('php'):
|
||||
upstream php-handler {
|
||||
|
@ -31,13 +28,5 @@ http {
|
|||
}
|
||||
|
||||
% endif
|
||||
|
||||
% if has_websockets:
|
||||
map $http_upgrade $connection_upgrade {
|
||||
default upgrade;
|
||||
'' close;
|
||||
}
|
||||
% endif
|
||||
|
||||
include /etc/nginx/sites-enabled/*;
|
||||
include /etc/nginx/sites/*;
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ directories = {
|
|||
'svc_systemd:nginx:restart',
|
||||
},
|
||||
},
|
||||
'/etc/nginx/sites-available': {
|
||||
'/etc/nginx/sites': {
|
||||
'purge': True,
|
||||
'triggers': {
|
||||
'svc_systemd:nginx:restart',
|
||||
|
@ -32,8 +32,6 @@ files = {
|
|||
'content_type': 'mako',
|
||||
'context': {
|
||||
'modules': node.metadata.get('nginx/modules'),
|
||||
'worker_processes': node.metadata.get('vm/cores'),
|
||||
'has_websockets': node.metadata.get('nginx/has_websockets'),
|
||||
},
|
||||
'triggers': {
|
||||
'svc_systemd:nginx:restart',
|
||||
|
@ -76,15 +74,9 @@ files = {
|
|||
},
|
||||
}
|
||||
|
||||
symlinks = {
|
||||
'/etc/nginx/sites-enabled': {
|
||||
'target': '/etc/nginx/sites-available',
|
||||
},
|
||||
}
|
||||
|
||||
actions = {
|
||||
'nginx-generate-dhparam': {
|
||||
'command': 'openssl dhparam -dsaparam -out /etc/ssl/certs/dhparam.pem 4096',
|
||||
'command': 'openssl dhparam -out /etc/ssl/certs/dhparam.pem 2048',
|
||||
'unless': 'test -f /etc/ssl/certs/dhparam.pem',
|
||||
},
|
||||
}
|
||||
|
@ -100,7 +92,7 @@ svc_systemd = {
|
|||
|
||||
|
||||
for name, config in node.metadata.get('nginx/vhosts').items():
|
||||
files[f'/etc/nginx/sites-available/{name}'] = {
|
||||
files[f'/etc/nginx/sites/{name}'] = {
|
||||
'content': Template(filename=join(repo.path, 'data', config['content'])).render(
|
||||
server_name=name,
|
||||
**config.get('context', {}),
|
||||
|
@ -116,6 +108,6 @@ for name, config in node.metadata.get('nginx/vhosts').items():
|
|||
}
|
||||
|
||||
if name in node.metadata.get('letsencrypt/domains'):
|
||||
files[f'/etc/nginx/sites-available/{name}']['needs'].append(
|
||||
files[f'/etc/nginx/sites/{name}']['needs'].append(
|
||||
f'action:letsencrypt_ensure-some-certificate_{name}',
|
||||
)
|
||||
|
|
|
@ -18,7 +18,6 @@ defaults = {
|
|||
'nginx': {
|
||||
'vhosts': {},
|
||||
'modules': set(),
|
||||
'has_websockets': False,
|
||||
},
|
||||
'systemd': {
|
||||
'units': {
|
||||
|
@ -96,7 +95,7 @@ def monitoring(metadata):
|
|||
'monitoring': {
|
||||
'services': {
|
||||
hostname: {
|
||||
'vars.command': f"/usr/bin/curl -X GET -L --fail --no-progress-meter -o /dev/null {vhost.get('check_protocol', 'https')}://{quote(hostname + vhost.get('check_path', '/'))}",
|
||||
'vars.command': f"/usr/bin/curl -X GET -L --fail --no-progress-meter -o /dev/null {quote(hostname + vhost.get('check_path', ''))}",
|
||||
}
|
||||
for hostname, vhost in metadata.get('nginx/vhosts').items()
|
||||
},
|
||||
|
|
|
@ -1,3 +1,9 @@
|
|||
from os.path import join
|
||||
import json
|
||||
|
||||
from bundlewrap.utils.dicts import merge_dict
|
||||
|
||||
|
||||
version = node.metadata.get('php/version')
|
||||
|
||||
files = {
|
||||
|
@ -15,7 +21,7 @@ files = {
|
|||
f'pkg_apt:php{version}-fpm',
|
||||
},
|
||||
'triggers': {
|
||||
f'svc_systemd:php{version}-fpm.service:restart',
|
||||
f'svc_systemd:php{version}-fpm:restart',
|
||||
},
|
||||
},
|
||||
f'/etc/php/{version}/fpm/pool.d/www.conf': {
|
||||
|
@ -27,13 +33,13 @@ files = {
|
|||
f'pkg_apt:php{version}-fpm',
|
||||
},
|
||||
'triggers': {
|
||||
f'svc_systemd:php{version}-fpm.service:restart',
|
||||
f'svc_systemd:php{version}-fpm:restart',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
svc_systemd = {
|
||||
f'php{version}-fpm.service': {
|
||||
f'php{version}-fpm': {
|
||||
'needs': {
|
||||
'pkg_apt:',
|
||||
f'file:/etc/php/{version}/fpm/php.ini',
|
||||
|
|
|
@ -145,7 +145,7 @@ def www_conf(metadata):
|
|||
'pm': 'dynamic',
|
||||
'pm.max_children': int(threads*2),
|
||||
'pm.start_servers': int(threads),
|
||||
'pm.min_spare_servers': max([1, int(threads/2)]),
|
||||
'pm.min_spare_servers': int(threads/2),
|
||||
'pm.max_spare_servers': int(threads),
|
||||
'pm.max_requests': int(threads*32),
|
||||
},
|
||||
|
|
|
@ -86,8 +86,6 @@ if node.has_bundle('telegraf'):
|
|||
'needs': [
|
||||
'pkg_apt:acl',
|
||||
'svc_systemd:postfix',
|
||||
'svc_systemd:postfix:reload',
|
||||
'svc_systemd:postfix:restart',
|
||||
],
|
||||
}
|
||||
actions['postfix_setfacl_default_telegraf'] = {
|
||||
|
@ -96,7 +94,5 @@ if node.has_bundle('telegraf'):
|
|||
'needs': [
|
||||
'pkg_apt:acl',
|
||||
'svc_systemd:postfix',
|
||||
'svc_systemd:postfix:reload',
|
||||
'svc_systemd:postfix:restart',
|
||||
],
|
||||
}
|
||||
|
|
|
@ -1,22 +0,0 @@
|
|||
# DO NOT DISABLE!
|
||||
# If you change this first entry you will need to make sure that the
|
||||
# database superuser can access the database using some other method.
|
||||
# Noninteractive access to all databases is required during automatic
|
||||
# maintenance (custom daily cronjobs, replication, and similar tasks).
|
||||
#
|
||||
# Database administrative login by Unix domain socket
|
||||
local all postgres peer
|
||||
|
||||
# TYPE DATABASE USER ADDRESS METHOD
|
||||
|
||||
# "local" is for Unix domain socket connections only
|
||||
local all all peer
|
||||
# IPv4 local connections:
|
||||
host all all 127.0.0.1/32 ${node.metadata.get('postgresql/password_algorithm', 'md5')}
|
||||
# IPv6 local connections:
|
||||
host all all ::1/128 ${node.metadata.get('postgresql/password_algorithm', 'md5')}
|
||||
# Allow replication connections from localhost, by a user with the
|
||||
# replication privilege.
|
||||
local replication all peer
|
||||
host replication all 127.0.0.1/32 ${node.metadata.get('postgresql/password_algorithm', 'md5')}
|
||||
host replication all ::1/128 ${node.metadata.get('postgresql/password_algorithm', 'md5')}
|
|
@ -12,27 +12,12 @@ directories = {
|
|||
'zfs_dataset:tank/postgresql',
|
||||
],
|
||||
'needed_by': [
|
||||
'svc_systemd:postgresql.service',
|
||||
'svc_systemd:postgresql',
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
files = {
|
||||
f"/etc/postgresql/{version}/main/pg_hba.conf": {
|
||||
'content_type': 'mako',
|
||||
'mode': '0640',
|
||||
'owner': 'postgres',
|
||||
'group': 'postgres',
|
||||
'needs': [
|
||||
'pkg_apt:postgresql',
|
||||
],
|
||||
'needed_by': [
|
||||
'svc_systemd:postgresql.service',
|
||||
],
|
||||
'triggers': [
|
||||
'svc_systemd:postgresql.service:restart',
|
||||
],
|
||||
},
|
||||
f"/etc/postgresql/{version}/main/conf.d/managed.conf": {
|
||||
'content': '\n'.join(
|
||||
f'{key} = {value}'
|
||||
|
@ -40,19 +25,16 @@ files = {
|
|||
) + '\n',
|
||||
'owner': 'postgres',
|
||||
'group': 'postgres',
|
||||
'needs': [
|
||||
'pkg_apt:postgresql',
|
||||
],
|
||||
'needed_by': [
|
||||
'svc_systemd:postgresql.service',
|
||||
'svc_systemd:postgresql',
|
||||
],
|
||||
'triggers': [
|
||||
'svc_systemd:postgresql.service:restart',
|
||||
'svc_systemd:postgresql:restart',
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
svc_systemd['postgresql.service'] = {
|
||||
svc_systemd['postgresql'] = {
|
||||
'needs': [
|
||||
'pkg_apt:postgresql',
|
||||
],
|
||||
|
@ -61,13 +43,13 @@ svc_systemd['postgresql.service'] = {
|
|||
for user, config in node.metadata.get('postgresql/roles').items():
|
||||
postgres_roles[user] = merge_dict(config, {
|
||||
'needs': [
|
||||
'svc_systemd:postgresql.service',
|
||||
'svc_systemd:postgresql',
|
||||
],
|
||||
})
|
||||
|
||||
for database, config in node.metadata.get('postgresql/databases').items():
|
||||
postgres_dbs[database] = merge_dict(config, {
|
||||
'needs': [
|
||||
'svc_systemd:postgresql.service',
|
||||
'svc_systemd:postgresql',
|
||||
],
|
||||
})
|
||||
|
|
|
@ -6,11 +6,7 @@ root_password = repo.vault.password_for(f'{node.name} postgresql root')
|
|||
defaults = {
|
||||
'apt': {
|
||||
'packages': {
|
||||
'postgresql': {
|
||||
'needs': {
|
||||
'zfs_dataset:tank/postgresql',
|
||||
},
|
||||
},
|
||||
'postgresql': {},
|
||||
},
|
||||
},
|
||||
'backup': {
|
||||
|
|
|
@ -1,36 +0,0 @@
|
|||
# Firtzbox
|
||||
|
||||
Internet > Zugangsdaten
|
||||
|
||||
Internetanbieter
|
||||
- weitere Internetanbieter
|
||||
- anderer Internetanbieter
|
||||
- Name: "My PPPOE" (nicht leer lassen)
|
||||
|
||||
Anschluss
|
||||
(x) Anschluss an einen DSL-Anschluss
|
||||
|
||||
Zugangsdaten
|
||||
(x) Nein
|
||||
|
||||
Verbindungseinstellungen
|
||||
|
||||
[x] VLAN für den Internetanschluss verwenden
|
||||
VLAN-ID: 7
|
||||
PBit: 0
|
||||
|
||||
DSL-ATM-Einstellungen
|
||||
VPI: 1
|
||||
VCI: 32
|
||||
|
||||
Kapselung
|
||||
(x) Routed Bridge Encapsulation
|
||||
[x] IP-Adresse automatisch über DHCP beziehen
|
||||
DHCP-Hostname: fritz.box
|
||||
|
||||
PPPoE-Passthrough
|
||||
[x] Angeschlossene Netzwerkgeräte dürfen zusätzlich ihre eigene Internetverbindung aufbauen (nicht empfohlen)
|
||||
|
||||
[ ] Internetzugang nach dem "Übernehmen" prüfen
|
||||
|
||||
-> Danach muss bei "Internetanbieter" statt "weitere Internetanbieter" der gewählte Name stehen, also zB "My PPPOE"
|
|
@ -1,3 +0,0 @@
|
|||
# Secrets for authentication using CHAP
|
||||
# client server secret IP addresses
|
||||
"${user}" * "${secret}" *
|
|
@ -1,30 +0,0 @@
|
|||
# --- Plugin & Interface ---
|
||||
plugin rp-pppoe.so ${interface}
|
||||
unit 0
|
||||
|
||||
# --- IPv4 Einstellungen ---
|
||||
noipdefault # keine selbstgewählte lokale IP
|
||||
defaultroute # Default-Route über ppp0 anlegen
|
||||
replacedefaultroute # ersetzt vorherige Default-Route
|
||||
|
||||
# --- IPv6 Einstellungen ---
|
||||
+ipv6 # IPv6CP aktivieren
|
||||
ipv6cp-accept-local # lokale IPv6 vom ISP übernehmen
|
||||
ipv6cp-accept-remote # remote IPv6 vom ISP übernehmen
|
||||
ipv6cp-use-ipaddr # statt Link-Local die zugewiesene IPv6 nutzen
|
||||
defaultroute6
|
||||
|
||||
# --- Verbindungsmanagement ---
|
||||
persist # bei Abbruch automatisch neu verbinden
|
||||
maxfail 0 # unbegrenzt Neuversuche
|
||||
|
||||
# --- LCP‐Keepalive (zuverlässiger Ausfall-Check) ---
|
||||
lcp-echo-interval 20
|
||||
lcp-echo-failure 3
|
||||
|
||||
# --- Sicherheit / Logging ---
|
||||
hide-password # Passwort nicht im Log anzeigen
|
||||
noauth # Auth nur über chap-secrets
|
||||
|
||||
# --- Zugangsdaten (nur Username, das Passwort kommt aus /etc/ppp/chap-secrets) ---
|
||||
user "${user}"
|
|
@ -1,38 +0,0 @@
|
|||
files = {
|
||||
'/etc/ppp/peers/isp': {
|
||||
'content_type': 'mako',
|
||||
'mode': '0644',
|
||||
'context': {
|
||||
'interface': node.metadata.get('pppoe/interface'),
|
||||
'user': node.metadata.get('pppoe/user'),
|
||||
},
|
||||
'needs': {
|
||||
'pkg_apt:pppoe',
|
||||
},
|
||||
},
|
||||
'/etc/ppp/chap-secrets': {
|
||||
'content_type': 'mako',
|
||||
'mode': '0600',
|
||||
'context': {
|
||||
'user': node.metadata.get('pppoe/user'),
|
||||
'secret': node.metadata.get('pppoe/secret'),
|
||||
},
|
||||
'needs': {
|
||||
'pkg_apt:pppoe',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
svc_systemd = {
|
||||
'pppoe-isp.service': {
|
||||
'needs': {
|
||||
'file:/etc/ppp/peers/isp',
|
||||
'file:/etc/ppp/chap-secrets',
|
||||
},
|
||||
},
|
||||
'qdisc-ppp0.service': {
|
||||
'needs': {
|
||||
'svc_systemd:pppoe-isp.service',
|
||||
},
|
||||
},
|
||||
}
|
|
@ -1,55 +0,0 @@
|
|||
defaults = {
|
||||
'apt': {
|
||||
'packages': {
|
||||
'pppoe': {},
|
||||
},
|
||||
},
|
||||
'modules-load': {
|
||||
'pppoe',
|
||||
'pppox',
|
||||
'ppp_generic',
|
||||
},
|
||||
'nftables': {
|
||||
'nat': {
|
||||
'oifname ppp0 masquerade',
|
||||
},
|
||||
},
|
||||
'systemd': {
|
||||
'units': {
|
||||
'pppoe-isp.service': {
|
||||
'Unit': {
|
||||
'Description': 'PPPoE Internet Connection',
|
||||
'After': 'network.target',
|
||||
},
|
||||
'Service': {
|
||||
'Type': 'forking',
|
||||
'ExecStart': '/usr/sbin/pppd call isp',
|
||||
'Restart': 'on-failure',
|
||||
'RestartSec': 5,
|
||||
},
|
||||
},
|
||||
'qdisc-ppp0.service': {
|
||||
'Unit': {
|
||||
'Description': 'setup queuing discipline for interface ppp0',
|
||||
'After': {
|
||||
'pppoe-isp.service',
|
||||
'sys-devices-virtual-net-ppp0.device',
|
||||
},
|
||||
'PartOf': 'pppoe-isp.service',
|
||||
'BindsTo': 'sys-devices-virtual-net-ppp0.device',
|
||||
},
|
||||
'Service': {
|
||||
'Type': 'oneshot',
|
||||
'ExecStart': '/sbin/tc qdisc replace root dev ppp0 cake bandwidth 37Mbit internet besteffort triple-isolate nat egress memlimit 256mb',
|
||||
# - no drops save
|
||||
# - bis 37MBit keine retries bei: iperf3 --client 49.12.184.229 -t999 -i5 --bidir
|
||||
#'ExecStart': '/sbin/tc qdisc replace root dev ppp0 cake bandwidth 37Mbit internet besteffort nat egress memlimit 256mb',
|
||||
'RemainAfterExit': 'yes',
|
||||
},
|
||||
'Install': {
|
||||
'WantedBy': 'multi-user.target',
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
files = {
|
||||
'/etc/apt/apt.conf.d/10pveapthook': {
|
||||
'content_type': 'any',
|
||||
'mode': '0644',
|
||||
},
|
||||
'/etc/apt/apt.conf.d/76pveconf': {
|
||||
'content_type': 'any',
|
||||
'mode': '0444',
|
||||
},
|
||||
'/etc/apt/apt.conf.d/76pveproxy': {
|
||||
'content_type': 'any',
|
||||
'mode': '0644',
|
||||
},
|
||||
'/etc/network/interfaces': {
|
||||
'content_type': 'any',
|
||||
},
|
||||
}
|
||||
|
||||
symlinks['/etc/ssh/ssh_host_rsa_key.pub'] = {
|
||||
'target': '/etc/ssh/ssh_host_managed_key.pub',
|
||||
}
|
|
@ -1,100 +0,0 @@
|
|||
defaults = {
|
||||
'apt': {
|
||||
'packages': {
|
||||
'linux-image-amd64': {
|
||||
'installed': False,
|
||||
},
|
||||
'proxmox-default-kernel': {},
|
||||
# after reboot
|
||||
'proxmox-ve': {},
|
||||
'postfix': {},
|
||||
'open-iscsi': {},
|
||||
'chrony': {},
|
||||
'os-prober': {
|
||||
'installed': False,
|
||||
},
|
||||
'dnsmasq-base': {},
|
||||
},
|
||||
'sources': {
|
||||
'proxmox-ve': {
|
||||
'options': {
|
||||
'aarch': 'amd64',
|
||||
},
|
||||
'urls': {
|
||||
'http://download.proxmox.com/debian/pve',
|
||||
},
|
||||
'suites': {
|
||||
'{codename}',
|
||||
},
|
||||
'components': {
|
||||
'pve-no-subscription',
|
||||
},
|
||||
'key': 'proxmox-ve-{codename}',
|
||||
},
|
||||
},
|
||||
},
|
||||
# 'nftables': {
|
||||
# 'input': {
|
||||
# 'tcp dport 8006 accept',
|
||||
# },
|
||||
# },
|
||||
'zfs': {
|
||||
'datasets': {
|
||||
'tank/proxmox-ve': {
|
||||
'mountpoint': '/var/lib/proxmox-ve',
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# @metadata_reactor.provides(
|
||||
# 'systemd',
|
||||
# )
|
||||
# def bridge(metadata):
|
||||
# return {
|
||||
# 'systemd': {
|
||||
# 'units': {
|
||||
# # f'internal.network': {
|
||||
# # 'Network': {
|
||||
# # 'Bridge': 'br0',
|
||||
# # },
|
||||
# # },
|
||||
# 'br0.netdev': {
|
||||
# 'NetDev': {
|
||||
# 'Name': 'br0',
|
||||
# 'Kind': 'bridge'
|
||||
# },
|
||||
# },
|
||||
# 'br0.network': {
|
||||
# 'Match': {
|
||||
# 'Name': 'br0',
|
||||
# },
|
||||
# 'Network': {
|
||||
# 'Unmanaged': 'yes'
|
||||
# },
|
||||
# },
|
||||
# },
|
||||
# },
|
||||
# }
|
||||
|
||||
|
||||
@metadata_reactor.provides(
|
||||
'nginx/has_websockets',
|
||||
'nginx/vhosts',
|
||||
)
|
||||
def nginx(metadata):
|
||||
return {
|
||||
'nginx': {
|
||||
'has_websockets': True,
|
||||
'vhosts': {
|
||||
metadata.get('proxmox-ve/domain'): {
|
||||
'content': 'nginx/proxy_pass.conf',
|
||||
'context': {
|
||||
'target': 'https://localhost:8006',
|
||||
'websockets': True,
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
from shlex import quote
|
||||
|
||||
directories = {
|
||||
'/opt/pyenv': {},
|
||||
'/opt/pyenv/install': {},
|
||||
}
|
||||
|
||||
git_deploy = {
|
||||
'/opt/pyenv/install': {
|
||||
'repo': 'https://github.com/pyenv/pyenv.git',
|
||||
'rev': 'master',
|
||||
'needs': {
|
||||
'directory:/opt/pyenv/install',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for version in node.metadata.get('pyenv/versions'):
|
||||
actions[f'pyenv_install_{version}'] = {
|
||||
'command': f'PYENV_ROOT=/opt/pyenv /opt/pyenv/install/bin/pyenv install {quote(version)}',
|
||||
'unless': f'PYENV_ROOT=/opt/pyenv /opt/pyenv/install/bin/pyenv versions --bare | grep -Fxq {quote(version)}',
|
||||
'needs': {
|
||||
'git_deploy:/opt/pyenv/install',
|
||||
},
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
defaults = {
|
||||
'apt': {
|
||||
'packages': {
|
||||
'build-essential': {},
|
||||
'libssl-dev': {},
|
||||
'zlib1g-dev': {},
|
||||
'libbz2-dev': {},
|
||||
'libreadline-dev': {},
|
||||
'libsqlite3-dev': {},
|
||||
'curl': {},
|
||||
'libncurses-dev': {},
|
||||
'xz-utils': {},
|
||||
'tk-dev': {},
|
||||
'libxml2-dev': {},
|
||||
'libxmlsec1-dev': {},
|
||||
'libffi-dev': {},
|
||||
'liblzma-dev': {},
|
||||
},
|
||||
},
|
||||
'pyenv': {
|
||||
'versions': set(),
|
||||
},
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue