Host Your Own Forgejo - Part 2

Updates

Hosting a Git Forge on the Web with Guix

Let’s pick up where we left off on the last stream and get Forgejo running in a Guix container!

Here’s what we’ll try to do:

  • Finish getting the container working correctly
  • Test out the new Forgejo instance by creating a repo and pushing to it
  • Try setting up Laminar for CI

Reference:

Previous Forgejo Package and Container Config

(use-modules (gnu)
             (guix gexp)
             (guix packages)
             (guix download)
             (guix build utils)
             (guix build-system gnu)
             (guix build-system copy)
             (nonguix build-system binary)
             ((guix licenses) #:prefix license:))

(use-package-modules version-control gcc bash certs admin linux)
(use-service-modules certbot shepherd configuration networking ssh web)

(define forgejo
  (let ((version "1.20.5-0"))
    (package
     (name "forgejo")
     (version version)
     (source
      (origin
       (method url-fetch)
       (uri (string-append "https://codeberg.org/forgejo/forgejo/releases/download/v" version "/forgejo-" version "-linux-amd64"))
       (file-name "forgejo")
       (sha256
        (base32 "0jj13qwq67acbqsina3gqi5fr2lsm80jxjmm26i1ixxhf0r0w641"))))
     (build-system binary-build-system)
     (arguments
      `(#:phases (modify-phases %standard-phases
                                (add-after 'unpack 'chmod-to-allow-patchelf
                                           (lambda _
                                             (chmod "forgejo" #o755))))
        #:install-plan `(("forgejo" "bin/"))))
     (propagated-inputs (list git git-lfs bash))
     (home-page "https://forgejo.org")
     (synopsis "A self-hosted lightweight software forge.")
     (description "Forgejo is a self-hosted lightweight software forge.")
     (license license:expat))))

(define-configuration/no-serialization forgejo-configuration
  (forgejo
   (file-like forgejo)
   "The forgejo package.")

  (domain
   (string "foo")
   "The domain name of the server.")

  (user
   (string "git")
   "The name of the user under which Forgejo will be executed.")

  (group
   (string "git")
   "The name of the group under which Forgejo will be executed."))

(define (forgejo-configuration->file config)
  (mixed-text-file "forgejo.ini" "
APP_NAME = Crafter Tools
RUN_USER = " (forgejo-configuration-user config) "
WORK_PATH = /srv/forgejo
RUN_MODE = prod

[git]
HOME_PATH = " (forgejo-configuration-user config) "

[database]
DB_TYPE = sqlite3
HOST = 127.0.0.1:3306
NAME = forgejo
USER = forgejo
PASSWD =
SCHEMA =
SSL_MODE = disable
PATH = /srv/forgejo/data/forgejo.db
LOG_SQL = false

[repository]
ROOT = /srv/forgejo/data/forgejo-repositories

[server]
SSH_DOMAIN = " (forgejo-configuration-domain config) "
DOMAIN = localhost
HTTP_PORT = 3000
ROOT_URL = https://" (forgejo-configuration-domain config) ":3000/
APP_DATA_PATH = /srv/forgejo/data
DISABLE_SSH = false
SSH_PORT = 22
LFS_START_SERVER = true
LFS_JWT_SECRET = 1_yZPWVD-sFZmGSvMjt9_eMqjiHm1V5_oWEhmw8i3IM
OFFLINE_MODE = false

[lfs]
PATH = /srv/forgejo/data/lfs

[mailer]
ENABLED = false

[service]
REGISTER_EMAIL_CONFIRM = false
ENABLE_NOTIFY_MAIL = false
DISABLE_REGISTRATION = false
ALLOW_ONLY_EXTERNAL_REGISTRATION = false
ENABLE_CAPTCHA = false
REQUIRE_SIGNIN_VIEW = false
DEFAULT_KEEP_EMAIL_PRIVATE = true
DEFAULT_ALLOW_CREATE_ORGANIZATION = true
DEFAULT_ENABLE_TIMETRACKING = true
NO_REPLY_ADDRESS = noreply.localhost

[openid]
ENABLE_OPENID_SIGNIN = true
ENABLE_OPENID_SIGNUP = true

[cron.update_checker]
ENABLED = false

[session]
PROVIDER = file

[log]
MODE = console
LEVEL = info
ROOT_PATH = /srv/forgejo/log

[repository.pull-request]
DEFAULT_MERGE_STYLE = merge

[repository.signing]
DEFAULT_TRUST_MODEL = committer

[security]
INSTALL_LOCK = true
INTERNAL_TOKEN = eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYmYiOjE3MDAyNDAwMTh9.3MFnsZWtz-Qu1I5mC1TWIXyhdGN6pDJsYE1iSugEhdM
PASSWORD_HASH_ALGO = pbkdf2_hi

[oauth2]
JWT_SECRET = DrvU6DPu8tIRVmeDfpmwLakm5m_IY13Cv00uMWaBo34
"))

;; TODO: Generate the tokens using `forgejo generate secret INTERNAL_TOKEN` etc by using a more elaborate gexp:
;; #~(begin
;;     (let* ((forgejo (forgejo-configuration-forgejo config))
;;            (forgejo-bin (file-append forgejo "/bin/forgejo"))
;;            (INTERNAL_TOKEN (system* #$forgejo-bin "generate" "secret" "INTERNAL_TOKEN")))
;;       ;; Write out the entire configuration file, inserting the generated tokens
;;       ))

(define (forgejo-shepherd-service config)
  "Return a <shepherd-service> for Forgejo with config."
  (let* ((forgejo (forgejo-configuration-forgejo config))
         (forgejo-bin (file-append forgejo "/bin/forgejo"))
         (forgejo-cfg (forgejo-configuration->file config)))
    (list (shepherd-service
           (documentation "Run the Forgejo Git forge")
           (requirement '(networking user-processes))
           (provision '(forgejo))
           (start #~(make-forkexec-constructor
                     (list #$forgejo-bin "--config" #$forgejo-cfg)
                     #:user #$(forgejo-configuration-user config)
                     #:group #$(forgejo-configuration-group config)
                     #:environment-variables (append (default-environment-variables)
                                                     (list (string-append "HOME=/home/"
                                                                          #$(forgejo-configuration-user config))))))
           (stop  #~(make-kill-destructor))))))

(define %forgejo-accounts
  (list (user-group (name "git")
                    (system? #t))
        (user-account
         (name "git")
         (group "git")
         (system? #t)
         (comment "Forgejo User")
         (home-directory "/home/git")
         ;; (shell (file-append shadow "/sbin/nologin"))
         )))

(define %forgejo-activation
  #~(begin
      (use-modules (guix build utils))
      (mkdir-p "/srv/forgejo")
      (let ((user (getpwnam "git")))
        (chown "/srv/forgejo"
               (passwd:uid user)
               (passwd:gid user)))))

(define forgejo-service-type
  (service-type (name 'forgejo)
                (extensions
                 (list (service-extension shepherd-root-service-type
                                          forgejo-shepherd-service)
                       (service-extension account-service-type
                                          (const %forgejo-accounts))
                       (service-extension activation-service-type
                                          (const %forgejo-activation))))
                (default-value (forgejo-configuration))
                (description
                 "Run Forgejo.")))

;; (define (cert-path host file)
;;   (format #f "/etc/letsencrypt/live/~a/~a.pem" host (symbol->string file)))

(operating-system
  (host-name "crafter-forge")
  (timezone "Etc/UTC")
  (locale "en_US.utf8")

  (bootloader (bootloader-configuration
               (bootloader grub-bootloader)
               (targets '("unused"))))

  (firmware '())
  (file-systems %base-file-systems)

  ;; minimal packages for remote debugging
  (packages (list coreutils bash iproute procps forgejo))

  ;; basic services
  (services (list (service dhcp-client-service-type)

                  ;; (service certbot-service-type
                  ;;          (certbot-configuration
                  ;;           (email "david@systemcrafters.net")
                  ;;           (webroot "/srv/http/certbot")
                  ;;           (certificates
                  ;;            (list
                  ;;             (certificate-configuration
                  ;;              (domains '("crafter.tools"))
                  ;;              (deploy-hook
                  ;;               (program-file
                  ;;                "nginx-deploy-hook"
                  ;;                #~(let ((pid (call-with-input-file "/var/run/nginx/pid" read)))
                  ;;                    (kill pid sighup)))))))))

                  ;; (service nginx-service-type
                  ;;          (nginx-configuration
                  ;;           (server-blocks
                  ;;            (list (nginx-server-configuration
                  ;;                   (listen '("443 ssl http2"
                  ;;                             "[::]:443 ssl http2"
                  ;;                             "8448 ssl http2"
                  ;;                             "[::]:8448 ssl http2"))
                  ;;                   (server-name '("crafter.tools"))
                  ;;                   (root "/srv/http/crafter.tools")
                  ;;                   (ssl-certificate (cert-path "crafter.tools" 'fullchain))
                  ;;                   (ssl-certificate-key (cert-path "crafter.tools" 'privkey))
                  ;;                   (raw-content '("client_max_body_size 20m;"))
                  ;;                   (locations
                  ;;                    (list (nginx-location-configuration
                  ;;                           (uri "/_matrix/")
                  ;;                           ;; note: this must be the same as the conduit port!
                  ;;                           (body '("proxy_pass http://127.0.0.1:6167$request_uri;"
                  ;;                                   "proxy_set_header host $http_host;"
                  ;;                                   "proxy_buffering off;"
                  ;;                                   "proxy_read_timeout 5m;"))))))))))

                  (service syslog-service-type
                           (syslog-configuration))

                  (service forgejo-service-type
                           (forgejo-configuration
                            (domain "crafter.tools"))))))

run.sh

#!/bin/sh

cd /root

mkdir -p /var/data/letsencrypt

$(guix time-machine -C channels.scm -- system container --network --share=/var/data=/srv --share=/var/data/letsencrypt=/etc/letsencrypt config.scm)

forge.service

[Unit]
Description=Forge Service
Wants=guix-daemon.service

[Service]
ExecStart=/bin/sh -c /root/run.sh

[Install]
WantedBy=multi-user.target

Setting up the Host VM

# Unblock port 80
echo net.ipv4.ip_unprivileged_port_start=80 >> /etc/sysctl.conf
sysctl --system

# Configure the firewall
# TODO: How to persist across reboots?
ufw enable
ufw allow http
ufw allow https

# Set up the service
mkdir -p /var/data/certs
ln -sf /root/cons.service /etc/systemd/system/cons.service

# Run the service for the first time to generate certificates
/root/run.sh
guix container exec 6623 /var/lib/certbot/renew-certificates

# Run the service for real
systemctl enable cons.service
systemctl start cons.service
Subscribe to the System Crafters Newsletter!
Stay up to date with the latest System Crafters news and updates! Read the Newsletter page for more information.
Name (optional)
Email Address