Add Docker support for FoodsharingSiegen project
Introduced Docker setup, including `docker-compose.yml`, `dockerfile.server`, and related Python helper scripts (`image-create.py`, `image-push.py`, `publish-project.py`, `publish-aio.py`) for building, managing, and deploying Docker images. Updated `.gitignore` to exclude published files, and renamed a field label in the Blazor component for better clarity.
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -3,3 +3,5 @@ obj/
|
|||||||
/packages/
|
/packages/
|
||||||
riderModule.iml
|
riderModule.iml
|
||||||
/_ReSharper.Caches/
|
/_ReSharper.Caches/
|
||||||
|
|
||||||
|
Publish/
|
||||||
10
Docker/docker-compose.yml
Normal file
10
Docker/docker-compose.yml
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
services:
|
||||||
|
server:
|
||||||
|
container_name: fs-onboarding
|
||||||
|
hostname: fs-onboarding
|
||||||
|
image: ghcr.io/troogs/fs-onboarding/server:latest
|
||||||
|
ports:
|
||||||
|
- "8100:56000"
|
||||||
|
volumes:
|
||||||
|
- /docker/data/fs-onboarding/config:/app/config/
|
||||||
|
- /docker/data/fs-onboarding/data:/app/data/
|
||||||
11
Docker/dockerfile.server
Normal file
11
Docker/dockerfile.server
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS base
|
||||||
|
RUN apt-get update && apt-get install iputils-ping -y
|
||||||
|
|
||||||
|
LABEL org.opencontainers.image.source=https://github.com/TroogS/FoodsharingSiegen
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy the Blazor Server published files from the Publish folder relative to the build context
|
||||||
|
COPY ../Publish/Server/ .
|
||||||
|
|
||||||
|
ENTRYPOINT ["dotnet", "FoodsharingSiegen.Server.dll"]
|
||||||
63
Docker/image-create.py
Normal file
63
Docker/image-create.py
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import subprocess
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
def image_exists_on_server(full_image_tag):
|
||||||
|
"""
|
||||||
|
Check if a Docker image exists on the remote server.
|
||||||
|
Uses 'docker manifest inspect' which returns 0 if the image is found.
|
||||||
|
Debug output is printed for inspection.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
subprocess.run(
|
||||||
|
["docker", "manifest", "inspect", full_image_tag],
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.PIPE,
|
||||||
|
text=True,
|
||||||
|
check=True
|
||||||
|
)
|
||||||
|
print(f"[DEBUG] Found image on server: {full_image_tag}")
|
||||||
|
return True
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
print(f"[DEBUG] Image not found on server: {full_image_tag}. Error: {e.stderr.strip()}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def get_date_tag(base_tag, today):
|
||||||
|
"""
|
||||||
|
Given the base image tag (e.g. 'ghcr.io/troogs/fs-onboarding/server')
|
||||||
|
and today's date, find the first available tag in the format YYYYMMDD-i
|
||||||
|
that is not already present on the remote server.
|
||||||
|
"""
|
||||||
|
i = 1
|
||||||
|
while True:
|
||||||
|
date_tag = f"{today}-{i}"
|
||||||
|
full_tag = f"{base_tag}:{date_tag}"
|
||||||
|
print(f"[DEBUG] Checking tag {date_tag}: {full_tag}")
|
||||||
|
|
||||||
|
if not image_exists_on_server(full_tag):
|
||||||
|
print(f"[DEBUG] Tag {date_tag} is available for use.")
|
||||||
|
return date_tag
|
||||||
|
else:
|
||||||
|
print(f"[DEBUG] Tag {date_tag} is already in use. Incrementing index.")
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
def main():
|
||||||
|
# Get today's date in YYYYMMDD format.
|
||||||
|
today = datetime.now().strftime("%Y%m%d")
|
||||||
|
print(f"Using date tag base: {today}")
|
||||||
|
|
||||||
|
base_image_tag = "ghcr.io/troogs/fs-onboarding/server"
|
||||||
|
date_tag = get_date_tag(base_image_tag, today)
|
||||||
|
print(f"Common date tag determined: {date_tag}")
|
||||||
|
|
||||||
|
# Build command using the common date tag for the server image.
|
||||||
|
command = (
|
||||||
|
f"docker build -f dockerfile.server "
|
||||||
|
f"-t {base_image_tag}:latest "
|
||||||
|
f"-t {base_image_tag}:{date_tag} ..\\"
|
||||||
|
)
|
||||||
|
print(f"Executing: {command}")
|
||||||
|
subprocess.run(command, shell=True, check=True)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
65
Docker/image-push.py
Normal file
65
Docker/image-push.py
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import subprocess
|
||||||
|
import re
|
||||||
|
|
||||||
|
def get_latest_timestamp_tag():
|
||||||
|
"""
|
||||||
|
Searches local images for tags matching the pattern:
|
||||||
|
ghcr.io/troogs/fs-onboarding/server:YYYYMMDD-i
|
||||||
|
and returns the latest timestamp tag (YYYYMMDD-i) based on date and index.
|
||||||
|
"""
|
||||||
|
# Regular expression to match tags like: ghcr.io/troogs/fs-onboarding/server:20250328-1
|
||||||
|
pattern = re.compile(r"ghcr\.io/troogs/fs-onboarding/server:(\d{8})-(\d+)$")
|
||||||
|
|
||||||
|
# List images for the repository using the docker images command.
|
||||||
|
result = subprocess.run(
|
||||||
|
["docker", "images", "ghcr.io/troogs/fs-onboarding/server", "--format", "{{.Repository}}:{{.Tag}}"],
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
text=True,
|
||||||
|
check=True
|
||||||
|
)
|
||||||
|
|
||||||
|
tags = result.stdout.strip().splitlines()
|
||||||
|
timestamp_tags = []
|
||||||
|
for tag in tags:
|
||||||
|
m = pattern.match(tag)
|
||||||
|
if m:
|
||||||
|
date_part, index = m.groups()
|
||||||
|
timestamp_tags.append((date_part, int(index)))
|
||||||
|
|
||||||
|
if not timestamp_tags:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Choose the maximum tuple; lexicographical order works for YYYYMMDD and then by numeric index.
|
||||||
|
latest = max(timestamp_tags, key=lambda x: (x[0], x[1]))
|
||||||
|
return f"{latest[0]}-{latest[1]}"
|
||||||
|
|
||||||
|
def push_image(image_tag):
|
||||||
|
"""
|
||||||
|
Pushes the specified image tag to the container registry.
|
||||||
|
"""
|
||||||
|
print(f"Pushing image: {image_tag}")
|
||||||
|
subprocess.run(["docker", "push", image_tag], check=True)
|
||||||
|
|
||||||
|
def main():
|
||||||
|
# Retrieve the latest timestamp tag for the server image.
|
||||||
|
server_timestamp = get_latest_timestamp_tag()
|
||||||
|
|
||||||
|
if server_timestamp is None:
|
||||||
|
print("Error: Could not find timestamp tagged images for server.")
|
||||||
|
return
|
||||||
|
|
||||||
|
common_timestamp = server_timestamp
|
||||||
|
print(f"Common timestamp tag identified: {common_timestamp}")
|
||||||
|
|
||||||
|
# Prepare the list of images to push: both 'latest' and the timestamp-tagged images.
|
||||||
|
images_to_push = [
|
||||||
|
"ghcr.io/troogs/fs-onboarding/server:latest",
|
||||||
|
f"ghcr.io/troogs/fs-onboarding/server:{common_timestamp}"
|
||||||
|
]
|
||||||
|
|
||||||
|
for image in images_to_push:
|
||||||
|
push_image(image)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
15
Docker/publish-aio.py
Normal file
15
Docker/publish-aio.py
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
|
||||||
|
# List of scripts to run in order
|
||||||
|
scripts = ['publish-project.py', 'image-create.py', 'image-push.py']
|
||||||
|
|
||||||
|
for script in scripts:
|
||||||
|
print(f"Executing {script}...")
|
||||||
|
try:
|
||||||
|
# Run the script using the current Python interpreter
|
||||||
|
subprocess.run([sys.executable, script], check=True)
|
||||||
|
print(f"{script} executed successfully.\n")
|
||||||
|
except subprocess.CalledProcessError as error:
|
||||||
|
print(f"Error: {script} failed with exit code {error.returncode}.")
|
||||||
|
break
|
||||||
38
Docker/publish-project.py
Normal file
38
Docker/publish-project.py
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
|
||||||
|
def main():
|
||||||
|
# Determine the absolute path of this script's directory
|
||||||
|
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
|
||||||
|
# Compute the path to the FoodsharingSiegen.Server project directory
|
||||||
|
# This assumes the following structure:
|
||||||
|
# files/
|
||||||
|
# Docker/ <-- this script resides here
|
||||||
|
# FoodsharingSiegen.Server/
|
||||||
|
project_dir = os.path.normpath(os.path.join(script_dir, '..', 'FoodsharingSiegen.Server'))
|
||||||
|
|
||||||
|
# Check if the project directory exists
|
||||||
|
if not os.path.isdir(project_dir):
|
||||||
|
print(f"Error: Project directory not found: {project_dir}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Prepare the dotnet publish command
|
||||||
|
command = ["dotnet", "publish", project_dir]
|
||||||
|
print("Running command:", " ".join(command))
|
||||||
|
|
||||||
|
# Execute the command and capture the output
|
||||||
|
try:
|
||||||
|
result = subprocess.run(command, capture_output=True, text=True, check=True)
|
||||||
|
print(result.stdout)
|
||||||
|
if result.stderr:
|
||||||
|
print("Warnings/Errors:", result.stderr, file=sys.stderr)
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
print("An error occurred while publishing the dotnet project.")
|
||||||
|
print(e.stdout)
|
||||||
|
print(e.stderr, file=sys.stderr)
|
||||||
|
sys.exit(e.returncode)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
@@ -23,7 +23,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Field>
|
<Field>
|
||||||
<FieldLabel>Memo (optional)</FieldLabel>
|
<FieldLabel>Info (optional)</FieldLabel>
|
||||||
<TextEdit @bind-Text="Prospect.Memo" Placeholder="Beliebige Info"></TextEdit>
|
<TextEdit @bind-Text="Prospect.Memo" Placeholder="Beliebige Info"></TextEdit>
|
||||||
</Field>
|
</Field>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user