19 Commits

Author SHA1 Message Date
Thomas Peetz 85b5168445 fix typo in workflow demo.yaml
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 4s
2026-05-05 18:05:19 +02:00
Thomas Peetz e8763fe635 test gitea actions
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 13s
2026-05-05 18:02:08 +02:00
Thomas Peetz 69e58e6dfb Add environment variable for DB server 2026-05-04 15:21:18 +02:00
Thomas Peetz d8914627a3 move *.service files to subdirectory container and add service file for standalone operations in standalone 2026-05-04 15:02:41 +02:00
Thomas Peetz b256e20491 renamed systemd unit files 2026-04-27 07:08:41 +02:00
tpeetz a8df860752 Merge branch 'feature/44-evaluate-typst-for-documentation' into 'develop/0.3.0'
Resolve "Evaluate Typst for documentation"

Closes #44

See merge request tpeetz/kontor!42
2026-04-26 21:09:54 +02:00
tpeetz 4e88b52dc3 use separate files for chapters 2026-04-26 21:09:06 +02:00
tpeetz 9772c9f554 add subprojekt kontor-doc 2026-04-26 16:08:17 +02:00
tpeetz e81d473a69 Einrichtung Build 2026-04-23 23:14:26 +02:00
tpeetz aca9a1aa4a integrate kontor-angular 2026-04-19 20:05:50 +02:00
tpeetz 57129c2d37 integration von kontor-angular 2026-04-18 00:29:25 +02:00
tpeetz e09f7cddf3 rename Dockerfile to Containerfile and integrate kontor-angular in pod kontor 2026-04-17 00:18:50 +02:00
Thomas Peetz a68dd05794 update kontor-api to reflect api changes 2026-04-15 14:38:38 +02:00
Thomas Peetz 62e22c56c4 update usage of api for login by email or username 2026-04-15 11:32:18 +02:00
Thomas Peetz 7aa6a12044 improve konto-vue/Containerfile by explicitly stating images 2026-04-15 10:26:35 +02:00
tpeetz 39a0759b0b fix OAuth authentication 2026-04-12 20:08:42 +02:00
tpeetz e82f2825ae update systemd service files and add script to copy these files 2026-04-12 17:51:19 +02:00
tpeetz 74d29ae8ef Merge branch 'feature/43-typeerror-not-all-arguments-converted-during-string-formatting' into 'develop/0.3.0'
Resolve "TypeError: not all arguments converted during string formatting"

Closes #43

See merge request tpeetz/kontor!41
2026-04-12 17:14:46 +02:00
tpeetz 614c5f20aa Resolve "TypeError: not all arguments converted during string formatting" 2026-04-12 17:14:46 +02:00
43 changed files with 1276 additions and 104 deletions
+19
View File
@@ -0,0 +1,19 @@
name: Gitea Actions Demo
run-name: ${{ gitea.actor }} ist testing out Gitea Actions
on: [push]
jobs:
Explore-Gitea-Actions:
runs-on: inky
steps:
- run: echo "The job was automatically triggered by a ${{ gitea.event_name }} event."
- run: echo "This job is now running on a ${{ runner.os }} server hosted by Gitea!"
- run: echo "The name of your branch is ${{ gitea.ref }} and your repository is ${{ gitea.repository }}."
- name: Check out repository code
uses: actions/checkout@v4
- run: echo "The ${{ gitea.repository }} repository has been cloned to the runner."
- run: echo "The workflow is now ready to test your code on the runner."
- name: List files in the directory
run: |
ls ${{ gitea.workspace }}
- run: echo "The job's status is ${{ job.status }}."
@@ -1,5 +1,5 @@
### STAGE 1: Build ###
FROM node:22.15-alpine AS build
FROM docker.io/node:22-alpine AS build
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm install
@@ -7,8 +7,9 @@ COPY . .
RUN npm run build
### STAGE 2: Run ###
FROM nginx:1.17.1-alpine
FROM docker.io/library/nginx:stable-alpine
COPY nginx.conf /etc/nginx/conf.d/default.conf
COPY --from=build /app/dist/kontor-angular/browser /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
+1
View File
@@ -1,4 +1,5 @@
server {
listen 8800;
# Root-Verzeichnis für den Server setzen (wir kopieren unsere Anwendung hierher)
root /usr/share/nginx/html;
+5 -3
View File
@@ -5,7 +5,8 @@ from fastapi import APIRouter, Body, HTTPException, status, Depends, Response
from fastapi.security import OAuth2PasswordRequestForm
from src.core.config import settings
from src.core.security import create_access_token, authenticate_user, get_current_active_user
from src.core.log_conf import logger
from src.core.security import create_access_token, authenticate_user_by_email, authenticate_user_by_username, get_current_active_user
from src.db.models.admin import Profile
from src.schema.admin import Token, ProfileModel
from src.webapps.auth.forms import LoginForm
@@ -15,7 +16,8 @@ router = APIRouter()
@router.post("/token")
def login_for_access_token(form_data: Annotated[OAuth2PasswordRequestForm, Depends()]) -> Token:
user = authenticate_user(form_data.username, form_data.password)
user = authenticate_user_by_username(form_data.username, form_data.password)
logger.info(f"Request /token: login with {form_data.username}")
if not user:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
@@ -31,7 +33,7 @@ def login_for_access_token(form_data: Annotated[OAuth2PasswordRequestForm, Depen
# @router.post("/token-cookie", response_model=Token)
def login_for_token_cookie(response: Response, form_data: LoginForm = Depends()):
user = authenticate_user(form_data.username, form_data.password) # type: ignore
user = authenticate_user_by_email(form_data.username, form_data.password) # type: ignore
if not user:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
+19 -4
View File
@@ -1,11 +1,12 @@
from datetime import timedelta
from fastapi import APIRouter, HTTPException, status
from fastapi import APIRouter, Depends, HTTPException, status
from fastapi.security import OAuth2PasswordRequestForm, OAuth2PasswordBearer, SecurityScopes
from pydantic import BaseModel
from typing import Annotated
from src.core.config import settings
from src.core.log_conf import logger
from src.core.security import authenticate_user, create_access_token
from src.core.security import authenticate_user_by_email, authenticate_user_by_username, create_access_token
from src.schema.admin import Token
login_router = APIRouter()
@@ -25,7 +26,7 @@ class LoginRequest(BaseModel):
)
def login(request: LoginRequest) -> Token:
logger.info(f"login with {request.email}")
user = authenticate_user(request.email, request.password)
user = authenticate_user_by_email(request.email, request.password)
scopes = ["admin", "read"]
if not user:
raise HTTPException(
@@ -39,3 +40,17 @@ def login(request: LoginRequest) -> Token:
expires_delta=access_token_expires,
)
return Token(access_token=access_token, token_type="bearer")
@login_router.post("/token", tags=["login"], summary="Login for access token")
async def login_for_access_token(
form_data: Annotated[OAuth2PasswordRequestForm, Depends()],
) -> Token:
user = authenticate_user_by_username(form_data.username, form_data.password)
if not user:
raise HTTPException(status_code=400, detail="Incorrect username or password")
access_token_expires = timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES)
access_token = create_access_token(
data={"sub": user.user_name, "scope": " ".join(form_data.scopes)},
expires_delta=access_token_expires,
)
return Token(access_token=access_token, token_type="bearer")
+57 -37
View File
@@ -13,48 +13,47 @@ from pydantic import ValidationError
from src.core.config import settings
from src.core.log_conf import logger
from src.db.models.admin import Profile
from src.db.repository.admin import get_profile, is_database_empty
from src.db.repository.admin import get_profile_by_username, get_profile_by_email, is_database_empty
from src.db.session import SessionLocal
from src.schema.admin import ProfileModel, TokenData
oauth2_scheme = OAuth2PasswordBearer(
tokenUrl="/api/login/token",
scopes={"me": "read", "admin": "read"},
tokenUrl="/token",
scopes={"me": "read", "admin": "read", "ROLE_ADMIN": "admin", "ROLE_MEDIA": "media", "ROLE_USER": "user"},
)
class OAuth2PasswordBearerWithCookie(OAuth2):
def __init__(
self,
tokenUrl: str,
scheme_name: Optional[str] = None,
scopes: Optional[Dict[str, str]] = None,
auto_error: bool = True,
):
if not scopes:
scopes = {}
flows = OAuthFlowsModel(password={"tokenUrl": tokenUrl, "scopes": scopes}) # type: ignore
super().__init__(flows=flows, scheme_name=scheme_name, auto_error=auto_error)
# class OAuth2PasswordBearerWithCookie(OAuth2):
# def __init__(
# self,
# tokenUrl: str,
# scheme_name: Optional[str] = None,
# scopes: Optional[Dict[str, str]] = None,
# auto_error: bool = True,
# ):
# if not scopes:
# scopes = {}
# flows = OAuthFlowsModel(password={"tokenUrl": tokenUrl, "scopes": scopes}) # type: ignore
# super().__init__(flows=flows, scheme_name=scheme_name, auto_error=auto_error)
async def __call__(self, request: Request) -> Optional[str]:
authorization: str = request.cookies.get("access_token") # changed to accept access token from httpOnly Cookie
# async def __call__(self, request: Request) -> Optional[str]:
# authorization: str = request.cookies.get("access_token") # changed to accept access token from httpOnly Cookie
scheme, param = get_authorization_scheme_param(authorization)
if not authorization or scheme.lower() != "bearer":
if self.auto_error:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Not authenticated",
headers={"WWW-Authenticate": "Bearer"},
)
else:
return None
return param
# scheme, param = get_authorization_scheme_param(authorization)
# if not authorization or scheme.lower() != "bearer":
# if self.auto_error:
# raise HTTPException(
# status_code=status.HTTP_401_UNAUTHORIZED,
# detail="Not authenticated",
# headers={"WWW-Authenticate": "Bearer"},
# )
# else:
# return None
# return param
def authenticate_user(username: str, password: str) -> Optional[Profile]:
def authenticate_user_by_email(email: str, password: str) -> Optional[Profile]:
with SessionLocal() as db:
user = get_profile(username=username, db=db)
user = get_profile_by_email(email=email, db=db)
logger.debug(user)
if not user:
if is_database_empty(db):
@@ -65,7 +64,26 @@ def authenticate_user(username: str, password: str) -> Optional[Profile]:
return None
else:
if bcrypt.checkpw(password.encode(), user.password.encode()):
print("User successful authenticated")
logger.info("User successful authenticated")
else:
logger.info("Authentication failed!")
return user
def authenticate_user_by_username(username: str, password: str) -> Optional[Profile]:
with SessionLocal() as db:
user = get_profile_by_username(username=username, db=db)
logger.debug(user)
if not user:
if is_database_empty(db):
logger.info("database is empty, use temporary access")
user = Profile()
user.email = "init_user@thpeetz.de"
return user
return None
else:
if bcrypt.checkpw(password.encode(), user.password.encode()):
logger.info("User successful authenticated")
else:
logger.info("Authentication failed!")
return user
@@ -103,17 +121,19 @@ async def get_current_user(
token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM]
)
username: str = payload.get("sub") # type: ignore
logger.info("username/email extracted is ", username)
logger.info("username/email extracted is %s", username)
if username is None:
raise credentials_exception
scope: str = payload.get("scope", "")
token_scopes: List[str] = scope.split(" ")
token_data = TokenData(scopes=token_scopes, username=username)
except (JWTError, ValidationError):
logger.info("Exception raised", exc_info=True)
raise credentials_exception
with SessionLocal() as db:
user = get_profile(username=token_data.username, db=db) # type: ignore
user = get_profile_by_username(username=token_data.username, db=db)
if user is None:
logger.info("user not found")
raise credentials_exception
for scope in security_scopes.scopes:
if scope not in token_scopes:
@@ -128,7 +148,7 @@ async def get_current_user(
async def get_current_active_user(
current_user: Annotated[Profile, Security(get_current_user, scopes=["me"])],
) -> ProfileModel:
if not current_user.enabled: # type: ignore
if not current_user.enabled:
raise HTTPException(status_code=400, detail="Inactive user")
user_model = ProfileModel(
username=current_user.user_name,
@@ -150,13 +170,13 @@ def get_current_user_from_token(token: str = Depends(oauth2_scheme)):
token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM]
)
username: str = payload.get("sub") # type: ignore
logger.info("username/email extracted is ", username)
logger.info("username/email extracted is %s", username)
if username is None:
raise credentials_exception
except JWTError:
raise credentials_exception
with SessionLocal() as db:
user = get_profile(username=username, db=db)
user = get_profile_by_email(email=username, db=db)
if user is None:
raise credentials_exception
return user
+7 -3
View File
@@ -1,12 +1,16 @@
from typing import AnyStr, Optional
from typing import Optional
from sqlalchemy.orm import Session
from src.db.models.admin import Profile
def get_profile(username: AnyStr, db: Session) -> Optional[Profile]:
profile = db.query(Profile).filter(Profile.email == username).first()
def get_profile_by_username(username: str, db: Session) -> Optional[Profile]:
profile = db.query(Profile).filter(Profile.user_name == username).first()
return profile
def get_profile_by_email(email: str, db: Session) -> Optional[Profile]:
profile = db.query(Profile).filter(Profile.email == email).first()
return profile
def is_database_empty(db: Session) -> bool:
+12
View File
@@ -0,0 +1,12 @@
#
# https://help.github.com/articles/dealing-with-line-endings/
#
# Linux start script should use lf
/gradlew text eol=lf
# These are Windows script files and should use crlf
*.bat text eol=crlf
# Binary files should be left untouched
*.jar binary
+8
View File
@@ -0,0 +1,8 @@
# Ignore Gradle project-specific cache directory
.gradle
# Ignore Gradle build output directory
build
# Ignore Kotlin plugin data
.kotlin
+21
View File
@@ -0,0 +1,21 @@
/*
* This file was generated by the Gradle 'init' task.
*
* This is a general purpose Gradle build.
* Learn more about Gradle by exploring our Samples at https://docs.gradle.org/9.4.1/samples
*/
plugins {
id("base")
id("de.infolektuell.typst") version "0.8.0"
}
typst.version = "v0.14.2"
typst.sourceSets {
// Sources in src/main
val main by registering {
// The files to compile (without .typ extension) in src/main/typst
documents = listOf("kontor") // src/main/typst/document.typst
}
}
+5
View File
@@ -0,0 +1,5 @@
# This file was generated by the Gradle 'init' task.
# https://docs.gradle.org/current/userguide/build_environment.html#sec:gradle_configuration_properties
org.gradle.configuration-cache=true
+2
View File
@@ -0,0 +1,2 @@
# This file was generated by the Gradle 'init' task.
# https://docs.gradle.org/current/userguide/version_catalogs.html#sec::toml-dependencies-format
Binary file not shown.
+7
View File
@@ -0,0 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Vendored Executable
+248
View File
@@ -0,0 +1,248 @@
#!/bin/sh
#
# Copyright © 2015 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/2d6327017519d23b96af35865dc997fcb544fb40/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
warn () {
echo "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"
+93
View File
@@ -0,0 +1,93 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:execute
@rem Setup the command line
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
:end
@rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
+8
View File
@@ -0,0 +1,8 @@
/*
* This file was generated by the Gradle 'init' task.
*
* The settings file is used to specify which projects to include in your build.
* For more detailed information on multi-project builds, please refer to https://docs.gradle.org/9.4.1/userguide/multi_project_builds.html in the Gradle documentation.
*/
rootProject.name = "kontor-doc"
+25
View File
@@ -0,0 +1,25 @@
= Anforderungen der Domäne
== Systemfunktionen
=== Anwendungsfälle
=== Akteure
=== Zielgruppen
== Anforderungen
=== Anforderungen an externe Schnittstellen
=== Funktionale Anforderungen
=== Qualitätsanforderungen
=== Randbedingungen
=== Weitere Anforderungen
=== Wartungs- und Supportinformationen
== Verifikation
+99
View File
@@ -0,0 +1,99 @@
//#set page("a4")
#import "@preview/basic-report:0.4.0": *
#import "@preview/in-dexter:0.7.2": *
#import "@preview/pintorita:0.1.4"
#set text(fill: white)
#show raw.where(lang: "pintora"): it => pintorita.render(it.text)
#show: it => basic-report(
doc-category: "Entwicklungs- und Projekthandbuch",
doc-title: "Projekt kontor",
author: "Thomas Peetz",
//affiliation: "MouseTec, Entenhausen",
//logo: image("assets/aerospace-engineering.png", width: 2cm),
// <a href="https://www.flaticon.com/free-icons/aerospace" title="aerospace icons">Aerospace icons created by gravisio - Flaticon</a>
language: "de",
compact-mode: false,
it
)
= Allgemeines
#figure(
table(
columns: 4,
[Version], [Datum], [Autor], [Änderungsgrund / Bemerkungen],
[1.0.0], [16.05.2022], [Thomas Peetz], [Ersterstellung],
),
caption: [Dokumenthistorie],
)
== Zweck des Dokumentes
Das Entwicklungshandbuch beschreibt die Werkzeuge und die Vorgehensweise bei der Entwicklung
im Projekt kontor-spring und der Erstellung der Dokumentation.
== Verwendete Tools
=== Gitea
Für die Verwaltung des Sourcecode kommt Gitea #index[Gitea]@gitea zum Einsatz.
Mit Gitea werden auch die Projektaufgaben verwaltet.
Das Projekt und das dazugehörige Git Repository sind unter der Adresse @gitea-kontor zu finden.
= Erstellung der Dokumentation
Die Dokumentation des Projektes wird mit Asciidoctor #index[Asciidoctor]@asciidoctor geschrieben.
Die Dokumente erhalten ihre Namen nach dem jeweiligen Hauptdokument.
== Quellcode Verwaltung
Die Asciidoctor-Dateien haben die Endung `.adoc`.
== Buildsystem
Zur Erstellung der PDF-Dateien aus den Asciidoctor-Dateien wird das Buildsystem Gradle #index[Gradle]@gradle verwendet.
Die Dateien für die Dokumente liegen im Verzeichnis `src/docs/asciidoc`.
Der Gradle Build wird über die Datei `build.gradle` definiert.
#pagebreak()
= Einführung
== Zweck
== Stakeholder des Systems
== Systemumfang
=== Zielsetzung des Systems
#pagebreak()
#include "system.typ"
#include "domain.typ"
#include "projekt.typ"
= Verschiedenes
== Erreichbarkeiten
= Referenzen
#bibliography("reference.yaml", title: none, full: true)
= Glossar
= Index
#make-index(title: none, outlined: true, use-page-counter: true)
= Verzeichnisse
== Abbildungsverzeichnis
#outline(title: none, target: figure.where(kind: image))
== Tabellenverzeichnis
#outline(title: none, target: figure.where(kind: table))
+46
View File
@@ -0,0 +1,46 @@
= Projektbeschreibung
== Ausgangslage
//==== Rechtliche Vorgaben und Rahmenbedingungen
//=== Rahmenbedingungen
//==== Vorhandene Regelungen
== Projektziele
== Projektabgrenzung
//=== Voraussichtliche Kosten
//=== Projektrisiken
//==== Produktivität
//==== Finanzielle Risiken
//==== Akzeptanz
= Projektorganisation
== Projekt-Aufbauorganisation
== Rollendefinition
//==== Projektauftraggeber
//==== Projektausschuss
//==== Beratung / Qualitätssicherung
=== Projekteiter
=== Projektteam
=== Liste der Stakeholder
== Projektablauforganisation
=== Projekt-Phasen
== Erstellung der Projektdokumentation
+15
View File
@@ -0,0 +1,15 @@
asciidoctor:
type: web
url: http://asciidoctor.org
gitea:
type: web
url: http://www.gitea.org
gradle:
type: web
url: http://www.gradle.org
jenkins:
type: web
url: http://jenkins-ci.org
gitea-kontor:
type: web
url: https://gitea.thpeetz.de/kontor/kontor-spring
+336
View File
@@ -0,0 +1,336 @@
= Systemübersicht
== Systemkontext
== Systemarchitektur
== Systemschnittstellen
=== Realisierte Schnittstellen
=== Verwendete Schnittstellen
== Logisches Datenmodell
#figure(
kind: image,
```pintora
erDiagram
USER {
string id PK
datetime created_date
datetime last_modified_date
int version
string email
boolean enabled
string firstName
string lastName
string password
string token
boolean tokenExpired
string userName UNIQUE
}
USER ||--o{ AUTHORIZATION_MATRIX : "matrix"
ROLE {
string id PK
datetime created_date
datetime last_modified_date
int version
string name
}
ROLE ||--o{ AUTHORIZATION_MATRIX : "matrix"
AUTHORIZATION_MATRIX {
string id PK
datetime created_date
datetime last_modified_date
int version
string user_id FK
string role_id FK
}
```,
caption: [Benutzer ER-Diagramm]
)
#figure(
kind: image,
```pintora
erDiagram
comic {
string id PK
datetime created_date
datetime last_modified_date
int version
boolean completed
boolean currentOrder
string title
string publisher_id FK
}
comic ||--o{ comic_work : "1"
comic ||--o{ issue : "1"
comic ||--o{ volume : "1"
comic ||--o{ story_arc : "1"
comic ||--o{ trade_paperback : "1"
volume {
string id PK
datetime created_date
datetime last_modified_date
int version
string name
string comic_id FK
}
volume ||--o{ issue : "1"
issue {
string id PK
datetime created_date
datetime last_modified_date
int version
boolean in_stock
boolean is_read
string issue_number
string comic_id FK
string volume_id FK
}
publisher {
string id PK
datetime created_date
datetime last_modified_date
int version
string name
}
publisher ||--o{ comic : "1"
artist {
string id PK
datetime created_date
datetime last_modified_date
int version
string name
}
artist ||--o{ comic_work : "1"
story_arc {
string id PK
datetime created_date
datetime last_modified_date
int version
string name
string comic_id FK
}
trade_paperback {
string id PK
datetime created_date
datetime last_modified_date
int version
int issueStart
int issueEnd
string name
string comic_id FK
}
worktype {
string id PK
datetime created_date
datetime last_modified_date
int version
string name
}
worktype ||--o{ comic-work : "1"
comic_work {
string id PK
datetime created_date
datetime last_modified_date
int version
string artist_id FK
string comic_id FK
string worktype_id FK
}
```,
caption: [Comics ER-Diagramm]
)
#figure(
kind: image,
```pintora
erDiagram
sport {
string id PK
datetime created_date
datetime last_modified_date
int version
string name
}
team {
string id PK
datetime created_date
datetime last_modified_date
int version
string name
string short_name
string sport_id FK
}
field_position {
string id PK
datetime created_date
datetime last_modified_date
int version
string name
string short_name
string sport_id FK
}
rooster {
string id PK
datetime created_date
datetime last_modified_date
int version
int year
string player_id FK
string position_id FK
string team_id FK
}
player {
string id PK
datetime created_date
datetime last_modified_date
int version
string first_name
string last_name
}
vendor {
string id PK
datetime created_date
datetime last_modified_date
int version
string name
}
card_set {
string id PK
datetime created_date
datetime last_modified_date
int version
boolean insert_set
string name
boolean parallel_set
string vendor_id FK
}
card {
string id PK
datetime created_date
datetime last_modified_date
int version
int cardNumber
int year
string card_set FK
string rooster_id FK
string vendor_id FK
}
sport ||--o{ team : "1"
sport ||--o{ field_position : "1"
field_position ||--o{ rooster : "1"
player ||--o{ rooster : "1"
team ||--o{ rooster : "1"
vendor ||--o{ card : "1"
card_set ||--o{ card : "1"
rooster ||--o{ card : "1"
```,
caption: [TYSC ER-Diagramm]
)
#figure(
kind: image,
```pintora
erDiagram
book {
string id PK
datetime created_date
datetime last_modified_date
int version
string isbn UNIQUE
string title
int year
string publisher_id FK
}
bookshelf_publisher {
string id PK
datetime created_date
datetime last_modified_date
int version
string name UNIQUE
}
author {
string id PK
datetime created_date
datetime last_modified_date
int version
string first_name
string last_name
}
article {
string id PK
datetime created_date
datetime last_modified_date
int version
string title
}
article_author {
string id PK
datetime created_date
datetime last_modified_date
int version
string article_id FK
string author_id FK
}
book_author {
string id PK
datetime created_date
datetime last_modified_date
int version
string book_id FK
string author_id FK
}
bookshelf_publisher ||--o{ book : "1"
article ||--o{ article_author : "1"
author ||--o{ article_author : "1"
book ||--o{ book_author : "1"
author ||--o{ book_author : "1"
```,
caption: [Bookshelf ER-Diagramm]
)
#figure(
kind: image,
```pintora
erDiagram
mail {
string id PK
datetime created_date
datetime last_modified_date
int version
string subject
string content
datetime received_date
datetime sent_date
}
mail_account {
string id PK
datetime created_date
datetime last_modified_date
int version
string host
string password
int port
string protocol
boolean start_tls
string user_name
}
mail_address {
string id PK
datetime created_date
datetime last_modified_date
int version
string internet_address UNIQUE
string personal
string user_id FK
}
user ||--o{ mail_address : "1"
```,
caption: [Mail ER-Diagramm]
)
=== Einschränkungen
+1
View File
@@ -30,6 +30,7 @@ plugins {
alias(libs.plugins.asciidoctorPdf)
alias(libs.plugins.asciidoctorConvert)
alias(libs.plugins.asciidoctorGems)
id "de.infolektuell.typst" version "0.8.0"
}
repositories {
+3 -2
View File
@@ -1,4 +1,4 @@
FROM node:22.18.0-bookworm-slim AS build
FROM docker.io/node:20-alpine AS build
WORKDIR /app
ENV PATH=/app/node_modules/.bin:$PATH
COPY package.json /app/package.json
@@ -7,7 +7,8 @@ RUN npm install -g @vue/cli
COPY . /app
RUN npm run build
FROM nginx:1.29.0-alpine
FROM docker.io/library/nginx:stable-alpine
COPY nginx.conf /etc/nginx/conf.d/default.conf
COPY --from=build /app/dist /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
+38
View File
@@ -0,0 +1,38 @@
server {
listen 8700;
# Root-Verzeichnis für den Server setzen (wir kopieren unsere Anwendung hierher)
root /usr/share/nginx/html;
# Definieren der Standard-Indexdatei (Angular erstellt die Datei index.html für uns und sie befindet sich im oben genannten Verzeichnis)
index index.html;
# Cache-Header für Medien-ASsets
location ~* \.(?:cur|jpe?g|gif|htc|ico|png|xml|otf|ttf|eot|woff|woff2|svg)$ {
access_log off;
add_header Pragma "must-revalidate, public";
add_header Cache-Control "must-revalidate, public";
expires max;
tcp_nodelay off;
}
# Cache-Header für HTML, CSS und JS-Dateien
location ~* \.(?:css|js|html)$ {
access_log off;
add_header Pragma "must-revalidate, public";
add_header Cache-Control "must-revalidate, public";
expires 2d;
tcp_nodelay off;
}
# Konfiguration für den /-Pfad
location / {
# Zunächst versuchen wir die angeforderte URI auzuliefern
# Klappt das nicht, versuchen wir es mit einem abschließenden Slash
# Klappt auch das nicht, liefern wir die index.html aus.
# Das ist nötig, damit Angular-Routen korrekt augeflöst und ausgeliefert werden
try_files $uri $uri/ /index.html;
}
}
+3 -3
View File
@@ -5,12 +5,12 @@ const API_URL = "http://localhost:8200/";
class AuthService {
login(user) {
return axios
.post(API_URL + "login", {
email: user.username,
.post(API_URL + "api/login/token", {
username: user.username,
password: user.password,
})
.then((response) => {
if (response.data.accessToken) {
if (response.data.access_token) {
localStorage.setItem("user", JSON.stringify(response.data));
}
return response.data;
+2
View File
@@ -20,4 +20,6 @@ echo " => Baue Image localhost/kontor-javalin:0.3.0"
buildah build -t kontor-javalin:0.3.0 kontor-javalin
echo " => Baue Image localhost/kontor-vue:0.3.0"
buildah build -t kontor-vue:0.3.0 kontor-vue
echo " => Baue Image localhost/kontor-angular:0.3.0"
buildah build -t kontor-angular:0.3.0 kontor-angular
Executable
+11
View File
@@ -0,0 +1,11 @@
#!/bin/sh
# script/service: Setup user systemd scripts
set -e
cd "$(dirname "$0")/.."
echo "==> Setting up systemd user services"
cp service/container/*.service $HOME/.config/systemd/user/
+21 -2
View File
@@ -8,7 +8,7 @@ cd "$(dirname "$0")/.."
script/bootstrap
script/cibuild
#script/cibuild
echo "==> Setting up pod kontor"
@@ -29,7 +29,8 @@ else
-p 8400:8400 \
-p 8500:8500 \
-p 8600:8600 \
-p 8700:80 \
-p 8700:8700 \
-p 8800:8800 \
-p 8900:8080 \
-p 61616:61616 \
-p 8161:8161 \
@@ -235,3 +236,21 @@ else
localhost/kontor-vue:0.3.0
fi
echo "==> Setting up container kontor-angular"
if podman image exists localhost/kontor-angular:0.3.0; then
echo " => Image localhost/kontor-angular:0.3.0 available"
fi
if podman container exists kontor-angular; then
if podman ps -q --filter "name=kontor-angular"; then
echo " => kontor-angular is running"
fi
else
podman run -d \
--replace \
--pod kontor \
--name kontor-angular \
--label "io.containers.autoupdate=local" \
--label "PODMAN_SYSTEMD_UNIT=container-kontor-angular.service" \
localhost/kontor-angular:0.3.0
fi
+3 -1
View File
@@ -9,7 +9,9 @@ cd "$(dirname "$0")/.."
echo "==> Stopping and removing pod kontor"
if podman pod exists kontor; then
podman pod stop kontor
podman pod rm kontor
fi
script/cibuild
script/setup
@@ -1,12 +1,11 @@
# container-activemq.service
# kontor-activemq.service
# autogenerated by Podman 4.9.3
# Fri Feb 6 17:23:12 CET 2026
# Sun Apr 19 16:28:14 CEST 2026
[Unit]
Description=Podman container-activemq.service
Description=Podman kontor-activemq.service
Documentation=man:podman-generate-systemd(1)
Wants=network-online.target
After=network-online.target
RequiresMountsFor=%t/containers
BindsTo=pod-kontor.service
After=pod-kontor.service
@@ -1,12 +1,11 @@
# container-adminer.service
# kontor-adminer.service
# autogenerated by Podman 4.9.3
# Fri Feb 6 17:23:12 CET 2026
# Sun Apr 19 16:28:14 CEST 2026
[Unit]
Description=Podman container-adminer.service
Description=Podman kontor-adminer.service
Documentation=man:podman-generate-systemd(1)
Wants=network-online.target
After=network-online.target
RequiresMountsFor=%t/containers
BindsTo=pod-kontor.service
After=pod-kontor.service
@@ -23,7 +22,8 @@ ExecStart=/usr/bin/podman run \
--sdnotify=conmon \
-d \
--replace \
--name adminer docker.io/library/adminer
--name kontor-adminer \
docker.io/library/adminer
ExecStop=/usr/bin/podman stop \
--ignore -t 10 \
--cidfile=%t/%n.ctr-id
+41
View File
@@ -0,0 +1,41 @@
# kontor-angular.service
# autogenerated by Podman 4.9.3
# Sun Apr 19 16:28:14 CEST 2026
[Unit]
Description=Podman kontor-angular.service
Documentation=man:podman-generate-systemd(1)
Wants=network-online.target
After=network-online.target
RequiresMountsFor=%t/containers
BindsTo=pod-kontor.service
After=pod-kontor.service
[Service]
Environment=PODMAN_SYSTEMD_UNIT=%n
Restart=on-failure
TimeoutStopSec=70
ExecStart=/usr/bin/podman run \
--cidfile=%t/%n.ctr-id \
--cgroups=no-conmon \
--rm \
--pod-id-file %t/pod-kontor.pod-id \
--sdnotify=conmon \
-d \
--replace \
--name kontor-angular \
--label io.containers.autoupdate=local \
--label PODMAN_SYSTEMD_UNIT=kontor-angular.service \
localhost/kontor-angular:0.3.0
ExecStop=/usr/bin/podman stop \
--ignore -t 10 \
--cidfile=%t/%n.ctr-id
ExecStopPost=/usr/bin/podman rm \
-f \
--ignore -t 10 \
--cidfile=%t/%n.ctr-id
Type=notify
NotifyAccess=all
[Install]
WantedBy=default.target
@@ -1,12 +1,11 @@
# container-kontor-api.service
# kontor-api.service
# autogenerated by Podman 4.9.3
# Fri Feb 6 17:23:12 CET 2026
# Sun Apr 19 16:28:14 CEST 2026
[Unit]
Description=Podman container-kontor-api.service
Description=Podmanco kontor-api.service
Documentation=man:podman-generate-systemd(1)
Wants=network-online.target
After=network-online.target
RequiresMountsFor=%t/containers
BindsTo=pod-kontor.service
After=pod-kontor.service
@@ -25,10 +24,12 @@ ExecStart=/usr/bin/podman run \
--replace \
--name kontor-api \
--label io.containers.autoupdate=local \
--label PODMAN_SYSTEMD_UNIT=container-kontor-api.service "--health-cmd=curl -f http://kontor-api:8200/health || exit 1" \
--label PODMAN_SYSTEMD_UNIT=kontor-api.service \
--health-cmd="curl -f http://kontor-api:8200/health || exit 1" \
--health-interval=1s \
--health-timeout=5s \
--health-retries=10 localhost/kontor-api:0.3.0
--health-retries=10 \
localhost/kontor-api:0.3.0
ExecStop=/usr/bin/podman stop \
--ignore -t 10 \
--cidfile=%t/%n.ctr-id
@@ -1,9 +1,9 @@
# container-kontor-echo.service
# kontor-echo.service
# autogenerated by Podman 4.9.3
# Fri Feb 6 17:23:12 CET 2026
# Sun Apr 19 16:28:14 CEST 2026
[Unit]
Description=Podman container-kontor-echo.service
Description=Podman kontor-echo.service
Documentation=man:podman-generate-systemd(1)
Wants=network-online.target
After=network-online.target
@@ -25,10 +25,12 @@ ExecStart=/usr/bin/podman run \
--replace \
--name kontor-echo \
--label io.containers.autoupdate=local \
--label PODMAN_SYSTEMD_UNIT=container-kontor-echo.service "--health-cmd=curl -f http://kontor-echo:8400/health || exit 1" \
--label PODMAN_SYSTEMD_UNIT=kontor-echo.service \
--health-cmd="curl -f http://kontor-echo:8400/health || exit 1" \
--health-interval=1s \
--health-timeout=5s \
--health-retries=10 localhost/kontor-echo:0.3.0
--health-retries=10 \
localhost/kontor-echo:0.3.0
ExecStop=/usr/bin/podman stop \
--ignore -t 10 \
--cidfile=%t/%n.ctr-id
@@ -1,9 +1,9 @@
# container-kontor-fiber.service
# kontor-fiber.service
# autogenerated by Podman 4.9.3
# Fri Feb 6 17:23:12 CET 2026
# Sun Apr 19 16:28:14 CEST 2026
[Unit]
Description=Podman container-kontor-fiber.service
Description=Podman kontor-fiber.service
Documentation=man:podman-generate-systemd(1)
Wants=network-online.target
After=network-online.target
@@ -25,10 +25,12 @@ ExecStart=/usr/bin/podman run \
--replace \
--name kontor-fiber \
--label io.containers.autoupdate=local \
--label PODMAN_SYSTEMD_UNIT=container-kontor-fiber.service "--health-cmd=curl -f http://kontor-echo:8500/health || exit 1" \
--label PODMAN_SYSTEMD_UNIT=kontor-fiber.service \
--health-cmd="curl -f http://kontor-echo:8500/health || exit 1" \
--health-interval=1s \
--health-timeout=5s \
--health-retries=10 localhost/kontor-fiber:0.3.0
--health-retries=10 \
localhost/kontor-fiber:0.3.0
ExecStop=/usr/bin/podman stop \
--ignore -t 10 \
--cidfile=%t/%n.ctr-id
@@ -1,9 +1,9 @@
# container-kontor-javalin.service
# kontor-javalin.service
# autogenerated by Podman 4.9.3
# Fri Feb 6 17:23:12 CET 2026
# Sun Apr 19 16:28:14 CEST 2026
[Unit]
Description=Podman container-kontor-javalin.service
Description=Podman kontor-javalin.service
Documentation=man:podman-generate-systemd(1)
Wants=network-online.target
After=network-online.target
@@ -25,10 +25,12 @@ ExecStart=/usr/bin/podman run \
--replace \
--name kontor-javalin \
--label io.containers.autoupdate=local \
--label PODMAN_SYSTEMD_UNIT=container-kontor-javalin.service "--health-cmd=curl -f http://kontor-echo:8600/health || exit 1" \
--label PODMAN_SYSTEMD_UNIT=kontor-javalin.service \
--health-cmd="curl -f http://kontor-echo:8600/health || exit 1" \
--health-interval=1s \
--health-timeout=5s \
--health-retries=10 localhost/kontor-javalin:0.3.0
--health-retries=10 \
localhost/kontor-javalin:0.3.0
ExecStop=/usr/bin/podman stop \
--ignore -t 10 \
--cidfile=%t/%n.ctr-id
@@ -1,9 +1,9 @@
# container-postgres.service
# kontor-postgres.service
# autogenerated by Podman 4.9.3
# Fri Feb 6 17:23:12 CET 2026
# Sun Apr 19 16:28:14 CEST 2026
[Unit]
Description=Podman container-postgres.service
Description=Podman kontor-postgres.service
Documentation=man:podman-generate-systemd(1)
Wants=network-online.target
After=network-online.target
@@ -27,10 +27,12 @@ ExecStart=/usr/bin/podman run \
-e POSTGRES_PASSWORD=kontor \
-e POSTGRES_DB=kontor \
-e POSTGRES_USER=kontor \
-v kontor-db:/var/lib/postgresql/data "--health-cmd=pg_isready -U kontor || exit 1" \
-v kontor-db:/var/lib/postgresql/data \
--health-cmd="pg_isready -U kontor || exit 1" \
--health-interval=1s \
--health-timeout=5s \
--health-retries=10 docker.io/library/postgres:17
--health-retries=10 \
docker.io/library/postgres:17
ExecStop=/usr/bin/podman stop \
--ignore -t 10 \
--cidfile=%t/%n.ctr-id
@@ -1,9 +1,9 @@
# container-kontor-quarkus.service
# kontor-quarkus.service
# autogenerated by Podman 4.9.3
# Fri Feb 6 17:23:12 CET 2026
# Sun Apr 19 16:28:14 CEST 2026
[Unit]
Description=Podman container-kontor-quarkus.service
Description=Podman kontor-quarkus.service
Documentation=man:podman-generate-systemd(1)
Wants=network-online.target
After=network-online.target
@@ -25,10 +25,12 @@ ExecStart=/usr/bin/podman run \
--replace \
--name kontor-quarkus \
--label io.containers.autoupdate=local \
--label PODMAN_SYSTEMD_UNIT=container-kontor-quarkus.service "--health-cmd=curl -f http://kontor-quarkus:8300/q/health || exit 1" \
--label PODMAN_SYSTEMD_UNIT=kontor-quarkus.service \
--health-cmd="curl -f http://kontor-quarkus:8300/q/health || exit 1" \
--health-interval=1s \
--health-timeout=5s \
--health-retries=10 localhost/kontor-quarkus:0.3.0
--health-retries=10 \
localhost/kontor-quarkus:0.3.0
ExecStop=/usr/bin/podman stop \
--ignore -t 10 \
--cidfile=%t/%n.ctr-id
@@ -1,9 +1,9 @@
# container-kontor-spring.service
# kontor-spring.service
# autogenerated by Podman 4.9.3
# Fri Feb 6 17:23:12 CET 2026
# Sun Apr 19 16:28:14 CEST 2026
[Unit]
Description=Podman container-kontor-spring.service
Description=Podman kontor-spring.service
Documentation=man:podman-generate-systemd(1)
Wants=network-online.target
After=network-online.target
@@ -25,7 +25,8 @@ ExecStart=/usr/bin/podman run \
--replace \
--name kontor-spring \
--label io.containers.autoupdate=local \
--label PODMAN_SYSTEMD_UNIT=container-kontor-spring.service localhost/kontor-spring:0.3.0
--label PODMAN_SYSTEMD_UNIT=kontor-spring.service \
localhost/kontor-spring:0.3.0
ExecStop=/usr/bin/podman stop \
--ignore -t 10 \
--cidfile=%t/%n.ctr-id
+41
View File
@@ -0,0 +1,41 @@
# kontor-vue.service
# autogenerated by Podman 4.9.3
# Sun Apr 19 16:28:14 CEST 2026
[Unit]
Description=Podman kontor-vue.service
Documentation=man:podman-generate-systemd(1)
Wants=network-online.target
After=network-online.target
RequiresMountsFor=%t/containers
BindsTo=pod-kontor.service
After=pod-kontor.service
[Service]
Environment=PODMAN_SYSTEMD_UNIT=%n
Restart=on-failure
TimeoutStopSec=70
ExecStart=/usr/bin/podman run \
--cidfile=%t/%n.ctr-id \
--cgroups=no-conmon \
--rm \
--pod-id-file %t/pod-kontor.pod-id \
--sdnotify=conmon \
-d \
--replace \
--name kontor-vue \
--label io.containers.autoupdate=local \
--label PODMAN_SYSTEMD_UNIT=kontor-vue.service \
localhost/kontor-vue:0.3.0
ExecStop=/usr/bin/podman stop \
--ignore -t 10 \
--cidfile=%t/%n.ctr-id
ExecStopPost=/usr/bin/podman rm \
-f \
--ignore -t 10 \
--cidfile=%t/%n.ctr-id
Type=notify
NotifyAccess=all
[Install]
WantedBy=default.target
@@ -1,6 +1,6 @@
# pod-kontor.service
# autogenerated by Podman 4.9.3
# Fri Feb 6 17:23:12 CET 2026
# Sun Apr 19 16:28:14 CEST 2026
[Unit]
Description=Podman pod-kontor.service
@@ -8,8 +8,8 @@ Documentation=man:podman-generate-systemd(1)
Wants=network-online.target
After=network-online.target
RequiresMountsFor=/run/user/1000/containers
Wants=container-activemq.service container-adminer.service container-kontor-api.service container-kontor-echo.service container-kontor-fiber.service container-kontor-javalin.service container-kontor-quarkus.service container-kontor-spring.service container-postgres.service
Before=container-activemq.service container-adminer.service container-kontor-api.service container-kontor-echo.service container-kontor-fiber.service container-kontor-javalin.service container-kontor-quarkus.service container-kontor-spring.service container-postgres.service
Wants=kontor-activemq.service kontor-adminer.service kontor-angular.service kontor-api.service kontor-echo.service kontor-fiber.service kontor-javalin.service kontor-quarkus.service kontor-spring.service kontor-vue.service kontor-postgres.service
Before=kontor-activemq.service kontor-adminer.service kontor-angular.service kontor-api.service kontor-echo.service kontor-fiber.service kontor-javalin.service kontor-quarkus.service kontor-spring.service kontor-vue.service kontor-postgres.service
[Service]
Environment=PODMAN_SYSTEMD_UNIT=%n
@@ -28,6 +28,8 @@ ExecStartPre=/usr/bin/podman pod create \
-p 8400:8400 \
-p 8500:8500 \
-p 8600:8600 \
-p 8700:8700 \
-p 8800:8800 \
-p 8900:8080 \
-p 61616:61616 \
-p 8161:8161 \
+16
View File
@@ -0,0 +1,16 @@
[Unit]
Description=kontor-api via UV
After=network.target
[Service]
Type=simple
User=tpeetz
Group=tpeetz
Environment="DB_SERVER=localhost"
WorkingDirectory=/home/tpeetz/projects/kontor/kontor-api
ExecStart=/home/tpeetz/projects/kontor/kontor-api/.venv/bin/uvicorn src.main:kontor --log-level info --host 127.0.0.1 --port 8200
Restart=on-failure
[Install]
WantedBy=multi-user.target