latest commit

This commit is contained in:
2025-12-19 15:31:57 +01:00
commit 4dd6474a78
5 changed files with 644 additions and 0 deletions

21
LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2025 Allan Christensen
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

221
README.md Normal file
View File

@@ -0,0 +1,221 @@
# Gitea MultiInstance for Ubuntu 24.04
[![OS](https://img.shields.io/badge/ubuntu-24.04-E95420)](#)
[![Shell](https://img.shields.io/badge/shell-bash-121011)](#)
[![WebServer](https://img.shields.io/badge/server-nginx-009639)](#)
[![MariaDB](https://img.shields.io/badge/db-mariadb-003545)](#)
[![MySQL](https://img.shields.io/badge/db-mysql-4479A1)](#)
[![Gitea](https://img.shields.io/badge/app-gitea-609926)](#)
[![License](https://img.shields.io/badge/License-MIT-green)](./LICENSE)
Install multiple Gitea instances on Ubuntu 24.04 server.
This is not a demo environment.
This is a production-focused installer built to scale cleanly and update centrally.
## Why this installer exists
Running several Gitea instances on one server should be easy. This script makes multi-instance hosting repeatable.
## What this installer does
✔ Auto-numbers instances (gitea1, gitea2 …)
✔ Creates isolated DB + service per instance
✔ Assigns final ports safely post-install
✔ Includes robots + sitemap generation
✔ Shared binary — one upgrade updates all
## What this installer does *NOT* do
It wont stop you from running the script without reading the documentation like theres no tomorrow.
Skip the README, and whatever happens next is your headache, not a bug report.
---
## Requirements
You must already have:
✔ Nginx installed
✔ MariaDB/MySQL running
✔ Port **3000 free during install** (temporary only)
### Optional Installers (if you need them — saves you some googling)
- [Install Nginx + PHP-FPM on Ubuntu](https://git.x-files.dk/webserver/nginx-ubuntu)
- [Install MariaDB on Ubuntu](https://git.x-files.dk/database/mariadb-ubuntu)
- [Install MySQL on Ubuntu](https://git.x-files.dk/database/mysql-ubuntu)
---
## 1. Download
```
git clone https://git.x-files.dk/webapps/gitea-ubuntu-multi.git
```
```
cd gitea-ubuntu-multi
```
---
## 2. Install a new instance
```
sudo ./giteainstall-multi -n <domain> -p <dbpassword> [options]
```
### Examples
Socket auth present:
```
sudo ./giteainstall-multi -n git1.example.com -p gitea1pwd
```
No socket → use DB credentials:
```
sudo ./giteainstall-multi -n git2.example.com -p pass2 -m rootpwd
```
### Flags
| Option | Meaning |
|---|---|
| `-p` password | DB password for the new Gitea instance |
| `-m` password | Admin/root MariaDB password *(if no socket)* |
| `-a` username | Admin DB user *(default = root)* |
| `-h` | Help screen |
---
## How instances are created
Each install assigns the **next instance number automatically**:
| Instance | User | DB | Config | Service | Final Port |
|---|---|---|---|---|---|
| 1 | gitea1 | gitea1db | /etc/gitea1 | gitea1.service | 3001 |
| 2 | gitea2 | gitea2db | /etc/gitea2 | gitea2.service | 3002 |
| 3 | gitea3 | gitea3db | /etc/gitea3 | gitea3.service | 3003 |
Temporary installer UI runs at `http://domain:3000`
Final port assigned **after postinstall**.
---
## Complete setup
1. Visit:
```
http://<domain>
```
2. Finish the web installer WITHOUT changing port.
3. Run the postinstall unique to this instance:
```
sudo /etc/giteaX/gitea-postinstall
```
(`X` = instance number)
That step:
✔ moves Gitea from 3000 → final assigned port
✔ configures logging, UI, paging, themes
✔ disables SSH by default
✔ updates Nginx proxy target
---
## File Layout (auto-increment per instance)
When first installed, paths look like this:
**Instance 1**
| Path | Purpose |
|---|---|
| `/etc/gitea1/app.ini` | Main config file |
| `/etc/gitea1/gitea-postinstall` | Postinstall script |
| `/var/lib/gitea1/` | Data, repos, indexers, logs |
| `/var/lib/gitea1/custom/templates/` | UI overrides |
| `/var/lib/gitea1/custom/public/assets/img/` | Logos + branding |
| `/var/lib/gitea1/custom/public/` | robots + sitemap *(optional)* |
| `/usr/local/bin/gitea` | Shared binary |
**Instance 2 (identical structure, next number auto-assigned)**
| Path | Purpose |
|---|---|
| `/etc/gitea2/app.ini` | Main config file |
| `/etc/gitea2/gitea-postinstall` | Postinstall script |
| `/var/lib/gitea2/` | Data, repos, indexers, logs |
| `/var/lib/gitea2/custom/templates/` | UI overrides |
| `/var/lib/gitea2/custom/public/assets/img/` | Logos + branding |
| `/var/lib/gitea2/custom/public/` | robots + sitemap *(optional)* |
| `/usr/local/bin/gitea` | Shared binary |
> Every new install increments automatically (`gitea3`, `gitea4`, `gitea5`…)
> Important (but optional):
> `robots.txt` and `sitemap.xml` are supported automatically by the Nginx configuration being created,
> however the installer does not generate these files. Create them manually in the directory listed above.
---
## HTTPS
Use the TLS template here:
https://git.x-files.dk/webserver/nginx-snippets/src/branch/main/hostfiles/gitea.443.conf
Then update in `/etc/giteaX/app.ini`:
```
ROOT_URL = https://<domain>
```
Restart:
```
systemctl restart giteaX nginx
```
---
## Version Handling
Script checks latest from:
```
https://dl.gitea.com/gitea/version.json
```
Fallback file contains static version:
```
fallback
1.25.2
```
---
## Troubleshooting
| Issue | Cause | Fix |
|---|---|---|
| Nginx restart fails | Bad config | `nginx -t` |
| DB denied | No socket | Add `-m` (and `-a`) |
| Blank web UI | Service down | `systemctl status giteaX` |
| Next instance reusing port | Postinstall not run | Run `/etc/giteaX/gitea-postinstall` |
---
### More Information
More guides and documentation can be found on [wiki.x-files.dk](https://wiki.x-files.dk)
---
### License
Licensed under the [MIT License](./LICENSE).
---

1
fallback Normal file
View File

@@ -0,0 +1 @@
1.25.2

395
giteainstall Executable file
View File

@@ -0,0 +1,395 @@
#!/usr/bin/env bash
# Author : Allan Christensen
# First Created : 15-09-2022 (DD-MM-YYYY)
# Description : Installs multiple isolated Gitea instances on Ubuntu 24.04
# License : MIT License
#
# Are we root
#
if [[ $(id -u) -ne 0 ]]; then echo "" && echo "Must be root or use sudo" && echo "" ; exit ; fi
#
# Detect database engine (MariaDB or MySQL)
#
if systemctl list-unit-files | grep -q '^mariadb\.service'; then
db_engine="mariadb"
db_service="mariadb"
elif systemctl list-unit-files | grep -q '^mysql\.service'; then
db_engine="mysql"
db_service="mysql"
else
printf "\nNo supported database server found.\n\n"
exit 1
fi
#
# Check required services
#
phpfpm=$(systemctl list-unit-files --type=service | awk '/php[0-9]+\.[0-9]+-fpm\.service/ {sub(".service","",$1); print $1; exit}')
for svc in nginx "$db_service"; do systemctl is-active --quiet "$svc" || { printf "\n%s is not running, cannot continue...\n\n" "${svc^}" ; exit 1 ; }; done
#
# Detect socket authentication (local root access)
#
if mysql -u root -e ";" 2>/dev/null; then
socketauth="yes"
else
socketauth="no"
fi
#
# Variables
#
fallbackversion=$(<fallback)
giteadb_suffix="db"
gitealocation="/usr/local/bin/gitea"
nginxsnippets="/etc/nginx/nginx-snippets"
nginxrepo="https://git.x-files.dk/webserver/nginx-snippets.git"
#
# Port 3000 must be temporarily free for the Gitea web installer
#
installer_port=3000
if ss -tuln | grep -q ":${installer_port}\b"; then
printf "\nPort %s appears to be in use.\n" "$installer_port"
listener=$(ss -tulpn 2>/dev/null | awk -v port=":${installer_port}\\b" '$0 ~ port && /LISTEN/ {print $7}' | sed 's/users:(("//; s/",.*//')
if [[ -n "$listener" ]]; then
printf "Process using port %s : %s\n" "$installer_port" "$listener"
fi
printf "\nThis port is required temporarily by the Gitea web installer.\n"
printf "Please free port %s before running this installer.\n\n" "$installer_port"
exit 1
fi
#
# Usage
#
usage() {
printf -- "\ngiteainstall multi instance\n\n"
printf -- "Database engine detected: %s\n\n" "$db_engine"
if [[ "$socketauth" == "yes" ]]; then
printf -- "LOCAL ROOT ACCESS DETECTED — no need for -a or -m\n\n"
printf -- "Installs multiple Gitea instances using local socket authentication.\n\n"
printf -- "Usage:\n"
printf -- " sudo ./giteainstall -n <gitea domain> -p <gitea database password>\n\n"
printf -- "Example:\n"
printf -- " sudo ./giteainstall -n git1.example.com -p gitea1dbpwd\n\n"
else
printf -- "NO LOCAL ROOT ACCESS — you must use -a and -m\n\n"
printf -- "Installs multiple Gitea instances using database admin credentials.\n\n"
printf -- "Usage:\n"
printf -- " sudo ./giteainstall -n <gitea domain> -p <gitea database password> -a <adminuser> -m <adminpwd>\n\n"
printf -- "Examples:\n"
printf -- " sudo ./giteainstall -n git2.example.com -p gitea2dbpwd -a root -m rootpwd\n"
printf -- " sudo ./giteainstall -n git3.example.com -p gitea3dbpwd -a admin -m adminpwd\n\n"
fi
printf -- "Options:\n"
printf -- " -h | -help | --help Show this help screen\n\n"
}
#
# Let's go
#
clear
#
# Help traps
#
firstarg="${1:-}"
if [[ "$firstarg" == "-help" || "$firstarg" == "--help" ]]; then usage ; exit 0 ; fi
if [[ $# -eq 0 || ! "$firstarg" =~ ^- ]]; then usage ; exit 1 ; fi
#
# Configure command line options
#
while getopts "n:p:a:m:h" option; do
case "$option" in
n) hostname=$(echo "$OPTARG" | tr '[:upper:]' '[:lower:]');;
p) dbpass="$OPTARG";;
a) db_admin_user="$OPTARG";;
m) db_admin_pwd="$OPTARG";;
h) usage ; exit 0 ;;
\?) printf "Type sudo %s -h for help\n" "$0" ; exit 1 ;;
esac
done
#
# Validate input
#
if [[ -z "${hostname:-}" || -z "${dbpass:-}" ]]; then
usage
printf "\nERROR: Both -n (domain) and -p (database password) are required.\n\n"
exit 1
fi
#
# Normalise DB admin defaults
#
db_admin_user="${db_admin_user:-root}"
#
# If no socket auth, require admin password
#
if [[ "$socketauth" == "no" && -z "${db_admin_pwd:-}" ]]; then
usage
printf "\nERROR: No local root access detected — you must provide -a and -m.\n\n"
exit 1
fi
#
# Ensure required tools
#
for tool in curl git; do dpkg -s "$tool" &>/dev/null || apt install -y -qq "$tool" ; done
#
# Check for the latest Gitea version
#
version=$(curl -fsSL https://dl.gitea.com/gitea/version.json | grep -oP '"version"\s*:\s*"\K[^"]+')
if [[ -z "$version" ]]; then
printf "Could not determine latest version. Falling back to version %s\n\n" "$fallbackversion"
version="$fallbackversion"
fi
printf "\nUsing Gitea version: %s\n" "$version"
#
# Download gitea binary if not already present (non-interactive)
#
if [[ ! -x "$gitealocation" ]]; then
curl -fL "https://dl.gitea.com/gitea/${version}/gitea-${version}-linux-amd64" -o "$gitealocation" \
|| { printf "\nDownload failed.\n\n" ; exit 1 ; }
chmod 755 "$gitealocation"
fi
#
# Clone nginx-snippets; if it exists then pull latest
#
if [[ -d "$nginxsnippets/.git" ]]; then
git -C "$nginxsnippets" pull --quiet
else
git clone --quiet "$nginxrepo" "$nginxsnippets"
fi
#
# Auto-increment instance number (gitea1, gitea2, ...)
#
mkdir -p /var/lib/gitea
counter_file="/var/lib/gitea/giteausercount"
[[ ! -f "$counter_file" ]] && printf "0\n" > "$counter_file"
current=$(cat "$counter_file")
next=$((current + 1))
printf "%s\n" "$next" > "$counter_file"
giteauser="gitea${next}"
gitea_db="${giteauser}${giteadb_suffix}"
final_port=$((3000 + next))
#
# Basic collision protection
#
if id "$giteauser" &>/dev/null; then
printf "\nERROR: User %s already exists — refusing to continue.\n\n" "$giteauser"
exit 1
fi
if [[ -d "/var/lib/$giteauser" || -d "/etc/$giteauser" ]]; then
printf "\nERROR: Instance paths already exist for %s — refusing to continue.\n\n" "$giteauser"
exit 1
fi
if [[ -f "/etc/nginx/conf.d/${hostname}.conf" ]]; then
printf "\nERROR: A configuration file already exists for %s.\n" "$hostname"
printf "Refusing to overwrite existing site.\n\n"
exit 1
fi
#
# Create Gitea system user
#
adduser --system --group --disabled-password --shell /bin/bash --home /home/$giteauser --gecos 'Git Version Control' $giteauser
mkdir -p /home/$giteauser/.ssh
chown -R $giteauser:$giteauser /home/$giteauser
chmod 700 /home/$giteauser/.ssh
#
# Create Gitea data structure
#
mkdir -p /var/lib/$giteauser/{custom,data,indexers,public,log}
chown "$giteauser":"$giteauser" /var/lib/$giteauser/{data,indexers,log}
chmod 750 /var/lib/$giteauser/{data,indexers,log}
mkdir -p /var/lib/$giteauser/custom/{templates,public/assets/img}
#
# Create /etc/$giteauser and its custom directory
#
mkdir -p /etc/$giteauser/custom
chown root:"$giteauser" /etc/$giteauser
chmod 770 /etc/$giteauser
chown -R "$giteauser":"$giteauser" /etc/$giteauser/custom
chmod -R 750 /etc/$giteauser/custom
#
# Create database and user
#
printf "\nCreating database: %s\n" "$gitea_db"
if [[ "$socketauth" == "yes" ]]; then
mysql -u root <<EOF
CREATE DATABASE IF NOT EXISTS $gitea_db;
CREATE USER IF NOT EXISTS '$giteauser'@'localhost' IDENTIFIED BY '$dbpass';
GRANT ALL PRIVILEGES ON $gitea_db.* TO '$giteauser'@'localhost';
FLUSH PRIVILEGES;
EOF
else
mysql -u "$db_admin_user" -p"$db_admin_pwd" <<EOF
CREATE DATABASE IF NOT EXISTS $gitea_db;
CREATE USER IF NOT EXISTS '$giteauser'@'localhost' IDENTIFIED BY '$dbpass';
GRANT ALL PRIVILEGES ON $gitea_db.* TO '$giteauser'@'localhost';
FLUSH PRIVILEGES;
EOF
fi
#
# Create temporary Nginx configuration file using port 3000 for web installer
#
cp "$nginxsnippets/hostfiles/gitea-multi.80.conf" /etc/nginx/conf.d/"$hostname".conf
sed -i -- "s/DOMAIN/$hostname/g" /etc/nginx/conf.d/"$hostname".conf
sed -i "s/GITEAPORT/3000/g" /etc/nginx/conf.d/"$hostname".conf
sed -i "s/GITEAUSER/$giteauser/g" /etc/nginx/conf.d/"$hostname".conf
systemctl reload nginx
#
# Create systemd service (temporary: always runs installer on 3000)
#
cat > /etc/systemd/system/$giteauser.service <<EOF
[Unit]
Description=Gitea Multi-Instance: $giteauser
After=syslog.target network.target
Requires=${db_service}.service
[Service]
LimitMEMLOCK=infinity
LimitNOFILE=65535
RestartSec=2s
Type=simple
User=$giteauser
Group=$giteauser
WorkingDirectory=/var/lib/$giteauser/
ExecStart=/usr/local/bin/gitea web -c /etc/$giteauser/app.ini
Restart=always
Environment=USER=$giteauser HOME=/home/$giteauser GITEA_WORK_DIR=/var/lib/$giteauser GITEA_CUSTOM=/var/lib/$giteauser/custom
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable "$giteauser"
systemctl start "$giteauser"
#
# Create postinstall script (moves port 3000 -> final port and locks down settings)
#
postscript="/etc/$giteauser/gitea-postinstall"
cat > "$postscript" <<EOF
#!/usr/bin/env bash
if [[ \$(id -u) -ne 0 ]]; then printf "\\nMust be root or use sudo.\\n\\n" ; exit 1 ; fi
hostname="$hostname"
giteauser="$giteauser"
final_port="$final_port"
cp -Rp /etc/\$giteauser/app.ini /etc/\$giteauser/app.ini.orig
# Switch HTTP_PORT from 3000 to final instance port
sed -i -E "s/^HTTP_PORT *= *3000/HTTP_PORT = \$final_port/" /etc/\$giteauser/app.ini
# A few sane defaults (keep your existing pattern)
sed -i '/gitea-repositories/a MAX_FILES = 500' /etc/\$giteauser/app.ini
sed -i '/gitea-repositories/a FILE_MAX_SIZE = 200' /etc/\$giteauser/app.ini
sed -i 's/LEVEL = info/LEVEL = warn/' /etc/\$giteauser/app.ini
sed -i 's/MODE = console/MODE = file/' /etc/\$giteauser/app.ini
sed -i 's/DISABLE_SSH = false/DISABLE_SSH = true/' /etc/\$giteauser/app.ini
cat >> /etc/\$giteauser/app.ini <<'INNER_EOF'
[ui.admin]
USER_PAGING_NUM = 50
REPO_PAGING_NUM = 50
NOTICE_PAGING_NUM = 25
ORG_PAGING_NUM = 25
[ui.user]
USER_PAGING_NUM = 50
REPO_PAGING_NUM = 50
NOTICE_PAGING_NUM = 25
ORG_PAGING_NUM = 25
[ui]
THEMES = gitea-auto, gitea-light, gitea-dark
EXPLORE_PAGING_DEFAULT_SORT = alphabetically
[other]
SHOW_FOOTER_POWERED_BY = false
SHOW_FOOTER_VERSION = false
SHOW_FOOTER_TEMPLATE_LOAD_TIME = false
ENABLE_FEED = false
INNER_EOF
# Update Nginx proxy to final assigned port
sed -i "s/127\\.0\\.0\\.1:3000/127.0.0.1:\$final_port/" /etc/nginx/conf.d/\$hostname.conf
systemctl reload nginx
# Restart Gitea instance
systemctl restart \$giteauser
# Remove postinstall script once it has run
rm -f "$postscript"
EOF
chmod 755 "$postscript"
#
# Web installer + postinstall notice
#
dbhost="127.0.0.1"
[[ "$socketauth" == "yes" ]] && dbhost="localhost"
clear
cat <<EOF
--------------------------------------------------------------------------------------
NEXT STEP — WEB INSTALLER
Complete the Gitea web installer here. During initial setup, Gitea runs on port 3000:
http://$hostname:3000/
Database configuration (Gitea web installer):
Database Type : MySQL
Database Host : $dbhost
Database Name : $gitea_db
Database User : $giteauser
Database Pass : $dbpass
--------------------------------------------------------------------------------------
IMPORTANT — POST INSTALL (REQUIRED)
After completing the web installer and logging in for the first time,
you MUST run the postinstall script:
sudo /etc/$giteauser/gitea-postinstall
This will move the instance from port 3000 to port $final_port and finalize setup.
--------------------------------------------------------------------------------------
EOF
#
# All done
#
printf "\nAll Done...\n"
#
# End of script
#

6
last-tested Normal file
View File

@@ -0,0 +1,6 @@
------------------------------------
Last tested: 23-11-2025 (DD-MM-YYYY)
Environment: Ubuntu Server 24.04 LTS
Database : MariaDB 10.11.13
Database : MySQL 8.0.44-0
------------------------------------