Compare commits

...

4 Commits

Author SHA1 Message Date
Joey Hafner
51f55e8d07
Enhance sieve logic
Some checks failed
Deploy NixOS System Configurations to Silver-Flame Hosts / deploy (push) Failing after 15s
- Switch from guard statements to case block (via if ... elsif ... else)
- Add "X-Original-To" header to list of fields to check when determining receiver
- File mail that matches none of the configured addresses under "Mailing Lists"
2024-10-28 18:06:05 -07:00
Joey Hafner
bbe52da9bb
#10 Reorganize Nix code
- Flake to build personal systems moved to `dotfiles/`
- Flake to build silver-flame cluster moved to `homelab/local-hosts/silver-flame
- Delete undercooked "digitalocean" and "iso" image builds
2024-10-28 18:04:04 -07:00
Joey Hafner
aa1527ccb5
#19 Delete legacy docs, extract useful content into other docs, delete homelab/docs/ 2024-10-28 18:01:52 -07:00
Joey Hafner
e5109b1f9b
#19 Delete archive. Version control is my archive 2024-10-28 17:59:37 -07:00
399 changed files with 349 additions and 11217 deletions

View File

@ -3,13 +3,17 @@ A monorepo for all my projects and dotfiles. Hosted on [my Gitea](https://gitea.
## Map of Contents ## Map of Contents
| Project | Summary | Path | | Project | Summary |
|:-------------------:|:-------:|:----:| |:----------------------:|:-------:|
| homelab | Configuration and documentation for my homelab. | [`homelab/`](homelab/) | | [dotfiles](/dotfiles/) | Configuration and documentation for my PCs. |
| nix | Nix flake defining my PC & k3s cluster configurations | [`nix`](nix/) | | [homelab](/homelab/) | Configuration and documentation for my homelab. |
| Jafner.dev | Hugo static site configuration files for my [Jafner.dev](https://jafner.dev) blog. | [`blog/`](blog/) | | [projects](/projects/) | Self-contained projects in a variety of scripting and programming languages. |
| razer-bat | Indicate Razer mouse battery level with the RGB LEDs on the dock. Less metal than it sounds. | [`projects/razer-bat/`](projects/razer-bat/) | | [sites](/sites/) | Static site files |
| 5etools-docker | Docker image to make self-hosting 5eTools a little bit better. | [`projects/5etools-docker/`](projects/5etools-docker/) | | [.gitea/workflows](/.gitea/workflows/) & [.github/workflows](/.github/workflows/) | GitHub Actions workflows running on [Gitea](https://gitea.jafner.tools/Jafner/Jafner.net/actions) and [GitHub](https://github.com/Jafner/Jafner.net/actions), respectively. |
| 5eHomebrew | 5eTools-compatible homebrew content by me. | [`projects/5ehomebrew/`](projects/5ehomebrew/) | | [.sops](/.sops/) | Scripts and documentation implementing [sops](https://github.com/getsops/sops) to securely store secrets in this repo. |
| archive | Old, abandoned, unmaintained projects. Fun to look back at. | [`archive/`](archive/) |
## LICENSE: MIT License
> See [LICENSE](/LICENSE) for details.
## Contributing
Presently this project is a one-man operation with no external contributors. All contributions will be addressed in good faith on a best-effort basis.

View File

@ -1,2 +0,0 @@
DOMAIN=
EMAIL=

View File

@ -1,77 +0,0 @@
# Project No Longer Maintained
> There are better ways to do this.
> I recommend looking for general-purpose guides to self-hosting with Docker and Traefik. For 5eTools specifically, I do maintain [5etools-docker](https://github.com/Jafner/5etools-docker).
This guide will walk you through setting up an Oracle Cloud VM to host a personal instance of 5eTools using your own domain.
# Before Getting Started
1. You will need a domain! I used NameCheap to purchase my `.tools` domain for $7 per year.
2. You will need an [Oracle Cloud](https://www.oracle.com/cloud/) account - This will be used to create an Always Free cloud virtual machine, which will host the services we need. You will need to attach a credit card to your account. I used a [Privacy.com](https://privacy.com/) temporary card to ensure I wouldn't be charged accidentally at the end of the 30-day trial. The services used in this guide are under Oracle's Always Free category, so unless you exceed the 10TB monthly traffic alotment, you won't be charged.
3. You will need a [Cloudflare](https://www.cloudflare.com/) account - This will be used to manage the domain name after purchase. You will need to migrate your domain from the registrar you bought the domain from to Cloudflare.
4. An SSH terminal (I use Tabby (formerly Terminus)). This will be used to log into and manage the Oracle Cloud virtual machine.
# Walkthrough
## Purchase a domain name from a domain registrar.
I used NameCheap, which offered my `.tools` domain for $7 per year. Some top-level domains (TLDs) can be purchased for as little as $2-3 per year (such as `.xyz`, `.one`, or `.website`). Warning: these are usually 1-year special prices, and the price will increase significantly after the first year.
## Migrate your domain to Cloudflare.
The Cloudflare docs have a [domain transfer guide](https://developers.cloudflare.com/registrar/domain-transfers/transfer-to-cloudflare), which addresses how to do this. This process may take up to 24 hours. Cloudflare won't like that you are importing the domain without any DNS records, but that's okay.
## Create your Oracle Cloud virtual machine.
If you've already created your Oracle Cloud account, go to the [Oracle Cloud portal](https://cloud.oracle.com). Then under the "Launch Resources" section, click "Create a VM instance". Most of the default settings are fine. Click "Change Image" and uncheck Oracle Linux, then check Cannonical Ubuntu, then click "Select Image". Under "Add SSH keys", download the private key for the instance by clicking the "Save Private Key" button. Finally, click "Create". You will need to wait a while for the instance to come online.
## Move your SSH key.
Move your downloaded SSH key to your `.ssh` folder with `mkdir ~/.ssh/` and then `mv ~/Downloads/ssh-key-*.key ~/.ssh/`. If you already have a process for SSH key management, feel free to ignore this.
## SSH into your VM.
Once your Oracle Cloud VM is provisioned (created), SSH into it. Get its public IP address from the "Instance Access" section of the instance's details page. Then run `ssh -i ~/.ssh/ssh-key-<YOUR-KEY>.key ubuntu@<YOUR-INSTANCE-IP>`, replacing "<YOUR-KEY>" and "<YOUR-INSTANCE-IP>" with your key name and instance IP. (Tip: you can use tab to auto-complete the filename of the key). Then enter the command to connect to the instance.
## Set up the VM with all the software we need.
Now that we're in the terminal, you can just copy-paste commands to run. You can either run the following command (which is just a bunch of commands strung together), or run each command one at a time by following the lettered instructions below:
`sudo apt-get update && sudo apt-get upgrade -y && sudo apt-get -y install git docker docker-compose && sudo systemctl enable docker && sudo usermod -aG docker $USER && logout` OR:
### Update the system.
with `sudo apt-get update && sudo apt-get upgrade -y`.
### Install Git, Docker, and Docker Compose.
with `sudo apt-get -y install git docker docker-compose`.
### Enable the docker service.
with `sudo systemctl enable docker`.
### Add your user to the docker group.
with `sudo usermod -aG docker $USER`.
### Log out.
with `logout`.
## Configure the VM firewall.
On the "Compute -> Instances -> Instance Details" page, under "Instance Information -> Primary VNIC -> Subnet", click the link to the subnet's configuration page, then click on the default security list. Click "Add Ingress Rules", then "+ Another Ingress Rule" and fill out your ingress rules like this:
![ingress_rules.png](https://github.com/jafner/cloud_tools/blob/main/ingress_rules.PNG?raw=true)
This will allow incoming traffic from the internet on ports 80 and 443 (the ports used by HTTP and HTTPS respectively).
## Configure the Cloudflare DNS records.
After your domain has been transferred to Cloudflare, log into the [Cloudflare dashboard](https://dash.cloudflare.com) and click on your domain. Then click on the DNS button at the top, and click "Add record" with the following information:
* Type: A
* Name: 5e
* IPv4 Address: <YOUR-INSTANCE-IP>
* TTL: Auto
* Proxy status: DNS only
This will route `5e.your.domain` to <YOUR-INSTANCE-IP>. You can change the name to whatever you prefer, or use @ to use the root domain (just `your.domain`) instead. I found that using Cloudflare's proxy interferes with acquiring certificates.
## Log back into your VM and set up the services.
Clone this repository onto the host with `git clone https://github.com/jafner/cloud_tools.git`, then move into the directory with `cd cloud_tools/`. Edit the file `.env` with your domain (including subdomain) and email. For example:
```
DOMAIN=5e.your.domain
EMAIL=youremail@gmail.com
```
Make the setup script executable, then run it with `chmod +x setup.sh && ./setup.sh`.

View File

@ -1,29 +0,0 @@
version: "3"
services:
traefik:
container_name: traefik
image: traefik:latest
networks:
- web
ports:
- 80:80
- 443:443
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./traefik.toml:/traefik.toml
- ./acme.json:/acme.json
5etools:
container_name: 5etools
image: jafner/5etools-docker
volumes:
- ./htdocs:/usr/local/apache2/htdocs
networks:
- web
labels:
- traefik.http.routers.5etools.rule=Host(`$DOMAIN`)
- traefik.http.routers.5etools.tls.certresolver=lets-encrypt
networks:
web:
external: true

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

View File

@ -1,7 +0,0 @@
#!/bin/bash
docker network create web
sed -i "s/email = \"\"/email = \"$EMAIL\"/g" traefik.toml
mkdir -p ./htdocs/download
touch acme.json
chmod 600 acme.json
docker-compose up -d

View File

@ -1,18 +0,0 @@
[entryPoints]
[entryPoints.web]
address = ":80"
[entryPoints.web.http.redirections.entryPoint]
to = "websecure"
scheme = "https"
[entryPoints.websecure]
address = ":443"
[certificatesResolvers.lets-encrypt.acme]
email = ""
storage = "acme.json"
caServer = "https://acme-v02.api.letsencrypt.org/directory"
[certificatesResolvers.lets-encrypt.acme.tlsChallenge]
[providers.docker]
watch = true
network = "web"

View File

@ -1,19 +0,0 @@
# Assign test file names/paths
SOURCE_FILE=$(realpath ~/Git/Clip/TestClips/"x264Source.mkv") && echo "SOURCE_FILE: $SOURCE_FILE"
TRANSCODED_FILE="$(realpath ~/Git/Clip/TestClips)/TRANSCODED.mp4" && echo "TRANSCODED_FILE: $TRANSCODED_FILE"
# TRANSCODE $SOURCE_FILE into 'TRANSCODED.mp4'
ffmpeg -hide_banner -i "$SOURCE_FILE" -copyts -copytb 0 -map 0 -bf 0 -c:v libx264 -crf 23 -preset slow -video_track_timescale 1000 -g 60 -keyint_min 60 -bsf:v setts=ts=STARTPTS+N/TB_OUT/60 -c:a copy "$TRANSCODED_FILE"
# GET KEYFRAMES FROM $SOURCE_FILE
KEYFRAMES=( $(ffprobe -hide_banner -loglevel error -select_streams v:0 -show_entries packet=pts,flags -of csv=print_section=0 "$SOURCE_FILE" | grep K | cut -d',' -f 1) ) && echo "$KEYFRAMES"
ffprobe -hide_banner -loglevel error -select_streams v:0 -show_entries packet=pts,flags -of csv=print_section=0 "$SOURCE_FILE"
# GET KEYFRAMES FROM $TRANSCODED_FILE
KEYFRAMES=( $(ffprobe -hide_banner -loglevel error -select_streams v:0 -show_entries packet=pts,flags -of csv=print_section=0 "$TRANSCODED_FILE" | grep K | cut -d',' -f 1) ) && echo "$KEYFRAMES"
ffprobe -hide_banner -loglevel error -select_streams v:0 -show_entries packet=pts,flags -of csv=print_section=0 "$TRANSCODED_FILE"
# Compare keyframes between $SOURCE_FILE and $TRANSCODED_FILE
sdiff <(ffprobe -hide_banner -loglevel error -select_streams v:0 -show_entries packet=pts,flags -of csv=print_section=0 "$SOURCE_FILE") <(ffprobe -hide_banner -loglevel error -select_streams v:0 -show_entries packet=pts,flags -of csv=print_section=0 "$TRANSCODED_FILE")
https://code.videolan.org/videolan/x264/-/blob/master/common/base.c#L489

View File

@ -1,7 +0,0 @@
{
"folders": [
{
"path": "."
}
]
}

View File

@ -1,262 +0,0 @@
import tkinter as tk # https://docs.python.org/3/library/tkinter.html
from tkinter import filedialog, ttk
import av
import subprocess
from pathlib import Path
import time
import datetime
from PIL import Image, ImageTk, ImageOps
from RangeSlider.RangeSlider import RangeSliderH
from ffpyplayer.player import MediaPlayer
from probe import get_keyframes_list, get_keyframe_interval, get_video_duration
class VideoClipExtractor:
def __init__(self, master):
# Initialize variables
self.video_duration = int() # milliseconds
self.video_path = Path() # Path object
self.video_keyframes = list() # list of ints (keyframe pts in milliseconds)
self.clip_start = tk.IntVar(value = 0) # milliseconds
self.clip_end = tk.IntVar(value = 1) # milliseconds
self.preview_image_timestamp = tk.IntVar(value = 0) # milliseconds
self.debug_checkvar = tk.IntVar() # Checkbox variable
self.background_color = "#BBBBBB"
self.text_color = "#000000"
self.preview_background_color = "#2222FF"
# Set up master UI
self.master = master
self.master.title("Video Clip Extractor")
self.master.configure(background=self.background_color)
self.master.resizable(False, False)
self.master.geometry("")
self.window_max_width = self.master.winfo_screenwidth()*0.75
self.window_max_height = self.master.winfo_screenheight()*0.75
self.preview_width = 1280
self.preview_height = 720
self.preview_image = Image.new("RGB", (self.preview_width, self.preview_height), color=self.background_color)
self.preview_image_tk = ImageTk.PhotoImage(self.preview_image)
self.timeline_width = self.preview_width
self.timeline_height = 64
self.interface_width = self.preview_width
self.interface_height = 200
# Initialize frames, buttons and labels
self.preview_frame = tk.Frame(self.master, width=self.preview_width, height=self.preview_height, bg=self.preview_background_color, borderwidth=0, bd=0)
self.timeline_frame = tk.Frame(self.master, width=self.timeline_width, height=self.timeline_height, bg=self.background_color)
self.interface_pane = tk.Frame(self.master, width=self.interface_width, height=self.interface_height, bg=self.background_color)
self.buttons_pane = tk.Frame(self.interface_pane, bg=self.background_color)
self.info_pane = tk.Frame(self.interface_pane, bg=self.background_color)
self.preview_canvas = tk.Canvas(self.preview_frame, width=self.preview_width, height=self.preview_height, bg=self.preview_background_color, borderwidth=0, bd=0)
self.browse_button = tk.Button(self.buttons_pane, text="Browse...", command=self.browse_video_file, background=self.background_color, foreground=self.text_color)
self.extract_button = tk.Button(self.buttons_pane, text="Extract Clip", command=self.extract_clip, background=self.background_color, foreground=self.text_color)
self.debug_checkbutton = tk.Checkbutton(self.buttons_pane, text="Print ffmpeg to console", variable=self.debug_checkvar, background=self.background_color, foreground=self.text_color)
self.preview_button = tk.Button(self.buttons_pane, text="Preview Clip", command=self.ffplaySegment, background=self.background_color, foreground=self.text_color)
self.video_path_label = tk.Label(self.info_pane, text=f"Source video: {self.video_path}", background=self.background_color, foreground=self.text_color)
self.clip_start_label = tk.Label(self.timeline_frame, text=f"{self.timeStr(self.clip_start.get())}", background=self.background_color, foreground=self.text_color)
self.clip_end_label = tk.Label(self.timeline_frame, text=f"{self.timeStr(self.clip_end.get())}", background=self.background_color, foreground=self.text_color)
self.video_duration_label = tk.Label(self.info_pane, text=f"Video duration: {self.timeStr(self.video_duration)}", background=self.background_color, foreground=self.text_color)
self.timeline_canvas = tk.Canvas(self.timeline_frame, width=self.preview_width, height=self.timeline_height, background=self.background_color)
self.timeline = RangeSliderH(
self.timeline_canvas,
[self.clip_start, self.clip_end],
max_val=max(self.video_duration,1),
show_value=False,
bgColor=self.background_color,
Width=self.timeline_width,
Height=self.timeline_height
)
self.preview_label = tk.Label(self.preview_frame, image=self.preview_image_tk)
print(f"Widget widths (after pack):\n\
self.clip_start_label.winfo_width(): {self.clip_start_label.winfo_width()}\n\
self.clip_end_label.winfo_width(): {self.clip_end_label.winfo_width()}\n\
self.timeline.winfo_width(): {self.timeline.winfo_width()}\n\
")
# Arrange frames inside master window
self.preview_frame.pack(side='top', fill='both', expand=True, padx=0, pady=0)
self.timeline_frame.pack(fill='x', expand=True, padx=20, pady=20)
self.interface_pane.pack(side='bottom', fill='both', expand=True, padx=10, pady=10)
self.buttons_pane.pack(side='left')
self.info_pane.pack(side='right')
# Draw elements inside frames
self.browse_button.pack(side='top')
self.extract_button.pack(side='top')
self.preview_button.pack(side='top')
self.debug_checkbutton.pack(side='top')
self.video_path_label.pack(side='top')
self.clip_start_label.pack(side='left')
self.clip_end_label.pack(side='right')
self.video_duration_label.pack(side='top')
self.preview_label.pack(fill='both', expand=True)
# Draw timeline canvas and timeline slider
self.timeline_canvas.pack(fill="both", expand=True)
self.timeline.pack(fill="both", expand=True)
print(f"Widget widths (after pack):\n\
self.clip_start_label.winfo_width(): {self.clip_start_label.winfo_width()}\n\
self.clip_end_label.winfo_width(): {self.clip_end_label.winfo_width()}\n\
self.timeline.winfo_width(): {self.timeline.winfo_width()}\n\
")
def getThumbnail(self):
with av.open(str(self.video_path)) as container:
time_ms = self.clip_start.get() # This works as long as container has a timebase of 1/1000
container.seek(time_ms, stream=container.streams.video[0])
time.sleep(0.1)
frame = next(container.decode(video=0)) # Get the frame object for the seeked timestamp
if self.preview_image_timestamp != time_ms:
self.preview_image_tk = ImageTk.PhotoImage(frame.to_image(width=self.preview_width, height=self.preview_height)) # Convert the frame object to an image
self.preview_label.config(image=self.preview_image_tk)
self.preview_image_timestamp = time_ms
def ffplaySegment(self):
ffplay_command = [
"ffplay",
"-hide_banner",
"-autoexit",
"-volume", "10",
"-window_title", f"{self.timeStr(self.clip_start.get())} to {self.timeStr(self.clip_end.get())}",
"-x", "1280",
"-y", "720",
"-ss", f"{self.clip_start.get()}ms",
"-i", str(self.video_path),
"-t", f"{self.clip_end.get() - self.clip_start.get()}ms"
]
print("Playing video. Press \"q\" or \"Esc\" to exit.")
print("")
subprocess.run(ffplay_command, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
def redrawTimeline(self):
self.timeline.forget()
step_size = get_keyframe_interval(self.video_keyframes)
step_marker = False
if len(self.video_keyframes) < self.timeline_width/4 and step_size > 0:
step_marker = True
self.timeline = RangeSliderH(
self.timeline_canvas,
[self.clip_start, self.clip_end],
max_val=max(self.video_duration,1),
step_marker=step_marker,
step_size=step_size,
show_value=False,
bgColor=self.background_color,
Width=self.timeline_width,
Height=self.timeline_height
)
self.timeline.pack()
#self.preview_canvas.create_text(self.preview_canvas.winfo_width() // 2, self.preview_canvas.winfo_height() // 2, text=f"Loading video...", fill="black", font=("Helvetica", 48))
def timeStr(self, milliseconds: int): # Takes milliseconds int or float and returns a string in the preferred format
h = int(milliseconds/3600000) # Get the hours component
m = int((milliseconds%3600000)/60000) # Get the minutes component
s = int((milliseconds%60000)/1000) # Get the seconds component
ms = int(milliseconds%1000) # Get the milliseconds component
if milliseconds < 60000:
return f"{s}.{ms:03}"
elif milliseconds < 3600000:
return f"{m}:{s:02}.{ms:03}"
else:
return f"{h}:{m:02}:{s:02}.{ms:03}"
def clip_selector(self):
def updateClipRange(var, index, mode):
clip_end = self.clip_end.get()
nearest_keyframe_start = self.nearest_keyframe(self.clip_start.get(), self.video_keyframes)
# Add a specific check to make sure that the clip end is not changing to be equal to or less than the clip start
if clip_end <= nearest_keyframe_start:
clip_end = nearest_keyframe_start + self.timeline.__dict__['step_size']
self.clip_start_label.config(text=f"{self.timeStr(nearest_keyframe_start)}")
self.clip_end_label.config(text=f"{self.timeStr(clip_end)}")
self.timeline.forceValues([nearest_keyframe_start, clip_end])
self.getThumbnail()
if str(self.video_path) == "()":
return False
self.clip_start.trace_add("write", callback=updateClipRange) # This actually triggers on both start and end
def nearest_keyframe(self, test_pts: int, valid_pts: list):
return(min(valid_pts, key=lambda x:abs(x-float(test_pts))))
def browse_video_file(self):
video_path = filedialog.askopenfilename(
initialdir="~/Git/Clip/TestClips/",
title="Select file",
filetypes=(("mp4/mkv files", '*.mp4 *.mkv'), ("all files", "*.*"))
)
print(f"video path: \"{video_path}\" (type: {type(video_path)})")
if not Path(str(video_path)).is_file():
return
video_keyframes = get_keyframes_list(video_path)
while video_keyframes == None:
print(f"No keyframes found in {video_path}. Choose a different video file.")
video_path = filedialog.askopenfilename(
initialdir="~/Git/Clip/TestClips/",
title="Select file",
filetypes=(("mp4/mkv files", '*.mp4 *.mkv'), ("all files", "*.*"))
)
# Once we have a video file, we need to set the Source video, Clip start, Clip end, and Video duration values and redraw the GUI.
self.video_path = Path(video_path)
self.video_duration = get_video_duration(video_path)
self.video_keyframes = video_keyframes
self.clip_start.set(min(self.video_keyframes))
self.clip_end.set(max(self.video_keyframes))
self.clip_start_label.config(text=f"{self.timeStr(self.nearest_keyframe(self.clip_start.get(), self.video_keyframes))}")
self.clip_end_label.config(text=f"{self.timeStr(self.clip_end.get())}")
self.getThumbnail()
self.video_path_label.config(text=f"Source video: {self.video_path}")
self.video_duration_label.config(text=f"Video duration: {self.timeStr(self.video_duration)}")
self.redrawTimeline()
self.clip_selector()
def extract_clip(self):
video_path = self.video_path
file_extension = video_path.suffix
clip_start = self.clip_start.get()
clip_end = self.clip_end.get()
output_path = Path(
filedialog.asksaveasfilename(
initialdir=video_path.parent,
initialfile=str(
f"[Clip] {video_path.stem} ({datetime.timedelta(milliseconds=clip_start)}-{datetime.timedelta(milliseconds=clip_end)}){file_extension}"),
title="Select output file",
defaultextension=file_extension
)
)
if output_path == Path("."):
return False
ffmpeg_command = [
"ffmpeg",
"-y", # The output path prompt asks for confirmation before overwriting
"-hide_banner",
"-i", str(video_path),
"-ss", f"{clip_start}ms",
"-to", f"{clip_end}ms",
"-map", "0",
"-c:v", "copy",
"-c:a", "copy",
str(output_path),
]
if self.debug_checkvar.get() == 1:
subprocess.run(ffmpeg_command)
else:
subprocess.run(ffmpeg_command, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
print(f"Finished! Saved to {output_path}")
root = tk.Tk()
app = VideoClipExtractor(root)
root.mainloop()

View File

@ -1,35 +0,0 @@
# Version using ffPyPlayer
from pathlib import Path
import tkinter as tk
import time
from ffpyplayer.player import MediaPlayer
def ffplaySegment(file: Path, start: int, end: int):
print(f"Playing {file} from {start}ms to {end}ms")
file = str(file) # Must be string
seek_to = float(start)/1000
play_for = float(end - start)/1000
x = int(1280)
y = int(720)
volume = float(0.2) # Float 0.0 to 1.0
# Must be dict
ff_opts = {
"paused": False, # Bool
"t": play_for, # Float seconds
"ss": seek_to, # Float seconds
"x": x,
"y": y,
"volume": volume
}
val = ''
player = MediaPlayer(file, ff_opts=ff_opts)
while val != 'eof':
frame, val = player.get_frame()
print(f"frame: (type: {type(frame)})", end=', ')
if val != 'eof' and frame is not None:
img, t = frame
print(f"img: (type: {type(img)})", end=', ')
print(f"t: (type: {type(t)})")
# Use the create_image method of the canvas widget to draw the image to the canvas.

View File

@ -1,43 +0,0 @@
import subprocess
from pathlib import Path
import numpy as np
import ffmpeg
# Get a list of keyframes by pts (milliseconds from start) from a video file.
def get_keyframes_list(video_path):
ffprobe_command = [
"ffprobe",
"-hide_banner",
"-loglevel", "error",
"-skip_frame", "nokey",
"-select_streams", "v:0",
"-show_entries", "packet=pts,flags",
"-of", "csv=print_section=0",
video_path
]
ffprobe_output = subprocess.run(ffprobe_command, capture_output=True, text=True)
keyframes = list(map(int, np.array([line.split(",") for line in list(filter(lambda frame_packet: "K" in frame_packet, ffprobe_output.stdout.splitlines()))])[:,0]))
if len(keyframes) <= 1:
# Pop up a warning if there are no keyframes.
return(None)
return(keyframes)
def get_keyframe_interval(keyframes_list: list): # Takes a list of ints representing keyframe pts (milliseconds from start) and returns either the keyframe interval in milliseconds, or None if the keyframe intervals are not all the same.
intervals = list(np.diff(keyframes_list)) # List of keyframe intervals in milliseconds.
if np.all(intervals == intervals[0]):
return(intervals[0])
else:
return(0)
def get_keyframe_intervals(keyframes_list):
# Return a list of keyframe intervals in milliseconds.
return(list(np.diff(keyframes_list)))
def keyframe_intervals_are_clean(keyframe_intervals):
# Return whether the keyframe intervals are all the same.
return(np.all(keyframe_intervals == keyframe_intervals[0]))
# Get the duration of a video file in milliseconds (useful for ffmpeg pts).
def get_video_duration(video_path):
return int(float(ffmpeg.probe(video_path)["format"]["duration"])*1000)

View File

@ -1,6 +0,0 @@
av==12.0.0
ffmpeg-python==0.2.0
ffpyplayer==4.5.1
numpy==1.26.4
pillow==10.3.0
RangeSlider==2023.7.2

View File

@ -1,21 +0,0 @@
import av
with av.open("TestClips/x264Source.mkv") as container:
frame_num = 622
time_base = container.streams.video[0].time_base
framerate = container.streams.video[0].average_rate
timestamp = frame_num/framerate
rounded_pts = round((frame_num / framerate) / time_base)
print(f"Variables:\n\
frame_num: {frame_num} (type: {type(frame_num)}\n\
time_base: {time_base} (type: {type(time_base)}\n\
timestamp: {timestamp} (type: {type(timestamp)}\n\
frame_num / framerate: {frame_num / framerate}\n\
frame_num / time_base: {frame_num / time_base}\n\
(frame_num / framerate) / time_base: {(frame_num / framerate) / time_base}\n\
rounded_pts = {rounded_pts}\
")
container.seek(rounded_pts, backward=True, stream=container.streams.video[0])
frame = next(container.decode(video=0))
frame.to_image().save("TestClips/Thumbnail3.jpg".format(frame.pts), quality=80)

View File

@ -1,3 +0,0 @@
DOCKER_DATA=/home/joey/docker_data/arr
DOWNLOAD_DIR=/mnt/torrenting/NZB
INCOMPLETE_DOWNLOAD_DIR=/mnt/torrenting/NZB_incomplete

View File

@ -1,82 +0,0 @@
version: "3"
services:
radarr:
image: linuxserver/radarr
container_name: radarr
networks:
- web
environment:
- PUID=1000
- PGID=1000
- TZ=America/Los_Angeles
volumes:
- /mnt/nas/Video/Movies:/movies
- "${DOCKER_DATA}/radarr_config:/config"
- "${DOWNLOAD_DIR}:/downloads"
labels:
- traefik.http.routers.radarr.rule=Host(`radarr.jafner.net`)
- traefik.http.routers.radarr.tls.certresolver=lets-encrypt
- traefik.http.services.radarr.loadbalancer.server.port=7878
- traefik.http.routers.radarr.middlewares=lan-only@file
sonarr:
image: linuxserver/sonarr
container_name: sonarr
networks:
- web
environment:
- PUID=1000
- PGID=1000
- TZ=America/Los_Angeles
volumes:
- /mnt/nas/Video/Shows:/shows
- "${DOCKER_DATA}/sonarr_config:/config"
- "${DOWNLOAD_DIR}:/downloads"
labels:
- traefik.http.routers.sonarr.rule=Host(`sonarr.jafner.net`)
- traefik.http.routers.sonarr.tls.certresolver=lets-encrypt
- traefik.http.services.sonarr.loadbalancer.server.port=8989
- traefik.http.routers.sonarr.middlewares=lan-only@file
nzbhydra2:
image: linuxserver/nzbhydra2
container_name: nzbhydra2
networks:
- web
environment:
- PUID=1000
- PGID=1000
- TZ=America/Los_Angeles
volumes:
- "${DOCKER_DATA}/nzbhydra2_config:/config"
- "${DOWNLOAD_DIR}:/downloads"
labels:
- traefik.http.routers.nzbhydra2.rule=Host(`nzbhydra.jafner.net`)
- traefik.http.routers.nzbhydra2.tls.certresolver=lets-encrypt
- traefik.http.services.nzbhydra2.loadbalancer.server.port=5076
- traefik.http.routers.nzbhydra2.middlewares=lan-only@file
sabnzbd:
image: linuxserver/sabnzbd
container_name: sabnzbd
networks:
- web
environment:
- PUID=1000
- PGID=1000
- TZ=America/Los_Angeles
ports:
- 8085:8080
volumes:
- "${DOCKER_DATA}/sabnzbd_config:/config"
- "${DOWNLOAD_DIR}:/downloads"
- "${INCOMPLETE_DOWNLOAD_DIR}:/incomplete-downloads"
labels:
- traefik.http.routers.sabnzbd.rule=Host(`sabnzbd.jafner.net`)
- traefik.http.routers.sabnzbd.tls.certresolver=lets-encrypt
- traefik.http.services.sabnzbd.loadbalancer.server.port=8080
- traefik.http.routers.sabnzbd.middlewares=lan-only@file
networks:
web:
external: true

View File

@ -1 +0,0 @@
LIBRARY_DIR=/mnt/nas/Ebooks/Calibre

View File

@ -1,40 +0,0 @@
version: '3'
services:
calibre-web-rpg:
image: linuxserver/calibre-web
container_name: calibre-web-rpg
environment:
- PUID=1000
- PGID=1000
- TZ=America/Los_Angeles
volumes:
- calibre-web-rpg_data:/config
- /mnt/calibre/rpg:/books
labels:
- traefik.http.routers.calibre-rpg.rule=Host(`rpg.jafner.net`)
- traefik.http.routers.calibre-rpg.tls.certresolver=lets-encrypt
networks:
- web
calibre-web-sff:
image: linuxserver/calibre-web
container_name: calibre-web-sff
environment:
- PUID=1000
- PGID=1000
- TZ=America/Los_Angeles
volumes:
- calibre-web-sff_data:/config
- /mnt/calibre/sff:/books
labels:
- traefik.http.routers.calibre.rule=Host(`calibre.jafner.net`)
- traefik.http.routers.calibre.tls.certresolver=lets-encrypt
networks:
- web
networks:
web:
external: true
volumes:
calibre-web-rpg_data:
calibre-web-sff_data:

View File

@ -1,12 +0,0 @@
version: "3"
services:
cloudflare-ddns:
image: oznu/cloudflare-ddns
container_name: cloudflare-ddns
restart: unless-stopped
environment:
- API_KEY=***REMOVED***
- ZONE=jafner.net
- SUBDOMAIN=*
labels:
- traefik.enable=false

View File

@ -1 +0,0 @@
DRAWIO_BASE_URL=https://draw.jafner.net

View File

@ -1,74 +0,0 @@
version: '3'
services:
plantuml-server:
image: jgraph/plantuml-server
container_name: drawio_plantuml-server
restart: unless-stopped
expose:
- "8080"
networks:
- drawionet
volumes:
- fonts_volume:/usr/share/fonts/drawio
image-export:
image: jgraph/export-server
container_name: drawio_export-server
restart: unless-stopped
expose:
- "8000"
networks:
- drawionet
volumes:
- fonts_volume:/usr/share/fonts/drawio
environment:
- DRAWIO_SERVER_URL=${DRAWIO_BASE_URL}
drawio:
image: jgraph/drawio
container_name: drawio_drawio
links:
- plantuml-server:plantuml-server
- image-export:image-export
depends_on:
- plantuml-server
- image-export
networks:
- drawionet
- web
environment:
- DRAWIO_SELF_CONTAINED=1
- PLANTUML_URL=http://plantuml-server:8080/
- EXPORT_URL=http://image-export:8000/
- DRAWIO_BASE_URL=${DRAWIO_BASE_URL}
- DRAWIO_CSP_HEADER=${DRAWIO_CSP_HEADER}
- DRAWIO_VIEWER_URL=${DRAWIO_VIEWER_URL}
- DRAWIO_CONFIG=${DRAWIO_CONFIG}
- DRAWIO_GOOGLE_CLIENT_ID=${DRAWIO_GOOGLE_CLIENT_ID}
- DRAWIO_GOOGLE_APP_ID=${DRAWIO_GOOGLE_APP_ID}
- DRAWIO_GOOGLE_CLIENT_SECRET=${DRAWIO_GOOGLE_CLIENT_SECRET}
- DRAWIO_GOOGLE_VIEWER_CLIENT_ID=${DRAWIO_GOOGLE_VIEWER_CLIENT_ID}
- DRAWIO_GOOGLE_VIEWER_APP_ID=${DRAWIO_GOOGLE_VIEWER_APP_ID}
- DRAWIO_GOOGLE_VIEWER_CLIENT_SECRET=${DRAWIO_GOOGLE_VIEWER_CLIENT_SECRET}
- DRAWIO_MSGRAPH_CLIENT_ID=${DRAWIO_MSGRAPH_CLIENT_ID}
- DRAWIO_MSGRAPH_CLIENT_SECRET=${DRAWIO_MSGRAPH_CLIENT_SECRET}
- DRAWIO_GITLAB_ID=${DRAWIO_GITLAB_ID}
- DRAWIO_GITLAB_URL=${DRAWIO_GITLAB_URL}
- DRAWIO_CLOUD_CONVERT_APIKEY=${DRAWIO_CLOUD_CONVERT_APIKEY}
- DRAWIO_CACHE_DOMAIN=${DRAWIO_CACHE_DOMAIN}
- DRAWIO_MEMCACHED_ENDPOINT=${DRAWIO_MEMCACHED_ENDPOINT}
- DRAWIO_PUSHER_MODE=2
- DRAWIO_IOT_ENDPOINT=${DRAWIO_IOT_ENDPOINT}
- DRAWIO_IOT_CERT_PEM=${DRAWIO_IOT_CERT_PEM}
- DRAWIO_IOT_PRIVATE_KEY=${DRAWIO_IOT_PRIVATE_KEY}
- DRAWIO_IOT_ROOT_CA=${DRAWIO_IOT_ROOT_CA}
- DRAWIO_MXPUSHER_ENDPOINT=${DRAWIO_MXPUSHER_ENDPOINT}
labels:
- traefik.http.routers.drawio.rule=Host(`draw.jafner.net`)
- traefik.http.routers.drawio.tls.certresolver=lets-encrypt
networks:
drawionet:
web:
external: true
volumes:
fonts_volume:

View File

@ -1,5 +0,0 @@
#!/bin/bash
cd /home/joey/docker_config/
git add --all
git commit -am "$(date)"
git push

View File

@ -1,2 +0,0 @@
DOCKER_DATA=/home/joey/docker_data/grafana-stack
MINECRAFT_DIR=/home/joey/docker_data/minecraft

View File

@ -1,63 +0,0 @@
version: '3'
services:
influxdb:
image: influxdb:latest
container_name: influxdb
restart: unless-stopped
networks:
- monitoring
ports:
- 8086:8086
- 8089:8089/udp
volumes:
- ./influxdb.conf:/etc/influxdb/influxdb.conf:ro
- "${DOCKER_DATA}/influxdb:/var/lib/influxdb"
environment:
- TZ=America/Los_Angeles
- INFLUXDB_HTTP_ENABLED=true
- INFLUXDB_DB=host
command: -config /etc/influxdb/influxdb.conf
telegraf:
image: telegraf:latest
container_name: telegraf
restart: unless-stopped
depends_on:
- influxdb
networks:
- monitoring
volumes:
- ./telegraf.conf:/etc/telegraf/telegraf.conf:ro
- ./scripts/.forgetps.json:/.forgetps.json:ro
- /var/run/docker.sock:/var/run/docker.sock:ro
- /sys:/rootfs/sys:ro
- /proc:/rootfs/proc:ro
- /etc:/rootfs/etc:ro
grafana:
image: mbarmem/grafana-render:latest
container_name: grafana
restart: unless-stopped
depends_on:
- influxdb
- telegraf
networks:
- monitoring
- web
user: "0"
volumes:
- ./grafana:/var/lib/grafana
- ./grafana.ini:/etc/grafana/grafana.ini
environment:
- GF_INSTALL_PLUGINS=grafana-clock-panel,grafana-simple-json-datasource,grafana-worldmap-panel,grafana-piechart-panel
labels:
- traefik.http.routers.grafana.rule=Host(`grafana.jafner.net`)
- traefik.http.routers.grafana.tls.certresolver=lets-encrypt
#- traefik.http.routers.grafana.middlewares=authelia@file
networks:
monitoring:
external: true
web:
external: true

View File

@ -1,622 +0,0 @@
##################### Grafana Configuration Example #####################
#
# Everything has defaults so you only need to uncomment things you want to
# change
# possible values : production, development
;app_mode = production
# instance name, defaults to HOSTNAME environment variable value or hostname if HOSTNAME var is empty
;instance_name = ${HOSTNAME}
#################################### Paths ####################################
[paths]
# Path to where grafana can store temp files, sessions, and the sqlite3 db (if that is used)
;data = /var/lib/grafana
# Temporary files in `data` directory older than given duration will be removed
;temp_data_lifetime = 24h
# Directory where grafana can store logs
;logs = /var/log/grafana
# Directory where grafana will automatically scan and look for plugins
;plugins = /var/lib/grafana/plugins
# folder that contains provisioning config files that grafana will apply on startup and while running.
;provisioning = conf/provisioning
#################################### Server ####################################
[server]
# Protocol (http, https, h2, socket)
;protocol = http
# The ip address to bind to, empty will bind to all interfaces
;http_addr =
# The http port to use
;http_port = 3000
# The public facing domain name used to access grafana from a browser
;domain = localhost
# Redirect to correct domain if host header does not match domain
# Prevents DNS rebinding attacks
;enforce_domain = false
# The full public facing url you use in browser, used for redirects and emails
# If you use reverse proxy and sub path specify full url (with sub path)
;root_url = http://localhost:3000
# Serve Grafana from subpath specified in `root_url` setting. By default it is set to `false` for compatibility reasons.
;serve_from_sub_path = false
# Log web requests
;router_logging = false
# the path relative working path
;static_root_path = public
# enable gzip
;enable_gzip = false
# https certs & key file
;cert_file =
;cert_key =
# Unix socket path
;socket =
#################################### Database ####################################
[database]
# You can configure the database connection by specifying type, host, name, user and password
# as separate properties or as on string using the url properties.
# Either "mysql", "postgres" or "sqlite3", it's your choice
;type = sqlite3
;host = 127.0.0.1:3306
;name = grafana
;user = root
# If the password contains # or ; you have to wrap it with triple quotes. Ex """#password;"""
;password =
# Use either URL or the previous fields to configure the database
# Example: mysql://user:secret@host:port/database
;url =
# For "postgres" only, either "disable", "require" or "verify-full"
;ssl_mode = disable
# For "sqlite3" only, path relative to data_path setting
;path = grafana.db
# Max idle conn setting default is 2
;max_idle_conn = 2
# Max conn setting default is 0 (mean not set)
;max_open_conn =
# Connection Max Lifetime default is 14400 (means 14400 seconds or 4 hours)
;conn_max_lifetime = 14400
# Set to true to log the sql calls and execution times.
;log_queries =
# For "sqlite3" only. cache mode setting used for connecting to the database. (private, shared)
;cache_mode = private
#################################### Cache server #############################
[remote_cache]
# Either "redis", "memcached" or "database" default is "database"
;type = database
# cache connectionstring options
# database: will use Grafana primary database.
# redis: config like redis server e.g. `addr=127.0.0.1:6379,pool_size=100,db=0,ssl=false`. Only addr is required. ssl may be 'true', 'false', or 'insecure'.
# memcache: 127.0.0.1:11211
;connstr =
#################################### Data proxy ###########################
[dataproxy]
# This enables data proxy logging, default is false
;logging = false
# How long the data proxy should wait before timing out default is 30 (seconds)
;timeout = 30
# If enabled and user is not anonymous, data proxy will add X-Grafana-User header with username into the request, default is false.
;send_user_header = false
#################################### Analytics ####################################
[analytics]
# Server reporting, sends usage counters to stats.grafana.org every 24 hours.
# No ip addresses are being tracked, only simple counters to track
# running instances, dashboard and error counts. It is very helpful to us.
# Change this option to false to disable reporting.
;reporting_enabled = true
# Set to false to disable all checks to https://grafana.net
# for new vesions (grafana itself and plugins), check is used
# in some UI views to notify that grafana or plugin update exists
# This option does not cause any auto updates, nor send any information
# only a GET request to http://grafana.com to get latest versions
;check_for_updates = true
# Google Analytics universal tracking code, only enabled if you specify an id here
;google_analytics_ua_id =
# Google Tag Manager ID, only enabled if you specify an id here
;google_tag_manager_id =
#################################### Security ####################################
[security]
# default admin user, created on startup
;admin_user = admin
admin_user = jafner
# default admin password, can be changed before first start of grafana, or in profile settings
;admin_password = admin
admin_password = joeyyeoj
# used for signing
;secret_key = ***REMOVED***
# disable gravatar profile images
;disable_gravatar = false
# data source proxy whitelist (ip_or_domain:port separated by spaces)
;data_source_proxy_whitelist =
# disable protection against brute force login attempts
;disable_brute_force_login_protection = false
# set to true if you host Grafana behind HTTPS. default is false.
;cookie_secure = false
# set cookie SameSite attribute. defaults to `lax`. can be set to "lax", "strict" and "none"
;cookie_samesite = lax
# set to true if you want to allow browsers to render Grafana in a <frame>, <iframe>, <embed> or <object>. default is false.
;allow_embedding = false
# Set to true if you want to enable http strict transport security (HSTS) response header.
# This is only sent when HTTPS is enabled in this configuration.
# HSTS tells browsers that the site should only be accessed using HTTPS.
# The default version will change to true in the next minor release, 6.3.
;strict_transport_security = false
# Sets how long a browser should cache HSTS. Only applied if strict_transport_security is enabled.
;strict_transport_security_max_age_seconds = 86400
# Set to true if to enable HSTS preloading option. Only applied if strict_transport_security is enabled.
;strict_transport_security_preload = false
# Set to true if to enable the HSTS includeSubDomains option. Only applied if strict_transport_security is enabled.
;strict_transport_security_subdomains = false
# Set to true to enable the X-Content-Type-Options response header.
# The X-Content-Type-Options response HTTP header is a marker used by the server to indicate that the MIME types advertised
# in the Content-Type headers should not be changed and be followed. The default will change to true in the next minor release, 6.3.
;x_content_type_options = false
# Set to true to enable the X-XSS-Protection header, which tells browsers to stop pages from loading
# when they detect reflected cross-site scripting (XSS) attacks. The default will change to true in the next minor release, 6.3.
;x_xss_protection = false
#################################### Snapshots ###########################
[snapshots]
# snapshot sharing options
;external_enabled = true
;external_snapshot_url = https://snapshots-origin.raintank.io
;external_snapshot_name = Publish to snapshot.raintank.io
# Set to true to enable this Grafana instance act as an external snapshot server and allow unauthenticated requests for
# creating and deleting snapshots.
;public_mode = false
# remove expired snapshot
;snapshot_remove_expired = true
#################################### Dashboards History ##################
[dashboards]
# Number dashboard versions to keep (per dashboard). Default: 20, Minimum: 1
;versions_to_keep = 20
#################################### Users ###############################
[users]
# disable user signup / registration
;allow_sign_up = true
# Allow non admin users to create organizations
;allow_org_create = true
# Set to true to automatically assign new users to the default organization (id 1)
;auto_assign_org = true
# Default role new users will be automatically assigned (if disabled above is set to true)
;auto_assign_org_role = Viewer
# Background text for the user field on the login page
;login_hint = email or username
;password_hint = password
# Default UI theme ("dark" or "light")
;default_theme = dark
# External user management, these options affect the organization users view
;external_manage_link_url =
;external_manage_link_name =
;external_manage_info =
# Viewers can edit/inspect dashboard settings in the browser. But not save the dashboard.
;viewers_can_edit = false
# Editors can administrate dashboard, folders and teams they create
;editors_can_admin = false
[auth]
# Login cookie name
;login_cookie_name = grafana_session
# The lifetime (days) an authenticated user can be inactive before being required to login at next visit. Default is 7 days,
;login_maximum_inactive_lifetime_days = 7
login_maximum_inactive_lifetime_days = 999
# The maximum lifetime (days) an authenticated user can be logged in since login time before being required to login. Default is 30 days.
;login_maximum_lifetime_days = 30
login_maximum_lifetime_days = 999
# How often should auth tokens be rotated for authenticated users when being active. The default is each 10 minutes.
;token_rotation_interval_minutes = 10
# Set to true to disable (hide) the login form, useful if you use OAuth, defaults to false
;disable_login_form = false
# Set to true to disable the signout link in the side menu. useful if you use auth.proxy, defaults to false
;disable_signout_menu = false
# URL to redirect the user to after sign out
;signout_redirect_url =
# Set to true to attempt login with OAuth automatically, skipping the login screen.
# This setting is ignored if multiple OAuth providers are configured.
;oauth_auto_login = false
#################################### Anonymous Auth ######################
[auth.anonymous]
# enable anonymous access
;enabled = false
enabled = true
# specify organization name that should be used for unauthenticated users
;org_name = Main Org.
# specify role for unauthenticated users
;org_role = Viewer
#################################### Github Auth ##########################
[auth.github]
;enabled = false
;allow_sign_up = true
;client_id = some_id
;client_secret = some_secret
;scopes = user:email,read:org
;auth_url = https://github.com/login/oauth/authorize
;token_url = https://github.com/login/oauth/access_token
;api_url = https://api.github.com/user
;team_ids =
;allowed_organizations =
#################################### Google Auth ##########################
[auth.google]
;enabled = false
;allow_sign_up = true
;client_id = some_client_id
;client_secret = some_client_secret
;scopes = https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email
;auth_url = https://accounts.google.com/o/oauth2/auth
;token_url = https://accounts.google.com/o/oauth2/token
;api_url = https://www.googleapis.com/oauth2/v1/userinfo
;allowed_domains =
#################################### Generic OAuth ##########################
[auth.generic_oauth]
;enabled = false
;name = OAuth
;allow_sign_up = true
;client_id = some_id
;client_secret = some_secret
;scopes = user:email,read:org
;email_attribute_name = email:primary
;email_attribute_path =
;auth_url = https://foo.bar/login/oauth/authorize
;token_url = https://foo.bar/login/oauth/access_token
;api_url = https://foo.bar/user
;team_ids =
;allowed_organizations =
;tls_skip_verify_insecure = false
;tls_client_cert =
;tls_client_key =
;tls_client_ca =
; Set to true to enable sending client_id and client_secret via POST body instead of Basic authentication HTTP header
; This might be required if the OAuth provider is not RFC6749 compliant, only supporting credentials passed via POST payload
;send_client_credentials_via_post = false
#################################### SAML Auth ###########################
[auth.saml] # Enterprise only
# Defaults to false. If true, the feature is enabled.
;enabled = false
# Base64-encoded public X.509 certificate. Used to sign requests to the IdP
;certificate =
# Path to the public X.509 certificate. Used to sign requests to the IdP
;certificate_path =
# Base64-encoded private key. Used to decrypt assertions from the IdP
;private_key =
;# Path to the private key. Used to decrypt assertions from the IdP
;private_key_path =
# Base64-encoded IdP SAML metadata XML. Used to verify and obtain binding locations from the IdP
;idp_metadata =
# Path to the SAML metadata XML. Used to verify and obtain binding locations from the IdP
;idp_metadata_path =
# URL to fetch SAML IdP metadata. Used to verify and obtain binding locations from the IdP
;idp_metadata_url =
# Duration, since the IdP issued a response and the SP is allowed to process it. Defaults to 90 seconds.
;max_issue_delay = 90s
# Duration, for how long the SP's metadata should be valid. Defaults to 48 hours.
;metadata_valid_duration = 48h
# Friendly name or name of the attribute within the SAML assertion to use as the user's name
;assertion_attribute_name = displayName
# Friendly name or name of the attribute within the SAML assertion to use as the user's login handle
;assertion_attribute_login = mail
# Friendly name or name of the attribute within the SAML assertion to use as the user's email
;assertion_attribute_email = mail
#################################### Grafana.com Auth ####################
[auth.grafana_com]
;enabled = false
;allow_sign_up = true
;client_id = some_id
;client_secret = some_secret
;scopes = user:email
;allowed_organizations =
#################################### Auth Proxy ##########################
[auth.proxy]
;enabled = false
;header_name = X-WEBAUTH-USER
;header_property = username
;auto_sign_up = true
;ldap_sync_ttl = 60
;whitelist = 192.168.1.1, 192.168.2.1
;headers = Email:X-User-Email, Name:X-User-Name
#################################### Basic Auth ##########################
[auth.basic]
;enabled = true
#################################### Auth LDAP ##########################
[auth.ldap]
;enabled = false
;config_file = /etc/grafana/ldap.toml
;allow_sign_up = true
# LDAP backround sync (Enterprise only)
# At 1 am every day
;sync_cron = "0 0 1 * * *"
;active_sync_enabled = true
#################################### SMTP / Emailing ##########################
[smtp]
# enabled = true
# host =
# user =
# If the password contains # or ; you have to wrap it with trippel quotes. Ex """#password;"""
# password =
;cert_file =
;key_file =
# skip_verify = true
# from_address =
# from_name =
# EHLO identity in SMTP dialog (defaults to instance_name)
;ehlo_identity = dashboard.example.com
[emails]
;welcome_email_on_sign_up = false
#################################### Logging ##########################
[log]
# Either "console", "file", "syslog". Default is console and file
# Use space to separate multiple modes, e.g. "console file"
;mode = console file
# Either "debug", "info", "warn", "error", "critical", default is "info"
;level = info
# optional settings to set different levels for specific loggers. Ex filters = sqlstore:debug
;filters =
# For "console" mode only
[log.console]
;level =
# log line format, valid options are text, console and json
;format = console
# For "file" mode only
[log.file]
;level =
# log line format, valid options are text, console and json
;format = text
# This enables automated log rotate(switch of following options), default is true
;log_rotate = true
# Max line number of single file, default is 1000000
;max_lines = 1000000
# Max size shift of single file, default is 28 means 1 << 28, 256MB
;max_size_shift = 28
# Segment log daily, default is true
;daily_rotate = true
# Expired days of log file(delete after max days), default is 7
;max_days = 7
[log.syslog]
;level =
# log line format, valid options are text, console and json
;format = text
# Syslog network type and address. This can be udp, tcp, or unix. If left blank, the default unix endpoints will be used.
;network =
;address =
# Syslog facility. user, daemon and local0 through local7 are valid.
;facility =
# Syslog tag. By default, the process' argv[0] is used.
;tag =
#################################### Alerting ############################
[alerting]
# Disable alerting engine & UI features
;enabled = true
# Makes it possible to turn off alert rule execution but alerting UI is visible
;execute_alerts = true
# Default setting for new alert rules. Defaults to categorize error and timeouts as alerting. (alerting, keep_state)
;error_or_timeout = alerting
# Default setting for how Grafana handles nodata or null values in alerting. (alerting, no_data, keep_state, ok)
;nodata_or_nullvalues = no_data
# Alert notifications can include images, but rendering many images at the same time can overload the server
# This limit will protect the server from render overloading and make sure notifications are sent out quickly
;concurrent_render_limit = 5
# Default setting for alert calculation timeout. Default value is 30
;evaluation_timeout_seconds = 30
# Default setting for alert notification timeout. Default value is 30
;notification_timeout_seconds = 30
# Default setting for max attempts to sending alert notifications. Default value is 3
;max_attempts = 3
#################################### Explore #############################
[explore]
# Enable the Explore section
;enabled = true
#################################### Internal Grafana Metrics ##########################
# Metrics available at HTTP API Url /metrics
[metrics]
# Disable / Enable internal metrics
;enabled = true
# Disable total stats (stat_totals_*) metrics to be generated
;disable_total_stats = false
# Publish interval
;interval_seconds = 10
# Send internal metrics to Graphite
[metrics.graphite]
# Enable by setting the address setting (ex localhost:2003)
;address =
;prefix = prod.grafana.%(instance_name)s.
#################################### Distributed tracing ############
[tracing.jaeger]
# Enable by setting the address sending traces to jaeger (ex localhost:6831)
;address = localhost:6831
# Tag that will always be included in when creating new spans. ex (tag1:value1,tag2:value2)
;always_included_tag = tag1:value1
# Type specifies the type of the sampler: const, probabilistic, rateLimiting, or remote
;sampler_type = const
# jaeger samplerconfig param
# for "const" sampler, 0 or 1 for always false/true respectively
# for "probabilistic" sampler, a probability between 0 and 1
# for "rateLimiting" sampler, the number of spans per second
# for "remote" sampler, param is the same as for "probabilistic"
# and indicates the initial sampling rate before the actual one
# is received from the mothership
;sampler_param = 1
# Whether or not to use Zipkin propagation (x-b3- HTTP headers).
;zipkin_propagation = false
# Setting this to true disables shared RPC spans.
# Not disabling is the most common setting when using Zipkin elsewhere in your infrastructure.
;disable_shared_zipkin_spans = false
#################################### Grafana.com integration ##########################
# Url used to import dashboards directly from Grafana.com
[grafana_com]
;url = https://grafana.com
#################################### External image storage ##########################
[external_image_storage]
# Used for uploading images to public servers so they can be included in slack/email messages.
# you can choose between (s3, webdav, gcs, azure_blob, local)
;provider =
[external_image_storage.s3]
;bucket =
;region =
;path =
;access_key =
;secret_key =
[external_image_storage.webdav]
;url =
;public_url =
;username =
;password =
[external_image_storage.gcs]
;key_file =
;bucket =
;path =
[external_image_storage.azure_blob]
;account_name =
;account_key =
;container_name =
[external_image_storage.local]
# does not require any configuration
[rendering]
# Options to configure a remote HTTP image rendering service, e.g. using https://github.com/grafana/grafana-image-renderer.
# URL to a remote HTTP image renderer service, e.g. http://localhost:8081/render, will enable Grafana to render panels and dashboards to PNG-images using HTTP requests to an external service.
;server_url =
# If the remote HTTP image renderer service runs on a different server than the Grafana server you may have to configure this to a URL where Grafana is reachable, e.g. http://grafana.domain/.
;callback_url =
[enterprise]
# Path to a valid Grafana Enterprise license.jwt file
;license_path =
[panels]
# If set to true Grafana will allow script tags in text panels. Not recommended as it enable XSS vulnerabilities.
;disable_sanitize_html = false
[plugins]
;enable_alpha = false
;app_tls_skip_verify_insecure = false

View File

@ -1,305 +0,0 @@
### Welcome to the InfluxDB configuration file.
# Once every 24 hours InfluxDB will report usage data to usage.influxdata.com
# The data includes a random ID, os, arch, version, the number of series and other
# usage data. No data from user databases is ever transmitted.
# Change this option to true to disable reporting.
reporting-disabled = false
# we'll try to get the hostname automatically, but if it the os returns something
# that isn't resolvable by other servers in the cluster, use this option to
# manually set the hostname
# hostname = "localhost"
###
### [meta]
###
### Controls the parameters for the Raft consensus group that stores metadata
### about the InfluxDB cluster.
###
[meta]
# Where the metadata/raft database is stored
dir = "/var/lib/influxdb/meta"
retention-autocreate = true
# If log messages are printed for the meta service
logging-enabled = true
pprof-enabled = false
# The default duration for leases.
lease-duration = "1m0s"
###
### [data]
###
### Controls where the actual shard data for InfluxDB lives and how it is
### flushed from the WAL. "dir" may need to be changed to a suitable place
### for your system, but the WAL settings are an advanced configuration. The
### defaults should work for most systems.
###
[data]
# Controls if this node holds time series data shards in the cluster
enabled = true
dir = "/var/lib/influxdb/data"
# These are the WAL settings for the storage engine >= 0.9.3
wal-dir = "/var/lib/influxdb/wal"
wal-logging-enabled = true
# Trace logging provides more verbose output around the tsm engine. Turning
# this on can provide more useful output for debugging tsm engine issues.
# trace-logging-enabled = false
# Whether queries should be logged before execution. Very useful for troubleshooting, but will
# log any sensitive data contained within a query.
# query-log-enabled = true
# Settings for the TSM engine
# CacheMaxMemorySize is the maximum size a shard's cache can
# reach before it starts rejecting writes.
# cache-max-memory-size = 524288000
# CacheSnapshotMemorySize is the size at which the engine will
# snapshot the cache and write it to a TSM file, freeing up memory
# cache-snapshot-memory-size = 26214400
# CacheSnapshotWriteColdDuration is the length of time at
# which the engine will snapshot the cache and write it to
# a new TSM file if the shard hasn't received writes or deletes
# cache-snapshot-write-cold-duration = "1h"
# MinCompactionFileCount is the minimum number of TSM files
# that need to exist before a compaction cycle will run
# compact-min-file-count = 3
# CompactFullWriteColdDuration is the duration at which the engine
# will compact all TSM files in a shard if it hasn't received a
# write or delete
# compact-full-write-cold-duration = "24h"
# MaxPointsPerBlock is the maximum number of points in an encoded
# block in a TSM file. Larger numbers may yield better compression
# but could incur a performance penalty when querying
# max-points-per-block = 1000
###
### [coordinator]
###
### Controls the clustering service configuration.
###
[coordinator]
write-timeout = "10s"
max-concurrent-queries = 0
query-timeout = "0"
log-queries-after = "0"
max-select-point = 0
max-select-series = 0
max-select-buckets = 0
###
### [retention]
###
### Controls the enforcement of retention policies for evicting old data.
###
[retention]
enabled = true
check-interval = "30m"
###
### [shard-precreation]
###
### Controls the precreation of shards, so they are available before data arrives.
### Only shards that, after creation, will have both a start- and end-time in the
### future, will ever be created. Shards are never precreated that would be wholly
### or partially in the past.
[shard-precreation]
enabled = true
check-interval = "10m"
advance-period = "30m"
###
### Controls the system self-monitoring, statistics and diagnostics.
###
### The internal database for monitoring data is created automatically if
### if it does not already exist. The target retention within this database
### is called 'monitor' and is also created with a retention period of 7 days
### and a replication factor of 1, if it does not exist. In all cases the
### this retention policy is configured as the default for the database.
[monitor]
store-enabled = true # Whether to record statistics internally.
store-database = "_internal" # The destination database for recorded statistics
store-interval = "10s" # The interval at which to record statistics
###
### [admin]
###
### Controls the availability of the built-in, web-based admin interface. If HTTPS is
### enabled for the admin interface, HTTPS must also be enabled on the [http] service.
###
[admin]
enabled = true
bind-address = ":8083"
https-enabled = false
https-certificate = "/etc/ssl/influxdb.pem"
###
### [http]
###
### Controls how the HTTP endpoints are configured. These are the primary
### mechanism for getting data into and out of InfluxDB.
###
[http]
enabled = true
bind-address = ":8086"
auth-enabled = false
log-enabled = true
write-tracing = false
pprof-enabled = false
https-enabled = false
https-certificate = "/etc/ssl/influxdb.pem"
### Use a separate private key location.
# https-private-key = ""
max-row-limit = 10000
realm = "InfluxDB"
###
### [subsciber]
###
### Controls the subscriptions, which can be used to fork a copy of all data
### received by the InfluxDB host.
###
[subsciber]
enabled = true
http-timeout = "30s"
###
### [[graphite]]
###
### Controls one or many listeners for Graphite data.
###
[[graphite]]
enabled = false
# database = "graphite"
# bind-address = ":2003"
# protocol = "tcp"
# consistency-level = "one"
# These next lines control how batching works. You should have this enabled
# otherwise you could get dropped metrics or poor performance. Batching
# will buffer points in memory if you have many coming in.
# batch-size = 5000 # will flush if this many points get buffered
# batch-pending = 10 # number of batches that may be pending in memory
# batch-timeout = "1s" # will flush at least this often even if we haven't hit buffer limit
# udp-read-buffer = 0 # UDP Read buffer size, 0 means OS default. UDP listener will fail if set above OS max.
### This string joins multiple matching 'measurement' values providing more control over the final measurement name.
# separator = "."
### Default tags that will be added to all metrics. These can be overridden at the template level
### or by tags extracted from metric
# tags = ["region=us-east", "zone=1c"]
### Each template line requires a template pattern. It can have an optional
### filter before the template and separated by spaces. It can also have optional extra
### tags following the template. Multiple tags should be separated by commas and no spaces
### similar to the line protocol format. There can be only one default template.
# templates = [
# "*.app env.service.resource.measurement",
# # Default template
# "server.*",
# ]
###
### [collectd]
###
### Controls one or many listeners for collectd data.
###
[[collectd]]
enabled = false
# bind-address = ""
# database = ""
# typesdb = ""
# These next lines control how batching works. You should have this enabled
# otherwise you could get dropped metrics or poor performance. Batching
# will buffer points in memory if you have many coming in.
# batch-size = 1000 # will flush if this many points get buffered
# batch-pending = 5 # number of batches that may be pending in memory
# batch-timeout = "1s" # will flush at least this often even if we haven't hit buffer limit
# read-buffer = 0 # UDP Read buffer size, 0 means OS default. UDP listener will fail if set above OS max.
###
### [opentsdb]
###
### Controls one or many listeners for OpenTSDB data.
###
[[opentsdb]]
enabled = false
# bind-address = ":4242"
# database = "opentsdb"
# retention-policy = ""
# consistency-level = "one"
# tls-enabled = false
# certificate= ""
# log-point-errors = true # Log an error for every malformed point.
# These next lines control how batching works. You should have this enabled
# otherwise you could get dropped metrics or poor performance. Only points
# metrics received over the telnet protocol undergo batching.
# batch-size = 1000 # will flush if this many points get buffered
# batch-pending = 5 # number of batches that may be pending in memory
# batch-timeout = "1s" # will flush at least this often even if we haven't hit buffer limit
###
### [[udp]]
###
### Controls the listeners for InfluxDB line protocol data via UDP.
###
[[udp]]
enabled = true
bind-address = "0.0.0.0:8089"
database = "host"
# retention-policy = ""
# These next lines control how batching works. You should have this enabled
# otherwise you could get dropped metrics or poor performance. Batching
# will buffer points in memory if you have many coming in.
batch-size = 1000 # will flush if this many points get buffered
# batch-pending = 5 # number of batches that may be pending in memory
batch-timeout = "1s" # will flush at least this often even if we haven't hit buffer limit
# read-buffer = 0 # UDP Read buffer size, 0 means OS default. UDP listener will fail if set above OS max.
# set the expected UDP payload size; lower values tend to yield better performance, default is max UDP size 65536
# udp-payload-size = 65536
###
### [continuous_queries]
###
### Controls how continuous queries are run within InfluxDB.
###
[continuous_queries]
log-enabled = true
enabled = true
# run-interval = "1s" # interval for how often continuous queries will be checked if they need to run

View File

@ -1,19 +0,0 @@
#!/usr/bin/env sh
SMARTCTL=/usr/local/sbin/smartctl
DISKS=$(/sbin/sysctl -n kern.disks | cut -d= -f2)
for DISK in ${DISKS}
do
TEMP=$(${SMARTCTL} -l scttemp /dev/${DISK} | grep '^Current Temperature:' | awk '{print $3}')
HEALTH=$(${SMARTCTL} -H /dev/${DISK} | grep 'test result:' | cut -d: -f2 | sed 's/^[ \t]*//')
if [ -z != ${TEMP} ] && [ -z != ${HEALTH} ]
then
JSON=$(echo ${JSON}{\"disk\":\"${DISK}\",\"health\":\"${HEALTH}\",\"temperature\":${TEMP}},)
fi
done
JSON=$(echo ${JSON} | sed 's/,$//')
echo [${JSON}] >&1

View File

@ -1,3 +0,0 @@
#!/bin/bash
docker exec e6 rcon-cli forge entity list "minecraft:player" >&1

View File

@ -1,19 +0,0 @@
#!/bin/bash
# this script converts the output of the "forge tps" command (in the form of the .forgetps file) into json for sending to influxdb
# by default it reads from stdin and outputs to a .forgetps.json file
while IFS= read -r line; do
if [ "$line" != "" ]; then
DIM=$(echo -n "$line" | awk '{print $2}')
if [ "$DIM" = "Mean" ]; then
DIM="Overall"
fi
TPT=$(echo "$line" | grep -oE 'Mean tick time: .+ms' | awk '{print $4}')
TPS=$(echo "$line" | grep -oE 'Mean TPS: .+' | awk '{print $3}')
JSON+=\{$(echo \"dim\":\"$DIM\",\"tpt\":$TPT,\"tps\":$TPS)\},
fi
#done < .forgetps # inputs from .forgetps file
done <$1 # inputs from file passed via stdin
JSON=$(echo ${JSON} | sed 's/,$//')
#echo [${JSON}] >&1 # outputs to stdout
echo [${JSON}] > .forgetps.json # uncomment this to output to file

View File

@ -1,32 +0,0 @@
[global_tags]
[agent]
interval = "10s"
round_interval = true
metric_batch_size = 1000
metric_buffer_limit = 10000
collection_jitter = "0s"
flush_interval = "10s"
flush_jitter = "0s"
precision = ""
hostname = ""
omit_hostname = false
[[outputs.influxdb]]
urls = ["http://influxdb:8086"]
database = "jafgraf"
[[inputs.cpu]]
percpu = true
totalcpu = true
collect_cpu_time = false
report_active = false
[[inputs.disk]]
ignore_fs = ["tmpfs", "devtmpfs", "devfs", "iso9660", "overlay", "aufs", "squashfs"]
[[inputs.mem]]
[[inputs.system]]
[[inputs.docker]]
endpoint = "unix:///var/run/docker.sock"
[[inputs.sensors]]
[[inputs.file]]
files = ["/.forgetps.json"]
data_format = "json"
name_override = "tickinfo"
tag_keys = ["dim"]

View File

@ -1,12 +0,0 @@
DOCKER_DATA=/home/joey/docker_data/joplin
DB_CLIENT=pg
POSTGRES_PASSWORD=postgres
POSTGRES_DATABASE=joplin
POSTGRES_DB=joplin
POSTGRES_USER=postgres
POSTGRES_PORT=5432
POSTGRES_HOST=joplin_db
APP_BASE_URL=https://joplin.jafner.net
APP_PORT=22300

View File

@ -1,34 +0,0 @@
version: '3'
services:
joplin:
image: joplin/server:2.6-beta
container_name: joplin
restart: unless-stopped
env_file:
- .env
depends_on:
- joplin_db
networks:
- web
- joplin
labels:
- traefik.http.routers.joplin.rule=Host(`joplin.jafner.net`)
- traefik.http.routers.joplin.tls.certresolver=lets-encrypt
- traefik.http.middlewares.joplin.headers.customrequestheaders.X-Forwarded-Proto = http
- traefik.http.services.joplin.loadbalancer.server.port=22300
- traefik.http.services.joplin.loadbalancer.passhostheader=true
joplin_db:
image: postgres:13.1
container_name: joplin_db
restart: unless-stopped
env_file:
- .env
volumes:
- ${DOCKER_DATA}/db:/var/lib/postresql/data
networks:
- joplin
networks:
joplin:
web:
external: true

View File

@ -1,2 +0,0 @@
DOCKER_DATA=/home/joey/docker_data/landing

View File

@ -1,46 +0,0 @@
version: '3.1'
services:
landing:
image: wordpress
container_name: landing
restart: always
environment:
WORDPRESS_DB_HOST: landing_db
WORDPRESS_DB_USER: wordpress
WORDPRESS_DB_PASSWORD: wordpress
WORDPRESS_DB_NAME: wordpressdb
volumes:
- ${DOCKER_DATA}/html:/var/www/html
- ./docker-php-memlimit.ini:/usr/local/etc/php/conf.d/docker-php-memlimit.ini:ro
labels:
- traefik.http.routers.landing.rule=Host(`www.jafner.net`)
- traefik.http.routers.landing.tls=true
- traefik.http.routers.landing.tls.certresolver=lets-encrypt
- traefik.port=80
networks:
- web
- landing
depends_on:
- landing_db
landing_db:
image: mysql:5.7
container_name: landing_db
restart: always
networks:
- landing
environment:
MYSQL_DATABASE: wordpressdb
MYSQL_USER: wordpress
MYSQL_PASSWORD: wordpress
MYSQL_RANDOM_ROOT_PASSWORD: '1'
volumes:
- ${DOCKER_DATA}/db:/var/lib/mysql
labels:
- traefik.enable=false
networks:
web:
external: true
landing:

View File

@ -1 +0,0 @@
memory_limit = 512M

View File

@ -1,18 +0,0 @@
version: '3'
services:
telegraf:
image: telegraf:1.13
restart: unless-stopped
ports:
- 8094:8094
volumes:
- ./telegraf.conf:/etc/telegraf/telegraf.conf:ro
monitor:
image: itzg/mc-monitor
command: gather-for-telegraf
restart: unless-stopped
environment:
GATHER_INTERVAL: 10s
GATHER_TELEGRAF_ADDRESS: telegraf:8094
GATHER_SERVERS: e6.jafner.net

View File

@ -1,9 +0,0 @@
[agent]
interval = "10s"
[[outputs.influxdb]]
urls = ["http://192.168.1.23:8086"]
database = "mc-monitor"
[[inputs.socket_listener]]
service_address = "tcp://:8094"

View File

@ -1,3 +0,0 @@
DOCKER_DATA=/home/joey/docker_data/minecraft
DOCKER_CONFIG=/home/joey/docker_config/minecraft
RCON_PASSWORD=***REMOVED***

View File

@ -1,23 +0,0 @@
version: '3'
services:
e6-056:
image: itzg/minecraft-server:java8
container_name: e6-056
environment:
- EULA=TRUE
- MAX_MEMORY=12G
- TYPE=FORGE
- VERSION=1.16.5
- FORGEVERSION=36.1.31
- OPS=jafner425
- ENABLE_RCON=true
- RCON_PASSWORD=${RCON_PASSWORD}
volumes:
- $DOCKER_DATA/e6-056:/data:rw
networks:
- mc-router
restart: always
networks:
mc-router:
external: true

View File

@ -1,17 +0,0 @@
version: '3'
services:
router:
image: itzg/mc-router
container_name: mc-router
restart: always
networks:
- mc-router
ports:
- 25565:25565
command: --mapping=e6.jafner.net=e6-056:25565,vanilla.jafner.net=vanilla:25565,tnp.jafner.net=tnp:25565,bmcp.jafner.net=bmcp:25565 --api-binding=0.0.0.0:25566
networks:
mc-router:
external: true
volumes:
mc-router:

View File

@ -1,21 +0,0 @@
[global_tags]
[agent]
interval = "10s"
round_interval = true
metric_batch_size = 1000
metric_buffer_limit = 10000
collection_jitter = "0s"
flush_interval = "10s"
flush_jitter = "0s"
precision = ""
hostname = ""
omit_hostname = false
[[outputs.influxdb]]
urls = ["http://192.168.1.23:8086"]
database = "minecraft"
[[inputs.exec]]
name_override = "tickinfo"
commands = ["/data/get-tps.sh"]
timeout = "30s"
data_format = "json"
tag_keys = ["dim","tpt"]

View File

@ -1,18 +0,0 @@
version: '3'
services:
vanilla:
image: itzg/minecraft-server:java16
container_name: vanilla
environment:
- EULA=TRUE
- VERSION=1.17.1
- OPS=mollymsmom
- MAX_MEMORY=6G
volumes:
- $DOCKER_DATA/vanilla:/data:rw
networks:
- mc-router
networks:
mc-router:
external: true

View File

@ -1,2 +0,0 @@
DOCKER_DATA=/home/joey/docker_data/nvgm

View File

@ -1,45 +0,0 @@
version: '3.1'
services:
nvgm:
image: wordpress
container_name: nvgm
restart: unless-stopped
environment:
WORDPRESS_DB_HOST: nvgm_db
WORDPRESS_DB_USER: wordpress
WORDPRESS_DB_PASSWORD: wordpress
WORDPRESS_DB_NAME: wordpressdb
volumes:
- ${DOCKER_DATA}/html:/var/www/html
labels:
- traefik.http.routers.nvgm.rule=Host(`nvgm.jafner.net`)
- traefik.http.routers.nvgm.tls=true
- traefik.http.routers.nvgm.tls.certresolver=lets-encrypt
- traefik.port=80
networks:
- web
- nvgm
depends_on:
- nvgm_db
nvgm_db:
image: mysql:5.7
container_name: nvgm_db
restart: unless-stopped
networks:
- nvgm
environment:
MYSQL_DATABASE: wordpressdb
MYSQL_USER: wordpress
MYSQL_PASSWORD: wordpress
MYSQL_RANDOM_ROOT_PASSWORD: '1'
labels:
- traefik.enable=false
volumes:
- ${DOCKER_DATA}/db:/var/lib/mysql
networks:
web:
external: true
nvgm:

View File

@ -1,51 +0,0 @@
# Database / Postgres service configuration
POSTGRES_USER=postgresuser
POSTGRES_PASSWORD=postgrespassword
# Postgres database name "peertube"
POSTGRES_DB=peertube
# Editable only with a suffix :
#POSTGRES_DB=peertube_prod
#PEERTUBE_DB_SUFFIX=_prod
PEERTUBE_DB_USERNAME=postgresuser
PEERTUBE_DB_PASSWORD=postgrespassword
PEERTUBE_DB_SSL=false
# Default to Postgres service name "postgres" in docker-compose.yml
PEERTUBE_DB_HOSTNAME=postgres
# Server configuration
PEERTUBE_WEBSERVER_HOSTNAME=peertube.jafner.net
# If you do not use https and a reverse-proxy in docker-compose.yml
#PEERTUBE_WEBSERVER_PORT=80
#PEERTUBE_WEBSERVER_HTTPS=false
# If you need more than one IP as trust_proxy
# pass them as a comma separated array:
PEERTUBE_TRUST_PROXY=["127.0.0.1", "loopback", "172.80.0.0/16"]
# E-mail configuration
# If you use a Custom SMTP server
#PEERTUBE_SMTP_USERNAME=
#PEERTUBE_SMTP_PASSWORD=
# Default to Postfix service name "postfix" in docker-compose.yml
# May be the hostname of your Custom SMTP server
PEERTUBE_SMTP_HOSTNAME=postfix
PEERTUBE_SMTP_PORT=25
PEERTUBE_SMTP_FROM=noreply@jafner.net
PEERTUBE_SMTP_TLS=false
PEERTUBE_SMTP_DISABLE_STARTTLS=false
PEERTUBE_ADMIN_EMAIL=joey@jafner.net
# Postfix service configuration
POSTFIX_myhostname=jafner.net
# If you need to generate a list of sub/DOMAIN keys
# pass them as a whitespace separated string <DOMAIN>=<selector>
OPENDKIM_DOMAINS=jafner.net=peertube
# see https://github.com/wader/postfix-relay/pull/18
OPENDKIM_RequireSafeKeys=no
# /!\ Prefer to use the PeerTube admin interface to set the following configurations /!\
#PEERTUBE_SIGNUP_ENABLED=true
#PEERTUBE_TRANSCODING_ENABLED=true
#PEERTUBE_CONTACT_FORM_ENABLED=true
# Docker volume location
DOCKER_VOLUME=/mnt/md0/peertube

View File

@ -1,70 +0,0 @@
version: "3.3"
services:
peertube:
image: chocobozzz/peertube:production-buster
container_name: peertube_peertube
networks:
web:
peertube:
ipv4_address: 172.80.0.42
env_file:
- .env
ports:
- "1935:1935" # If you don't want to use the live feature, you can comment this line
volumes:
- assets:/app/client/dist
- ${DOCKER_VOLUME}/data:/data
- ${DOCKER_VOLUME}/config:/config
labels:
- "traefik.http.routers.peertube.rule=Host(`peertube.jafner.net`)"
- "traefik.http.routers.peertube.tls.certresolver=lets-encrypt"
- "traefik.http.services.peertube.loadbalancer.server.port=9000"
depends_on:
- postgres
- redis
- postfix
restart: "unless-stopped"
postgres:
image: postgres:13-alpine
container_name: peertube_postgres
networks:
- peertube
env_file:
- .env
volumes:
- ${DOCKER_VOLUME}/db:/var/lib/postgresql/data
restart: "unless-stopped"
redis:
image: redis:6-alpine
container_name: peertube_redis
networks:
- peertube
volumes:
- ${DOCKER_VOLUME}/redis:/data
restart: "unless-stopped"
postfix:
image: mwader/postfix-relay
container_name: peertube_postfix
networks:
- peertube
env_file:
- .env
volumes:
- ${DOCKER_VOLUME}/opendkim/keys:/etc/opendkim/keys
restart: "unless-stopped"
networks:
peertube:
ipam:
driver: default
config:
- subnet: 172.80.0.0/16
web:
external: true
volumes:
assets:

View File

@ -1 +0,0 @@
DOCKER_DATA=/home/joey/docker_data/plex

View File

@ -1,53 +0,0 @@
version: "3"
services:
plex:
image: linuxserver/plex
container_name: plex
restart: "no"
networks:
- web
ports:
- 32400:32400/tcp
- 32400:32400/udp
- 3005:3005/tcp
- 8324:8324/tcp
- 32469:32469/tcp
- 1900:1900/udp
- 32410:32410/udp
- 32412:32412/udp
- 32413:32413/udp
- 32414:32414/udp
environment:
- PUID=1000
- PGID=1000
- VERSION=latest
- ADVERTISE_IP="https://plex.jafner.net:443"
- PLEX_CLAIM=claim-DPRoiMnzcby-YxKdFpqJ
volumes:
- /mnt/nas/Video/Movies:/movies
- /mnt/nas/Video/Shows:/shows
- "${DOCKER_DATA}/plex:/config"
labels:
- traefik.http.routers.plex.rule=Host(`plex.jafner.net`)
- traefik.http.routers.plex.tls.certresolver=lets-encrypt
- traefik.http.services.plex.loadbalancer.server.port=32400
ombi:
image: ghcr.io/linuxserver/ombi
container_name: ombi
restart: "no"
networks:
- web
environment:
- PUID=1000
- PGID=1000
- TZ=America/Los_Angeles
volumes:
- "${DOCKER_DATA}/ombi:/config"
labels:
- traefik.http.routers.ombi.rule=Host(`ombi.jafner.net`)
- traefik.http.routers.ombi.tls.certresolver=lets-encrypt
- traefik.http.services.ombi.loadbalancer.server.port=3579
networks:
web:
external: true

View File

@ -1,24 +0,0 @@
version: "3"
services:
portainer:
image: portainer/portainer-ce
container_name: portainer
restart: unless-stopped
command: -H unix:///var/run/docker.sock
networks:
- web
restart: always
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- portainer_data:/data
labels:
- traefik.http.routers.portainer.rule=Host(`portainer.jafner.net`)
- traefik.http.routers.portainer.tls.certresolver=lets-encrypt
- traefik.http.services.portainer.loadbalancer.server.port=9000
- traefik.http.routers.portainer.middlewares=authelia@file
volumes:
portainer_data:
networks:
web:
external: true

View File

@ -1,2 +0,0 @@
DOCKER_DATA=/home/joey/docker_data/portfolio

View File

@ -1,45 +0,0 @@
version: '3.1'
services:
portfolio:
image: wordpress
container_name: portfolio
restart: always
environment:
WORDPRESS_DB_HOST: portfolio_db
WORDPRESS_DB_USER: wordpress
WORDPRESS_DB_PASSWORD: wordpress
WORDPRESS_DB_NAME: wordpressdb
volumes:
- ${DOCKER_DATA}/html:/var/www/html
labels:
- traefik.http.routers.portfolio.rule=Host(`portfolio.jafner.net`)
- traefik.http.routers.portfolio.tls=true
- traefik.http.routers.portfolio.tls.certresolver=lets-encrypt
- traefik.port=80
networks:
- web
- portfolio
depends_on:
- portfolio_db
portfolio_db:
image: mysql:5.7
container_name: portfolio_db
restart: always
networks:
- portfolio
environment:
MYSQL_DATABASE: wordpressdb
MYSQL_USER: wordpress
MYSQL_PASSWORD: wordpress
MYSQL_RANDOM_ROOT_PASSWORD: '1'
volumes:
- ${DOCKER_DATA}/db:/var/lib/mysql
labels:
- traefik.enable=false
networks:
web:
external: true
portfolio:

View File

@ -1,45 +0,0 @@
version: '3'
services:
prometheus:
image: prom/prometheus:latest
container_name: prometheus
networks:
- monitoring
- web
ports:
- 9090:9090
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
restart: unless-stopped
command:
- "--config.file=/etc/prometheus/prometheus.yml"
labels:
- traefik.http.routers.prometheus.rule=Host(`prometheus.jafner.net`)
- traefik.http.routers.prometheus.tls.certresolver=lets-encrypt
5e-jafner-tools:
image: lusotycoon/apache-exporter
container_name: prometheus-5e-jafner-tools
restart: unless-stopped
networks:
- monitoring
command: '--scrape_uri "https://5e.jafner.tools/server-status/?auto"'
pihole-jafner-net:
image: ekofr/pihole-exporter:latest
container_name: prometheus-pihole-jafner-net
restart: unless-stopped
networks:
- monitoring
environment:
- PIHOLE_HOSTNAME=pihole.jafner.net
- PIHOLE_PASSWORD=***REMOVED***
- INTERVAL=15s
- PORT=9617
networks:
monitoring:
external: true
web:
external: true

View File

@ -1,28 +0,0 @@
global:
scrape_interval: 15s
scrape_configs:
- job_name: 'prometheus'
scrape_interval: 60s
static_configs:
- targets: ['localhost:9090']
- job_name: '5e.jafner.tools'
scrape_interval: 60s
static_configs:
- targets: ['5e-jafner-tools:9117']
metrics_path: "/metrics"
- job_name: 'pihole.jafner.net'
scrape_interval: 60s
static_configs:
- targets: ['pihole-jafner-net:9617']
metrics_path: "/metrics"
- job_name: 'uptime-kuma'
scrape_interval: 60s
static_configs:
- targets: ['uptime.jafner.net']
basic_auth:
username: Jafner
password: ***REMOVED***

File diff suppressed because one or more lines are too long

View File

@ -1,59 +0,0 @@
---
###############################################################
# Authelia configuration #
###############################################################
host: 0.0.0.0
port: 9091
# This secret can also be set using the env variables AUTHELIA_JWT_SECRET_FILE
jwt_secret: XvvpN7uFoSmQAPdgAynDTHG8yQX4tjtS
default_redirection_url: https://www.jafner.net
totp:
issuer: authelia.com
period: 30
skew: 1
authentication_backend:
file:
path: /config/users_database.yml
password:
algorithm: argon2id
iterations: 1
salt_length: 16
parallelism: 8
memory: 1024
access_control:
default_policy: deny
rules:
# Rules applied to everyone
- domain:
- "*.jafner.net"
- "jafner.net"
policy: two_factor
session:
name: authelia_session
# This secret can also be set using the env variables AUTHELIA_SESSION_SECRET_FILE
secret: ***REMOVED***
expiration: 3600 # 1 hour
inactivity: 300 # 5 minutes
domain: jafner.net # Should match whatever your root protected domain is
redis:
host: redis
port: 6379
# This secret can also be set using the env variables AUTHELIA_SESSION_REDIS_PASSWORD_FILE
# password: authelia
regulation:
max_retries: 3
find_time: 120
ban_time: 300
storage:
local:
path: /config/db.sqlite3
notifier:
filesystem:
filename: /config/notification.txt

View File

@ -1,58 +0,0 @@
version: "3"
services:
traefik:
container_name: traefik
image: traefik:latest
depends_on:
- authelia
restart: unless-stopped
networks:
- web
ports:
- 80:80
- 443:443
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./traefik.toml:/traefik.toml
- ./traefik_dynamic.toml:/traefik_dynamic.toml
- ./acme.json:/acme.json
- ./.htpasswd:/.htpasswd
labels:
- traefik.http.routers.traefik.rule=Host(`traefik.jafner.net`)
- traefik.http.routers.traefik.tls.certresolver=lets-encrypt
authelia:
image: authelia/authelia
container_name: authelia
restart: unless-stopped
volumes:
- ./authelia:/config
networks:
- web
labels:
- 'traefik.http.routers.authelia.rule=Host(`auth.jafner.net`)'
- 'traefik.http.routers.authelia.entrypoints=websecure'
- 'traefik.http.routers.authelia.tls.certresolver=lets-encrypt'
- "traefik.http.middlewares.security.headers.sslRedirect=true"
- "traefik.http.middlewares.security.headers.stsSeconds=15768000"
- "traefik.http.middlewares.security.headers.browserXSSFilter=true"
- "traefik.http.middlewares.security.headers.contentTypeNosniff=true"
- "traefik.http.middlewares.security.headers.forceSTSHeader=true"
- "traefik.http.middlewares.security.headers.stsPreload=true"
- "traefik.http.middlewares.security.headers.frameDeny=true"
redis:
image: redis:alpine
container_name: redis
restart: unless-stopped
volumes:
- ./redis:/data
networks:
- web
expose:
- 6379
networks:
web:
external: true

View File

@ -1,8 +0,0 @@
# for all web-facing services
traefik.http.routers.router-name.rule=Host(`subdomain.jafner.net`)
traefik.http.routers.router-name.tls=true
traefik.http.routers.router-name.tls.certresolver=lets-encrypt
# for restricting service to LAN IPs
traefik.http.routers.router-name.middlewares=lan-only@file
# for setting a non-default port
traefik.http.services.service-name.loadbalancer.server.port=1234

View File

@ -1,28 +0,0 @@
[entryPoints]
[entryPoints.web]
address = ":80"
[entryPoints.web.http.redirections.entryPoint]
to = "websecure"
scheme = "https"
[entryPoints.websecure]
address = ":443"
[api]
dashboard = true
[metrics]
[metrics.prometheus]
[certificatesResolvers.lets-encrypt.acme]
email = "jafner425@gmail.com"
storage = "acme.json"
[certificatesResolvers.lets-encrypt.acme.tlsChallenge]
[providers.docker]
watch = true
network = "web"
[providers.file]
filename = "traefik_dynamic.toml"

View File

@ -1,18 +0,0 @@
[http.middlewares]
[http.middlewares.lan-only.ipWhiteList]
sourceRange = ["127.0.0.1/32", "192.168.1.1/24"]
[http.middlewares.simpleauth.basicAuth]
usersFile = "/.htpasswd"
[http.middlewares.authelia.forwardAuth]
address = "http://authelia:9091/api/verify?rd=https://auth.jafner.net"
trustForwardHeader = "true"
authResponseHeaders = ["Remote-User", "Remote-Groups", "Remote-Name", "Remote-Email"]
[http.routers.api]
rule = "Host(`traefik.jafner.net`)"
entrypoints = ["websecure"]
middlewares = ["authelia@file"]
service = "api@internal"
[http.routers.api.tls]
certResolver = "lets-encrypt"

View File

@ -1,23 +0,0 @@
version: '3'
services:
unifi-controller:
image: lscr.io/linuxserver/unifi-controller
container_name: unifi_controller
restart: unless-stopped
environment:
- PUID=1000
- PGID=1000
- MEM_LIMIT=1024M
- MEM_STARTUP=1024M
volumes:
- ${PWD}/config:/config
ports:
- 3478:3478/udp # unifi STUN port
- 10001:10001/udp # AP discovery port
- 8080:8080 # communicate with devices
- 8443:8443 # web admin port
#- 1900:1900/udp # discoverability on layer 2
- 8843:8843 # guest portal http
- 8880:8880 # guest portal https
- 6789:6789 # mobile throughput test port
- 5514:5514/udp # remote syslog

View File

@ -1,21 +0,0 @@
# Simple docker-composer.yml
# You can change your port or volume location
version: '3.3'
services:
uptime-kuma:
image: louislam/uptime-kuma
container_name: uptime-kuma
restart: unless-stopped
volumes:
- ./data:/app/data
networks:
- web
labels:
- traefik.http.routers.uptime-kuma.rule=Host(`uptime.jafner.net`)
- traefik.http.routers.uptime-kuma.tls.certresolver=lets-encrypt
networks:
web:
external: true

View File

@ -1 +0,0 @@
DOCKER_DATA=/home/joey/docker_data/vaultwarden

View File

@ -1,17 +0,0 @@
version: '3'
services:
vaultwarden:
image: vaultwarden/server:latest
container_name: vaultwarden
restart: unless-stopped
networks:
- web
volumes:
- ${DOCKER_DATA}:/data
labels:
- traefik.http.routers.vaultwarden.rule=Host(`bitwarden.jafner.net`)
- traefik.http.routers.vaultwarden.tls=true
- traefik.http.routers.vaultwarden.tls.certresolver=lets-encrypt
networks:
web:
external: true

View File

@ -1 +0,0 @@
DOCKER_DATA=/home/joey/docker_data/wikijs

View File

@ -1,48 +0,0 @@
version: '3'
services:
db:
image: postgres:11-alpine
container_name: wikijs_db
restart: unless-stopped
environment:
POSTGRES_DB: wiki
POSTGRES_PASSWORD: wikijsrocks
POSTGRES_USER: wikijs
networks:
- wikijs
logging:
driver: "none"
volumes:
- wikijs_db:/var/lib/postgresql/data
wiki:
image: requarks/wiki:2
container_name: wikijs_wiki
restart: unless-stopped
depends_on:
- db
networks:
- web
- wikijs
environment:
DB_TYPE: postgres
DB_HOST: db
DB_PORT: 5432
DB_USER: wikijs
DB_PASS: wikijsrocks
DB_NAME: wiki
volumes:
- ${PWD}/id_rsa:/id_rsa
- ${PWD}/id_rsa.pub:/id_rsa.pub
labels:
- traefik.http.routers.wikijs.rule=Host(`wikijs.jafner.net`)
- traefik.http.routers.wikijs.tls.certresolver=lets-encrypt
- traefik.http.services.wikijs.loadbalancer.server.port=3000
volumes:
wikijs_db:
networks:
wikijs:
web:
external: true

View File

@ -1,6 +0,0 @@
ORIG_SERVERURL="www.jafner.net"
ORIG_SERVERPORT="53820"
ORIG_PEERDNS="10.13.13.1"
ORIG_PEERS="joey-phone,joey-xps13,maddie-phone"
ORIG_INTERFACE="10.13.13"
ORIG_ALLOWEDIPS="0.0.0.0/0, ::/0"

View File

@ -1,4 +0,0 @@
. {
loop
forward . /etc/resolv.conf
}

View File

@ -1,10 +0,0 @@
[Interface]
Address = 10.13.13.7
PrivateKey = ***REMOVED***
ListenPort = 51820
DNS = 10.13.13.1
[Peer]
PublicKey = 9MgzBzqw0J+xX/IJJHTBUTLRaT82p7fjv+1W5Y6vVE8=
Endpoint = www.jafner.net:53820
AllowedIPs = 0.0.0.0/0, ::/0

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1013 B

View File

@ -1 +0,0 @@
+jGu2Iz7Wy7m3bfxcE3IXAo6bnUGtKVWUtD+yH7vc2M=

View File

@ -1,10 +0,0 @@
[Interface]
Address = 10.13.13.8
PrivateKey = wNeoTRbK++UbDbvGR9k3DGKYsjhoOKMAO8QgV2aO5n4=
ListenPort = 51820
DNS = 10.13.13.1
[Peer]
PublicKey = 9MgzBzqw0J+xX/IJJHTBUTLRaT82p7fjv+1W5Y6vVE8=
Endpoint = www.jafner.net:53820
AllowedIPs = 0.0.0.0/0, ::/0

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1012 B

View File

@ -1 +0,0 @@
wNeoTRbK++UbDbvGR9k3DGKYsjhoOKMAO8QgV2aO5n4=

View File

@ -1,10 +0,0 @@
[Interface]
Address = 10.13.13.9
PrivateKey = MFUebhcmzAMVDy1DYvbj10K9AcDEaedG0k/MFWtVOWo=
ListenPort = 51820
DNS = 10.13.13.1
[Peer]
PublicKey = 9MgzBzqw0J+xX/IJJHTBUTLRaT82p7fjv+1W5Y6vVE8=
Endpoint = www.jafner.net:53820
AllowedIPs = 0.0.0.0/0, ::/0

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1021 B

View File

@ -1 +0,0 @@
MFUebhcmzAMVDy1DYvbj10K9AcDEaedG0k/MFWtVOWo=

View File

@ -1 +0,0 @@
qh5czZ7MHzjNgUqxZ/fsAlMnaVHoUU8mxcJCXwb0Pxs=

View File

@ -1 +0,0 @@
***REMOVED***

View File

@ -1 +0,0 @@
9MgzBzqw0J+xX/IJJHTBUTLRaT82p7fjv+1W5Y6vVE8=

View File

@ -1,10 +0,0 @@
[Interface]
Address = ${CLIENT_IP}
PrivateKey = $(cat /config/${PEER_ID}/privatekey-${PEER_ID})
ListenPort = 51820
DNS = ${PEERDNS}
[Peer]
PublicKey = $(cat /config/server/publickey-server)
Endpoint = ${SERVERURL}:${SERVERPORT}
AllowedIPs = ${ALLOWEDIPS}

View File

@ -1,6 +0,0 @@
[Interface]
Address = ${INTERFACE}.1
ListenPort = 51820
PrivateKey = $(cat /config/server/privatekey-server)
PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -A FORWARD -o %i -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -D FORWARD -o %i -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE

View File

@ -1,22 +0,0 @@
[Interface]
Address = 10.13.13.1
ListenPort = 51820
PrivateKey = ***REMOVED***
PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -A FORWARD -o %i -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -D FORWARD -o %i -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE
[Peer]
# peer_joey-phone
PublicKey = +jGu2Iz7Wy7m3bfxcE3IXAo6bnUGtKVWUtD+yH7vc2M=
AllowedIPs = 10.13.13.7/32
[Peer]
# peer_joey-xps13
PublicKey = ***REMOVED***
AllowedIPs = 10.13.13.8/32
[Peer]
# peer_maddie-phone
PublicKey = qh5czZ7MHzjNgUqxZ/fsAlMnaVHoUU8mxcJCXwb0Pxs=
AllowedIPs = 10.13.13.9/32

View File

@ -1,23 +0,0 @@
version: "3"
services:
wireguard:
image: linuxserver/wireguard
container_name: wireguard
restart: unless-stopped
ports:
- 53820:51820/udp
cap_add:
- NET_ADMIN
- SYS_MODULE
environment:
- PUID=1000
- PGID=1000
- TZ=America/Los_Angeles
- PEERS=joey-phone,joey-xps13,maddie-phone
- SERVERURL=www.jafner.net
- SERVERPORT=53820
volumes:
- ./config:/config
- /lib/modules:/lib/modules
sysctls:
- net.ipv4.conf.all.src_valid_mark=1

View File

@ -1,25 +0,0 @@
# Design doc for API
Stuff we're gonna need:
- GET for each of the four metrics, plus most recent "rating".
- POST for data-generating events: deployment, outage/restoration.
Given that this service relies on having data *pushed* to it, we can only ever return metrics based on the most recent deployment or outage/restoration event.
So with that in mind, we have the following design for each of our endpoints:
| Method | Description | Endpoint | Request Payload | Response Payload |
|:------:|:-----------:|:--------:|:---------------:|:----------------:|
| GET | Get deployment frequency | /api/metrics/deployment_frequency | - | {"TIMESTAMP", "COUNT", "UNIT"} |
| GET | Get lead time for changes | /api/metrics/lead_time_for_changes | - | {"TIMESTAMP", "COMPUTED_TIME"} |
| GET | Get time to restore service | /api/metrics/time_to_restore_service | - | {"TIMESTAMP", "COMPUTED_TIME"} |
| GET | Get change failure rate | /api/metrics/change_failure_rate | - | {"TIMESTAMP", "RATE"} |
| GET | Get current rating | /api/metrics/vanity | - | {"TIMESTAMP", "RATING"} |
| POST | Post new deployment event | /api/events/deployment | {"TIMESTAMP", "{INCLUDED_GIT_HASHES}", "OLDEST_COMMIT_TIMESTAMP", "DEPLOY_RETURN_STATUS"} | OK |
| POST | Post new service availability change event | /api/events/service_availability | {"TIMESTAMP", "SERVICE_ID", "EVENT_TYPE"} |
### Notes
- As-is, this API leaves no room for versioning, publisher IDs, or meaningful correlation between deployments and service availability changes.
- As-is, we have no identification, authentication, or authorization systems.
- As-is, we have no way to view the dataset from which the values are calculated.

View File

@ -1,22 +0,0 @@
### Building and running your application
When you're ready, start your application by running:
`docker compose up --build`.
Your application will be available at http://localhost:8000.
### Deploying your application to the cloud
First, build your image, e.g.: `docker build -t myapp .`.
If your cloud uses a different CPU architecture than your development
machine (e.g., you are on a Mac M1 and your cloud provider is amd64),
you'll want to build the image for that platform, e.g.:
`docker build --platform=linux/amd64 -t myapp .`.
Then, push it to your registry, e.g. `docker push myregistry.com/myapp`.
Consult Docker's [getting started](https://docs.docker.com/go/get-started-sharing/)
docs for more detail on building and pushing.
### References
* [Docker's Python guide](https://docs.docker.com/language/python/)

View File

@ -1,120 +0,0 @@
from datetime import datetime, timedelta
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from pydantic.functional_validators import field_validator
import re
app = FastAPI()
class Deployment(BaseModel):
event_timestamp: datetime = None # should look like 2024-03-12T14:29:46-0700
hashes: list = None # each should match an sha1 hash format regex(\b[0-9a-f]{5,40}\b)
timestamp_oldest_commit: datetime = None # should look like 2024-03-12T14:29:46-0700
deploy_return_status: str = None # should be "Success", "Failure", or "Invalid"
@field_validator("event_timestamp","timestamp_oldest_commit")
def validate_datetime(cls, d):
# oh lord jesus datetime validation
date_text = str(d)
iso8601_regex = r"^([\+-]?\d{4}(?!\d{2}\b))((-?)((0[1-9]|1[0-2])(\3([12]\d|0[1-9]|3[01]))?|W([0-4]\d|5[0-2])(-?[1-7])?|(00[1-9]|0[1-9]\d|[12]\d{2}|3([0-5]\d|6[1-6])))([T\s]((([01]\d|2[0-3])((:?)[0-5]\d)?|24\:?00)([\.,]\d+(?!:))?)?(\17[0-5]\d([\.,]\d+)?)?([zZ]|([\+-])([01]\d|2[0-3]):?([0-5]\d)?)?)?)?$"
if re.match(iso8601_regex, date_text):
return d
else:
raise ValueError(f"date must be in ISO-8601 format: {d}")
@field_validator("hashes")
def validate_hashes(cls, hashes):
if not len(hashes) > 0:
raise ValueError(f"commit hash list cannot be empty")
for h in hashes:
if not re.match(r"\b[0-9a-f]{5,40}\b", h):
raise ValueError(f"hash not valid sha1: {h}")
else:
return hashes
@field_validator("deploy_return_status")
def validate_return_status(cls, status):
if status not in ["Success", "Failure", "Invalid"]:
raise ValueError(f"return_status must be one of \"Success\", \"Failure\", or \"Invalid\": {status}")
else:
return status
class ServiceAvailabilityChange(BaseModel):
event_timestamp: datetime # should look like 2024-03-12T14:29:46-0700
service_id: str # practically arbitrary, but maybe useful for later
event_type: str # should be "outage" or "restoration"
@field_validator("event_type")
def validate_balanced_events(cls,event_type):
# since all inputs are validated one at a time, we can simplify the balancing logic
# we can use a naive algorithm (count outages, count restorations) here because we validate each input one at a time
stack = []
for event in service_events:
if event.event_type == "outage":
stack.append(event)
else:
if not stack or (\
event.event_type == 'restoration' and \
stack[-1] != 'outage'\
):
raise ValueError("no preceding outage for restoration event")
stack.pop()
# please replace "store the dataset in an array in memory" before deploying
deployments = []
service_events = []
@app.post("/api/events/deployment")
def append_deployment(deployment: Deployment):
deployments.append(deployment)
return deployment
@app.post("/api/events/service_availability")
def append_service_availability(service_event: ServiceAvailabilityChange):
service_events.append(service_event)
return service_event
@app.get("/api/metrics/deployment_frequency")
def get_deployment_frequency():
deploys_in_day = {}
for deployment in deployments:
if deployment.event_timestamp.date() in deploys_in_day:
deploys_in_day[deployment.event_timestamp.date()] += 1
else:
deploys_in_day[deployment.event_timestamp.date()] = 1
return len(deployments) / len(deploys_in_day)
@app.get("/api/metrics/lead_time_for_changes")
def get_lead_time_for_changes():
time_deltas = []
for deployment in deployments:
time_delta = deployment.event_timestamp - deployment.timestamp_oldest_commit
time_deltas.append(time_delta.seconds)
lead_time_for_changes = sum(time_deltas) / len(time_deltas)
return str(timedelta(seconds=lead_time_for_changes)) # standardize output format?
@app.get("/api/metrics/time_to_restore_service")
def get_time_to_restore_service():
# check for balanced events (a preceding outage for each restoration)
# for each balanced root-level event, get the time delta from first outage to final restoration
# append time delta to array of time deltas
# return average of time deltas array
@app.get("/api/metrics/change_failure_rate")
def get_change_failure_rate():
success_counter = 0
failure_counter = 0
for deployment in deployments:
if deployment.deploy_return_status == "Invalid":
pass
elif deployment.deploy_return_status == "Success":
success_counter += 1
else:
failure_counter += 1
return failure_counter / (success_counter + failure_counter)
# @app.get("/api/metrics/vanity")
# def get_vanity():

View File

@ -1,18 +0,0 @@
from datetime import datetime
import json
import requests
valid_deployment = {
"event_timestamp": str(datetime.now()),
"hashes": ["d7d8937e8f169727852dea77bae30a8749fe21fc"],
"oldest_commit_timestamp": str(datetime.now()),
"deploy_return_status": "Success"
}
def test_valid_deployment():
#payload =
endpoint = "http://127.0.0.1:8000/api/events/deployment"
response = requests.post(endpoint, json=json.dumps(valid_deployment))
print(response)
print(valid_deployment)
#assert response.status_code == 200

View File

@ -1,4 +0,0 @@
uvicorn==0.28.0
fastapi==0.110.0
pydantic==2.6.4
pytest==8.1.1

View File

@ -1,6 +0,0 @@
#!/bin/bash
curl -X 'POST' 'http://127.0.0.1:8000/api/events/deployment' -H 'accept: application/json' -H 'Content-Type: application/json' -d '{"event_timestamp": "2024-03-12T22:02:38.689Z","hashes": ["6ece311c24dd6a4b3dbbf8525a3a61854a32838d","d7d8937e8f169727852dea77bae30a8749fe21fc"],"timestamp_oldest_commit": "2024-03-11T22:02:38.689Z","deploy_return_status": "Failure"}'
curl -X 'POST' 'http://127.0.0.1:8000/api/events/deployment' -H 'accept: application/json' -H 'Content-Type: application/json' -d '{"event_timestamp": "2024-03-12T23:03:38.689Z","hashes": ["f5521851965c4866c5dc0e8edc9d5e2a40b5ebe6","b8c3bb11a978dbcbe507c53c62f715a728cdfd52"],"timestamp_oldest_commit": "2024-03-10T22:05:38.689Z","deploy_return_status": "Success"}'
curl -X 'POST' 'http://127.0.0.1:8000/api/events/deployment' -H 'accept: application/json' -H 'Content-Type: application/json' -d '{"event_timestamp": "2024-03-11T21:03:38.689Z","hashes": ["ae35c9c0e4f71ddf280bd297c42f04f2c0ce3838","d53e974d7e60295ed36c38a57870d1a6bfc7e399"],"timestamp_oldest_commit": "2024-03-11T20:05:38.689Z","deploy_return_status": "Success"}'
curl -X 'POST' 'http://127.0.0.1:8000/api/events/deployment' -H 'accept: application/json' -H 'Content-Type: application/json' -d '{"event_timestamp": "2024-03-10T23:03:38.689Z","hashes": ["b6a707faa68bc987ae549c0f36d053a412bd40da","b6a707faa68bc987ae549c0f36d053a412bd40da"],"timestamp_oldest_commit": "2024-03-10T14:05:38.689Z","deploy_return_status": "Success"}'
curl -X 'POST' 'http://127.0.0.1:8000/api/events/deployment' -H 'accept: application/json' -H 'Content-Type: application/json' -d '{"event_timestamp": "2024-02-10T23:03:38.689Z","hashes": ["94036270dd329559b58edc6f8780e03bd94509a3","b6d1abc911c08778424fb244de1f172f54905b81"],"timestamp_oldest_commit": "2024-02-09T14:05:38.689Z","deploy_return_status": "Invalid"}'

View File

@ -1,11 +0,0 @@
#!/bin/bash
curl -X 'POST' 'http://127.0.0.1:8000/api/events/service_availability' -H 'accept: application/json' -H 'Content-Type: application/json' -d '{"event_timestamp": "2024-03-11T18:02:00.000Z","service_id": "plex","event_type": "outage"}'
curl -X 'POST' 'http://127.0.0.1:8000/api/events/service_availability' -H 'accept: application/json' -H 'Content-Type: application/json' -d '{"event_timestamp": "2024-03-11T19:02:00.000Z","service_id": "plex","event_type": "restoration"}'
curl -X 'POST' 'http://127.0.0.1:8000/api/events/service_availability' -H 'accept: application/json' -H 'Content-Type: application/json' -d '{"event_timestamp": "2024-03-11T20:02:00.000Z","service_id": "nextcloud","event_type": "outage"}'
curl -X 'POST' 'http://127.0.0.1:8000/api/events/service_availability' -H 'accept: application/json' -H 'Content-Type: application/json' -d '{"event_timestamp": "2024-03-11T21:02:00.000Z","service_id": "nextcloud","event_type": "restoration"}'
curl -X 'POST' 'http://127.0.0.1:8000/api/events/service_availability' -H 'accept: application/json' -H 'Content-Type: application/json' -d '{"event_timestamp": "2024-03-11T22:02:00.000Z","service_id": "nextcloud","event_type": "outage"}'
curl -X 'POST' 'http://127.0.0.1:8000/api/events/service_availability' -H 'accept: application/json' -H 'Content-Type: application/json' -d '{"event_timestamp": "2024-03-12T01:02:00.000Z","service_id": "nextcloud","event_type": "restoration"}'
curl -X 'POST' 'http://127.0.0.1:8000/api/events/service_availability' -H 'accept: application/json' -H 'Content-Type: application/json' -d '{"event_timestamp": "2024-03-12T02:02:00.000Z","service_id": "plex","event_type": "outage"}'
curl -X 'POST' 'http://127.0.0.1:8000/api/events/service_availability' -H 'accept: application/json' -H 'Content-Type: application/json' -d '{"event_timestamp": "2024-03-12T03:02:00.000Z","service_id": "plex","event_type": "restoration"}'
curl -X 'POST' 'http://127.0.0.1:8000/api/events/service_availability' -H 'accept: application/json' -H 'Content-Type: application/json' -d '{"event_timestamp": "2024-03-12T04:02:00.000Z","service_id": "plex","event_type": "outage"}'
curl -X 'POST' 'http://127.0.0.1:8000/api/events/service_availability' -H 'accept: application/json' -H 'Content-Type: application/json' -d '{"event_timestamp": "2024-03-12T04:02:00.000Z","service_id": "plex","event_type": "restoration"}'

View File

@ -1,25 +0,0 @@
[user]
name = Joey Hafner
email = joey@jafner.net
[init]
defaultBranch = main
[filter "lfs"]
clean = git-lfs clean -- %f
smudge = git-lfs smudge -- %f
process = git-lfs filter-process
required = true
[credential "https://huggingface.co"]
username = ShortSwedishMan
[core]
editor = nano
excludesfile = ~/.gitignore
autocrlf = input
[color]
branch = auto
diff = auto
interactive = auto
status = auto
[push]
default = simple
[pull]
rebase = true

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

Some files were not shown because too many files have changed in this diff Show More