0xGA: Artifact Content

Yet another PHP framework, but made for org-mode and geeks.

Artifact 4d4f5fda46778be66f548179313d6b57181e3893:


#!/usr/bin/env bash

if ! which python3 &> /dev/null ; then
    echo "You must have python3 installed on your computer to use Narv."
    exit 1
fi

function usage_help {
    cat <<EOF
Narv - Tiny org-mode file server

Platform: `uname -o` `uname -r`; `python3 --version`

Usage: $0 [command] [args] <appname>

Where command should be one of:

help: show general help
create: initialize a new website
start: start narv server (require root priviledges to chroot)
stop: stop narv server (require root priviledges to chroot)
restart: restart narv server (require root priviledges to chroot)

And args could be:

--dest|-d <value>: working repository. Default is pwd
--host|-H <value>: domain name of your website. Default is localhost
EOF
}

function narv_start {
    if [ -f "$WORKINGREP/var/$APPNAME.pid" ] ; then
        echo "Your Narv application is already running."
        exit 1;
    fi

    echo -n "Starting $WORKINGREP/usr/bin/$APPNAME.py"
    $WORKINGREP/usr/bin/$APPNAME.py &
    pgrep -f $APPNAME.py > $WORKINGREP/var/$APPNAME.pid
    echo "                   [OK]"
}

function narv_stop {
    if [ ! -f "$WORKINGREP/var/$APPNAME.pid" ] ; then
        echo "Your Narv application is NOT running."
        exit 1;
    fi
    echo -n "Stopping $WORKINGREP/usr/bin/$APPNAME.py"
    pkill -f $APPNAME.py
    rm $WORKINGREP/var/$APPNAME.pid
    echo "                   [OK]"
}

function narv_restart {
    if [ ! -f "$WORKINGREP/var/$APPNAME.pid" ] ; then
        echo "Your Narv application is NOT running."
        exit 1;
    fi
    echo -n "Stopping $WORKINGREP/usr/bin/$APPNAME.py"
    pkill -f $APPNAME.py
    echo "                   ..."
    echo -n "Starting $WORKINGREP/usr/bin/$APPNAME.py"
    $WORKINGREP/usr/bin/$APPNAME.py &
    pgrep -f $APPNAME.py > $WORKINGREP/var/$APPNAME.pid
    echo "                   [OK]"
}

function init_chroot {
    if [ "$UID" != "0" ] ; then
        echo "You must be root to create the chroot"
        return
    fi

    # Programs to add in the chroot for minimal functionnalities
    BINS="bash env false python3"
    echo "Populate new chroot... Please wait"

    for basecommand in $BINS ; do
        # Look for the totality of involved binaries per program
        all=`whereis -b $basecommand | cut -f2 -d":"`

        for comm in $all ; do
            # Create parent folder and copy current binary
            mkdir -p $WORKINGREP`dirname $comm` > /dev/null 2>&1
            cp -R -L "$comm" $WORKINGREP`dirname $comm`

            # Find related lib and copy them to the chroot
            for f in `ldd $comm 2>/dev/null | cut -f2 -d ">" | cut -f1 -d "(" ` ; do
                if [[ -f $f || -h $f ]]; then
                    mkdir -p $WORKINGREP`dirname $f` > /dev/null 2>&1
                    cp -R -L "$f" $WORKINGREP`dirname $f`
                fi
            done
        done
    done

    # We need this lib from threading too
    cp -R -L /usr/lib/libgcc_s.so.1 $WORKINGREP/usr/lib/

    # Creating /dev/null
    install -dm 755 $WORKINGREP/dev
    mknod $WORKINGREP/dev/null c 1 3

    # Creating urandom
    mknod -m 0444 $WORKINGREP/dev/random c 1 8
    mknod -m 0444 $WORKINGREP/dev/urandom c 1 9

    exec 6>&1 # bind fd #6 to stdout (save stdout)
    exec > $WORKINGREP/etc/passwd # redirect stdout to etc/passwd
    cat <<EOF
root:x:0:0:root:/root:/bin/bash
nobody:x:1:1:nobody:/:/bin/false
EOF

    exec > $WORKINGREP/etc/group # redirect stdout to etc/group
    cat <<EOF
root:x:0:root
nobody:x:1:
EOF

    exec 1>&6 6>&- # Restore stdout and close fd #6
    chmod 644 $WORKINGREP/etc/passwd
    chmod 644 $WORKINGREP/etc/group


    # Creating /bin for compatibility. Link MUST be local
    if [ ! -e "$WORKINGREP/bin" ] ; then
        cd $WORKINGREP
        ln -s usr/bin
    fi

    echo "Chroot created"
}


APPNAME=$USER
FOSSILFILE=/dev/null
COMMAND="help"
WORKINGREP=`pwd`
DOMAINNAME='localhost'

if [ $# -gt 0 ] ; then
    while [ -n "$1" ] ; do
        case $1 in
            --dest|-d)
                shift
                WORKINGREP=`realpath $1`
                ;;
            --fossil|-f)
                shift
                FOSSILFILE=`realpath $1`
                ;;
            --host|-H)
                shift
                DOMAINNAME=$1
                ;;
            console|create|init-chroot|restart|start|stop|-h|'help')
                COMMAND=$1
                ;;
            *)
                APPNAME=$1
                ;;
        esac
        shift
    done
fi

if [ "$WORKINGREP" = "/" ] ; then
    echo ""
    echo "DO NOT RUN THIS SCRIPT AT THE ROOT OF YOUR SYSTEM, YOU FOOL"
    echo ""
    echo "Please choose a more appropriate destination"
    echo "like /srv/http, /home/whatever/public_html or /var/www"
    exit 1
fi

case $COMMAND in
    -h|'help')
        usage_help
        ;;
    create)
        if [ -z $APPNAME -o -z $DOMAINNAME ]; then
            echo
            echo "Please provide necessary informations:"
            echo
            echo "--dest|-d: Destination folder of your installation"
            echo "--host|-H: Domain name of your application"
            echo "<appname>: Name of your application"
            echo
            echo "If you have any doubt, please see help"
            echo "$0 help"
        fi

        echo ":: configuring new Website"
        install -dm 755 $WORKINGREP/home/$APPNAME/.themes
        install -dm 755 $WORKINGREP/srv/$APPNAME

        exec 6>&1 # bind fd #6 to stdout (save stdout)
        exec > $WORKINGREP/etc/rc.conf # redirect stdout to etc/rc.conf
        cat <<EOF
[general]
debug = info

[$DOMAINNAME]
something = wonderfull
EOF

        exec > $WORKINGREP/etc/routes.conf
        cat <<EOF
[$DOMAINNAME]
/ = my_first_custom_path
EOF

        exec > $WORKINGREP/usr/bin/$APPNAME.py
        cat <<EOF
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

from narv import IcanDoThat
from narv import Narv

class MyBeautifulApp(IcanDoThat):

    appname = '$APPNAME'

    def my_first_custom_path(self):
        self.extension = 'html'
        return b'<h1>Hello World!</h1>'


if __name__ == "__main__":
    app = Narv(('', 8000), MyBeautifulApp)
    app.start()
EOF

        exec > $WORKINGREP/var/$APPNAME.el
        cat <<EOF
;;; Org-mode configuration

;;; Default to HTML5
(setq org-html-doctype "html5")
(setq org-html-html5-fancy t)

;;; Disable default org style (you DO want to use your own style)
(setq org-html-head-include-default-style nil)

;;; Project configuration
(setq org-publish-project-alist
      '(
        ("${APPNAME}-org"
         :base-directory "${WORKINGREP}/home/$APPNAME"
         :recursive t
         :publishing-function (org-html-publish-to-html org-org-publish-to-org)
         :publishing-directory "${WORKINGREP}/srv/$APPNAME"
         :base-extension "org"
;;       :language "fr"                     ;; Your language?
;;       :headline-levels 3                 ;; You can customize
;;       :section-numbers nil               ;; the following lines too
;;       :table-of-contents nil
;;       :html-preamble "<h1>My Blog</h1>"
;;       :html-postamble "<p>Still reading?</p>"
;;       :html-head "<link rel=\"stylesheet\" href=\"/.themes/style.css\" type=\"text/css\"/>"
         )
        ("${APPNAME}-data"
         :base-directory "${WORKINGREP}/home/$APPNAME"
         :recursive t
         :base-extension "png\\\|jpg\\\|pdf\\\|ogg"
         :publishing-function org-publish-attachment
         :publishing-directory "${WORKINGREP}/srv/$APPNAME")
        ("${APPNAME}-style"
         :base-directory "${WORKINGREP}/home/${APPNAME}/.themes"
         :recursive t
         :base-extension "css\\\|js\\\|png\\\|jpg\\\|otf"
         :publishing-function org-publish-attachment
         :publishing-directory "${WORKINGREP}/srv/${APPNAME}/.themes")
        ("$APPNAME" :components ("${APPNAME}-org" "${APPNAME}-data" "${APPNAME}-style"))))


;;; Utility functions. DO NOT MODIFY !

(defun ed/orgx-get-file-title ()
  (with-current-buffer (current-buffer)
    (save-excursion
      (goto-char (point-min))
      (if (re-search-forward "#\\+title: ?\\(.*\\)" nil t)
          (match-string 1)
        ""))))

(defun ed/orgx-get-file-name ()
  (when (string= (substring (buffer-name) 0 11) "content.org")
    (file-name-nondirectory (directory-file-name (file-name-directory buffer-file-name)))))

(defun asciify-string (string)
  "Convert STRING to ASCII string.
For example:
“passé” becomes “passe”"
  ;; Code originally by Teemu Likonen found on
  ;; http://ergoemacs.org/emacs/emacs_zap_gremlins.html
  (with-temp-buffer
    (insert string)
    (call-process-region (point-min) (point-max) "iconv" t t nil "--to-code=ASCII//TRANSLIT")
    (buffer-substring-no-properties (point-min) (point-max))))

(defun ed/orgx-get-permalink (title)
  (interactive "sTitle: ")
  (message
   (replace-regexp-in-string
    "--+" "-"
    (replace-regexp-in-string
     "^-+\\|-+$" ""
     (replace-regexp-in-string
      "[^a-z]" "-" (downcase (asciify-string title)))))))

(defun ed/orgx-parse-links ()
  (save-excursion
    (goto-char (point-min))
    (let* ((base-path (file-name-directory buffer-file-name))
           (media-folder (file-name-as-directory (concat base-path "media"))))
      (while (re-search-forward org-any-link-re nil t)
        (let* ((link-url (match-string 2))
               (file-path (when (and (stringp link-url)
                                     (string= (substring link-url 0 5) "file:"))
                            (substring link-url 5)))
               (file-name (when (and (stringp file-path)
                                     (file-exists-p file-path)
                                     (file-readable-p file-path))
                            (file-name-nondirectory file-path)))
               (dest-file (when (stringp file-name)
                            (concat media-folder file-name))))
          (when (and (stringp dest-file)
                     (not (file-exists-p dest-file)))
            (copy-file file-path dest-file)
            (save-excursion
              (search-backward link-url)
              (replace-match (concat media-folder file-name)))
            (message (concat file-path " copied to " dest-file))))))))

(defun ed/orgx-propagate-save ()
  (let* ((base-path (file-name-directory buffer-file-name))
         (meta-file (concat base-path "meta.conf"))
         (file-title (ed/orgx-get-file-title)))
    (when (and (string= (file-name-nondirectory buffer-file-name) "content.org")
               (file-exists-p meta-file)
               (file-writable-p meta-file))
      (ed/orgx-parse-links)
      (with-current-buffer (find-file-noselect meta-file)
        (goto-char (point-min))
        (when (re-search-forward "title=.*" nil t)
          (replace-match (concat "title=\"" file-title "\""))
          (save-buffer 0))
        (kill-buffer))
      (message (concat "Saved " (ed/orgx-get-file-name))))))
(add-hook 'after-save-hook 'ed/orgx-propagate-save)

(defun ed/orgx-create-file-structure (title destination &optional buffer)
  (unless (file-exists-p (directory-file-name destination))
    (make-directory destination t)
    (make-directory (concat destination "media"))
    (make-directory (concat destination "templates"))
    (let ((timestamp (current-time)))
      (with-current-buffer (find-file-noselect (concat destination "meta.conf"))
        (insert "author=\"" (user-full-name) "\"\ntimestamp="
                (format-time-string "%Y%m%d%H%M%S" timestamp)
                "\"\ntitle=\"" title "\"")
        (save-buffer 0)
        (kill-buffer))
      (when buffer
        (with-current-buffer buffer
          (ed/orgx-parse-links)
          (copy-file buffer-file-name
                     (concat destination "content.org"))))
      (with-current-buffer (find-file (concat destination "content.org"))
        (unless buffer
          (goto-char 1)
          (insert "#+title: " title "\n#+date: "
                  (format-time-string "<%Y-%m-%d %a %H:%M>" timestamp)
                  "\n\n"))))
    (message (concat "Opened " (ed/orgx-get-permalink title)))))

(defun ed/orgx-create-file (dir filename)
  "Creating a new orgx file and open it"
  (interactive "Ddirectory where to save your new orgx file: \nsname of your new orgx file: ")
  (let ((destination
         (file-name-as-directory
          (concat (file-name-as-directory dir)
                  (ed/orgx-get-permalink filename)))))
    (ed/orgx-create-file-structure filename destination)))

(defun ed/orgx-create-from-buffer (dir)
  "Creating a new orgx file from the current buffer"
  (interactive "Ddirectory where to save your new orgx file: ")
  (let* ((file-title (ed/orgx-get-file-title))
         (filename (if (string= file-title "")
                       (file-name-sans-extension (buffer-name))
                     (ed/orgx-get-permalink file-title)))
         (destination
          (file-name-as-directory (concat (file-name-as-directory dir) filename))))
    (ed/orgx-create-file-structure file-title destination (current-buffer))))

(defun ed/orgx-open-file (dir)
  "Open an orgx file"
  (interactive "Dorgx file to open: ")
  (let* ((file-path (file-name-as-directory dir))
         (meta-file (concat file-path "meta.conf"))
         (content-file (concat file-path "content.org")))
    (if (and (file-exists-p meta-file)
             (file-writable-p meta-file)
             (file-exists-p content-file)
             (file-writable-p content-file))
        (progn
          (find-file content-file)
          (message (concat "Opened " (ed/orgx-get-file-name))))
      (message (concat "Not a valid orgx file: " dir)))))
EOF

        exec 1>&6 6>&- # Restore stdout and close fd #6

        chmod u+x $WORKINGREP/usr/bin/$APPNAME.py

        echo "Application installation finished."
        echo ":: A sample emacs configuration file has been created in
:: $WORKINGREP/$APPNAME.el
:: Feel free to customize it."
        echo
        echo ":: Sample configuration files has been put in
:: $WORKINGREP/etc/
:: Don't forget to review and customize them."
        echo
        echo ":: You may start your new app now by entering the following command:"
        echo
        echo "$0 start"
        ;;
    start)
        narv_start
        ;;
    stop)
        narv_stop
        ;;
    restart)
        narv_restart
        ;;
    init-chroot)
        init_chroot
        ;;
    console)
        cat <<EOF
Narv - Tiny org-mode file server - Console
Platform: `uname -o` `uname -r`; `python3 --version`
EOF
        while [ 0 ] ; do
            echo -n ">> "
            read SUBCOMMAND

            case $SUBCOMMAND in
                'help')
                    usage_help
                    ;;
                'bye'|'exit'|quit)
                    exit 0
                    ;;
                start)
                    narv_start
                    ;;
                stop)
                    narv_stop
                    ;;
                restart)
                    narv_restart
                    ;;
                init-chroot)
                    init_chroot
                    ;;
                *)
                    echo "Wrong command $SUBCOMMAND. Try help"
            esac
        done
        ;;
    *)
        echo "There's something strange
in your neighborhood.
Who ya gonna call?"
        ;;
esac