Compare commits
50 Commits
159efd2c9a
...
develop
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a0d1e10c78 | ||
|
|
88892ac425 | ||
|
|
b7d091c84a | ||
|
|
93ba03f619 | ||
|
|
92d7e04154 | ||
|
|
51834e732e | ||
|
|
3498a23ee1 | ||
|
|
c5b66c4298 | ||
|
|
f3984f8b57 | ||
|
|
d0292ac192 | ||
|
|
ef15dc1dfe | ||
|
|
105600ed9b | ||
|
|
d0234fc270 | ||
|
|
0d156b9107 | ||
|
|
9fd3df83e7 | ||
|
|
e9a52787ce | ||
|
|
c48eaf6b49 | ||
|
|
2ee6580519 | ||
|
|
85a4d3cb59 | ||
|
|
10d0fd0c98 | ||
|
|
14dadbbbae | ||
|
|
cdd8b400d9 | ||
|
|
aeb50fe3be | ||
|
|
832e7e4056 | ||
|
|
8ff2a0513f | ||
|
|
c677c8533c | ||
|
|
b2a4b0ff3b | ||
|
|
c04d55c02f | ||
|
|
3314e31514 | ||
|
|
3f0f5a35c6 | ||
|
|
3c3d4eda48 | ||
|
|
27237bd8c6 | ||
|
|
70cd6a879c | ||
|
|
ae6d0e28a3 | ||
|
|
e6d6d7999c | ||
|
|
22a90824ff | ||
|
|
f62c0f21d8 | ||
|
|
38b486ab2c | ||
| 8336d9c19b | |||
|
|
c9a470e044 | ||
|
|
4b1ae663ec | ||
|
|
e09e0018b2 | ||
|
|
8bd5ed3e05 | ||
|
|
73e364dd11 | ||
|
|
31555d8386 | ||
|
|
bf48b02fb5 | ||
|
|
fc2393b5c3 | ||
|
|
b4b668dd41 | ||
|
|
a6da3566d0 | ||
|
|
56cb4555f8 |
@@ -6,28 +6,102 @@ on:
|
|||||||
- main
|
- main
|
||||||
- develop
|
- develop
|
||||||
|
|
||||||
|
env: # global: unkritische, strukturgebende Variablen
|
||||||
|
TARGET_HOST: host.containers.internal
|
||||||
|
TARGET_USER: traefik
|
||||||
|
APP_DIR: /home/traefik/valtrix-website
|
||||||
|
CONTAINER_NAME: valtrix-website
|
||||||
|
QUADLET_FILE: ./deploy/valtrix-website.container
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build_and_deploy:
|
build_and_deploy:
|
||||||
runs-on: self-hosted
|
runs-on: ubuntu-latest
|
||||||
|
env: # Job-spezifisch: Secrets und sensible Werte
|
||||||
|
SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
|
||||||
|
SSH_KNOWN_HOSTS: ${{ secrets.SSH_KNOWN_HOSTS }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
- name: Pre-clean Git global config (avoid https→ssh rewrite)
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
echo "Cleaning up global git config"
|
||||||
|
git config --global --unset-all core.sshCommand || true
|
||||||
|
for key in $(git config --global --get-regexp '^url\\..*\\.insteadof$' 2>/dev/null | awk '{print $1}'); do
|
||||||
|
if echo "$key" | grep -qi 'gitea\\.smb-corp\\.de'; then
|
||||||
|
git config --global --unset-all "$key" || true
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
- name: Setup SSH for git/scp
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
install -m 700 -d ~/.ssh
|
||||||
|
printf "%s\n" "$SSH_PRIVATE_KEY" > ~/.ssh/id_ed25519
|
||||||
|
chmod 600 ~/.ssh/id_ed25519
|
||||||
|
printf "%s\n" "$SSH_KNOWN_HOSTS" > ~/.ssh/known_hosts || true
|
||||||
|
chmod 644 ~/.ssh/known_hosts
|
||||||
|
# Ensure host keys exist
|
||||||
|
(ssh-keygen -F "$TARGET_HOST" >/dev/null || ssh-keyscan -H "$TARGET_HOST" >> ~/.ssh/known_hosts) || true
|
||||||
|
(ssh-keygen -F gitea.smb-corp.de >/dev/null || ssh-keyscan -H gitea.smb-corp.de >> ~/.ssh/known_hosts) || true
|
||||||
|
|
||||||
- name: Checkout Repository
|
- name: Checkout Repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Copy code to target host
|
- name: Copy repository to target host (atomic replace)
|
||||||
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
rsync -avz --delete ./ user@zielserver:/home/user/app/
|
set -euo pipefail
|
||||||
|
TMP_DIR="$APP_DIR.tmp.$(date +%s)"
|
||||||
|
ssh -i ~/.ssh/id_ed25519 $TARGET_USER@$TARGET_HOST "mkdir -p '$TMP_DIR'"
|
||||||
|
scp -r -i ~/.ssh/id_ed25519 ./* $TARGET_USER@$TARGET_HOST:$TMP_DIR/
|
||||||
|
ssh -i ~/.ssh/id_ed25519 $TARGET_USER@$TARGET_HOST "
|
||||||
|
set -euo pipefail;
|
||||||
|
if [ -d '$APP_DIR' ]; then rm -rf '$APP_DIR'; fi;
|
||||||
|
mv '$TMP_DIR' '$APP_DIR'
|
||||||
|
"
|
||||||
|
|
||||||
- name: Build container on target host
|
- name: Build container on target host
|
||||||
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
ssh user@zielserver '
|
ssh -i ~/.ssh/id_ed25519 $TARGET_USER@$TARGET_HOST "
|
||||||
cd /home/user/app
|
set -euo pipefail
|
||||||
podman build -t myapp:latest .
|
export APP_DIR='$APP_DIR' CONTAINER_NAME='$CONTAINER_NAME'
|
||||||
'
|
cd \"\$APP_DIR\"
|
||||||
|
echo 'Building container: '\$CONTAINER_NAME 'in' \$APP_DIR
|
||||||
|
podman build -t \$CONTAINER_NAME:latest .
|
||||||
|
"
|
||||||
|
|
||||||
- name: Replace Quadlet file
|
- name: Backup existing Quadlet file
|
||||||
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
scp ./systemd/myapp.container user@zielserver:~/.config/containers/systemd/
|
ssh -i ~/.ssh/id_ed25519 $TARGET_USER@$TARGET_HOST "
|
||||||
ssh user@zielserver '
|
set -euo pipefail
|
||||||
|
export CONTAINER_NAME='$CONTAINER_NAME'
|
||||||
|
QFILE=~/.config/containers/systemd/\$CONTAINER_NAME.container
|
||||||
|
test -f \"\$QFILE\" && cp \"\$QFILE\" \"\$QFILE.bak\" || true
|
||||||
|
"
|
||||||
|
|
||||||
|
- name: Replace Quadlet file and restart service
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
scp -i ~/.ssh/id_ed25519 "$QUADLET_FILE" $TARGET_USER@$TARGET_HOST:~/.config/containers/systemd/
|
||||||
|
ssh -i ~/.ssh/id_ed25519 $TARGET_USER@$TARGET_HOST "
|
||||||
|
set -euo pipefail
|
||||||
|
export CONTAINER_NAME='$CONTAINER_NAME'
|
||||||
systemctl --user daemon-reload
|
systemctl --user daemon-reload
|
||||||
systemctl --user restart myapp.service
|
systemctl --user restart \$CONTAINER_NAME.service
|
||||||
'
|
echo 'Service restarted: '\$CONTAINER_NAME
|
||||||
|
"
|
||||||
|
|
||||||
|
- name: Verify deployment
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
ssh -i ~/.ssh/id_ed25519 $TARGET_USER@$TARGET_HOST "
|
||||||
|
set -euo pipefail
|
||||||
|
export CONTAINER_NAME='$CONTAINER_NAME'
|
||||||
|
echo 'Running containers:'
|
||||||
|
podman ps --filter \"name=\$CONTAINER_NAME\" --format \"table {{.Names}}\t{{.Image}}\t{{.Status}}\"
|
||||||
|
echo '--- Last 20 log lines ---'
|
||||||
|
podman logs \$CONTAINER_NAME --tail 20 || echo 'No logs available'
|
||||||
|
"
|
||||||
41
.gitea/workflows/image-scan.yaml
Normal file
41
.gitea/workflows/image-scan.yaml
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
name: Scan Image for CVEs
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
- develop
|
||||||
|
|
||||||
|
env: # global: unkritische, strukturgebende Variablen
|
||||||
|
TARGET_HOST: host.containers.internal
|
||||||
|
TARGET_USER: traefik
|
||||||
|
CONTAINER_NAME: localhost/valtrix-website
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
scan_image:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
env: # Job-spezifisch: Secrets und sensible Werte
|
||||||
|
SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
|
||||||
|
SSH_KNOWN_HOSTS: ${{ secrets.SSH_KNOWN_HOSTS }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Setup SSH for git/scp
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
install -m 700 -d ~/.ssh
|
||||||
|
printf "%s\n" "$SSH_PRIVATE_KEY" > ~/.ssh/id_ed25519
|
||||||
|
chmod 600 ~/.ssh/id_ed25519
|
||||||
|
printf "%s\n" "$SSH_KNOWN_HOSTS" > ~/.ssh/known_hosts || true
|
||||||
|
chmod 644 ~/.ssh/known_hosts
|
||||||
|
# Ensure host keys exist
|
||||||
|
(ssh-keygen -F "$TARGET_HOST" >/dev/null || ssh-keyscan -H "$TARGET_HOST" >> ~/.ssh/known_hosts) || true
|
||||||
|
(ssh-keygen -F gitea.smb-corp.de >/dev/null || ssh-keyscan -H gitea.smb-corp.de >> ~/.ssh/known_hosts) || true
|
||||||
|
|
||||||
|
- name: Scan container image with Trivy
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
ssh -i ~/.ssh/id_ed25519 $TARGET_USER@$TARGET_HOST "
|
||||||
|
set -euo pipefail
|
||||||
|
export CONTAINER_NAME='$CONTAINER_NAME'
|
||||||
|
trivy image localhost/valtrix-website:latest
|
||||||
|
"
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,5 +1,4 @@
|
|||||||
node_modules
|
node_modules
|
||||||
package-lock.json
|
|
||||||
dist
|
dist
|
||||||
.env
|
.env
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ ENV NODE_ENV=production
|
|||||||
ENV PORT=3000
|
ENV PORT=3000
|
||||||
ENV WEB_ROOT=/app/dist
|
ENV WEB_ROOT=/app/dist
|
||||||
ENV TZ=Europe/Berlin
|
ENV TZ=Europe/Berlin
|
||||||
|
ENV ASTRO_TELEMETRY_DISABLED=1
|
||||||
COPY --from=builder /app/dist /app/dist
|
COPY --from=builder /app/dist /app/dist
|
||||||
COPY server.mjs /app/server.mjs
|
COPY server.mjs /app/server.mjs
|
||||||
# Drop root: use the pre-created node user
|
# Drop root: use the pre-created node user
|
||||||
|
|||||||
36
Containerfile.notworking
Normal file
36
Containerfile.notworking
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
# syntax=docker/dockerfile:1.7
|
||||||
|
|
||||||
|
###########
|
||||||
|
# BUILD STAGE
|
||||||
|
###########
|
||||||
|
FROM cgr.dev/chainguard/node:latest-dev AS build
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy dependency manifests
|
||||||
|
COPY package*.json ./
|
||||||
|
|
||||||
|
# Install all deps (inkl. dev)
|
||||||
|
RUN --mount=type=cache,target=/root/.npm npm ci
|
||||||
|
|
||||||
|
# Copy app source and build
|
||||||
|
COPY . .
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
###########
|
||||||
|
# RUNTIME STAGE
|
||||||
|
###########
|
||||||
|
FROM cgr.dev/chainguard/node:latest
|
||||||
|
ENV NODE_ENV=production
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy only what’s needed to run
|
||||||
|
COPY --from=build /app/package*.json ./
|
||||||
|
COPY --from=build /app/node_modules ./node_modules
|
||||||
|
COPY --from=build /app/dist ./dist
|
||||||
|
COPY --from=build /app/server.mjs ./server.mjs
|
||||||
|
|
||||||
|
# Chainguard runs as nonroot by default (user `nonroot`)
|
||||||
|
USER node
|
||||||
|
#EXPOSE 3000
|
||||||
|
|
||||||
|
CMD ["node", "./server.mjs"]
|
||||||
@@ -1,33 +1,30 @@
|
|||||||
[Unit]
|
[Unit]
|
||||||
Description=Valtrix Website
|
Description=Valtrix Website
|
||||||
Requires=edge.network
|
|
||||||
After=edge.network
|
|
||||||
|
|
||||||
[Container]
|
[Container]
|
||||||
Image=localhost/valtrixweb
|
Image=localhost/valtrix-website
|
||||||
ContainerName=valtrixwebsite
|
ContainerName=valtrix-website
|
||||||
Network=edge
|
Network=edge
|
||||||
AutoUpdate=registry
|
AutoUpdate=registry
|
||||||
Environment=TZ=Europe/Berlin
|
Environment=TZ=Europe/Berlin
|
||||||
|
|
||||||
#Traefik Labels
|
#Traefik Labels
|
||||||
Label=traefik.enable=true
|
Label="traefik.enable=true"
|
||||||
Label=traefik.http.routers.wtw.rule=Host(`www.valtrix.systems`)
|
Label="traefik.http.routers.wtw.rule=Host(`www.valtrix.systems`)"
|
||||||
Label=traefik.http.services.wtw.loadbalancer.server.port=3000
|
Label="traefik.http.services.wtw.loadbalancer.server.port=3000"
|
||||||
Label=traefik.http.routers.wtw.entrypoints=websecure
|
Label="traefik.http.routers.wtw.entrypoints=websecure"
|
||||||
Label=traefik.http.routers.wtw.tls=true
|
Label="traefik.http.routers.wtw.tls=true"
|
||||||
Label=traefik.http.routers.wtw.tls.certresolver=le
|
Label="traefik.http.routers.wtw.tls.certresolver=le"
|
||||||
|
|
||||||
Label=traefik.http.routers.wtw-http.rule=Host(`www.valtrix.systems`)
|
Label="traefik.http.routers.wtw-http.rule=Host(`www.valtrix.systems`)"
|
||||||
Label=traefik.http.routers.wtw-http.entrypoints=web
|
Label="traefik.http.routers.wtw-http.entrypoints=web"
|
||||||
Label=traefik.http.routers.wtw-http.middlewares=wtw-redirect
|
Label="traefik.http.routers.wtw-http.middlewares=wtw-redirect"
|
||||||
Label=traefik.http.middlewares.wtw-redirect.redirectscheme.scheme=https
|
Label="traefik.http.middlewares.wtw-redirect.redirectscheme.scheme=https"
|
||||||
Label=traefik.http.middlewares.wtw-redirect.redirectscheme.permanent=true
|
Label="traefik.http.middlewares.wtw-redirect.redirectscheme.permanent=true"
|
||||||
Label=traefik.http.routers.wtw.middlewares=secure-headers@file
|
Label="traefik.http.routers.wtw.middlewares=secure-headers@file"
|
||||||
|
|
||||||
Label="traefik.http.middlewares.wtw-sec.headers.customResponseHeaders.Content-Security-Policy=default-src 'self'; base-uri 'self'; object-src 'none'; frame-ancestors 'self'; img-src 'self' data:; style-src 'self' 'unsafe-inline'; script-src 'self'; script-src-elem 'self' 'unsafe-inline'; connect-src 'self' wss: https:; font-src 'self' data:; worker-src 'self' blob:;"
|
Label="traefik.http.middlewares.wtw-sec.headers.customResponseHeaders.Content-Security-Policy=default-src 'self'; base-uri 'self'; object-src 'none'; frame-ancestors 'self'; img-src 'self' data:; style-src 'self' 'unsafe-inline'; script-src 'self'; script-src-elem 'self' 'unsafe-inline'; connect-src 'self' wss: https:; font-src 'self' data:; worker-src 'self' blob:;"
|
||||||
Label=traefik.http.routers.wtw.middlewares=wtw-sec@docker
|
Label="traefik.http.routers.wtw.middlewares=wtw-sec@docker"
|
||||||
Label=traefik.http.routers.wtw.middlewares=wtw-sec@docker
|
|
||||||
Label="traefik.http.routers.wtw.middlewares=auth"
|
Label="traefik.http.routers.wtw.middlewares=auth"
|
||||||
Label="traefik.http.middlewares.auth.basicauth.users=smb:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/"
|
Label="traefik.http.middlewares.auth.basicauth.users=smb:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/"
|
||||||
|
|
||||||
|
|||||||
6320
package-lock.json
generated
Normal file
6320
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -4,13 +4,15 @@ const { title = "Valtrix – Struktur schafft Wert", description = "Cloud, Secur
|
|||||||
// Language & routing helpers
|
// Language & routing helpers
|
||||||
const pathname = Astro.url.pathname;
|
const pathname = Astro.url.pathname;
|
||||||
const isEn = pathname.startsWith('/en');
|
const isEn = pathname.startsWith('/en');
|
||||||
|
const isDe = pathname.startsWith('/de');
|
||||||
const lang = isEn ? 'en' : 'de';
|
const lang = isEn ? 'en' : 'de';
|
||||||
const prefix = isEn ? '/en' : '';
|
// Default to /de for links so navigation always points to language-prefixed routes
|
||||||
const stripEn = (p) => p.replace(/^\/en(?=\/|$)/, '');
|
const prefix = isEn ? '/en' : '/de';
|
||||||
const altHref = isEn ? (stripEn(pathname) || '/') : ('/en' + (pathname === '/' ? '' : pathname));
|
const stripLang = (p) => p.replace(/^\/(?:en|de)(?=\/|$)/, '');
|
||||||
|
const altHref = isEn ? ('/de' + (stripLang(pathname) || '/')) : ('/en' + (stripLang(pathname) || '/'));
|
||||||
// Canonical/alternate URLs (use provided path when present for og/link tags)
|
// Canonical/alternate URLs (use provided path when present for og/link tags)
|
||||||
const canonicalPath = path || pathname;
|
const canonicalPath = path || pathname;
|
||||||
const deHref = stripEn(canonicalPath) || '/';
|
const deHref = canonicalPath.startsWith('/de') ? canonicalPath : ('/de' + (canonicalPath === '/' ? '' : canonicalPath));
|
||||||
const enHref = canonicalPath.startsWith('/en') ? canonicalPath : ('/en' + (canonicalPath === '/' ? '' : canonicalPath));
|
const enHref = canonicalPath.startsWith('/en') ? canonicalPath : ('/en' + (canonicalPath === '/' ? '' : canonicalPath));
|
||||||
// Nav labels per language
|
// Nav labels per language
|
||||||
const labels = isEn ? {
|
const labels = isEn ? {
|
||||||
@@ -50,22 +52,39 @@ const labels = isEn ? {
|
|||||||
<link rel="alternate" hreflang="en" href={`https://valtrix.systems${enHref}`} />
|
<link rel="alternate" hreflang="en" href={`https://valtrix.systems${enHref}`} />
|
||||||
</head>
|
</head>
|
||||||
<body class="bg-white transition-colors duration-300">
|
<body class="bg-white transition-colors duration-300">
|
||||||
<header class="max-w-7xl mx-auto px-6 py-6">
|
<header class="max-w-7xl mx-auto px-6 py-4">
|
||||||
<nav class="flex items-center justify-between">
|
<nav class="flex items-center justify-between">
|
||||||
<a href={`${prefix || '/'}`} class="flex items-center gap-3 font-semibold">
|
<a href={`${prefix || '/'}`} class="flex items-center gap-3 font-semibold min-w-0">
|
||||||
<img src="/logo-valtrix.png" alt="Valtrix" class="h-9 w-9 rounded-md shadow-sm"/>
|
<img src="/logo-valtrix.png" alt="Valtrix" class="h-9 w-9 rounded-md shadow-sm"/>
|
||||||
<span class="tracking-wide text-lg">VALTRIX</span>
|
<span class="tracking-wide text-lg">VALTRIX</span>
|
||||||
</a>
|
</a>
|
||||||
<div class="flex items-center gap-6">
|
<div class="hidden md:flex items-center gap-6">
|
||||||
<a href={`${prefix}/leistungen`} class="text-textMuted hover:text-white">{labels.services}</a>
|
<a href={`${prefix}/leistungen`} class="text-textMuted hover:text-white">{labels.services}</a>
|
||||||
<a href={`${prefix}/sicherheit`} class="text-textMuted hover:text-white">{labels.security}</a>
|
<a href={`${prefix}/sicherheit`} class="text-textMuted hover:text-white">{labels.security}</a>
|
||||||
<a href={`${prefix}/cases`} class="text-textMuted hover:text-white">{labels.cases}</a>
|
<a href={`${prefix}/cases`} class="text-textMuted hover:text-white">{labels.cases}</a>
|
||||||
<a href={`${prefix}/ueber-uns`} class="text-textMuted hover:text-white">{labels.about}</a>
|
<a href={`${prefix}/ueber-uns`} class="text-textMuted hover:text-white">{labels.about}</a>
|
||||||
<a href={`${prefix}/kontakt`} class="px-4 py-2 rounded-brand bg-primary text-white hover:opacity-90">{labels.contact}</a>
|
<a href={`${prefix}/kontakt`} class="px-4 py-2 rounded-brand bg-primary text-white hover:opacity-90">{labels.contact}</a>
|
||||||
<a href={altHref} class="px-3 py-2 border rounded-brand hover:bg-graybg" aria-label="Language switch">{labels.langShort}</a>
|
<a href={altHref} class="px-3 py-2 border rounded-brand hover:bg-graybg" aria-label="Language switch">{labels.langShort}</a>
|
||||||
<button id="themeToggle" aria-label="Theme" class="px-3 py-2 border rounded-brand hover:bg-graybg">Light</button>
|
<button data-theme-toggle aria-label="Theme" class="px-3 py-2 border rounded-brand hover:bg-graybg">Light</button>
|
||||||
</div>
|
</div>
|
||||||
|
<button id="navToggle" class="md:hidden px-3 py-2 border rounded-brand hover:bg-graybg" aria-controls="mobileMenu" aria-expanded="false" aria-label="Navigation">
|
||||||
|
Menu
|
||||||
|
</button>
|
||||||
</nav>
|
</nav>
|
||||||
|
<!-- Mobile menu -->
|
||||||
|
<div id="mobileMenu" class="md:hidden hidden mt-3 border rounded-brand p-4 bg-[color:var(--color-bg,#0b0e14)]">
|
||||||
|
<div class="grid gap-3">
|
||||||
|
<a href={`${prefix}/leistungen`} class="block text-white">{labels.services}</a>
|
||||||
|
<a href={`${prefix}/sicherheit`} class="block text-white">{labels.security}</a>
|
||||||
|
<a href={`${prefix}/cases`} class="block text-white">{labels.cases}</a>
|
||||||
|
<a href={`${prefix}/ueber-uns`} class="block text-white">{labels.about}</a>
|
||||||
|
<a href={`${prefix}/kontakt`} class="block px-4 py-2 text-center rounded-brand bg-primary text-white hover:opacity-90">{labels.contact}</a>
|
||||||
|
<div class="flex items-center gap-3 pt-2 border-t mt-2">
|
||||||
|
<a href={altHref} class="px-3 py-2 border rounded-brand hover:bg-graybg" aria-label="Language switch">{labels.langShort}</a>
|
||||||
|
<button data-theme-toggle aria-label="Theme" class="px-3 py-2 border rounded-brand hover:bg-graybg">Light</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</header>
|
</header>
|
||||||
<main class="max-w-7xl mx-auto px-6">
|
<main class="max-w-7xl mx-auto px-6">
|
||||||
<slot />
|
<slot />
|
||||||
@@ -77,14 +96,36 @@ const labels = isEn ? {
|
|||||||
© {new Date().getFullYear()} Valtrix · <a href={`${prefix}/impressum`} class="underline">{labels.imprint}</a> · <a href={`${prefix}/datenschutz`} class="underline">{labels.privacy}</a>
|
© {new Date().getFullYear()} Valtrix · <a href={`${prefix}/impressum`} class="underline">{labels.imprint}</a> · <a href={`${prefix}/datenschutz`} class="underline">{labels.privacy}</a>
|
||||||
</footer>
|
</footer>
|
||||||
<script>
|
<script>
|
||||||
const btn = document.getElementById('themeToggle');
|
// Theme toggle for all buttons
|
||||||
const html = document.documentElement;
|
const html = document.documentElement;
|
||||||
const set = (m)=>{ html.setAttribute('data-theme', m); btn.textContent = m==='dark' ? 'Light' : 'Dark'; };
|
const themeBtns = Array.from(document.querySelectorAll('[data-theme-toggle]'));
|
||||||
set(localStorage.getItem('theme')||'dark');
|
const setTheme = (m)=>{
|
||||||
btn?.addEventListener('click', ()=>{
|
html.setAttribute('data-theme', m);
|
||||||
|
themeBtns.forEach(b=> b.textContent = m==='dark' ? 'Light' : 'Dark');
|
||||||
|
};
|
||||||
|
setTheme(localStorage.getItem('theme')||'dark');
|
||||||
|
themeBtns.forEach(btn=> btn.addEventListener('click', ()=>{
|
||||||
const next = html.getAttribute('data-theme')==='dark' ? 'light' : 'dark';
|
const next = html.getAttribute('data-theme')==='dark' ? 'light' : 'dark';
|
||||||
set(next); localStorage.setItem('theme', next);
|
setTheme(next); localStorage.setItem('theme', next);
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Mobile nav toggle
|
||||||
|
const navToggle = document.getElementById('navToggle');
|
||||||
|
const mobileMenu = document.getElementById('mobileMenu');
|
||||||
|
const closeMenu = () => {
|
||||||
|
mobileMenu?.classList.add('hidden');
|
||||||
|
navToggle?.setAttribute('aria-expanded','false');
|
||||||
|
};
|
||||||
|
navToggle?.addEventListener('click', ()=>{
|
||||||
|
const isOpen = !mobileMenu.classList.contains('hidden');
|
||||||
|
if (isOpen) { closeMenu(); }
|
||||||
|
else {
|
||||||
|
mobileMenu.classList.remove('hidden');
|
||||||
|
navToggle.setAttribute('aria-expanded','true');
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
// Close menu on link click
|
||||||
|
mobileMenu?.querySelectorAll('a').forEach(a=> a.addEventListener('click', closeMenu));
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
---
|
---
|
||||||
import Base from "../layouts/Base.astro";
|
import Base from "../../layouts/Base.astro";
|
||||||
const items = [
|
const items = [
|
||||||
{ slug: "cloud-migration", title: "Sichere Cloud-Migration (AWS/Azure)", summary: "Landing Zone, Zero-Trust, Pipeline & Observability in 6 Wochen." },
|
{ slug: "cloud-migration", title: "Sichere Cloud-Migration (AWS/Azure)", summary: "Landing Zone, Zero-Trust, Pipeline & Observability in 6 Wochen." },
|
||||||
{ slug: "k8s-hardening", title: "Kubernetes Hardening & GitOps", summary: "CIS Benchmarks, OPA Policies-as-Code, ArgoCD Rollouts." }
|
{ slug: "k8s-hardening", title: "Kubernetes Hardening & GitOps", summary: "CIS Benchmarks, OPA Policies-as-Code, ArgoCD Rollouts." }
|
||||||
];
|
];
|
||||||
---
|
---
|
||||||
<Base path="/cases" title="Referenzen – Valtrix">
|
<Base path="/de/cases" title="Referenzen – Valtrix">
|
||||||
<h1 class="text-3xl font-bold mt-8">Referenzen</h1>
|
<h1 class="text-3xl font-bold mt-8">Referenzen</h1>
|
||||||
<div class="grid md:grid-cols-2 gap-6 mt-6">
|
<div class="grid md:grid-cols-2 gap-6 mt-6">
|
||||||
{items.map(i => (
|
{items.map(i => (
|
||||||
<a href={`/cases/${i.slug}`} class="p-6 border rounded-brand card hover:shadow-sm transition-shadow block">
|
<a href={`/de/cases/${i.slug}`} class="p-6 border rounded-brand card hover:shadow-sm transition-shadow block">
|
||||||
<h2 class="font-semibold text-lg">{i.title}</h2>
|
<h2 class="font-semibold text-lg">{i.title}</h2>
|
||||||
<p class="mt-2 text-textMuted">{i.summary}</p>
|
<p class="mt-2 text-textMuted">{i.summary}</p>
|
||||||
<span class="mt-3 inline-block text-primary underline">Case ansehen</span>
|
<span class="mt-3 inline-block text-primary underline">Case ansehen</span>
|
||||||
36
src/pages/de/cases/cloud-migration.astro
Normal file
36
src/pages/de/cases/cloud-migration.astro
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
---
|
||||||
|
import Base from "../../../layouts/Base.astro";
|
||||||
|
---
|
||||||
|
<Base path="/de/cases/cloud-migration" title="Case Study – Sichere Cloud-Migration">
|
||||||
|
<h1 class="text-3xl font-bold mt-8">Case Study: Sichere Cloud-Migration</h1>
|
||||||
|
<p class="mt-4 text-textMuted">Enterprise-Migration von On-Prem nach Cloud mit Fokus auf Sicherheit & Compliance.</p>
|
||||||
|
|
||||||
|
<section class="mt-8 grid md:grid-cols-3 gap-6">
|
||||||
|
<div class="p-6 border rounded-brand card">
|
||||||
|
<h3 class="font-semibold">Ziel</h3>
|
||||||
|
<p class="mt-2 text-textMuted">Sichere AWS/Azure Landing Zone, segmentiertes Netzwerk, zentralisiertes IAM.</p>
|
||||||
|
</div>
|
||||||
|
<div class="p-6 border rounded-brand card">
|
||||||
|
<h3 class="font-semibold">Vorgehen</h3>
|
||||||
|
<p class="mt-2 text-textMuted">Guardrails, IaC (Terraform/OpenTofu), GitOps, Observability, Cost Controls.</p>
|
||||||
|
</div>
|
||||||
|
<div class="p-6 border rounded-brand card">
|
||||||
|
<h3 class="font-semibold">Ergebnis</h3>
|
||||||
|
<p class="mt-2 text-textMuted">Schnelleres Deployment, Auditfähigkeit, reduzierte Risiken & Kosten.</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="mt-10">
|
||||||
|
<h2 class="text-2xl font-bold mb-3">Kennzahlen</h2>
|
||||||
|
<ul class="list-disc pl-6 text-textMuted">
|
||||||
|
<li>6 Wochen bis produktionsreifer Basis</li>
|
||||||
|
<li>~40% weniger manuelle Deployments durch CI/CD</li>
|
||||||
|
<li>100% Infrastruktur als Code & Policies-as-Code</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<div class="mt-12 rounded-brand p-6 bg-gradient-to-r from-accent to-primary text-white flex items-center justify-between">
|
||||||
|
<div class="text-lg font-semibold tracking-wide">Ähnliches Projekt geplant?</div>
|
||||||
|
<a href="/de/kontakt" class="px-5 py-3 rounded-brand bg-white text-primary font-medium">Jetzt anfragen</a>
|
||||||
|
</div>
|
||||||
|
</Base>
|
||||||
48
src/pages/de/cases/k8s-hardening.astro
Normal file
48
src/pages/de/cases/k8s-hardening.astro
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
---
|
||||||
|
import Base from "../../../layouts/Base.astro";
|
||||||
|
---
|
||||||
|
<Base path="/de/cases/k8s-hardening" title="Case Study – Kubernetes Hardening & GitOps">
|
||||||
|
<h1 class="text-3xl font-bold mt-8">Case Study: Kubernetes Hardening & GitOps</h1>
|
||||||
|
<p class="mt-4 text-textMuted">Produktionsreifes K8s‑Setup mit Härtung nach CIS, durchgängigen Policies‑as‑Code und GitOps‑Deployments.</p>
|
||||||
|
|
||||||
|
<section class="mt-8 grid md:grid-cols-3 gap-6">
|
||||||
|
<div class="p-6 border rounded-brand card">
|
||||||
|
<h3 class="font-semibold">Ziel</h3>
|
||||||
|
<p class="mt-2 text-textMuted">Sicheres Cluster‑Baseline: identitätszentrierte Zugriffe, Netzwerk‑Segmentierung, saubere Supply Chain.</p>
|
||||||
|
</div>
|
||||||
|
<div class="p-6 border rounded-brand card">
|
||||||
|
<h3 class="font-semibold">Vorgehen</h3>
|
||||||
|
<p class="mt-2 text-textMuted">CIS Benchmarks, OPA/Gatekeeper oder Kyverno Policies, Pod Security Standards, signierte Artefakte & GitOps (Argo CD).</p>
|
||||||
|
</div>
|
||||||
|
<div class="p-6 border rounded-brand card">
|
||||||
|
<h3 class="font-semibold">Ergebnis</h3>
|
||||||
|
<p class="mt-2 text-textMuted">Reproduzierbare Deployments, Policy‑konforme Releases, Auditfähigkeit und reduzierte Angriffsfläche.</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="mt-10">
|
||||||
|
<h2 class="text-2xl font-bold mb-3">Kennzahlen</h2>
|
||||||
|
<ul class="list-disc pl-6 text-textMuted">
|
||||||
|
<li>100% deklarative Deployments (GitOps, Pull‑basiert)</li>
|
||||||
|
<li>~80% der CIS‑Kontrollen automatisiert überprüfbar</li>
|
||||||
|
<li>Policy‑Breaks verhindern riskante Manifeste vor dem Rollout</li>
|
||||||
|
<li>Signierte Container‑Images & SBOM für Kernservices</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="mt-10 grid md:grid-cols-2 gap-6">
|
||||||
|
<div class="p-6 border rounded-brand card">
|
||||||
|
<h3 class="font-semibold">GitOps Setup</h3>
|
||||||
|
<p class="mt-2 text-textMuted">Mehrstufige Environments (dev/stage/prod) mit Branch/Tag‑Strategie, Kustomize‑Overlays und PR‑basierten Changes.</p>
|
||||||
|
</div>
|
||||||
|
<div class="p-6 border rounded-brand card">
|
||||||
|
<h3 class="font-semibold">Cluster Hardening</h3>
|
||||||
|
<p class="mt-2 text-textMuted">RBAC Least‑Privilege, NetworkPolicies default‑deny, Pod Security, Image‑Scanning, Secrets‑Management.</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<div class="mt-12 rounded-brand p-6 bg-gradient-to-r from-accent to-primary text-white flex items-center justify-between">
|
||||||
|
<div class="text-lg font-semibold tracking-wide">Ähnliches Projekt geplant?</div>
|
||||||
|
<a href="/de/kontakt" class="px-5 py-3 rounded-brand bg-white text-primary font-medium">Jetzt anfragen</a>
|
||||||
|
</div>
|
||||||
|
</Base>
|
||||||
@@ -1,15 +1,15 @@
|
|||||||
---
|
---
|
||||||
import Base from "../layouts/Base.astro";
|
import Base from "../../layouts/Base.astro";
|
||||||
const updated = new Date().toLocaleDateString('de-DE', { year: 'numeric', month: 'long', day: '2-digit' });
|
const updated = new Date().toLocaleDateString('de-DE', { year: 'numeric', month: 'long', day: '2-digit' });
|
||||||
---
|
---
|
||||||
<Base path="/datenschutz" title="Datenschutz – Valtrix">
|
<Base path="/de/datenschutz" title="Datenschutz – Valtrix">
|
||||||
<h1 class="text-2xl font-bold mt-8">Datenschutzhinweise</h1>
|
<h1 class="text-2xl font-bold mt-8">Datenschutzhinweise</h1>
|
||||||
|
|
||||||
<p class="mt-4 text-textMuted">Diese Hinweise informieren darüber, wie wir personenbezogene Daten beim Besuch dieser Website verarbeiten. Die Website ist als statische Seite umgesetzt; es werden keine Tracking‑ oder Marketing‑Cookies eingesetzt und keine externen Ressourcen (z. B. Google Fonts, Analytics, Formdienste) geladen.</p>
|
<p class="mt-4 text-textMuted">Diese Hinweise informieren darüber, wie wir personenbezogene Daten beim Besuch dieser Website verarbeiten. Die Website ist als statische Seite umgesetzt; es werden keine Tracking‑ oder Marketing‑Cookies eingesetzt und keine externen Ressourcen (z. B. Google Fonts, Analytics, Formdienste) geladen.</p>
|
||||||
|
|
||||||
<section class="mt-8 grid gap-2">
|
<section class="mt-8 grid gap-2">
|
||||||
<h2 class="text-xl font-semibold">1. Verantwortlicher</h2>
|
<h2 class="text-xl font-semibold">1. Verantwortlicher</h2>
|
||||||
<p>Verantwortlicher im Sinne der DSGVO ist: <strong>Valtrix</strong>. Anschrift siehe <a class="underline text-primary" href="/impressum">Impressum</a>. E‑Mail: <a class="underline text-primary" href="mailto:kontakt@valtrix.systems">kontakt@valtrix.systems</a></p>
|
<p>Verantwortlicher im Sinne der DSGVO ist: <strong>Valtrix</strong>. Anschrift siehe <a class="underline text-primary" href="/de/impressum">Impressum</a>. E‑Mail: <a class="underline text-primary" href="mailto:kontakt@valtrix.systems">kontakt@valtrix.systems</a></p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="mt-6 grid gap-2">
|
<section class="mt-6 grid gap-2">
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
import Base from "../layouts/Base.astro";
|
import Base from "../../layouts/Base.astro";
|
||||||
---
|
---
|
||||||
<Base path="/impressum" title="Impressum – Valtrix">
|
<Base path="/de/impressum" title="Impressum – Valtrix">
|
||||||
<h1 class="text-2xl font-bold mt-8">Impressum</h1>
|
<h1 class="text-2xl font-bold mt-8">Impressum</h1>
|
||||||
<p><strong>VALTRIX</strong> GmbH i.G.</p>
|
<p><strong>VALTRIX</strong> GmbH i.G.</p>
|
||||||
<p>Am Wassergraben 4, 10179 Berlin</p>
|
<p>Am Wassergraben 4, 10179 Berlin</p>
|
||||||
82
src/pages/de/index.astro
Normal file
82
src/pages/de/index.astro
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
---
|
||||||
|
import Base from "../../layouts/Base.astro";
|
||||||
|
const benefits = [
|
||||||
|
{ title: "Zero-Trust & Härtung", text: "Security-by-Design, CIS/BSI-Richtlinien, automatisierte Policies." },
|
||||||
|
{ title: "Cloud Foundations", text: "Landing Zones, Identity, Observability – sauber & skalierbar." },
|
||||||
|
{ title: "Automatisierung", text: "IaC, CI/CD, GitOps, wiederholbar & auditfähig." }
|
||||||
|
];
|
||||||
|
const approach = [
|
||||||
|
{ title: 'Assess', text: 'Risiko- und Reifegradanalyse: schneller Quick-Scan, klare Prioritäten.' },
|
||||||
|
{ title: 'Design', text: 'Zielarchitektur & Guardrails: sichere, skalierbare Blueprint-Entwürfe.' },
|
||||||
|
{ title: 'Build', text: 'IaC, Pipelines & Policies-as-Code: reproduzierbar und überprüfbar.' },
|
||||||
|
{ title: 'Run', text: 'Monitoring, Audits & Kostentransparenz: nachhaltig betreibbar.' }
|
||||||
|
];
|
||||||
|
---
|
||||||
|
<Base path="/de">
|
||||||
|
<section class="py-16 grid lg:grid-cols-2 gap-12 items-center">
|
||||||
|
<div class="fade-in">
|
||||||
|
<h1 class="text-4xl md:text-5xl font-bold leading-tight">
|
||||||
|
Security-first Cloud Consulting.
|
||||||
|
</h1>
|
||||||
|
<p class="mt-4 text-lg text-textMuted">
|
||||||
|
Wir verbinden <strong>Struktur</strong> und <strong>Wert</strong>: sichere Cloud-Architekturen, Automatisierung und Compliance – pragmatisch und messbar.
|
||||||
|
</p>
|
||||||
|
<div class="mt-8 flex gap-4">
|
||||||
|
<a href="/de/leistungen" class="px-5 py-3 rounded-brand bg-primary text-white hover:opacity-90">Leistungen</a>
|
||||||
|
<a href="/de/kontakt" class="px-5 py-3 rounded-brand border border-primary text-primary hover:bg-graybg">Kontakt</a>
|
||||||
|
</div>
|
||||||
|
<div class="mt-8 grid grid-cols-3 gap-4 text-sm text-textMuted">
|
||||||
|
<div class="p-3 border rounded-brand card">DSGVO / EU-Hosting</div>
|
||||||
|
<div class="p-3 border rounded-brand card">BSI/ISO-orientiert</div>
|
||||||
|
<div class="p-3 border rounded-brand card">Auditfähig</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="py-10 grid md:grid-cols-3 gap-6">
|
||||||
|
{benefits.map(b => (
|
||||||
|
<div class="p-6 border rounded-brand hover:shadow-sm transition-shadow card">
|
||||||
|
<h3 class="font-semibold text-lg">{b.title}</h3>
|
||||||
|
<p class="mt-2 text-textMuted">{b.text}</p>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="py-12">
|
||||||
|
<h2 class="text-2xl font-bold mb-6">Leistungen</h2>
|
||||||
|
<div class="grid md:grid-cols-3 gap-6">
|
||||||
|
<div class="p-6 border rounded-brand card">
|
||||||
|
<h3 class="font-semibold text-lg">Cloud & DevSecOps</h3>
|
||||||
|
<p class="mt-2 text-textMuted">Plan, Build & Run – effizient, sicher, skalierbar.</p>
|
||||||
|
<a href="/de/leistungen" class="mt-3 inline-block text-primary underline">Mehr erfahren</a>
|
||||||
|
</div>
|
||||||
|
<div class="p-6 border rounded-brand card">
|
||||||
|
<h3 class="font-semibold text-lg">Security & Compliance</h3>
|
||||||
|
<p class="mt-2 text-textMuted">Zero-Trust, IAM, Hardening, Audits, BSI/ISO‑orientierte Prozesse.</p>
|
||||||
|
</div>
|
||||||
|
<div class="p-6 border rounded-brand card">
|
||||||
|
<h3 class="font-semibold text-lg">AI & Automation</h3>
|
||||||
|
<p class="mt-2 text-textMuted">RAG/Agenten, Automatisierung von Betriebsprozessen, sichere Integration.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="py-14">
|
||||||
|
<h2 class="text-2xl font-bold mb-6">Unser Ansatz</h2>
|
||||||
|
<div class="grid md:grid-cols-4 gap-6">
|
||||||
|
{approach.map((a, i) => (
|
||||||
|
<div class="p-6 border rounded-brand card">
|
||||||
|
<div class="text-xl font-semibold">{i+1}. {a.title}</div>
|
||||||
|
<p class="mt-2 text-textMuted">{a.text}</p>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="py-12">
|
||||||
|
<div class="rounded-brand p-8 bg-gradient-to-r from-accent to-primary text-white flex flex-col md:flex-row items-center justify-between gap-6">
|
||||||
|
<div class="text-xl font-semibold tracking-wide">Sichere Cloud – pragmatisch umgesetzt.</div>
|
||||||
|
<a href="/de/kontakt" class="px-6 py-3 rounded-brand bg-white text-primary font-medium">Projekt anfragen</a>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</Base>
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
import Base from "../layouts/Base.astro";
|
import Base from "../../layouts/Base.astro";
|
||||||
---
|
---
|
||||||
<Base path="/kontakt" title="Kontakt – Valtrix">
|
<Base path="/de/kontakt" title="Kontakt – Valtrix">
|
||||||
<h1 class="text-3xl font-bold mt-8">Kontakt</h1>
|
<h1 class="text-3xl font-bold mt-8">Kontakt</h1>
|
||||||
<form id="contactForm" class="mt-6 grid gap-4 max-w-xl">
|
<form id="contactForm" class="mt-6 grid gap-4 max-w-xl">
|
||||||
<input class="border rounded-brand p-3" name="name" placeholder="Ihr Name" required>
|
<input class="border rounded-brand p-3" name="name" placeholder="Ihr Name" required>
|
||||||
@@ -20,7 +20,7 @@ import Base from "../layouts/Base.astro";
|
|||||||
const message = (data.get('message')||'').toString();
|
const message = (data.get('message')||'').toString();
|
||||||
const subject = `Kontaktanfrage – ${name}`;
|
const subject = `Kontaktanfrage – ${name}`;
|
||||||
const body = `Name: ${name}\nE-Mail: ${email}\n\n${message}`;
|
const body = `Name: ${name}\nE-Mail: ${email}\n\n${message}`;
|
||||||
const mail = `mailto:kontakt@valtrix.systems?subject=${encodeURIComponent(subject)}&body=${encodeURIComponent(body)}`;
|
const mail = `mailto:kontakt@valtrix.systems?subject=${encodeURIComponent(subject)}&body=${encodeURIComponent(body)}`;
|
||||||
window.location.href = mail;
|
window.location.href = mail;
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
import Base from "../layouts/Base.astro";
|
import Base from "../../layouts/Base.astro";
|
||||||
---
|
---
|
||||||
<Base path="/leistungen" title="Leistungen – Valtrix">
|
<Base path="/de/leistungen" title="Leistungen – Valtrix">
|
||||||
<h1 class="text-3xl font-bold font-bold mt-8">Leistungen</h1>
|
<h1 class="text-3xl font-bold font-bold mt-8">Leistungen</h1>
|
||||||
<div class="grid md:grid-cols-3 gap-6 mt-6">
|
<div class="grid md:grid-cols-3 gap-6 mt-6">
|
||||||
<div class="p-6 border rounded-brand card">
|
<div class="p-6 border rounded-brand card">
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
import Base from "../layouts/Base.astro";
|
import Base from "../../layouts/Base.astro";
|
||||||
---
|
---
|
||||||
<Base path="/sicherheit" title="Sicherheit & Compliance – Valtrix">
|
<Base path="/de/sicherheit" title="Sicherheit & Compliance – Valtrix">
|
||||||
<h1 class="text-3xl font-bold mt-8">Sicherheit & Compliance</h1>
|
<h1 class="text-3xl font-bold mt-8">Sicherheit & Compliance</h1>
|
||||||
<ul class="list-disc pl-6 mt-4 text-textMuted">
|
<ul class="list-disc pl-6 mt-4 text-textMuted">
|
||||||
<li>Datensouverän: EU-Hosting, DSGVO-konform</li>
|
<li>Datensouverän: EU-Hosting, DSGVO-konform</li>
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
import Base from "../layouts/Base.astro";
|
import Base from "../../layouts/Base.astro";
|
||||||
---
|
---
|
||||||
<Base path="/ueber-uns" title="Über uns – Valtrix">
|
<Base path="/de/ueber-uns" title="Über uns – Valtrix">
|
||||||
<h1 class="text-3xl font-bold mt-8">Über uns</h1>
|
<h1 class="text-3xl font-bold mt-8">Über uns</h1>
|
||||||
<p class="mt-4 text-textMuted max-w-2xl">Wir sind ein Team aus Cloud-, Security- und AI-Engineers. Wir bauen robuste Systeme – pragmatisch und messbar.</p>
|
<p class="mt-4 text-textMuted max-w-2xl">Wir sind ein Team aus Cloud-, Security- und AI-Engineers. Wir bauen robuste Systeme – pragmatisch und messbar.</p>
|
||||||
</Base>
|
</Base>
|
||||||
@@ -3,10 +3,10 @@ import Base from "../../layouts/Base.astro";
|
|||||||
const benefits = [
|
const benefits = [
|
||||||
{ title: "Zero-Trust & Hardening", text: "Security-by-Design, CIS/BSI guidelines, automated policies." },
|
{ title: "Zero-Trust & Hardening", text: "Security-by-Design, CIS/BSI guidelines, automated policies." },
|
||||||
{ title: "Cloud Foundations", text: "Landing zones, identity, observability – clean & scalable." },
|
{ title: "Cloud Foundations", text: "Landing zones, identity, observability – clean & scalable." },
|
||||||
{ title: "Automation", text: "IaC/CI/CD, GitOps, reproducible & auditable." }
|
{ title: "Automation", text: "IaC, CI/CD, GitOps, reproducible & auditable." }
|
||||||
];
|
];
|
||||||
const features = [
|
const features = [
|
||||||
{ title: "Cloud & DevOps", text: "Plan, Build & Run – efficient, secure, scalable." },
|
{ title: "Cloud & SecOps", text: "Plan, Build & Run – efficient, secure, scalable." },
|
||||||
{ title: "Security & Compliance", text: "BSI/ISO-aligned, Zero-Trust, audits, policies-as-code." },
|
{ title: "Security & Compliance", text: "BSI/ISO-aligned, Zero-Trust, audits, policies-as-code." },
|
||||||
{ title: "AI & Automation", text: "RAG, agents & process automation with measurable outcome." }
|
{ title: "AI & Automation", text: "RAG, agents & process automation with measurable outcome." }
|
||||||
];
|
];
|
||||||
@@ -56,12 +56,24 @@ const features = [
|
|||||||
|
|
||||||
<section class="py-14">
|
<section class="py-14">
|
||||||
<h2 class="text-2xl font-bold mb-6">Our approach</h2>
|
<h2 class="text-2xl font-bold mb-6">Our approach</h2>
|
||||||
<ol class="grid md:grid-cols-4 gap-6 list-decimal pl-5">
|
<div class="grid md:grid-cols-4 gap-6">
|
||||||
<li class="p-5 border rounded-brand card"><strong>Assess:</strong> Risk & maturity → quick scan.</li>
|
<div class="p-6 border rounded-brand card">
|
||||||
<li class="p-5 border rounded-brand card"><strong>Design:</strong> Target architecture & guardrails.</li>
|
<div class="text-xl font-semibold">1. Assess</div>
|
||||||
<li class="p-5 border rounded-brand card"><strong>Build:</strong> IaC, pipelines, policies-as-code.</li>
|
<p class="mt-2 text-textMuted">Risk & maturity analysis: quick-scan, clear priorities.</p>
|
||||||
<li class="p-5 border rounded-brand card"><strong>Run:</strong> Monitoring, audits, cost transparency.</li>
|
</div>
|
||||||
</ol>
|
<div class="p-6 border rounded-brand card">
|
||||||
|
<div class="text-xl font-semibold">2. Design</div>
|
||||||
|
<p class="mt-2 text-textMuted">Target architecture & guardrails: secure, scalable blueprints.</p>
|
||||||
|
</div>
|
||||||
|
<div class="p-6 border rounded-brand card">
|
||||||
|
<div class="text-xl font-semibold">3. Build</div>
|
||||||
|
<p class="mt-2 text-textMuted">IaC, pipelines & policies-as-code: reproducible & testable.</p>
|
||||||
|
</div>
|
||||||
|
<div class="p-6 border rounded-brand card">
|
||||||
|
<div class="text-xl font-semibold">4. Run</div>
|
||||||
|
<p class="mt-2 text-textMuted">Monitoring, audits & cost transparency for sustainable operations.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="py-12">
|
<section class="py-12">
|
||||||
|
|||||||
@@ -1,73 +1,10 @@
|
|||||||
---
|
---
|
||||||
import Base from "../layouts/Base.astro";
|
import Base from "../layouts/Base.astro";
|
||||||
const benefits = [
|
|
||||||
{ title: "Zero-Trust & Hardening", text: "Security-by-Design, CIS/BSI-Guidelines, automatisierte Policies." },
|
|
||||||
{ title: "Cloud Foundations", text: "Landing Zones, Identity, Observability – sauber & skalierbar." },
|
|
||||||
{ title: "Automatisierung", text: "IaC/CI/CD, GitOps, wiederholbar & auditfähig." }
|
|
||||||
];
|
|
||||||
const features = [
|
|
||||||
{ title: "Cloud & DevOps", text: "Plan, Build & Run – effizient, sicher, skalierbar." },
|
|
||||||
{ title: "Security & Compliance", text: "BSI/ISO-aligned, Zero-Trust, Audits, Policies-as-Code." },
|
|
||||||
{ title: "AI & Automation", text: "RAG, Agenten & Process Automation mit messbarem Outcome." }
|
|
||||||
];
|
|
||||||
---
|
---
|
||||||
<Base path="/">
|
<Base path="/">
|
||||||
<section class="py-16 grid lg:grid-cols-2 gap-12 items-center">
|
<meta http-equiv="refresh" content="0;url=/de/" />
|
||||||
<div class="fade-in">
|
<main class="max-w-7xl mx-auto px-6 py-12">
|
||||||
<h1 class="text-4xl md:text-5xl font-bold leading-tight">
|
<h1 class="text-2xl font-bold">Weiterleitung</h1>
|
||||||
Security-first Cloud Consulting.
|
<p class="mt-4">Die Seite wurde verschoben nach <a href="/de/" class="underline">/de/</a>. Sollte die automatische Weiterleitung fehlschlagen, klicke den Link.</p>
|
||||||
</h1>
|
</main>
|
||||||
<p class="mt-4 text-lg text-textMuted">
|
|
||||||
Wir verbinden <strong>Struktur</strong> und <strong>Wert</strong>: sichere Cloud-Architekturen, Automatisierung und Compliance – pragmatisch und messbar.
|
|
||||||
</p>
|
|
||||||
<div class="mt-8 flex gap-4">
|
|
||||||
<a href="/leistungen" class="px-5 py-3 rounded-brand bg-primary text-white hover:opacity-90">Leistungen</a>
|
|
||||||
<a href="/kontakt" class="px-5 py-3 rounded-brand border border-primary text-primary hover:bg-graybg">Kontakt</a>
|
|
||||||
</div>
|
|
||||||
<div class="mt-8 grid grid-cols-3 gap-4 text-sm text-textMuted">
|
|
||||||
<div class="p-3 border rounded-brand card">DSGVO / EU-Hosting</div>
|
|
||||||
<div class="p-3 border rounded-brand card">BSI/ISO-orientiert</div>
|
|
||||||
<div class="p-3 border rounded-brand card">Auditfähig</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="py-10 grid md:grid-cols-3 gap-6">
|
|
||||||
{benefits.map(b => (
|
|
||||||
<div class="p-6 border rounded-brand hover:shadow-sm transition-shadow card">
|
|
||||||
<h3 class="font-semibold text-lg">{b.title}</h3>
|
|
||||||
<p class="mt-2 text-textMuted">{b.text}</p>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="py-12">
|
|
||||||
<h2 class="text-2xl font-bold mb-6">Leistungen</h2>
|
|
||||||
<div class="grid md:grid-cols-3 gap-6">
|
|
||||||
{features.map(f => (
|
|
||||||
<div class="p-6 border rounded-brand hover:-translate-y-0.5 transition card">
|
|
||||||
<h3 class="font-semibold text-lg">{f.title}</h3>
|
|
||||||
<p class="mt-2 text-textMuted">{f.text}</p>
|
|
||||||
<a href="/leistungen" class="mt-3 inline-block text-primary underline">Mehr erfahren</a>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="py-14">
|
|
||||||
<h2 class="text-2xl font-bold mb-6">Unser Ansatz</h2>
|
|
||||||
<ol class="grid md:grid-cols-4 gap-6 list-decimal pl-5">
|
|
||||||
<li class="p-5 border rounded-brand card"><strong>Assess:</strong> Risiko & Reifegrad → Quick-Scan.</li>
|
|
||||||
<li class="p-5 border rounded-brand card"><strong>Design:</strong> Zielarchitektur & Guardrails.</li>
|
|
||||||
<li class="p-5 border rounded-brand card"><strong>Build:</strong> IaC, Pipelines, Policies-as-Code.</li>
|
|
||||||
<li class="p-5 border rounded-brand card"><strong>Run:</strong> Monitoring, Audits, Kostentransparenz.</li>
|
|
||||||
</ol>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="py-12">
|
|
||||||
<div class="rounded-brand p-8 bg-gradient-to-r from-accent to-primary text-white flex flex-col md:flex-row items-center justify-between gap-6">
|
|
||||||
<div class="text-xl font-semibold tracking-wide">Sichere Cloud – pragmatisch umgesetzt.</div>
|
|
||||||
<a href="/kontakt" class="px-6 py-3 rounded-brand bg-white text-primary font-medium">Projekt anfragen</a>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
</Base>
|
</Base>
|
||||||
|
|||||||
Reference in New Issue
Block a user