{"id":13741950,"url":"https://github.com/Honestpuck/scriptorium","last_synced_at":"2025-05-08T22:32:49.602Z","repository":{"id":43660471,"uuid":"352070948","full_name":"Honestpuck/scriptorium","owner":"Honestpuck","description":"A utility for managing the scripts in Jamf Pro","archived":false,"fork":false,"pushed_at":"2023-07-26T03:40:55.000Z","size":85,"stargazers_count":52,"open_issues_count":0,"forks_count":6,"subscribers_count":10,"default_branch":"main","last_synced_at":"2024-11-15T12:37:11.350Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Honestpuck.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2021-03-27T12:46:31.000Z","updated_at":"2024-08-12T05:42:06.000Z","dependencies_parsed_at":"2024-01-07T18:09:32.957Z","dependency_job_id":null,"html_url":"https://github.com/Honestpuck/scriptorium","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Honestpuck%2Fscriptorium","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Honestpuck%2Fscriptorium/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Honestpuck%2Fscriptorium/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Honestpuck%2Fscriptorium/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Honestpuck","download_url":"https://codeload.github.com/Honestpuck/scriptorium/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253158581,"owners_count":21863328,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-03T04:01:04.493Z","updated_at":"2025-05-08T22:32:49.270Z","avatar_url":"https://github.com/Honestpuck.png","language":"Python","funding_links":[],"categories":["Command-line tools"],"sub_categories":["scriptorium"],"readme":"# scriptorium\n\nA utility for managing the scripts in Jamf Pro. It provides backup, change tracking, and an easier edit process. \n\nThe system comprises two git repos in two local directories that should echo the scripts in your Jamf Pro server. One git repo stores the entire record from the server in XML format and the other stores the actual script as text. The script has the same name in all three spots.\n\nWith the system installed you can make changes to the scripts in the text directory and use the tools in the system to keep the three in sync.\n\nThe basic structure is `scriptorium \u003ccommand\u003e` some with required parameters and command options.\n\nThere is a `#scriptorium` channel on the MacAdmins Slack. Hit me up there if you want some help setting up. Raise an issue here if you find a bug or want a particular improvement.\n\nSee [Docker](#docker) and [Poetry](#poetry) sections for container and virtual environment usage.\n\n_Note: Bug reports and change suggestions gratefully accepted_\n\n## Commands\n\n- `add`       used to add a script to the system.\n- `commit`    `git commit` in both directories.\n- `delete`    remove a script in all three spots\n- `down`      pulls all scripts from the server\n- `git`       asks for a string and runs it as a git command in both directories\n- `list`      lists all scripts on the JP server\n- `push`      `git push` in both directories\n- `rename`    rename a script in all three spots\n- `up`        uploads all changes and additions to the server\n- `verify`    verify text against XML against Jamf server\n\n\u003c!--@Honestpuck this appears to be the formatting you're looking for above:\n```\nadd         used to add a script to the system.\ncommit      `git commit` in both directories.\ndelete      remove a script in all three spots\ndown        pulls all scripts from the server\ngit         asks for a string and runs it as a git command in both directories\nlist        lists all scripts on the JP server\npush        `git push` in both directories\nrename      rename a script in all three spots\nup          uploads all changes and additions to the server\nverify      verify text against XML against Jamf server\n```\n--\u003e\n\n## Install\n\n`scriptorium` requires the `requests` and `inquirer` libraries. They can be installed by running `pip3 install requests` and `pip3 install inquirer`.\n\nNow pull down a copy of this repo so you can keep it up to date easily. `git clone git@github.com:Honestpuck/scriptorium.git` will do the trick.\n\nAt this point you need to create the repositories, `text` and `xml` in your git store. Create them as empty (and private if you are using a public repo store such as Github).\n\nNow create a directory `scripts` on your computer, cd into it and perform a git clone to pull down both repositories. Git will tell you you just pulled blank repositories.\n\nSet the variables at the top of scriptorium to point to the two directories and the location of the prefs file containing the JSS location, user and password. The script assumes it is in the same format as the jss_importer prefs in the AutoPkg prefs file. Here is the minimum required plist:\n```xml\n\u003c?xml version=\"1.0\" encoding=\"UTF-8\"?\u003e\n\u003c!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\"\u003e\n\u003cplist version=\"1.0\"\u003e\n\u003cdict\u003e\n\t\u003ckey\u003eAPI_PASSWORD\u003c/key\u003e\n\t\u003cstring\u003epassword\u003c/string\u003e\n\t\u003ckey\u003eAPI_USERNAME\u003c/key\u003e\n\t\u003cstring\u003eaccount\u003c/string\u003e\n\t\u003ckey\u003eJSS_URL\u003c/key\u003e\n\t\u003cstring\u003ehttps://example.jamfcloud.com\u003c/string\u003e\n\u003c/dict\u003e\n\u003c/plist\u003e\n```\n\nYou can create it or add it to your existing AutoPkg preference file with:\n```bash\ndefaults write com.github.autopkg.plist API_PASSWORD 'password'\ndefaults write com.github.autopkg.plist API_USERNAME 'account'\ndefaults write com.github.autopkg.plist JSS_URL 'https://example.jamfcloud.com'\n```\n\nYou can find the spot to set the variables in scriptorium, it looks like:\n```python\n# where we stash the XML files\nxml_dir = f\"{cwd}/config/xml\"\n\n# where we stash the text files\ntxt_dir = f\"{cwd}/config/text\"\n\n# prefs file\nif platform.system() == \"Darwin\":\n    prefs_file = Path(f\"{home}/Library/Preferences/com.github.autopkg.stage.plist\")\nelif platform.system() == \"linux\" or platform.system() == \"linux2\":\n    prefs_file = Path(f\"{cwd}/config/com.github.autopkg.stage.plist\")\n```\n\nLink `scriptorium` into somewhere in your path like `/usr/local/bin`, personally I have a bin directory in my home directory just for tools like this.\n\nNow run `scriptorium down` and all the scripts will be populated on your Mac. Now send them to the upstream repositories with `scriptorium commit --message \"First commit\" --push`. You are now at point where you can use the system.\n\nThe other important command is `add`. So that you can keep everything in sync when you want to add a new script to the system you use `scriptorium add` and the script will spring into existence in all three places. It will be as good as empty, it's contents are set to `# \u003cname\u003e` where `\u003cname\u003e` is the name you have given it.\n\nThe file `_scriptorium` is a bash command completion for scriptorium. See https://github.com/Honestpuck/apple_complete for instructions on how to install it for your shell.\n\n## Work practices\n\nThe delete and rename commands do a commit by default at the moment. The problem in doing a commit is that the up command relies on changed files not being committed until after up.\n\nThe best way of solving this dilemma would be for all commits to be preceded by an up command within scriptorium. Since remove, rename and add already modify the server this problem is only triggered when you edit a script so this seems acceptable and has been implemented.\n\nYou have to run the system from the directory that holds the text files. If you do that in a shell that has a git add on you can have the prompt give you useful information. It also means you can use file name completion when you need to specify a file. I also have Visual Studio Code open to the same folder, adding the \"GitLens\" extension to VS Code gives you good feedback on when various changes were made to your script.\n\n#### Adding a script\n\nThe process of adding a script starts with running `scriptorium add`. I usually put `-d` so that the system doesn't do a commit so I can edit the script before it goes into Jam Pro. After the add completes I can edit the stub in my editor and when ready for final testing run `scriptorium commit -m \"First commit for script something.sh\" --push`. That propagates the new script through the system.\n#### Editing a script\n\nEditing a script just means opening it in your editor of choice. Once you've made your changes and saved them in your editor the process is almost identical to adding a script. Run `scriptorium commit -m \"Fixed widgets in script acme.sh\" --push`\n### Still to be done\n\nOn the roadmap is a command `modify` which will modify the aspects of the script record in Jamf that aren't the actual script such as notes and parameters.\n\nSome notes on best practices and work methods should also appear at some point. Contributions to these would be greatly appreciated.\n\nModification of the script to produce another to handle extension attributes instead of scripts is in the planning stages.\n\n### Suggestions \n\nSuggestions for changes or extensions are always appreciated.\n\n## Command Descriptions\n\n### `add`\n\n```\nusage: scriptorium add [-h] [-f FILENAME] [-c CATEGORY] [-n NOTES] [-p | -d] [-m MESSAGE] \n                       [-a | -b | -r] [-z]\n\noptional arguments:\n  -h, --help            show this help message and exit\n  -f FILENAME, --filename FILENAME\n                        name of new script\n  -c CATEGORY, --category CATEGORY\n                        category of script\n  -n NOTES, --notes NOTES\n                        note about script\n  -p, --push            do a git push after commit\n  -d, --dont-commit     don't do a commit\n  -m MESSAGE, --message MESSAGE\n                        set commit message\n  -a, --after           run script with priority 'after'\n  -b, --before          run script with priority 'before'\n  -r, --reboot          run script at reboot\n  -z, --zero            zero parameters for script\n```\n\nAdd goes through the process of adding a script in all the required spots. You end up with an almost empty script in the scripts folder, an XML file with all the bits filled in and uploaded to the server.\n\nIf you don't specify something on the command line you will be prompted for it.\n\nYou will also be prompted for the 4th to 11th parameters if you don't specify zero. When you _are_ prompted for parameters entering a blank will stop the process.\n\nBy default it will do a commit but not a push.\n\nIf you allow a commit and don't specify a commit message it will be the name of the new file.\n\n### `commit`\n\n```\nusage: scriptorium commit [-h] [-p] [-m MESSAGE]\n\noptional arguments:\n  -h, --help            show this help message and exit\n  -p, --push            do a git push after commit\n  -m MESSAGE, --message MESSAGE\n                        set commit message\n```\n\nIf you've made a number of changes with `--dont-commit` then you are going to want to perform a commit eventually. This does an `up` and `git add *` before the actual commit.\n\n### `delete`\n\n```\nusage: scriptorium delete [-h] [-p | -d] [-m MESSAGE] name\n\npositional arguments:\n  name                  name of script to remove\n\noptional arguments:\n  -h, --help            show this help message and exit\n  -p, --push            do a git push after commit\n  -d, --dont-commit     don't do a commit\n  -m MESSAGE, --message MESSAGE\n                        set commit message\n```\n\nDeletes a script from both directories and Jamf\n\n### `down`\n\n```\nusage: scriptorium down [-h] [-n] [-p | -d] [-m MESSAGE]\n\noptional arguments:\n  -h, --help            show this help message and exit\n  -n, --no-force        don't force overwrite of existing script or XML file\n  -s SCRIPT, --script SCRIPT download just one script\n```\n\nThis downloads all the scripts from the JPC server copying over the current contents.\n\n### `git`\n\n```\nusage: scriptorium git [-h] [-c COMMAND]\n\noptional arguments:\n  -h, --help  show this help message and exit\n  -c, --command quoted string containing git command\n```\n\nThe `git` command will ask for a string and run it as a git command in both directories. The string is split using `shlex`, a library that splits the string into command \"words\" honouring any quoted strings and leaving the quotes in the \"word\". An example, the string 'this that \"and that\"' will split into `['this', 'that', '\"and that\"']`. You don't need to include the `git` at the front, scriptorium will add it for you.\n\n### `list`\n\n```\nusage: scriptorium list [-h]\n\noptional arguments:\n  -h, --help  show this help message and exit\n```\n\nIt's simple, no arguments. Just lists the scripts on the server.\n\n### `push`\n\n```\nusage: scriptorium push [-h]\n\noptional arguments:\n  -h, --help  show this help message and exit\n```\n\nIf you make a number of commits without a `--push` then you will need to do it eventually. This does it in both directories at once.\n\n### `rename`\n\n```\nusage: scriptorium rename [-h] [-p | -d] [-m MESSAGE] src dst\n\npositional arguments:\n  src                   current name of script\n  dst                   new name of script\n\noptional arguments:\n  -h, --help            show this help message and exit\n  -p, --push            do a git push after commit\n  -d, --dont-commit     don't do a commit\n  -m MESSAGE, --message MESSAGE\n                        set commit message\n```\n\n### `up`\n\n```\nusage: scriptorium up [-h]\n\noptional arguments:\n  -h, --help  show this help message and exit\n```\n\nUploads all changes and additions to the server. Behind scenes it does a `git diff` in the scripts directory to get a list of changes then updates those scripts in the XMl files and uploads them. This means it must be done before any commits, which is why `scriptorium commit` does an up before the requested commit.\n\n### `verify`\n\n```\noptional arguments:\n  -h, --help            show this help message and exit\n  -d, --diff            print diff when checking every script\n  -q, --quick           Just check lists not actual text\n  -s SCRIPT, --script SCRIPT\n                        specify a single script to check\n```\nVerify does a compare of the two directories and the server. The quick option just lists the files in each location and compares the lists. If they are different it prints a diff. If you don't specify `--quick` then the actual text is compared. The normal form just prints a short message if they differ. With `--diff` a diff is printed, this can quickly become quite large. You can specify a single script to verify with `-s`.\n\n## Poetry\n\n[Poetry](https://python-poetry.org/docs/) covers three common Python hurdles: dependencies, virtual environments (`venv`), and packaging.\n\nFor `scriptorium`'s use case, it generates the `venv` and `requirements.txt`, respectively.\n\n### Usage\n```bash\n# Install\ncurl -sSL https://install.python-poetry.org | $(which python3) -\n\n# Change config\npoetry config virtualenvs.in-project true           # .venv in `pwd`\n\n# Activate virtual environment (venv)\npoetry shell\n\n# Deactivate venv\nexit  # ctrl-d\n\n# Install multiple libraries\npoetry add requests inquirer\n\n# Initialize existing project\npoetry init\n\n# Run script and exit environment\npoetry run scriptorium\n\n# Install from requirements.txt\npoetry add `cat requirements.txt`\n\n# Update dependencies\npoetry update\n\n# Remove library\npoetry remove icecream\n\n# Generate requirements.txt\npoetry export -f requirements.txt --output requirements.txt --without-hashes\n```\n\n### Alternative to Poetry\nIt's possible to create a `venv` and `requirements.txt` file with built-in Python and pip.\n\n```bash\n# create a virtual environment via python\npython3 -m venv .venv\n\n# activate virtual environment\nsource .venv/bin/activate\n\n# install dependencies\npython3 -m pip install requests inquirer\n\n# generate requirements.txt\npython3 -m pip freeze \u003e requirements.txt\n\n# exit virtual environment\ndeactivate\n```\n\n## Docker\nAs mentioned in the [MacAdmins Slack](https://macadmins.slack.com/archives/C02JJ35PZ51/p1644515156418309?thread_ts=1644273031.107999\u0026cid=C02JJ35PZ51), this section adds experimental Docker support to the `scriptorium` script.\n\nPlease feel free to reach out via DM [@pythoninthegrass](https://macadmins.slack.com/archives/D1TE80HA7).\n\n### Setup\n* The `Dockerfile` heavily borrows from a [Medium](https://luis-sena.medium.com/creating-the-perfect-python-dockerfile-51bdec41f1c8) article\n    * It creates two images:\n        * `builder-image`: scaffolds the environment and its dependencies\n        * `runner-image`:\n            * installs minimal dependencies\n            * creates user, sets permissions, copies virtualenv from builder\n            * creates a virtualenv and sets `$PATH` for Python\n            * calls `ENTRYPOINT` and/or `CMD`\n    * By discarding the first `builder-image` it reduces file size of the final image\n* To initiate the build process via `docker-compose`:\n    ```bash\n    # clean build (remove `--no-cache` to improve rebuilds with minor edits)\n    docker-compose build --no-cache --parallel\n    ```\n    * Other functionality\n        * Mounts the current working directory as a volume in the `WORKDIR`\n            * This allows for testing on the host machine while running the container\n        * Setup an interactive environment via `stdin_open` and `tty` directives set to `true`\n            * Equivalent to `docker run -it`\n            * If both are set to `false`, `docker-compose` will look for `ENTRYPOINT` and/or `CMD` in the `Dockerfile`\n### Teardown\n* Remove the container and network\n    ```bash\n    # destroy container\n    docker-compose down\n    ```\n\n### Usage\n#### docker-compose run\n* Use `scriptorium` similarly to a binary from within the repo directory\n    ```bash\n    $ docker-compose run scriptorium -h\n    usage: scriptorium [-h] {add,commit,delete,down,git,list,push,rename,up,verify} ...\n\n    options:\n    -h, --help            show this help message and exit\n\n    subcommands:\n\n    {add,commit,delete,down,git,list,push,rename,up,verify}\n        add                 add script to system\n        commit              git commit\n        delete              delete a script from system\n        down                downloads all scripts from the server\n        git                 asks for a string and runs it as a git command\n        list                lists all scripts on the server\n        push                git push\n        rename              rename a script\n        up                  upload changed scripts\n        verify              verify text against XML against Jamf server\n\n    for command help: `scriptorium \u003ccommand\u003e -h`\n    ```\n\n#### Interactive shell\n* For general testing within a shell, leave the entrypoint commented out to simply call `CMD [ \"bash\" ]`\n* Change the `Dockerfile`  to comment out `ENTRYPOINT [\"python\", \"scriptorium\"]` and `CMD [\"-h\"]`, then uncomment `CMD [ \"bash\" ]`\n    ```bash\n    # call script\n    # ENTRYPOINT [\"python\", \"scriptorium\"]\n\n    # pass help flag by default `docker run scriptorium`\n    # can be overrridden via `docker run scriptorium down`\n    # CMD [\"-h\"]\n    CMD [ \"bash\" ]\n    ```\n* Set `docker-compose.yml` to be interactive\n    ```bash\n    stdin_open: true\n    tty: true\n    ```\n* Rebuild the image\n    ```bash\n    docker-compose build --parallel\n    ```\n* Start and exec into the container\n    ```bash\n    docker-compose up --remove-orphans -d               # start container\n    docker attach scriptorium                           # exec into container\n    appuser@02f13416c25b:~/app$ python scriptorium -h   # run command inside container\n    usage: scriptorium [-h] {add,commit,delete,down,git,list,push,rename,up,verify} ...\n    ```\n \n## Further Reading\n[Basic usage | Documentation | Poetry - Python dependency management and packaging made easy](https://python-poetry.org/docs/basic-usage/)\n\n[venv — Creation of virtual environments — Python 3.7.2 documentation](https://docs.python.org/3/library/venv.html)\n\n[pip freeze - pip documentation v22.0.3](https://pip.pypa.io/en/stable/cli/pip_freeze/)\n\n[Orientation and setup | Docker Documentation](https://docs.docker.com/get-started/)\n\n[Creating the Perfect Python Dockerfile | by Luis Sena | Medium](https://luis-sena.medium.com/creating-the-perfect-python-dockerfile-51bdec41f1c8)\n\n[Dockerfile: ENTRYPOINT vs CMD - CenturyLink Cloud Developer Center](https://www.ctl.io/developers/blog/post/dockerfile-entrypoint-vs-cmd/)\n\n[Interactive shell using Docker Compose - Stack Overflow](https://stackoverflow.com/a/39150040)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FHonestpuck%2Fscriptorium","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FHonestpuck%2Fscriptorium","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FHonestpuck%2Fscriptorium/lists"}