after migration

This commit is contained in:
Admin 2025-11-10 15:24:10 +01:00
parent 84c33e423b
commit b05d8a8ab3
43 changed files with 474 additions and 66 deletions

13
.gitignore vendored
View file

@ -111,3 +111,16 @@ dmypy.json
*.sqlite3
.github
.venv
# Virtual environment
venv/
.autoenv.zsh
.autoenv_leave.zsh
.autoenv.*.zsh
# Python cache
__pycache__/
*.pyc
*.pyo
*.pyd
.Python

View file

@ -22,10 +22,11 @@
- **📚 BookStack Wiki**: Documentation and knowledge base (port 6875)
- **🤖 Ollama + Open WebUI**: Local LLM chatbot interface (port 8080)
- **📨 Matrix Synapse**: Print order collection and payment tracking (port 8008)
- **🐳 Portainer**: Container management dashboard (port 9000)
- **<EFBFBD> JupyterHub**: Multi-user Jupyter notebook server for drone programming (port 8001)
- **<EFBFBD> Element Web**: Browser-based Matrix chat client (port 8082)
- **<EFBFBD>🐳 Portainer**: Container management dashboard (port 9000)
- **📓 JupyterHub**: Multi-user Jupyter notebook server for drone programming (port 8001)
- **🦊 Forgejo**: Self-hosted Git service for code collaboration (port 3003)
- **<EFBFBD>🔄 Watchtower**: Automatic container updates every 24 hours
- **🔄 Watchtower**: Automatic container updates every 24 hours
---
@ -126,9 +127,13 @@ graph TB
### Prerequisites
**On the host system:**
- **Docker** & **Docker Compose** installed
- **curl** - for testing and healthchecks (install: `brew install curl` on macOS)
- **Poppler** (for pdf2image - included in Docker)
- **Port availability**: 80, 3003, 6875, 8001, 8008, 8080, 9000, 11434
- **Port availability**: 80, 3003, 6875, 8001, 8008, 8080, 8082, 9000, 11434
**Note:** Python dependencies are containerized and don't need to be installed on the host.
### Installation
@ -158,8 +163,10 @@ graph TB
- Open WebUI: <http://localhost:8080>
- Portainer: <http://localhost:9000>
- Matrix: <http://localhost:8008>
- Element Web: <http://localhost:8082>
- JupyterHub: <http://localhost:8001>
- Forgejo: <http://localhost:3003>
- Forgejo: <http://localhost:3003>
### First-Time Setup

40
create_room.py Normal file
View file

@ -0,0 +1,40 @@
#!/usr/bin/env python3
"""
Create a Matrix room for print orders
"""
import json
import requests
access_token = "syt_ZWluc3p3b3ZpZXI_vebsCCpDYUkfsqLgnvjz_1WOI0g"
user_id = "@einszwovier:124.local"
# Create room
data = {
"name": "Print Orders",
"topic": "PDF print order submissions from the web app",
"preset": "private_chat",
"visibility": "private"
}
headers = {
"Authorization": f"Bearer {access_token}",
"Content-Type": "application/json"
}
response = requests.post(
"http://localhost:8008/_matrix/client/r0/createRoom",
json=data,
headers=headers
)
print(f"Room creation response: {response.status_code}")
print(json.dumps(response.json(), indent=2))
if response.status_code == 200:
room_id = response.json()['room_id']
print(f"\n✅ Room created successfully!")
print(f"Room ID: {room_id}")
print(f"\n📋 Add this to your .env file:")
print(f"MATRIX_ROOM={room_id}")
else:
print(f"\n❌ Room creation failed")

View file

@ -65,12 +65,7 @@ services:
mem_limit: 16g
cpus: 6.0
mem_reservation: 4g
healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost:11434/ || exit 1"]
interval: 60s
timeout: 10s
retries: 3
start_period: 60s
# Healthcheck removed - ollama image doesn't include curl
labels:
- "com.centurylinklabs.watchtower.enable=true"
- "description=Local LLM inference engine"
@ -179,11 +174,7 @@ services:
- WATCHTOWER_INCLUDE_RESTARTING=true
- WATCHTOWER_LABEL_ENABLE=true
command: --cleanup --interval 86400 --label-enable
healthcheck:
test: ["CMD-SHELL", "pgrep watchtower || exit 1"]
interval: 60s
timeout: 10s
retries: 3
# Healthcheck removed - watchtower runs as background process, pgrep check unreliable
labels:
- "description=Watchtower Auto-Update Service"
- "maintainer=studio einszwovier"
@ -198,16 +189,7 @@ services:
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- portainer_data:/data
healthcheck:
test:
[
"CMD-SHELL",
"curl -f http://localhost:9000/api/system/status || exit 1",
]
interval: 30s
timeout: 10s
retries: 3
start_period: 30s
# Healthcheck removed - portainer image doesn't include curl
labels:
- "description=Portainer Container Management UI"
- "maintainer=studio einszwovier"
@ -232,16 +214,7 @@ services:
cpus: 1.0
depends_on:
- web
healthcheck:
test:
[
"CMD-SHELL",
"curl -f http://localhost:8001/hub/health || curl -f http://localhost:8001/hub/ || exit 1",
]
interval: 30s
timeout: 10s
retries: 3
start_period: 60s
# Healthcheck removed - jupyterhub custom image doesn't include curl
labels:
- "com.centurylinklabs.watchtower.enable=true"
- "description=JupyterHub for interactive notebooks"
@ -286,12 +259,7 @@ services:
mem_limit: 512m
cpus: 0.5
mem_reservation: 128m
healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost:80 || exit 1"]
interval: 30s
timeout: 10s
retries: 3
start_period: 20s
# Healthcheck removed - element-web image doesn't include curl
depends_on:
synapse:
condition: service_started

View file

@ -4,12 +4,12 @@ Element Web is a browser-based Matrix client that connects to the studio einszwo
## Configuration
The `config.json` file configures Element Web to connect to the local Synapse homeserver at `http://einszwovier.local:8008`.
The `config.json` file configures Element Web to connect to the local Synapse homeserver at `http://124.local:8008`.
### Key Settings
- **Homeserver**: Points to local Synapse instance
- **Server name**: `einszwovier.local`
- **Server name**: `124.local`
- **Brand**: "studio einszwovier Chat"
- **Default language**: German (DE)
- **Default theme**: Light mode
@ -17,13 +17,13 @@ The `config.json` file configures Element Web to connect to the local Synapse ho
## Access
- **URL**: `http://einszwovier.local:8082`
- **URL**: `http://124.local:8082`
- **No app required**: Works in any modern web browser
- **Mobile friendly**: Responsive design works on phones and tablets
## Usage
1. Navigate to `http://einszwovier.local:8082`
1. Navigate to `http://124.local:8082`
2. Click "Sign In" or "Create Account"
3. Use existing Matrix credentials or create a new account
4. Join rooms (e.g., the print order room)
@ -49,7 +49,7 @@ Element Web is managed by Watchtower and updates automatically with the rest of
- Ensure Synapse is running: `docker-compose ps synapse`
- Check Synapse logs: `docker-compose logs synapse`
- Verify hostname resolution: `ping einszwovier.local`
- Verify hostname resolution: `ping 124.local`
**Login fails:**

View file

@ -1,8 +1,8 @@
{
"default_server_config": {
"m.homeserver": {
"base_url": "http://einszwovier.local:8008",
"server_name": "einszwovier.local"
"base_url": "http://124.local:8008",
"server_name": "124.local"
}
},
"brand": "studio einszwovier Chat",
@ -16,11 +16,11 @@
},
"room_directory": {
"servers": [
"einszwovier.local"
"124.local"
]
},
"enable_presence_by_hs_url": {
"http://einszwovier.local:8008": true
"http://124.local:8008": true
},
"terms_and_conditions_links": [],
"privacy_policy_url": null

View file

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

View file

@ -1,5 +1,8 @@
APP_NAME = Forgejo: Beyond coding. We forge.
APP_NAME = einszwovier forge
RUN_MODE = prod
APP_SLOGAN =
RUN_USER = git
WORK_PATH = /data/gitea
[repository]
ROOT = /data/git/repositories
@ -13,14 +16,16 @@ TEMP_PATH = /data/gitea/uploads
[server]
APP_DATA_PATH = /data/gitea
DOMAIN = localhost
SSH_DOMAIN = localhost
DOMAIN = 124.local
SSH_DOMAIN = 124.local
HTTP_PORT = 3000
ROOT_URL =
ROOT_URL = http://124.local:3003/
DISABLE_SSH = false
SSH_PORT = 22
SSH_LISTEN_PORT = 22
LFS_START_SERVER = false
LFS_START_SERVER = true
LFS_JWT_SECRET = hZvHy32wQr50I0x51W9WqQvYqNEfm45PqAoq7KJcw2k
OFFLINE_MODE = true
[database]
PATH = /data/gitea/gitea.db
@ -30,12 +35,15 @@ NAME = gitea
USER = root
PASSWD =
LOG_SQL = false
SCHEMA =
SSL_MODE = disable
[indexer]
ISSUE_INDEXER_PATH = /data/gitea/indexers/issues.bleve
[session]
PROVIDER_CONFIG = /data/gitea/sessions
PROVIDER = file
[picture]
AVATAR_UPLOAD_PATH = /data/gitea/avatars
@ -50,14 +58,43 @@ LEVEL = info
ROOT_PATH = /data/gitea/log
[security]
INSTALL_LOCK = false
INSTALL_LOCK = true
SECRET_KEY =
REVERSE_PROXY_LIMIT = 1
REVERSE_PROXY_TRUSTED_PROXIES = *
INTERNAL_TOKEN = eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYmYiOjE3NjI3NzI1NDN9.XRM_LxqMxCNJC4odRqZcNZ-C8LNxCV1pDFGi5ks789s
PASSWORD_HASH_ALGO = pbkdf2_hi
[service]
DISABLE_REGISTRATION = false
REQUIRE_SIGNIN_VIEW = false
REGISTER_EMAIL_CONFIRM = false
ENABLE_NOTIFY_MAIL = false
ALLOW_ONLY_EXTERNAL_REGISTRATION = false
ENABLE_CAPTCHA = true
DEFAULT_KEEP_EMAIL_PRIVATE = false
DEFAULT_ALLOW_CREATE_ORGANIZATION = true
DEFAULT_ENABLE_TIMETRACKING = true
NO_REPLY_ADDRESS = noreply.localhost
[lfs]
PATH = /data/git/lfs
[mailer]
ENABLED = false
[openid]
ENABLE_OPENID_SIGNIN = true
ENABLE_OPENID_SIGNUP = true
[cron.update_checker]
ENABLED = true
[repository.pull-request]
DEFAULT_MERGE_STYLE = merge
[repository.signing]
DEFAULT_TRUST_MODEL = committer
[oauth2]
JWT_SECRET = DCWJLYmNbdJngkBLfZBNHX9IHBFE1A-Op3EvURtCsU0

View file

@ -0,0 +1,22 @@
[diff]
algorithm = histogram
[core]
logallrefupdates = true
quotePath = false
commitGraph = true
[gc]
reflogexpire = 90
writeCommitGraph = true
[user]
name = Gitea
email = gitea@fake.local
[receive]
advertisePushOptions = true
procReceiveRefs = refs/for
[fetch]
writeCommitGraph = true
[safe]
directory = *
[uploadpack]
allowfilter = true
allowAnySHA1InWant = true

View file

@ -0,0 +1 @@
{"storage":"boltdb","index_type":"scorch"}

View file

@ -0,0 +1 @@
{"version":4}

Binary file not shown.

View file

@ -0,0 +1 @@
MANIFEST-000004

View file

View file

@ -0,0 +1,16 @@
=============== Nov 10, 2025 (CET) ===============
12:02:27.139922 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed
12:02:27.141510 db@open opening
12:02:27.141943 version@stat F·[] S·0B[] Sc·[]
12:02:27.142289 db@janitor F·2 G·0
12:02:27.142333 db@open done T·789.083µs
=============== Nov 10, 2025 (CET) ===============
14:41:11.536178 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed
14:41:11.537691 version@stat F·[] S·0B[] Sc·[]
14:41:11.537733 db@open opening
14:41:11.537879 journal@recovery F·1
14:41:11.537972 journal@recovery recovering @1
14:41:11.538886 memdb@flush created L0@2 N·26 S·542B "act..igh,v26":"web..low,v17"
14:41:11.539093 version@stat F·[1] S·542B[542B] Sc·[0.25]
14:41:11.543912 db@janitor F·3 G·0
14:41:11.543982 db@open done T·6.225416ms

Binary file not shown.

Binary file not shown.

View file

@ -11,7 +11,7 @@ async def send_order(pdf_path: str, analysis: dict, room_id: str, name: str, com
"""
matrix_user = os.environ.get("MATRIX_USER")
matrix_pass = os.environ.get("MATRIX_PASS")
homeserver = os.environ.get("MATRIX_HOMESERVER", "http://einszwovier.local:8008")
homeserver = os.environ.get("MATRIX_HOMESERVER", "http://124.local:8008")
if not matrix_user or not matrix_pass:
raise RuntimeError("Missing MATRIX_USER or MATRIX_PASS in environment")

View file

@ -14,7 +14,7 @@ from dotenv import load_dotenv
load_dotenv()
# Get server hostname from environment
SERVER_HOSTNAME = os.environ.get("SERVER_HOSTNAME", "einszwovier.local")
SERVER_HOSTNAME = os.environ.get("SERVER_HOSTNAME", "124.local")
BOOKSTACK_PORT = os.environ.get("BOOKSTACK_PORT", "6875")
OPENWEBUI_PORT = os.environ.get("OPENWEBUI_PORT", "8080")
PORTAINER_PORT = os.environ.get("PORTAINER_PORT", "9000")
@ -162,7 +162,7 @@ def send_order_endpoint(
analysis = analyze_pdf(path)
# Get Matrix room ID from environment
matrix_room = os.environ.get("MATRIX_ROOM", "!eFWbWEnYsgeIKqyfjw:einszwovier.local")
matrix_room = os.environ.get("MATRIX_ROOM", "!PuLwSlWKNgNKvhhCIr:124.local")
try:
send_order_sync(

View file

@ -0,0 +1,30 @@
# Configuration file for Synapse.
server_name: "einszwovier.local"
pid_file: /data/homeserver.pid
listeners:
- port: 8008
tls: false
type: http
x_forwarded: true
resources:
- names: [client, federation]
compress: false
database:
name: sqlite3
args:
database: /data/homeserver.db
# Connection and performance settings
max_upload_size: 50M
url_preview_enabled: false
log_config: "/data/localhost.log.config"
media_store_path: /data/media_store
registration_shared_secret: "D2mw3LqNKe98ga-pYO1l5KbXf^jgx&s5yjq&ipAGjln:AzLag8"
report_stats: false
macaroon_secret_key: "T26aaiHWLHbm+P6fi_8:VXTIn0W_kHH__CQAdhPyaLhBe~OG*_"
form_secret: "k,C38Dw^6b8Y+9-cSQpLb@GPoS*1POr8GDWXsLMKLHEU2+&q-@"
signing_key_path: "/data/localhost.signing.key"
trusted_key_servers:
- server_name: "matrix.org"

View file

@ -0,0 +1,39 @@
version: 1
formatters:
precise:
format: '%(asctime)s - %(name)s - %(lineno)d - %(levelname)s - %(request)s - %(message)s'
handlers:
console:
class: logging.StreamHandler
formatter: precise
loggers:
# This is just here so we can leave `loggers` in the config regardless of whether
# we configure other loggers below (avoid empty yaml dict error).
_placeholder:
level: "INFO"
synapse.storage.SQL:
# beware: increasing this to DEBUG will make synapse log sensitive
# information such as access tokens.
level: INFO
root:
level: INFO
handlers: [console]
disable_existing_loggers: false

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

View file

@ -1,6 +1,6 @@
# Configuration file for Synapse.
server_name: "einszwovier.local"
server_name: "124.local"
pid_file: /data/homeserver.pid
listeners:
- port: 8008
@ -28,3 +28,10 @@ form_secret: "k,C38Dw^6b8Y+9-cSQpLb@GPoS*1POr8GDWXsLMKLHEU2+&q-@"
signing_key_path: "/data/localhost.signing.key"
trusted_key_servers:
- server_name: "matrix.org"
# Allow guest access
allow_guest_access: true
# Enable registration
enable_registration: true
enable_registration_without_verification: true

60
register_user.py Normal file
View file

@ -0,0 +1,60 @@
#!/usr/bin/env python3
"""
Register a user on Matrix server using shared secret
"""
import hmac
import hashlib
import json
import requests
# Get nonce
nonce_response = requests.get("http://localhost:8008/_synapse/admin/v1/register")
nonce = nonce_response.json()["nonce"]
print(f"Got nonce: {nonce[:20]}...")
# Registration details
username = "einszwovier"
password = "einszwo4"
admin = False
shared_secret = "D2mw3LqNKe98ga-pYO1l5KbXf^jgx&s5yjq&ipAGjln:AzLag8"
# Generate MAC
mac = hmac.new(
key=shared_secret.encode('utf8'),
digestmod=hashlib.sha1,
)
mac.update(nonce.encode('utf8'))
mac.update(b"\x00")
mac.update(username.encode('utf8'))
mac.update(b"\x00")
mac.update(password.encode('utf8'))
mac.update(b"\x00")
mac.update(b"notadmin" if not admin else b"admin")
mac_str = mac.hexdigest()
# Register user
data = {
"nonce": nonce,
"username": username,
"password": password,
"admin": admin,
"mac": mac_str
}
response = requests.post(
"http://localhost:8008/_synapse/admin/v1/register",
json=data
)
print(f"\nRegistration response: {response.status_code}")
print(json.dumps(response.json(), indent=2))
if response.status_code == 200:
print(f"\n✅ User registered successfully!")
print(f"User ID: {response.json()['user_id']}")
print(f"Access Token: {response.json()['access_token']}")
else:
print(f"\n❌ Registration failed")

62
setup_matrix.py Normal file
View file

@ -0,0 +1,62 @@
#!/usr/bin/env python3
"""
Setup script for Matrix: register user and create room
"""
import asyncio
import os
from nio import AsyncClient, RegisterResponse, RoomCreateResponse
async def setup_matrix():
homeserver = "http://124.local:8008"
username = "einszwovier"
password = "einszwo4"
client = AsyncClient(homeserver)
print("🔧 Setting up Matrix server...")
print(f" Homeserver: {homeserver}")
print(f" Username: {username}")
# Register the user
print("\n📝 Registering user...")
response = await client.register(
username=username,
password=password
)
if isinstance(response, RegisterResponse):
print(f" ✅ User registered: {response.user_id}")
user_id = response.user_id
else:
print(f" User might already exist, trying to login...")
# Try to login instead
response = await client.login(password)
if response.user_id:
print(f" ✅ Logged in as: {response.user_id}")
user_id = response.user_id
else:
print(f" ❌ Failed: {response}")
await client.close()
return
# Create a room
print("\n🏠 Creating print orders room...")
response = await client.room_create(
name="Print Orders",
topic="PDF print order submissions from the web app",
is_public=False
)
if isinstance(response, RoomCreateResponse):
print(f" ✅ Room created!")
print(f" Room ID: {response.room_id}")
print(f"\n✅ Setup complete!")
print(f"\n📋 Add this to your .env file:")
print(f"MATRIX_ROOM={response.room_id}")
else:
print(f" ❌ Failed to create room: {response}")
await client.close()
if __name__ == "__main__":
asyncio.run(setup_matrix())

View file

@ -538,6 +538,98 @@ a.link-card:hover,
font-size: 0.9rem;
}
/* ========================================
ARCHITECTURE SPOILER
======================================== */
.architecture-spoiler {
margin: 0 auto 1.5rem;
max-width: 1200px;
width: calc(100% - 2rem);
background: #ffffff;
border: 1px solid #dee2e6;
border-radius: 8px;
overflow: hidden;
}
.architecture-summary {
padding: 0.8rem 1.2rem;
background: #f8f9fa;
color: #2C3E50;
cursor: pointer;
font-size: 0.95rem;
font-weight: 600;
display: flex;
align-items: center;
gap: 0.6rem;
transition: background-color 0.2s ease;
user-select: none;
border-bottom: 1px solid #dee2e6;
}
.architecture-summary:hover {
background: #e9ecef;
}
.architecture-summary i {
font-size: 1.1rem;
color: #6c757d;
}
.architecture-spoiler[open] .architecture-summary {
background: #e9ecef;
}
.architecture-content {
padding: 1.5rem;
background: #ffffff;
text-align: center;
max-height: 70vh;
overflow-y: auto;
}
.architecture-image {
width: 100%;
max-width: 100%;
height: auto;
max-height: 60vh;
object-fit: contain;
border-radius: 4px;
transition: opacity 0.2s ease;
}
.architecture-image:hover {
opacity: 0.95;
}
.architecture-caption {
margin-top: 1rem;
color: #6c757d;
font-size: 0.9rem;
font-style: italic;
}
/* Responsive adjustments for mobile */
@media (max-width: 768px) {
.architecture-spoiler {
width: calc(100% - 1rem);
}
.architecture-content {
padding: 1rem;
max-height: 50vh;
}
.architecture-image {
max-height: 45vh;
}
.architecture-summary {
font-size: 0.85rem;
padding: 0.7rem 1rem;
}
}
/* ========================================
BUTTONS

Binary file not shown.

After

Width:  |  Height:  |  Size: 236 KiB

View file

@ -36,8 +36,6 @@
</div>
</div>
<!-- Information and Tools Cards -->
<div class="link-cards-grid">
<a class="link-card" href="/about">
@ -76,7 +74,7 @@
</div>
</a>
<a class="link-card" href="http://{{ server_hostname }}:8001" target="_blank">
<a class="link-card" href="http://{{ server_hostname }}:8001/hub/" target="_blank">
<i class="fas fa-code card-icon"></i>
<div class="card-content">
<div class="title">JupyterHub</div>
@ -117,6 +115,20 @@
<!-- Footer with Admin Panel and Source Link -->
<footer class="footer">
<!-- Architecture Diagram Spoiler -->
<details class="architecture-spoiler">
<summary class="architecture-summary">
<i class="fas fa-sitemap"></i>
<span>Systemarchitektur anzeigen</span>
</summary>
<div class="architecture-content">
<img src="/static/images/architecture.png" alt="Studio einszwovier Architecture Diagram"
class="architecture-image">
<p class="architecture-caption">Übersicht über alle Dienste und deren Zusammenspiel im Studio einszwovier
System</p>
</div>
</details>
<div class="footer-container">
<a href="http://{{ server_hostname }}:{{ portainer_port }}" target="_blank" class="admin-link">Admin Panel
(Portainer)</a>

View file

@ -11,7 +11,7 @@ async def test_connection(homeserver_url: str):
"""Test connection to Matrix homeserver"""
print(f"\n🔍 Testing connection to: {homeserver_url}")
matrix_user = os.environ.get("MATRIX_USER", "@einszwovier:einszwovier.local")
matrix_user = os.environ.get("MATRIX_USER", "@einszwovier:124.local")
matrix_pass = os.environ.get("MATRIX_PASS", "einszwo4")
client = AsyncClient(homeserver_url, matrix_user)
@ -44,7 +44,7 @@ async def main():
test_urls = [
"http://localhost:8008",
"http://127.0.0.1:8008",
"http://einszwovier.local:8008",
"http://124.local:8008",
]
for url in test_urls:
@ -52,7 +52,7 @@ async def main():
print("\n" + "=" * 60)
print("Recommendation:")
print("If localhost/127.0.0.1 works but einszwovier.local fails,")
print("If localhost/127.0.0.1 works but 124.local fails,")
print("configure Element to use: http://localhost:8008")
print("=" * 60)