4 Commits

Author SHA1 Message Date
d4a9f2f40a Make the displayed SSH username configurable
This patch allows the SSH username displayed in templates to be
configured through the SSH_USERNAME environment variable.
2021-08-10 00:30:45 +00:00
7b16606b16 Merge pull request 'shell_scripts: Fix reporting of VM IP addresses' (#9) from mirsal/capsul-flask:ipv6 into master
Reviewed-on: 3wordchant/capsul-flask#9
2021-08-04 12:13:44 +02:00
d9f3e68278 shell_scripts: Fix reporting of VM IP addresses
The shell script which gets informations from libvirt incorrectly
matches virsh output lines which contain VMs' public IPv4 addresses.
It doesn't work when there are multiple address families which breaks
reporting of IPv4 address when a VM also has an IPv6 address and virsh
happen to output that one first.

This commit changes it to explicitely match the first ipv4 address instead.
2021-08-04 00:56:55 +00:00
72c04d8495 Docker image, & local development docker-compose.yml (#2)
Adds:
- a Docker image, which can be used in both development and production
- a `docker-compose.yml` file for local development (could probably be adapted for production deployments)

Testing:
- `git checkout docker`
- `docker-compose up`
- pray 🙏
- go to http://localhost:5000

## App architecture

I added the ability to load secret config variables (`HUB_TOKEN`, `STRIPE_SECRET_KEY` etc) from files, to support [Docker Secrets](https://docs.docker.com/engine/swarm/secrets/) natively. The code should probably log an error if a specified `HUB_TOKEN_FILE` doesn't exist instead of failing silently..

## Docker architecture

This uses a multi-stage build to reduce the size of the final image -- having pipenv install to a predefined virtualenv, and then copying that over.

The compose file doesn't include a definition for a cron runner service, and I haven't tested running one yet. Here be dragons!

You can rebuild the image locally using `docker-compose build`, but this isn't required for changes to the app code, only if you edit the `Dockerfile`, or want to publish your image for use on a swarm server (in which case you will need to edit the image name to put in your own Docker hub credentials).

Currently, the image is rebuilt (should set up auto-tagging..) and published with every push to this 3wordchant/capsul-flask fork.

Reviewed-on: 3wordchant/capsul-flask#2
Co-authored-by: 3wordchant <3wordchant@noreply.git.autonomic.zone>
Co-committed-by: 3wordchant <3wordchant@noreply.git.autonomic.zone>
2021-07-22 01:18:10 +02:00
9 changed files with 122 additions and 6 deletions

13
.drone.yml Normal file
View File

@ -0,0 +1,13 @@
---
kind: pipeline
name: publish docker image
steps:
- name: build and publish
image: plugins/docker
settings:
username:
from_secret: docker_reg_username_3wc
password:
from_secret: docker_reg_passwd_3wc
repo: 3wordchant/capsul-flask
tags: ${DRONE_COMMIT_BRANCH}

48
Dockerfile Normal file
View File

@ -0,0 +1,48 @@
FROM python:3.8-alpine as build
RUN apk add --no-cache \
build-base \
gcc \
gettext \
git \
jpeg-dev \
libffi-dev \
libjpeg \
musl-dev \
postgresql-dev \
python3-dev \
zlib-dev
RUN mkdir -p /app/{code,venv}
WORKDIR /app/code
COPY Pipfile Pipfile.lock /app/code/
RUN python3 -m venv /app/venv
RUN pip install pipenv setuptools
ENV PATH="/app/venv/bin:$PATH" VIRTUAL_ENV="/app/venv"
RUN pip install wheel cppy
# Install dependencies into the virtual environment with Pipenv
RUN pipenv install --deploy --verbose
FROM python:3.8-alpine
RUN apk add --no-cache \
cloud-utils \
libjpeg \
libpq \
libstdc++ \
libvirt-client \
openssh-client \
virt-install
COPY . /app/code/
WORKDIR /app/code
COPY --from=build /app/venv /app/venv
ENV PATH="/app/venv/bin:$PATH" VIRTUAL_ENV="/app/venv"
CMD ["gunicorn", "--bind", "0.0.0.0:5000", "-k", "gevent", "--worker-connections", "1000", "app:app"]
VOLUME /app/code
EXPOSE 5000

View File

@ -27,8 +27,24 @@ class StdoutMockFlaskMail:
def send(self, message: Message): def send(self, message: Message):
current_app.logger.info(f"Email would have been sent if configured:\n\nto: {','.join(message.recipients)}\nsubject: {message.subject}\nbody:\n\n{message.body}\n\n") current_app.logger.info(f"Email would have been sent if configured:\n\nto: {','.join(message.recipients)}\nsubject: {message.subject}\nbody:\n\n{message.body}\n\n")
load_dotenv(find_dotenv()) load_dotenv(find_dotenv())
for var_name in [
"SPOKE_HOST_TOKEN", "HUB_TOKEN", "STRIPE_SECRET_KEY",
"BTCPAY_PRIVATE_KEY", "MAIL_PASSWORD"
]:
var = os.environ.get(f"{var_name}_FILE")
if not var:
continue
if not os.path.isfile(var):
continue
with open(var) as secret_file:
os.environ[var_name] = secret_file.read().rstrip('\n')
del os.environ[f"{var_name}_FILE"]
app = Flask(__name__) app = Flask(__name__)
app.config.from_mapping( app.config.from_mapping(
@ -42,6 +58,7 @@ app.config.from_mapping(
LOG_LEVEL=os.environ.get("LOG_LEVEL", default="INFO"), LOG_LEVEL=os.environ.get("LOG_LEVEL", default="INFO"),
SPOKE_HOST_ID=os.environ.get("SPOKE_HOST_ID", default="baikal"), SPOKE_HOST_ID=os.environ.get("SPOKE_HOST_ID", default="baikal"),
SPOKE_HOST_TOKEN=os.environ.get("SPOKE_HOST_TOKEN", default="changeme"), SPOKE_HOST_TOKEN=os.environ.get("SPOKE_HOST_TOKEN", default="changeme"),
SSH_USERNAME=os.environ.get("SSH_USERNAME", default="cyberian"),
HUB_TOKEN=os.environ.get("HUB_TOKEN", default="changeme"), HUB_TOKEN=os.environ.get("HUB_TOKEN", default="changeme"),
# https://www.postgresql.org/docs/9.1/libpq-ssl.html#LIBPQ-SSL-SSLMODE-STATEMENTS # https://www.postgresql.org/docs/9.1/libpq-ssl.html#LIBPQ-SSL-SSLMODE-STATEMENTS

View File

@ -108,6 +108,8 @@ def detail(id):
if vm is None: if vm is None:
return abort(404, f"{id} doesn't exist.") return abort(404, f"{id} doesn't exist.")
vm['ssh_username'] = current_app.config['SSH_USERNAME']
if vm['deleted']: if vm['deleted']:
return render_template("capsul-detail.html", vm=vm, delete=True, deleted=True) return render_template("capsul-detail.html", vm=vm, delete=True, deleted=True)

View File

@ -22,7 +22,7 @@ def pricing():
@bp.route("/faq") @bp.route("/faq")
def faq(): def faq():
return render_template("faq.html") return render_template("faq.html", ssh_username=current_app.config['SSH_USERNAME'])
@bp.route("/about-ssh") @bp.route("/about-ssh")
def about_ssh(): def about_ssh():

View File

@ -30,6 +30,6 @@ if virsh domuuid "$vmname" | grep -vqE '^[\t\s\n]*$'; then
fi fi
# this gets the ipv4 # this gets the ipv4
ipv4="$(virsh domifaddr "$vmname" | awk '/vnet/ {print $4}' | cut -d'/' -f1)" ipv4="$(virsh domifaddr "$vmname" | awk '/ipv4/ {print $4}' | cut -d'/' -f1)"
echo "$exists $state $ipv4" echo "$exists $state $ipv4"

View File

@ -97,7 +97,7 @@
</div> </div>
<div class="row justify-start"> <div class="row justify-start">
<label class="align" for="ssh_username">SSH Username</label> <label class="align" for="ssh_username">SSH Username</label>
<span id="ssh_username">cyberian</span> <span id="ssh_username">{{ vm['ssh_username'] }}</span>
</div> </div>
<div class="row justify-start"> <div class="row justify-start">
<label class="align" for="ssh_authorized_keys">SSH Authorized Keys</label> <label class="align" for="ssh_authorized_keys">SSH Authorized Keys</label>

View File

@ -21,13 +21,13 @@
</li> </li>
<li> <li>
How do I log in? How do I log in?
<p>ssh to the ip provided to you using the cyberian user.</p> <p>ssh to the ip provided to you using the "{{ ssh_username }}" user.</p>
<pre class='code'>$ ssh cyberian@1.2.3.4</pre> <pre class='code'>$ ssh {{ ssh_username }}@1.2.3.4</pre>
<p>For more information, see <a href="/about-ssh">Understanding the Secure Shell Protocol (SSH)</a>.</p> <p>For more information, see <a href="/about-ssh">Understanding the Secure Shell Protocol (SSH)</a>.</p>
</li> </li>
<li> <li>
How do I change to the root user? How do I change to the root user?
<p>The cyberian user has passwordless sudo access by default. This should work:</p> <p>The "{{ ssh_username }}" user has passwordless sudo access by default. This should work:</p>
<pre class='code'> <pre class='code'>
# Linux # Linux
$ sudo su - $ sudo su -

36
docker-compose.yml Normal file
View File

@ -0,0 +1,36 @@
---
version: "3.8"
services:
app:
image: 3wordchant/capsul-flask:latest
build: .
volumes:
- "./:/app/code"
- "../tank:/tank"
# - "/var/run/libvirt/libvirt-sock:/var/run/libvirt/libvirt-sock"
depends_on:
- db
ports:
- "5000:5000"
environment:
- "POSTGRES_CONNECTION_PARAMETERS=host=db port=5432 user=capsul password=capsul dbname=capsul"
- SPOKE_MODEL=shell-scripts
#- FLASK_DEBUG=1
- BASE_URL=http://localhost:5000
- ADMIN_PANEL_ALLOW_EMAIL_ADDRESSES=3wc.capsul@doesthisthing.work
- VIRSH_DEFAULT_CONNECT_URI=qemu:///system
# The image uses gunicorn by default, let's override it with Flask's
# built-in development server
command: ["flask", "run", "-h", "0.0.0.0", "-p", "5000"]
db:
image: "postgres:9.6.5-alpine"
volumes:
- "postgres:/var/lib/postgresql/data"
environment:
POSTGRES_USER: capsul
POSTGRES_PASSWORD: capsul
POSTGRES_DB: capsul
volumes:
postgres: