27 Commits
v0.30 ... v0.34

Author SHA1 Message Date
yo
dc4213a8d5 v.0.34 : Jail names can be shortened 2023-07-09 10:38:00 +02:00
yo
5eed121f0b Protect devfs last ruleset acquiring with mutex 2023-06-25 23:32:15 +02:00
yo
812c77790a Add CHANGELOG 2023-06-25 23:09:03 +02:00
yo
7575da794e 0.33c : parallelize start/stop of jails up to gMaxThreads 2023-06-25 23:07:53 +02:00
yo
6f9bb504be Fix forever waiting on services not properly closing pipes at start 2023-06-10 14:12:53 +02:00
yo
7c3e14f0f1 version bump, README update 2023-06-03 11:18:42 +02:00
yo
a4ff9c1d51 datasets now should be specified with zpool 2023-06-03 11:18:40 +02:00
yo
00fd283987 resolver.conf bugfix, datasets now should be specified with zpool 2023-06-03 11:18:37 +02:00
yo
7cf4594f34 Update TODO list with some bugs 2022-11-20 20:20:44 +01:00
yo
37fea55e42 Add update command 2022-11-20 20:20:20 +01:00
yo
c15ee68d2e Add update command 2022-11-20 20:20:06 +01:00
yo
54fd1f8064 FIXME: Update Last_started 2022-11-20 20:19:37 +01:00
yo
89db166040 FIXME: Update release in config file when stopping jail 2022-11-20 20:17:59 +01:00
yo
9c18a83ee8 ExecuteCommandWithOutputToStdout 2022-11-20 20:16:23 +01:00
yo
561ae4386a FIXME 2022-11-20 20:13:47 +01:00
yo
667c73216e gocage fetch finished 2022-11-06 16:34:52 +01:00
yo
9e506145a8 downloaded files goes to download 2022-10-16 15:32:47 +02:00
yo
d636d963ff WIP: fetch command 2022-10-16 15:20:00 +02:00
yo
56b4d8ea84 WIP: fetch command 2022-10-16 15:19:51 +02:00
yo
abaa4a11f9 Check if dataset exist, create dataset 2022-10-16 15:19:03 +02:00
yo
74602dc0df Add arch property to jailhost 2022-10-16 15:18:35 +02:00
yo
be756edea7 rm blank line 2022-10-16 15:17:56 +02:00
yo
546382ded7 Add TODO in readme 2022-10-15 16:38:17 +02:00
yo
07eccffbd1 Add Devfs_ruleset property to reflect generated RS 2022-10-15 16:33:29 +02:00
yo
7809107ea4 Update readme 2022-10-15 15:24:24 +02:00
yo
5ab0a59db4 Update readme 2022-10-15 15:23:58 +02:00
yo
1b27753718 Add service file 2022-10-15 15:16:26 +02:00
15 changed files with 1084 additions and 107 deletions

1
.gitignore vendored
View File

@ -1,3 +1,2 @@
gocage
go.sum

3
CHANGELOG Normal file
View File

@ -0,0 +1,3 @@
v.0.33b : Support jailing datasets on differents pools : jail_zfs_dataset now have to include the pool name
v.0.33c : Parallelize start/stop of jails with same priority
v.0.34 : jail name can be shortened

View File

@ -6,7 +6,13 @@ Support iocage jails, so they can coexist.
Gocage is meant to be a complete jail management tool with network, snapshots, jail cloning support and a web interface. This is the hypothetic future.
Gocage can handle multiple datastores, so you can have jails on HDD storage and jails on SSD storage.
From v0.33b, due to multi ZFS pool support, gocage is no longer 100% compatible with iocage.
Zfs datasets now should be specified with the ZFS pool. e.g. :
<code>
Config.Jail_zfs = 1
Config.Jail_zfs_dataset = myzfspool/poudriere
Config.Jail_zfs_mountpoint = none
</code>
List jails
----------
@ -143,7 +149,7 @@ gocage datastore list
+------------+-------------+------------+-----------+----------+------------+
</pre></code>
#### Filter datastores
### Filter datastores
As with jails and snapshots, you can filter by name:
<pre><code>
gocage datastore list iocage
@ -154,7 +160,7 @@ gocage datastore list iocage
+------------+-------------+------------+-----------+----------+------------+
</pre></code>
#### Sort datastores
### Sort datastores
You can sort datastores:
<pre><code>
gocage datastore list -s -Available
@ -175,6 +181,11 @@ With multi datastore comes the need to migrate a jail between datastores.
Migration can be done with a minimal downtime, using zfs differential send/receive.
Source jail datasets are sent to the destination datastore, jail is stopped and a last differential sync is done before starting jail on new datastore.
### Warning
Be aware the moment you migrate a jail to another datastore than /iocage default, you lose compatibility with iocage.
Then you need to disable iocage service, and enable gocage so the jails will start automatically at boot.
Also make sure, if you don't destroy source jail, that it won't have the "boot" property set or you will have the 2 jails up at boot.
<pre><code>
gocage migrate -d fastiocage srv-random
Snapshot data/iocage/jails/srv-random: Done
@ -182,3 +193,22 @@ Snapshot data/iocage/jails/srv-random/root: Done
Migrate jail config dataset to fastdata/iocage/jails/srv-random: Done
Migrate jail filesystem dataset to fastdata/iocage/jails/srv-random/root: Done
</pre></code>
Fetch
----------
Files can be fetched from custom repository, or from local directory with "from" option.
For example if you destroyed releases/12.3-RELEASE and still have the downloaded files in /iocage/download/12.3-RELEASE:
<pre><code>
gocage fetch -r 12.3 -o iocage --from file:/iocage/download
</pre></code>
TODO
----------
gocage update
gocage upgrade
gocage create
gocage destroy
gocage init
create default pool with defaults.json

View File

@ -1,4 +1,8 @@
Replicating jails between two servers (use zrepl)
DEBUG:
- cmd/list.go:275:
// FIXME ??? Shouldn't be ioc-$Name ?
j.InternalName = rj.Name
- WriteConfigToDisk don't write neither "release" in cmd stop neither "last_started" in cmd start

374
cmd/fetch.go Normal file
View File

@ -0,0 +1,374 @@
package cmd
import (
"io"
"os"
"fmt"
"bufio"
"bytes"
//"errors"
"strings"
"net/http"
//"archive/tar"
"encoding/hex"
"crypto/sha256"
//"github.com/ulikunitz/xz"
log "github.com/sirupsen/logrus"
)
const (
ReleaseServer = "download.freebsd.org"
ReleaseRootDir = "ftp/releases"
)
var (
FetchFiles = []string{"base.txz", "lib32.txz", "src.txz"}
)
// TODO: Check if files already exist
// Fetch release files, verify, put in datastore under ${datastore}/download
// Only support http and file protocols
func fetchRelease(release string, proto string, arch string, datastore string, fetchFrom string) error {
var ds Datastore
log.SetReportCaller(true)
if len(fetchFrom) > 0 {
proto = strings.Split(fetchFrom, ":")[0]
}
if false == strings.EqualFold(proto, "http") &&
false == strings.EqualFold(proto, "file") {
return fmt.Errorf("Unsupported protocol: %s\n", proto)
}
for _, ds = range gDatastores {
if strings.EqualFold(datastore, ds.Name) {
break
}
}
if false == strings.EqualFold(datastore, ds.Name) {
return fmt.Errorf("Datastore not found: %s\n", datastore)
}
// Check datastore have a download dataset, and it is mounted
downloadDsName := fmt.Sprintf("%s/download", ds.ZFSDataset)
downloadDsMountPoint := fmt.Sprintf("%s/download", ds.Mountpoint)
exist, err := doZfsDatasetExist(downloadDsName)
if err != nil {
return fmt.Errorf("Error accessing dataset %s: %v\n", downloadDsName, err)
}
if false == exist {
// Then create dataset
if err := createZfsDataset(downloadDsName, downloadDsMountPoint, "lz4"); err != nil {
return fmt.Errorf("Error creating dataset %s: %v\n", downloadDsName, err)
}
}
// Create download/XX.X-RELEASE dataset if necessary
thisDownloadDsName := fmt.Sprintf("%s/%s-RELEASE", downloadDsName, release)
thisDownloadDsMountPoint := fmt.Sprintf("%s/%s-RELEASE", downloadDsMountPoint, release)
exist, err = doZfsDatasetExist(thisDownloadDsName)
if err != nil {
return fmt.Errorf("Error accessing dataset %s: %v\n", thisDownloadDsName, err)
}
if false == exist {
// Then create dataset
if err := createZfsDataset(thisDownloadDsName, thisDownloadDsMountPoint, "lz4"); err != nil {
return fmt.Errorf("Error creating dataset %s: %v\n", thisDownloadDsName, err)
}
}
var fetchUrl string
if len(fetchFrom) > 0 {
fetchUrl = fmt.Sprintf("%s/%s-RELEASE", fetchFrom, release)
} else {
fetchUrl = fmt.Sprintf("%s://%s/%s/%s/%s-RELEASE", proto, ReleaseServer, ReleaseRootDir, arch, release)
}
log.Debugf("FetchURL = %s", fetchUrl)
// check if proto/server/arch/release is available
if strings.EqualFold(proto, "http") {
resp, err := http.Get(fetchUrl)
if err != nil {
return fmt.Errorf("Can not get %s: %v\n", fetchUrl, err)
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
return fmt.Errorf("Get %s returned %d, check release name\n", fetchUrl, resp.StatusCode)
}
}
// Fetch files
// Get MANIFEST so we get sha256 sums
if err := fetchFile(proto, fetchUrl, "MANIFEST", thisDownloadDsMountPoint, []byte{}); err != nil {
return fmt.Errorf("%v\n", err)
}
// Build an array of "file;checksum"
checksumMap, err := buildFileChecksumFromManifest(fmt.Sprintf("%s/MANIFEST", thisDownloadDsMountPoint), FetchFiles)
if err != nil {
return fmt.Errorf("%v\n", err)
}
// Fetch remaining files, verify integrity and write to disk
for f, c := range checksumMap {
if err := fetchFile(proto, fetchUrl, f, thisDownloadDsMountPoint, c); err != nil {
return fmt.Errorf("%v\n", err)
}
}
return nil
}
// Extract release files stored in iocage/download/$RELEASE/ to iocage/releases/$RELEASE/root/
func extractRelease(release string, datastore string) {
log.SetReportCaller(true)
var ds Datastore
for _, ds = range gDatastores {
if strings.EqualFold(datastore, ds.Name) {
break
}
}
if false == strings.EqualFold(datastore, ds.Name) {
fmt.Printf("Datastore not found: %s\n", datastore)
return
}
// Check datastore have a releases dataset, and it is mounted
releaseDsName := fmt.Sprintf("%s/releases", ds.ZFSDataset)
releaseDsMountPoint := fmt.Sprintf("%s/releases", ds.Mountpoint)
exist, err := doZfsDatasetExist(releaseDsName)
if err != nil {
fmt.Printf("Error accessing dataset %s: %v\n", releaseDsName, err)
return
}
if false == exist {
// Then create dataset
if err := createZfsDataset(releaseDsName, releaseDsMountPoint, "lz4"); err != nil {
fmt.Printf("Error creating dataset %s: %v\n", releaseDsName, err)
return
}
}
// Create releases/XX.X-RELEASE dataset if necessary
thisReleaseDsName := fmt.Sprintf("%s/%s-RELEASE", releaseDsName, release)
thisReleaseDsMountPoint := fmt.Sprintf("%s/%s-RELEASE", releaseDsMountPoint, release)
exist, err = doZfsDatasetExist(thisReleaseDsName)
if err != nil {
fmt.Printf("Error accessing dataset %s: %v\n", thisReleaseDsName, err)
return
}
if false == exist {
// Then create dataset
if err := createZfsDataset(thisReleaseDsName, thisReleaseDsMountPoint, "lz4"); err != nil {
fmt.Printf("Error creating dataset %s: %v\n", thisReleaseDsName, err)
return
}
}
// Create releases/XX.X-RELEASE/root dataset if necessary
thisReleaseRootDsName := fmt.Sprintf("%s/root", thisReleaseDsName)
thisReleaseRootDsMountPoint := fmt.Sprintf("%s/root", thisReleaseDsMountPoint)
exist, err = doZfsDatasetExist(thisReleaseRootDsName)
if err != nil {
fmt.Printf("Error accessing dataset %s: %v\n", thisReleaseRootDsName, err)
return
}
if false == exist {
// Then create dataset
if err := createZfsDataset(thisReleaseRootDsName, thisReleaseRootDsMountPoint, "lz4"); err != nil {
fmt.Printf("Error creating dataset %s: %v\n", thisReleaseRootDsName, err)
return
}
}
// Now extract download/$RELEASE/*.txz to releases/XX.X-RELEASE/root
downloadDsMountPoint := fmt.Sprintf("%s/download", ds.Mountpoint)
downloadDir := fmt.Sprintf("%s/%s-RELEASE", downloadDsMountPoint, release)
d, err := os.Open(downloadDir)
defer d.Close()
if err != nil {
fmt.Printf("Can not read %s directory: %v\n", downloadDir, err)
return
}
files, err := d.Readdir(0)
if err != nil {
fmt.Printf("Can not browse %s directory: %v\n", downloadDir, err)
return
}
// Extract every .txz files
for _, fi := range files {
if false == fi.IsDir() {
if strings.HasSuffix(fi.Name(), ".txz") {
ar := fmt.Sprintf("%s/%s", downloadDir, fi.Name())
fmt.Printf("Extracting file %s... ", ar)
// pure Go method, sorry this is so slow. Also I did not handle permissions in this
/* f, err := os.Open(ar)
defer f.Close()
if err != nil {
fmt.Printf("Can not open %s: %v\n", ar, err)
return
}
// xz reader
r, err := xz.NewReader(f)
if err != nil {
fmt.Printf("Can not read %s: %v\n", ar, err)
return
}
// tar reader
tr := tar.NewReader(r)
// Iterate through the files in the archive.
for {
hdr, err := tr.Next()
if err == io.EOF {
// end of tar archive
break
}
if err != nil {
log.Fatal(err)
}
switch hdr.Typeflag {
case tar.TypeDir:
// create a directory
dest := fmt.Sprintf("%s/%s", thisReleaseRootDsMountPoint, hdr.Name)
// FIXME: Access rights?
err = os.MkdirAll(dest, 0777)
if err != nil {
log.Fatal(err)
}
case tar.TypeReg, tar.TypeRegA:
// write a file
dest := fmt.Sprintf("%s/%s", thisReleaseRootDsMountPoint, hdr.Name)
w, err := os.Create(dest)
defer w.Close()
if err != nil {
log.Fatal(err)
}
_, err = io.Copy(w, tr)
if err != nil {
log.Fatal(err)
}
}
}
*/
cmd := fmt.Sprintf("/usr/bin/tar xpf %s -C %s", ar, thisReleaseRootDsMountPoint)
out, err := executeCommand(cmd)
if err != nil && len(out) > 0 {
fmt.Printf("Error: %v: %s\n", err, out)
} else {
fmt.Printf("Done\n")
}
}
}
}
}
func fetchFile(proto, baseUrl, fileName, storeDir string, checksum []byte) error {
// Check storeDir exist
_, err := os.Stat(storeDir)
if os.IsNotExist(err) {
return fmt.Errorf("Directory does not exist: %s\n", storeDir)
}
url := fmt.Sprintf("%s/%s", baseUrl, fileName)
fmt.Printf("Fetching %s...", url)
var body []byte
if strings.EqualFold(proto, "http") {
resp, err := http.Get(url)
if err != nil {
fmt.Printf(" Error\n")
return fmt.Errorf("Can not get %s: %v\n", url, err)
}
defer resp.Body.Close()
body, err = io.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf("Can not read %s response body: %v\n", url, err)
}
} else if strings.EqualFold(proto, "file") {
url = strings.Replace(url, "file:", "", 1)
f, err := os.Open(url)
if err != nil {
return fmt.Errorf("Error accessing file %s", url)
}
defer f.Close()
body, err = io.ReadAll(f)
if err != nil {
return fmt.Errorf("Can not read file %s: %v\n", url, err)
}
}
// Check integrity
if len(checksum) > 0 {
err = checkIntegrity(body, checksum)
if err != nil {
return fmt.Errorf("Error checking integrity")
}
}
dest := fmt.Sprintf("%s/%s", storeDir, fileName)
f, err := os.Create(dest) // creates a file at current directory
if err != nil {
return fmt.Errorf("Can not create file %s: %v\n", dest, err)
}
defer f.Close()
f.Write(body)
fmt.Printf(" Done\n")
return nil
}
func checkIntegrity(data []byte, checksum []byte) error {
sum := sha256.Sum256(data)
if false == bytes.Equal(checksum[:],sum[:]) {
return fmt.Errorf("Invalid checksum: %x != %x", sum, checksum)
}
return nil
}
// Get checksum from manifest, for each file in fileList
/* MANIFEST format:
* base-dbg.txz a5b51f3d54686509e91ca9c30e9f1cd93dc757f25c643609b3c35e7119c0531d 1654 base_dbg "Base system (Debugging)" off
* base.txz e85b256930a2fbc04b80334106afecba0f11e52e32ffa197a88d7319cf059840 26492 base "Base system (MANDATORY)" on
* kernel-dbg.txz 6b47a6cb83637af1f489aa8cdb802d9db936ea864887188cfc69d8075762214e 912 kernel_dbg "Kernel (Debugging)" on
*/
func buildFileChecksumFromManifest(manifest string, fileList []string) (map[string][]byte, error) {
var ckarr = make(map[string][]byte)
rm, err := os.Open(manifest)
if err != nil {
return ckarr, fmt.Errorf("Unable to open MANIFEST: %v", err)
}
fscan := bufio.NewScanner(rm)
fscan.Split(bufio.ScanLines)
// For each MANIFEST line...
for fscan.Scan() {
fields := strings.Fields(fscan.Text())
fn := fields[0]
fck := fields[1]
hexSum, err := hex.DecodeString(fck)
if err != nil {
return ckarr, fmt.Errorf("Invalid value for checksum %s", fck)
}
// ... Find the corresponding file in fileList, then add to checksum array ckarr
for _, f := range fileList {
if strings.EqualFold(f, fn) {
ckarr[fn] = hexSum
break
}
}
}
if len(ckarr) < len(fileList) {
return ckarr, fmt.Errorf("Missing file in MANIFEST")
}
return ckarr, nil
}

View File

@ -179,6 +179,15 @@ func getHostId() (string, error) {
return strings.Split(string(content), "\n")[0], nil
}
func getArch() (string, error) {
out, err := executeCommand("/usr/bin/uname -p")
if err != nil {
return "", fmt.Errorf("Error executing \"/usr/bin/uname -p\": %v", err)
}
return strings.Split(out, "\n")[0], nil
}
func getFreeBSDVersion() (FreeBSDVersion, error) {
var version FreeBSDVersion
regex := `([0-9]{1,2})(\.)?([0-9]{1,2})?\-([^\-]*)(\-)?(p[0-9]{1,2})?`
@ -219,6 +228,9 @@ func NewJailHost() (JailHost, error) {
if jh.hostname, err = getHostname(); err != nil {
return jh, err
}
if jh.arch, err = getArch(); err != nil {
return jh, err
}
if jh.hostid, err = getHostId(); err != nil {
return jh, err
}

View File

@ -272,7 +272,9 @@ func listJailsFromDirectory(dir string, dsname string) ([]Jail, error) {
if rj.Path == j.RootPath {
j.JID = rj.Jid
j.Running = true
// FIXME ??? Shouldn't be ioc-$Name ?
j.InternalName = rj.Name
j.Devfs_ruleset = rj.Devfs_ruleset
break
}
}

View File

@ -1,11 +1,12 @@
package cmd
import (
"encoding/json"
"fmt"
"io/ioutil"
"os"
"fmt"
"sync"
"strings"
"io/ioutil"
"encoding/json"
"github.com/spf13/cobra"
"github.com/spf13/viper"
@ -14,7 +15,7 @@ import (
)
const (
gVersion = "0.30"
gVersion = "0.34"
// TODO : Get from $jail_zpool/defaults.json
MIN_DYN_DEVFS_RULESET = 1000
@ -51,6 +52,12 @@ var (
gMigrateDestDatastore string
gYesToAll bool
gFetchRelease string
gFetchIntoDS string
gFetchFrom string
gMdevfs sync.Mutex
rootCmd = &cobra.Command{
Use: "gocage",
Short: "GoCage is a FreeBSD Jail management tool",
@ -129,7 +136,7 @@ ex: gocage list srv-db srv-web`,
} else {
StartJail(args)
}
WriteConfigToDisk(false)
WriteConfigToDisk("", false, false)
},
}
@ -143,7 +150,7 @@ ex: gocage list srv-db srv-web`,
ListJails(args, false)
StopJail(args)
StartJail(args)
WriteConfigToDisk(false)
WriteConfigToDisk("", false, false)
},
}
@ -166,7 +173,7 @@ Multiples properties can be specified, separated with space (Ex: gocage set allo
// Load inventory
ListJails(args, false)
SetJailProperties(args)
WriteConfigToDisk(true)
WriteConfigToDisk("", true, false)
},
}
@ -248,7 +255,7 @@ You can specify multiple jails.`,
// Load inventory
ListJails(args, false)
MigrateJail(args)
WriteConfigToDisk(false)
WriteConfigToDisk("", false, false)
},
}
@ -285,6 +292,28 @@ You can specify multiple datastores.`,
},
}
fetchCmd = &cobra.Command{
Use: "fetch",
Short: "Fetch FreeBSD release to local datastore",
Run: func(cmd *cobra.Command, args []string) {
err := fetchRelease(gFetchRelease, "http", gJailHost.arch, gFetchIntoDS, gFetchFrom)
if err != nil {
fmt.Printf("%v\n", err)
} else {
extractRelease(gFetchRelease, gFetchIntoDS)
}
},
}
UpdateCmd = &cobra.Command{
Use: "update",
Short: "Update FreeBSD release",
Run: func(cmd *cobra.Command, args []string) {
ListJails(args, false)
UpdateJail(args)
},
}
testCmd = &cobra.Command{
Use: "test",
Short: "temporary command to test some code snippet",
@ -304,7 +333,7 @@ func init() {
rootCmd.PersistentFlags().StringVarP(&gConfigFile, "config", "c", "/usr/local/etc/gocage.conf.yml", "GoCage configuration file")
rootCmd.PersistentFlags().BoolVarP(&gUseSudo, "sudo", "u", false, "Use sudo to run commands")
rootCmd.PersistentFlags().StringVarP(&gTimeZone, "timezone", "t", "", "Specify timezone. Will get from /var/db/zoneinfo if not set.")
rootCmd.PersistentFlags().BoolVarP(&gDebug, "debug", "d", false, "Debug mode")
rootCmd.PersistentFlags().BoolVar(&gDebug, "debug", false, "Debug mode")
// Command dependant switches
@ -337,6 +366,13 @@ func init() {
migrateCmd.Flags().StringVarP(&gMigrateDestDatastore, "datastore", "d", "", "Path of destination datastore for jail (Ex: \"/iocage\")")
migrateCmd.Flags().BoolVarP(&gYesToAll, "yes", "y", false, "Answer yes to all questions")
migrateCmd.MarkFlagRequired("datastore")
fetchCmd.Flags().StringVarP(&gFetchRelease, "release", "r", "", "Release to fetch (e.g.: \"13.1\"")
fetchCmd.Flags().StringVarP(&gFetchIntoDS, "datastore", "o", "", "Datastore release will be saved to")
fetchCmd.Flags().StringVarP(&gFetchFrom, "from", "d", "", "Repository to download from. Should contain XY.Z-RELEASE. File protocol supported")
fetchCmd.MarkFlagRequired("release")
fetchCmd.MarkFlagRequired("datastore")
// Now declare commands
rootCmd.AddCommand(versionCmd)
@ -352,6 +388,8 @@ func init() {
rootCmd.AddCommand(snapshotCmd)
rootCmd.AddCommand(migrateCmd)
rootCmd.AddCommand(datastoreCmd)
rootCmd.AddCommand(fetchCmd)
rootCmd.AddCommand(UpdateCmd)
rootCmd.AddCommand(testCmd)
@ -435,59 +473,66 @@ func initConfig() {
}
/********************************************************************************
* Write jails config which been updated to disk.
* Write jail(s) config which been updated to disk.
* If name is specified, work on the jail. If name is empty string, work on all.
* If changeauto not set, values which are in "auto" mode on disk
* won't be overwritten (p.ex defaultrouter wont be overwritten with current
* default route, so if route change on jailhost this will reflect on jail next
* start)
*******************************************************************************/
func WriteConfigToDisk(changeauto bool) {
func WriteConfigToDisk(jailName string, changeauto bool, forceWrite bool) {
for _, j := range gJails {
if j.ConfigUpdated {
//log.Debug("%s config has changed, write changes to disk\n", j.Name)
if len(jailName) > 0 && j.Name == jailName || len(jailName) == 0 {
if j.ConfigUpdated || forceWrite {
log.Debug("%s config has changed, write changes to disk\n", j.Name)
// we will manipulate properties so get a copy
jc := j.Config
// we will manipulate properties so get a copy
jc := j.Config
if changeauto == false {
// Overwrite "auto" properties
ondiskjc, err := getJailConfig(j.ConfigPath)
if err != nil {
panic(err)
}
// TODO : List all fields, then call getStructFieldValue to compare value with "auto"
// If "auto" then keep it that way before writing ondiskjc to disk
var properties []string
properties = getStructFieldNames(ondiskjc, properties, "")
for _, p := range properties {
v, _, err := getStructFieldValue(ondiskjc, p)
if changeauto == false {
// Overwrite "auto" properties
ondiskjc, err := getJailConfig(j.ConfigPath)
if err != nil {
panic(err)
}
if v.String() == "auto" {
err = setStructFieldValue(&jc, p, "auto")
// TODO : List all fields, then call getStructFieldValue to compare value with "auto"
// If "auto" then keep it that way before writing ondiskjc to disk
var properties []string
properties = getStructFieldNames(ondiskjc, properties, "")
for _, p := range properties {
v, _, err := getStructFieldValue(ondiskjc, p)
if err != nil {
fmt.Printf("ERROR sanitizing config: %s\n", err.Error())
os.Exit(1)
panic(err)
}
if v.String() == "auto" {
err = setStructFieldValue(&jc, p, "auto")
if err != nil {
fmt.Printf("ERROR sanitizing config: %s\n", err.Error())
os.Exit(1)
}
}
}
}
}
marshaled, err := json.MarshalIndent(jc, "", " ")
if err != nil {
fmt.Printf("ERROR marshaling config: %s\n", err.Error())
}
//fmt.Printf(string(marshaled))
if os.WriteFile(j.ConfigPath, []byte(marshaled), 0644); err != nil {
fmt.Printf("Error writing config file %s: %v\n", j.ConfigPath, err)
os.Exit(1)
marshaled, err := json.MarshalIndent(jc, "", " ")
if err != nil {
fmt.Printf("ERROR marshaling config: %s\n", err.Error())
}
//fmt.Printf("DEBUG: Will write config to disk, with content:\n")
//fmt.Printf(string(marshaled))
if os.WriteFile(j.ConfigPath, []byte(marshaled), 0644); err != nil {
fmt.Printf("Error writing config file %s: %v\n", j.ConfigPath, err)
os.Exit(1)
}
}
}
}
}
func Execute() {
if err := rootCmd.Execute(); err != nil {
fmt.Fprintln(os.Stderr, err)

View File

@ -4,6 +4,8 @@ import (
"os"
"fmt"
"net"
"sync"
"time"
"errors"
"regexp"
"reflect"
@ -189,24 +191,26 @@ func prepareJailedZfsDatasets(jail *Jail) error {
}
for _, d := range strings.Split(jail.Config.Jail_zfs_dataset, " ") {
// Check if dataset exist, create if necessary
cmd := fmt.Sprintf("zfs get -H creation %s/%s", jail.Zpool, d)
// Support jailing datasets on differents pools : dataset should be specified with pool name
cmd := fmt.Sprintf("zfs get -H creation %s", d)
out, err := executeCommand(cmd)
if err != nil {
if strings.HasSuffix(out, "dataset does not exist") {
cmd = fmt.Sprintf("zfs create -o compression=lz4 -o mountpoint=none %s/%s", jail.Zpool, d)
// Support jailing datasets on differents pools : dataset should be specified with pool name
cmd = fmt.Sprintf("zfs create -o compression=lz4 -o mountpoint=none %s", d)
_, err = executeCommand(cmd)
if err != nil {
return errors.New(fmt.Sprintf("Error creating dataset %s/%s: %s", jail.Zpool, d, err.Error()))
return errors.New(fmt.Sprintf("Error creating dataset %s: %s", d, err.Error()))
}
} else {
return errors.New(fmt.Sprintf("Error getting zfs dataset %s: %s", d, err.Error()))
}
}
cmd = fmt.Sprintf("zfs set jailed=on %s/%s", jail.Zpool, d)
cmd = fmt.Sprintf("zfs set jailed=on %s", d)
out, err = executeCommand(cmd)
if err != nil {
return errors.New(fmt.Sprintf("Error executing \"zfs set jailed=on %s/%s\": %s", jail.Zpool, d, err.Error()))
return errors.New(fmt.Sprintf("Error executing \"zfs set jailed=on %s\": %s", d, err.Error()))
}
}
}
@ -217,27 +221,27 @@ func jailZfsDatasets(jail *Jail) error {
if jail.Config.Jail_zfs > 0 {
for _, d := range strings.Split(jail.Config.Jail_zfs_dataset, " ") {
// Jail dataset
cmd := fmt.Sprintf("zfs jail %d %s/%s", jail.JID, jail.Zpool, d)
// Support jailing datasets on differents pools : dataset should be specified with pool name
cmd := fmt.Sprintf("zfs jail %d %s", jail.JID, d)
out, err := executeCommand(cmd)
if err != nil {
return errors.New(fmt.Sprintf("Error jailling zfs dataset %s: %v: out", d, err, out))
}
// Mount from inside jail if mountpoint is set
cmd = fmt.Sprintf("zfs get -H -o value mountpoint %s/%s", jail.Zpool, d)
cmd = fmt.Sprintf("zfs get -H -o value mountpoint %s", d)
out, err = executeCommand(cmd)
if err != nil {
return errors.New(fmt.Sprintf("Error getting zfs dataset %s/%s mountpoint: %v: %s", jail.Zpool, d, err, out))
return errors.New(fmt.Sprintf("Error getting zfs dataset %s mountpoint: %v: %s", d, err, out))
}
if len(out) > 0 && out != "-" && (false == strings.EqualFold(out, "none")) {
//cmd = fmt.Sprintf("zfs mount %s/%s", jail.Zpool, d)
cmd = fmt.Sprintf("zfs mount -a")
// Should we "mount -a" ? cmd = fmt.Sprintf("zfs mount -a")
cmd = fmt.Sprintf("zfs mount %s", d)
out, err = executeCommandInJail(jail, cmd)
if err != nil {
// If already mounted, continue processing
if ! strings.HasSuffix(out, "filesystem already mounted\n") {
//return errors.New(fmt.Sprintf("Error mounting zfs dataset %s/%s: %v: %s", jail.Zpool, d, err, out))
return errors.New(fmt.Sprintf("Error executing \"zfs mount -a\" from inside jail: %v: %s", err, out))
return errors.New(fmt.Sprintf("Error mounting zfs dataset %s from inside jail: %v: %s", d, err, out))
}
}
}
@ -472,12 +476,17 @@ func genNatIpv4(jail *Jail) ([]string, error) {
return ippair, nil
}
func buildDevfsRuleSet(jail *Jail) (error, int) {
// FIXME : Must lock this function so parallel start do not
func buildDevfsRuleSet(jail *Jail, m *sync.Mutex) (error, int) {
rulesets := []int{}
m.Lock()
//defer m.Unlock()
// Get known rulesets
out, err := executeCommand("devfs rule showsets")
if err != nil {
m.Unlock()
return errors.New(fmt.Sprintf("Error executing command \"devfs rule showsets\": %v; command returned: %s\n", err, out)), 0
}
srs := strings.Split(out, "\n")
@ -505,19 +514,23 @@ func buildDevfsRuleSet(jail *Jail) (error, int) {
// UPDATE: We don't need this as every jail have a default Devfs_ruleset value
/*ds, err := getDatastoreFromArray(jail.Datastore, gDatastores)
if err != nil {
m.Unlock()
return errors.New(fmt.Sprintf("Error getting datastore %s for jail %s", jail.Datastore, jail.Name)), 0
}
defaultrs, err := strconv.ParseInt(ds.DefaultJailConfig.Devfs_ruleset, 10, 64)
if err != nil {
m.Unlock()
return errors.New(fmt.Sprintf("Error parsing default devfs_ruleset for datastore %s", jail.Datastore)), 0
}*/
// Clone configured devfs_ruleset to a dynamic ruleset
if false == isStringInArray(srs, jail.Config.Devfs_ruleset) {
m.Unlock()
return errors.New(fmt.Sprintf("Unknown ruleset: %s", jail.Config.Devfs_ruleset)), 0
}
rs, _ := strconv.Atoi(jail.Config.Devfs_ruleset)
err = copyDevfsRuleset(ruleset, rs)
m.Unlock()
if err != nil {
return err, 0
}
@ -945,7 +958,7 @@ func setupVnetInterfaceJailSide(jail *Jail, hsepair string) error {
// Get bridge MTU
mtu, err := gJailHost.GetBridgeMTU(bridge)
if err != nil {
return fmt.Errorf("Error getting bridge mtu: %v\n", err)
return fmt.Errorf("Error getting bridge %s mtu: %v\n", bridge, err)
}
cmd = fmt.Sprintf("/usr/sbin/jexec %d ifconfig %s mtu %d", jail.JID, jsepair, mtu)
@ -1010,7 +1023,7 @@ func generateResolvConf(jail *Jail) error {
for _, l := range strings.Split(jail.Config.Resolver, ";") {
f.WriteString(fmt.Sprintf("%s\n", l))
}
} else if jail.Config.Resolver == "none" {
} else if jail.Config.Resolver == "none" || jail.Config.Resolver == "/etc/resolv.conf" {
read, err := ioutil.ReadFile("/etc/resolv.conf")
if err != nil {
return fmt.Errorf("Error opening /etc/resolv.conf: %v", err)
@ -1052,6 +1065,9 @@ func cleanAfterStartCrash() {
// Start all jails with boot=true, in priority order
func StartJailsAtBoot() {
var startList []Jail
var wg *sync.WaitGroup
var curThNb int
var curPri int
// Get boot enabled jails
for _, j := range gJails {
@ -1069,11 +1085,51 @@ func StartJailsAtBoot() {
}
JailsOrderedBy(fct.Interface().(jailLessFunc)).Sort(startList)
for _, j := range startList {
wg = new(sync.WaitGroup)
curThNb = 0
for i, j := range startList {
jFullName := fmt.Sprintf("%s/%s", j.Datastore, j.Name)
log.Debugf("Starting %s with priority %s\n", jFullName, j.Config.Priority)
StartJail([]string{jFullName})
jailPri, err := strconv.Atoi(j.Config.Priority)
if err != nil {
panic(fmt.Sprintf("Invalid format for Priority (Jail %s)\n", jFullName))
}
if (curThNb >= gMaxThreads || i == 0) {
// FIXME : Use a pool instead of waiting for all threads to run a new one
wg.Wait()
curThNb = 0
wg.Add(1)
curThNb++
curPri = jailPri
go func(jailFullName string) {
defer wg.Done()
StartJail([]string{jailFullName})
}(jFullName)
} else {
if (curPri == jailPri) {
wg.Add(1)
curThNb++
go func(jailFullName string) {
defer wg.Done()
StartJail([]string{jailFullName})
}(jFullName)
} else {
wg.Wait()
curThNb = 0
wg.Add(1)
curThNb++
curPri = jailPri
go func(jailFullName string) {
defer wg.Done()
StartJail([]string{jailFullName})
}(jFullName)
}
}
}
wg.Wait()
}
@ -1296,7 +1352,7 @@ func StartJail(args []string) {
net = append(net, strings.Split(cj.Config.Vnet_interfaces, " ")...)
}
err, dynrs := buildDevfsRuleSet(cj)
err, dynrs := buildDevfsRuleSet(cj, &gMdevfs)
if err != nil {
fmt.Printf("%s\n", err.Error())
return
@ -1309,7 +1365,7 @@ func StartJail(args []string) {
}
// Synchronize jail config to disk
WriteConfigToDisk(false)
WriteConfigToDisk(cj.Name, false, false)
start_cmd := fmt.Sprintf("/usr/sbin/jail -f /var/run/jail.%s.conf -c", cj.InternalName)
@ -1331,8 +1387,9 @@ func StartJail(args []string) {
fmt.Printf(" > Start jail: OK\n")
fmt.Printf(" > With devfs ruleset %d\n", dynrs)
// Update running state and JID
// Update running state, JID and Devfs_ruleset
cj.Running = true
cj.Devfs_ruleset = dynrs
rjails, err := jail.GetJails()
if err != nil {
fmt.Printf("Error: Unable to list running jails\n")
@ -1419,9 +1476,9 @@ func StartJail(args []string) {
if len(cj.Config.Exec_start) > 0 {
fmt.Printf(" > Start services:\n")
cmd := fmt.Sprintf("/usr/sbin/setfib %s /usr/sbin/jexec %d %s", cj.Config.Exec_fib, cj.JID, cj.Config.Exec_start)
out, err := executeCommand(cmd)
err := executeCommandNonBlocking(cmd)
if err != nil && len(out) > 0 {
fmt.Printf("Error: %v: %s\n", err, out)
fmt.Printf("Error: %v\n", err)
} else {
fmt.Printf(" > Start services: OK\n")
}
@ -1454,6 +1511,11 @@ func StartJail(args []string) {
// TODO: Handle dhcp
// TODO: Apply rctl
// Update last_started
dt := time.Now()
curDate := fmt.Sprintf("%s", dt.Format("2006-01-02 15:04:05"))
fmt.Sprintf(cj.Config.Last_started, curDate)
WriteConfigToDisk(cj.Name, false, true)
/*

View File

@ -4,6 +4,7 @@ import (
"os"
"fmt"
//"log"
"sync"
"errors"
"regexp"
"os/exec"
@ -50,10 +51,10 @@ func umountAndUnjailZFS(jail *Jail) error {
for _, zd := range ds {
// 1. Get dataset and childs
cmd := fmt.Sprintf("zfs list -H -r -o name -S name %s/%s", jail.Zpool, zd)
cmd := fmt.Sprintf("zfs list -H -r -o name -S name %s", zd)
out, err := executeCommand(cmd)
if err != nil {
fmt.Printf(fmt.Sprintf("ERROR listing dataset %s/%s\n", jail.Zpool, zd))
fmt.Printf(fmt.Sprintf("ERROR listing dataset %s\n", zd))
os.Exit(1)
}
for _, c := range strings.Split(out, "\n") {
@ -71,10 +72,10 @@ func umountAndUnjailZFS(jail *Jail) error {
}
// 2. Unjail dataset from the host
cmd := fmt.Sprintf("zfs unjail %s %s/%s", jail.InternalName, jail.Zpool, ds[len(ds)-1])
cmd := fmt.Sprintf("zfs unjail %s %s", jail.InternalName, ds[len(ds)-1])
_, err := executeCommand(cmd)
if err != nil {
fmt.Printf("ERROR unjailing %s/%s: %s\n", jail.Zpool, ds[len(ds)-1], err.Error())
fmt.Printf("ERROR unjailing %s: %s\n", ds[len(ds)-1], err.Error())
return err
}
@ -169,8 +170,13 @@ func stopJail(jail *Jail) error {
}
// Stop all running jails by reverse priority
// Parallelize up to gMaxThreads
// Only parallelize same priority level jails
func StopAllRunningJails() {
var stopList []Jail
var wg *sync.WaitGroup
var curThNb int
var curPri int
// Get boot enabled jails
for _, j := range gJails {
@ -187,12 +193,53 @@ func StopAllRunningJails() {
return
}
JailsOrderedBy(fct.Interface().(jailLessFunc)).Sort(stopList)
for _, j := range stopList {
wg = new(sync.WaitGroup)
curThNb = 0
for i, j := range stopList {
jFullName := fmt.Sprintf("%s/%s", j.Datastore, j.Name)
log.Debugf("Stopping %s with priority %s\n", jFullName, j.Config.Priority)
StopJail([]string{jFullName})
jailPri, err := strconv.Atoi(j.Config.Priority)
if err != nil {
panic(fmt.Sprintf("Invalid format for Priority (Jail %s)\n", jFullName))
}
if (curThNb >= gMaxThreads || i == 0) {
// FIXME : Use a pool instead of waiting for all threads to run a new one
wg.Wait()
curThNb = 0
wg.Add(1)
curThNb++
curPri = jailPri
go func(jailFullName string) {
defer wg.Done()
StopJail([]string{jailFullName})
}(jFullName)
} else {
if (curPri == jailPri) {
wg.Add(1)
curThNb++
go func(jailFullName string) {
defer wg.Done()
StopJail([]string{jailFullName})
}(jFullName)
} else {
wg.Wait()
curThNb = 0
wg.Add(1)
curThNb++
curPri = jailPri
go func(jailFullName string) {
defer wg.Done()
StopJail([]string{jailFullName})
}(jFullName)
}
}
}
wg.Wait()
}
/*
@ -228,14 +275,32 @@ func StopJail(args []string) {
fmt.Printf("Error getting jail: %s\n", err)
continue
}
if cj.Running == false {
fmt.Printf("Jail %s is not running!\n", cj.Name)
continue
}
fmt.Printf("> Stopping jail %s\n", a)
// Get current version to update config.json
cvers, err := executeCommandInJail(cj, "/bin/freebsd-version")
if err != nil {
fmt.Printf("ERROR: %s\n", err.Error())
return
}
cvers = strings.TrimRight(cvers, "\n")
//fmt.Sprintf(cj.Config.Release, cvers)
//cj.Config.Release = cvers
//cj.ConfigUpdated = true
// This is working in this context, but value is not available in WriteConfigToDisk context :/
setStructFieldValue(cj, "Config.Release", cvers)
fmt.Printf("DEBUG: release was set, now is : %s\n", cj.Config.Release)
// We need to get the real Config object, not a copy of it
out, err := executeCommand(fmt.Sprintf("rctl jail:%s", cj.InternalName))
if err == nil && len(out) > 0 {
fmt.Printf(" > Remove RCTL rules:\n")
@ -286,20 +351,13 @@ func StopJail(args []string) {
fmt.Printf(" > Destroy VNet interfaces: OK\n")
}
}
// Get currently used ruleset from /var/run/jail.$internal_name.conf
ruleset, err := getValueFromRunningConfig(cj.InternalName, "devfs_ruleset")
if err != nil {
fmt.Printf("ERROR getting current devfs ruleset: %s\n", err.Error())
return
}
rsi, _ := strconv.Atoi(ruleset)
fmt.Printf(" > Remove devfs ruleset %d: \n", rsi)
err = deleteDevfsRuleset(rsi)
fmt.Printf(" > Remove devfs ruleset %d: \n", cj.Devfs_ruleset)
err = deleteDevfsRuleset(cj.Devfs_ruleset)
if err != nil {
fmt.Printf("ERROR: %s\n", err.Error())
} else {
fmt.Printf(" > Remove devfsruleset %d: OK\n", rsi)
fmt.Printf(" > Remove devfsruleset %d: OK\n", cj.Devfs_ruleset)
}
fmt.Printf(" > Stop jail %s:\n", cj.Name)
@ -401,7 +459,14 @@ func StopJail(args []string) {
if err = setStructFieldValue(&gJails[i], "InternalName", ""); err != nil {
fmt.Printf("ERROR: clearing InternalName property: %s\n", err.Error())
}
if err = setStructFieldValue(&gJails[i], "Devfs_ruleset", "0"); err != nil {
fmt.Printf("ERROR: setting Devfs_ruleset property to 0: %s\n", err.Error())
}
}
}
fmt.Printf("DEBUG: release = %s\n", cj.Config.Release)
WriteConfigToDisk(cj.Name, false, true)
}
}

View File

@ -27,6 +27,7 @@ type Jail struct {
Running bool
// No need, Config.Release always represent what is running (plus it know release for non-running jails)
//Release string
Devfs_ruleset int // The effective devfs ruleset generated at runtime
Zpool string
Datastore string
}
@ -212,6 +213,7 @@ type FreeBSDVersion struct {
type JailHost struct {
hostname string
hostid string
arch string
default_gateway4 string
default_gateway6 string
default_interface string
@ -247,6 +249,8 @@ type JailSort struct {
DatastoreDec jailLessFunc
ZpoolInc jailLessFunc
ZpoolDec jailLessFunc
Devfs_rulesetInc jailLessFunc
Devfs_rulesetDec jailLessFunc
Config JailConfigSort
}

157
cmd/update.go Normal file
View File

@ -0,0 +1,157 @@
package cmd
import (
"os"
"fmt"
//"log"
"time"
)
const (
fbsdUpdateConfig = `
# $FreeBSD: releng/12.2/usr.sbin/freebsd-update/freebsd-update.conf 337338 2018-08-04 22:25:41Z brd $
# Trusted keyprint. Changing this is a Bad Idea unless you've received
# a PGP-signed email from <security-officer@FreeBSD.org> telling you to
# change it and explaining why.
KeyPrint 800651ef4b4c71c27e60786d7b487188970f4b4169cc055784e21eb71d410cc5
# Server or server pool from which to fetch updates. You can change
# this to point at a specific server if you want, but in most cases
# using a "nearby" server won't provide a measurable improvement in
# performance.
ServerName update.FreeBSD.org
# Components of the base system which should be kept updated.
Components world
# Example for updating the userland and the kernel source code only:
# Components src/base src/sys world
# Paths which start with anything matching an entry in an IgnorePaths
# statement will be ignored.
IgnorePaths
# Paths which start with anything matching an entry in an IDSIgnorePaths
# statement will be ignored by "freebsd-update IDS".
IDSIgnorePaths /usr/share/man/cat
IDSIgnorePaths /usr/share/man/whatis
IDSIgnorePaths /var/db/locate.database
IDSIgnorePaths /var/log
# Paths which start with anything matching an entry in an UpdateIfUnmodified
# statement will only be updated if the contents of the file have not been
# modified by the user (unless changes are merged; see below).
UpdateIfUnmodified /etc/ /var/ /root/ /.cshrc /.profile
# When upgrading to a new FreeBSD release, files which match MergeChanges
# will have any local changes merged into the version from the new release.
MergeChanges /etc/
### Default configuration options:
# Directory in which to store downloaded updates and temporary
# files used by FreeBSD Update.
# WorkDir /var/db/freebsd-update
# Destination to send output of "freebsd-update cron" if an error
# occurs or updates have been downloaded.
# MailTo root
# Is FreeBSD Update allowed to create new files?
# AllowAdd yes
# Is FreeBSD Update allowed to delete files?
# AllowDelete yes
# If the user has modified file ownership, permissions, or flags, should
# FreeBSD Update retain this modified metadata when installing a new version
# of that file?
# KeepModifiedMetadata yes
# When upgrading between releases, should the list of Components be
# read strictly (StrictComponents yes) or merely as a list of components
# which *might* be installed of which FreeBSD Update should figure out
# which actually are installed and upgrade those (StrictComponents no)?
# StrictComponents no
# When installing a new kernel perform a backup of the old one first
# so it is possible to boot the old kernel in case of problems.
# BackupKernel yes
# If BackupKernel is enabled, the backup kernel is saved to this
# directory.
# BackupKernelDir /boot/kernel.old
# When backing up a kernel also back up debug symbol files?
# BackupKernelSymbolFiles no
# Create a new boot environment when installing patches
# CreateBootEnv yes
`
)
// Internal usage only
func updateJail(jail *Jail) error {
// Create default config as temporary file
cfgFile, err := os.CreateTemp("", "gocage-jail-update-")
if err != nil {
return err
}
cfgFile.Write([]byte(fbsdUpdateConfig))
defer cfgFile.Close()
//defer os.Remove(cfgFile.Name())
cmd := fmt.Sprintf("/usr/sbin/freebsd-update --not-running-from-cron -f %s -b %s --currently-running %s fetch install",
cfgFile.Name(), jail.RootPath, jail.Config.Release)
fmt.Printf("DEBUG: Prepare to execute \"%s\"\n", cmd)
err = executeCommandWithOutputToStdout(cmd)
if err != nil {
return err
}
// Get and write new release into config.json
return nil
}
func UpdateJail(args []string) {
// Current jail were stopping
var cj *Jail
var err error
for _, a := range args {
// Check if jail exist and is distinctly named
cj, err = getJailFromArray(a, gJails)
if err != nil {
fmt.Printf("Error getting jail: %s\n", err)
continue
}
fmt.Printf(" > Snapshot jail %s\n", cj.Name)
// Set snapshot name
dt := time.Now()
curDate := fmt.Sprintf("%s", dt.Format("2006-01-02_15-04-05"))
gSnapshotName = fmt.Sprintf("goc_update_%s_%s", cj.Config.Release, curDate)
err := createJailSnapshot(*cj)
if err != nil {
fmt.Printf(" > Snapshot jail %s: ERROR: %s\n", cj.Name, err.Error())
return
}
fmt.Printf(" > Snapshot jail %s: OK\n", cj.Name)
fmt.Printf(" > Update jail %s\n", cj.Name)
err = updateJail(cj)
if err != nil {
fmt.Printf("ERROR: %s\n", err.Error())
} else {
fmt.Printf(" > Update jail %s: OK\n", cj.Name)
}
}
}

View File

@ -4,7 +4,7 @@ import (
"io"
"os"
"fmt"
"log"
//"log"
"sort"
"bufio"
"errors"
@ -15,11 +15,14 @@ import (
"io/ioutil"
"github.com/google/shlex"
"github.com/c2h5oh/datasize"
log "github.com/sirupsen/logrus"
)
const (
ipv4re = `[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}`
ifconfigipv4re = `inet[[:space:]](` + ipv4re + `)`
// Maximum thread qty for start/stop
gMaxThreads = 4
)
/*****************************************************************************
@ -214,7 +217,7 @@ func executeCommand(cmdline string) (string, error) {
// else
word = word + string(c)
}
if len(cmd) > 1 {
out, err = exec.Command(cmd[0], cmd[1:]...).CombinedOutput()
} else {
@ -224,6 +227,144 @@ func executeCommand(cmdline string) (string, error) {
return string(out), err
}
/* From iocage:
* # Courtesy of @william-gr
* # service(8) and some rc.d scripts have the bad h*abit of
* # exec'ing and never closing stdout/stderr. This makes
* # sure we read only enough until the command exits and do
* # not wait on the pipe to close on the other end.
* So this function executes process without waiting after completion
*/
func executeCommandNonBlocking(cmdline string) (error) {
var cmd []string
var oCmd *exec.Cmd
var err error
if gUseSudo {
cmd = append(cmd, "sudo")
}
var word string
var in_escaped bool
// Split by words, or " enclosed words
for i, c := range (cmdline) {
if string(c) == "\"" {
if in_escaped {
// This is the closing "
cmd = append(cmd, word)
in_escaped = false
} else {
in_escaped = true
}
continue
}
if string(c) == " " {
if in_escaped {
word = word + string(c)
continue
} else {
cmd = append(cmd, word)
word = ""
continue
}
}
if i == (len(cmdline) - 1) {
word = word + string(c)
cmd = append(cmd, word)
break
}
// else
word = word + string(c)
}
if len(cmd) > 1 {
oCmd = exec.Command(cmd[0], cmd[1:]...)
} else {
oCmd = exec.Command(cmd[0])
}
if err = oCmd.Start(); err != nil {
return err
}
err = oCmd.Wait()
return err
}
// Executed command outputs to stdout in realtime
func executeCommandWithOutputToStdout(cmdline string) (error) {
var cmd []string
var err error
if gUseSudo {
cmd = append(cmd, "sudo")
}
var word string
var in_escaped bool
// Split by words, or " enclosed words
for i, c := range (cmdline) {
if string(c) == "\"" {
if in_escaped {
// This is the closing "
cmd = append(cmd, word)
in_escaped = false
} else {
in_escaped = true
}
continue
}
if string(c) == " " {
if in_escaped {
word = word + string(c)
continue
} else {
cmd = append(cmd, word)
word = ""
continue
}
}
if i == (len(cmdline) - 1) {
word = word + string(c)
cmd = append(cmd, word)
break
}
// else
word = word + string(c)
}
var execHandle *exec.Cmd
if len(cmd) > 1 {
execHandle = exec.Command(cmd[0], cmd[1:]...)
} else {
execHandle = exec.Command(cmd[0])
}
stdout, err := execHandle.StdoutPipe()
if err != nil {
return err
}
execHandle.Start()
buf := bufio.NewReader(stdout)
for {
line, _, err := buf.ReadLine()
if err != nil {
if err.Error() == "EOF" {
return nil
} else {
return err
}
}
fmt.Println(string(line))
}
return fmt.Errorf("Unknown error: you shouldn't be here!\n")
}
func executeCommandInJail(jail *Jail, cmdline string) (string, error) {
var cmd []string
@ -411,6 +552,31 @@ func zfsCopyIncremental(firstsnap string, secondsnap string, dest string) error
return nil
}
// Return true if dataset exist, false if not, false & error if anything else
func doZfsDatasetExist(dataset string) (bool, error) {
cmd := fmt.Sprintf("zfs list %s", dataset)
out, err := executeCommand(cmd)
if err != nil {
if strings.HasSuffix(strings.TrimSuffix(out, "\n"), "dataset does not exist") {
return false, nil
} else {
return false, errors.New(fmt.Sprintf("%v; command returned \"%s\"", err, out))
}
}
return true, nil
}
// Create ZFS dataset. mountpoint can be "none", then the dataset won't be mounted
func createZfsDataset(dataset, mountpoint, compression string) error {
cmd := fmt.Sprintf("zfs create -o mountpoint=%s -o compression=%s %s", mountpoint, compression, dataset)
out, err := executeCommand(cmd)
if err != nil {
return errors.New(fmt.Sprintf("%v; command returned \"%s\"", err, out))
}
return nil
}
/*****************************************************************************
*
* rc.conf management
@ -518,7 +684,7 @@ func copyDevfsRuleset(ruleset int, srcrs int) error {
/********************************************************************************
* Returns value of parameter as read in /var/run/jail.$InternalName.conf
* Directoves without value will return "true" if found
* Directives without value will return "true" if found
* Returns an error if parameter not found in file
*******************************************************************************/
func getValueFromRunningConfig(jname string, param string) (string, error) {
@ -606,7 +772,8 @@ func getJailFromArray(name string, jarray []Jail) (*Jail, error) {
}
for i, j := range jarray {
if jail == j.Name {
//if jail == j.Name {
if strings.HasPrefix(j.Name, jail) {
if len(ds) > 0 {
if strings.EqualFold(ds, j.Datastore) {
return &jarray[i], nil
@ -620,7 +787,7 @@ func getJailFromArray(name string, jarray []Jail) (*Jail, error) {
}
if len(jails) > 0 {
if len(jails) > 1 {
return &Jail{}, errors.New("More than one jail found with this name, please use datastore/jail format")
return &Jail{}, errors.New("More than one jail matching, please use datastore/jail format or full name")
} else {
return &jails[0], nil
}
@ -651,7 +818,7 @@ func setJailConfigUpdated(jail *Jail) error {
return err
}
j.ConfigUpdated = true
return nil
}

View File

@ -14,16 +14,17 @@ package jail
*/
import "C"
import (
"strconv"
"strconv"
// "syscall"
"unsafe"
)
type Jail struct {
Name string
Jid int
Path string
Name string
Jid int
Path string
Devfs_ruleset int
}
@ -33,8 +34,8 @@ func GetJails() ([]Jail, error) {
var jl Jail
var err error
// Make "params" a list of 4 jails parameters
params := make([]C.struct_jailparam, 4)
// Make "params" a list of 5 jails parameters
params := make([]C.struct_jailparam, 5)
// initialize parameter names
csname := C.CString("name")
@ -43,27 +44,31 @@ func GetJails() ([]Jail, error) {
defer C.free(unsafe.Pointer(csjid))
cspath := C.CString("path")
defer C.free(unsafe.Pointer(cspath))
csdevfsrs := C.CString("devfs_ruleset")
defer C.free(unsafe.Pointer(csdevfsrs))
cslastjid := C.CString("lastjid")
defer C.free(unsafe.Pointer(cslastjid))
// initialize params struct with parameter names
C.jailparam_init(&params[0], csname)
C.jailparam_init(&params[1], csjid)
C.jailparam_init(&params[2], cspath)
C.jailparam_init(&params[3], csdevfsrs)
// The key to retrieve jail. lastjid = 0 returns first jail and its jid as jailparam_get return value
C.jailparam_init(&params[3], cslastjid)
C.jailparam_init(&params[4], cslastjid)
lastjailid := 0
cslastjidval := C.CString(strconv.Itoa(lastjailid))
defer C.free(unsafe.Pointer(cslastjidval))
C.jailparam_import(&params[3], cslastjidval)
C.jailparam_import(&params[4], cslastjidval)
// loop on existing jails
for lastjailid >= 0 {
// get parameter values
lastjailid = int(C.jailparam_get(&params[0], 4, 0))
lastjailid = int(C.jailparam_get(&params[0], 5, 0))
if lastjailid > 0 {
nametmp := C.jailparam_export(&params[0])
jl.Name = C.GoString(nametmp)
@ -75,23 +80,28 @@ func GetJails() ([]Jail, error) {
// Memory mgmt : Non gere par Go
C.free(unsafe.Pointer(jidtmp))
pathtmp := C.jailparam_export(&params[2])
pathtmp := C.jailparam_export(&params[2])
jl.Path = C.GoString(pathtmp)
// Memory mgmt : Non gere par Go
C.free(unsafe.Pointer(pathtmp))
drstmp := C.jailparam_export(&params[3])
jl.Devfs_ruleset, _ = strconv.Atoi(C.GoString(drstmp))
// Memory mgmt : Non gere par Go
C.free(unsafe.Pointer(drstmp))
jls = append(jls, jl)
//log.Debug("Got jid " + strconv.Itoa(jl.jid) + " with name " + jl.name)
// Prepare next loop iteration
cslastjidval := C.CString(strconv.Itoa(lastjailid))
defer C.free(unsafe.Pointer(cslastjidval))
C.jailparam_import(&params[3], cslastjidval)
C.jailparam_import(&params[4], cslastjidval)
}
}
// Free 4 items of params list
C.jailparam_free(&params[0], 4)
// Free 5 items of params list
C.jailparam_free(&params[0], 5)
return jls, err
}

43
service/gocage Executable file
View File

@ -0,0 +1,43 @@
#!/bin/sh
#
# $FreeBSD$
#
# PROVIDE: gocage
# REQUIRE: LOGIN cleanvar
# KEYWORD: shutdown
# Add the following lines to /etc/rc.conf to enable :
#
# gocage_enable="YES"
#
# gocage_conf="/usr/local/etc/gocage.conf.yml"
#
. /etc/rc.subr
name="gocage"
rcvar=gocage_enable
# read configuration and set defaults
load_rc_config "$name"
: ${gocage_enable:="NO"}
: ${gocage_conf="/usr/local/etc/gocage.conf.yml"}
start_cmd=${name}_start
stop_cmd=${name}_stop
gocage_start()
{
echo "Gocage starting jails... "
/usr/local/bin/gocage -c ${gocage_conf} start
}
gocage_stop()
{
echo "Gocage stopping jails... "
/usr/local/bin/gocage -c ${gocage_conf} stop
}
run_rc_command "$1"