Compare commits
2 Commits
dd3ea726db
...
cd2b304a5c
| Author | SHA1 | Date | |
|---|---|---|---|
| cd2b304a5c | |||
| 4ccd00db2d |
@ -1,9 +1,22 @@
|
||||
# NAxS Homelab
|
||||
Prerequisites:
|
||||
- Create a default network called homelab
|
||||
## Prerequisites
|
||||
0. Make sure that your user is part of the docker group
|
||||
- `cat /etc/group | grep docker` - if the entry looks like `docker:x:<Group ID>:<username>`, you're good to go
|
||||
- Otherwise please run `sudo usermod -aG docker <username>`, followed by logging out & in again for these changes to take into effect
|
||||
|
||||
1. Create a default network called homelab
|
||||
```
|
||||
docker network create homelab
|
||||
```
|
||||
2. Set up 1Password for access to secrets
|
||||
- Install `pass` & `gpg`
|
||||
- Generate key with `gpg --full-generate-key`
|
||||
- stick to defaults
|
||||
- as password, use `GPG cert password` stored inside the `NAxS Homelab` vault in 1Password
|
||||
- Initialize password storage with `pass init "GPG key ID"`
|
||||
- You can check out the ID by using `gpg --list-secret-keys --keyid-format LONG` - you should see a line with `sec`, containing the following information `<encryption technology>/ID`
|
||||
- Store the 1Password service account token in `pass` as `op-sa_token` by executing `pass insert op-sa_token`
|
||||
- Make sure your .zshrc file loads the token into the `OP_SERVICE_ACCOUNT_TOKEN` (this is needed by the 1Password CLI for authentication purposes when loading the secrets) environment variable by executing `export OP_SERVICE_ACCOUNT_TOKEN="$(pass op-sa_token)"`
|
||||
|
||||
// TODO: Create template script
|
||||
Template script which helps with setting up new applications (asks for potential secrets needs, adds default network to compose file, creates new users/groups to run containers rootless)
|
||||
@ -0,0 +1,180 @@
|
||||
#!/bin/zsh
|
||||
while [[ "$#" -gt 0 ]]
|
||||
do
|
||||
case $1 in
|
||||
--app_name) app_name="$2"
|
||||
shift;;
|
||||
*) echo "Unknown parameter passed: $1"
|
||||
exit 1;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
# Make sure app_name exists and is not empty
|
||||
if [[ -z "$app_name" ]]; then
|
||||
echo "app_name is unset or empty."
|
||||
exit 1;
|
||||
fi
|
||||
|
||||
# Define the base directory for the new application structure
|
||||
# This will be one level up from where this script is executed.
|
||||
app_base_dir="../${app_name}"
|
||||
app_data_dir="${app_base_dir}/data"
|
||||
compose_file="${app_base_dir}/compose.yaml"
|
||||
readme_file="${app_base_dir}/README.md"
|
||||
start_script="${app_base_dir}/start.sh"
|
||||
|
||||
echo "--- Setting up files and directories for application: ${app_name} ---"
|
||||
echo "Base directory: ${app_base_dir}"
|
||||
|
||||
# 1. Create the application base directory and data directory
|
||||
echo "Creating directory: ${app_data_dir}"
|
||||
mkdir -p "${app_data_dir}" || { echo "Error: Failed to create directory ${app_data_dir}"; exit 1; }
|
||||
|
||||
# 2. Create compose.yaml and README.md
|
||||
echo "Creating file: ${compose_file}"
|
||||
touch "${compose_file}" || { echo "Error: Failed to create file ${compose_file}"; exit 1; }
|
||||
|
||||
echo "Creating file: ${readme_file}"
|
||||
touch "${readme_file}" || { echo "Error: Failed to create file ${readme_file}"; exit 1; }
|
||||
|
||||
# 3. Prepopulate README.md with title case app name
|
||||
# Convert app_name to title case (first letter of each word capitalized)
|
||||
# This is a simple conversion; for more robust title casing, a function would be better.
|
||||
app_name_title_case=$(echo "$app_name" | awk '{for(i=1;i<=NF;i++){ $i=toupper(substr($i,1,1)) tolower(substr($i,2)) }}1')
|
||||
|
||||
echo "Prepopulating ${readme_file}..."
|
||||
cat > "${readme_file}" << EOF
|
||||
# ${app_name_title_case}
|
||||
|
||||
This is the README file for the **${app_name}** application.
|
||||
|
||||
## Overview
|
||||
Provide a brief description of your application here.
|
||||
|
||||
## Setup
|
||||
Instructions for setting up the application.
|
||||
|
||||
EOF
|
||||
|
||||
# 4. Ask the user if secrets will be required
|
||||
read "requires_secrets?Will this application require secrets (yes/no)? "
|
||||
requires_secrets=$(echo "$requires_secrets" | tr '[:upper:]' '[:lower:]') # Convert to lowercase
|
||||
|
||||
# 5. Prepopulate compose.yaml based on user selection re. secrets
|
||||
echo "Prepopulating ${compose_file} based on secrets requirement..."
|
||||
if [[ "$requires_secrets" == "yes" || "$requires_secrets" == "y" ]]; then
|
||||
cat > "${compose_file}" << EOF
|
||||
version: '3.8'
|
||||
services:
|
||||
${app_name}:
|
||||
image: your-app-image:latest
|
||||
container_name: ${app_name}-container
|
||||
ports:
|
||||
- "8080:8080"
|
||||
volumes:
|
||||
- ./data:/app/data
|
||||
environment:
|
||||
# Example of how to pass secrets as environment variables
|
||||
# These would typically be loaded from a .env file or a secrets management system
|
||||
- DATABASE_URL=\${DATABASE_URL}
|
||||
- API_KEY=\${API_KEY}
|
||||
# Example of secrets usage with Docker Compose secrets (requires Docker Swarm or specific setup)
|
||||
# secrets:
|
||||
# - my_database_password
|
||||
# - my_api_key
|
||||
# deploy:
|
||||
# resources:
|
||||
# limits:
|
||||
# cpus: '0.5'
|
||||
# memory: 512M
|
||||
secrets:
|
||||
my_database_password:
|
||||
external: true # Assumes secret is created externally (e.g., docker secret create)
|
||||
my_api_key:
|
||||
external: true
|
||||
EOF
|
||||
else
|
||||
cat > "${compose_file}" << EOF
|
||||
services:
|
||||
${app_name}:
|
||||
image: your-app-image:latest
|
||||
container_name: ${app_name}-container
|
||||
ports:
|
||||
- "8080:8080"
|
||||
volumes:
|
||||
- ./data:/app/data
|
||||
# No secrets configuration needed for this version
|
||||
# deploy:
|
||||
# resources:
|
||||
# limits:
|
||||
# cpus: '0.5'
|
||||
# memory: 512M
|
||||
EOF
|
||||
fi
|
||||
|
||||
# 6. If secrets will be used - touch ../"app_name"/start.sh and prepopulate it
|
||||
if [[ "$requires_secrets" == "yes" || "$requires_secrets" == "y" ]]; then
|
||||
echo "Creating and prepopulating ${start_script}..."
|
||||
cat > "${start_script}" << EOF
|
||||
#!/bin/zsh
|
||||
|
||||
# Example start script for an application using secrets
|
||||
# This script assumes you have a .env file or similar mechanism
|
||||
# for loading secrets into the environment before starting Docker Compose.
|
||||
|
||||
# --- IMPORTANT: Replace with your actual secret loading mechanism ---
|
||||
# Example 1: Load from a .env file
|
||||
# if [[ -f ".env" ]]; then
|
||||
# echo "Loading environment variables from .env"
|
||||
# source .env
|
||||
# else
|
||||
# echo "Warning: .env file not found. Secrets might be missing."
|
||||
# fi
|
||||
|
||||
# Example 2: Using a secrets management tool (e.g., HashiCorp Vault, AWS Secrets Manager)
|
||||
# echo "Fetching secrets from Vault..."
|
||||
# export DATABASE_URL=\$(vault read -field=value secret/myapp/database_url)
|
||||
# export API_KEY=\$(vault read -field=value secret/myapp/api_key)
|
||||
# -------------------------------------------------------------------
|
||||
|
||||
echo "Starting Docker Compose services for ${app_name}..."
|
||||
docker compose -f "${compose_file}" up -d
|
||||
|
||||
echo "Application ${app_name} started. Check logs with: docker compose logs -f ${app_name}"
|
||||
EOF
|
||||
chmod +x "${start_script}" # Make the start script executable
|
||||
fi
|
||||
|
||||
# 7. If secrets will be used, append another chapter at the end of the README.md file
|
||||
if [[ "$requires_secrets" == "yes" || "$requires_secrets" == "y" ]]; then
|
||||
echo "Appending 'Secrets Management' chapter to ${readme_file}..."
|
||||
cat >> "${readme_file}" << EOF
|
||||
|
||||
## Secrets Management
|
||||
|
||||
This application utilizes secrets for sensitive configurations (e.g., database credentials, API keys).
|
||||
|
||||
### Configuration
|
||||
Secrets are expected to be provided via environment variables or Docker Compose secrets.
|
||||
Refer to the \`start.sh\` script for an example of how to load these secrets before application startup.
|
||||
|
||||
**Example Environment Variables (to be set in your environment or a \`.env\` file):**
|
||||
\`\`\`
|
||||
DATABASE_URL="postgres://user:password@host:port/database"
|
||||
API_KEY="your_super_secret_api_key"
|
||||
\`\`\`
|
||||
|
||||
**For Docker Compose secrets:**
|
||||
Ensure the necessary Docker secrets are created on your Docker host or Swarm cluster.
|
||||
\`\`\`bash
|
||||
docker secret create my_database_password <(echo "your_db_password")
|
||||
docker secret create my_api_key <(echo "your_api_key")
|
||||
\`\`\`
|
||||
Then, update the \`compose.yaml\` to reference these secrets.
|
||||
|
||||
EOF
|
||||
fi
|
||||
|
||||
echo "--- Setup complete for ${app_name} ---"
|
||||
echo "Check the '${app_base_dir}' directory for your new files."
|
||||
80
homelab/applications/app-template-generator/create-user.sh
Normal file
80
homelab/applications/app-template-generator/create-user.sh
Normal file
@ -0,0 +1,80 @@
|
||||
#!/bin/zsh
|
||||
while [[ "$#" -gt 0 ]]
|
||||
do
|
||||
case $1 in
|
||||
--app_name) app_name="$2"
|
||||
shift;;
|
||||
--id) desired_id="$2"
|
||||
shift;;
|
||||
*) echo "Unknown parameter passed: $1"
|
||||
exit 1;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
# Validate desired_id is a number
|
||||
if ! [[ "$desired_id" =~ ^[0-9]+$ ]]; then
|
||||
echo "Error: Invalid UID/GID. Please enter a numeric value."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
user_name="${app_name}-user"
|
||||
group_name="${app_name}-group"
|
||||
|
||||
echo "--- Checking/Creating User and Group for ${app_name} ---"
|
||||
|
||||
# --- Handle Group ---
|
||||
echo "Checking group: ${group_name}"
|
||||
existing_gid=$(getent group "${group_name}" | cut -d: -f3)
|
||||
|
||||
if [[ -n "$existing_gid" ]]; then
|
||||
if [[ "$existing_gid" -eq "$desired_id" ]]; then
|
||||
echo "Group '${group_name}' already exists with the correct GID (${desired_id})."
|
||||
else
|
||||
echo "Group '${group_name}' exists with GID ${existing_gid}, but desired GID is ${desired_id}."
|
||||
echo "Attempting to modify group GID..."
|
||||
if sudo groupmod -g "$desired_id" "${group_name}"; then
|
||||
echo "Successfully adjusted group '${group_name}' to GID ${desired_id}."
|
||||
else
|
||||
echo "Failed to adjust group '${group_name}' GID. Please check permissions or try manually."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
else
|
||||
echo "Group '${group_name}' does not exist. Creating..."
|
||||
if sudo groupadd -g "$desired_id" "${group_name}"; then
|
||||
echo "Successfully created group '${group_name}' with GID ${desired_id}."
|
||||
else
|
||||
echo "Failed to create group '${group_name}'. Please check permissions or try manually."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# --- Handle User ---
|
||||
echo "Checking user: ${user_name}"
|
||||
existing_uid=$(getent passwd "${user_name}" | cut -d: -f3)
|
||||
|
||||
if [[ -n "$existing_uid" ]]; then
|
||||
if [[ "$existing_uid" -eq "$desired_id" ]]; then
|
||||
echo "User '${user_name}' already exists with the correct UID (${desired_id})."
|
||||
else
|
||||
echo "User '${user_name}' exists with UID ${existing_uid}, but desired UID is ${desired_id}."
|
||||
echo "Attempting to modify user UID..."
|
||||
if sudo usermod -u "$desired_id" -g "$desired_id" "${user_name}"; then
|
||||
echo "Successfully adjusted user '${user_name}' to UID ${desired_id} and primary GID ${desired_id}."
|
||||
else
|
||||
echo "Failed to adjust user '${user_name}' UID/GID. Please check permissions or try manually."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
else
|
||||
echo "User '${user_name}' does not exist. Creating..."
|
||||
if sudo useradd -u "$desired_id" -g "$desired_id" -s /sbin/nologin -c "Application User for ${app_name}" "${user_name}"; then
|
||||
echo "Successfully created user '${user_name}' with UID ${desired_id} and primary GID ${desired_id}."
|
||||
else
|
||||
echo "Failed to create user '${user_name}'. Please check permissions or try manually."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "--- Operation complete for ${app_name} ---"
|
||||
8
homelab/applications/app-template-generator/main.sh
Normal file
8
homelab/applications/app-template-generator/main.sh
Normal file
@ -0,0 +1,8 @@
|
||||
#!/bin/zsh
|
||||
# Ask for the application name
|
||||
read "app_name?Enter the name of the application: "
|
||||
# Ask for the desired UID/GID
|
||||
read "desired_id?Enter the desired UID/GID for the application (e.g., 1001): "
|
||||
|
||||
./create-user.sh --app_name ${app_name} --id ${desired_id}
|
||||
./create-files+directories.sh --app_name ${app_name}
|
||||
@ -1,34 +1,28 @@
|
||||
// TODO: refactor for gitea
|
||||
# Gitea
|
||||
## Set up database
|
||||
- Create database called `gitea`
|
||||
- Create database user called `gitea` incl. password
|
||||
- Create database called `gitea` in Postgres
|
||||
- Define new user as owner of the database
|
||||
- Store database credentials in 1Password
|
||||
|
||||
## Set up non-root user for container
|
||||
We are providing a non-root user to the container to limit the attack surface for privilege escalations. In order for this to work in our setup, please make sure to check if you have a user called `gitea-user` & group called `gitea-group` set up.
|
||||
|
||||
1. Check if user `postgres` exists and if the UID is 1002
|
||||
1. Check if user `gitea-user` & group `gitea-group` exist and if the UID/GID is 1003
|
||||
|
||||
```
|
||||
cat /etc/passwd | grep gitea
|
||||
cat /etc/passwd | grep gitea-user
|
||||
cat /etc/group | grep gitea-group
|
||||
```
|
||||
|
||||
In case the `postgres` user exists but the UID is not 1002, please adjust it via
|
||||
In case user/group exists but the UID/GID is not 1003, please adjust it via
|
||||
```
|
||||
sudo usermod -u 1002 postgres
|
||||
sudo usermod -u 1003 gitea-user
|
||||
sudo groupmod -g 1003 gitea-group
|
||||
```
|
||||
|
||||
In case the `postgres` user doesn't exist at all, please create the user incl. the right UID by running
|
||||
In case user/group doesn't exist at all, please create the user & group incl. the right UID/GID by running
|
||||
```
|
||||
sudo useradd -u 1002 postgres
|
||||
```
|
||||
|
||||
## About secrets
|
||||
In order to manage secrets centrally in 1Password and due to the need for secrets in Postgres, using `docker compose` directly in the terminal does not work.
|
||||
|
||||
## Bring up/tear down container
|
||||
Please use the `start.sh` to spin up the container
|
||||
### Prerequisites start.sh
|
||||
- User executing the script is part of the `docker` group
|
||||
- Env variable `OP_SERVICE_ACCOUNT_TOKEN` is set up \[check out top-level README.md for more information on how to set this up\]
|
||||
sudo groupadd -g 1003 gitea-group
|
||||
sudo useradd -g gitea-group -u 1003 gitea-user
|
||||
```
|
||||
@ -1,6 +1,3 @@
|
||||
secrets:
|
||||
gitea_postgres_password:
|
||||
environment: GITEA_POSTGRES_PASSWORD
|
||||
services:
|
||||
gitea:
|
||||
image: docker.gitea.com/gitea:1-rootless
|
||||
@ -14,11 +11,9 @@ services:
|
||||
GITEA__database__HOST: postgres:5432
|
||||
GITEA__database__NAME: gitea
|
||||
GITEA__database__USER: gitea
|
||||
GITEA__database__PASSWD_FILE: /run/secrets/gitea_postgres_password
|
||||
restart: always
|
||||
volumes: ['./data:/var/lib/gitea', './config:/etc/gitea']
|
||||
ports: ['8030:3000', '2222:2222']
|
||||
secrets: ['gitea_postgres_password']
|
||||
networks: ['homelab']
|
||||
|
||||
networks:
|
||||
|
||||
@ -1,12 +0,0 @@
|
||||
#!/bin/zsh
|
||||
# Exit immediately if a command exits with a non-zero status.
|
||||
set -e
|
||||
|
||||
echo "--- Starting Docker Secret Management ---"
|
||||
# Mount secrets
|
||||
export GITEA_POSTGRES_PASSWORD="$(op read 'op://NAxS Homelab/Gitea Postgres credentials/password')"
|
||||
|
||||
# Bring up container
|
||||
docker compose up -d
|
||||
|
||||
echo "--- Docker Secret Management Complete ---"
|
||||
@ -2,20 +2,26 @@
|
||||
## Set up non-root user for container
|
||||
We are providing a non-root user to the container to limit the attack surface for privilege escalations. In order for this to work in our setup, please make sure to check if you have a user called `postgres` set up.
|
||||
|
||||
1. Check if user `postgres` exists and if the UID is 1002
|
||||
1. Check if user `postgres` exists
|
||||
|
||||
```
|
||||
cat /etc/passwd | grep postgres
|
||||
```
|
||||
|
||||
In case the `postgres` user exists but the UID is not 1002, please adjust it via
|
||||
In case the `postgres` user doesn't exist, please create the user by running
|
||||
```
|
||||
sudo usermod -u 1002 postgres
|
||||
sudo useradd postgres
|
||||
```
|
||||
2. `data` folder ownership
|
||||
Also you need to make sure that the `postgres` owner owns the volumes mounted for docker
|
||||
```
|
||||
sudo chown -R postgres:postgres data
|
||||
sudo chmod 770 data
|
||||
```
|
||||
|
||||
In case the `postgres` user doesn't exist at all, please create the user incl. the right UID by running
|
||||
3. Adjust compose.yml
|
||||
Within `services > postgres > user`, make sure to replare `postgres` with the UID of the user on your machine
|
||||
```
|
||||
sudo useradd -u 1002 postgres
|
||||
cat /etc/passwd | grep postgres
|
||||
```
|
||||
|
||||
## About secrets
|
||||
@ -25,4 +31,4 @@ In order to manage secrets centrally in 1Password and due to the need for secret
|
||||
Please use the `start.sh` to spin up the container
|
||||
### Prerequisites start.sh
|
||||
- User executing the script is part of the `docker` group
|
||||
- Env variable `OP_SERVICE_ACCOUNT_TOKEN` is set up \[check out top-level README.md for more information on how to set this up\]
|
||||
- Environment variable `OP_SERVICE_ACCOUNT_TOKEN` is set up \[check out top-level README.md for more information on how to set this up\]
|
||||
@ -101,6 +101,4 @@ op --version
|
||||
apt install pass gnupg2
|
||||
|
||||
|
||||
|
||||
|
||||
echo "Done. Recommended: log out and back in (or reboot) to start using zsh and ensure all services are active."
|
||||
29
working_machine/alfred/addToOmnifocus.js
Normal file
29
working_machine/alfred/addToOmnifocus.js
Normal file
@ -0,0 +1,29 @@
|
||||
function run(argv) {
|
||||
const title = $.NSProcessInfo.processInfo.environment.objectForKey("title").js;
|
||||
const notes = $.NSProcessInfo.processInfo.environment.objectForKey("notes").js
|
||||
|
||||
function addToOmnifocus(transportText) {
|
||||
newTasks = Task.byParsingTransportText(transportText, true);
|
||||
taskID = newTasks[0].id.primaryKey;
|
||||
URL.fromString("omnifocus:///task/" + taskID).open();
|
||||
}
|
||||
|
||||
function generateTransportText(title, notes) {
|
||||
const tag = 'work';
|
||||
let transportText = `${title} @${tag}`;
|
||||
if (notes) {
|
||||
transportText = `${transportText} // ${notes}`
|
||||
}
|
||||
return transportText;
|
||||
}
|
||||
|
||||
const transportText = generateTransportText(title, notes);
|
||||
const encodedFunctionAndInput = `%28${encodeURIComponent(addToOmnifocus.toString())}%29%28argument%29&arg=%22${encodeURIComponent(transportText)}%22`;
|
||||
const omnifocusUrl = `omnifocus://localhost/omnijs-run?script=${encodedFunctionAndInput}`;
|
||||
|
||||
console.log(omnifocusUrl);
|
||||
|
||||
let app = Application.currentApplication();
|
||||
app.includeStandardAdditions = true;
|
||||
app.openLocation(omnifocusUrl);
|
||||
}
|
||||
BIN
working_machine/alfred/omnifocus-alfred.alfredworkflow
Normal file
BIN
working_machine/alfred/omnifocus-alfred.alfredworkflow
Normal file
Binary file not shown.
5
working_machine/zshrc/README.md
Normal file
5
working_machine/zshrc/README.md
Normal file
@ -0,0 +1,5 @@
|
||||
# ZSH setup
|
||||
1. Place .zshrc file in home directory
|
||||
2. Create folder called zshrc
|
||||
3. Include all needed .sh files inside the zshcrc folder
|
||||
4. To activate the changes right away, make sure to execute `source .zshrc` from within the home directory in your terminal
|
||||
1
working_machine/zshrc/env.sh
Normal file
1
working_machine/zshrc/env.sh
Normal file
@ -0,0 +1 @@
|
||||
export OP_SERVICE_ACCOUNT_TOKEN="$(pass op-sa_token)"
|
||||
2
working_machine/zshrc/general.sh
Normal file
2
working_machine/zshrc/general.sh
Normal file
@ -0,0 +1,2 @@
|
||||
bindkey -v
|
||||
PS1='%n@%m %~ $ '
|
||||
@ -9,4 +9,4 @@ export PATH="/Users/mucas/.local/share/gem/ruby/3.2.0/bin:$PATH"
|
||||
|
||||
export PYENV_ROOT="$HOME/.pyenv"
|
||||
[[ -d $PYENV_ROOT/bin ]] && export PATH="$PYENV_ROOT/bin:$PATH"
|
||||
eval "$(pyenv init - zsh)"
|
||||
eval "$(pyenv init - zsh)"
|
||||
3
working_machine/zshrc/zshrc
Normal file
3
working_machine/zshrc/zshrc
Normal file
@ -0,0 +1,3 @@
|
||||
for FILE in ~/zshrc/*; do
|
||||
source $FILE
|
||||
done
|
||||
Reference in New Issue
Block a user