Adding Scripts

Joe Kaiser edited this page Feb 19, 2018 · 8 revisions

Adding Scripts

Whatever you can do in Linux, you can do in a Cart across all your installs.

When a machine comes up, it should be exactly the way you want at first boot. Stacki should be used to bring machines to a known state. One of the ways to do that is run the scripts and start the services that need to be available during the install. It's part of the secret to scaling: offload as much work to the individual machines themselves.

Scripts

Scripts are similar to config files, except you might want to run them during installation or after first boot.

There are two ways to add/run scripts:

  • Adding them in a file and then running them
  • Running them in a <stack:script> </stack:script> tags.

Starting/enabling services

In CentOS/RHEL 7.x and SLES 12, systemd is the init script runner. If a service runs as a daemon, these can be enabled during installation and will start on first boot.

For example: Let's say we want to run a web-farm and httpd needs to be started on every machine. Let's make sure httpd is installed and gets started on every machine in our site-custom cart.

<stack:stack>
<!-- add httpd -->
<stack:package>httpd</stack:package>
<!-- enable httpd will autostart on first boot -->
<stack:script stack:stage="install-post">
systemctl enable httpd
</stack:script>
</stack:stack>

Add a script to be run during install.

You can run scripts to take care of configuration during the install phase.

They can be run as straight shell code or you can put them in a file and then run the file.

Let's say I want to add some administrative users and allow them to sudo without a tty.

<stack:script stack:stage="install-post">
<!-- just add them -->
groupadd -g 405 siteadmin
useradd -u 405 -g 405 siteadmin someperson1
useradd -u 406 -g 405 siteadmin someperson1
sed -i /"Defaults requiretty"/\c"#Defaults requiretty" /etc/sudoers
echo "%siteadmin ALL=(ALL) NOPASSWD: ALL" &gt; /etc/sudoers.d/siteadmin
</stack:script>

The scripts run bash in order they're compiled by Stacki. I could do this another way by adding it to a script in a stack:file tags and running the script:

<stack:script stack:stage="install-post">
<stack:file name="/tmp/addadmins.sh" perms="0755">
groupadd -g 405 siteadmin
useradd -u 405 -g 405 siteadmin someperson1
useradd -u 406 -g 405 siteadmin someperson1
sed -i /"Defaults requiretty"/\c"#Defaults requiretty" /etc/sudoers
</stack:file>
/tmp/addadmins.sh
<stack:file name="/etc/sudoers.d/siteadmin"
%siteadmin ALL=(ALL) NOPASSWD: ALL
</stack:admin>
</stack:script>

So we put the code of adding admins into /tmp/addadmins.sh with executable permissions and then we run it. I also pulled the "echo" into file tags. That is more Stacki idiomatic.

If a file or directory does not exist in a file tag, the directory and the file will be created without having to do additional "mkdirs"

First boot scripts

If your scripts need full network and services, then you can run them at first boot. Still using <stack:script> tags but changing the stack:stage in that tag.

<stack:script stack:stage="boot-post">
echo "Finished `date +'%H:%M %d-%b-%Y'`" &gt;&gt; /etc/motd
echo "finis=`date +'%s'`" &gt;&gt; /tmp/time.sh
<stack:file stack:name="/tmp/time.sh" stack:mode="append" stack:perms="755">
installed=`echo "$(((finis - built)/60))"`
echo "Installed in ${installed} minutes" &gt;&gt; /etc/motd
</stack:file>
/tmp/time.sh
</stack:script>

This script runs during first-boot after all the network and other services are up.

Note a few things:

  • The "stack:stage" is now "boot-post" rather than "install-post" - it runs at first boot.
  • There is a mix of shell commands plus adding a file to be run.
  • Echo some things into /etc/motd. Note the "date" commands - it's just shell.
  • Append to a time.sh script that will do some math for me.
  • Run the file.

This produces output that tell me how long it takes to install a backend node. This mix and matching of tags tells the installer how to behave.

Run a different shell interpreter

Really whenever you create a set of <stack:script> tags, you're running with the bash interpreter. You can do more difficult scripts during installation by switching interpreters, python, perl, ksh, csh, tcsh, javascript,ruby, on and on and on.

Do it like this:

<stack:script stack:interpreter="/usr/bin/python">
import os
tlvs = ["portDesc", "sysName", "sysDesc", "sysCap",
"mngAddr", "macPhyCfg", "powerMdi", "linkAgg",
"MTU", "LLDP-MED", "medCap", "medPolicy",
"medLoc", "medPower", "medHwRev", "medFwRev",
"medSwRev", "medSerNum", "medManuf", "medModel",
"medAssetID", "CIN-DCBX", "CEE-DCBX", "evbCfg",
"vdp", "IEEE-DCBX", "ETS-CFG", "ETS-REC",
"PFC", "APP", "PVID", "PPVID", "vlanName",
"ProtoID", "vidUsage", "mgmtVID", "linkAggr", "uPoE"]
lldp='/usr/sbin/lldptool'
def setLLDP(iface):
os.system("%s -L -i %s adminStatus=rxtx" % (lldp,iface))
os.system("%s -i %s -T -V chassisID " % (lldp,iface) +
"subtype=CHASSIS_ID_NETWORK_ADDRESS")
for tlv in tlvs:
cmd = "%s -T -i %s -V %s " % (lldp,iface,tlv)
cmd += "-c enableTx=yes &gt;&gt; /root/lldp.log 2&gt;&amp;1"
os.system(cmd)
ifaces = os.listdir("/sys/class/net")
ifaces.remove('lo')
for iface in ifaces:
setLLDP(iface)
</stack:script>

I'm setting up lldp, and I want to initialize the interfaces. I don't know what capabilities they have, so I'm just going to enable everything and whatever happens, happens. I could be more exact about this if I really wanted to. I'm using the 'interpreter="/usr/bin/python"' to tell the post tag to use python.

It's the equivalent of a shebang at the beginning of a python script file.

#!/usr/bin/python

So if you have perl/python/lua/ruby scripts you've written or someone else has, call the interpreter you need, and put the script in-between <stack:script> tags with <stack:interpreter=/path/to/perl>.

Further Reading

We have touched the surface of what you can do to configure backend machines. See the Stacki Universal XML documentation for a more complete listing of SUX syntax.

Clone this wiki locally

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Adding Scripts

Joe Kaiser edited this page Feb 19, 2018 · 8 revisions

Adding Scripts

Whatever you can do in Linux, you can do in a Cart across all your installs.

When a machine comes up, it should be exactly the way you want at first boot. Stacki should be used to bring machines to a known state. One of the ways to do that is run the scripts and start the services that need to be available during the install. It's part of the secret to scaling: offload as much work to the individual machines themselves.

Scripts

Scripts are similar to config files, except you might want to run them during installation or after first boot.

There are two ways to add/run scripts:

  • Adding them in a file and then running them
  • Running them in a <stack:script> </stack:script> tags.

Starting/enabling services

In CentOS/RHEL 7.x and SLES 12, systemd is the init script runner. If a service runs as a daemon, these can be enabled during installation and will start on first boot.

For example: Let's say we want to run a web-farm and httpd needs to be started on every machine. Let's make sure httpd is installed and gets started on every machine in our site-custom cart.

<stack:stack>
<!-- add httpd -->
<stack:package>httpd</stack:package>
<!-- enable httpd will autostart on first boot -->
<stack:script stack:stage="install-post">
systemctl enable httpd
</stack:script>
</stack:stack>

Add a script to be run during install.

You can run scripts to take care of configuration during the install phase.

They can be run as straight shell code or you can put them in a file and then run the file.

Let's say I want to add some administrative users and allow them to sudo without a tty.

<stack:script stack:stage="install-post">
<!-- just add them -->
groupadd -g 405 siteadmin
useradd -u 405 -g 405 siteadmin someperson1
useradd -u 406 -g 405 siteadmin someperson1
sed -i /"Defaults requiretty"/\c"#Defaults requiretty" /etc/sudoers
echo "%siteadmin ALL=(ALL) NOPASSWD: ALL" &gt; /etc/sudoers.d/siteadmin
</stack:script>

The scripts run bash in order they're compiled by Stacki. I could do this another way by adding it to a script in a stack:file tags and running the script:

<stack:script stack:stage="install-post">
<stack:file name="/tmp/addadmins.sh" perms="0755">
groupadd -g 405 siteadmin
useradd -u 405 -g 405 siteadmin someperson1
useradd -u 406 -g 405 siteadmin someperson1
sed -i /"Defaults requiretty"/\c"#Defaults requiretty" /etc/sudoers
</stack:file>
/tmp/addadmins.sh
<stack:file name="/etc/sudoers.d/siteadmin"
%siteadmin ALL=(ALL) NOPASSWD: ALL
</stack:admin>
</stack:script>

So we put the code of adding admins into /tmp/addadmins.sh with executable permissions and then we run it. I also pulled the "echo" into file tags. That is more Stacki idiomatic.

If a file or directory does not exist in a file tag, the directory and the file will be created without having to do additional "mkdirs"

First boot scripts

If your scripts need full network and services, then you can run them at first boot. Still using <stack:script> tags but changing the stack:stage in that tag.

<stack:script stack:stage="boot-post">
echo "Finished `date +'%H:%M %d-%b-%Y'`" &gt;&gt; /etc/motd
echo "finis=`date +'%s'`" &gt;&gt; /tmp/time.sh
<stack:file stack:name="/tmp/time.sh" stack:mode="append" stack:perms="755">
installed=`echo "$(((finis - built)/60))"`
echo "Installed in ${installed} minutes" &gt;&gt; /etc/motd
</stack:file>
/tmp/time.sh
</stack:script>

This script runs during first-boot after all the network and other services are up.

Note a few things:

  • The "stack:stage" is now "boot-post" rather than "install-post" - it runs at first boot.
  • There is a mix of shell commands plus adding a file to be run.
  • Echo some things into /etc/motd. Note the "date" commands - it's just shell.
  • Append to a time.sh script that will do some math for me.
  • Run the file.

This produces output that tell me how long it takes to install a backend node. This mix and matching of tags tells the installer how to behave.

Run a different shell interpreter

Really whenever you create a set of <stack:script> tags, you're running with the bash interpreter. You can do more difficult scripts during installation by switching interpreters, python, perl, ksh, csh, tcsh, javascript,ruby, on and on and on.

Do it like this:

<stack:script stack:interpreter="/usr/bin/python">
import os
tlvs = ["portDesc", "sysName", "sysDesc", "sysCap",
"mngAddr", "macPhyCfg", "powerMdi", "linkAgg",
"MTU", "LLDP-MED", "medCap", "medPolicy",
"medLoc", "medPower", "medHwRev", "medFwRev",
"medSwRev", "medSerNum", "medManuf", "medModel",
"medAssetID", "CIN-DCBX", "CEE-DCBX", "evbCfg",
"vdp", "IEEE-DCBX", "ETS-CFG", "ETS-REC",
"PFC", "APP", "PVID", "PPVID", "vlanName",
"ProtoID", "vidUsage", "mgmtVID", "linkAggr", "uPoE"]
lldp='/usr/sbin/lldptool'
def setLLDP(iface):
os.system("%s -L -i %s adminStatus=rxtx" % (lldp,iface))
os.system("%s -i %s -T -V chassisID " % (lldp,iface) +
"subtype=CHASSIS_ID_NETWORK_ADDRESS")
for tlv in tlvs:
cmd = "%s -T -i %s -V %s " % (lldp,iface,tlv)
cmd += "-c enableTx=yes &gt;&gt; /root/lldp.log 2&gt;&amp;1"
os.system(cmd)
ifaces = os.listdir("/sys/class/net")
ifaces.remove('lo')
for iface in ifaces:
setLLDP(iface)
</stack:script>

I'm setting up lldp, and I want to initialize the interfaces. I don't know what capabilities they have, so I'm just going to enable everything and whatever happens, happens. I could be more exact about this if I really wanted to. I'm using the 'interpreter="/usr/bin/python"' to tell the post tag to use python.

It's the equivalent of a shebang at the beginning of a python script file.

#!/usr/bin/python

So if you have perl/python/lua/ruby scripts you've written or someone else has, call the interpreter you need, and put the script in-between <stack:script> tags with <stack:interpreter=/path/to/perl>.

Further Reading

We have touched the surface of what you can do to configure backend machines. See the Stacki Universal XML documentation for a more complete listing of SUX syntax.

Clone this wiki locally

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Adding Scripts

Joe Kaiser edited this page Feb 19, 2018 · 8 revisions

Adding Scripts

Whatever you can do in Linux, you can do in a Cart across all your installs.

When a machine comes up, it should be exactly the way you want at first boot. Stacki should be used to bring machines to a known state. One of the ways to do that is run the scripts and start the services that need to be available during the install. It's part of the secret to scaling: offload as much work to the individual machines themselves.

Scripts

Scripts are similar to config files, except you might want to run them during installation or after first boot.

There are two ways to add/run scripts:

  • Adding them in a file and then running them
  • Running them in a <stack:script> </stack:script> tags.

Starting/enabling services

In CentOS/RHEL 7.x and SLES 12, systemd is the init script runner. If a service runs as a daemon, these can be enabled during installation and will start on first boot.

For example: Let's say we want to run a web-farm and httpd needs to be started on every machine. Let's make sure httpd is installed and gets started on every machine in our site-custom cart.

<stack:stack>
<!-- add httpd -->
<stack:package>httpd</stack:package>
<!-- enable httpd will autostart on first boot -->
<stack:script stack:stage="install-post">
systemctl enable httpd
</stack:script>
</stack:stack>

Add a script to be run during install.

You can run scripts to take care of configuration during the install phase.

They can be run as straight shell code or you can put them in a file and then run the file.

Let's say I want to add some administrative users and allow them to sudo without a tty.

<stack:script stack:stage="install-post">
<!-- just add them -->
groupadd -g 405 siteadmin
useradd -u 405 -g 405 siteadmin someperson1
useradd -u 406 -g 405 siteadmin someperson1
sed -i /"Defaults requiretty"/\c"#Defaults requiretty" /etc/sudoers
echo "%siteadmin ALL=(ALL) NOPASSWD: ALL" &gt; /etc/sudoers.d/siteadmin
</stack:script>

The scripts run bash in order they're compiled by Stacki. I could do this another way by adding it to a script in a stack:file tags and running the script:

<stack:script stack:stage="install-post">
<stack:file name="/tmp/addadmins.sh" perms="0755">
groupadd -g 405 siteadmin
useradd -u 405 -g 405 siteadmin someperson1
useradd -u 406 -g 405 siteadmin someperson1
sed -i /"Defaults requiretty"/\c"#Defaults requiretty" /etc/sudoers
</stack:file>
/tmp/addadmins.sh
<stack:file name="/etc/sudoers.d/siteadmin"
%siteadmin ALL=(ALL) NOPASSWD: ALL
</stack:admin>
</stack:script>

So we put the code of adding admins into /tmp/addadmins.sh with executable permissions and then we run it. I also pulled the "echo" into file tags. That is more Stacki idiomatic.

If a file or directory does not exist in a file tag, the directory and the file will be created without having to do additional "mkdirs"

First boot scripts

If your scripts need full network and services, then you can run them at first boot. Still using <stack:script> tags but changing the stack:stage in that tag.

<stack:script stack:stage="boot-post">
echo "Finished `date +'%H:%M %d-%b-%Y'`" &gt;&gt; /etc/motd
echo "finis=`date +'%s'`" &gt;&gt; /tmp/time.sh
<stack:file stack:name="/tmp/time.sh" stack:mode="append" stack:perms="755">
installed=`echo "$(((finis - built)/60))"`
echo "Installed in ${installed} minutes" &gt;&gt; /etc/motd
</stack:file>
/tmp/time.sh
</stack:script>

This script runs during first-boot after all the network and other services are up.

Note a few things:

  • The "stack:stage" is now "boot-post" rather than "install-post" - it runs at first boot.
  • There is a mix of shell commands plus adding a file to be run.
  • Echo some things into /etc/motd. Note the "date" commands - it's just shell.
  • Append to a time.sh script that will do some math for me.
  • Run the file.

This produces output that tell me how long it takes to install a backend node. This mix and matching of tags tells the installer how to behave.

Run a different shell interpreter

Really whenever you create a set of <stack:script> tags, you're running with the bash interpreter. You can do more difficult scripts during installation by switching interpreters, python, perl, ksh, csh, tcsh, javascript,ruby, on and on and on.

Do it like this:

<stack:script stack:interpreter="/usr/bin/python">
import os
tlvs = ["portDesc", "sysName", "sysDesc", "sysCap",
"mngAddr", "macPhyCfg", "powerMdi", "linkAgg",
"MTU", "LLDP-MED", "medCap", "medPolicy",
"medLoc", "medPower", "medHwRev", "medFwRev",
"medSwRev", "medSerNum", "medManuf", "medModel",
"medAssetID", "CIN-DCBX", "CEE-DCBX", "evbCfg",
"vdp", "IEEE-DCBX", "ETS-CFG", "ETS-REC",
"PFC", "APP", "PVID", "PPVID", "vlanName",
"ProtoID", "vidUsage", "mgmtVID", "linkAggr", "uPoE"]
lldp='/usr/sbin/lldptool'
def setLLDP(iface):
os.system("%s -L -i %s adminStatus=rxtx" % (lldp,iface))
os.system("%s -i %s -T -V chassisID " % (lldp,iface) +
"subtype=CHASSIS_ID_NETWORK_ADDRESS")
for tlv in tlvs:
cmd = "%s -T -i %s -V %s " % (lldp,iface,tlv)
cmd += "-c enableTx=yes &gt;&gt; /root/lldp.log 2&gt;&amp;1"
os.system(cmd)
ifaces = os.listdir("/sys/class/net")
ifaces.remove('lo')
for iface in ifaces:
setLLDP(iface)
</stack:script>

I'm setting up lldp, and I want to initialize the interfaces. I don't know what capabilities they have, so I'm just going to enable everything and whatever happens, happens. I could be more exact about this if I really wanted to. I'm using the 'interpreter="/usr/bin/python"' to tell the post tag to use python.

It's the equivalent of a shebang at the beginning of a python script file.

#!/usr/bin/python

So if you have perl/python/lua/ruby scripts you've written or someone else has, call the interpreter you need, and put the script in-between <stack:script> tags with <stack:interpreter=/path/to/perl>.

Further Reading

We have touched the surface of what you can do to configure backend machines. See the Stacki Universal XML documentation for a more complete listing of SUX syntax.

Clone this wiki locally

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Adding Scripts

Joe Kaiser edited this page Feb 19, 2018 · 8 revisions

Adding Scripts

Whatever you can do in Linux, you can do in a Cart across all your installs.

When a machine comes up, it should be exactly the way you want at first boot. Stacki should be used to bring machines to a known state. One of the ways to do that is run the scripts and start the services that need to be available during the install. It's part of the secret to scaling: offload as much work to the individual machines themselves.

Scripts

Scripts are similar to config files, except you might want to run them during installation or after first boot.

There are two ways to add/run scripts:

  • Adding them in a file and then running them
  • Running them in a <stack:script> </stack:script> tags.

Starting/enabling services

In CentOS/RHEL 7.x and SLES 12, systemd is the init script runner. If a service runs as a daemon, these can be enabled during installation and will start on first boot.

For example: Let's say we want to run a web-farm and httpd needs to be started on every machine. Let's make sure httpd is installed and gets started on every machine in our site-custom cart.

<stack:stack>
<!-- add httpd -->
<stack:package>httpd</stack:package>
<!-- enable httpd will autostart on first boot -->
<stack:script stack:stage="install-post">
systemctl enable httpd
</stack:script>
</stack:stack>

Add a script to be run during install.

You can run scripts to take care of configuration during the install phase.

They can be run as straight shell code or you can put them in a file and then run the file.

Let's say I want to add some administrative users and allow them to sudo without a tty.

<stack:script stack:stage="install-post">
<!-- just add them -->
groupadd -g 405 siteadmin
useradd -u 405 -g 405 siteadmin someperson1
useradd -u 406 -g 405 siteadmin someperson1
sed -i /"Defaults requiretty"/\c"#Defaults requiretty" /etc/sudoers
echo "%siteadmin ALL=(ALL) NOPASSWD: ALL" &gt; /etc/sudoers.d/siteadmin
</stack:script>

The scripts run bash in order they're compiled by Stacki. I could do this another way by adding it to a script in a stack:file tags and running the script:

<stack:script stack:stage="install-post">
<stack:file name="/tmp/addadmins.sh" perms="0755">
groupadd -g 405 siteadmin
useradd -u 405 -g 405 siteadmin someperson1
useradd -u 406 -g 405 siteadmin someperson1
sed -i /"Defaults requiretty"/\c"#Defaults requiretty" /etc/sudoers
</stack:file>
/tmp/addadmins.sh
<stack:file name="/etc/sudoers.d/siteadmin"
%siteadmin ALL=(ALL) NOPASSWD: ALL
</stack:admin>
</stack:script>

So we put the code of adding admins into /tmp/addadmins.sh with executable permissions and then we run it. I also pulled the "echo" into file tags. That is more Stacki idiomatic.

If a file or directory does not exist in a file tag, the directory and the file will be created without having to do additional "mkdirs"

First boot scripts

If your scripts need full network and services, then you can run them at first boot. Still using <stack:script> tags but changing the stack:stage in that tag.

<stack:script stack:stage="boot-post">
echo "Finished `date +'%H:%M %d-%b-%Y'`" &gt;&gt; /etc/motd
echo "finis=`date +'%s'`" &gt;&gt; /tmp/time.sh
<stack:file stack:name="/tmp/time.sh" stack:mode="append" stack:perms="755">
installed=`echo "$(((finis - built)/60))"`
echo "Installed in ${installed} minutes" &gt;&gt; /etc/motd
</stack:file>
/tmp/time.sh
</stack:script>

This script runs during first-boot after all the network and other services are up.

Note a few things:

  • The "stack:stage" is now "boot-post" rather than "install-post" - it runs at first boot.
  • There is a mix of shell commands plus adding a file to be run.
  • Echo some things into /etc/motd. Note the "date" commands - it's just shell.
  • Append to a time.sh script that will do some math for me.
  • Run the file.

This produces output that tell me how long it takes to install a backend node. This mix and matching of tags tells the installer how to behave.

Run a different shell interpreter

Really whenever you create a set of <stack:script> tags, you're running with the bash interpreter. You can do more difficult scripts during installation by switching interpreters, python, perl, ksh, csh, tcsh, javascript,ruby, on and on and on.

Do it like this:

<stack:script stack:interpreter="/usr/bin/python">
import os
tlvs = ["portDesc", "sysName", "sysDesc", "sysCap",
"mngAddr", "macPhyCfg", "powerMdi", "linkAgg",
"MTU", "LLDP-MED", "medCap", "medPolicy",
"medLoc", "medPower", "medHwRev", "medFwRev",
"medSwRev", "medSerNum", "medManuf", "medModel",
"medAssetID", "CIN-DCBX", "CEE-DCBX", "evbCfg",
"vdp", "IEEE-DCBX", "ETS-CFG", "ETS-REC",
"PFC", "APP", "PVID", "PPVID", "vlanName",
"ProtoID", "vidUsage", "mgmtVID", "linkAggr", "uPoE"]
lldp='/usr/sbin/lldptool'
def setLLDP(iface):
os.system("%s -L -i %s adminStatus=rxtx" % (lldp,iface))
os.system("%s -i %s -T -V chassisID " % (lldp,iface) +
"subtype=CHASSIS_ID_NETWORK_ADDRESS")
for tlv in tlvs:
cmd = "%s -T -i %s -V %s " % (lldp,iface,tlv)
cmd += "-c enableTx=yes &gt;&gt; /root/lldp.log 2&gt;&amp;1"
os.system(cmd)
ifaces = os.listdir("/sys/class/net")
ifaces.remove('lo')
for iface in ifaces:
setLLDP(iface)
</stack:script>

I'm setting up lldp, and I want to initialize the interfaces. I don't know what capabilities they have, so I'm just going to enable everything and whatever happens, happens. I could be more exact about this if I really wanted to. I'm using the 'interpreter="/usr/bin/python"' to tell the post tag to use python.

It's the equivalent of a shebang at the beginning of a python script file.

#!/usr/bin/python

So if you have perl/python/lua/ruby scripts you've written or someone else has, call the interpreter you need, and put the script in-between <stack:script> tags with <stack:interpreter=/path/to/perl>.

Further Reading

We have touched the surface of what you can do to configure backend machines. See the Stacki Universal XML documentation for a more complete listing of SUX syntax.

Clone this wiki locally

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Adding Scripts

Joe Kaiser edited this page Feb 19, 2018 · 8 revisions

Adding Scripts

Whatever you can do in Linux, you can do in a Cart across all your installs.

When a machine comes up, it should be exactly the way you want at first boot. Stacki should be used to bring machines to a known state. One of the ways to do that is run the scripts and start the services that need to be available during the install. It's part of the secret to scaling: offload as much work to the individual machines themselves.

Scripts

Scripts are similar to config files, except you might want to run them during installation or after first boot.

There are two ways to add/run scripts:

  • Adding them in a file and then running them
  • Running them in a <stack:script> </stack:script> tags.

Starting/enabling services

In CentOS/RHEL 7.x and SLES 12, systemd is the init script runner. If a service runs as a daemon, these can be enabled during installation and will start on first boot.

For example: Let's say we want to run a web-farm and httpd needs to be started on every machine. Let's make sure httpd is installed and gets started on every machine in our site-custom cart.

<stack:stack>
<!-- add httpd -->
<stack:package>httpd</stack:package>
<!-- enable httpd will autostart on first boot -->
<stack:script stack:stage="install-post">
systemctl enable httpd
</stack:script>
</stack:stack>

Add a script to be run during install.

You can run scripts to take care of configuration during the install phase.

They can be run as straight shell code or you can put them in a file and then run the file.

Let's say I want to add some administrative users and allow them to sudo without a tty.

<stack:script stack:stage="install-post">
<!-- just add them -->
groupadd -g 405 siteadmin
useradd -u 405 -g 405 siteadmin someperson1
useradd -u 406 -g 405 siteadmin someperson1
sed -i /"Defaults requiretty"/\c"#Defaults requiretty" /etc/sudoers
echo "%siteadmin ALL=(ALL) NOPASSWD: ALL" &gt; /etc/sudoers.d/siteadmin
</stack:script>

The scripts run bash in order they're compiled by Stacki. I could do this another way by adding it to a script in a stack:file tags and running the script:

<stack:script stack:stage="install-post">
<stack:file name="/tmp/addadmins.sh" perms="0755">
groupadd -g 405 siteadmin
useradd -u 405 -g 405 siteadmin someperson1
useradd -u 406 -g 405 siteadmin someperson1
sed -i /"Defaults requiretty"/\c"#Defaults requiretty" /etc/sudoers
</stack:file>
/tmp/addadmins.sh
<stack:file name="/etc/sudoers.d/siteadmin"
%siteadmin ALL=(ALL) NOPASSWD: ALL
</stack:admin>
</stack:script>

So we put the code of adding admins into /tmp/addadmins.sh with executable permissions and then we run it. I also pulled the "echo" into file tags. That is more Stacki idiomatic.

If a file or directory does not exist in a file tag, the directory and the file will be created without having to do additional "mkdirs"

First boot scripts

If your scripts need full network and services, then you can run them at first boot. Still using <stack:script> tags but changing the stack:stage in that tag.

<stack:script stack:stage="boot-post">
echo "Finished `date +'%H:%M %d-%b-%Y'`" &gt;&gt; /etc/motd
echo "finis=`date +'%s'`" &gt;&gt; /tmp/time.sh
<stack:file stack:name="/tmp/time.sh" stack:mode="append" stack:perms="755">
installed=`echo "$(((finis - built)/60))"`
echo "Installed in ${installed} minutes" &gt;&gt; /etc/motd
</stack:file>
/tmp/time.sh
</stack:script>

This script runs during first-boot after all the network and other services are up.

Note a few things:

  • The "stack:stage" is now "boot-post" rather than "install-post" - it runs at first boot.
  • There is a mix of shell commands plus adding a file to be run.
  • Echo some things into /etc/motd. Note the "date" commands - it's just shell.
  • Append to a time.sh script that will do some math for me.
  • Run the file.

This produces output that tell me how long it takes to install a backend node. This mix and matching of tags tells the installer how to behave.

Run a different shell interpreter

Really whenever you create a set of <stack:script> tags, you're running with the bash interpreter. You can do more difficult scripts during installation by switching interpreters, python, perl, ksh, csh, tcsh, javascript,ruby, on and on and on.

Do it like this:

<stack:script stack:interpreter="/usr/bin/python">
import os
tlvs = ["portDesc", "sysName", "sysDesc", "sysCap",
"mngAddr", "macPhyCfg", "powerMdi", "linkAgg",
"MTU", "LLDP-MED", "medCap", "medPolicy",
"medLoc", "medPower", "medHwRev", "medFwRev",
"medSwRev", "medSerNum", "medManuf", "medModel",
"medAssetID", "CIN-DCBX", "CEE-DCBX", "evbCfg",
"vdp", "IEEE-DCBX", "ETS-CFG", "ETS-REC",
"PFC", "APP", "PVID", "PPVID", "vlanName",
"ProtoID", "vidUsage", "mgmtVID", "linkAggr", "uPoE"]
lldp='/usr/sbin/lldptool'
def setLLDP(iface):
os.system("%s -L -i %s adminStatus=rxtx" % (lldp,iface))
os.system("%s -i %s -T -V chassisID " % (lldp,iface) +
"subtype=CHASSIS_ID_NETWORK_ADDRESS")
for tlv in tlvs:
cmd = "%s -T -i %s -V %s " % (lldp,iface,tlv)
cmd += "-c enableTx=yes &gt;&gt; /root/lldp.log 2&gt;&amp;1"
os.system(cmd)
ifaces = os.listdir("/sys/class/net")
ifaces.remove('lo')
for iface in ifaces:
setLLDP(iface)
</stack:script>

I'm setting up lldp, and I want to initialize the interfaces. I don't know what capabilities they have, so I'm just going to enable everything and whatever happens, happens. I could be more exact about this if I really wanted to. I'm using the 'interpreter="/usr/bin/python"' to tell the post tag to use python.

It's the equivalent of a shebang at the beginning of a python script file.

#!/usr/bin/python

So if you have perl/python/lua/ruby scripts you've written or someone else has, call the interpreter you need, and put the script in-between <stack:script> tags with <stack:interpreter=/path/to/perl>.

Further Reading

We have touched the surface of what you can do to configure backend machines. See the Stacki Universal XML documentation for a more complete listing of SUX syntax.

Clone this wiki locally

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Adding Scripts

Joe Kaiser edited this page Feb 19, 2018 · 8 revisions

Adding Scripts

Whatever you can do in Linux, you can do in a Cart across all your installs.

When a machine comes up, it should be exactly the way you want at first boot. Stacki should be used to bring machines to a known state. One of the ways to do that is run the scripts and start the services that need to be available during the install. It's part of the secret to scaling: offload as much work to the individual machines themselves.

Scripts

Scripts are similar to config files, except you might want to run them during installation or after first boot.

There are two ways to add/run scripts:

  • Adding them in a file and then running them
  • Running them in a <stack:script> </stack:script> tags.

Starting/enabling services

In CentOS/RHEL 7.x and SLES 12, systemd is the init script runner. If a service runs as a daemon, these can be enabled during installation and will start on first boot.

For example: Let's say we want to run a web-farm and httpd needs to be started on every machine. Let's make sure httpd is installed and gets started on every machine in our site-custom cart.

<stack:stack>
<!-- add httpd -->
<stack:package>httpd</stack:package>
<!-- enable httpd will autostart on first boot -->
<stack:script stack:stage="install-post">
systemctl enable httpd
</stack:script>
</stack:stack>

Add a script to be run during install.

You can run scripts to take care of configuration during the install phase.

They can be run as straight shell code or you can put them in a file and then run the file.

Let's say I want to add some administrative users and allow them to sudo without a tty.

<stack:script stack:stage="install-post">
<!-- just add them -->
groupadd -g 405 siteadmin
useradd -u 405 -g 405 siteadmin someperson1
useradd -u 406 -g 405 siteadmin someperson1
sed -i /"Defaults requiretty"/\c"#Defaults requiretty" /etc/sudoers
echo "%siteadmin ALL=(ALL) NOPASSWD: ALL" &gt; /etc/sudoers.d/siteadmin
</stack:script>

The scripts run bash in order they're compiled by Stacki. I could do this another way by adding it to a script in a stack:file tags and running the script:

<stack:script stack:stage="install-post">
<stack:file name="/tmp/addadmins.sh" perms="0755">
groupadd -g 405 siteadmin
useradd -u 405 -g 405 siteadmin someperson1
useradd -u 406 -g 405 siteadmin someperson1
sed -i /"Defaults requiretty"/\c"#Defaults requiretty" /etc/sudoers
</stack:file>
/tmp/addadmins.sh
<stack:file name="/etc/sudoers.d/siteadmin"
%siteadmin ALL=(ALL) NOPASSWD: ALL
</stack:admin>
</stack:script>

So we put the code of adding admins into /tmp/addadmins.sh with executable permissions and then we run it. I also pulled the "echo" into file tags. That is more Stacki idiomatic.

If a file or directory does not exist in a file tag, the directory and the file will be created without having to do additional "mkdirs"

First boot scripts

If your scripts need full network and services, then you can run them at first boot. Still using <stack:script> tags but changing the stack:stage in that tag.

<stack:script stack:stage="boot-post">
echo "Finished `date +'%H:%M %d-%b-%Y'`" &gt;&gt; /etc/motd
echo "finis=`date +'%s'`" &gt;&gt; /tmp/time.sh
<stack:file stack:name="/tmp/time.sh" stack:mode="append" stack:perms="755">
installed=`echo "$(((finis - built)/60))"`
echo "Installed in ${installed} minutes" &gt;&gt; /etc/motd
</stack:file>
/tmp/time.sh
</stack:script>

This script runs during first-boot after all the network and other services are up.

Note a few things:

  • The "stack:stage" is now "boot-post" rather than "install-post" - it runs at first boot.
  • There is a mix of shell commands plus adding a file to be run.
  • Echo some things into /etc/motd. Note the "date" commands - it's just shell.
  • Append to a time.sh script that will do some math for me.
  • Run the file.

This produces output that tell me how long it takes to install a backend node. This mix and matching of tags tells the installer how to behave.

Run a different shell interpreter

Really whenever you create a set of <stack:script> tags, you're running with the bash interpreter. You can do more difficult scripts during installation by switching interpreters, python, perl, ksh, csh, tcsh, javascript,ruby, on and on and on.

Do it like this:

<stack:script stack:interpreter="/usr/bin/python">
import os
tlvs = ["portDesc", "sysName", "sysDesc", "sysCap",
"mngAddr", "macPhyCfg", "powerMdi", "linkAgg",
"MTU", "LLDP-MED", "medCap", "medPolicy",
"medLoc", "medPower", "medHwRev", "medFwRev",
"medSwRev", "medSerNum", "medManuf", "medModel",
"medAssetID", "CIN-DCBX", "CEE-DCBX", "evbCfg",
"vdp", "IEEE-DCBX", "ETS-CFG", "ETS-REC",
"PFC", "APP", "PVID", "PPVID", "vlanName",
"ProtoID", "vidUsage", "mgmtVID", "linkAggr", "uPoE"]
lldp='/usr/sbin/lldptool'
def setLLDP(iface):
os.system("%s -L -i %s adminStatus=rxtx" % (lldp,iface))
os.system("%s -i %s -T -V chassisID " % (lldp,iface) +
"subtype=CHASSIS_ID_NETWORK_ADDRESS")
for tlv in tlvs:
cmd = "%s -T -i %s -V %s " % (lldp,iface,tlv)
cmd += "-c enableTx=yes &gt;&gt; /root/lldp.log 2&gt;&amp;1"
os.system(cmd)
ifaces = os.listdir("/sys/class/net")
ifaces.remove('lo')
for iface in ifaces:
setLLDP(iface)
</stack:script>

I'm setting up lldp, and I want to initialize the interfaces. I don't know what capabilities they have, so I'm just going to enable everything and whatever happens, happens. I could be more exact about this if I really wanted to. I'm using the 'interpreter="/usr/bin/python"' to tell the post tag to use python.

It's the equivalent of a shebang at the beginning of a python script file.

#!/usr/bin/python

So if you have perl/python/lua/ruby scripts you've written or someone else has, call the interpreter you need, and put the script in-between <stack:script> tags with <stack:interpreter=/path/to/perl>.

Further Reading

We have touched the surface of what you can do to configure backend machines. See the Stacki Universal XML documentation for a more complete listing of SUX syntax.

Clone this wiki locally

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Adding Scripts

Joe Kaiser edited this page Feb 19, 2018 · 8 revisions

Adding Scripts

Whatever you can do in Linux, you can do in a Cart across all your installs.

When a machine comes up, it should be exactly the way you want at first boot. Stacki should be used to bring machines to a known state. One of the ways to do that is run the scripts and start the services that need to be available during the install. It's part of the secret to scaling: offload as much work to the individual machines themselves.

Scripts

Scripts are similar to config files, except you might want to run them during installation or after first boot.

There are two ways to add/run scripts:

  • Adding them in a file and then running them
  • Running them in a <stack:script> </stack:script> tags.

Starting/enabling services

In CentOS/RHEL 7.x and SLES 12, systemd is the init script runner. If a service runs as a daemon, these can be enabled during installation and will start on first boot.

For example: Let's say we want to run a web-farm and httpd needs to be started on every machine. Let's make sure httpd is installed and gets started on every machine in our site-custom cart.

<stack:stack>
<!-- add httpd -->
<stack:package>httpd</stack:package>
<!-- enable httpd will autostart on first boot -->
<stack:script stack:stage="install-post">
systemctl enable httpd
</stack:script>
</stack:stack>

Add a script to be run during install.

You can run scripts to take care of configuration during the install phase.

They can be run as straight shell code or you can put them in a file and then run the file.

Let's say I want to add some administrative users and allow them to sudo without a tty.

<stack:script stack:stage="install-post">
<!-- just add them -->
groupadd -g 405 siteadmin
useradd -u 405 -g 405 siteadmin someperson1
useradd -u 406 -g 405 siteadmin someperson1
sed -i /"Defaults requiretty"/\c"#Defaults requiretty" /etc/sudoers
echo "%siteadmin ALL=(ALL) NOPASSWD: ALL" &gt; /etc/sudoers.d/siteadmin
</stack:script>

The scripts run bash in order they're compiled by Stacki. I could do this another way by adding it to a script in a stack:file tags and running the script:

<stack:script stack:stage="install-post">
<stack:file name="/tmp/addadmins.sh" perms="0755">
groupadd -g 405 siteadmin
useradd -u 405 -g 405 siteadmin someperson1
useradd -u 406 -g 405 siteadmin someperson1
sed -i /"Defaults requiretty"/\c"#Defaults requiretty" /etc/sudoers
</stack:file>
/tmp/addadmins.sh
<stack:file name="/etc/sudoers.d/siteadmin"
%siteadmin ALL=(ALL) NOPASSWD: ALL
</stack:admin>
</stack:script>

So we put the code of adding admins into /tmp/addadmins.sh with executable permissions and then we run it. I also pulled the "echo" into file tags. That is more Stacki idiomatic.

If a file or directory does not exist in a file tag, the directory and the file will be created without having to do additional "mkdirs"

First boot scripts

If your scripts need full network and services, then you can run them at first boot. Still using <stack:script> tags but changing the stack:stage in that tag.

<stack:script stack:stage="boot-post">
echo "Finished `date +'%H:%M %d-%b-%Y'`" &gt;&gt; /etc/motd
echo "finis=`date +'%s'`" &gt;&gt; /tmp/time.sh
<stack:file stack:name="/tmp/time.sh" stack:mode="append" stack:perms="755">
installed=`echo "$(((finis - built)/60))"`
echo "Installed in ${installed} minutes" &gt;&gt; /etc/motd
</stack:file>
/tmp/time.sh
</stack:script>

This script runs during first-boot after all the network and other services are up.

Note a few things:

  • The "stack:stage" is now "boot-post" rather than "install-post" - it runs at first boot.
  • There is a mix of shell commands plus adding a file to be run.
  • Echo some things into /etc/motd. Note the "date" commands - it's just shell.
  • Append to a time.sh script that will do some math for me.
  • Run the file.

This produces output that tell me how long it takes to install a backend node. This mix and matching of tags tells the installer how to behave.

Run a different shell interpreter

Really whenever you create a set of <stack:script> tags, you're running with the bash interpreter. You can do more difficult scripts during installation by switching interpreters, python, perl, ksh, csh, tcsh, javascript,ruby, on and on and on.

Do it like this:

<stack:script stack:interpreter="/usr/bin/python">
import os
tlvs = ["portDesc", "sysName", "sysDesc", "sysCap",
"mngAddr", "macPhyCfg", "powerMdi", "linkAgg",
"MTU", "LLDP-MED", "medCap", "medPolicy",
"medLoc", "medPower", "medHwRev", "medFwRev",
"medSwRev", "medSerNum", "medManuf", "medModel",
"medAssetID", "CIN-DCBX", "CEE-DCBX", "evbCfg",
"vdp", "IEEE-DCBX", "ETS-CFG", "ETS-REC",
"PFC", "APP", "PVID", "PPVID", "vlanName",
"ProtoID", "vidUsage", "mgmtVID", "linkAggr", "uPoE"]
lldp='/usr/sbin/lldptool'
def setLLDP(iface):
os.system("%s -L -i %s adminStatus=rxtx" % (lldp,iface))
os.system("%s -i %s -T -V chassisID " % (lldp,iface) +
"subtype=CHASSIS_ID_NETWORK_ADDRESS")
for tlv in tlvs:
cmd = "%s -T -i %s -V %s " % (lldp,iface,tlv)
cmd += "-c enableTx=yes &gt;&gt; /root/lldp.log 2&gt;&amp;1"
os.system(cmd)
ifaces = os.listdir("/sys/class/net")
ifaces.remove('lo')
for iface in ifaces:
setLLDP(iface)
</stack:script>

I'm setting up lldp, and I want to initialize the interfaces. I don't know what capabilities they have, so I'm just going to enable everything and whatever happens, happens. I could be more exact about this if I really wanted to. I'm using the 'interpreter="/usr/bin/python"' to tell the post tag to use python.

It's the equivalent of a shebang at the beginning of a python script file.

#!/usr/bin/python

So if you have perl/python/lua/ruby scripts you've written or someone else has, call the interpreter you need, and put the script in-between <stack:script> tags with <stack:interpreter=/path/to/perl>.

Further Reading

We have touched the surface of what you can do to configure backend machines. See the Stacki Universal XML documentation for a more complete listing of SUX syntax.

Clone this wiki locally

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Adding Scripts

Joe Kaiser edited this page Feb 19, 2018 · 8 revisions

Adding Scripts

Whatever you can do in Linux, you can do in a Cart across all your installs.

When a machine comes up, it should be exactly the way you want at first boot. Stacki should be used to bring machines to a known state. One of the ways to do that is run the scripts and start the services that need to be available during the install. It's part of the secret to scaling: offload as much work to the individual machines themselves.

Scripts

Scripts are similar to config files, except you might want to run them during installation or after first boot.

There are two ways to add/run scripts:

  • Adding them in a file and then running them
  • Running them in a <stack:script> </stack:script> tags.

Starting/enabling services

In CentOS/RHEL 7.x and SLES 12, systemd is the init script runner. If a service runs as a daemon, these can be enabled during installation and will start on first boot.

For example: Let's say we want to run a web-farm and httpd needs to be started on every machine. Let's make sure httpd is installed and gets started on every machine in our site-custom cart.

<stack:stack>
<!-- add httpd -->
<stack:package>httpd</stack:package>
<!-- enable httpd will autostart on first boot -->
<stack:script stack:stage="install-post">
systemctl enable httpd
</stack:script>
</stack:stack>

Add a script to be run during install.

You can run scripts to take care of configuration during the install phase.

They can be run as straight shell code or you can put them in a file and then run the file.

Let's say I want to add some administrative users and allow them to sudo without a tty.

<stack:script stack:stage="install-post">
<!-- just add them -->
groupadd -g 405 siteadmin
useradd -u 405 -g 405 siteadmin someperson1
useradd -u 406 -g 405 siteadmin someperson1
sed -i /"Defaults requiretty"/\c"#Defaults requiretty" /etc/sudoers
echo "%siteadmin ALL=(ALL) NOPASSWD: ALL" &gt; /etc/sudoers.d/siteadmin
</stack:script>

The scripts run bash in order they're compiled by Stacki. I could do this another way by adding it to a script in a stack:file tags and running the script:

<stack:script stack:stage="install-post">
<stack:file name="/tmp/addadmins.sh" perms="0755">
groupadd -g 405 siteadmin
useradd -u 405 -g 405 siteadmin someperson1
useradd -u 406 -g 405 siteadmin someperson1
sed -i /"Defaults requiretty"/\c"#Defaults requiretty" /etc/sudoers
</stack:file>
/tmp/addadmins.sh
<stack:file name="/etc/sudoers.d/siteadmin"
%siteadmin ALL=(ALL) NOPASSWD: ALL
</stack:admin>
</stack:script>

So we put the code of adding admins into /tmp/addadmins.sh with executable permissions and then we run it. I also pulled the "echo" into file tags. That is more Stacki idiomatic.

If a file or directory does not exist in a file tag, the directory and the file will be created without having to do additional "mkdirs"

First boot scripts

If your scripts need full network and services, then you can run them at first boot. Still using <stack:script> tags but changing the stack:stage in that tag.

<stack:script stack:stage="boot-post">
echo "Finished `date +'%H:%M %d-%b-%Y'`" &gt;&gt; /etc/motd
echo "finis=`date +'%s'`" &gt;&gt; /tmp/time.sh
<stack:file stack:name="/tmp/time.sh" stack:mode="append" stack:perms="755">
installed=`echo "$(((finis - built)/60))"`
echo "Installed in ${installed} minutes" &gt;&gt; /etc/motd
</stack:file>
/tmp/time.sh
</stack:script>

This script runs during first-boot after all the network and other services are up.

Note a few things:

  • The "stack:stage" is now "boot-post" rather than "install-post" - it runs at first boot.
  • There is a mix of shell commands plus adding a file to be run.
  • Echo some things into /etc/motd. Note the "date" commands - it's just shell.
  • Append to a time.sh script that will do some math for me.
  • Run the file.

This produces output that tell me how long it takes to install a backend node. This mix and matching of tags tells the installer how to behave.

Run a different shell interpreter

Really whenever you create a set of <stack:script> tags, you're running with the bash interpreter. You can do more difficult scripts during installation by switching interpreters, python, perl, ksh, csh, tcsh, javascript,ruby, on and on and on.

Do it like this:

<stack:script stack:interpreter="/usr/bin/python">
import os
tlvs = ["portDesc", "sysName", "sysDesc", "sysCap",
"mngAddr", "macPhyCfg", "powerMdi", "linkAgg",
"MTU", "LLDP-MED", "medCap", "medPolicy",
"medLoc", "medPower", "medHwRev", "medFwRev",
"medSwRev", "medSerNum", "medManuf", "medModel",
"medAssetID", "CIN-DCBX", "CEE-DCBX", "evbCfg",
"vdp", "IEEE-DCBX", "ETS-CFG", "ETS-REC",
"PFC", "APP", "PVID", "PPVID", "vlanName",
"ProtoID", "vidUsage", "mgmtVID", "linkAggr", "uPoE"]
lldp='/usr/sbin/lldptool'
def setLLDP(iface):
os.system("%s -L -i %s adminStatus=rxtx" % (lldp,iface))
os.system("%s -i %s -T -V chassisID " % (lldp,iface) +
"subtype=CHASSIS_ID_NETWORK_ADDRESS")
for tlv in tlvs:
cmd = "%s -T -i %s -V %s " % (lldp,iface,tlv)
cmd += "-c enableTx=yes &gt;&gt; /root/lldp.log 2&gt;&amp;1"
os.system(cmd)
ifaces = os.listdir("/sys/class/net")
ifaces.remove('lo')
for iface in ifaces:
setLLDP(iface)
</stack:script>

I'm setting up lldp, and I want to initialize the interfaces. I don't know what capabilities they have, so I'm just going to enable everything and whatever happens, happens. I could be more exact about this if I really wanted to. I'm using the 'interpreter="/usr/bin/python"' to tell the post tag to use python.

It's the equivalent of a shebang at the beginning of a python script file.

#!/usr/bin/python

So if you have perl/python/lua/ruby scripts you've written or someone else has, call the interpreter you need, and put the script in-between <stack:script> tags with <stack:interpreter=/path/to/perl>.

Further Reading

We have touched the surface of what you can do to configure backend machines. See the Stacki Universal XML documentation for a more complete listing of SUX syntax.

Clone this wiki locally