feature/ssh-server #1
@@ -1,32 +1,41 @@
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- "docker/**"
|
||||
- ".gitea/**"
|
||||
- "docker/ssh-client/**"
|
||||
- ".gitea/workflows/ssh-client**"
|
||||
schedule:
|
||||
- cron: "0 02 * * *"
|
||||
- cron: "0 14 * * 6" #every Saturday at 2:00 PM,
|
||||
|
||||
jobs:
|
||||
|
||||
build-docker-image:
|
||||
ssh-client:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ${{gitea.workspace}}docker/ssh-client
|
||||
steps:
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Log in to git.limbosolutions.com docker registry
|
||||
- name: Log in to git.limbosolutions.com container registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: git.limbosolutions.com
|
||||
username: ${{ secrets.GITLIMBO_DOCKER_REGISTRY_USERNAME }}
|
||||
password: ${{ secrets.GITLIMBO_DOCKER_REGISTRY_PASSWORD }}
|
||||
|
||||
- name: Build and push Docker images
|
||||
- name: Build and push ssh-client Docker images
|
||||
id: push
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
file: ${{gitea.workspace}}/docker/Dockerfile
|
||||
file: ${{gitea.workspace}}/docker/ssh-client/Dockerfile
|
||||
push: true
|
||||
tags: git.limbosolutions.com/kb/ssh-client
|
||||
tags: git.limbosolutions.com/kb/ssh-client
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
35
.gitea/workflows/ssh-server-build-deploy.yaml
Normal file
35
.gitea/workflows/ssh-server-build-deploy.yaml
Normal file
@@ -0,0 +1,35 @@
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- "docker/ssh-server/**"
|
||||
- ".gitea/workflows/ssh-server**"
|
||||
schedule:
|
||||
- cron: "0 14 * * 6" #every Saturday at 2:00 PM,
|
||||
|
||||
jobs:
|
||||
|
||||
ssh-server:
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ${{gitea.workspace}}/docker/ssh-server
|
||||
|
||||
steps:
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Log in to git.limbosolutions.com container registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: git.limbosolutions.com
|
||||
username: ${{ secrets.GITLIMBO_DOCKER_REGISTRY_USERNAME }}
|
||||
password: ${{ secrets.GITLIMBO_DOCKER_REGISTRY_PASSWORD }}
|
||||
|
||||
- name: Build ssh-server images
|
||||
run: BUILD_ENV=prod ./scripts/build.sh
|
||||
|
||||
- name: Push image
|
||||
run: docker push git.limbosolutions.com/kb/ssh-server
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM ubuntu:latest
|
||||
|
||||
RUN apt-get update && apt-get upgrade -y
|
||||
RUN apt-get install -y openssh-client --fix-missing
|
||||
RUN apt-get install -y openssh-client --fix-missing
|
||||
1
docker/ssh-server/.env
Normal file
1
docker/ssh-server/.env
Normal file
@@ -0,0 +1 @@
|
||||
SSH_PORT="2222"
|
||||
5
docker/ssh-server/.env.dev
Normal file
5
docker/ssh-server/.env.dev
Normal file
@@ -0,0 +1,5 @@
|
||||
#DEBUG_FILE="sshserver.py"
|
||||
#DEBUG_FILE="users.py"
|
||||
SSH_SERVER_ENABLED="true"
|
||||
CONTAINER_TAG="ssh-server:dev"
|
||||
|
||||
2
docker/ssh-server/.gitignore
vendored
Normal file
2
docker/ssh-server/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
build.env.local
|
||||
.env.local
|
||||
27
docker/ssh-server/.vscode/launch.json
vendored
Normal file
27
docker/ssh-server/.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
|
||||
{
|
||||
"name": "Python: Remote Attach",
|
||||
"type": "debugpy",
|
||||
"request": "attach",
|
||||
"connect": {
|
||||
"host": "localhost",
|
||||
"port": 5678
|
||||
},
|
||||
"pathMappings": [
|
||||
{
|
||||
"localRoot": "${workspaceFolder}/app",
|
||||
"remoteRoot": "/app"
|
||||
}
|
||||
],
|
||||
"justMyCode": false,
|
||||
"preLaunchTask": "debug"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
26
docker/ssh-server/.vscode/tasks.json
vendored
Normal file
26
docker/ssh-server/.vscode/tasks.json
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "debug",
|
||||
"type": "shell",
|
||||
"command": "RUN_ENV=dev ./scripts/run-dev.sh",
|
||||
"problemMatcher": [],
|
||||
"detail": "Runs the container with environment from .env"
|
||||
},
|
||||
{
|
||||
"label": "build: debug",
|
||||
"type": "shell",
|
||||
"command": "BUILD_ENV=dev ./scripts/build.sh",
|
||||
"problemMatcher": [],
|
||||
"detail": "Builds docker image with debug requirements"
|
||||
},
|
||||
{
|
||||
"label": "build: production",
|
||||
"type": "shell",
|
||||
"command": "./scripts/build.sh",
|
||||
"problemMatcher": [],
|
||||
"detail": "Builds docker image for productions"
|
||||
}
|
||||
]
|
||||
}
|
||||
48
docker/ssh-server/Dockerfile
Normal file
48
docker/ssh-server/Dockerfile
Normal file
@@ -0,0 +1,48 @@
|
||||
# ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
# ┃ 🐳 Dockerfile: Multi-Stage Build for Python + SSH App ┃
|
||||
# ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||
|
||||
# ──────────────── Stage 1: Base ────────────────
|
||||
# Base image using Debian Bullseye
|
||||
FROM debian:bullseye AS base
|
||||
|
||||
|
||||
# Suppress interactive prompts during package installation
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
# Install core dependencies:
|
||||
# - Python 3 and pip
|
||||
# - OpenSSH server for remote access
|
||||
# - sudo for privilege escalation
|
||||
RUN apt-get update && \
|
||||
apt-get install -y \
|
||||
python3 \
|
||||
python3-pip \
|
||||
openssh-server \
|
||||
curl && \
|
||||
mkdir /var/run/sshd && \
|
||||
apt-get clean
|
||||
|
||||
# Create config directory for app (can be used by dev/prod stages)
|
||||
RUN mkdir -p /etc/app/config
|
||||
|
||||
# ──────────────── Stage 2: Prod ────────────────
|
||||
# Production stage inherits from base
|
||||
FROM base AS prod
|
||||
|
||||
# Copy full app code into image (self-contained)
|
||||
COPY app/ /app
|
||||
|
||||
# Set working directory
|
||||
WORKDIR /app
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
|
||||
ENV CONFIGURATION=Production
|
||||
ENV DEBUG=False
|
||||
|
||||
# Default command for production container
|
||||
CMD ["python3", "-u", "/app/main.py"]
|
||||
|
||||
|
||||
#EXPOSE 22
|
||||
76
docker/ssh-server/Dockerfile.dev
Normal file
76
docker/ssh-server/Dockerfile.dev
Normal file
@@ -0,0 +1,76 @@
|
||||
# ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
# ┃ 🐳 Dockerfile: Multi-Stage Build for Python + SSH App /(dev) ┃
|
||||
# ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||
|
||||
# ──────────────── Stage 1: Base ────────────────
|
||||
# Base image using Debian Bullseye
|
||||
FROM debian:bullseye as base
|
||||
|
||||
# Suppress interactive prompts during package installation
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
# Install core dependencies:
|
||||
# - Python 3 and pip
|
||||
# - OpenSSH server for remote access
|
||||
# - sudo for privilege escalation
|
||||
RUN apt-get update && \
|
||||
apt-get install -y \
|
||||
python3 \
|
||||
python3-pip \
|
||||
openssh-server \
|
||||
curl && \
|
||||
mkdir /var/run/sshd && \
|
||||
apt-get clean
|
||||
|
||||
# Create config directory for app (can be used by dev/prod stages)
|
||||
RUN mkdir -p /etc/app/config
|
||||
|
||||
# ──────────────── Stage 2: Dev ────────────────
|
||||
# Development stage inherits from base
|
||||
FROM base AS dev
|
||||
|
||||
|
||||
|
||||
# Install debugging tools (e.g. debugpy for remote debugging)
|
||||
RUN echo "Installing debug tools..." && \
|
||||
pip install debugpy
|
||||
|
||||
|
||||
# Set working directory
|
||||
WORKDIR /app
|
||||
|
||||
# Install Python dependencies from requirements.txt
|
||||
COPY app/requirements.txt requirements.txt
|
||||
|
||||
# This allows caching of dependencies even if app code is mounted
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
EXPOSE 5678
|
||||
|
||||
|
||||
# runtime environment
|
||||
ENV CONFIGURATION=Debug
|
||||
ENV DEBUG=True
|
||||
ENV SSH_SERVER_ENABLED=False
|
||||
|
||||
# Default command for dev container
|
||||
CMD ["python3","-u", "/app/main.py"]
|
||||
|
||||
|
||||
|
||||
# ──────────────── Optional: User & SSH Setup ────────────────
|
||||
# Uncomment below to create a non-root user and configure SSH access
|
||||
|
||||
# Create a non-root user with sudo privileges
|
||||
# RUN useradd -m -s /bin/bash devuser && \
|
||||
# echo "devuser:devpass" | chpasswd && \
|
||||
# usermod -aG sudo devuser
|
||||
|
||||
# Configure SSH server to allow password login and specific users
|
||||
# RUN sed -i 's/#PermitRootLogin prohibit-password/PermitRootLogin no/' /etc/ssh/sshd_config && \
|
||||
# sed -i 's/#PasswordAuthentication yes/PasswordAuthentication yes/' /etc/ssh/sshd_config && \
|
||||
# echo "AllowUsers devuser" >> /etc/ssh/sshd_config
|
||||
|
||||
# Expose SSH port
|
||||
|
||||
|
||||
50
docker/ssh-server/README.md
Normal file
50
docker/ssh-server/README.md
Normal file
@@ -0,0 +1,50 @@
|
||||
# ssh-server
|
||||
|
||||
## config file example
|
||||
|
||||
``` yaml
|
||||
users:
|
||||
- username: xx
|
||||
password: "123456"
|
||||
public_keys: ## array with public keys
|
||||
- "ssh-ed25519 ssdfdsxvxcsxdfrer"
|
||||
uid: 1002
|
||||
server:
|
||||
options:
|
||||
PermitRootLogin: "no"
|
||||
PasswordAuthentication: "no"
|
||||
```
|
||||
|
||||
## Podman
|
||||
|
||||
``` bash
|
||||
podman pull git.limbosolutions.com/kb/ssh-server:latest
|
||||
|
||||
podman container run \
|
||||
-p 2222:22 \
|
||||
-p 5678:5678 \
|
||||
-v ./local/config:/etc/app/config \
|
||||
-v ./local/server-certs:/etc/ssh/certs \
|
||||
-v ./local/home:/home \
|
||||
git.limbosolutions.com/kb/ssh-server:latest
|
||||
|
||||
```
|
||||
|
||||
## docker
|
||||
|
||||
``` bash
|
||||
docker pull git.limbosolutions.com/kb/ssh-server:latest
|
||||
|
||||
docker container run \
|
||||
-p 2222:22 \
|
||||
-p 5678:5678 \
|
||||
-v ./local/config:/etc/app/config \
|
||||
-v ./local/server-certs:/etc/ssh/certs \
|
||||
-v ./local/home:/home \
|
||||
git.limbosolutions.com/kb/ssh-server:latest
|
||||
|
||||
```
|
||||
|
||||
## dev and testing
|
||||
|
||||
Using vscode, check .vscode folder build and debug tasks as launch settings.
|
||||
1
docker/ssh-server/app/.gitignore
vendored
Normal file
1
docker/ssh-server/app/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
__pycache__
|
||||
28
docker/ssh-server/app/globals.py
Normal file
28
docker/ssh-server/app/globals.py
Normal file
@@ -0,0 +1,28 @@
|
||||
import os
|
||||
import yaml
|
||||
def is_debugging(): return os.getenv("CONFIGURATION", "").lower() == "debug"
|
||||
|
||||
|
||||
file_path="/etc/app/config/config.yaml"
|
||||
config=None
|
||||
|
||||
def config_exits():
|
||||
return get_config() is not None
|
||||
|
||||
def sshserver_enabled():
|
||||
return not is_debugging() or os.getenv("SSH_SERVER_ENABLED", "false").lower() == "true"
|
||||
|
||||
def get_config():
|
||||
global config
|
||||
if config == None:
|
||||
load_config()
|
||||
return config
|
||||
|
||||
def load_config():
|
||||
global config
|
||||
if os.path.exists(file_path):
|
||||
with open(file_path, 'r') as f:
|
||||
config = yaml.safe_load(f)
|
||||
else:
|
||||
print(f"⚠️ missing " + file_path)
|
||||
|
||||
41
docker/ssh-server/app/init.py
Normal file
41
docker/ssh-server/app/init.py
Normal file
@@ -0,0 +1,41 @@
|
||||
import os
|
||||
import subprocess
|
||||
import globals
|
||||
import sys
|
||||
|
||||
def main():
|
||||
|
||||
if globals.is_debugging():
|
||||
import debugpy
|
||||
debugpy.listen(("0.0.0.0", 5678))
|
||||
print("INFO: Waiting for debugger to attach...")
|
||||
debugpy.wait_for_client()
|
||||
print("INFO: Debugger attached")
|
||||
else:
|
||||
print("👉 starting setup")
|
||||
|
||||
setup_container()
|
||||
|
||||
if globals.is_debugging():
|
||||
debug_file = os.getenv("DEBUG_FILE", "")
|
||||
if debug_file and os.path.isfile(debug_file):
|
||||
print(f"🔍 Running debug file: {debug_file}")
|
||||
subprocess.run(["python3", debug_file], check=True, stdout=sys.stdout, stderr=sys.stderr)
|
||||
|
||||
return False
|
||||
elif debug_file:
|
||||
print(f"⚠️ DEBUG_FILE set but file not found: {debug_file}")
|
||||
return True
|
||||
|
||||
def setup_container():
|
||||
marker_file = "/var/run/container_initialized"
|
||||
if not os.path.exists(marker_file):
|
||||
print("INFO: First-time setup running...")
|
||||
# 👉 Your one-time setup logic here
|
||||
|
||||
# Create the marker file
|
||||
with open(marker_file, "w") as f:
|
||||
f.write("initialized\n")
|
||||
else:
|
||||
print("TRACE: Already initialized. Skipping setup.")
|
||||
|
||||
14
docker/ssh-server/app/main.py
Normal file
14
docker/ssh-server/app/main.py
Normal file
@@ -0,0 +1,14 @@
|
||||
import globals
|
||||
import subprocess
|
||||
import init
|
||||
import users
|
||||
import sshserver
|
||||
|
||||
if init.main():
|
||||
users.load()
|
||||
sshserver.load()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
1
docker/ssh-server/app/requirements.txt
Normal file
1
docker/ssh-server/app/requirements.txt
Normal file
@@ -0,0 +1 @@
|
||||
PyYAML>=5.4
|
||||
126
docker/ssh-server/app/sshserver.py
Normal file
126
docker/ssh-server/app/sshserver.py
Normal file
@@ -0,0 +1,126 @@
|
||||
import yaml
|
||||
import subprocess
|
||||
import crypt
|
||||
import os
|
||||
import globals
|
||||
import sys
|
||||
|
||||
config_file_path='/etc/ssh/sshd_config'
|
||||
|
||||
def set_sshd_option(file_path: str, key: str, value: str) -> None:
|
||||
updated = False
|
||||
lines = []
|
||||
|
||||
with open(file_path, 'r') as f:
|
||||
for line in f:
|
||||
if line.strip().startswith(key):
|
||||
lines.append(f"{key} {value}\n")
|
||||
updated = True
|
||||
else:
|
||||
lines.append(line)
|
||||
|
||||
if not updated:
|
||||
lines.append(f"{key} {value}\n")
|
||||
|
||||
with open(file_path, 'w') as f:
|
||||
f.writelines(lines)
|
||||
|
||||
print(f"✅ Updated {key} to '{value}' in {file_path}")
|
||||
|
||||
def load():
|
||||
setup()
|
||||
#print_server_config()
|
||||
if globals.sshserver_enabled():
|
||||
start_server()
|
||||
|
||||
|
||||
|
||||
|
||||
def setup_certs():
|
||||
certs=[
|
||||
"/etc/ssh/certs/ssh_host_rsa_key",
|
||||
"/etc/ssh/certs/ssh_host_ecdsa_key",
|
||||
"/etc/ssh/certs/ssh_host_ed25519_key"
|
||||
]
|
||||
if not os.path.exists("/etc/ssh/certs"):
|
||||
os.makedirs("/etc/ssh/certs")
|
||||
print(f"📁 Created folder: /etc/ssh/certs")
|
||||
|
||||
if not os.listdir("/etc/ssh/certs"):
|
||||
subprocess.run([
|
||||
"ssh-keygen", "-t", "rsa", "-f",
|
||||
"/etc/ssh/certs/ssh_host_rsa_key"
|
||||
], check=True, stdout=sys.stdout, stderr=sys.stderr)
|
||||
print(f"✅ RSA key and certificate created:🔑 /etc/ssh/certs/ssh_host_rsa_key")
|
||||
|
||||
subprocess.run([
|
||||
"ssh-keygen", "-t", "ecdsa", "-f",
|
||||
"/etc/ssh/certs/ssh_host_ecdsa_key"
|
||||
], check=True, stdout=sys.stdout, stderr=sys.stderr)
|
||||
print(f"✅ RSA key and certificate created:🔑 /etc/ssh/certs/ssh_host_ecdsa_key")
|
||||
|
||||
subprocess.run([
|
||||
"ssh-keygen", "-t", "ed25519", "-f",
|
||||
"/etc/ssh/certs/ssh_host_ed25519_key"
|
||||
], check=True, stdout=sys.stdout, stderr=sys.stderr)
|
||||
print(f"✅ RSA key and certificate created:🔑 /etc/ssh/certs/ssh_host_ed25519_key")
|
||||
|
||||
certLines=[]
|
||||
for cert in certs:
|
||||
if os.path.exists(cert):
|
||||
certLines.append(f"HostKey {cert}\n")
|
||||
else:
|
||||
print(f"❌ HostKey path not found {cert}")
|
||||
if not certLines: RuntimeError("❌ Missing server certificates configuration. Bind Volume to /etc/ssh/certs")
|
||||
|
||||
lines = []
|
||||
with open(config_file_path, 'r') as f:
|
||||
for line in f:
|
||||
if line.strip().startswith("HostKey"):
|
||||
continue # remove existing HostKey lines
|
||||
lines.append(line)
|
||||
|
||||
for key in certLines:
|
||||
print(f"✅ HostKey path updated to use {key}")
|
||||
lines.append(key)
|
||||
|
||||
with open(config_file_path, 'w') as f:
|
||||
f.writelines(lines)
|
||||
|
||||
|
||||
|
||||
def setup():
|
||||
global config_file_path
|
||||
|
||||
serverConfig = globals.get_config().get("server") if globals.config_exits() else None
|
||||
|
||||
if not serverConfig:
|
||||
return
|
||||
|
||||
optionsConfig = serverConfig.get("options")
|
||||
if optionsConfig:
|
||||
for option in optionsConfig:
|
||||
set_sshd_option(config_file_path, option, optionsConfig[option])
|
||||
setup_certs()
|
||||
|
||||
|
||||
|
||||
def print_server_config():
|
||||
with open(config_file_path, 'r') as f:
|
||||
content = f.read()
|
||||
print(content)
|
||||
|
||||
|
||||
def start_server():
|
||||
print("INFO: Starting ssh server.")
|
||||
serverPort=None
|
||||
serverConfig = globals.get_config().get("server") if globals.config_exits() else None
|
||||
if serverConfig:
|
||||
serverPort = serverConfig.get("port")
|
||||
if serverPort:
|
||||
subprocess.run(["/usr/sbin/sshd", "-D", "-e", "-p", str(serverPort)])
|
||||
else:
|
||||
subprocess.run(["/usr/sbin/sshd", "-D", "-e"])
|
||||
|
||||
if __name__ == "__main__":
|
||||
load()
|
||||
89
docker/ssh-server/app/users.py
Normal file
89
docker/ssh-server/app/users.py
Normal file
@@ -0,0 +1,89 @@
|
||||
import yaml
|
||||
import subprocess
|
||||
import crypt
|
||||
import os
|
||||
import globals
|
||||
import pwd
|
||||
# users:
|
||||
# - username: alice
|
||||
# authorized_keys: publich ssh key
|
||||
# - username: bob
|
||||
# password: hunter2
|
||||
|
||||
|
||||
def user_exists(username):
|
||||
try:
|
||||
pwd.getpwnam(username)
|
||||
return True
|
||||
except KeyError:
|
||||
return False
|
||||
|
||||
def create_user(uid, username ,password, shell="/bin/bash"):
|
||||
|
||||
if not shell: shell = "/bin/bash"
|
||||
|
||||
if not username:
|
||||
return
|
||||
|
||||
useradd_cmd = [
|
||||
'useradd',
|
||||
'-m',
|
||||
'-s', shell,
|
||||
]
|
||||
|
||||
if uid: useradd_cmd.append("-u " + str(uid))
|
||||
if password: useradd_cmd.append("-p" + crypt.crypt(password, crypt.mksalt(crypt.METHOD_SHA512)))
|
||||
useradd_cmd.append(username)
|
||||
|
||||
try:
|
||||
subprocess.run(useradd_cmd
|
||||
, check=True)
|
||||
print(f"✅ User '{username}' created with shell '{shell}' and password.")
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f"❌ Failed to create user '{username}': {e}")
|
||||
|
||||
|
||||
|
||||
|
||||
def setup_ssh(username, public_key):
|
||||
ssh_dir = f"/home/{username}/.ssh"
|
||||
auth_keys = os.path.join(ssh_dir, "authorized_keys")
|
||||
uid = pwd.getpwnam(username).pw_uid
|
||||
gid = pwd.getpwnam(username).pw_gid
|
||||
|
||||
os.makedirs(ssh_dir, mode=0o700, exist_ok=True)
|
||||
|
||||
# Check if key already exists
|
||||
key_exists = False
|
||||
if os.path.exists(auth_keys):
|
||||
with open(auth_keys, "r") as f:
|
||||
existing_keys = f.read().splitlines()
|
||||
key_exists = public_key.strip() in existing_keys
|
||||
|
||||
if not key_exists:
|
||||
with open(auth_keys, "a") as f:
|
||||
f.write(public_key.strip() + "\n")
|
||||
print(f"🔐 SSH key added for '{username}'.")
|
||||
else:
|
||||
print(f"⚠️ SSH key already exists for '{username}'. Skipping.")
|
||||
|
||||
os.chmod(ssh_dir, 0o700)
|
||||
os.chmod(auth_keys, 0o600)
|
||||
os.chown(ssh_dir, uid, gid)
|
||||
os.chown(auth_keys, uid, gid)
|
||||
|
||||
def load():
|
||||
users = globals.get_config()["users"] if globals.config_exits() else None
|
||||
if users:
|
||||
for user in users:
|
||||
if not user_exists(user.get('username')):
|
||||
create_user(user.get('uid'), user.get('username'),user.get('password'), user.get('shell'))
|
||||
if user.get('public_keys'):
|
||||
for public_key in user.get('public_keys'):
|
||||
setup_ssh(user.get('username'), public_key)
|
||||
else:
|
||||
print(f"⚠️ missing users configuration")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
load()
|
||||
2
docker/ssh-server/build.env
Normal file
2
docker/ssh-server/build.env
Normal file
@@ -0,0 +1,2 @@
|
||||
BUILD_ENV_IMAGE_TAG="git.limbosolutions.com/kb/ssh-server:latest"
|
||||
BUILD_CLI="docker"
|
||||
3
docker/ssh-server/build.env.dev
Normal file
3
docker/ssh-server/build.env.dev
Normal file
@@ -0,0 +1,3 @@
|
||||
BUILD_ENV_IMAGE_TAG="ssh-server:dev"
|
||||
BUILD_ENV_DOCKER_FILE="Dockerfile.dev"
|
||||
BUILD_CLI="podman"
|
||||
92
docker/ssh-server/scripts/build.sh
Executable file
92
docker/ssh-server/scripts/build.sh
Executable file
@@ -0,0 +1,92 @@
|
||||
#!/bin/bash
|
||||
# ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
# ┃ 🐳 Podman Build Script with Layered .env Configuration ┃
|
||||
# ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||
#
|
||||
# This script builds a Podman image using a specified Dockerfile,
|
||||
# dynamically injecting build-time environment variables from a
|
||||
# prioritized set of `.env` files.
|
||||
#
|
||||
# ──────────────── Behavior ────────────────
|
||||
# - Loads variables from the following files (in order):
|
||||
# build.env
|
||||
# build.env.${BUILD_ENV}
|
||||
# build.local.env
|
||||
#
|
||||
# - Later files override earlier ones (last-write-wins)
|
||||
# - Variables starting with `BUILD_ENV` are excluded from build args
|
||||
#
|
||||
# ──────────────── Required Environment Variables ────────────────
|
||||
# - BUILD_ENV: (optional) Environment name (e.g. dev, prod)
|
||||
# - BUILD_ENV_DOCKER_FILE: Dockerfile to use (default: Dockerfile)
|
||||
# - BUILD_ENV_IMAGE_TAG: Tag to assign to the built image
|
||||
#
|
||||
# ──────────────── Example Usage ────────────────
|
||||
# BUILD_ENV=dev BUILD_ENV_IMAGE_TAG=myapp:dev ./build.sh
|
||||
# BUILD_ENV=prod BUILD_ENV_DOCKER_FILE=Dockerfile.prod BUILD_ENV_IMAGE_TAG=myapp:prod ./build.sh
|
||||
#
|
||||
# ──────────────── Output ────────────────
|
||||
# - Logs each loaded file
|
||||
# - Displays the final build command
|
||||
# - Executes the podman build with all resolved --build-arg flags
|
||||
#
|
||||
# Author: Márcio
|
||||
# Last Updated: September 2025
|
||||
|
||||
POSSIBLE_BUILD_ARGS_FILES="\
|
||||
build.env \
|
||||
"
|
||||
|
||||
|
||||
if [ -n "${BUILD_ENV+x}" ]; then
|
||||
POSSIBLE_BUILD_ARGS_FILES="$POSSIBLE_BUILD_ARGS_FILES \
|
||||
build.env.${BUILD_ENV} \
|
||||
"
|
||||
fi
|
||||
|
||||
POSSIBLE_BUILD_ARGS_FILES="$POSSIBLE_BUILD_ARGS_FILES \
|
||||
build.env.local"
|
||||
|
||||
|
||||
# load variables into this context
|
||||
declare -A build_args_map
|
||||
BUILD_ARGS=""
|
||||
|
||||
for file in $POSSIBLE_BUILD_ARGS_FILES; do
|
||||
if [ -f "$file" ]; then
|
||||
echo "🔧 Loading variables from $file"
|
||||
set -a
|
||||
source "$file"
|
||||
set +a
|
||||
while IFS= read -r line || [ -n "$line" ]; do
|
||||
# Skip comments and empty lines
|
||||
[[ "$line" =~ ^#.*$ || -z "$line" ]] && continue
|
||||
|
||||
# Extract key and value
|
||||
key="$(echo "$line" | cut -d= -f1 | xargs)"
|
||||
value="$(echo "$line" | cut -d= -f2- | xargs)"
|
||||
|
||||
# Skip keys that start with BUILD_ENV
|
||||
if [[ "$key" == BUILD_ENV* ]]; then
|
||||
echo "Skipping key starting with BUILD_ENV: $key"
|
||||
continue
|
||||
fi
|
||||
build_args_map["$key"]="$value"
|
||||
done < "$file"
|
||||
else
|
||||
echo "⚠️ Skipping missing file: $file"
|
||||
fi
|
||||
done
|
||||
|
||||
for key in "${!build_args_map[@]}"; do
|
||||
BUILD_ARGS+=" --build-arg $key=${build_args_map[$key]}"
|
||||
done
|
||||
# sets default docker file
|
||||
BUILD_ENV_DOCKER_FILE="${BUILD_ENV_DOCKER_FILE:-Dockerfile}"
|
||||
|
||||
echo "Build env: $BUILD_ENV"
|
||||
echo "Build DockerFile: $BUILD_ENV_DOCKER_FILE"
|
||||
echo "Build Tag: $BUILD_ENV_IMAGE_TAG"
|
||||
echo "Running: $BUILD_CLI -f $BUILD_ENV_DOCKER_FILE $BUILD_ARGS -t $BUILD_ENV_IMAGE_TAG ."
|
||||
$BUILD_CLI build -f $BUILD_ENV_DOCKER_FILE $BUILD_ARGS -t $BUILD_ENV_IMAGE_TAG .
|
||||
|
||||
40
docker/ssh-server/scripts/run-dev.sh
Executable file
40
docker/ssh-server/scripts/run-dev.sh
Executable file
@@ -0,0 +1,40 @@
|
||||
#!/bin/bash
|
||||
RUN_ENV=dev
|
||||
|
||||
POSSIBLE_ENV_FILES="\
|
||||
.env
|
||||
"
|
||||
|
||||
if [ -n "${RUN_ENV+x}" ]; then
|
||||
POSSIBLE_ENV_FILES="$POSSIBLE_ENV_FILES \
|
||||
.env.${RUN_ENV} \
|
||||
"
|
||||
fi
|
||||
|
||||
POSSIBLE_ENV_FILES="$POSSIBLE_ENV_FILES \
|
||||
.env.local"
|
||||
|
||||
for file in $POSSIBLE_ENV_FILES; do
|
||||
if [ -f "$file" ]; then
|
||||
echo "🔧 Loading variables from $file"
|
||||
set -a
|
||||
source "$file"
|
||||
set +a
|
||||
else
|
||||
echo "⚠️ Skipping missing file: $file"
|
||||
fi
|
||||
done
|
||||
# Ignored if uing dev docker file:
|
||||
# - DEBUG_FILE
|
||||
# - SSH_SERVER_ENABLED
|
||||
podman container run -d --rm \
|
||||
-e DEBUG_FILE="${DEBUG_FILE}" \
|
||||
-e SSH_SERVER_ENABLED="${SSH_SERVER_ENABLED:-false}" \
|
||||
-p 2222:22 \
|
||||
-p 5678:5678 \
|
||||
-v ./app:/app \
|
||||
-v ./local/home:/home \
|
||||
-v ./local/config:/etc/app/config \
|
||||
-v ./local/server-certs:/etc/ssh/certs \
|
||||
${CONTAINER_TAG}
|
||||
|
||||
Reference in New Issue
Block a user