Compare commits

..

10 Commits

Author SHA1 Message Date
Orion Kindel
a82612b5f9
fix: stuff 2023-05-31 14:52:38 -05:00
Orion Kindel
41e5fce0b2
fix: no sudo 2023-05-21 13:11:15 -05:00
Orion Kindel
32bb3ca3a3
docs: add readme 2023-05-21 13:10:03 -05:00
Orion Kindel
ae4ef7d1f6
fix: server can be re-scaffolded without invalidating sessions, don't leak secrets 2023-05-21 12:58:31 -05:00
Orion Kindel
4e7c9aaf8a
fix: shellcheck 2023-05-20 19:20:53 -05:00
Orion Kindel
6d86bbb538
feat: perms, gitea actions, ssl 2023-05-20 14:08:52 -05:00
Orion Kindel
726e0e797e
fix: misc permissions, path issues 2023-05-18 23:12:46 -05:00
Orion Kindel
3e0f409f13
fix: permissions issues 2023-05-18 22:51:02 -05:00
Orion Kindel
647c8e5762
fix: public/private port distinction is no longer necessary 2023-05-18 22:04:50 -05:00
Orion Kindel
47a02b8356
fix: undo oopsie 2023-05-18 22:02:04 -05:00
26 changed files with 418 additions and 176 deletions

2
.gitignore vendored
View File

@ -1 +1,3 @@
src/opengist.yml
src/gitea-app.ini
ext

29
README.md Normal file
View File

@ -0,0 +1,29 @@
# srv
## principles
* strong security
* fine-grained user-based access and security
* rootless docker
* user-space systemctl for scheduled tasks
* re-runnable and idempotent; changes to configuration does the same work as initial setup without losing state
## observable outputs
* given gitea domain `<git_url>`:
* configures ssl for `https://<git_url>`
* forwards `http://<git_url>` -> `https://<git_url>`
* `https://<git_url>` serves gitea instance using sqlite3
* SSH git authentication via `git@<git_url>` is fully supported
* gitea has actions enabled and a runner instance provided
* any gitea instance data and sessions are preserved (_Note: the linux user that "owns" the gitea instance was deleted and recreated, and configuration was overwritten by `src/gitea-app.ini`._)
## setup
copy `src/gitea-app.ini.sample` to `src/gitea-app.ini` and fill in the `; <snip>` secrets
## running
copy this repository to the debian image, ex with sshfs:
```sh
> mkdir ./ext
> sshfs user@host:/mnt ./ext
> rm ./ext/*; cp ./src/* ./ext/ # <- effectively deploys new configuration
```
then on the host run `/mnt/000-entry.sh` in an interactive shell.

View File

@ -3,14 +3,9 @@
set -xo pipefail
domain_root="${DOMAIN_ROOT:-orionkindel.com}"
subdomain_gitea="${SUBDOMAIN_GITEA:-git}"
uid_git="${UID_GIT:-1000}"
# NOTE: hard-coded in ./gitea-docker-compose.yml, ./nginx.conf
port_gitea_public="${PORT_GITEA:-8880}"
port_gitea_internal="${PORT_GITEA:-8881}"
# Creates a login session for `user` (positional argument 1) in their home directory,
# and executes a bash command string (positional argument 2) as `user`.
#
@ -31,16 +26,22 @@ port_gitea_internal="${PORT_GITEA:-8881}"
# ...
# ```
function doas {
ssh -F /dev/null -o IdentitiesOnly=yes -i /root/.ssh/local_ed25519 $1@localhost "set -xo pipefail; $2"
ssh -F /dev/null -o IdentitiesOnly=yes -i /root/.ssh/local_ed25519 "$1@localhost" "set -xo pipefail; $2"
}
rm /root/.ssh/local_ed25519 || true;
rm /root/.ssh/local_ed25519.pub || true;
ssh-keygen -t ed25519 -C "local" -f /root/.ssh/local_ed25519 -P ''
source ./010-apt.sh
source ./020-users.sh
source ./030-net.sh
source ./031-routing.sh
source ./040-gitea.sh
cp ./sshd_config.presetup /etc/ssh/sshd_config
systemctl restart sshd
source ./010-system-apt.sh
source ./011-system-users.sh
source ./020-net.sh
source ./021-net-routing.sh
source ./022-net-ssl.sh
source ./030-gitea-actions.sh
source ./031-gist.sh
source ./039-gitea.sh
source ./999-post.sh

View File

@ -22,8 +22,10 @@ apt-get install -fy \
install -m 0755 -d /etc/apt/keyrings
rm /etc/apt/keyrings/docker.gpg || true;
curl -fsSL https://download.docker.com/linux/debian/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg
chmod a+r /etc/apt/keyrings/docker.gpg
# shellcheck disable=SC2027,SC2046
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" | \

38
src/011-system-users.sh Normal file
View File

@ -0,0 +1,38 @@
#! /usr/bin/bash
function user_del_if_exist {
if id "$1" &>/dev/null; then
set +x
grp=$(id -g "$1")
# https://i.imgflip.com/3ggbcq.jpg
until userdel "$1"; do pkill -eU "$1" || true; done;
set -x
groupdel "$grp" || true
rm -rf "/home/${1:?}" || true
fi
}
function user_init {
loginctl enable-linger "$1"
rm -r "/home/$1/.ssh" || true
mkdir "/home/$1/.ssh"
chown "$1:$1" "/home/$1/.ssh"
cp /root/.ssh/local_ed25519.pub "/home/$1/.ssh/authorized_keys"
chown "$1:$1" "/home/$1/.ssh/authorized_keys"
chmod 600 "/home/$1/.ssh/authorized_keys"
doas "$1" "
echo \"$2\" >> ~/.ssh/authorized_keys;
echo \"export DOCKER_HOST=unix:///run/user/$(id -u "$1")/docker.sock\" > ~/.bashrc;
echo \"export PATH=/usr/bin:/usr/sbin:$PATH\" >> ~/.bashrc;
source ~/.bashrc;
dockerd-rootless-setuptool.sh install;
systemctl --user enable docker;
systemctl --user start docker;
"
}
source ./012-system-users-gitea.sh
source ./013-system-users-other.sh

View File

@ -0,0 +1,66 @@
#! /usr/bin/bash
uid_git=${uid_git:-}
## backup gitea data to /tmp
mkdir -p /tmp/git
if id git &>/dev/null; then
mkdir -p /tmp/git
mv /home/git/opengist /tmp/git/opengist || true
mv /home/git/data /tmp/git/data || true
mv /home/git/config /tmp/git/config || true
fi
## delete and recreate `git` user
user_del_if_exist git
echo "$uid_git"
groupadd --gid "$uid_git" git
useradd \
--gid "$uid_git" \
--uid "$uid_git" \
--create-home \
--shell /bin/bash \
git
mkdir -p /tmp/git/opengist/
mkdir -p /tmp/git/config/
mkdir -p /tmp/git/data/
mkdir -p /tmp/git/data/git/
mkdir -p /tmp/git/data/act_runner/
read -rp "enter public ssh key allowing sessions as \`git\`:" git_ssh_pub
user_init git "$git_ssh_pub"
## restore homedir (if applicable)
if [ -d /tmp/git/data ]; then
mv /tmp/git/data /home/git/ || true
mv /tmp/git/config /home/git/ || true
mv /tmp/git/opengist /home/git/ || true
fi
## gitea
cp ./gitea-docker-compose.yml /home/git/docker-compose.yml
cp ./gitea-app.ini /home/git/config/app.ini
sed -i "s/\\\${{TIMESTAMP}}/$(date +%s)/g" /home/git/config/app.ini
## runner
touch /home/git/runner-config.yml
touch /home/git/.env.runner
## gist
touch /home/git/opengist.yml
## ownership & permissions
chown -R git:git /home/git
chown -R git:git /home/git/opengist.yml
chown -R git:git /home/git/runner-config.yml
chown -R git:git /home/git/.env.runner
chown -R git:git /home/git/data
chown -R git:git /home/git/data/git
chown -R git:git /home/git/data/act_runner
chown -R git:git /home/git/config
chmod -R 777 /home/git/opengist
chmod -R 777 /home/git/data
chmod -R 777 /home/git/config

6
src/013-system-users-other.sh Executable file
View File

@ -0,0 +1,6 @@
#! /usr/bin/bash
user_del_if_exist orion
useradd --create-home --shell /bin/bash orion
read -rp "enter public ssh key allowing sessions as \`orion\`:" orion_ssh_pub
user_init orion "$orion_ssh_pub"

View File

@ -5,5 +5,4 @@ ufw default allow outgoing
ufw status verbose
ufw allow ssh
ufw allow 'Nginx Full'
ufw allow 8880/tcp
ufw --force enable

View File

@ -1,38 +0,0 @@
#! /usr/bin/bash
function user_del_if_exist {
if id "$1" &>/dev/null; then
set +x
grp=`id -g $1`
# https://i.imgflip.com/3ggbcq.jpg
until userdel $1; do pkill -eU $1 || true; done;
set -x
groupdel $grp || true
rm -rf /home/$1 || true
fi
}
function user_init {
loginctl enable-linger $1
rm -r /home/$1/.ssh || true
mkdir /home/$1/.ssh
chown $1:$1 /home/$1/.ssh
cp /root/.ssh/local_ed25519.pub /home/$1/.ssh/authorized_keys
chown $1:$1 /home/$1/.ssh/authorized_keys
chmod 755 /home/$1/.ssh/authorized_keys
doas $1 "
echo $2 >> ~/.ssh/authorized_keys;
echo \"export DOCKER_HOST=unix:///run/user/`id -u $1`/docker.sock\" > ~/.bashrc;
echo \"export PATH=/usr/bin:/usr/sbin:$PATH\" >> ~/.bashrc;
source ~/.bashrc;
dockerd-rootless-setuptool.sh install;
systemctl --user enable docker;
systemctl --user start docker;
"
}
source ./021-user-gitea.sh
source ./022-user-others.sh

34
src/021-net-routing.sh Executable file
View File

@ -0,0 +1,34 @@
#! /usr/bin/bash
domain_root=${domain_root:-}
git_domain="git.$domain_root"
gist_domain="gist.$domain_root"
mkdir -p /etc/nginx/sites-available
mkdir -p /etc/nginx/sites-enabled
rm -r "/etc/nginx/sites-available/$domain_root" 2>/dev/null || true
rm -r "/etc/nginx/sites-enabled/$domain_root" 2>/dev/null || true
# git.<domain>
rm -r "/etc/nginx/sites-available/$git_domain" 2>/dev/null || true
rm -r "/etc/nginx/sites-enabled/$git_domain" 2>/dev/null || true
touch "/etc/nginx/sites-available/$git_domain"
ln -s "/etc/nginx/sites-available/$git_domain" "/etc/nginx/sites-enabled/$git_domain"
cp ./git.orionkindel.com.nginx.conf "/etc/nginx/sites-available/$git_domain"
chmod 777 "/etc/nginx/sites-available/$git_domain"
# gist.<domain>
rm -r "/etc/nginx/sites-available/$gist_domain" 2>/dev/null || true
rm -r "/etc/nginx/sites-enabled/$gist_domain" 2>/dev/null || true
touch "/etc/nginx/sites-available/$gist_domain"
ln -s "/etc/nginx/sites-available/$gist_domain" "/etc/nginx/sites-enabled/$gist_domain"
cp ./gist.orionkindel.com.nginx.conf "/etc/nginx/sites-available/$gist_domain"
chmod 777 "/etc/nginx/sites-available/$gist_domain"
systemctl enable nginx
systemctl start nginx

View File

@ -1,38 +0,0 @@
#! /usr/bin/bash
## backup gitea data to /tmp
mkdir -p /tmp/git
if id git &>/dev/null; then
mkdir -p /tmp/git
mv /home/git/data /tmp/git/data
mv /home/git/config /tmp/git/config
else
mkdir /tmp/git
mkdir /tmp/git/data
mkdir /tmp/git/data/git
mkdir /tmp/git/config
fi
## delete and recreate `git` user
user_del_if_exist git
echo $uid_git
groupadd --gid $uid_git git
useradd \
--gid $uid_git \
--uid $uid_git \
--create-home \
--shell /bin/bash \
git
read -p 'enter public ssh key allowing sessions as `git`:' git_ssh_pub
user_init git $git_ssh_pub
## restore homedir
mv /tmp/git/data /home/git/
mv /tmp/git/config /home/git/
cp ./gitea-docker-compose.yml /home/git/docker-compose.yml
cp ./gitea-app.ini /home/git/config/app.ini
chown -R git:git /home/git

4
src/022-net-ssl.sh Normal file
View File

@ -0,0 +1,4 @@
#! /usr/bin/bash
certbot --nginx -d git.orionkindel.com -n
certbot --nginx -d gist.orionkindel.com -n

View File

@ -1,6 +0,0 @@
#! /usr/bin/bash
user_del_if_exist orion
useradd --create-home --shell /bin/bash orion
read -p 'enter public ssh key allowing sessions as `orion`:' orion_ssh_pub
user_init orion $orion_ssh_pub

14
src/030-gitea-actions.sh Normal file
View File

@ -0,0 +1,14 @@
#! /usr/bin/bash
read -rp 'enter action runner token: ' token
cp ./gitea-actions-runner-config.yml /home/git/runner-config.yml
cat << EOF >> /home/git/.env.runner
CONFIG_FILE=/config.yml
GITEA_INSTANCE_URL=https://git.orionkindel.com
GITEA_RUNNER_REGISTRATION_TOKEN=$token
EOF
chown git:git -R /home/git/runner-config.yml
chown git:git -R /home/git/.env.runner

9
src/031-gist.sh Normal file
View File

@ -0,0 +1,9 @@
#! /usr/bin/bash
cp ./opengist.yml /home/git/opengist.yml
chown git:git -R /home/git/opengist.yml
cp ./opengist-embed.html /home/git/opengist.embed.html
chown git:git -R /home/git/opengist.embed.html
echo "Follow https://github.com/thomiceli/opengist#configure-oauth, enter secrets in opengist.yml then re-run this script to enable gist server"

View File

@ -1,16 +0,0 @@
#! /usr/bin/bash
mkdir -p /etc/nginx/sites-available
mkdir -p /etc/nginx/sites-enabled
rm -r /etc/nginx/sites-available/$domain_root 2>/dev/null || true
rm -r /etc/nginx/sites-enabled/$domain_root 2>/dev/null || true
touch /etc/nginx/sites-available/$domain_root
ln -s /etc/nginx/sites-available/$domain_root /etc/nginx/sites-enabled/$domain_root
cp ./nginx.conf /etc/nginx/sites-available/$domain_root
chmod 777 /etc/nginx/sites-available/$domain_root
systemctl enable nginx
systemctl start nginx

View File

@ -17,11 +17,7 @@ rm /usr/local/bin/gitea-shell || true;
cat << "EOF" >> /usr/local/bin/gitea-shell
#!/bin/sh
/usr/bin/docker context use rootless
/usr/bin/docker exec -i \
--env SSH_ORIGINAL_COMMAND="$SSH_ORIGINAL_COMMAND" \
gitea \
sh "$@"
/usr/bin/docker compose exec -i --env SSH_ORIGINAL_COMMAND="$SSH_ORIGINAL_COMMAND" gitea sh "$@"
EOF
chmod +x /usr/local/bin/gitea-shell

View File

@ -0,0 +1,17 @@
server {
listen 80;
server_name gist.orionkindel.com;
location ~ ^/embed(/.*)$ {
root /home/git;
try_files /opengist.embed.html =404;
}
location / {
proxy_pass http://localhost:8881;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}

View File

@ -4,7 +4,8 @@ server {
location / {
client_max_body_size 512M;
proxy_pass http://localhost:8881;
# see also: ./gitea-docker-compose.yml
proxy_pass http://localhost:8880;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

View File

@ -0,0 +1,20 @@
log:
level: info
runner:
file: .runner
capacity: 1
timeout: 3h
insecure: false
fetch_timeout: 5s
fetch_interval: 2s
cache:
enabled: true
host: "152.44.36.48"
container:
network_mode: bridge
privileged: false
options:
workdir_parent:

View File

@ -1,55 +0,0 @@
; https://github.com/go-gitea/gitea/blob/main/custom/conf/app.example.ini
APP_NAME = git@orionkindel.com
RUN_MODE = prod
[server]
DOMAIN = localhost
SSH_DOMAIN = localhost
HTTP_PORT = 3000
ROOT_URL = git.orionkindel.com
DISABLE_SSH = false
SSH_PORT = 22
SSH_LISTEN_PORT = 22
LFS_START_SERVER = false
[database]
PATH = /data/gitea/gitea.db
DB_TYPE = sqlite3
HOST = localhost:3306
NAME = gitea
USER = root
PASSWD =
LOG_SQL = false
[indexer]
ISSUE_INDEXER_PATH = /data/gitea/indexers/issues.bleve
[session]
PROVIDER_CONFIG = /data/gitea/sessions
[picture]
AVATAR_UPLOAD_PATH = /data/gitea/avatars
REPOSITORY_AVATAR_UPLOAD_PATH = /data/gitea/repo-avatars
[attachment]
PATH = /data/gitea/attachments
[log]
MODE = console
LEVEL = info
ROUTER = console
ROOT_PATH = /data/gitea/log
[security]
INSTALL_LOCK = false
SECRET_KEY =
REVERSE_PROXY_LIMIT = 1
REVERSE_PROXY_TRUSTED_PROXIES = *
[service]
DISABLE_REGISTRATION = false
REQUIRE_SIGNIN_VIEW = false
[lfs]
PATH = /data/git/lfs

93
src/gitea-app.ini.sample Normal file
View File

@ -0,0 +1,93 @@
; https://github.com/go-gitea/gitea/blob/main/custom/conf/app.example.ini
APP_NAME = git@orionkindel.com
RUN_MODE = prod
RUN_USER = git
[server]
DOMAIN = git.orionkindel.com
SSH_DOMAIN = git.orionkindel.com
HTTP_PORT = 3000
DISABLE_SSH = false
START_SSH_SERVER = true
SSH_PORT = 22
SSH_LISTEN_PORT = 22
LFS_START_SERVER = true
ROOT_URL = https://git.orionkindel.com/
LFS_JWT_SECRET = ; <snip>
OFFLINE_MODE = false
[database]
PATH = /data/gitea/gitea.db
DB_TYPE = sqlite3
HOST = localhost:3306
NAME = gitea
USER = root
PASSWD =
LOG_SQL = false
SCHEMA =
SSL_MODE = disable
CHARSET = utf8
[indexer]
ISSUE_INDEXER_PATH = /data/gitea/indexers/issues.bleve
[session]
PROVIDER = db
[picture]
AVATAR_UPLOAD_PATH = /data/gitea/avatars
REPOSITORY_AVATAR_UPLOAD_PATH = /data/gitea/repo-avatars
[attachment]
PATH = /data/gitea/attachments
[log]
MODE = console
LEVEL = info
ROUTER = console
ROOT_PATH = /data/gitea/log
[security]
INSTALL_LOCK = true
SECRET_KEY =
REVERSE_PROXY_LIMIT = 1
REVERSE_PROXY_TRUSTED_PROXIES = *
INTERNAL_TOKEN = ; <snip>
PASSWORD_HASH_ALGO = pbkdf2
[service]
DISABLE_REGISTRATION = false
REQUIRE_SIGNIN_VIEW = false
REGISTER_EMAIL_CONFIRM = false
ENABLE_NOTIFY_MAIL = false
ALLOW_ONLY_EXTERNAL_REGISTRATION = false
ENABLE_CAPTCHA = false
DEFAULT_KEEP_EMAIL_PRIVATE = false
DEFAULT_ALLOW_CREATE_ORGANIZATION = true
DEFAULT_ENABLE_TIMETRACKING = true
NO_REPLY_ADDRESS = noreply.localhost
[lfs]
PATH = /data/git/lfs
[repository]
ROOT = /data/gitea-repositories
[mailer]
ENABLED = false
[openid]
ENABLE_OPENID_SIGNIN = true
ENABLE_OPENID_SIGNUP = true
[cron.update_checker]
ENABLED = true
[repository.pull-request]
DEFAULT_MERGE_STYLE = merge
[repository.signing]
DEFAULT_TRUST_MODEL = committer
[actions]
ENABLED = true

View File

@ -3,17 +3,41 @@ version: "3"
name: gitea_compose
services:
server:
image: gitea/gitea:dev-rootless
gitea:
container_name: gitea
image: gitea/gitea:latest-rootless
user: "1000"
restart: always
volumes:
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
- /home/git/data/:/data
- /home/git/data/:/var/lib/gitea/data
- /home/git/git/:/var/lib/gitea/git
- /home/git/config/:/etc/gitea
ports:
- "8881:3000"
- "8880:3000" # see also: ./nginx.conf
- "127.0.0.1:2222:22"
gitea_runner:
container_name: gitea_runner
image: gitea/act_runner:latest
restart: always
depends_on:
- gitea
volumes:
- /home/git/data/act_runner:/data
- /home/git/runner-config.yml:/config.yml
- /run/user/1000/docker.sock:/var/run/docker.sock
env_file:
- /home/git/.env.runner
opengist:
container_name: opengist
image: ghcr.io/thomiceli/opengist:1
command: ['./opengist', '--config', '/root/opengist.yml']
restart: always
volumes:
- "/home/git/opengist:/root/.opengist"
- "/home/git/opengist.yml:/root/opengist.yml"
ports:
- "8881:6157" # http

16
src/opengist-embed.html Normal file
View File

@ -0,0 +1,16 @@
<body>
<script>
const href = window.location.href.replace('embed/', '');
const file = window.location.hash;
const iframe = document.createElement('iframe');
iframe.setAttribute('style', 'position: fixed; top: 0; left: 0; width: 100%; height: 100%; border: none; outline: none;');
iframe.onload = () => {
const files = iframe.contentDocument.querySelectorAll('div.grid > div');
const fileDiv = Array.from(files).find(f => f.querySelector('span' + file) !== undefined);
fileDiv.setAttribute('style', 'position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: white;');
iframe.contentDocument.body.setAttribute('style', 'overflow: hidden;')
}
iframe.src = href;
document.body.append(iframe);
</script>
</body>

8
src/opengist.example.yml Normal file
View File

@ -0,0 +1,8 @@
log-level: info
ssh.git-enabled: false
# Fill these in
# https://github.com/thomiceli/opengist#configure-oauth
gitea.client-key:
gitea.secret:
gitea.url:

16
src/sshd_config.presetup Normal file
View File

@ -0,0 +1,16 @@
# $OpenBSD: sshd_config,v 1.103 2018/04/09 20:41:22 tj Exp $
Include /etc/ssh/sshd_config.d/*.conf
ChallengeResponseAuthentication no
UsePAM yes
X11Forwarding yes
PrintMotd no
AcceptEnv LANG LC_*
Subsystem sftp /usr/lib/openssh/sftp-server
PasswordAuthentication no