first release
This commit is contained in:
parent
e685ce4a43
commit
83bf64bd39
83
.drone.yml
Normal file
83
.drone.yml
Normal file
@ -0,0 +1,83 @@
|
||||
kind: pipeline
|
||||
name: default
|
||||
|
||||
steps:
|
||||
- name: create_targz
|
||||
image: registry.libreschool.org/libreschool-automation/rpm-pack:latest
|
||||
pull: always
|
||||
settings:
|
||||
files:
|
||||
- root
|
||||
- createlinks.pl
|
||||
- README.md
|
||||
- LICENSE
|
||||
when:
|
||||
event: [ push, tag ]
|
||||
|
||||
- name: build
|
||||
image: registry.libreschool.org/libreschool-automation/rpm-build:7
|
||||
pull: always
|
||||
commands:
|
||||
- yum-builddep -y libreschool-client-manager.spec
|
||||
- rpmbuild -ba libreschool-client-manager.spec
|
||||
- mkdir out ; mv *.rpm noarch/*.rpm out/
|
||||
when:
|
||||
event: [ push, tag ]
|
||||
|
||||
- name: sign
|
||||
image: registry.libreschool.org/libreschool-automation/rpm-sign:latest
|
||||
pull: always
|
||||
settings:
|
||||
files: out/*.rpm
|
||||
gpg_key:
|
||||
from_secret: gpg_key
|
||||
passphrase:
|
||||
from_secret: passphrase
|
||||
when:
|
||||
event: tag
|
||||
|
||||
- name: gitea_release
|
||||
image: plugins/gitea-release
|
||||
settings:
|
||||
files: out/*.rpm
|
||||
checksum:
|
||||
- md5
|
||||
- sha1
|
||||
- sha256
|
||||
- sha512
|
||||
api_key:
|
||||
from_secret: gitea_token
|
||||
base_url: https://git.libreschool.org
|
||||
file_exists: skip
|
||||
when:
|
||||
event: tag
|
||||
|
||||
- name: repo_upload
|
||||
image: appleboy/drone-scp
|
||||
settings:
|
||||
source: out/*.noarch.rpm
|
||||
target: /vhosts/repo.libreschool.org/https/nethserver/7/devel/x86_64/Packages/
|
||||
host: repo.libreschool.org
|
||||
strip_components: 1
|
||||
username:
|
||||
from_secret: ssh_username
|
||||
key:
|
||||
from_secret: ssh_key
|
||||
when:
|
||||
event: tag
|
||||
repo: LibreSchool/libreschool-client-manager
|
||||
|
||||
- name: repo_upload_srpms
|
||||
image: appleboy/drone-scp
|
||||
settings:
|
||||
source: out/*.src.rpm
|
||||
target: /vhosts/repo.libreschool.org/https/nethserver/7/devel/Source/SPackages/
|
||||
host: repo.libreschool.org
|
||||
strip_components: 1
|
||||
username:
|
||||
from_secret: ssh_username
|
||||
key:
|
||||
from_secret: ssh_key
|
||||
when:
|
||||
event: tag
|
||||
repo: LibreSchool/libreschool-client-manager
|
23
.editorconfig
Normal file
23
.editorconfig
Normal file
@ -0,0 +1,23 @@
|
||||
# EditorConfig is awesome: http://EditorConfig.org
|
||||
|
||||
root = true
|
||||
|
||||
[*]
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
[Makefile]
|
||||
indent_style = tab
|
||||
|
||||
[**.php]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
|
||||
[**.{js,coffee,html,less,css,json}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
[**.md]
|
||||
trim_trailing_whitespace = false
|
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
*.sh
|
||||
*.tar.*
|
||||
*.rpm
|
||||
*.log
|
39
README.md
39
README.md
@ -1,2 +1,41 @@
|
||||
# libreschool-client-manager
|
||||
|
||||
This software uses Ansible Playbooks to configure all connected clients.
|
||||
|
||||
## Available Events
|
||||
|
||||
Adopt a machine (which should already exist in the *hosts* db):
|
||||
# signal-event client-adopt pc-99 username password
|
||||
Reboot a machine:
|
||||
# signal-event client-reboot pc-01
|
||||
Reboot all machines:
|
||||
# signal-event client-reboot-all
|
||||
Force reconfiguration of a machine:
|
||||
# signal-event client-reconfigure pc-02
|
||||
Force reconfiguration of all machines:
|
||||
# signal-event client-reconfigure-all
|
||||
Shutdown a machine:
|
||||
# signal-event client-shutdown pc-02
|
||||
Shutdown all machines:
|
||||
# signal-event client-shutdown-all
|
||||
Update source configuration timestamp:
|
||||
# signal-event client-update-lastconfig
|
||||
Wakeup a machine:
|
||||
# signal-event client-wake pc-02
|
||||
Wakeup all machines:
|
||||
# signal-event client-wake-all
|
||||
|
||||
### Support for a group of machines
|
||||
If you defined a group of machines, you can send signals to groups also.
|
||||
# db hosts set epoptes_lab1 group members 'pc-01 pc-02 pc-03' master pc-01
|
||||
# signal-event client-wake epoptes_lab1
|
||||
|
||||
## E-smith DB reference
|
||||
The general configuration is stored in the *client-manager* key. Properties of such key are:
|
||||
* *lastConfig*: timestamp of the actual configuration set
|
||||
* *ManagementKeyFile*: management user's SSH public key
|
||||
* *ManagementPrivateKeyFile*: management user's SSH private key
|
||||
|
||||
|
||||
All host-specific information are stored in the *hosts* db. For each host we have:
|
||||
* *sshUser*: ssh user used to connect to the host. This is set when SSH adoption is successful
|
||||
|
16
createlinks.pl
Normal file
16
createlinks.pl
Normal file
@ -0,0 +1,16 @@
|
||||
#!/usr/bin/perl -w
|
||||
|
||||
use esmith::Build::CreateLinks qw(:all);
|
||||
use File::Basename;
|
||||
use File::Path;
|
||||
|
||||
event_actions('client-adopt', 'client-adopt' => 50);
|
||||
event_actions('client-reboot-all', 'client-reboot-all' => 50);
|
||||
event_actions('client-reboot', 'client-reboot' => 50);
|
||||
event_actions('client-reconfigure-all', 'client-reconfigure-all' => 50);
|
||||
event_actions('client-reconfigure', 'client-reconfigure' => 50);
|
||||
event_actions('client-shutdown-all', 'client-shutdown-all' => 50);
|
||||
event_actions('client-shutdown', 'client-shutdown' => 50);
|
||||
event_actions('client-update-lastconfig', 'client-update-lastconfig' => 50);
|
||||
event_actions('client-wake-all', 'client-wake-all' => 50);
|
||||
event_actions('client-wake', 'client-wake' => 50);
|
91
libreschool-client-manager.spec
Normal file
91
libreschool-client-manager.spec
Normal file
@ -0,0 +1,91 @@
|
||||
Summary: LibreSchool Client Manager
|
||||
Name: libreschool-client-manager
|
||||
Version: 1.0
|
||||
Release: 1.ns7
|
||||
License: GPL
|
||||
BuildArch: noarch
|
||||
BuildRoot: %{_tmppath}/%{name}-root
|
||||
Source0: %{name}-%{version}.tar.gz
|
||||
Source1: createlinks.pl
|
||||
Source2: LICENSE
|
||||
URL: %{url_prefix}/%{name}
|
||||
Group: System Environment/Daemons
|
||||
Requires: libreschool-release
|
||||
Requires: ansible
|
||||
Requires: httpd, php, sudo, php-xml, php-mcrypt
|
||||
Requires: nethserver-base
|
||||
Requires: ansible
|
||||
Requires(post): systemd
|
||||
Requires(preun): systemd
|
||||
Requires(postun): systemd
|
||||
Requires(pre): /usr/sbin/useradd, /usr/bin/getent
|
||||
Requires(postun): /usr/sbin/userdel
|
||||
BuildRequires: nethserver-devtools > 1.0.1
|
||||
BuildRequires: systemd
|
||||
|
||||
%description
|
||||
LibreSchool Ansible base structure for client management
|
||||
|
||||
%prep
|
||||
%setup
|
||||
|
||||
%build
|
||||
%{makedocs}
|
||||
perl createlinks.pl
|
||||
|
||||
%install
|
||||
rm -rf $RPM_BUILD_ROOT
|
||||
(cd root ; find . -depth -not -name '*.orig' -print | cpio -dump $RPM_BUILD_ROOT)
|
||||
%{genfilelist} $RPM_BUILD_ROOT > %{name}-%{version}-%{release}-filelist
|
||||
echo "%doc LICENSE" >> %{name}-%{version}-%{release}-filelist
|
||||
mkdir -p %{buildroot}%{_localstatedir}/log/client-manager
|
||||
mkdir -p %{buildroot}%{_localstatedir}/cache/client-manager
|
||||
|
||||
%files -f %{name}-%{version}-%{release}-filelist
|
||||
%attr(0755,srvmgr,srvmgr) %dir %{_localstatedir}/cache/client-manager
|
||||
%attr(0755,srvmgr,srvmgr) %dir %{_localstatedir}/log/client-manager
|
||||
%defattr(-,root,root)
|
||||
|
||||
%pre
|
||||
/usr/bin/getent passwd amgmt || /usr/sbin/useradd -r -g wheel -d /home/amgmt -s /sbin/nologin amgmt
|
||||
if [ "$1" = 2 ]; then
|
||||
#upgrade
|
||||
/usr/bin/systemctl stop client-manager >/dev/null 2>&1 || :
|
||||
fi
|
||||
|
||||
%post
|
||||
/etc/e-smith/events/actions/initialize-default-databases
|
||||
[ -e /usr/sbin/client-manager ] && [ ! -L /usr/sbin/client-manager ] && rm /usr/sbin/client-manager
|
||||
[ ! -L /usr/sbin/client-manager ] && ln -s httpd /usr/sbin/client-manager
|
||||
/sbin/e-smith/expand-template /etc/sudoers
|
||||
/sbin/e-smith/expand-template /etc/httpd/client-manager/httpd.conf
|
||||
/sbin/e-smith/expand-template /etc/ansible/reconfigure_clients.yml
|
||||
/sbin/e-smith/signal-event runlevel-adjust
|
||||
/usr/bin/systemctl --no-block start client-manager >/dev/null 2>&1 || :
|
||||
/sbin/e-smith/signal-event firewall-adjust
|
||||
|
||||
%preun
|
||||
#if [ "$1" = 1 ]; then
|
||||
# #upgrade
|
||||
#fi
|
||||
if [ "$1" = 0 ]; then
|
||||
#uninstallation
|
||||
/usr/bin/systemctl stop client-manager >/dev/null 2>&1 || :
|
||||
fi
|
||||
|
||||
%postun
|
||||
#if [ "$1" = 1 ]; then
|
||||
# #upgrade
|
||||
#fi
|
||||
if [ "$1" = 0 ]; then
|
||||
#uninstallation
|
||||
/usr/sbin/userdel amgmt
|
||||
/sbin/e-smith/db configuration delete client-manager
|
||||
/sbin/e-smith/expand-template /etc/sudoers
|
||||
/sbin/e-smith/signal-event runlevel-adjust
|
||||
/sbin/e-smith/signal-event firewall-adjust
|
||||
fi
|
||||
|
||||
%changelog
|
||||
* Sun Apr 28 2019 Paolo Asperti <paolo@asperti.com> - 1.0-1.ns7
|
||||
- First release for NethServer 7
|
13
root/etc/ansible/ansible-libreschool.cfg
Normal file
13
root/etc/ansible/ansible-libreschool.cfg
Normal file
@ -0,0 +1,13 @@
|
||||
[defaults]
|
||||
host_key_checking = False
|
||||
inventory = /etc/ansible/inventory/
|
||||
remote_user = amgmt
|
||||
private_key_file=/home/amgmt/.ssh/id_rsa
|
||||
nocows = 1
|
||||
log_path = /var/log/ansible.log
|
||||
|
||||
[privilege_escalation]
|
||||
become=True
|
||||
become_method=sudo
|
||||
become_user=root
|
||||
become_ask_pass=False
|
78
root/etc/ansible/inventory/clients.pl
Executable file
78
root/etc/ansible/inventory/clients.pl
Executable file
@ -0,0 +1,78 @@
|
||||
#!/usr/bin/perl -w
|
||||
|
||||
package esmith;
|
||||
|
||||
use strict;
|
||||
use Errno;
|
||||
use esmith::DB::db;
|
||||
use IO::File;
|
||||
use English;
|
||||
use JSON;
|
||||
|
||||
my $db_hosts = esmith::DB::db->open('hosts')
|
||||
|| die( "Could not open e-smith db (" . esmith::DB::db->error . ")\n" );
|
||||
my $db_configuration = esmith::DB::db->open('configuration')
|
||||
|| die( "Could not open e-smith db (" . esmith::DB::db->error . ")\n" );
|
||||
|
||||
my @hostnames = ();
|
||||
my %vars = ();
|
||||
my %hostvars = ();
|
||||
|
||||
my $default_user = "amgmt";
|
||||
my $ansible_ssh_private_key_file =
|
||||
$db_configuration->get_prop( 'client-manager', 'ManagementPrivateKeyFile' )
|
||||
|| '/home/amgmt/.ssh/id_rsa';
|
||||
|
||||
$vars{'configuration_timestamp'} =
|
||||
qx/find \/etc\/ansible\/roles -type f -printf '%T@\n' | sort -r | head -n 1/;
|
||||
chomp( $vars{'configuration_timestamp'} );
|
||||
$vars{"ansible_ssh_private_key_file"} =
|
||||
$db_configuration->get_prop( 'client-manager', 'ManagementPrivateKeyFile' );
|
||||
|
||||
my $domainname = $db_configuration->get_prop( 'DomainName', 'type' );
|
||||
|
||||
my @items = $db_hosts->get_all_by_prop('type' => 'local');
|
||||
foreach my $item (@items) {
|
||||
push @hostnames, $item->key . '.' . $domainname;
|
||||
my $ansible_user = $item->prop('sshUser') || $default_user;
|
||||
my %vars = (
|
||||
'ansible_ssh_private_key_file' => $ansible_ssh_private_key_file,
|
||||
'ansible_user' => $ansible_user
|
||||
);
|
||||
$hostvars{ $item->key . '.' . $domainname } = {%vars};
|
||||
}
|
||||
|
||||
my %output = (
|
||||
'all' => {
|
||||
'hosts' => [@hostnames],
|
||||
'vars' => {%vars},
|
||||
'children' => ['clients','ungrouped'],
|
||||
},
|
||||
'_meta' => {
|
||||
'hostvars' => {%hostvars}
|
||||
},
|
||||
'ungrouped' => [],
|
||||
'clients' => [@hostnames]
|
||||
);
|
||||
|
||||
my @groups = $db_hosts->get_all_by_prop('type' => 'group');
|
||||
foreach my $group (@groups) {
|
||||
my $members_str = $group->prop('members') || '';
|
||||
my @members = split ' ', $members_str;
|
||||
for my $member (@members) {
|
||||
$member = $member . '.' . $domainname;
|
||||
}
|
||||
|
||||
my %props = $group->props;
|
||||
delete( $props{'members'} );
|
||||
delete( $props{'type'} );
|
||||
|
||||
$output{ $group->key } = { 'hosts' => [@members], 'vars' => {%props} };
|
||||
push $output{'all'}{'children'}, $group->key;
|
||||
|
||||
}
|
||||
|
||||
my $json = encode_json \%output;
|
||||
print "$json\n";
|
||||
|
||||
exit 0;
|
38
root/etc/ansible/library/e-smith
Normal file
38
root/etc/ansible/library/e-smith
Normal file
@ -0,0 +1,38 @@
|
||||
#!/usr/bin/perl -w
|
||||
|
||||
package esmith;
|
||||
|
||||
use strict;
|
||||
use Errno;
|
||||
use esmith::DB::db;
|
||||
use IO::File;
|
||||
use English;
|
||||
use JSON;
|
||||
|
||||
my %ENV;
|
||||
|
||||
my @d = `bash -c 'source $ARGV[0] && echo db=\${db:-configuration}'`;
|
||||
for my $item (@d) {
|
||||
chomp $item;
|
||||
print "ITEM: $item \n";
|
||||
my ( $k, $v ) = split /=/, $item, 2;
|
||||
$v =~ s/^(['"])(.*)\1/$2/; #' fix highlighter
|
||||
$v =~ s/\$([a-zA-Z]\w*)/$ENV{$1}/g;
|
||||
$v =~ s/`(.*?)`/`$1`/ge; #dangerous
|
||||
$ENV{$k} = $v;
|
||||
}
|
||||
|
||||
my $db = esmith::DB::db->open( $ENV{'db'} ) || die("{}");
|
||||
my @items = $db->get_all();
|
||||
my %out;
|
||||
foreach my $item (@items) {
|
||||
my %props = $item->props();
|
||||
if ( scalar( keys %props ) <= 1 ) {
|
||||
$out{ $item->key } = $props{'type'};
|
||||
}
|
||||
else {
|
||||
$out{ $item->key } = { $item->props() };
|
||||
}
|
||||
}
|
||||
|
||||
print encode_json \%out;
|
21
root/etc/ansible/roles/common/tasks/main.yml
Normal file
21
root/etc/ansible/roles/common/tasks/main.yml
Normal file
@ -0,0 +1,21 @@
|
||||
---
|
||||
|
||||
- name: Import e-smith configuration db
|
||||
e-smith:
|
||||
register: esmith
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Add LibreSchool signing key
|
||||
apt_key:
|
||||
url: http://repo.libreschool.org/libreschool.asc
|
||||
state: present
|
||||
|
||||
- name: Add LibreSchool repository
|
||||
apt_repository:
|
||||
repo: deb http://repo.libreschool.org/ubuntu {{ ansible_distribution_release }} main
|
||||
state: present
|
||||
|
||||
- name: Update repositories cache and install "libreschool-client" package
|
||||
apt:
|
||||
name: libreschool-client
|
||||
update_cache: yes
|
@ -0,0 +1,4 @@
|
||||
---
|
||||
- name: rerun-libreschool-client
|
||||
shell: "(/usr/sbin/libreschool-client & )"
|
||||
ignore_errors: yes
|
@ -0,0 +1,19 @@
|
||||
---
|
||||
|
||||
- name: Check if libreschool-client is installed
|
||||
shell: dpkg-query -W -f='${Status}' libreschool-client | grep 'install ok installed'
|
||||
register: lsclient_installed
|
||||
failed_when: no
|
||||
changed_when: no
|
||||
|
||||
- name: Updating libreschool-client
|
||||
ini_file:
|
||||
path: /etc/libreschool-client/libreschool-client.conf
|
||||
option: last_config
|
||||
section: server
|
||||
no_extra_spaces: yes
|
||||
value: "{{ configuration_timestamp }}"
|
||||
notify: rerun-libreschool-client
|
||||
when: lsclient_installed.rc == 0
|
||||
|
||||
# TODO: ini_file always return "changed" even if it's not
|
4
root/etc/cron.d/libreschool-client-manager
Normal file
4
root/etc/cron.d/libreschool-client-manager
Normal file
@ -0,0 +1,4 @@
|
||||
# we also update via cron because you can change ansible playbooks
|
||||
# via other methods than the web ui
|
||||
|
||||
*/5 * * * * root /sbin/e-smith/signal-event client-update-lastconfig
|
@ -0,0 +1 @@
|
||||
/home/amgmt/.ssh/id_rsa.pub
|
@ -0,0 +1 @@
|
||||
/home/amgmt/.ssh/id_rsa
|
@ -0,0 +1 @@
|
||||
lab
|
@ -0,0 +1 @@
|
||||
2
|
@ -0,0 +1 @@
|
||||
3000
|
@ -0,0 +1 @@
|
||||
green
|
@ -0,0 +1 @@
|
||||
0
|
@ -0,0 +1 @@
|
||||
enabled
|
@ -0,0 +1 @@
|
||||
service
|
47
root/etc/e-smith/events/actions/client-adopt
Normal file
47
root/etc/e-smith/events/actions/client-adopt
Normal file
@ -0,0 +1,47 @@
|
||||
#!/bin/bash
|
||||
|
||||
EVENT="$1"
|
||||
HOST="$2"
|
||||
USER="$3"
|
||||
PASS="$4"
|
||||
|
||||
HOST_IN_DB=$(db hosts show "$HOST" >/dev/null && echo yes)
|
||||
|
||||
if [[ "$HOST_IN_DB" = "" ]] ; then
|
||||
echo "Host '$HOST' not in DB (or not registered)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
ping -c1 "$HOST" >/dev/null || (
|
||||
echo "Can't ping $HOST"
|
||||
exit 1
|
||||
)
|
||||
|
||||
sshpass -p "${PASS}" ssh -t -o StrictHostKeyChecking=no "${USER}@${HOST}" "whoami" || (
|
||||
echo "Can't login to $HOST as $USER"
|
||||
exit 1
|
||||
)
|
||||
|
||||
SUDO=$(sshpass -p "${PASS}" ssh -t -o StrictHostKeyChecking=no "${USER}@${HOST}" "which sudo")
|
||||
if [[ "$SUDO" = "" ]] ; then
|
||||
echo "Sudo not present in $HOST"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
AMIROOT=$(sshpass -p "${PASS}" ssh -t -o StrictHostKeyChecking=no "${USER}@${HOST}" "echo '${PASS}' | sudo -S whoami")
|
||||
if [[ "$AMIROOT" = "" ]]; then
|
||||
echo "Not enough privileges for $HOST"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
PUB=$(db configuration getprop client-manager ManagementKeyFile)
|
||||
cat $PUB | \
|
||||
sshpass -p "${PASS}" ssh -t -o StrictHostKeyChecking=no "${USER}@${HOST}" "echo '${PASS}' | sudo -S mkdir /root/.ssh >/dev/null ; sudo tee -a /root/.ssh/authorized_keys"
|
||||
|
||||
PRIV=$(db configuration getprop client-manager ManagementPrivateKeyFile)
|
||||
ssh -t -o StrictHostKeyChecking=no -i $PRIV "root@${HOST}" "whoami" || (
|
||||
echo "Something went wrong"
|
||||
exit 1
|
||||
)
|
||||
|
||||
db hosts setprop ${HOST} sshUser root
|
35
root/etc/e-smith/events/actions/client-reboot
Executable file
35
root/etc/e-smith/events/actions/client-reboot
Executable file
@ -0,0 +1,35 @@
|
||||
#!/usr/bin/perl -w
|
||||
|
||||
package esmith;
|
||||
|
||||
use strict;
|
||||
use Errno;
|
||||
use esmith::DB::db;
|
||||
use IO::File;
|
||||
use English;
|
||||
|
||||
my $db_hosts = esmith::DB::db->open('hosts') || die("Could not open e-smith db (" . esmith::DB::db->error . ")\n");
|
||||
|
||||
my $event = $ARGV[0];
|
||||
|
||||
defined $ARGV[1] || die "No host specified";
|
||||
my $host = $ARGV[1];
|
||||
|
||||
reboot_host($host);
|
||||
|
||||
exit 0;
|
||||
|
||||
sub reboot_host
|
||||
{
|
||||
my ($hostName) = @_;
|
||||
#------------------------------------------------------------
|
||||
# Reboot the host
|
||||
#------------------------------------------------------------
|
||||
|
||||
my $h = $db_hosts->get($hostName) or die "No host record for $hostName";
|
||||
my $user = $h->prop('sshUser') || 'amgmt';
|
||||
|
||||
system("/usr/bin/sudo -u amgmt /usr/bin/ssh -o StrictHostKeyChecking=no $user\@$hostName /usr/bin/sudo /sbin/reboot") == 0
|
||||
or die "Error rebooting $hostName";
|
||||
|
||||
}
|
21
root/etc/e-smith/events/actions/client-reboot-all
Executable file
21
root/etc/e-smith/events/actions/client-reboot-all
Executable file
@ -0,0 +1,21 @@
|
||||
#!/usr/bin/perl -w
|
||||
|
||||
package esmith;
|
||||
|
||||
use strict;
|
||||
use Errno;
|
||||
use esmith::DB::db;
|
||||
use IO::File;
|
||||
use English;
|
||||
|
||||
my $db_hosts = esmith::DB::db->open('hosts') || die("Could not open e-smith db (" . esmith::DB::db->error . ")\n");
|
||||
|
||||
my @items = $db_hosts->get_all('local');
|
||||
foreach my $item (@items) {
|
||||
my $hostName = $item->key;
|
||||
my $user = $item->prop('sshUser') || 'amgmt';
|
||||
system("/usr/bin/sudo -u amgmt /usr/bin/ssh -o StrictHostKeyChecking=no $user\@$hostName /usr/bin/sudo /sbin/reboot &") == 0
|
||||
or die "Error rebooting $hostName";
|
||||
}
|
||||
|
||||
exit 0;
|
37
root/etc/e-smith/events/actions/client-reconfigure
Executable file
37
root/etc/e-smith/events/actions/client-reconfigure
Executable file
@ -0,0 +1,37 @@
|
||||
#!/usr/bin/perl -w
|
||||
|
||||
package esmith;
|
||||
|
||||
use strict;
|
||||
use Errno;
|
||||
use esmith::DB::db;
|
||||
use IO::File;
|
||||
use English;
|
||||
|
||||
my $db_hosts = esmith::DB::db->open('hosts') || die("Could not open e-smith db (" . esmith::DB::db->error . ")\n");
|
||||
my $db_configuration = esmith::DB::db->open('configuration') || die("Could not open e-smith db (" . esmith::DB::db->error . ")\n");
|
||||
my $domainname = $db_configuration->get_prop('DomainName','type');
|
||||
|
||||
my $event = $ARGV [0];
|
||||
|
||||
defined $ARGV[1] || die "No host specied";
|
||||
my $host = $ARGV[1];
|
||||
|
||||
reconfigure_host($host);
|
||||
|
||||
exit 0;
|
||||
|
||||
sub reconfigure_host
|
||||
{
|
||||
my ($hostName) = @_;
|
||||
|
||||
my $h = $db_hosts->get($hostName) or die "No host record for user $hostName";
|
||||
|
||||
my $fqdn = $hostName . '.' . $domainname;
|
||||
|
||||
# ANSIBLE_CONFIG=/etc/ansible/ansible-libreschool.cfg ansible-playbook --list-hosts -v /etc/ansible/reconfigure_clients.yml
|
||||
|
||||
system("ANSIBLE_CONFIG=/etc/ansible/ansible-libreschool.cfg /usr/bin/ansible-playbook -l $fqdn -v /etc/ansible/reconfigure_clients.yml") == 0
|
||||
or die "Error reconfiguring $hostName";
|
||||
|
||||
}
|
5
root/etc/e-smith/events/actions/client-reconfigure-all
Executable file
5
root/etc/e-smith/events/actions/client-reconfigure-all
Executable file
@ -0,0 +1,5 @@
|
||||
#!/bin/bash
|
||||
|
||||
#/usr/bin/sudo -u amgmt
|
||||
export ANSIBLE_CONFIG=/etc/ansible/ansible-libreschool.cfg
|
||||
/usr/bin/ansible-playbook -v /etc/ansible/reconfigure_clients.yml
|
35
root/etc/e-smith/events/actions/client-shutdown
Executable file
35
root/etc/e-smith/events/actions/client-shutdown
Executable file
@ -0,0 +1,35 @@
|
||||
#!/usr/bin/perl -w
|
||||
|
||||
package esmith;
|
||||
|
||||
use strict;
|
||||
use Errno;
|
||||
use esmith::DB::db;
|
||||
use IO::File;
|
||||
use English;
|
||||
|
||||
my $db_hosts = esmith::DB::db->open('hosts') || die("Could not open e-smith db (" . esmith::DB::db->error . ")\n");
|
||||
|
||||
my $event = $ARGV[0];
|
||||
|
||||
defined $ARGV[1] || die "No host specified";
|
||||
my $host = $ARGV[1];
|
||||
|
||||
shutdown_host($host);
|
||||
|
||||
exit 0;
|
||||
|
||||
sub shutdown_host
|
||||
{
|
||||
my ($hostName) = @_;
|
||||
#------------------------------------------------------------
|
||||
# Shutdown the host
|
||||
#------------------------------------------------------------
|
||||
|
||||
my $h = $db_hosts->get($hostName) or die "No host record for $hostName";
|
||||
my $user = $h->prop('sshUser') || 'amgmt';
|
||||
|
||||
system("/usr/bin/sudo -u amgmt /usr/bin/ssh -o StrictHostKeyChecking=no $user\@$hostName /usr/bin/sudo /sbin/poweroff") == 0
|
||||
or die "Error shutting down $hostName";
|
||||
|
||||
}
|
21
root/etc/e-smith/events/actions/client-shutdown-all
Executable file
21
root/etc/e-smith/events/actions/client-shutdown-all
Executable file
@ -0,0 +1,21 @@
|
||||
#!/usr/bin/perl -w
|
||||
|
||||
package esmith;
|
||||
|
||||
use strict;
|
||||
use Errno;
|
||||
use esmith::DB::db;
|
||||
use IO::File;
|
||||
use English;
|
||||
|
||||
my $db_hosts = esmith::DB::db->open('hosts') || die("Could not open e-smith db (" . esmith::DB::db->error . ")\n");
|
||||
|
||||
my @items = $db_hosts->get_all('local');
|
||||
foreach my $item (@items) {
|
||||
my $hostName = $item->key;
|
||||
my $user = $item->prop('sshUser') || 'amgmt';
|
||||
system("/usr/bin/sudo -u amgmt /usr/bin/ssh -o StrictHostKeyChecking=no $user\@$hostName /usr/bin/sudo /sbin/poweroff &") == 0
|
||||
or die "Error shutting down $hostName";
|
||||
}
|
||||
|
||||
exit 0;
|
5
root/etc/e-smith/events/actions/client-update-lastconfig
Executable file
5
root/etc/e-smith/events/actions/client-update-lastconfig
Executable file
@ -0,0 +1,5 @@
|
||||
#!/bin/sh
|
||||
|
||||
LAST_CONFIG=$(/bin/find /etc/ansible/roles -type f -printf '%T@\n' | /bin/sort -r | /usr/bin/head -n 1)
|
||||
|
||||
/sbin/e-smith/db configuration setprop client-manager lastConfig "${LAST_CONFIG}"
|
35
root/etc/e-smith/events/actions/client-wake
Executable file
35
root/etc/e-smith/events/actions/client-wake
Executable file
@ -0,0 +1,35 @@
|
||||
#!/usr/bin/perl -w
|
||||
|
||||
package esmith;
|
||||
|
||||
use strict;
|
||||
use Errno;
|
||||
use esmith::DB::db;
|
||||
use IO::File;
|
||||
use English;
|
||||
|
||||
my $db_hosts = esmith::DB::db->open('hosts') || die("Could not open e-smith db (" . esmith::DB::db->error . ")\n");
|
||||
|
||||
my $event = $ARGV[0];
|
||||
|
||||
defined $ARGV[1] || die "No host specified";
|
||||
my $host = $ARGV[1];
|
||||
|
||||
wake_host($host);
|
||||
|
||||
exit 0;
|
||||
|
||||
sub wake_host
|
||||
{
|
||||
my ($hostName) = @_;
|
||||
#------------------------------------------------------------
|
||||
# Wake up the host
|
||||
#------------------------------------------------------------
|
||||
|
||||
my $h = $db_hosts->get($hostName) or die "No host record for $hostName";
|
||||
my $client = $h[0];
|
||||
my $mac = $client->prop('MacAddress');
|
||||
system("/sbin/ether-wake -b -i lan0 $mac") == 0
|
||||
or die "Error waking up $hostName";
|
||||
|
||||
}
|
21
root/etc/e-smith/events/actions/client-wake-all
Executable file
21
root/etc/e-smith/events/actions/client-wake-all
Executable file
@ -0,0 +1,21 @@
|
||||
#!/usr/bin/perl -w
|
||||
|
||||
package esmith;
|
||||
|
||||
use strict;
|
||||
use Errno;
|
||||
use esmith::DB::db;
|
||||
use IO::File;
|
||||
use English;
|
||||
|
||||
my $db_hosts = esmith::DB::db->open('hosts') || die("Could not open e-smith db (" . esmith::DB::db->error . ")\n");
|
||||
|
||||
my @items = $db_hosts->get_all('local');
|
||||
foreach my $item (@items) {
|
||||
my $hostName = $item->key;
|
||||
my $mac = $item->prop('MacAddress');
|
||||
system("/sbin/ether-wake -b -i lan0 $mac &") == 0
|
||||
or die "Error waking up $hostName";
|
||||
}
|
||||
|
||||
exit 0;
|
@ -0,0 +1,7 @@
|
||||
---
|
||||
|
||||
- hosts: all
|
||||
user: root
|
||||
|
||||
roles:
|
||||
- common
|
@ -0,0 +1 @@
|
||||
- update-libreschool-client
|
@ -0,0 +1,10 @@
|
||||
{
|
||||
#
|
||||
# 00template_defaults
|
||||
#
|
||||
|
||||
$localAccess = '';
|
||||
$port = ${'client-manager'}{TCPPort} || "3000";
|
||||
|
||||
"";
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
{
|
||||
|
||||
$OUT .= <<HERE;
|
||||
Listen $port
|
||||
HostnameLookups off
|
||||
|
||||
ServerAdmin root@$DomainName
|
||||
ServerRoot /etc/httpd
|
||||
ServerTokens ProductOnly
|
||||
|
||||
User srvmgr
|
||||
Group srvmgr
|
||||
|
||||
Include conf.modules.d/*.conf
|
||||
|
||||
ErrorLog /var/log/client-manager/error_log
|
||||
LogLevel warn
|
||||
HERE
|
||||
|
||||
|
||||
$OUT .= <<HERE;
|
||||
PidFile /var/run/client-manager.pid
|
||||
ScoreBoardFile /var/run/client-manager.scoreboard
|
||||
UseCanonicalName off
|
||||
LogFormat "%h %l %u %t \\"%r\\" %>s %b" common
|
||||
LogFormat "%{User-agent}i" agent
|
||||
|
||||
CustomLog /var/log/client-manager/access_log common
|
||||
|
||||
KeepAlive On
|
||||
MaxKeepAliveRequests 100
|
||||
KeepAliveTimeout 15
|
||||
|
||||
MaxClients 150
|
||||
MaxRequestsPerChild 100
|
||||
|
||||
MinSpareServers 1
|
||||
MaxSpareServers 5
|
||||
StartServers 1
|
||||
Timeout 300
|
||||
|
||||
DirectoryIndex index.php
|
||||
AccessFileName .htaccess
|
||||
|
||||
HERE
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
<Directory />
|
||||
Options None
|
||||
AllowOverride None
|
||||
order deny,allow
|
||||
deny from all
|
||||
allow from none
|
||||
</Directory>
|
@ -0,0 +1,16 @@
|
||||
|
||||
DocumentRoot /usr/share/client-manager
|
||||
|
||||
<Directory /usr/share/client-manager/>
|
||||
Options +Indexes
|
||||
Order Deny,Allow
|
||||
Deny from all
|
||||
Allow from { $localAccess || 'all' }
|
||||
AddType application/x-httpd-php .php
|
||||
Options +FollowSymlinks
|
||||
RewriteEngine on
|
||||
RewriteBase /
|
||||
RewriteRule ^ping$ index.php/ping [L]
|
||||
php_value session.save_path /var/cache/client-manager
|
||||
php_flag magic_quotes_gpc off
|
||||
</Directory>
|
@ -0,0 +1,4 @@
|
||||
AddDefaultCharset UTF-8
|
||||
<IfModule mime_module>
|
||||
TypesConfig /etc/mime.types
|
||||
</IfModule>
|
@ -0,0 +1 @@
|
||||
Include conf.d/php.conf
|
11
root/etc/e-smith/templates/etc/sudoers/50client-manager
Normal file
11
root/etc/e-smith/templates/etc/sudoers/50client-manager
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
$OUT = 'srvmgr ALL=NOPASSWD: /bin/cat ';
|
||||
|
||||
use esmith::ConfigDB;
|
||||
my $confdb = esmith::ConfigDB->open;
|
||||
my $backup = $confdb->get('client-manager') or die "No client-manager db entry found\n";
|
||||
my $ManagementKeyFile = $backup->prop('ManagementKeyFile') || '/home/amgmt/.ssh/id_rsa.pub';
|
||||
|
||||
$OUT = 'srvmgr ALL=NOPASSWD: /bin/cat ' . $ManagementKeyFile;
|
||||
|
||||
}
|
9
root/etc/logrotate.d/client-manager
Normal file
9
root/etc/logrotate.d/client-manager
Normal file
@ -0,0 +1,9 @@
|
||||
/var/log/client-manager/*log {
|
||||
missingok
|
||||
notifempty
|
||||
sharedscripts
|
||||
delaycompress
|
||||
postrotate
|
||||
/bin/systemctl reload client-manager > /dev/null 2>/dev/null || true
|
||||
endscript
|
||||
}
|
1
root/etc/sudoers.d/amgmt
Normal file
1
root/etc/sudoers.d/amgmt
Normal file
@ -0,0 +1 @@
|
||||
amgmt ALL=(ALL:ALL) NOPASSWD:ALL
|
7
root/etc/sysconfig/client-manager
Normal file
7
root/etc/sysconfig/client-manager
Normal file
@ -0,0 +1,7 @@
|
||||
#
|
||||
# This file can be used to set additional environment variables for
|
||||
# the client-manager process, or pass additional options to the
|
||||
# client-manager executable. See client-manager.service unit.
|
||||
#
|
||||
|
||||
LANG=C
|
@ -0,0 +1,7 @@
|
||||
[Unit]
|
||||
Description=Graceful restart of client-manager
|
||||
Requisite=client-manager.service
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/usr/sbin/httpd -f /etc/httpd/client-manager/httpd.conf -k graceful
|
6
root/usr/lib/systemd/system/client-manager-reload.timer
Normal file
6
root/usr/lib/systemd/system/client-manager-reload.timer
Normal file
@ -0,0 +1,6 @@
|
||||
[Unit]
|
||||
Description=Delayed graceful restart of client-manager
|
||||
Conflicts=client-manager-reload.service
|
||||
|
||||
[Timer]
|
||||
OnActiveSec=5
|
14
root/usr/lib/systemd/system/client-manager.service
Normal file
14
root/usr/lib/systemd/system/client-manager.service
Normal file
@ -0,0 +1,14 @@
|
||||
[Unit]
|
||||
Description=LibreSchool Client Manager httpd instance
|
||||
After=network.target remote-fs.target nss-lookup.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
EnvironmentFile=/etc/sysconfig/client-manager
|
||||
ExecStart=/usr/sbin/httpd -f /etc/httpd/client-manager/httpd.conf -c 'MaxConnectionsPerChild 12' -DFOREGROUND
|
||||
ExecReload=/usr/sbin/httpd -f /etc/httpd/client-manager/httpd.conf -k graceful
|
||||
LimitNOFILE=4096
|
||||
KillMode=process
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
391
root/usr/share/client-manager/EsmithDatabase.php
Normal file
391
root/usr/share/client-manager/EsmithDatabase.php
Normal file
@ -0,0 +1,391 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* Copyright (C) 2011 Nethesis S.r.l.
|
||||
*
|
||||
* This script is part of NethServer.
|
||||
*
|
||||
* NethServer is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* NethServer is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with NethServer. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Read and write parameters into SME DB
|
||||
*
|
||||
* Ths class implements an interface to SME database executing the command /sbin/e-smith/db with sudo.
|
||||
* The class needs /etc/sudoers configurazione. In the sudoers file you must have something like this:
|
||||
* <code>
|
||||
* Cmnd_Alias SME = /sbin/e-smith/db, /sbin/e-smith/signal-event
|
||||
* www ALL=NOPASSWD: SME
|
||||
* </code>
|
||||
*
|
||||
*
|
||||
* @author Giacomo Sanchietti <giacomo.sanchietti@nethesis.it>
|
||||
* @author Davide Principi <davide.principi@nethesis.it>
|
||||
* @since 1.0
|
||||
* @internal
|
||||
*/
|
||||
class EsmithDatabase
|
||||
{
|
||||
|
||||
/**
|
||||
* @var SME DB database command
|
||||
* */
|
||||
private $command = "/usr/bin/sudo /sbin/e-smith/db";
|
||||
|
||||
/**
|
||||
* @var $db Database name, it's translated into the db file path. For example: /home/e-smith/db/testdb
|
||||
* */
|
||||
private $db;
|
||||
|
||||
/**
|
||||
* This static class variable shares a socket connection to smwingsd
|
||||
*
|
||||
* @var resource
|
||||
*/
|
||||
private static $socket;
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Construct an object to access a SME Configuration database file
|
||||
* with $user's privileges.
|
||||
*
|
||||
* @param string $database Database name
|
||||
*/
|
||||
public function __construct($database)
|
||||
{
|
||||
if ( ! $database)
|
||||
sprintf("%s: You must provide a valid database name.", get_class($this));
|
||||
|
||||
$this->db = $database;
|
||||
}
|
||||
|
||||
public function getAll($type = NULL)
|
||||
{
|
||||
$output = "";
|
||||
|
||||
$ret = $this->dbRead('getjson', array(), $output);
|
||||
if ($ret !== 0) {
|
||||
sprintf("%s: internal database command failed!", __CLASS__);
|
||||
}
|
||||
|
||||
$data = json_decode($output, TRUE);
|
||||
if ( ! is_array($data)) {
|
||||
sprintf("%s: unexpected json string `%s`", __CLASS__, substr($output, 0, 8));
|
||||
}
|
||||
|
||||
$result = array();
|
||||
|
||||
foreach ($data as $item) {
|
||||
// Apply type check filter:
|
||||
if (isset($type) && $type !== $item['type']) {
|
||||
continue;
|
||||
}
|
||||
$props = isset($item['props']) ? $item['props'] : array();
|
||||
$result[$item['name']] = array_merge($props, array('type' => $item['type']));
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function getAllByProp($propName = NULL, $propValue = NULL)
|
||||
{
|
||||
$output = "";
|
||||
|
||||
$ret = $this->dbRead('getjson', array(), $output);
|
||||
if ($ret !== 0) {
|
||||
sprintf("%s: internal database command failed!", __CLASS__);
|
||||
}
|
||||
|
||||
$data = json_decode($output, TRUE);
|
||||
if ( ! is_array($data)) {
|
||||
sprintf("%s: unexpected json string `%s`", __CLASS__, substr($output, 0, 8));
|
||||
}
|
||||
|
||||
$result = array();
|
||||
|
||||
foreach ($data as $item) {
|
||||
$item['props']['type'] = $item['type'];
|
||||
$item['props']['name'] = $item['name'];
|
||||
|
||||
if (isset($propName) && isset($propValue)) {
|
||||
if (isset($item['props'][$propName]) && $propValue == $item['props'][$propName]) {
|
||||
$result[$item['name']] = $item['props'];
|
||||
}
|
||||
} else {
|
||||
$result[$item['name']] = $item['props'];
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function getKey($key)
|
||||
{
|
||||
$output = '';
|
||||
|
||||
$ret = $this->dbRead('getjson', array($key), $output);
|
||||
if ($ret !== 0) {
|
||||
sprintf("%s: internal database command failed", __CLASS__);
|
||||
}
|
||||
|
||||
$data = json_decode($output, TRUE);
|
||||
|
||||
if ($data === 1) {
|
||||
// Key has not been found
|
||||
return array();
|
||||
} elseif ( ! is_array($data)) {
|
||||
sprintf("%s: unexpected json string `%s`", __CLASS__, substr($output, 0, 8));
|
||||
}
|
||||
|
||||
$data['props']['type'] = $data['type'];
|
||||
$data['props']['name'] = $data['name'];
|
||||
return $data['props'];
|
||||
}
|
||||
|
||||
public function getKeyValue($key)
|
||||
{
|
||||
$output = '';
|
||||
|
||||
$ret = $this->dbRead('getjson', array($key), $output);
|
||||
if ($ret !== 0) {
|
||||
sprintf("%s: internal database command failed", __CLASS__);
|
||||
}
|
||||
|
||||
$data = json_decode($output, TRUE);
|
||||
|
||||
if ($data === 1) {
|
||||
// Key has not been found
|
||||
return array();
|
||||
} elseif ( ! is_array($data)) {
|
||||
sprintf("%s: unexpected json string `%s`", __CLASS__, substr($output, 0, 8));
|
||||
}
|
||||
|
||||
return $data['type'];
|
||||
}
|
||||
|
||||
public function setKey($key, $type, $props)
|
||||
{
|
||||
$output = NULL;
|
||||
$ret = $this->dbExec('set', $this->prepareArguments($key, $type, $props), $output);
|
||||
return ($ret == 0);
|
||||
}
|
||||
|
||||
public function deleteKey($key)
|
||||
{
|
||||
$output = NULL;
|
||||
$output = NULL;
|
||||
$ret = $this->dbExec('delete', $this->prepareArguments($key), $output);
|
||||
return ($ret == 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the type of a key
|
||||
* Act like: /sbin/e-smith/db dbfile gettype key
|
||||
*
|
||||
* @param string $key the key to retrieve
|
||||
* @access public
|
||||
* @return string the type of the key
|
||||
*/
|
||||
public function getType($key)
|
||||
{
|
||||
$output = NULL;
|
||||
$ret = $this->dbRead('gettype', array($key), $output);
|
||||
return trim($output);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the type of a key
|
||||
* Act like: /sbin/e-smith/db dbfile settype key type
|
||||
*
|
||||
* @param string $key the key to change
|
||||
* @param string $type the new type
|
||||
* @access public
|
||||
* @return bool true on success, FALSE otherwise
|
||||
*/
|
||||
public function setType($key, $type)
|
||||
{
|
||||
$output = NULL;
|
||||
$ret = $this->dbExec('settype', $this->prepareArguments($key, $type), $output);
|
||||
return ($ret == 0);
|
||||
}
|
||||
|
||||
public function getProp($key, $prop)
|
||||
{
|
||||
$output = NULL;
|
||||
$ret = $this->dbRead('getprop', array($key, $prop), $output);
|
||||
return trim($output);
|
||||
}
|
||||
|
||||
public function setProp($key, $props)
|
||||
{
|
||||
$output = NULL;
|
||||
$ret = $this->dbExec('setprop', $this->prepareArguments($key, $props), $output);
|
||||
return ($ret == 0);
|
||||
}
|
||||
|
||||
public function delProp($key, $props)
|
||||
{
|
||||
$output = NULL;
|
||||
$ret = $this->dbExec('delprop', array_merge(array($key), array_values($props)), $output);
|
||||
return ($ret == 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the db command
|
||||
* @param string $command The command to invoke
|
||||
* @param array $args The command arguments
|
||||
* @param string &$output The output from the command process
|
||||
* @return int The command exit code
|
||||
*/
|
||||
private function dbExec($command, $args, &$output)
|
||||
{
|
||||
// prepend the database name and command
|
||||
array_unshift($args, $this->db, $command);
|
||||
$exitCode = 0;
|
||||
$oArr = array();
|
||||
exec($this->command . ' ' . implode(' ', array_map('escapeshellarg', $args)), $oArr, $exitCode);
|
||||
$output = implode("\n", $oArr);
|
||||
return $exitCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read db data from memory cache daemon
|
||||
* @param string $command The command to invoke
|
||||
* @param array $args The command arguments
|
||||
* @param string &$output The output from the read socket
|
||||
* @return int The command exit code
|
||||
*/
|
||||
private function dbRead($command, $args, &$output)
|
||||
{
|
||||
if ( ! isset(self::$socket) ) {
|
||||
$socketPath = '/var/run/smwingsd.sock';
|
||||
$errno = 0;
|
||||
$errstr = '';
|
||||
self::$socket = fsockopen('unix://' . $socketPath, -1, $errno, $errstr);
|
||||
if( ! is_resource(self::$socket)) {
|
||||
sprintf("Invalid socket (%d): %s. Fall back to exec().", $errno, $errstr);
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! is_resource(self::$socket)) {
|
||||
return $this->dbExec($command, call_user_func_array(array($this, 'prepareArguments'), $args), $output);
|
||||
}
|
||||
|
||||
// prepend the database name and command
|
||||
array_unshift($args, $this->db, $command);
|
||||
|
||||
$ret = $this->sendMessage(self::$socket, 0x10, $args);
|
||||
if ( ! $ret ) {
|
||||
return 1;
|
||||
}
|
||||
$ret = $this->recvMessage(self::$socket);
|
||||
if ( ! $ret ) {
|
||||
return 1;
|
||||
}
|
||||
if ($command === 'getjson') {
|
||||
$output = $ret;
|
||||
} else {
|
||||
$output = json_decode($ret, TRUE);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private function sendMessage($socket, $type, $args = array())
|
||||
{
|
||||
$payload = json_encode($args);
|
||||
$data = pack('CN', (int) $type, strlen($payload)) . $payload;
|
||||
$written = fwrite($socket, $data);
|
||||
if ($written !== strlen($data)) {
|
||||
echo 'Socket write error';
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
private function recvMessage($socket)
|
||||
{
|
||||
$buf = $this->safeRead($socket, 5);
|
||||
if ($buf === FALSE) {
|
||||
echo 'Socket read error';
|
||||
return false;
|
||||
}
|
||||
|
||||
$header = unpack('Ctype/Nsize', $buf);
|
||||
if ( ! is_array($header)) {
|
||||
echo 'Invalid message header';
|
||||
return false;
|
||||
}
|
||||
|
||||
$message = $this->safeRead($socket, $header['size']);
|
||||
if ($message === FALSE) {
|
||||
echo 'Socket read error';
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($header['type'] & 0x02) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return $message;
|
||||
}
|
||||
|
||||
private function safeRead($socket, $size)
|
||||
{
|
||||
$buffer = "";
|
||||
$count = 0;
|
||||
while($count < $size) {
|
||||
if(feof($socket)) {
|
||||
return FALSE;
|
||||
}
|
||||
$chunk = fread($socket, $size - $count);
|
||||
$count += strlen($chunk);
|
||||
if($chunk === FALSE) {
|
||||
return FALSE;
|
||||
}
|
||||
$buffer .= $chunk;
|
||||
}
|
||||
return $buffer;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Take arbitrary arguments and flattenize to an array
|
||||
*
|
||||
* @param mixed $_
|
||||
* @return array
|
||||
*/
|
||||
private function prepareArguments()
|
||||
{
|
||||
$args = array();
|
||||
|
||||
foreach (func_get_args() as $arg) {
|
||||
if (is_array($arg)) {
|
||||
foreach ($arg as $propName => $propValue) {
|
||||
$args[] = $propName;
|
||||
$args[] = $propValue;
|
||||
}
|
||||
} else {
|
||||
$args[] = (String) $arg;
|
||||
}
|
||||
}
|
||||
|
||||
return $args;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
253
root/usr/share/client-manager/index.php
Normal file
253
root/usr/share/client-manager/index.php
Normal file
@ -0,0 +1,253 @@
|
||||
<?php
|
||||
include_once("EsmithDatabase.php");
|
||||
define('CONFIG_KEY', 'client-manager');
|
||||
|
||||
$hosts_db = new EsmithDatabase('hosts');
|
||||
$config_db = new EsmithDatabase('configuration');
|
||||
$dhcp_db = new EsmithDatabase('dhcp');
|
||||
|
||||
$uri = $_SERVER['REQUEST_URI'];
|
||||
// add leading '/' to uri string, works if $uri is empty
|
||||
$uri = (substr($uri, 0, 1) == '/') ? $uri : '/' . $uri;
|
||||
$pos = strpos($uri, '?');
|
||||
if (false !== $pos) {
|
||||
$route = substr($uri, 0, (int) $pos);
|
||||
} else {
|
||||
$route = $uri;
|
||||
}
|
||||
// remove trailing '/'
|
||||
$route = rtrim($route, '/');
|
||||
|
||||
$query_string = $_SERVER['QUERY_STRING'];
|
||||
$method = $_SERVER["REQUEST_METHOD"];
|
||||
$get_params = array();
|
||||
parse_str($query_string, $get_params);
|
||||
|
||||
// determine internal function name (all lowercase, dash2underscore, named ROUTE_methodname_routename )
|
||||
$function = preg_replace(':\-:', '_', strtolower($route));
|
||||
$function = preg_replace(':^/:', 'ROUTE_'.$method.'_', $function);
|
||||
|
||||
if (function_exists($function)) {
|
||||
$function();
|
||||
} else {
|
||||
die('Go away.');
|
||||
}
|
||||
|
||||
|
||||
function getClientFromUUID($uuid=false) {
|
||||
global $hosts_db;
|
||||
$hosts = $hosts_db->getAllByProp('uuid',$uuid);
|
||||
if (count($hosts)>0) {
|
||||
return array_pop($hosts);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function getClientFromIP($ip=false) {
|
||||
global $hosts_db;
|
||||
$hosts = $hosts_db->getAllByProp('IpAddress',$ip);
|
||||
if (count($hosts)>0) {
|
||||
return array_pop($hosts);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function getClientMac($ip=false) {
|
||||
if (false === $ip) {
|
||||
$ip = $_SERVER['REMOTE_ADDR'];
|
||||
}
|
||||
$result = pingHost($ip,1);
|
||||
if (!$result) return false;
|
||||
$macAddr=false;
|
||||
$arptable=`arp -n $ip`;
|
||||
$lines=explode("\n", $arptable);
|
||||
foreach($lines as $line) {
|
||||
$cols=preg_split('/\s+/', trim($line));
|
||||
if ($cols[0]==$ip)
|
||||
{
|
||||
return $cols[2];
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
function pingHost($host, $timeout=1) {
|
||||
$pingresult = exec("/bin/ping -c 1 -t 2 -W $timeout $host", $output, $retvar);
|
||||
return (0 == $retvar);
|
||||
}
|
||||
|
||||
function filtered_json() {
|
||||
global $_filtered_json;
|
||||
if (is_null($_filtered_json)) {
|
||||
$ok_elements = array('client_uuid','server_uuid','last_config','hostname');
|
||||
$data = json_decode(file_get_contents('php://input'), true);
|
||||
$_filtered_json = array_intersect_key($data,array_flip($ok_elements));
|
||||
}
|
||||
return $_filtered_json;
|
||||
}
|
||||
|
||||
function my_uuid() {
|
||||
global $config_db;
|
||||
$uuid = $config_db->getProp(CONFIG_KEY,'uuid');
|
||||
if (empty($uuid)) {
|
||||
$uuid=trim(shell_exec('/usr/bin/uuidgen'));
|
||||
$config_db->setProp(CONFIG_KEY,array('uuid'=>$uuid));
|
||||
}
|
||||
return $uuid;
|
||||
}
|
||||
|
||||
function find_free_ipaddress() {
|
||||
global $hosts_db, $dhcp_db;
|
||||
|
||||
$ip = ip2long($ip_str);
|
||||
if ( ! $ip_str_start = $dhcp_db->getProp('lan0','DhcpRangeStart') ) {
|
||||
return false;
|
||||
}
|
||||
if ( ! $ip_str_end = $dhcp_db->getProp('lan0','DhcpRangeEnd') ) {
|
||||
return false;
|
||||
}
|
||||
$ip_start = ip2long($ip_str_start)+1;
|
||||
$ip_end = ip2long($ip_str_end);
|
||||
|
||||
while($ip_start <= $ip_end){
|
||||
$hosts = $hosts_db->getAllByProp('IpAddress',long2ip($ip_start));
|
||||
if (count($hosts)==0) {
|
||||
return long2ip($ip_start);
|
||||
}
|
||||
$start_ip++;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
function ROUTE_POST_ping() {
|
||||
global $config_db, $hosts_db;
|
||||
$data = filtered_json();
|
||||
$server_uuid=my_uuid();
|
||||
|
||||
$out=array();
|
||||
$domainName = (string)$config_db->getKeyValue('DomainName');
|
||||
|
||||
if (!isset($data['server_uuid'])) {
|
||||
# client is unmanaged, let's fix that
|
||||
$systemName = (string)$config_db->getKeyValue('SystemName');
|
||||
$TCPPort = $config_db->getProp(CONFIG_KEY,'TCPPort');
|
||||
$out['ping_url']="http://$systemName.$domainName:$TCPPort/ping";
|
||||
$out['server_uuid']=$server_uuid;
|
||||
} elseif ($data['server_uuid'] != $server_uuid) {
|
||||
# this client belongs to someone else, let's ignore it
|
||||
echo json_encode(array('error'=>'you\'re devoted to another master'));
|
||||
exit;
|
||||
}
|
||||
|
||||
if (!isset($data['client_uuid'])) {
|
||||
# client hasn't sent its uuid. This is not allowed (client uuid is always generated client-side)
|
||||
echo json_encode(array('error'=>'missing client uuid'));
|
||||
exit;
|
||||
}
|
||||
|
||||
$client_ip = $_SERVER['REMOTE_ADDR'];
|
||||
|
||||
$client = false;
|
||||
if (! $client = getClientFromUUID($data['client_uuid'])) {
|
||||
# client uuid not registered, let's search for IP
|
||||
$mac = getClientMac();
|
||||
if ($client = getClientFromIP($client_ip)) {
|
||||
# we have one
|
||||
if (isset($client['uuid'])) {
|
||||
# with a registered UUID, that we know to be different from ours
|
||||
# so it's an IP collision.
|
||||
# we investigate:
|
||||
|
||||
if ($mac == $client['MacAddress']) {
|
||||
# the macaddress is the same, but we have different uuid
|
||||
echo json_encode(array('error'=>'uuid mismatch'));
|
||||
exit;
|
||||
} elseif (!$mac) {
|
||||
# we don't have any macaddress info (maybe because we're on
|
||||
# different networks, so no arp)
|
||||
echo json_encode(array('error'=>'can\'t add host because no macaddress'));
|
||||
exit;
|
||||
} else {
|
||||
# we have a macadrress different from the one we have
|
||||
# registered for this IP address, so it's a real IP collision
|
||||
# and we add a new host reservation
|
||||
if (! $new_ip = find_free_ipaddress()) {
|
||||
echo json_encode(array('error'=>'no more free ip addresses'));
|
||||
exit;
|
||||
}
|
||||
$props = array(
|
||||
'Description' => '',
|
||||
'IpAddress' => $new_ip,
|
||||
'MacAddress' => $mac,
|
||||
'uuid' => $data['client_uuid']
|
||||
);
|
||||
if (!$hosts_db->setKey($data['hostname'], 'local', $props)) {
|
||||
echo json_encode(array('error'=>'can\'t add new host'));
|
||||
exit;
|
||||
}
|
||||
$client = getClientFromUUID($data['client_uuid']);
|
||||
}
|
||||
} else {
|
||||
# the configured client doen't have the uuid
|
||||
# so we update it
|
||||
$props = array(
|
||||
'uuid' => $data['client_uuid']
|
||||
);
|
||||
if (!$hosts_db->setProp($client['name'], $props)) {
|
||||
echo json_encode(array('error'=>'can\'t update host'));
|
||||
exit;
|
||||
}
|
||||
$client['uuid'] = $data['client_uuid'];
|
||||
}
|
||||
|
||||
} else {
|
||||
# no client registered with this uuid or this IP
|
||||
if ($mac) {
|
||||
# we have a client mac address,
|
||||
# we can register a new host
|
||||
if (isset($data['hostname'])) {
|
||||
$props = array(
|
||||
'Description' => '',
|
||||
'IpAddress' => $client_ip,
|
||||
'MacAddress' => $mac,
|
||||
'uuid' => $data['client_uuid']
|
||||
);
|
||||
if (!$hosts_db->setKey($data['hostname'], 'local', $props)) {
|
||||
echo json_encode(array('error'=>'can\'t add host'));
|
||||
exit;
|
||||
}
|
||||
$client = getClientFromUUID($data['client_uuid']);
|
||||
} else {
|
||||
echo json_encode(array('error'=>'missing client hostname'));
|
||||
exit;
|
||||
}
|
||||
} else {
|
||||
# we don't have a mac address (maybe a different network)
|
||||
# we can't register a new host
|
||||
echo json_encode(array('error'=>'missing client mac address'));
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
$hosts_db->setProp($client['name'],array('LastSeen' => time()));
|
||||
if (isset($data['last_config'])) {
|
||||
$hosts_db->setProp($client['name'],array('LastConfig' => $data['last_config']));
|
||||
$current_config = $config_db->getProp(CONFIG_KEY,'lastConfig');
|
||||
if ($data['last_config'] != $current_config ) {
|
||||
exec('/usr/bin/sudo /sbin/e-smith/signal-event client-reconfigure ' . $client['name'] . ' > /dev/null &');
|
||||
}
|
||||
}
|
||||
$out['hostname']=$client['name'];
|
||||
$out['domainname']=$domainName;
|
||||
$out['ipaddress']=$client['IpAddress'];
|
||||
$out['server_uuid']=$server_uuid;
|
||||
|
||||
echo json_encode($out);
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user