Fosi Fossil Service
Small teams. Open source. Built on Fossil.

CaaS / Lamina

Unified Management for Fossil Repositories

Public | Owner: CaaS | Default branch: trunk | Last update: 2026-10-25 00:41

Artifact 914f9681d3539cc6a4ce8bd5d1acc1c80730f64134aeab4122d20aa05350dbfd:


;; 平台登录页(GET/POST /login, GET /logout)—— 版式按 docs/登录页面.png
(defmodule lamina_login_pages
  (export all))

(defun show (req)
  (case (lamina_auth:verify-request req)
    (`#(ok ,_) `#(redirect "/admin"))
    (_ (lamina_ui:html 200 (page "" 'error)))))

(defun do-login (req)
  (let ((params (maps:get 'params req #M())))
    (let ((username (maps:get #"username" params #""))
          (password (maps:get #"password" params #"")))
      (case (lamina_auth:login username password)
        (`#(ok ,cookie)
         (let ((value (cookie-value-of cookie)))
           (let ((req1 (cowboy_req:set_resp_cookie
                        (lamina_auth:session-cookie-name) value req
                        (cookie-options (lamina_auth:session-ttl-seconds)))))
             (tuple 'redirect "/admin" req1))))
        (`#(error ,msg)
         (lamina_ui:html 200 (page msg 'error)))
        (other
         (lamina_ui:html 200 (page (lists:flatten (io_lib:format "登录失败: ~p" (list other))) 'error)))))))

(defun do-logout (req)
  (let ((req1 (cowboy_req:set_resp_cookie
               (lamina_auth:session-cookie-name) #"" req
               (cookie-options 0))))
    (tuple 'redirect "/" req1)))

(defun cookie-options (max-age)
  (maps:from_list
   (list (tuple 'path #"/")
         (tuple 'max_age max-age)
         (tuple 'http_only 'true)
         (tuple 'same_site 'lax)
         (tuple 'secure (secure-cookie?)))))

(defun cookie-value-of (cookie-string)
  (let ((rest (string:slice cookie-string (length "lamina_session=") (length cookie-string))))
    (hd (string:split rest ";" 'leading))))

(defun page (err session)
  (let ((site (lamina_site_mgr:all)))
    (let* ((site-name (maps:get 'site_name site "lamina"))
           (tagline (maps:get 'site_tagline site "Small teams. Open source. Built on Fossil."))
           (root (lamina_ui:trim-right-slash (maps:get 'site_url site "http://localhost:8827"))))
      (lists:flatten
       (list
        (lamina_ui:public-head (lists:flatten (list "Sign in · " site-name)))
        (lamina_ui:public-nav site-name session)
        (lamina_ui:public-tagline tagline)
        "<main class=\"login-block\"><h1>Sign in to " (lamina_ui:escape site-name) "</h1>"
        "<p class=\"lede\">Access your repositories, organizations, and Fossil-hosted projects.</p>"
        (if (=:= "" err) "" (list "<div class=\"err\">" (lamina_ui:escape err) "</div>"))
        "<form method=\"POST\" action=\"/login\">"
        "<label>Username<input name=\"username\" autocomplete=\"username\" autofocus></label>"
        "<label>Password<input type=\"password\" name=\"password\" autocomplete=\"current-password\"></label>"
        "<button class=\"btn\" type=\"submit\">Sign in</button></form>"
        "<p class=\"hint\">Need an account? Contact your administrator.</p>"
        "<p class=\"hint\">" (lamina_ui:escape site-name)
        " is designed for self-hosted teams and private infrastructure.</p>"
        "<hr class=\"rule\"><h2>Using Fossil?</h2>"
        "<p>Repository permissions and clone access are managed after signing in. Once you have an account, you can clone repositories using the Fossil command-line tool:</p>"
        (lamina_ui:clone-box (lists:flatten (list "fossil clone " root "/repository")))
        "<p class=\"hint\">See the <a href=\"https://fossil-scm.org/home/doc/trunk/www/index.wiki\">project documentation</a> for more information.</p>"
        "</main>"
        (lamina_ui:public-footer site)
        "</div></body></html>")))))

(defun secure-cookie? ()
  (let ((url (lamina_site_mgr:site-url)))
    (or (lists:prefix "https://" (lamina_ui:to-str url))
        (lists:prefix "HTTPS://" (lamina_ui:to-str url)))))

Clone

You can clone this repository with the following command:

fossil clone https://fosi.dev/CaaS/Lamina

Contribute

The best way to contribute is through e-mail. See the project documentation for more details.