aboutsummaryrefslogtreecommitdiff
path: root/roles
diff options
context:
space:
mode:
Diffstat (limited to 'roles')
-rw-r--r--roles/cgit/handlers/main.yml5
-rw-r--r--roles/cgit/tasks/main.yml124
-rw-r--r--roles/cgit/templates/cgitrc.j211
-rw-r--r--roles/cgit/templates/site-cgit.conf.j260
4 files changed, 196 insertions, 4 deletions
diff --git a/roles/cgit/handlers/main.yml b/roles/cgit/handlers/main.yml
new file mode 100644
index 0000000..e4af55c
--- /dev/null
+++ b/roles/cgit/handlers/main.yml
@@ -0,0 +1,5 @@
+---
+- name: Restart fcgiwrap
+ ansible.builtin.systemd:
+ name: fcgiwrap.service
+ state: restarted
diff --git a/roles/cgit/tasks/main.yml b/roles/cgit/tasks/main.yml
index 44f9459..f454d93 100644
--- a/roles/cgit/tasks/main.yml
+++ b/roles/cgit/tasks/main.yml
@@ -4,17 +4,125 @@
name:
- cgit
- fcgiwrap
- - python3-pygments
+ - git
+ - python3-docutils
- python3-markdown
+ - python3-pygments
+ state: present
+
+- name: Ensure dedicated git system group exists
+ ansible.builtin.group:
+ name: "{{ cgit_group }}"
+ system: true
+ state: present
+
+- name: Ensure dedicated git system user exists
+ ansible.builtin.user:
+ name: "{{ cgit_user }}"
+ group: "{{ cgit_group }}"
+ home: "{{ cgit_repos_path }}"
+ shell: /usr/bin/git-shell
+ comment: "Git version control user"
+ system: true
+ create_home: true
state: present
-- name: Ensure Git repositories root directory exists
+- name: Add www-data to git group so Nginx/fcgiwrap can read repositories
+ ansible.builtin.user:
+ name: www-data
+ groups: "{{ cgit_group }}"
+ append: true
+ notify: Restart fcgiwrap
+
+- name: Ensure Git repositories root directory permissions (setgid for group inheritance)
ansible.builtin.file:
path: "{{ cgit_repos_path }}"
state: directory
+ owner: "{{ cgit_user }}"
+ group: "{{ cgit_group }}"
+ mode: "02750"
+
+- name: Configure git user safe.directory for cgit (www-data)
+ ansible.builtin.copy:
+ dest: /var/www/.gitconfig
owner: www-data
group: www-data
+ mode: "0644"
+ content: |
+ [safe]
+ directory = *
+
+- name: Ensure git-shell-commands directory exists (disables interactive shell login and provides helpers)
+ ansible.builtin.file:
+ path: "{{ cgit_repos_path }}/git-shell-commands"
+ state: directory
+ owner: root
+ group: "{{ cgit_group }}"
+ mode: "0755"
+
+- name: Deploy git-shell helper commands (no-interactive-login, init, list)
+ ansible.builtin.copy:
+ dest: "{{ cgit_repos_path }}/git-shell-commands/{{ item.name }}"
+ owner: root
+ group: "{{ cgit_group }}"
mode: "0755"
+ content: "{{ item.content }}"
+ loop:
+ - name: no-interactive-login
+ content: |
+ #!/bin/sh
+ printf '%s\n' "Hi! You've successfully authenticated as '{{ cgit_user }}', but interactive shell access is disabled."
+ printf '%s\n' "Tip: Run 'ssh {{ cgit_user }}@{{ primary_domain }} init <repo>.git [description]' to create a new repository."
+ exit 128
+ - name: list
+ content: |
+ #!/bin/sh
+ find "{{ cgit_repos_path }}" -maxdepth 2 -name "*.git" -type d -printf "%P\n" | sort
+ - name: init
+ content: |
+ #!/bin/sh
+ set -e
+ REPO="$1"
+ DESC="$2"
+ if [ -z "$REPO" ]; then
+ echo "Usage: ssh {{ cgit_user }}@{{ primary_domain }} init <repo-name>.git [description]"
+ exit 1
+ fi
+ case "$REPO" in
+ *.git) ;;
+ *) REPO="${REPO}.git" ;;
+ esac
+ TARGET="{{ cgit_repos_path }}/${REPO}"
+ if [ -d "$TARGET" ]; then
+ echo "Error: Repository ${REPO} already exists."
+ exit 1
+ fi
+ git init --bare --shared=group "$TARGET"
+ if [ -n "$DESC" ]; then
+ echo "$DESC" > "$TARGET/description"
+ else
+ echo "${REPO%.git}" > "$TARGET/description"
+ fi
+ echo "Initialized bare repository at {{ cgit_user }}@{{ primary_domain }}:${REPO}"
+
+- name: Ensure .ssh directory exists for git user
+ ansible.builtin.file:
+ path: "{{ cgit_repos_path }}/.ssh"
+ state: directory
+ owner: "{{ cgit_user }}"
+ group: "{{ cgit_group }}"
+ mode: "0700"
+
+- name: Configure authorized SSH keys for git user
+ ansible.builtin.copy:
+ dest: "{{ cgit_repos_path }}/.ssh/authorized_keys"
+ owner: "{{ cgit_user }}"
+ group: "{{ cgit_group }}"
+ mode: "0600"
+ content: |
+ {% for key in cgit_ssh_authorized_keys %}
+ {{ key }}
+ {% endfor %}
- name: Deploy /etc/cgitrc configuration
ansible.builtin.template:
@@ -24,11 +132,19 @@
group: root
mode: "0644"
-- name: Ensure fcgiwrap socket/service is enabled and running
+- name: Ensure fcgiwrap socket and service are enabled and running
ansible.builtin.systemd:
- name: fcgiwrap.socket
+ name: "{{ item }}"
enabled: true
state: started
+ loop:
+ - fcgiwrap.socket
+ - fcgiwrap.service
+
+- name: Check if TLS certificate exists for {{ cgit_domain }}
+ ansible.builtin.stat:
+ path: "/etc/letsencrypt/live/{{ cgit_domain }}/fullchain.pem"
+ register: cgit_tls_cert
- name: Deploy Nginx virtual host for cgit
ansible.builtin.template:
diff --git a/roles/cgit/templates/cgitrc.j2 b/roles/cgit/templates/cgitrc.j2
index 0cae2ab..6e9cbd0 100644
--- a/roles/cgit/templates/cgitrc.j2
+++ b/roles/cgit/templates/cgitrc.j2
@@ -7,11 +7,21 @@ root-title={{ cgit_title }}
root-desc={{ cgit_description }}
virtual-root=/
+{% if cgit_enable_http_clone | default(true) %}
+enable-http-clone=1
+clone-url=https://{{ cgit_domain }}/$CGIT_REPO_URL git@{{ primary_domain }}:$CGIT_REPO_URL
+{% else %}
+enable-http-clone=0
+clone-url=git@{{ primary_domain }}:$CGIT_REPO_URL
+{% endif %}
+
enable-index-links=1
enable-index-owner=0
enable-commit-graph=1
enable-log-filecount=1
enable-log-linecount=1
+enable-git-config=1
+remove-suffix=1
snapshots=tar.gz zip
about-filter=/usr/lib/cgit/filters/about-formatting.sh
@@ -23,4 +33,5 @@ readme=:README.rst
readme=:README.txt
readme=:README
+# Scan path must be placed after global repository defaults
scan-path={{ cgit_repos_path }}
diff --git a/roles/cgit/templates/site-cgit.conf.j2 b/roles/cgit/templates/site-cgit.conf.j2
index 557c364..36753d3 100644
--- a/roles/cgit/templates/site-cgit.conf.j2
+++ b/roles/cgit/templates/site-cgit.conf.j2
@@ -1,3 +1,55 @@
+{% if cgit_tls_cert.stat.exists | default(false) %}
+server {
+ server_name {{ cgit_domain }};
+
+ # Security headers
+ add_header X-Frame-Options "SAMEORIGIN" always;
+ add_header X-Content-Type-Options "nosniff" always;
+ add_header Referrer-Policy "strict-origin-when-cross-origin" always;
+ add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
+
+ root /usr/share/cgit;
+ try_files $uri @cgit;
+
+ # Serve static cgit assets directly
+ location ~* ^/cgit-css/(.*)$ {
+ alias /usr/share/cgit/$1;
+ expires 30d;
+ }
+
+ # Block git-Receive-pack over HTTP (pushing is strictly via SSH as git@)
+ location ~ ^/.*/git-receive-pack$ {
+ return 403;
+ }
+
+ location @cgit {
+ include fastcgi_params;
+ fastcgi_param SCRIPT_FILENAME /usr/lib/cgit/cgit.cgi;
+ fastcgi_param PATH_INFO $uri;
+ fastcgi_param QUERY_STRING $args;
+ fastcgi_param HTTP_HOST $server_name;
+ fastcgi_pass unix:/run/fcgiwrap.socket;
+ }
+
+ listen 443 ssl; # managed by Certbot / Ansible
+ listen [::]:443 ssl; # managed by Certbot / Ansible
+ ssl_certificate /etc/letsencrypt/live/{{ cgit_domain }}/fullchain.pem;
+ ssl_certificate_key /etc/letsencrypt/live/{{ cgit_domain }}/privkey.pem;
+ include /etc/letsencrypt/options-ssl-nginx.conf;
+ ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
+}
+
+server {
+ if ($host = {{ cgit_domain }}) {
+ return 301 https://$host$request_uri;
+ }
+
+ listen 80;
+ listen [::]:80;
+ server_name {{ cgit_domain }};
+ return 404;
+}
+{% else %}
server {
listen 80;
listen [::]:80;
@@ -6,8 +58,15 @@ server {
root /usr/share/cgit;
try_files $uri @cgit;
+ # Serve static cgit assets directly
location ~* ^/cgit-css/(.*)$ {
alias /usr/share/cgit/$1;
+ expires 30d;
+ }
+
+ # Block git-receive-pack over HTTP (pushing is strictly via SSH as git@)
+ location ~ ^/.*/git-receive-pack$ {
+ return 403;
}
location @cgit {
@@ -19,3 +78,4 @@ server {
fastcgi_pass unix:/run/fcgiwrap.socket;
}
}
+{% endif %}