Compare commits
15 Commits
1aca706a4e
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| abb69fac66 | |||
|
|
d3307429a6 | ||
|
|
ee68666b0a | ||
|
|
b25b8ccbe9 | ||
|
|
b093c5a6b4 | ||
|
|
0125eecb4a | ||
| f6cb68945c | |||
|
|
62e1d134ed | ||
|
|
e944ac175b | ||
|
|
e6bc9fe80e | ||
| 7bc92e0923 | |||
|
|
3964d23b00 | ||
| c9143bc108 | |||
| 2653b3d3a3 | |||
| 1959778062 |
109
README.md
109
README.md
@@ -9,88 +9,55 @@ Enjoy your reading!
|
||||
|
||||
## Deploiment
|
||||
|
||||
This GitHub page is based on Hugo and deployed using GitHub Actions
|
||||
This Gitea page is based on Hugo and deployed using GitHub Actions
|
||||
For more details about [HowTo](https://gohugo.io/hosting-and-deployment/hosting-on-github/)
|
||||
|
||||
Workflow used :
|
||||
|
||||
```yaml
|
||||
# Sample workflow for building and deploying a Hugo site to GitHub Pages
|
||||
name: Deploy Hugo site to Pages
|
||||
name: Build and Deploy Hugo
|
||||
|
||||
on:
|
||||
# Runs on pushes targeting the default branch
|
||||
push:
|
||||
pull_request:
|
||||
types: [closed]
|
||||
branches:
|
||||
- main
|
||||
|
||||
# Allows you to run this workflow manually from the Actions tab
|
||||
workflow_dispatch:
|
||||
|
||||
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
|
||||
permissions:
|
||||
contents: read
|
||||
pages: write
|
||||
id-token: write
|
||||
|
||||
# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
|
||||
# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
|
||||
concurrency:
|
||||
group: "pages"
|
||||
cancel-in-progress: false
|
||||
|
||||
# Default to bash
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
jobs:
|
||||
# Build job
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
HUGO_VERSION: 0.141.0
|
||||
steps:
|
||||
- name: Install Hugo CLI
|
||||
run: |
|
||||
wget -O ${{ runner.temp }}/hugo.deb https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_extended_${HUGO_VERSION}_linux-amd64.deb \
|
||||
&& sudo dpkg -i ${{ runner.temp }}/hugo.deb
|
||||
- name: Install Dart Sass
|
||||
run: sudo snap install dart-sass
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
fetch-depth: 0
|
||||
- name: Setup Pages
|
||||
id: pages
|
||||
uses: actions/configure-pages@v5
|
||||
- name: Install Node.js dependencies
|
||||
run: "[[ -f package-lock.json || -f npm-shrinkwrap.json ]] && npm ci || true"
|
||||
- name: Build with Hugo
|
||||
env:
|
||||
HUGO_CACHEDIR: ${{ runner.temp }}/hugo_cache
|
||||
HUGO_ENVIRONMENT: production
|
||||
TZ: Europe/Paris
|
||||
run: |
|
||||
hugo \
|
||||
--gc \
|
||||
--minify \
|
||||
--baseURL "${{ steps.pages.outputs.base_url }}/"
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-pages-artifact@v3
|
||||
with:
|
||||
path: ./public
|
||||
build_and_deploy:
|
||||
if: github.event.pull_request.merged == true
|
||||
name: Deploy Hugo Website
|
||||
runs-on: self-hosted
|
||||
|
||||
container:
|
||||
image: debian:bookworm-slim
|
||||
|
||||
# Deployment jobs
|
||||
deploy:
|
||||
environment:
|
||||
name: github-pages
|
||||
url: ${{ steps.deployment.outputs.page_url }}
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
steps:
|
||||
- name: Deploy to GitHub Pages
|
||||
id: deployment
|
||||
uses: actions/deploy-pages@v4
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
apt-get update
|
||||
apt-get install -y git curl ca-certificates wget
|
||||
|
||||
- name: Install Hugo
|
||||
run: |
|
||||
wget https://github.com/gohugoio/hugo/releases/download/v0.152.2/hugo_extended_withdeploy_0.152.2_linux-amd64.deb -O /tmp/hugo.deb
|
||||
dpkg -i /tmp/hugo.deb
|
||||
|
||||
- name: Checkout code
|
||||
run: |
|
||||
git clone --recurse-submodules https://gitea.arnodo.fr/Damien/Notebook.git /tmp/workspace
|
||||
cd /tmp/workspace
|
||||
git checkout ${{ github.sha }}
|
||||
|
||||
- name: Build site
|
||||
run: /usr/local/bin/hugo
|
||||
working-directory: /tmp/workspace
|
||||
|
||||
- name: Deploy to Scaleway
|
||||
run: /usr/local/bin/hugo deploy --force --maxDeletes -1
|
||||
working-directory: /tmp/workspace
|
||||
env:
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.SCW_ACCESS_KEY }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.SCW_SECRET_KEY }}
|
||||
|
||||
```
|
||||
|
||||
23
assets/css/custom.css
Normal file
23
assets/css/custom.css
Normal file
@@ -0,0 +1,23 @@
|
||||
/* Fix SVG visibility in dark mode by inverting colors */
|
||||
.dark svg {
|
||||
filter: invert(1) hue-rotate(180deg);
|
||||
}
|
||||
|
||||
/* Counter-invert images within SVGs to preserve their original colors */
|
||||
.dark svg image {
|
||||
filter: invert(1) hue-rotate(180deg);
|
||||
}
|
||||
|
||||
/* Fix tech-banner gradient overlays to respect site theme, not OS theme */
|
||||
:root {
|
||||
--color-bg: #ffffff;
|
||||
}
|
||||
|
||||
.dark {
|
||||
--color-bg: #111111 !important;
|
||||
}
|
||||
|
||||
/* Override OS-level dark mode when site is in light mode */
|
||||
html:not(.dark) {
|
||||
--color-bg: #ffffff !important;
|
||||
}
|
||||
@@ -42,6 +42,15 @@ layout: hextra-home
|
||||
imageClass="hx:top-[40%] hx:left-[36px] hx:w-[180%] hx:sm:w-[110%] hx:dark:opacity-80"
|
||||
style="background: radial-gradient(ellipse at 50% 80%,rgba(203, 28, 66, 0.1),hsla(0,0%,100%,0));"
|
||||
>}}
|
||||
{{< hextra/feature-card
|
||||
title="Blog"
|
||||
subtitle="Retours d'expérience et articles techniques"
|
||||
link="blog"
|
||||
class="hx:aspect-auto hx:md:aspect-[1.1/1] hx:max-md:min-h-[340px]"
|
||||
image="/images/blog.png"
|
||||
imageClass="hx:top-[40%] hx:left-[36px] hx:w-[180%] hx:sm:w-[110%] hx:dark:opacity-80"
|
||||
style="background: radial-gradient(ellipse at 50% 80%,rgba(142, 68, 173, 0.1),hsla(0,0%,100%,0));"
|
||||
>}}
|
||||
{{< /hextra/feature-grid >}}
|
||||
|
||||
<div style="margin-top: 8rem; margin-bottom: 1.5rem;">
|
||||
@@ -70,7 +79,7 @@ layout: hextra-home
|
||||
Les outils et plateformes avec lesquels je travaille
|
||||
</p>
|
||||
</div>
|
||||
{{< tech-banner speed="30s" height="80px" gap="4rem" >}}
|
||||
{{< tech-banner speed="60s" height="80px" gap="4rem" >}}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
|
||||
5
content/blog/_index.fr.md
Normal file
5
content/blog/_index.fr.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
title: "Blog"
|
||||
---
|
||||
|
||||
Mes articles et retours d'expérience sur le réseau, les systèmes et le DevOps.
|
||||
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 21 KiB |
602
content/blog/migration-gitlab-gitea/index.fr.md
Normal file
602
content/blog/migration-gitlab-gitea/index.fr.md
Normal file
@@ -0,0 +1,602 @@
|
||||
---
|
||||
title: "Self-Hosting et Cloud Hybride : Mon Infrastructure Perso avec Gitea et Scaleway"
|
||||
date: 2025-11-20
|
||||
authors:
|
||||
- name: Damien
|
||||
link: https://gitea.arnodo.fr/Damien
|
||||
tags:
|
||||
- Homelab
|
||||
- DevOps
|
||||
- Self-Hosting
|
||||
- Network Engineering
|
||||
---
|
||||
|
||||
Retour d'expérience sur la construction de mon infrastructure personnelle : migration de GitHub vers Gitea auto-hébergé, et déploiement de labs réseau éphémères sur Scaleway. Le tout avec Proxmox et Wireguard.
|
||||
|
||||
<!--more-->
|
||||
|
||||
## Contexte et Motivations
|
||||
|
||||
En tant qu'ingénieur réseau travaillant dans le domaine de l'automatisation et de l'orchestration, j'ai toujours eu besoin d'un environnement pour expérimenter et apprendre. Jusqu'à récemment, j'utilisais GitHub pour mon code et ContainerLab en local/AWS via [DevPod](/documentation/devpod/) pour mes simulations réseau.
|
||||
|
||||
Mais plusieurs envies ont émergé :
|
||||
|
||||
- **Apprentissage** : Déployer et gérer un serveur Git complet avec CI/CD
|
||||
- **Souveraineté** : Héberger mes données en France (à minima en Europe), contrôler mon infrastructure
|
||||
- **Flexibilité** : Pouvoir lancer des labs réseau à la demande sans saturer ma machine locale
|
||||
- **Automatisation** : Scripter le provisionnement complet de mes environnements
|
||||
|
||||
## L'Architecture Globale
|
||||
|
||||

|
||||
|
||||
### Stack Technique
|
||||
|
||||
**Homelab** :
|
||||
- **Proxmox VE** : Hyperviseur pour VMs et LXC
|
||||
- **LXC Containers** : Gitea, runners, services divers
|
||||
- **Ansible** : Gestion des configurations et mises à jour
|
||||
- **Grafana/Prometheus/Loki** : Monitoring et supervision
|
||||
|
||||
**Exposition publique** :
|
||||
- **Dedibox Scaleway** : Instance dédiée avec IP fixe
|
||||
- **Nginx Proxy Manager** : Reverse proxy avec SSL automatique
|
||||
- **Wireguard VPN** : Tunnel sécurisé entre Scaleway et homelab
|
||||
|
||||
**Cloud Scaleway** :
|
||||
- **Object Storage** : Hébergement du blog Hugo (S3-compatible)
|
||||
- **Instances** : Labs réseau éphémères provisionnés à la demande
|
||||
- **Scaleway CLI** : Automatisation complète
|
||||
|
||||
## Partie 1 : Migration GitHub → Gitea Auto-Hébergé
|
||||
|
||||
### Pourquoi Gitea ?
|
||||
|
||||
[Gitea](https://gitea.io/) est un serveur Git leger, parfait pour du self-hosting :
|
||||
|
||||
- **Léger** : Idéal pour un LXC sur Proxmox
|
||||
- **Compatible GitHub Actions** : Migration des workflows sans réécriture
|
||||
- **Complet** : Issues, PRs, CI/CD, webhooks
|
||||
- **Open-source** : Communauté active
|
||||
|
||||
### Déploiement avec Proxmox Helper Scripts
|
||||
|
||||
Plutôt que de tout configurer manuellement, j'utilise les excellents [Proxmox Helper Scripts](https://community-scripts.github.io/ProxmoxVE/) qui automatisent la création de LXC préconfigurés.
|
||||
|
||||
**Installation de Gitea** :
|
||||
|
||||
```bash
|
||||
# Sur le nœud Proxmox, exécuter le script
|
||||
bash -c "$(wget -qLO - https://github.com/community-scripts/ProxmoxVE/raw/main/ct/gitea.sh)"
|
||||
```
|
||||
|
||||
Le script :
|
||||
- Crée un LXC Debian 12
|
||||
- Installe Gitea et sqlite
|
||||
- Configure les services systemd
|
||||
- Prépare l'environnement avec les bonnes permissions
|
||||
|
||||
**Configuration post-installation** :
|
||||
- Accès web : `http://<IP_LXC>:3000`
|
||||
- Configuration initiale via l'interface
|
||||
- URL du site : `https://gitea.arnodo.fr`
|
||||
|
||||
### Architecture Réseau : Wireguard + Nginx Proxy Manager
|
||||
|
||||
**Le problème** : Gitea tourne dans mon homelab (IP privée), mais je veux y accéder depuis Internet.
|
||||
|
||||
**La solution** :
|
||||
1. **Dedibox Scaleway** avec IP publique fixe et Nginx Proxy Manager
|
||||
2. **Wireguard VPN** entre la Dedibox et le homelab
|
||||
3. Le reverse proxy route `gitea.arnodo.fr` vers l'IP privée du LXC via le tunnel
|
||||
|
||||
**Configuration Wireguard (côté homelab)** :
|
||||
|
||||
```ini
|
||||
[Interface]
|
||||
Address = 10.0.0.1/24
|
||||
PrivateKey = <private_key>
|
||||
ListenPort = 51820
|
||||
|
||||
[Peer]
|
||||
# Dedibox Scaleway
|
||||
PublicKey = <dedibox_public_key>
|
||||
AllowedIPs = 10.0.0.2/32
|
||||
Endpoint = <dedibox_public_ip>:51820
|
||||
PersistentKeepalive = 25
|
||||
```
|
||||
|
||||
**Nginx Proxy Manager** :
|
||||
- Proxy Host : `gitea.arnodo.fr`
|
||||
- Forward Hostname/IP : `10.0.0.x` (IP du LXC Gitea via Wireguard)
|
||||
- Forward Port : `3000`
|
||||
- SSL : Let's Encrypt automatique
|
||||
- Websockets : Activés
|
||||
|
||||
### Gestion avec Ansible
|
||||
|
||||
Pour maintenir Gitea à jour et gérer les configurations, j'utilise Ansible.
|
||||
|
||||
**Playbook de mise à jour** (`update-gitea.yml`) :
|
||||
|
||||
```yaml
|
||||
---
|
||||
- name: Update Gitea
|
||||
hosts: gitea
|
||||
become: yes
|
||||
tasks:
|
||||
- name: Update apt cache
|
||||
apt:
|
||||
update_cache: yes
|
||||
cache_valid_time: 3600
|
||||
|
||||
- name: Upgrade Gitea and system packages
|
||||
apt:
|
||||
upgrade: dist
|
||||
autoremove: yes
|
||||
autoclean: yes
|
||||
|
||||
- name: Restart Gitea service
|
||||
systemd:
|
||||
name: gitea
|
||||
state: restarted
|
||||
enabled: yes
|
||||
|
||||
- name: Check Gitea version
|
||||
command: gitea --version
|
||||
register: gitea_version
|
||||
|
||||
- debug:
|
||||
msg: "{{ gitea_version.stdout }}"
|
||||
```
|
||||
|
||||
**Exécution** :
|
||||
|
||||
```bash
|
||||
ansible-playbook -i inventory.ini update-gitea.yml
|
||||
```
|
||||
|
||||
### Monitoring avec Grafana
|
||||
|
||||
Gitea expose des métriques Prometheus (https://docs.gitea.com/administration/config-cheat-sheet#metrics-metrics)
|
||||
Configuration :
|
||||
|
||||
**Dans Gitea (`app.ini`)** :
|
||||
|
||||
```ini
|
||||
[metrics]
|
||||
ENABLED = true
|
||||
TOKEN = <secret_token>
|
||||
```
|
||||
|
||||
**Prometheus scrape config** :
|
||||
|
||||
```yaml
|
||||
scrape_configs:
|
||||
- job_name: 'gitea'
|
||||
metrics_path: /metrics
|
||||
bearer_token: '<secret_token>'
|
||||
static_configs:
|
||||
- targets: ['<gitea_lxc_ip>:3000']
|
||||
```
|
||||
|
||||
**Dashboard Grafana** (https://grafana.com/docs/grafana-cloud/monitor-infrastructure/integrations/integration-reference/integration-gitea/#gitea-integration-for-grafana-cloud) :
|
||||
- Nombre de repositories, utilisateurs
|
||||
- Requêtes HTTP (rate, latence)
|
||||
- État des runners CI/CD
|
||||
- Utilisation CPU/RAM du LXC
|
||||
|
||||
### Migration du Code depuis GitHub
|
||||
|
||||
Simple et rapide :
|
||||
Utiliser la fonction d'import de Gitea (Settings > New Migration > GitHub) qui migre aussi les issues et releases.
|
||||
|
||||
### CI/CD : Déploiement Hugo vers Scaleway Object Storage
|
||||
|
||||
Mon blog Hugo se déploie automatiquement sur Scaleway Object Storage à chaque push.
|
||||
|
||||
**Installation de Gitea Runner** (https://docs.gitea.com/usage/actions/act-runner)
|
||||
|
||||
Créer l’utilisateur système runner :
|
||||
|
||||
```bash
|
||||
useradd -r -m -d /var/lib/gitea-runner -s /bin/bash gitea-runner
|
||||
```
|
||||
|
||||
Voici un petit script qui peut aider à installer le runner directement dans un LXC:
|
||||
|
||||
```bash
|
||||
sudo apt install -y jq curl tar # si pas déjà
|
||||
|
||||
LATEST=$(curl -s 'https://gitea.com/api/v1/repos/gitea/act_runner/releases' | jq -r '.[0].tag_name')
|
||||
echo "Latest act_runner: $LATEST"
|
||||
|
||||
# construire URL de binaire (nommage used: act_runner-<os>-<arch>)
|
||||
OS=$(uname -s | tr '[:upper:]' '[:lower:]')
|
||||
ARCH=$(uname -m)
|
||||
# Certains serveurs distribuent binaire sans tar; adapter si archive
|
||||
URL="https://gitea.com/gitea/act_runner/releases/download/${LATEST}/act_runner-${LATEST#v}-${OS}-${ARCH}"
|
||||
|
||||
# essayer télécharger binaire
|
||||
curl -fL "$URL" -o /tmp/act_runner || {
|
||||
echo "Téléchargement direct échoué — vérifier le nom exact sur la page release." >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
sudo mv /tmp/act_runner /usr/local/bin/act_runner
|
||||
sudo chmod +x /usr/local/bin/act_runner
|
||||
```
|
||||
|
||||
et pour valider
|
||||
|
||||
```bash
|
||||
/usr/local/bin/act_runner --version
|
||||
```
|
||||
|
||||
>[!NOTE]
|
||||
> Il est important d'enregistrer le runner pour qu'il soit reconnu par Gitea.
|
||||
> Pour plus d'informations sur la configuration du runner, consultez la documentation officielle de Gitea.
|
||||
> https://docs.gitea.io/fr/docs/usage/actions/runner/
|
||||
|
||||
**Workflow Gitea Actions** (`.gitea/workflows/deploy.yml`) :
|
||||
|
||||
```yaml
|
||||
name: Build and Deploy Hugo
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [closed]
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
build_and_deploy:
|
||||
if: github.event.pull_request.merged == true
|
||||
name: Deploy Hugo Website
|
||||
runs-on: self-hosted
|
||||
|
||||
container:
|
||||
image: debian:bookworm-slim
|
||||
|
||||
steps:
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
apt-get update
|
||||
apt-get install -y git curl ca-certificates wget
|
||||
|
||||
- name: Install Hugo
|
||||
run: |
|
||||
wget https://github.com/gohugoio/hugo/releases/download/v0.152.2/hugo_extended_withdeploy_0.152.2_linux-amd64.deb -O /tmp/hugo.deb
|
||||
dpkg -i /tmp/hugo.deb
|
||||
|
||||
- name: Checkout code
|
||||
run: |
|
||||
git clone --recurse-submodules https://gitea.arnodo.fr/Damien/Notebook.git /tmp/workspace
|
||||
cd /tmp/workspace
|
||||
git checkout ${{ github.sha }}
|
||||
|
||||
- name: Build site
|
||||
run: /usr/local/bin/hugo
|
||||
working-directory: /tmp/workspace
|
||||
|
||||
- name: Deploy to Scaleway
|
||||
run: /usr/local/bin/hugo deploy --force --maxDeletes -1
|
||||
working-directory: /tmp/workspace
|
||||
env:
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.SCW_ACCESS_KEY }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.SCW_SECRET_KEY }}
|
||||
|
||||
```
|
||||
|
||||
**Configuration Hugo** (`hugo.yaml`) :
|
||||
|
||||
```yaml
|
||||
deployment:
|
||||
targets:
|
||||
- name: "notebook-arnodo-fr"
|
||||
URL: "s3://notebook-arnodo-fr?endpoint=https://s3.fr-par.scw.cloud®ion=fr-par"
|
||||
```
|
||||
|
||||
**Configuration Scaleway Object Storage** :
|
||||
1. Créer un bucket `notebook-arnodo-fr`
|
||||
2. Activer le mode "Static Website Hosting"
|
||||
3. Générer les credentials API (Access Key + Secret Key)
|
||||
4. Les ajouter comme secrets dans Gitea (Settings > Secrets > Actions)
|
||||
|
||||
**Déploiement** :
|
||||
|
||||
```bash
|
||||
git add .
|
||||
git commit -m "New blog post"
|
||||
git push origin main
|
||||
```
|
||||
|
||||
Le workflow se déclenche automatiquement, Hugo génère le site, et le déploie sur Scaleway Object Storage. Le site est accessible instantanément via le CDN.
|
||||
|
||||
## Partie 2 : Labs Réseau sur Scaleway
|
||||
|
||||
### Le Problème
|
||||
|
||||
ContainerLab avec plusieurs Arista EOS en local, c'est :
|
||||
- **Gourmand** : 4-8 GB RAM par conteneur cEOS
|
||||
- **Local** : Pas d'accès depuis l'extérieur
|
||||
- **Conflits** : Avec d'autres services Docker/K8s
|
||||
|
||||
### La Solution : Instances Scaleway à la Demande
|
||||
|
||||
**Concept** :
|
||||
- Créer une instance Scaleway quand j'ai besoin d'un lab
|
||||
- Installer automatiquement ContainerLab/le VPN via cloud-init
|
||||
- Détruire l'instance après utilisation
|
||||
- Facturation à l'heure (< 1€ pour quelques heures de lab)
|
||||
|
||||
### Script d'Automatisation : Scaleway CLI
|
||||
|
||||
J'ai développé un script Bash qui gère tout le cycle de vie d'une instance de lab.
|
||||
|
||||
**Fonctionnalités** :
|
||||
- **Création** : Instance + Security Group (SSH depuis mon IP uniquement)
|
||||
- **Start/Stop** : Gestion de l'instance
|
||||
- **Suppression** : Nettoyage complet (instance, volumes, IP, SG)
|
||||
|
||||
**Structure du script** (`scaleway-instance.sh`) :
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# Configuration
|
||||
INSTANCE_NAME="NetLab"
|
||||
ZONE="fr-par-1"
|
||||
IMAGE="debian_bookworm"
|
||||
VOLUME_SIZE=20 # GB
|
||||
USER_DATA_FILE="user_data.txt"
|
||||
SECURITY_GROUP_NAME="${INSTANCE_NAME}-SG"
|
||||
|
||||
# Détecte l'IP publique actuelle
|
||||
get_public_ip() {
|
||||
curl -4 -s ifconfig.me
|
||||
}
|
||||
|
||||
# Crée un Security Group limitant SSH à l'IP publique
|
||||
create_or_update_security_group() {
|
||||
PUBLIC_IP=$(get_public_ip)
|
||||
# Crée le SG avec inbound SSH uniquement depuis PUBLIC_IP/32
|
||||
# ...
|
||||
}
|
||||
|
||||
# Actions : create, start, stop, delete
|
||||
case "$1" in
|
||||
start)
|
||||
# Démarre l'instance existante
|
||||
scw instance server start "$INSTANCE_ID" --wait
|
||||
;;
|
||||
stop)
|
||||
# Arrête l'instance
|
||||
scw instance server stop "$INSTANCE_ID" --wait
|
||||
;;
|
||||
delete)
|
||||
# Supprime instance + volumes + IP + SG
|
||||
scw instance server terminate "$INSTANCE_ID" --with-ip --with-block
|
||||
scw instance security-group delete "$SG_ID"
|
||||
;;
|
||||
*)
|
||||
# Crée une nouvelle instance
|
||||
create_instance "$1" # Type d'instance (DEV1-S, GP1-XS, ...)
|
||||
;;
|
||||
esac
|
||||
```
|
||||
|
||||
### Cloud-Init : Configuration Automatique
|
||||
|
||||
Le fichier `user_data.txt` contient les instructions cloud-init pour provisionner l'instance automatiquement.
|
||||
|
||||
**Exemple** (`user_data.txt`) :
|
||||
|
||||
```yaml
|
||||
#cloud-config
|
||||
package_update: true
|
||||
package_upgrade: true
|
||||
|
||||
packages:
|
||||
- git
|
||||
- curl
|
||||
- docker.io
|
||||
- docker-compose
|
||||
|
||||
runcmd:
|
||||
# Installation de ContainerLab
|
||||
- bash -c "$(curl -sL https://get.containerlab.dev)"
|
||||
|
||||
# Clone d'un repo avec des topologies
|
||||
- git clone https://gitea.arnodo.fr/Damien/network-labs.git /root/labs
|
||||
|
||||
# Démarrage d'une topologie par défaut
|
||||
- cd /root/labs && containerlab deploy -t spine-leaf.clab.yml
|
||||
```
|
||||
|
||||
### Utilisation Pratique
|
||||
|
||||
**Créer un lab** :
|
||||
|
||||
```bash
|
||||
# Crée une instance DEV1-S avec 20 GB de stockage
|
||||
./scaleway-instance.sh DEV1-S 20
|
||||
|
||||
# Attend quelques minutes pour cloud-init
|
||||
# Récupère l'IP publique
|
||||
scw instance server list name=NetLab -o json | jq -r '.servers[0].public_ip.address'
|
||||
|
||||
# SSH vers l'instance
|
||||
ssh root@<IP_PUBLIQUE>
|
||||
|
||||
# ContainerLab est déjà lancé !
|
||||
containerlab inspect
|
||||
```
|
||||
|
||||
**Détruire le lab** :
|
||||
|
||||
```bash
|
||||
./scaleway-instance.sh delete
|
||||
```
|
||||
|
||||
### Intégration avec Raycast
|
||||
|
||||
Pour simplifier encore plus, j'ai créé un script Raycast qui me permet de gérer mes instances directement depuis mon Mac.
|
||||
|
||||
**Script Raycast** :
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# @raycast.schemaVersion 1
|
||||
# @raycast.title Scaleway Instance
|
||||
# @raycast.mode silent
|
||||
# @raycast.icon 🖥️
|
||||
# @raycast.argument1 { "type": "text", "placeholder": "Action or instance type" }
|
||||
# @raycast.argument2 { "type": "text", "placeholder": "Volume size", "optional": true }
|
||||
# @raycast.packageName NetLab
|
||||
|
||||
/path/to/scaleway-instance.sh "$1" "$2"
|
||||
```
|
||||
|
||||
**Utilisation** :
|
||||
- `⌘ + Space` → "Scaleway Instance DEV1-S" → Crée l'instance
|
||||
- `⌘ + Space` → "Scaleway Instance delete" → Supprime l'instance
|
||||
|
||||
### Cas d'Usage : Lab BGP/EVPN avec Arista
|
||||
|
||||
**Topologie ContainerLab** (`spine-leaf.clab.yml`) :
|
||||
|
||||
```yaml
|
||||
name: evpn-lab
|
||||
|
||||
topology:
|
||||
nodes:
|
||||
spine1:
|
||||
kind: ceos
|
||||
image: ceos:latest
|
||||
spine2:
|
||||
kind: ceos
|
||||
image: ceos:latest
|
||||
leaf1:
|
||||
kind: ceos
|
||||
image: ceos:latest
|
||||
leaf2:
|
||||
kind: ceos
|
||||
image: ceos:latest
|
||||
|
||||
links:
|
||||
- endpoints: ["spine1:eth1", "leaf1:eth1"]
|
||||
- endpoints: ["spine1:eth2", "leaf2:eth1"]
|
||||
- endpoints: ["spine2:eth1", "leaf1:eth2"]
|
||||
- endpoints: ["spine2:eth2", "leaf2:eth2"]
|
||||
```
|
||||
|
||||
**Workflow** :
|
||||
1. Créer l'instance Scaleway
|
||||
2. Cloud-init déploie la topologie
|
||||
3. Configurer BGP/EVPN via Ansible ou manuellement
|
||||
4. Tester, expérimenter
|
||||
5. Détruire l'instance
|
||||
|
||||
**Coût** : Instance DEV1-S (2 vCPU, 2GB) = ~0.015€/heure. 4 heures de lab = 0.06€.
|
||||
|
||||
## Souveraineté Numérique : Pourquoi C'est Important
|
||||
|
||||
Cette infrastructure hybride reflète une conviction personnelle sur la souveraineté numérique.
|
||||
|
||||
### Le Contexte
|
||||
|
||||
Dans mon travail d'ingénieur réseau, je vois l'importance de la maîtrise de ses infrastructures.
|
||||
|
||||
Choisir Scaleway (groupe Iliad, français) et self-hoster Gitea, c'est :
|
||||
- **Soutenir l'écosystème tech européen**
|
||||
- **Garantir la protection RGPD** : Juridiction française
|
||||
- **Réduire la latence** : Datacenters à Paris
|
||||
- **Comprendre** : Maîtriser sa chaîne complète
|
||||
|
||||
### Apprentissage par la Pratique
|
||||
|
||||
En tant que professionnel du réseau (Arista, BGP/EVPN, automation), self-hoster me permet de :
|
||||
- Appliquer les principes Infrastructure as Code
|
||||
- Comprendre en profondeur les mécanismes CI/CD
|
||||
- Expérimenter sans limite
|
||||
- Reproduire des environnements professionnels
|
||||
|
||||
## Bilan
|
||||
|
||||
### Ce qui Fonctionne Bien
|
||||
|
||||
**Gitea auto-hébergé** :
|
||||
- Très rapide et stable
|
||||
- Proxmox Helper Scripts = installation en 5 minutes
|
||||
- Ansible gère les mises à jour proprement
|
||||
- Grafana surveille tout
|
||||
|
||||
**Wireguard + Nginx Proxy Manager** :
|
||||
- Exposition sécurisée du homelab
|
||||
- Performances excellentes
|
||||
- Configuration simple
|
||||
|
||||
**Labs Scaleway** :
|
||||
- Provisionnement en 3 minutes
|
||||
- Flexibilité totale (taille, durée)
|
||||
- Coûts prévisibles (facturation à l'heure)
|
||||
|
||||
**CI/CD Hugo → Scaleway Object Storage** :
|
||||
- Push to deploy en 2 minutes
|
||||
- Gratuit (quelques centimes/mois pour le stockage)
|
||||
- CDN intégré = site ultra rapide
|
||||
|
||||
### Les Défis
|
||||
|
||||
**Complexité initiale** :
|
||||
- Wireguard + reverse proxy = courbe d'apprentissage
|
||||
- Première configuration Proxmox/LXC = quelques heures
|
||||
|
||||
**Maintenance** :
|
||||
- Responsabilité des mises à jour (heureusement Ansible aide !)
|
||||
- Monitoring à configurer soi-même
|
||||
- Sauvegardes à automatiser
|
||||
|
||||
**Dépendances** :
|
||||
- Si la Dedibox tombe, Gitea n'est plus accessible
|
||||
- Solution : Failover avec une 2e Dedibox ou VPS (à venir)
|
||||
|
||||
## Prochaines Étapes
|
||||
|
||||
- **Haute disponibilité** : Seconde Dedibox pour du failover
|
||||
- **Backup automatique** : Scripts pour sauvegarder Gitea vers Scaleway Object Storage
|
||||
- **Plus d'automatisation** : Terraform pour provisionner toute l'infra Scaleway
|
||||
- **MCP Arista** : Développer un serveur MCP pour interagir avec les équipements réseau via LLM Locaux
|
||||
- **Intégration Netbox** : Webhook depuis Netbox vers pipeline de validation réseau
|
||||
|
||||
## Conclusion
|
||||
|
||||
Cette infrastructure hybride (homelab Proxmox + cloud Scaleway) offre le meilleur des deux mondes :
|
||||
|
||||
- **Contrôle** : Données sensibles (code, configurations) dans le homelab
|
||||
- **Flexibilité** : Ressources cloud pour les besoins ponctuels
|
||||
- **Apprentissage** : Environnement complet pour expérimenter
|
||||
- **Souveraineté** : Tout hébergé en France, chez des acteurs européens
|
||||
|
||||
Le self-hosting n'est pas qu'une question de coûts (spoiler : je paie autant qu'avant, voire plus), mais d'apprentissage, de maîtrise et de compréhension profonde des systèmes.
|
||||
|
||||
Pour un ingénieur réseau ou DevOps, c'est l'environnement idéal pour reproduire des cas d'usage professionnels et monter en compétences.
|
||||
|
||||
## Ressources
|
||||
|
||||
### Documentation
|
||||
- [Gitea](https://docs.gitea.io/)
|
||||
- [Proxmox Helper Scripts](https://community-scripts.github.io/ProxmoxVE/)
|
||||
- [Scaleway CLI](https://www.scaleway.com/en/cli/)
|
||||
- [ContainerLab](https://containerlab.dev/)
|
||||
- [Nginx Proxy Manager](https://nginxproxymanager.com/)
|
||||
|
||||
### Mes Repos
|
||||
- [Blog Hugo](https://gitea.arnodo.fr/Damien/blog)
|
||||
- [Network Labs](https://gitea.arnodo.fr/Damien/arista-evpn-vxlan-clab) (topologies ContainerLab)
|
||||
- [Scaleway Scripts](https://gitea.arnodo.fr/Damien/scaleway-automation)
|
||||
|
||||
### Communauté
|
||||
- [Proxmox Forum](https://forum.proxmox.com/)
|
||||
- [r/homelab](https://reddit.com/r/homelab)
|
||||
- [ContainerLab Slack](https://containerlab.dev/)
|
||||
@@ -1,5 +1,7 @@
|
||||
---
|
||||
title: "VXLAN"
|
||||
sidebar:
|
||||
open: true
|
||||
cascade:
|
||||
type: docs
|
||||
---
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
---
|
||||
title: "Sécurité"
|
||||
sidebar:
|
||||
open: true
|
||||
cascade:
|
||||
type: docs
|
||||
---
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
---
|
||||
title: "Automatisation réseau"
|
||||
sidebar:
|
||||
open: true
|
||||
cascade:
|
||||
type: docs
|
||||
---
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
title: "Mon Premier Lab"
|
||||
date: 2025-02-14T12:00:00+02:00
|
||||
weight: 2
|
||||
sidebar:
|
||||
open: true
|
||||
cascade:
|
||||
type: docs
|
||||
---
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
---
|
||||
title: "Sécurité"
|
||||
sidebar:
|
||||
open: true
|
||||
cascade:
|
||||
type: docs
|
||||
---
|
||||
|
||||
20
hugo.yaml
20
hugo.yaml
@@ -13,15 +13,18 @@ menu:
|
||||
- name: Netlab
|
||||
pageRef: /netlab
|
||||
weight: 2
|
||||
- name: Blog
|
||||
pageRef: /blog
|
||||
weight: 3
|
||||
- name: About
|
||||
pageRef: /about
|
||||
weight: 3
|
||||
- name: Search
|
||||
weight: 4
|
||||
- name: Search
|
||||
weight: 5
|
||||
params:
|
||||
type: search
|
||||
- name: Gitea
|
||||
weight: 5
|
||||
weight: 6
|
||||
url: "https://gitea.arnodo.fr/Damien"
|
||||
params:
|
||||
icon: gitea
|
||||
@@ -29,7 +32,7 @@ menu:
|
||||
params:
|
||||
theme:
|
||||
default: light
|
||||
displayToggle: false
|
||||
displayToggle: true
|
||||
# Navbar
|
||||
navbar:
|
||||
displayTitle: true
|
||||
@@ -46,6 +49,15 @@ params:
|
||||
flexsearch:
|
||||
# index page by: content | summary | heading | title
|
||||
index: heading
|
||||
# Blog
|
||||
blog:
|
||||
list:
|
||||
displayTags: true
|
||||
sortBy: date
|
||||
sortOrder: desc
|
||||
pagerSize: 10
|
||||
article:
|
||||
displayPagination: true
|
||||
|
||||
markup:
|
||||
goldmark:
|
||||
|
||||
@@ -1,64 +1,188 @@
|
||||
{{- $speed := .Get "speed" | default "30s" -}}
|
||||
{{- $gap := .Get "gap" | default "4rem" -}}
|
||||
{{- $height := .Get "height" | default "80px" -}}
|
||||
{{- $speed := .Get "speed" | default "30s" -}} {{- $gap := .Get "gap" | default
|
||||
"4rem" -}} {{- $height := .Get "height" | default "80px" -}}
|
||||
|
||||
<div class="tech-banner-wrapper" style="overflow: hidden; width: 100%; position: relative; margin: 2rem 0;">
|
||||
<!-- Gradient overlays for smooth fade effect -->
|
||||
<div style="position: absolute; top: 0; left: 0; width: 150px; height: 100%; background: linear-gradient(to right, var(--color-bg), transparent); z-index: 2; pointer-events: none;"></div>
|
||||
<div style="position: absolute; top: 0; right: 0; width: 150px; height: 100%; background: linear-gradient(to left, var(--color-bg), transparent); z-index: 2; pointer-events: none;"></div>
|
||||
<div
|
||||
class="tech-banner-wrapper"
|
||||
style="overflow: hidden; width: 100%; position: relative; margin: 2rem 0"
|
||||
>
|
||||
<!-- Gradient overlays for smooth fade effect -->
|
||||
<div
|
||||
style="
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 150px;
|
||||
height: 100%;
|
||||
background: linear-gradient(to right, var(--color-bg), transparent);
|
||||
z-index: 2;
|
||||
pointer-events: none;
|
||||
"
|
||||
></div>
|
||||
<div
|
||||
style="
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 150px;
|
||||
height: 100%;
|
||||
background: linear-gradient(to left, var(--color-bg), transparent);
|
||||
z-index: 2;
|
||||
pointer-events: none;
|
||||
"
|
||||
></div>
|
||||
|
||||
<!-- Scrolling container -->
|
||||
<div class="tech-banner-scroll" style="display: flex; gap: {{ $gap }}; animation: scroll-left {{ $speed }} linear infinite; will-change: transform;">
|
||||
<!-- First set of logos -->
|
||||
<div style="display: flex; gap: {{ $gap }}; align-items: center; flex-shrink: 0;">
|
||||
<img src="/images/tech-banner/arista-color.svg" alt="Arista" style="height: {{ $height }}; width: auto; filter: grayscale(100%); opacity: 0.7; transition: all 0.3s ease;" onmouseover="this.style.filter='grayscale(0%)'; this.style.opacity='1';" onmouseout="this.style.filter='grayscale(100%)'; this.style.opacity='0.7';" />
|
||||
<img src="/images/tech-banner/aws-color.svg" alt="AWS" style="height: {{ $height }}; width: auto; filter: grayscale(100%); opacity: 0.7; transition: all 0.3s ease;" onmouseover="this.style.filter='grayscale(0%)'; this.style.opacity='1';" onmouseout="this.style.filter='grayscale(100%)'; this.style.opacity='0.7';" />
|
||||
<img src="/images/tech-banner/containerlab-color.svg" alt="ContainerLab" style="height: {{ $height }}; width: auto; filter: grayscale(100%); opacity: 0.7; transition: all 0.3s ease;" onmouseover="this.style.filter='grayscale(0%)'; this.style.opacity='1';" onmouseout="this.style.filter='grayscale(100%)'; this.style.opacity='0.7';" />
|
||||
<img src="/images/tech-banner/k8s-color.svg" alt="Kubernetes" style="height: {{ $height }}; width: auto; filter: grayscale(100%); opacity: 0.7; transition: all 0.3s ease;" onmouseover="this.style.filter='grayscale(0%)'; this.style.opacity='1';" onmouseout="this.style.filter='grayscale(100%)'; this.style.opacity='0.7';" />
|
||||
<img src="/images/tech-banner/nokia-color.svg" alt="Nokia" style="height: {{ $height }}; width: auto; filter: grayscale(100%); opacity: 0.7; transition: all 0.3s ease;" onmouseover="this.style.filter='grayscale(0%)'; this.style.opacity='1';" onmouseout="this.style.filter='grayscale(100%)'; this.style.opacity='0.7';" />
|
||||
<img src="/images/tech-banner/scaleway-color.svg" alt="Scaleway" style="height: {{ $height }}; width: auto; filter: grayscale(100%); opacity: 0.7; transition: all 0.3s ease;" onmouseover="this.style.filter='grayscale(0%)'; this.style.opacity='1';" onmouseout="this.style.filter='grayscale(100%)'; this.style.opacity='0.7';" />
|
||||
<!-- Scrolling container -->
|
||||
<div
|
||||
class="tech-banner-scroll"
|
||||
style="display: flex; gap: {{ $gap }}; will-change: transform;"
|
||||
>
|
||||
<!-- First set of logos -->
|
||||
<div
|
||||
class="tech-banner-track"
|
||||
style="display: flex; gap: {{ $gap }}; align-items: center; flex-shrink: 0; animation: scroll-left {{ $speed }} linear infinite;"
|
||||
>
|
||||
<img
|
||||
src="/images/tech-banner/arista-color.svg"
|
||||
alt="Arista"
|
||||
style="height: {{ $height }}; width: auto; filter: grayscale(100%); opacity: 0.7; transition: all 0.3s ease;"
|
||||
onmouseover="this.style.filter='grayscale(0%)'; this.style.opacity='1';"
|
||||
onmouseout="this.style.filter='grayscale(100%)'; this.style.opacity='0.7';"
|
||||
/>
|
||||
<img
|
||||
src="/images/tech-banner/aws-color.svg"
|
||||
alt="AWS"
|
||||
style="height: {{ $height }}; width: auto; filter: grayscale(100%); opacity: 0.7; transition: all 0.3s ease;"
|
||||
onmouseover="this.style.filter='grayscale(0%)'; this.style.opacity='1';"
|
||||
onmouseout="this.style.filter='grayscale(100%)'; this.style.opacity='0.7';"
|
||||
/>
|
||||
<img
|
||||
src="/images/tech-banner/containerlab-color.svg"
|
||||
alt="ContainerLab"
|
||||
style="height: {{ $height }}; width: auto; filter: grayscale(100%); opacity: 0.7; transition: all 0.3s ease;"
|
||||
onmouseover="this.style.filter='grayscale(0%)'; this.style.opacity='1';"
|
||||
onmouseout="this.style.filter='grayscale(100%)'; this.style.opacity='0.7';"
|
||||
/>
|
||||
<img
|
||||
src="/images/tech-banner/k8s-color.svg"
|
||||
alt="Kubernetes"
|
||||
style="height: {{ $height }}; width: auto; filter: grayscale(100%); opacity: 0.7; transition: all 0.3s ease;"
|
||||
onmouseover="this.style.filter='grayscale(0%)'; this.style.opacity='1';"
|
||||
onmouseout="this.style.filter='grayscale(100%)'; this.style.opacity='0.7';"
|
||||
/>
|
||||
<img
|
||||
src="/images/tech-banner/nokia-color.svg"
|
||||
alt="Nokia"
|
||||
style="height: {{ $height }}; width: auto; filter: grayscale(100%); opacity: 0.7; transition: all 0.3s ease;"
|
||||
onmouseover="this.style.filter='grayscale(0%)'; this.style.opacity='1';"
|
||||
onmouseout="this.style.filter='grayscale(100%)'; this.style.opacity='0.7';"
|
||||
/>
|
||||
<img
|
||||
src="/images/tech-banner/proxmox-color.svg"
|
||||
alt="Proxmox"
|
||||
style="height: {{ $height }}; width: auto; filter: grayscale(100%); opacity: 0.7; transition: all 0.3s ease;"
|
||||
onmouseover="this.style.filter='grayscale(0%)'; this.style.opacity='1';"
|
||||
onmouseout="this.style.filter='grayscale(100%)'; this.style.opacity='0.7';"
|
||||
/>
|
||||
<img
|
||||
src="/images/tech-banner/scaleway-color.svg"
|
||||
alt="Scaleway"
|
||||
style="height: {{ $height }}; width: 200px; object-fit: contain; filter: grayscale(100%); opacity: 0.7; transition: all 0.3s ease;"
|
||||
onmouseover="this.style.filter='grayscale(0%)'; this.style.opacity='1';"
|
||||
onmouseout="this.style.filter='grayscale(100%)'; this.style.opacity='0.7';"
|
||||
/>
|
||||
</div>
|
||||
<!-- Duplicate set for seamless loop -->
|
||||
<div
|
||||
class="tech-banner-track"
|
||||
style="display: flex; gap: {{ $gap }}; align-items: center; flex-shrink: 0; animation: scroll-left {{ $speed }} linear infinite;"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<img
|
||||
src="/images/tech-banner/arista-color.svg"
|
||||
alt="Arista"
|
||||
style="height: {{ $height }}; width: auto; filter: grayscale(100%); opacity: 0.7; transition: all 0.3s ease;"
|
||||
onmouseover="this.style.filter='grayscale(0%)'; this.style.opacity='1';"
|
||||
onmouseout="this.style.filter='grayscale(100%)'; this.style.opacity='0.7';"
|
||||
/>
|
||||
<img
|
||||
src="/images/tech-banner/aws-color.svg"
|
||||
alt="AWS"
|
||||
style="height: {{ $height }}; width: auto; filter: grayscale(100%); opacity: 0.7; transition: all 0.3s ease;"
|
||||
onmouseover="this.style.filter='grayscale(0%)'; this.style.opacity='1';"
|
||||
onmouseout="this.style.filter='grayscale(100%)'; this.style.opacity='0.7';"
|
||||
/>
|
||||
<img
|
||||
src="/images/tech-banner/containerlab-color.svg"
|
||||
alt="ContainerLab"
|
||||
style="height: {{ $height }}; width: auto; filter: grayscale(100%); opacity: 0.7; transition: all 0.3s ease;"
|
||||
onmouseover="this.style.filter='grayscale(0%)'; this.style.opacity='1';"
|
||||
onmouseout="this.style.filter='grayscale(100%)'; this.style.opacity='0.7';"
|
||||
/>
|
||||
<img
|
||||
src="/images/tech-banner/k8s-color.svg"
|
||||
alt="Kubernetes"
|
||||
style="height: {{ $height }}; width: auto; filter: grayscale(100%); opacity: 0.7; transition: all 0.3s ease;"
|
||||
onmouseover="this.style.filter='grayscale(0%)'; this.style.opacity='1';"
|
||||
onmouseout="this.style.filter='grayscale(100%)'; this.style.opacity='0.7';"
|
||||
/>
|
||||
<img
|
||||
src="/images/tech-banner/nokia-color.svg"
|
||||
alt="Nokia"
|
||||
style="height: {{ $height }}; width: auto; filter: grayscale(100%); opacity: 0.7; transition: all 0.3s ease;"
|
||||
onmouseover="this.style.filter='grayscale(0%)'; this.style.opacity='1';"
|
||||
onmouseout="this.style.filter='grayscale(100%)'; this.style.opacity='0.7';"
|
||||
/>
|
||||
<img
|
||||
src="/images/tech-banner/proxmox-color.svg"
|
||||
alt="Proxmox"
|
||||
style="height: {{ $height }}; width: auto; filter: grayscale(100%); opacity: 0.7; transition: all 0.3s ease;"
|
||||
onmouseover="this.style.filter='grayscale(0%)'; this.style.opacity='1';"
|
||||
onmouseout="this.style.filter='grayscale(100%)'; this.style.opacity='0.7';"
|
||||
/>
|
||||
<img
|
||||
src="/images/tech-banner/scaleway-color.svg"
|
||||
alt="Scaleway"
|
||||
style="height: {{ $height }}; width: 200px; object-fit: contain; filter: grayscale(100%); opacity: 0.7; transition: all 0.3s ease;"
|
||||
onmouseover="this.style.filter='grayscale(0%)'; this.style.opacity='1';"
|
||||
onmouseout="this.style.filter='grayscale(100%)'; this.style.opacity='0.7';"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Duplicate set for seamless loop -->
|
||||
<div style="display: flex; gap: {{ $gap }}; align-items: center; flex-shrink: 0;">
|
||||
<img src="/images/tech-banner/arista-color.svg" alt="Arista" style="height: {{ $height }}; width: auto; filter: grayscale(100%); opacity: 0.7; transition: all 0.3s ease;" onmouseover="this.style.filter='grayscale(0%)'; this.style.opacity='1';" onmouseout="this.style.filter='grayscale(100%)'; this.style.opacity='0.7';" />
|
||||
<img src="/images/tech-banner/aws-color.svg" alt="AWS" style="height: {{ $height }}; width: auto; filter: grayscale(100%); opacity: 0.7; transition: all 0.3s ease;" onmouseover="this.style.filter='grayscale(0%)'; this.style.opacity='1';" onmouseout="this.style.filter='grayscale(100%)'; this.style.opacity='0.7';" />
|
||||
<img src="/images/tech-banner/containerlab-color.svg" alt="ContainerLab" style="height: {{ $height }}; width: auto; filter: grayscale(100%); opacity: 0.7; transition: all 0.3s ease;" onmouseover="this.style.filter='grayscale(0%)'; this.style.opacity='1';" onmouseout="this.style.filter='grayscale(100%)'; this.style.opacity='0.7';" />
|
||||
<img src="/images/tech-banner/k8s-color.svg" alt="Kubernetes" style="height: {{ $height }}; width: auto; filter: grayscale(100%); opacity: 0.7; transition: all 0.3s ease;" onmouseover="this.style.filter='grayscale(0%)'; this.style.opacity='1';" onmouseout="this.style.filter='grayscale(100%)'; this.style.opacity='0.7';" />
|
||||
<img src="/images/tech-banner/nokia-color.svg" alt="Nokia" style="height: {{ $height }}; width: auto; filter: grayscale(100%); opacity: 0.7; transition: all 0.3s ease;" onmouseover="this.style.filter='grayscale(0%)'; this.style.opacity='1';" onmouseout="this.style.filter='grayscale(100%)'; this.style.opacity='0.7';" />
|
||||
<img src="/images/tech-banner/scaleway-color.svg" alt="Scaleway" style="height: {{ $height }}; width: auto; filter: grayscale(100%); opacity: 0.7; transition: all 0.3s ease;" onmouseover="this.style.filter='grayscale(0%)'; this.style.opacity='1';" onmouseout="this.style.filter='grayscale(100%)'; this.style.opacity='0.7';" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
:root {
|
||||
--color-bg: #ffffff;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--color-bg: #111111;
|
||||
--color-bg: #ffffff;
|
||||
}
|
||||
}
|
||||
|
||||
[data-theme="dark"] {
|
||||
--color-bg: #111111;
|
||||
}
|
||||
|
||||
[data-theme="light"] {
|
||||
--color-bg: #ffffff;
|
||||
}
|
||||
|
||||
@keyframes scroll-left {
|
||||
0% {
|
||||
transform: translateX(0);
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--color-bg: #111111;
|
||||
}
|
||||
}
|
||||
100% {
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
}
|
||||
|
||||
.tech-banner-scroll:hover {
|
||||
animation-play-state: paused;
|
||||
}
|
||||
[data-theme="dark"] {
|
||||
--color-bg: #111111;
|
||||
}
|
||||
|
||||
[data-theme="light"] {
|
||||
--color-bg: #ffffff;
|
||||
}
|
||||
|
||||
@keyframes scroll-left {
|
||||
0% {
|
||||
transform: translateX(0);
|
||||
}
|
||||
100% {
|
||||
transform: translateX(calc(-100% - {{$gap}}));
|
||||
}
|
||||
}
|
||||
|
||||
.tech-banner-track:hover {
|
||||
animation-play-state: paused;
|
||||
}
|
||||
|
||||
.tech-banner-scroll:hover .tech-banner-track {
|
||||
animation-play-state: paused;
|
||||
}
|
||||
</style>
|
||||
|
||||
BIN
static/images/blog.png
Normal file
BIN
static/images/blog.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 49 KiB |
27
static/images/tech-banner/proxmox-color.svg
Normal file
27
static/images/tech-banner/proxmox-color.svg
Normal file
@@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) by Marsupilami -->
|
||||
<svg
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
id="svg4060"
|
||||
version="1.1"
|
||||
width="1024"
|
||||
height="162"
|
||||
viewBox="-1.8685548 -1.8685548 418.4656296 66.0222696">
|
||||
<defs
|
||||
id="defs4057" />
|
||||
<path
|
||||
id="path55"
|
||||
d="M 19.35157,0 C 17.87755,0 16.59295,0.26384 15.34571,0.79297 14.13626,1.3221 13.0777,2.0395 12.13282,2.98438 L 35.98243,29.13867 59.79297,2.98438 C 58.84809,2.0395 57.78895,1.3221 56.50391,0.79297 55.33226,0.26384 53.97265,0 52.57422,0 51.10021,0 49.70233,0.30221 48.45508,0.86914 47.17004,1.43607 46.11159,2.26732 45.12891,3.25 L 35.98243,13.37891 26.72266,3.25 C 25.77778,2.26732 24.71932,1.43607 23.39649,0.86914 22.22484,0.30221 20.82558,0 19.35157,0 Z M 35.98243,33.10742 12.13282,59.30078 c 0.94488,0.90709 2.00344,1.66228 3.21289,2.19141 1.24724,0.52913 2.53321,0.79297 3.93164,0.79297 1.5496,0 2.87189,-0.34001 4.11914,-0.86914 1.32283,-0.60473 2.45551,-1.39818 3.40039,-2.38086 l 9.18555,-10.12891 9.18359,10.12891 c 0.94488,0.98268 2.00402,1.77613 3.28906,2.38086 1.24725,0.52913 2.60733,0.86914 4.11914,0.86914 1.39843,0 2.75804,-0.26384 3.92969,-0.79297 1.28504,-0.52913 2.34418,-1.28432 3.28906,-2.19141 z"
|
||||
style="fill:#000000;fill-rule:evenodd;stroke:none;stroke-width:1.06665826;stroke-linejoin:round" />
|
||||
<path
|
||||
id="path69"
|
||||
d="M 7.86133,7.86133 C 6.34952,7.89913 4.87557,8.20065 3.55274,8.76758 2.19211,9.33451 1.02048,10.12865 0,11.11133 L 18.21875,31.14258 0,51.13672 c 1.02048,1.02047 2.19211,1.81325 3.55274,2.41797 1.32283,0.60472 2.79678,0.86972 4.30859,0.94531 1.6252,-0.0756 3.13869,-0.33921 4.53711,-1.01953 1.39843,-0.64252 2.60648,-1.51331 3.62695,-2.60937 L 33.97852,31.14258 16.02539,11.45117 C 14.92933,10.39291 13.7578,9.52407 12.35938,8.84375 10.96095,8.20123 9.48653,7.89913 7.86133,7.86133 Z m 56.08985,0 c -1.6252,0.0378 -3.06252,0.3399 -4.46094,0.98242 -1.39843,0.68032 -2.60775,1.54916 -3.66602,2.60742 L 37.94727,31.14258 55.82422,50.8711 c 1.05827,1.09606 2.26759,1.96685 3.66602,2.60937 1.39842,0.68032 2.83574,0.94394 4.46094,1.01953 1.62519,-0.0756 3.02286,-0.34059 4.3457,-0.94531 1.43622,-0.60472 2.53226,-1.3975 3.55273,-2.41797 L 53.66993,31.14258 71.84961,11.11133 C 70.82914,10.12865 69.7331,9.33451 68.29688,8.76758 66.97404,8.20065 65.57637,7.89913 63.95118,7.86133 Z m 161.30858,3.40039 c -2.57007,0 -4.98798,1.05829 -6.72656,2.79688 l 15.60938,17.04687 -15.60938,17.19531 c 1.73858,1.73859 4.15649,2.83594 6.72656,2.83594 2.83465,0 5.29267,-1.17205 7.03125,-3.09961 l 8.61719,-9.52539 8.61719,9.52539 c 1.73858,1.92756 4.19534,3.09961 6.99219,3.09961 2.60787,0 4.98798,-1.09735 6.72656,-2.83594 L 247.63476,31.10547 263.24414,14.0586 c -1.73858,-1.73859 -4.11869,-2.79688 -6.72656,-2.79688 -2.79685,0 -5.25361,1.17274 -6.99219,3.0625 l -8.61719,9.44727 -8.69336,-9.44727 c -1.81417,-1.92756 -4.12043,-3.0625 -6.95508,-3.0625 z m 151.56055,0 c -2.68346,0 -5.06552,1.05829 -6.72851,2.79688 l 15.53515,17.04687 -15.53515,17.19531 c 1.66299,1.73859 4.04505,2.83594 6.72851,2.83594 2.72126,0 5.17743,-1.17205 6.91602,-3.09961 l 8.61718,-9.52539 8.6543,9.52539 c 1.66299,1.92756 4.12112,3.09961 6.91797,3.09961 2.60787,0 5.06415,-1.09735 6.80273,-2.83594 L 399.08203,31.10547 414.72851,14.0586 c -1.73858,-1.73859 -4.19486,-2.79688 -6.80273,-2.79688 -2.79685,0 -5.25498,1.17274 -6.91797,3.0625 l -8.6543,9.44727 -8.69336,-9.44727 c -1.70078,-1.92756 -4.04299,-3.0625 -6.83984,-3.0625 z"
|
||||
style="fill:#e57000;fill-rule:evenodd;stroke:none;stroke-width:1.06665826;stroke-linejoin:round" />
|
||||
<path
|
||||
id="path83"
|
||||
d="m 85.49414,11.1875 c -2.00314,0 -3.74218,1.6615 -3.74218,3.74024 v 36.20898 c 5.44252,0 9.93946,-4.45972 9.93946,-10.01562 h 20.03319 c 5.51811,0 9.97656,-4.45972 9.97656,-10.01563 v -9.90234 c 0,-5.55591 -4.45845,-10.01563 -9.97656,-10.01563 z m 45.58008,0 c -2.00315,0 -3.66602,1.6615 -3.66602,3.74024 v 36.20898 c 5.48032,0 10.01563,-4.45972 10.01563,-10.01562 v -2.53321 h 11.30078 l 5.7832,8.24024 c 1.88977,2.64567 4.72401,4.30859 8.20117,4.30859 1.663,0 3.2885,-0.37817 4.61133,-1.17187 l -8.08789,-11.45313 c 4.61102,-0.94488 8.08789,-4.98769 8.08789,-9.86328 v -7.44531 c 0,-5.55591 -4.45972,-10.01563 -10.01562,-10.01563 z m 51.85547,0 c -5.51811,0 -9.90235,4.45972 -9.90235,10.01563 V 41.1211 c 0,5.5559 4.38424,10.01562 9.90235,10.01562 h 19.99414 c 5.51811,0 9.97851,-4.45972 9.97851,-10.01562 V 21.20313 c 0,-5.55591 -4.4604,-10.01563 -9.97851,-10.01563 z m 89.76367,0 c -2.15433,0 -3.81641,1.6615 -3.81641,3.74024 v 36.20898 c 5.48032,0 10.01563,-4.45972 10.01563,-10.01562 v -19.125 c 0,-0.49134 0.30221,-0.86914 0.86914,-0.86914 0.26456,0 0.56757,0.26414 0.71875,0.45312 l 11.11133,24.45313 c 0.37795,0.86929 1.32269,1.51171 2.26757,1.51171 0.98268,0 1.85115,-0.60336 2.30469,-1.47265 l 11.07422,-24.49219 c 0.15118,-0.18898 0.41697,-0.45312 0.79492,-0.45312 0.41575,0 0.83008,0.3778 0.83008,0.86914 v 19.125 c 0,5.5559 4.45982,10.01562 9.90234,10.01562 V 14.92774 c 0,-2.07874 -1.66286,-3.74024 -3.66601,-3.74024 h -6.23633 c -4.08189,0 -7.55908,2.45506 -9.14648,6.00781 v -0.15039 l -5.85743,12.88868 -5.82031,-12.88868 v 0.15039 c -1.51181,-3.55275 -5.10239,-6.00781 -9.14648,-6.00781 z m 61.79492,0 c -5.55591,0 -10.01563,4.45972 -10.01563,10.01563 V 41.1211 c 0,5.5559 4.45972,10.01562 10.01563,10.01562 h 19.88086 c 5.5559,0 10.01562,-4.45972 10.01562,-10.01562 V 21.20313 c 0,-5.55591 -4.45972,-10.01563 -10.01562,-10.01563 z M 91.69142,21.20313 h 17.49999 c -1e-5,0 2.5332,-0.002 2.5332,2.45508 v 6.19921 c 0,0 -9.2e-4,2.53321 -2.5332,2.53321 H 91.69142 Z m 45.73241,0 h 17.42382 c 0,0 2.45704,-0.002 2.45704,2.45508 v 3.78125 c 0,0 -3.4e-4,2.41796 -2.45704,2.41796 h -17.42382 z m 48.03906,0 h 14.92773 c 0,0 2.53321,-0.002 2.53321,2.45508 v 14.92968 c 0,0 -9.2e-4,2.53321 -2.53321,2.53321 h -14.92773 c 0,0 -2.5332,-9.2e-4 -2.5332,-2.53321 V 23.65821 c 0,0 9.2e-4,-2.45508 2.5332,-2.45508 z m 151.48242,0 h 14.9668 c 0,0 2.45703,-0.002 2.45703,2.45508 v 14.92968 c 0,0 -3.4e-4,2.53321 -2.45703,2.53321 h -14.9668 c 0,0 -2.45703,-9.2e-4 -2.45703,-2.53321 V 23.65821 c 0,0 3.4e-4,-2.45508 2.45703,-2.45508 z"
|
||||
style="fill:#000000;fill-rule:evenodd;stroke:none;stroke-width:1.06665826;stroke-linejoin:round" />
|
||||
</svg>
|
||||
<!-- version: 20171223, original size: 414.72852 62.28516, border: 3% -->
|
||||
|
After Width: | Height: | Size: 6.4 KiB |
Reference in New Issue
Block a user