Plans are service presets that may be assigned to an account. ApisCP contains a default present named "basic" that is a good fit for non-power users to balance resource consumption and accessibility.
# Management helpers
A variety of helpers exist to add, delete, modify, suspend, activate, import, and export a domain. These helpers have corresponding API commands in the admin (opens new window) module paired with each topic. $flags denote any feature, besides -c service,param=value, passed to the helper. Example of such flags are:
Flag | Scope | Usage |
---|---|---|
-c | edit, add, import | Override default service values |
--reset | edit | Resets site to plan defaults |
--dry-run | edit, delete | Shows actions without performing |
--backup | edit | Backup metadata prior to editing |
--reason | suspend | Specify suspension reason |
--force | delete, edit | Delete a domain in a partially edited state. Bypass good judgment. |
--since=timespec | delete | Delete domains suspended since timespec ("now", "last month", unix timestamp) |
--filter | delete | Delete domains matching (regex) suspension reason |
--reconfig | edit | Reconfigure all services implementing ServiceReconfiguration |
--all | edit | Target all domains |
--help | all | Show help |
--output=type | all | Set the output format. Values are "json" or unset |
These flags may be passed to the API whenever $flags is an accepted parameter. Flags simply present are passed as '[name:true]' whereas flags that accept values are passed as '[name:value]'.
As an example, EditDomain -c cgroup,enabled=0 --all
disables cgroup resource enforcement on all sites.
# AddDomain
AddDomain
creates a site from command-line. Multiple parameters can be provided to alter the services assigned to an account. Nexus within the Administrative panel is a frontend for this utility.
# Basic usage
AddDomain -c siteinfo,domain=mydomain.com -c siteinfo,admin_user=myadmin
DETAILS
Creates a new domain named mydomain.com with an administrative user myadmin. The email address defaults to blackhole@apiscp.com and password is randomly generated.
Let's alter this and set an email address, which is used to contact the account owner whenever a Web App is updated or when the password is changed. Let's also prompt for a password. But first let's delete mydomain.com because domains must be unique per server.
DeleteDomain mydomain.com
AddDomain -c siteinfo,domain=mydomain.com -c siteinfo,admin_user=myadmin -c siteinfo,email=hello@apisnetworks.com -c auth,passwd=1
DETAILS
ApisCP will prompt for a password and require confirmation. Email address will be set to hello@apisnetworks.com.
# Tweaking services
ApisCP includes a variety of services that can be enabled for an account. Some services must be enabled whereas others can be optionally enabled. Let's create an account that allows up to 2 additional users, disables MySQL, disables email, disables shell access, and disables addon domains. A single-user domain with limited access that may only upload files.
AddDomain -c siteinfo,domain=securedomain.com -c siteinfo,admin_user=secureuser -c users,enabled=0 -c users,max=3 -c mysql,enabled=0 -c mail,enabled=0 -c ssh,enabled=0 -c aliases,enabled=0
# Flags
--plan|-p=name: apply named plan to account
--force: hook failure does not constitute addition failure
--bootstrap | --bootstrap=[true|false]: attempt to issue SSL certificate upon creation. Setting [letsencrypt] => auto_bootstrap tuneable implies this option. When enabled, --bootstrap=false
or -c ssl,enabled=0
disables this feature.
--notify: send a welcome email to the email address specified in siteinfo,email. This template may be overridden in resources/views/email/opcenter/account-created.blade.php
(see Customizing.md).
# API usage
admin:add-site(string $domain, string $admin, array $opts = [], array $flags = [])
is the backend API call (opens new window) for this command-line utility. $admin corresponds to siteinfo,admin_user and must be unique.
# EditDomain
EditDomain
is a helper to change account state without removing it. You can toggle services and make changes in-place in a non-destructive manner. From the above example, it's easier to change the email address without destroying the account.
EditDomain -c siteinfo,email=hello@apisnetworks.com -D mydomain.com
# Rename domain
A simple, common situation is to alter the primary domain of an account. Simply changing the domain attribute under the siteinfo service will accomplish this.
EditDomain -c siteinfo,domain=newdomain.com mydomain.com
# Changing password
Changing the password is another common operation:
EditDomain -c auth,tpasswd=newpasswd site12
DETAILS
A new password is set in plain-text, "newpasswd". The third password alternative is cpasswd, which is a crypt() (opens new window)'d password. An optimal crypted password may be generated with auth_crypt (opens new window). Alternatively, cpcmd auth_crypt newpasswd
may be used to create the crypted password or To note, EditDomain accepts either the primary domain of an account, an aliased domain of an account (addon domain), or the site identifier. Aliases are discussed next.
# Aliases
Aliases are domains for which the primary responds. Any alias also serves as a valid authentication mechanism in the user@domain login mechanism. Any alias without a defined document root will serve content from /var/www/html, which is the document root (opens new window) for the primary domain.
EditDomain -c aliases,aliases=['foobar.com'] mydomain.com
DANGER
aliases,aliases is dangerous! It is not an append-only operation, meaning that whatever aliases value is is what is attached, nothing more and nothing less. A safer option is aliases_add_domain
, aliases_remove_domain
part of the API, which adds or removes domains in a singular process. This is part of cpcmd
discussed later on.
# Dry-runs
A dry-run proposes changes without applying them.
EditDomain --dry-run -c siteinfo,domain=newdomain.com site1
# site1:
# siteinfo: { domain: newdomain.com }
# apache: { webserver: www.newdomain.com }
# ftp: { ftpserver: ftp.newdomain.com }
In the above example, changing siteinfo,domain implicitly renames that service field as well as apache,webserver and ftp,ftpserver.
# Mass edits
Two options complement EditDomain, --all
and --reconfig
.
--all
targets all domains on a server. --reconfig
applies service reconfiguration to all services that support the feature. It may be used to forcefully regenerate configuration after overriding configuration (see Customizing.md).
EditDomain --all
EditDomain --reconfig
# or do both!
EditDomain --all --reconfig
# Targeting specific accounts
admin:collect(?array $params = [], array $query = null, array $sites = []): array
is an API command to collect service values from matching accounts. This can be mixed with JSON formatting + jq to perform complex scripting without clunky parsing. For example, to fetch all sites that have the plan "basic" and reconfigure to "advanced":
# Install jq first
yum install -y jq
cpcmd -o json admin:collect '[]' '[siteinfo.plan:basic]' | jq -r 'keys[]' | while read -r SITE ; do
echo "Editing $SITE"
EditDomain -c siteinfo,plan="advanced" "$SITE"
done
# Resetting account
A reset is specified with --reset
. Depending upon context-specific options, a reset will either apply the unchanged differences between plans or reset an account to default plan settings.
When --plan=NEWPLAN --reset
is specified, only unchanged defaults are applied.
When --reset
exists without a plan specifier, then the default plan settings are applied. A plan setting is only applied if the new plan's default value is not an array or not null. Certain parameters distinct to accounts, such as paswords, addon domains, and invoice are never reset.
# Changing plans
Plans are covered later in this section. Note the behavior when changing plans is that only the unchanged differences are applied. To reset all unprotected service variables to their new plan value, use --reset
in conjunction with --plan
as noted above.
# Flags
--plan|-p=name: apply named plan to account. Same as setting -c siteinfo,plan=NAME.
--force: hook failure does not constitute addition failure.
--reset: reset service parameters. Context-specific, see above note on reset!
--dry-run: show proposed changes without applying them.
--backup: create a backup of the metadata prior to editing. This metadata is stored in siteXX/info/backup. Each successive run overwrites this data.
--force: force change that may put the site in an inconsistent/disabled state after edit such as quota reduction below usage.
# Listing services
AddDomain -h
will list all available services. These services map to resources/templates/plans/.skeleton/, which infer support data from lib/Opcenter/Service/Validators/Service Name/Service Var/.php.
# API access
admin:edit_site(string $site, array $opts = [], array $flags = []): bool
allows editing of sites. See API.md for implementing API access.
# Double-throw safety switches
MySQL, PostgreSQL, and DNS permit disablement without removing database configuration (or zone databases) through a double-throw safety switch ("DTSS") feature. When mysql
, postgresql
, or dns
services are disabled, DTSS restricts further usage of creating new databases or users while preserving existing data.
To remove these databases from the account while preserving the account, the following DTSS conditions must be met at the same time. Likewise to preserve existing data, the following value must be set at deletion time.
Service | DTSS Condition | Preservation Condition |
---|---|---|
MySQL | enabled=0, dbaseprefix=None | dbaseprefix=None |
PostgreSQL | enabled=0, dbaseprefix=None | dbaseprefix=None |
DNS | enabled=0, provider=None | provider=None |
For example, consider the following code snippet:
AddDomain -c siteinfo,domain=mytestdomain.test -c siteinfo,admin_user=testuser -c dns,provider=powerdns -c dns,enabled=1
# Create a dummy record called foo.mydomain.test on PowerDNS
cpcmd -d mytestdomain.test dns:add-record mytestdomain.test 'foo' A '1.2.3.4'
# Confirm record exists
cpcmd -d mytestdomain.test dns:get-records foo
# Preserve DNS, but disable DNS for now...
# "null" is the same as "None" - historical quirk
EditDomain -c dns,provider=null mytestdomain.test
# Confirm record is missing
cpcmd -d mytestdomain.test dns:get-records foo
# Revert DNS provider. Zone is preserved.
EditDomain -c dns,provider=powerdns mytestdomain.test
# Confirm record exists
cpcmd -d mytestdomain.test dns:get-records foo
# Remove DNS for good from site
EditDomain -c dns,provider=null -c dns,enabled=0 mytestdomain.test
EditDomain -c dns,provider=powerdns -c dns,enabled=1 mytestdomain.test
# Record now missing
cpcmd -d mytestdomain.test dns:get-records foo
Likewise setting provider=None
prior to deletion can be used to preserve DNS upon account deletion. Databases are allocated within each site's filesystem, so while it's possible to preserve MySQL/PostgreSQL databases and grants upon account deletion the underlying databases are still removed.
# DeleteDomain
Domains may be deleted using DeleteDomain
. DeleteDomain accepts a list of arguments that may be either the site identifier, domain, aliased domain, or invoice (billing,invoice OR billing,parent_invoice service value). Invoices allow you to quickly group multiple accounts. Invoices are discussed briefly below.
# Flags
--since=TIMESPEC: only delete domains suspended TIMESPEC ago ("last week", "now", 1579934058).
--force: delete a domain in a partial edit state.
--dry-run: show what will be deleted without deleting.
--match=IDENTIFIER: delete domains that match IDENTIFIER in the suspension reason (regular expression, delimiter implicitly added).
# Advanced matching
New in 3.2.5
Templates may contain hidden markup prefixed by ;
or #
that is not visible to the end user. These may contain special identifiers used by --match=IDENTIFIER
. For example assuming the following suspension message in siteXX/info/disabled:
Your account has been suspended.
# REASON-XYZ-123
; Some other internal notes
cpcmd -d domain.com auth:inactive-reason
will report "Your account has been suspended" masking any additional markup prefixed with "#" or ";".
Likewise to target this site suspended over 30 days ago, DeleteDomain --dry-run --match=REASON-XYZ-123 --since="last month"
# API access
admin:delete-site(?string $site, array $flags = []): bool
allows deletion of sites. See API.md for implementing API access.
Passing null to $site and [since:timespec] to $flags allows deletion of suspended sites suspended timespec ago. Specify a timespec of "now" to delete all suspended sites. For example to show what sites would be deleted from the shell,
cpcmd admin:delete-site null '[since:now,dry-run:true]'
# SuspendDomain
Domains may be deactivated from the command-line using SuspendDomain
. It accepts a list of arguments, which may be the site identifier, domain, aliased domain, or billing invoice.
TIP
A suspended domain revokes access to all services, except panel, as well as page access. Panel access may be overridden by setting [auth] => suspended_login to true in config.ini.
When a billing invoice is specified any site that possesses this identifier either as billing,invoice or billing,parent_invoice will be suspended.
# Reasons
New in 3.2.5 --reason=reason
may be used to specify a suspension reason.
--template=NAME
, if specified, uses the template NAME to refer to a template under resources/templates/opcenter/suspension
. The reason value is passed to the template as $reason
. This may be customized.
If a suspension reason is provided, the customer, upon login to panel UI will be shown this reason. Reason may be adjusted by setting [auth] => show_suspension_reason to false (see Tuneables.md).
Likewise if a suspension reason is given, DeleteDomain
accepts --match=word
to match a regular expression in the suspension reason. See DeleteDomain for more information.
# API access
admin:deactivate-site(string $site, array $flags = []): bool
allows suspension of sites. See API.md for implementing API access. $site may be any site identifier or billing invoice.
# ActivateDomain
Likewise a domain may be activated by using ActivateDomain
. It acts similarly to SuspendDomain except that it undoes what SuspendDomain does.
ActivateDomain apiscp-XYZ123
Where apiscp-XYZ123 is a billing invoice assigned to the account via -c billing,invoice=apiscp-XYX123
# API access
admin:activate-site(string $site): bool
allows reactivation of suspended sites. See API.md for implementing API access. $site may be any site identifier or billing invoice.
# Plans
Plans are related to AddDomain and EditDomain in that they assign preset settings to a domain.
# Creating plans
New plans may be created using artisan.
cd /usr/local/apnscp
./artisan opcenter:plan --new custom
The plan is created within resources/templates/plans/custom. Changes may be made at your leisure. Use ./artisan opcenter:plan --verify PLAN
to confirm the plan named PLAN.
A plan may copy another plan's definitions by specifying --base OLDPLAN
. For example,
./artisan opcenter:plan --new nossh --base custom
Plans may be created thinly in which services from the default plan are inherited unless explicitly named. This allows simplification such as the following "ssh" service definition which disables just SSH access, inheriting all other plan defaults.
Consider the service "nossh", which has 1 file in resources/templates/plans/nossh named ssh with the following line:
[DEFAULT]
enabled = 0
This is a valid plan definition and will inherit all other plan values from the default plan.
Additionally, the familiar -c
flag may be used to feed individual overrides to a plan,
./artisan opcenter:plan --new nossh -c ssh,enabled=0
# Managing plans
The following commands imply ./artisan opcenter:plan
is used.
PLAN --default
: set the default plan, e.g. to set the default plan for accounts going forward, use ./artisan opcenter:plan --default custom
. Note this is equivalent to using the cp.config Scope to change [opcenter] => default_plan.
PLAN --diff=COMPARE
: compare PLAN against COMPARE returning only the differences in PLAN that do not exist in COMPARE or are different.
PLAN --edit
: edit named PLAN. Use with -c|--config
to set service values.
PLAN --remove
: remove named PLAN.
PLAN --verify
: run internal verification against PLAN ensuring it can publish an account.
--list
: list all plans.
# Assigning plans
These plans can be customized and assigned to an account using -p, AddDomain -p custom -c siteinfo,domain=mydomain.com
. Likewise to assign a new plan to all accounts, EditDomain -c siteinfo,plan=advanced --all
would apply the plan named "advanced" to all accounts. This is a destructive operation and instead encourage a simpler route of filtering accounts, then applying plans individually.
# Install jq first
yum install -y jq
cpcmd -o json admin:collect '[]' '[siteinfo.plan:basic]' | jq -r 'keys[]' | while read -r SITE ; do
echo "Editing $SITE"
EditDomain -c siteinfo,plan="advanced" "$SITE"
done
A plan applied to an account does not reset any service values changed beyond the plan base. For example, if ssh,enabled=1 were the setting on an account and SSH were deactivated by setting ssh,enabled=0 outside the plan settings, then changing to a new plan in which ssh,enabled=1 exists would not apply to the site.
This behavior may be altered by supplying --reset
to EditDomain. See EditDomain above for more information.
# Editing plans
--edit
allows one-off edits for plans from the command-line. If --config
is used, then the plans are edited directly. If omitted and no --service
is specified, then all services are edited using the provided editor ($EDITOR
environment variable).
# Disable separate PHP-FPM users for plan named "simple" using composite
./artisan opcenter:plan --edit --config apache,webuser=None simple
# Increase storage for plan "storage" using --service syntax
# Verify plan correctness afterward
./artisan opcenter:plan --edit --service diskquota --config quota=1 --config units=T --verify storage
# Hand-edit ssh service for plan "interactive" using nano
# note --verify does not work in editor mode
./artisan opcenter:plan --edit --service ssh
# Complex plan usage
OverlayFS, part of BoxFS, may be used to create complex plans that add additional services.
A cPanelesque feature allows users to use cron while disabling ssh. Doing so still allows the user arbitrary access to SSH into the server by opening a tunnel within a cron task, so the only means to ensure an environment without shell access is to disable all ingress routes. Despite this advice, cron may be enabled while disabling terminal access with a new service layer that utilizes the whiteout feature of OverlayFS:
mkdir -p /home/virtual/FILESYSTEMTEMPLATE/nossh/etc/pam.d
mknod /home/virtual/FILESYSTEMTEMPLATE/nossh/etc/pam.d/sshd c 0 0
touch /home/virtual/site1/info/services/nossh
/etc/systemd/user/fsmount.init mount site1 nossh
DETAILS
Use OverlayFS' whiteout feature to mask the sshd pam service, so you'd have all the affordances of the ssh service layer/bins but without the ability to authenticate via SSH. Layers are left-to-right precedence and layers are mounted lexicographically. To have a service supercede "nossh" name it lower in the alphabet such as "negatednossh".
Any character device with major:minor 0:0 is hidden on upper layers. This a feature consistent with layered filesystems, OverlayFS and aufs notably. ApisCP uses OverlayFS presently but used aufs prior to 2016 (opens new window). A corresponding surrogate is created to mount the layer if the package name matches a package which lacks ssh but permits cron:
<?php declare(strict_types=1);
class Ssh_Module_Surrogate extends Ssh_Module
{
const SSHLESS_PLANS = ['basic'];
public function _create()
{
parent::_create();
$plan = $this->getServiceValue('siteinfo', 'plan', \Opcenter\Service\Plans::default());
if (!\in_array($plan, static::SSHLESS_PLANS, true)) {
return;
}
return $this->maskSsh();
}
public function _edit()
{
parent::_edit();
$newPlan = array_get($this->getNewServices('siteinfo'), 'plan', \Opcenter\Service\Plans::default());
$oldPlan = array_get($this->getOldServices('siteinfo'), 'plan', \Opcenter\Service\Plans::default());
if (\in_array($oldPlan, static::SSHLESS_PLANS, true) === ($post = \in_array($newPlan, static::SSHLESS_PLANS, true))) {
return;
}
return $post ? $this->maskSsh() : $this->unmaskSsh();
}
private function maskSsh(): bool {
$layer = new \Opcenter\Service\ServiceLayer($this->site);
if (!$layer->installServiceLayer('nossh') || !$layer->reload()) {
return error("Failed to mount `nossh' service");
}
return true;
}
private function unmaskSsh(): bool
{
$layer = new \Opcenter\Service\ServiceLayer($this->site);
if (!$layer->uninstallServiceLayer('nossh') || !$layer->reload()) {
return error("Failed to unmount `nossh' service");
}
return true;
}
}
Make sure the plan listed above in SSHLESS_PLANS
exists (see artisan opcenter:plan) and you're off to the races!
You may confirm the service layer has been mounted via mount in procfs:
EditDomain -p basic site12
grep 'site12/fst' /proc/mounts | grep lowerdir=
# You should see "nossh" in the composite layer
# Additionally, confirm the layer marker has been installed
ls /home/virtual/site12/info/services/nossh