├── .github ├── ISSUE_TEMPLATE │ ├── bug-report.md │ ├── config.yml │ ├── feature-request.md │ └── help-template.md └── workflows │ ├── closeinactiveissues.yml │ └── dockerhub-readme-sync.yml ├── .gitlab-ci.yml ├── .gitmessage ├── Dockerfile ├── README.md ├── docker-compose.yml ├── set_root_password.sh └── supervisord.conf /.github/ISSUE_TEMPLATE/bug-report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug Report 3 | about: Create a bug report to help us improve 4 | title: "[BUG] < replace with one liner description of the bug >" 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **host server (please complete the following information):** 27 | - OS: [e.g. debian] 28 | - architecture [e.g. x86, raspberry] 29 | - Version [e.g. 22] 30 | 31 | **Additional context** 32 | Add any other context about the problem here. 33 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature Request 3 | about: Suggest an idea for this project 4 | title: "[Feature Request] < replace with one liner description of the feature request 5 | >" 6 | labels: enhancement 7 | assignees: '' 8 | 9 | --- 10 | 11 | **Is your feature request related to a problem? Please describe.** 12 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 13 | 14 | **Describe the solution you'd like** 15 | A clear and concise description of what you want to happen. 16 | 17 | **Describe alternatives you've considered** 18 | A clear and concise description of any alternative solutions or features you've considered. 19 | 20 | **Additional context** 21 | Add any other context or screenshots about the feature request here. 22 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/help-template.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Help Template 3 | about: Please file an issue to ask for help 4 | title: "[Help] < replace with a one liner question >" 5 | labels: help wanted 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | -------------------------------------------------------------------------------- /.github/workflows/closeinactiveissues.yml: -------------------------------------------------------------------------------- 1 | name: Close inactive issues 2 | on: 3 | schedule: 4 | - cron: "8 11 * * *" 5 | 6 | jobs: 7 | close-issues: 8 | runs-on: ubuntu-latest 9 | permissions: 10 | issues: write 11 | pull-requests: write 12 | steps: 13 | - uses: actions/stale@v5 14 | with: 15 | days-before-issue-stale: 30 16 | days-before-issue-close: 14 17 | stale-issue-label: "stale" 18 | stale-issue-message: "This issue is stale because it has been open for 30 days with no activity." 19 | close-issue-message: "This issue was closed because it has been inactive for 14 days since being marked as stale." 20 | days-before-pr-stale: -1 21 | days-before-pr-close: -1 22 | repo-token: ${{ secrets.GITHUB_TOKEN }} 23 | 24 | -------------------------------------------------------------------------------- /.github/workflows/dockerhub-readme-sync.yml: -------------------------------------------------------------------------------- 1 | name: Docker Hub README Sync 2 | 3 | on: 4 | push: 5 | branches: 6 | - master # Trigger on pushes to the main branch 7 | 8 | jobs: 9 | sync-readme: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout code 13 | uses: actions/checkout@v3 14 | 15 | - name: Sync README to Docker Hub 16 | uses: peter-evans/dockerhub-description@v4 17 | with: 18 | username: ${{ secrets.DOCKERHUB_USERNAME }} 19 | password: ${{ secrets.DOCKERHUB_PASSWORD }} 20 | repository: bcleonard/proxmox-qdevice 21 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | default: 2 | # Official docker image. 3 | image: docker:latest 4 | services: 5 | - name: docker:dind 6 | alias: docker 7 | command: ["--tls=false"] 8 | 9 | stages: 10 | - build 11 | - release 12 | 13 | variables: 14 | DOCKER_HOST: tcp://docker:2375/ 15 | DOCKER_DRIVER: overlay2 16 | DOCKER_TLS_CERTDIR: "" 17 | 18 | build: 19 | stage: build 20 | script: 21 | - docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY 22 | - docker build --pull -t "$CI_REGISTRY_IMAGE" . 23 | - docker push "$CI_REGISTRY_IMAGE" 24 | - docker logout 25 | 26 | pushrm: 27 | stage: release 28 | image: 29 | name: chko/docker-pushrm 30 | entrypoint: ["/bin/sh", "-c", "/docker-pushrm"] 31 | variables: 32 | DOCKER_USER: $CI_REGISTRY_USER 33 | DOCKER_PASS: $CI_REGISTRY_PASSWORD 34 | PUSHRM_SHORT: Proxmox QDevice in a Container 35 | PUSHRM_TARGET: docker.io/$CI_REGISTRY_USER/proxmox-qdevice 36 | PUSHRM_DEBUG: 1 37 | PUSHRM_FILE: $CI_PROJECT_DIR/README.md 38 | script: "/bin/true" 39 | -------------------------------------------------------------------------------- /.gitmessage: -------------------------------------------------------------------------------- 1 | #[optional] summary, imperative, all lower case, don't end with a period 2 | # type = build, ci, docs, feat, fix, perf, refactor, style, test, chore 3 | # no more than 50 chars. #### 50 chars is here: # 4 | 5 | # Remember blank line between title and body. 6 | 7 | # [body] Explain *what* and *why* (not *how*) 8 | # Wrap at 72 chars. ############################### 72 chars is here: # 9 | 10 | # Remember blank line between body and footer. 11 | 12 | # [footer] 13 | 14 | # How to Write a Git Commit Message 15 | # https://chris.beams.io/posts/git-commit/ 16 | # 17 | # 1. We are following Conventional Commits https://conventionalcommits.org 18 | # 2. We are following the format of: 19 | # 20 | # [optional scope]: 21 | # 22 | # [optional body] 23 | # 24 | # [optional footer(s)] 25 | # 26 | # 3. The entire message should be lower case 27 | # 4. Everything in < > is required 28 | # 5. Everything in [ ] is optional 29 | # 6. type = build, ci, docs, feat, fix, perf, refactor, style, test, chore 30 | # 7. Limit the subject line to 50 characters 31 | # 8. Do not end the subject line with a period 32 | # 9. Use the imperative mood in the subject line 33 | # 10. Wrap the body at 72 characters 34 | # 11. Use the body to explain what and why vs. how 35 | # 12. Answer the question: Why have I made these changes? 36 | # 13. Answer the question: What effect have my changes made? 37 | # 14. Answer the question: Why was the change needed? 38 | # 15. Answer the question: What are the changes in reference to? 39 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM debian:bookworm-slim 2 | 3 | RUN apt update \ 4 | && apt -y upgrade \ 5 | && apt install --no-install-recommends -y supervisor \ 6 | && apt -y autoremove \ 7 | && apt clean all 8 | 9 | RUN apt update \ 10 | && apt install --no-install-recommends -y openssh-server \ 11 | && apt -y autoremove \ 12 | && apt clean all 13 | 14 | RUN sed -i 's/#PermitRootLogin prohibit-password/PermitRootLogin yes/' /etc/ssh/sshd_config 15 | COPY set_root_password.sh /usr/local/bin/set_root_password.sh 16 | RUN chown root.root /usr/local/bin/set_root_password.sh \ 17 | && chmod 755 /usr/local/bin/set_root_password.sh 18 | 19 | RUN apt update \ 20 | && apt install --no-install-recommends -y corosync-qnetd \ 21 | && apt -y autoremove \ 22 | && apt clean all 23 | 24 | RUN mkdir -p /run/sshd 25 | 26 | COPY supervisord.conf /etc/supervisord.conf 27 | 28 | EXPOSE 22 29 | EXPOSE 5403 30 | 31 | CMD ["/usr/bin/supervisord", "-c", "/etc/supervisord.conf"] 32 | 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Proxmox Qdevice 2 | 3 | This repository will allow you build and deploy a docker container for use with a proxmox cluster as an external qdevice. Properly configured proxmox clusters require an odd number servers in the cluster. In the event that you have an even number of proxmox servers (like 2, such as I have), you need an another device to vote. Proxmox supports this by allow you to configure a qdevice for an external vote. 4 | 5 | Normally running an even number of servers in a cluster isn't a problem, but I've had situations where I've booted both promox servers at the same time. In that case, the first server to come online doesn't have a quarum (1 of 2) so the images won't start. The 2nd server will (2 of 2). With an external qdevice thats already up, the first device to come up has quarom (2 of 3). 6 | 7 | For more information on proxmmox clusters, external qdevices, and how to configure/use them, go [here](https://pve.proxmox.com/wiki/Cluster_Manager#_corosync_external_vote_support). 8 | 9 | Run this container on a device that is *NOT* a virtual instance on one of your proxmox servers. 10 | 11 | ## Wiki 12 | 13 | A wiki has been created [here](https://github.com/bcleonard/proxmox-qdevice/wiki) which contains all kinds of information. 14 | 15 | ## Requirements/Prerequisites 16 | 17 | Please check the [wiki](https://github.com/bcleonard/proxmox-qdevice/wiki#pre-requisites) for the most up to date information. 18 | 19 | ## Install: 20 | 21 | This repo is designed to run from either docker compose or a container manager, like Portainer. 22 | 23 | ## Configuration: 24 | 25 | Modify the docker-compose.yml file. Make sure to change: 26 | 27 | * Environment Variable NEW_ROOT_PASSWORD 28 | * location of your corosync-data (so you can keep your configuration between restarts, etc.) 29 | * hostname 30 | * local network information 31 | parent (the ethernet device to bind macvlan) 32 | ipv4_address 33 | subnet 34 | ip_range 35 | gateway 36 | 37 | ## Running / Deploying: 38 | 39 | You can either run the command: 40 | 41 | `docker compose up -d` 42 | 43 | or cut and past the docker-compose.yml into portainer.io as a stack and then deploy. 44 | 45 | ## Tested as working on: 46 | 47 | * Debian 12 (bookworm) (all point releases up to 12) (Virtual Instance) 48 | * Docker version 24.0.5, build ced0996 (and higher) 49 | * Proxmox v8.0.4 (and higher) 50 | * Portainer Community Edition v2.18.4 (and higher) 51 | 52 | ## Problems & Troubleshooting: 53 | 54 | * You can find the most up to date information on issues, known problems and troubleshooting by reviewing the [issues](https://github.com/bcleonard/proxmox-qdevice/issues) and what is [not supported](https://github.com/bcleonard/proxmox-qdevice/wiki#whats-not-supported). 55 | 56 | ## Security Implications: 57 | 58 | This container installs and configures a sshd server that permits root logins. Proxmox runs in the same configuration. Upon startup, if the environment variable **NEW_ROOT_PASSWORD** exists the root password will be set to the value of that variable upon boot. You can specify what the root password should be setting the value of **NEW_ROOT_PASSWORD** to a password in one of the following ways: 59 | 60 | 1) If you are using a container manager, such as portainer, set the environment variable **NEW_ROOT_PASSWORD** to your specified root password. This variable should get passed to the container. 61 | 2) Follow one of the Docker provided ways documented in how to ["Set environment variables within your container's environment"](https://docs.docker.com/compose/how-tos/environment-variables/set-environment-variables/). Please note that one of the ways described is setting the password in the docker-compose.yml (or the stack) in the environment section (i.e. hardcoding it). If you hardcode the password like this, you can expose the password. You have been warned. 62 | 63 | Please note that all of the ways listed above to set the environment above should survive the recreation of the container. 64 | 65 | An alternative would be to **NOT SET** the password at all and change it after the container has started and is running. Please note that this method will not survive the recreation of the container. This means you have to change the passwor manually every time you upgrade and/or recreate the container. To change the password after the container has started, do the following: 66 | 67 | ```bash 68 | sudo docker exec -it proxmox-qdevice /bin/bash 69 | root@proxmox-qdevice:/# passwd 70 | New password: 71 | Retype new password: 72 | passwd: password updated successfully 73 | root@proxmox-qdevice:/# exit 74 | ``` 75 | 76 | > [!IMPORTANT] 77 | > 78 | > ## A note on `latest` and `beta`: 79 | > 80 | > It is not recommended to use the `latest` (`bcleonard/proxmox-qdevice`, `bcleonard/proxmox-qdevice:latest`) or `beta` (`bcleonard/proxmox-qdevice:beta`) tag for production setups. 81 | > 82 | > [Those tags point](https://hub.docker.com/r/bcleonard/proxmox-qdevice/tags) might not point to the latest commit in the `master` branch. They do not carry any promise of stability, and using them will probably put your proxmox-qdevice setup at risk of experiencing uncontrolled updates to non backward compatible versions (or versions with breaking changes). You should always specify the version you want to use explicitly to ensure your setup doesn't break when the image is updated. 83 | 84 | ## Acknowledgements: 85 | 86 | When I started looking at how to install & configure an external qdevice in a docker container, there was very little information available. All the info I found was relevant to earlier versions of Proxmox ( < 8 ) or didn't work when I tried to deploy the container. However, I did find the following very useful: 87 | 88 | * [Proxmox VE 7 Corosync QDevice in a Docker container](https://raymii.org/s/tutorials/Proxmox_VE_7_Corosync_QDevice_in_Docker.html) 89 | * [Dockerized Corosync QNet Daemon](https://github.com/modelrockettier/docker-corosync-qnetd) 90 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | qnetd: 3 | container_name: proxmox-qdevice 4 | image: bcleonard/proxmox-qdevice:v2.0 5 | build: . 6 | ports: 7 | - "22:22" 8 | - "5403:5403" 9 | environment: 10 | - VAR=example 11 | volumes: 12 | - /run/sshd 13 | - :/etc/corosync 14 | restart: unless-stopped 15 | hostname: 16 | networks: 17 | vlan: 18 | ipv4_address: 19 | 20 | networks: 21 | vlan: 22 | driver: macvlan 23 | driver_opts: 24 | parent: 25 | ipam: 26 | driver: default 27 | config: 28 | - subnet: "/32" 30 | gateway: "" 31 | -------------------------------------------------------------------------------- /set_root_password.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -x 2 | 3 | if [ ! -z ${NEW_ROOT_PASSWORD+x} ] 4 | then 5 | echo "root:${NEW_ROOT_PASSWORD}" | chpasswd 6 | fi 7 | -------------------------------------------------------------------------------- /supervisord.conf: -------------------------------------------------------------------------------- 1 | [supervisord] 2 | nodaemon=true 3 | user=root 4 | 5 | [program:set_root_password] 6 | command=/usr/local/bin/set_root_password.sh 7 | priority=100 8 | autorestart=false 9 | exitcodes=0 10 | startsecs=0 11 | startretries=0 12 | 13 | [program:sshd] 14 | command=/usr/sbin/sshd -D 15 | priority=200 16 | 17 | [program:corosync-qnetd] 18 | command=/usr/bin/corosync-qnetd -f 19 | priority=200 20 | --------------------------------------------------------------------------------