Compare commits
No commits in common. "19487527f96539fce8d83c9260d0605b0472b36f" and "4cb9752c239df8689786bfd9daaf234d7e27c046" have entirely different histories.
19487527f9
...
4cb9752c23
@ -12,8 +12,7 @@ steps:
|
||||
# - apt update -y # not needed with custom image
|
||||
# - apt install build-essential patchelf -y # not needed with custom image
|
||||
# - pip install nuitka # not needed with custom image
|
||||
# - python -m nuitka --onefile run.py --include-data-dir=./resources=resources --output-filename="ProxmoxDeploy${DRONE_TAG##v}" # not needed with new system with repo
|
||||
- python -m nuitka --onefile run.py --output-filename="ProxmoxDeploy${DRONE_TAG##v}"
|
||||
- python -m nuitka --onefile run.py --include-data-dir=./resources=resources --output-filename="ProxmoxDeploy${DRONE_TAG##v}"
|
||||
- name: gitea_release
|
||||
image: plugins/gitea-release
|
||||
settings:
|
||||
|
63
README.md
63
README.md
@ -10,70 +10,13 @@ Proxmox Deploy is a little script to manage my HomeLab with JSON file.
|
||||
As my homelab was growing I realised that it was harder and harder to keep everything in sync and up to date.
|
||||
So I decided to create a script to manage my Proxmox homelab.
|
||||
|
||||
# How it works
|
||||
The concept is simple, you have a Git repository with a the following structure:
|
||||
```
|
||||
.
|
||||
├── config.json
|
||||
├── lxc
|
||||
│ ├── <id>
|
||||
│ │ ├── config.json
|
||||
│ │ ├── <your files>
|
||||
│ │ └── <your folders>
|
||||
│ └── <id>
|
||||
│ ├── ...
|
||||
│── qemu
|
||||
│ ├── <id>
|
||||
│ │ ├── config.json
|
||||
│ │ ├── <your files>
|
||||
│ │ └── <your folders>
|
||||
│ └── <id>
|
||||
│ ├── ...
|
||||
│── scripts
|
||||
│ ├── <your scripts>
|
||||
│ └── ...
|
||||
```
|
||||
*See below for more information about the structure and differents files*
|
||||
|
||||
PDJ (Proxmox Deploy JSON) is a program that will read that repository files and execute the necessary commands to create/update your LXC/VM.
|
||||
Now ideally you have some sort of Git actions like Drone/GitHub(/Gitea) Actions to run PDJ automatically when you push a change, which will result in your homelab being updated almost instantly.
|
||||
|
||||
If you don't, no big deal, you'll just have to manually clone and update your repo, then start PDJ *(you could use a crontab with regular intervals)*
|
||||
|
||||
# Usage
|
||||
## Download
|
||||
Download the pre-compiled binaries from the release page or build it yourself.
|
||||
|
||||
## Build it yourself
|
||||
```bash
|
||||
# Build on Debian
|
||||
git clone <url of this repo>
|
||||
cd ProxmoxDeploy
|
||||
apt update && apt install -y build-essential patchelf
|
||||
pip install nuitka
|
||||
pip install -r requirements.txt
|
||||
python -m nuitka --onefile run.py --output-filename="ProxmoxDeploy"
|
||||
```
|
||||
*Also see the ``Dockerfile`` and ``.drone.yml`` for more information.*
|
||||
|
||||
## Run it
|
||||
```bash
|
||||
# Run it
|
||||
./ProxmoxDeploy --repo /path/to/repo
|
||||
```
|
||||
# How to use it
|
||||
Have a look at the resources folder to see how to use it.
|
||||
|
||||
|
||||
# Documentation
|
||||
## Configuration
|
||||
### General
|
||||
Before configuring your LXC and VM, you must decide how you'll run this program.
|
||||
As it requires SSH for some actions, you have two options:
|
||||
- Run it directly on the Proxmox VE host **(recommended)** *(refered as `local`)*
|
||||
- No configuration needed
|
||||
- You can use Git(ea)/Drone actions to run it automatically as soon as a change is pushed.
|
||||
- Run it on another machine and connect via SSH
|
||||
- You'll need to setup passwordless SSH connection between your machine and the Proxmox VE host.
|
||||
|
||||
## Configuration
|
||||
### Proxmox VE
|
||||
The Proxmox VE configuration is located in the `config.json` file.
|
||||
```json
|
||||
|
10
resources/config.json
Normal file
10
resources/config.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"pve":{
|
||||
"host": "192.168.11.99",
|
||||
"user": "root",
|
||||
"port": 22,
|
||||
"local": false
|
||||
},
|
||||
"settings": {
|
||||
}
|
||||
}
|
49
resources/lxc/100/config.json
Normal file
49
resources/lxc/100/config.json
Normal file
@ -0,0 +1,49 @@
|
||||
{
|
||||
"lxc_hostname": "traefik",
|
||||
"os": {
|
||||
"name": "alpine",
|
||||
"release": "3.17"
|
||||
},
|
||||
"resources": {
|
||||
"cpu": 2,
|
||||
"memory": 1024,
|
||||
"swap": 256,
|
||||
"disk": 8,
|
||||
"storage": "local-lvm"
|
||||
},
|
||||
"network": {
|
||||
"bridge": "vmbr0",
|
||||
"ipv4": "dhcp",
|
||||
"ipv6": "auto",
|
||||
"mac": "92:A6:71:77:8E:D8",
|
||||
"gateway4": "",
|
||||
"gateway6": "",
|
||||
"vlan": ""
|
||||
},
|
||||
"options": {
|
||||
"privileged": false,
|
||||
"start_on_boot": false,
|
||||
"startup_order": 2,
|
||||
"password": "qwertz1234",
|
||||
"tags": "2-proxy+auth"
|
||||
},
|
||||
"creation": {
|
||||
"conditions": {
|
||||
"programs": ["docker"],
|
||||
"folders": ["/var/data/traefik", "/var/data/config/traefik"],
|
||||
"files": ["/var/data/traefik/traefik.toml", "/var/data/config/traefikv2/docker-compose.yml"]
|
||||
},
|
||||
"steps": [
|
||||
{
|
||||
"type": "script",
|
||||
"local_path": "global/scripts/install-docker.sh"
|
||||
},
|
||||
{
|
||||
"type": "folder_copy",
|
||||
"path": "data/",
|
||||
"destination": "/var/"
|
||||
}
|
||||
]
|
||||
},
|
||||
"deploy": {}
|
||||
}
|
40
resources/lxc/100/data/config/traefikv2/docker-compose.yml
Normal file
40
resources/lxc/100/data/config/traefikv2/docker-compose.yml
Normal file
@ -0,0 +1,40 @@
|
||||
version: "3"
|
||||
|
||||
services:
|
||||
app:
|
||||
image: traefik:v2.9
|
||||
env_file: /var/data/config/traefikv2/traefik.env
|
||||
restart: always
|
||||
ports:
|
||||
- "80:80" # http
|
||||
- "443:443" # https
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
- /var/data/config/traefikv2/dyn:/dyn
|
||||
- /var/data/config/traefikv2/traefik.toml:/etc/traefik/traefik.toml
|
||||
- /var/data/traefik/traefik.log:/traefik.log
|
||||
- /var/data/traefik/access.log:/access.log
|
||||
- /var/data/traefik/acme.json:/acme.json
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.api.rule=Host(`traefik.xyz.abc`)"
|
||||
- "traefik.http.routers.api.entrypoints=https"
|
||||
|
||||
- "traefik.http.routers.api.service=api@internal"
|
||||
- "traefik.http.services.dummy.loadbalancer.server.port=9999"
|
||||
|
||||
- "traefik.http.routers.api.tls=true"
|
||||
- "traefik.http.routers.api.tls.domains[0].main=xyz.abc"
|
||||
- "traefik.http.routers.api.tls.domains[0].sans=*.xyz.abc"
|
||||
- "traefik.http.routers.api.tls.certresolver=cloudflare"
|
||||
networks:
|
||||
- traefik_public
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "2m"
|
||||
max-file: "2"
|
||||
|
||||
networks:
|
||||
traefik_public:
|
||||
external: true
|
3
resources/lxc/100/data/config/traefikv2/traefik.env
Normal file
3
resources/lxc/100/data/config/traefikv2/traefik.env
Normal file
@ -0,0 +1,3 @@
|
||||
# CloudFlare example
|
||||
CLOUDFLARE_EMAIL=me@xyz.abc
|
||||
CLOUDFLARE_API_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
52
resources/lxc/100/data/config/traefikv2/traefik.toml
Normal file
52
resources/lxc/100/data/config/traefikv2/traefik.toml
Normal file
@ -0,0 +1,52 @@
|
||||
[global]
|
||||
checkNewVersion = true
|
||||
|
||||
# Enable the Dashboard
|
||||
[api]
|
||||
dashboard = true
|
||||
|
||||
# Write out Traefik logs
|
||||
[log]
|
||||
level = "INFO"
|
||||
filePath = "/traefik.log"
|
||||
|
||||
# [accessLog]
|
||||
# filePath = "/access.log"
|
||||
|
||||
[entryPoints.http]
|
||||
address = ":80"
|
||||
# Redirect to HTTPS (why wouldn't you?)
|
||||
[entryPoints.http.http.redirections.entryPoint]
|
||||
to = "https"
|
||||
scheme = "https"
|
||||
|
||||
[entryPoints.http.forwardedHeaders]
|
||||
trustedIPs = ["10.0.0.0/8", "172.16.0.0/16", "192.168.0.0/16", "fc00::/7"]
|
||||
|
||||
[entryPoints.https]
|
||||
address = ":443"
|
||||
[entryPoints.https.http.tls]
|
||||
certResolver = "cloudflare"
|
||||
|
||||
[entryPoints.https.forwardedHeaders]
|
||||
trustedIPs = ["10.0.0.0/8", "172.16.0.0/16", "192.168.0.0/16", "fc00::/7"]
|
||||
|
||||
|
||||
# Cloudflare
|
||||
[certificatesResolvers.infomaniak.acme]
|
||||
email = "me@xyz.abc"
|
||||
storage = "acme.json"
|
||||
[certificatesResolvers.infomaniak.acme.dnsChallenge]
|
||||
provider = "cloudflare"
|
||||
resolvers = ["1.1.1.1:53", "8.8.8.8:53"]
|
||||
|
||||
# Docker Traefik provider
|
||||
[providers.docker]
|
||||
endpoint = "unix:///var/run/docker.sock"
|
||||
swarmMode = false
|
||||
watch = true
|
||||
exposedByDefault = false
|
||||
|
||||
[providers.file]
|
||||
directory = "/dyn"
|
||||
watch = true
|
1
resources/lxc/100/data/traefikv2/acme.json
Normal file
1
resources/lxc/100/data/traefikv2/acme.json
Normal file
@ -0,0 +1 @@
|
||||
{}
|
0
resources/lxc/100/data/traefikv2/treafik.log
Normal file
0
resources/lxc/100/data/traefikv2/treafik.log
Normal file
0
resources/qemu/id/0000/config.json
Normal file
0
resources/qemu/id/0000/config.json
Normal file
54
resources/scripts/install-docker.sh
Normal file
54
resources/scripts/install-docker.sh
Normal file
@ -0,0 +1,54 @@
|
||||
#!/bin/bash
|
||||
|
||||
if which docker >/dev/null 2>&1; then
|
||||
echo "Docker is installed"
|
||||
exit 1
|
||||
else
|
||||
echo "Docker is not installed"
|
||||
fi
|
||||
|
||||
if lsb_release -a 2>/dev/null | grep -q -E "Debian"; then
|
||||
echo "Running Debian"
|
||||
|
||||
sudo apt-get remove docker docker-engine docker.io containerd runc -y
|
||||
sudo apt-get update -y
|
||||
sudo apt-get install \
|
||||
ca-certificates \
|
||||
curl \
|
||||
gnupg -y
|
||||
sudo install -m 0755 -d /etc/apt/keyrings
|
||||
curl -fsSL https://download.docker.com/linux/debian/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
|
||||
sudo chmod a+r /etc/apt/keyrings/docker.gpg
|
||||
echo \
|
||||
"deb [arch="$(dpkg --print-architecture)" signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/debian \
|
||||
"$(. /etc/os-release && echo "$VERSION_CODENAME")" stable" |
|
||||
sudo tee /etc/apt/sources.list.d/docker.list >/dev/null
|
||||
sudo apt-get update -y
|
||||
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin -y
|
||||
|
||||
elif lsb_release -a 2>/dev/null | grep -q -E "Ubuntu"; then
|
||||
echo "Running Ubuntu"
|
||||
|
||||
sudo apt-get remove docker docker-engine docker.io containerd runc -y
|
||||
sudo apt-get update -y
|
||||
sudo apt-get install ca-certificates curl gnupg -y
|
||||
sudo install -m 0755 -d /etc/apt/keyrings
|
||||
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
|
||||
sudo chmod a+r /etc/apt/keyrings/docker.gpg
|
||||
echo \
|
||||
"deb [arch="$(dpkg --print-architecture)" signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
|
||||
"$(. /etc/os-release && echo "$VERSION_CODENAME")" stable" |
|
||||
sudo tee /etc/apt/sources.list.d/docker.list >/dev/null
|
||||
sudo apt-get update -y
|
||||
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin -y
|
||||
|
||||
elif cat /etc/os-release 2>/dev/null | grep -q -i "alpine"; then
|
||||
echo "Running Alpine"
|
||||
apk add docker docker-compose
|
||||
addgroup username docker
|
||||
rc-update add docker default
|
||||
service docker start
|
||||
else
|
||||
echo "Unknown distribution"
|
||||
exit 1
|
||||
fi
|
13
run.py
13
run.py
@ -1,18 +1,7 @@
|
||||
import argparse
|
||||
import logging
|
||||
|
||||
from src import main
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("-v", "--verbose", help="increase output verbosity", action="store_true")
|
||||
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.verbose:
|
||||
logging.basicConfig(format='[%(levelname)s] : %(message)s', level=logging.DEBUG)
|
||||
else:
|
||||
logging.basicConfig(format='[%(levelname)s] : %(message)s', level=logging.INFO)
|
||||
|
||||
logging.basicConfig(format='[%(levelname)s] : %(message)s', level=logging.DEBUG)
|
||||
main.run()
|
||||
|
@ -177,7 +177,7 @@ def run_command_on_pve(command: str, return_status_code: bool = False, exception
|
||||
logging.debug(f"Running command on PVE (ssh): {command}")
|
||||
|
||||
# catch errors code
|
||||
command = subprocess.run(f'ssh {username}@{host} -p {port} "{command}"', shell=shell, capture_output=True,
|
||||
command = subprocess.run(f'ssh -i {get_identity_file()} {username}@{host} -p {port} "{command}"', shell=shell, capture_output=True,
|
||||
encoding="utf-8")
|
||||
|
||||
# If return code is not 0 and that exception_on_exit is True and return_status_code is False, throw an exception
|
||||
@ -414,7 +414,7 @@ def copy_file_to_pve(path: Path, destination: str):
|
||||
config = get_config()
|
||||
|
||||
# copy the file to the PVE from the local machine
|
||||
run_command_locally(command=f"scp {str(path)} {config['pve']['user']}@{config['pve']['host']}:{destination}",
|
||||
run_command_locally(command=f"scp -i {get_identity_file()} {str(path)} {config['pve']['user']}@{config['pve']['host']}:{destination}",
|
||||
return_status_code=True)
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user