Compare commits
86 Commits
Author | SHA1 | Date | |
---|---|---|---|
2507f10b59 | |||
900e1939f3 | |||
43a958cdec | |||
5b9862d641 | |||
7ca633d946 | |||
199b3b1f7f | |||
372e92fc41 | |||
2afccc5a0e | |||
3d77209b53 | |||
5ce62bc7de | |||
0e0ab8c653 | |||
3b548e4c85 | |||
9d7db268cc | |||
4cc1c476aa | |||
ce79783540 | |||
18d35b9224 | |||
f41c93368d | |||
452b0e4b4e | |||
dbe9622a01 | |||
6ead474a78 | |||
4edd0b7414 | |||
b54ebfd915 | |||
69665fdcef | |||
7e1c213ff4 | |||
26ceb1630a | |||
87e9ae894a | |||
ed5f8f0b1c | |||
c55262690a | |||
c5aa547e5d | |||
cdcb466417 | |||
bb3136c9ef | |||
9208102c84 | |||
fce64b2939 | |||
44b877eae1 | |||
c2277ce10c | |||
549d517cf9 | |||
14984f417c | |||
346fd52a8e | |||
684d97cc21 | |||
a3dd0a7aa2 | |||
56926f7200 | |||
9f32cda6b8 | |||
00cd0421d3 | |||
2c3b4b18f2 | |||
46ad79c325 | |||
534deb371c | |||
fe4192da2d | |||
26b8973c6c | |||
6a8b022165 | |||
45e1c57ce4 | |||
bce37e6541 | |||
24439a8181 | |||
310564b4af | |||
881965e257 | |||
853bf5fb10 | |||
09b807c78e | |||
2ddf51f887 | |||
9e057ed1c5 | |||
925c3dd96b | |||
e11fc96e05 | |||
956e25c849 | |||
f9f1d48023 | |||
1b679bcd17 | |||
dc4213a8d5 | |||
5eed121f0b | |||
812c77790a | |||
7575da794e | |||
6f9bb504be | |||
7c3e14f0f1 | |||
a4ff9c1d51 | |||
00fd283987 | |||
7cf4594f34 | |||
37fea55e42 | |||
c15ee68d2e | |||
54fd1f8064 | |||
89db166040 | |||
9c18a83ee8 | |||
561ae4386a | |||
667c73216e | |||
9e506145a8 | |||
d636d963ff | |||
56b4d8ea84 | |||
abaa4a11f9 | |||
74602dc0df | |||
be756edea7 | |||
546382ded7 |
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,3 +1,2 @@
|
||||
gocage
|
||||
go.sum
|
||||
|
||||
|
4
CHANGELOG
Normal file
4
CHANGELOG
Normal file
@ -0,0 +1,4 @@
|
||||
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
|
||||
v.0.35 : One can now "gocage destroy"
|
54
README.md
54
README.md
@ -6,6 +6,26 @@ 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. :
|
||||
<pre><code>
|
||||
Config.Jail_zfs = 1
|
||||
Config.Jail_zfs_dataset = myzfspool/poudriere
|
||||
Config.Jail_zfs_mountpoint = none
|
||||
</code></pre>
|
||||
|
||||
Create jails
|
||||
------------
|
||||
For now, we can't pass config at creation time. We have to define config after creation:
|
||||
<pre><code>
|
||||
gocage create jail1 -r 13.2-RELEASE
|
||||
gocage set Config.Ip4_addr="vnet0|192.168.1.91/24" Config.Vnet=1 jail1
|
||||
</code></pre>
|
||||
|
||||
Create basejail (jail based on a release, system will be nullfs read-only mounted from the release directory):
|
||||
<pre><code>
|
||||
gocage create -b -r 14.0-RELEASE basejail1
|
||||
</code></pre>
|
||||
|
||||
|
||||
List jails
|
||||
@ -120,6 +140,25 @@ Stop jails
|
||||
`gocage stop test`
|
||||
|
||||
|
||||
Update jails
|
||||
----------
|
||||
To update jail patch version, use gocage update :
|
||||
`gocage update test`
|
||||
|
||||
|
||||
Upgrade jails
|
||||
----------
|
||||
To upgrade jail to newer release, use gocage upgrade :
|
||||
`gocage upgrade -r 13.2-RELEASE test`
|
||||
|
||||
A pre-upgrade snapshot wil be made so we can rollback if needed.
|
||||
|
||||
|
||||
Delete jails
|
||||
----------
|
||||
`gocage destroy test`
|
||||
|
||||
|
||||
Multi datastore
|
||||
----------
|
||||
A datastore is a ZFS dataset mounted. It should be declared in gocage.conf.yml, specifying its ZFS mountpoint :
|
||||
@ -188,3 +227,18 @@ 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 create from templates
|
||||
gocage init
|
||||
create default pool with defaults.json
|
||||
|
15
TODO.md
15
TODO.md
@ -1,4 +1,19 @@
|
||||
Replicating jails between two servers (use zrepl)
|
||||
Manage remote jails :
|
||||
- Make gocage a service
|
||||
- All commands should become API endpoint
|
||||
- How to handle authentication ?
|
||||
|
||||
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
|
||||
26/08/2023 : Last_started is updated
|
||||
|
||||
BUGS:
|
||||
- unable to set values containing equal sign :
|
||||
# gocage set Config.Exec_poststart="jail -m allow.mount.linprocfs=1 name=ioc-poudriere-noo" poudriere-noo
|
||||
Error parsing args: Config.Exec_poststart=jail -m allow.mount.linprocfs=1 name=ioc-poudriere-noo
|
||||
- Fix fstab when migrating jail
|
||||
|
||||
|
96
cmd/api.go
Normal file
96
cmd/api.go
Normal file
@ -0,0 +1,96 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
//"io"
|
||||
"fmt"
|
||||
"strings"
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gin-contrib/cors"
|
||||
)
|
||||
|
||||
func printJails(jails []string, fields []string) ([]byte, error) {
|
||||
var res []byte
|
||||
|
||||
res = append(res, []byte("[")...)
|
||||
|
||||
// Build a filtered jail list
|
||||
var filteredJails []Jail
|
||||
if (len(jails) >= 1 && len(jails[0]) > 0) {
|
||||
for _, j := range gJails {
|
||||
if isStringInArray(jails, j.Name) {
|
||||
filteredJails = append(filteredJails, j)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
filteredJails = gJails
|
||||
}
|
||||
|
||||
for i, j := range filteredJails {
|
||||
out, err := j.PrintJSON(fields)
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
res = append(res, out...)
|
||||
if i < len(filteredJails) - 1 {
|
||||
res = append(res, []byte(",")...)
|
||||
}
|
||||
}
|
||||
res = append(res, []byte("]")...)
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func startApi(address string, port int) {
|
||||
// Initialize gJails.
|
||||
// FIXME: How to update this at running time without doubling the same jails?
|
||||
// core will do this for us in add and delete functions
|
||||
ListJails([]string{""}, false)
|
||||
|
||||
r := gin.Default()
|
||||
|
||||
// Cross Origin Request Support
|
||||
config := cors.DefaultConfig()
|
||||
// DANGER !
|
||||
config.AllowOrigins = []string{"*"}
|
||||
r.Use(cors.New(config))
|
||||
|
||||
r.GET("/jail/list", func(c *gin.Context) {
|
||||
jailstr := c.Query("jail")
|
||||
fields := c.Query("fields")
|
||||
if len(fields) == 0 {
|
||||
// TODO : Put this default value in (user?) configuration
|
||||
fields = "Name,Running,Config.Release,Config.Ip4_addr"
|
||||
}
|
||||
jsout, err := printJails(strings.Split(jailstr, ","), strings.Split(fields, ","))
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"status": 1, "error": err.Error()})
|
||||
} else {
|
||||
c.Data(http.StatusOK, "application/json", jsout)
|
||||
}
|
||||
})
|
||||
|
||||
r.POST("/jail/start/:jail", func(c *gin.Context) {
|
||||
jailstr := c.Params.ByName("jail")
|
||||
// TODO : Capture or redirect output so we can know if jail was started or not
|
||||
StartJail(strings.Split(jailstr, ","))
|
||||
c.JSON(http.StatusOK, gin.H{"status": 0, "error": ""})
|
||||
})
|
||||
r.POST("/jail/stop/:jail", func(c *gin.Context) {
|
||||
jailstr := c.Params.ByName("jail")
|
||||
// TODO : Capture or redirect output so we can know if jail was started or not
|
||||
StopJail(strings.Split(jailstr, ","))
|
||||
c.JSON(http.StatusOK, gin.H{"status": 0, "error": ""})
|
||||
})
|
||||
|
||||
r.GET("/jail/properties", func(c *gin.Context) {
|
||||
props, err := ListJailsProps("json")
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"status": 1, "error": err.Error()})
|
||||
} else {
|
||||
c.Data(http.StatusOK, "application/json", []byte(props))
|
||||
}
|
||||
})
|
||||
|
||||
r.Run(fmt.Sprintf("%s:%d", address, port))
|
||||
}
|
@ -12,7 +12,7 @@ import (
|
||||
func ShellJail(args []string) error {
|
||||
// We cant shell more than one jail bc we replace gocage execution with jexec, so there wont be no return to gocage
|
||||
if len(args) > 0 {
|
||||
cj, err := getJailFromArray(args[0], gJails)
|
||||
cj, err := getJailFromArray(args[0], []string{"basejail", "jail"}, gJails)
|
||||
if err != nil {
|
||||
fmt.Printf("Error getting jail %s: %v\n", args[0], err)
|
||||
return err
|
||||
@ -33,7 +33,6 @@ func shellJail(jail *Jail) error {
|
||||
|
||||
jid := strconv.Itoa(jail.JID)
|
||||
|
||||
//err := syscall.Exec("/usr/sbin/jexec", []string{"jexec", jid, "/bin/csh"}, os.Environ())
|
||||
err := syscall.Exec("/usr/sbin/jexec", []string{"jexec", jid, "login", "-f", "root"}, os.Environ())
|
||||
|
||||
// We should never get here, as syscall.Exec replace the gocage binary execution with jexec
|
||||
|
289
cmd/create.go
Normal file
289
cmd/create.go
Normal file
@ -0,0 +1,289 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"os"
|
||||
"fmt"
|
||||
//"log"
|
||||
"time"
|
||||
"errors"
|
||||
"strings"
|
||||
cp "github.com/otiai10/copy"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// TODO : Add a flag to specify which parts of freebsd base we want : Slim jail only need base.txz, neither lib32 nor src.txz
|
||||
func CreateJail(args []string) {
|
||||
var err error
|
||||
var jtype []string
|
||||
|
||||
if gCreateArgs.BaseJail && gCreateArgs.Release == "" {
|
||||
fmt.Println("Release should be set when creating basejail")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if len(gCreateArgs.JailType) > 0 {
|
||||
jtype = []string{gCreateArgs.JailType}
|
||||
}
|
||||
|
||||
for _, jname := range args {
|
||||
// Check if jail exist and is distinctly named
|
||||
_, err = getJailFromArray(jname, jtype, gJails)
|
||||
if err != nil {
|
||||
if strings.EqualFold(err.Error(), "Jail not found") {
|
||||
|
||||
} else {
|
||||
fmt.Printf("ERROR: %s\n", err.Error())
|
||||
return
|
||||
}
|
||||
} else {
|
||||
fmt.Printf("Jail exist: %s\n", jname)
|
||||
continue
|
||||
}
|
||||
|
||||
fmt.Printf(" > create jail %s\n", jname)
|
||||
|
||||
var ds *Datastore
|
||||
if len(gCreateArgs.Datastore) > 0 {
|
||||
fmt.Printf("DEBUG: Use %s datastore\n", gCreateArgs.Datastore)
|
||||
ds, err = getDatastoreFromArray(gCreateArgs.Datastore, gDatastores)
|
||||
if err != nil {
|
||||
fmt.Printf("ERROR Getting datastore: %s\n", gCreateArgs.Datastore, err.Error())
|
||||
return
|
||||
}
|
||||
} else {
|
||||
ds = &gDatastores[0]
|
||||
}
|
||||
|
||||
// Get base template if specified
|
||||
if gCreateArgs.BaseJail {
|
||||
/**************************************************************************
|
||||
* Create based jail from a template
|
||||
*/
|
||||
log.Debugf("Jail will be created read-only from release %s\n", gCreateArgs.Release)
|
||||
|
||||
// First check if we got release on the same datastore
|
||||
releasePath := fmt.Sprintf("%s/releases/%s/root", ds.Mountpoint, gCreateArgs.Release)
|
||||
_, err := os.Stat(releasePath)
|
||||
if os.IsNotExist(err) {
|
||||
fmt.Printf("ERROR: Release locally not available. Run \"gocage fetch\"\n")
|
||||
return
|
||||
}
|
||||
|
||||
// Create jail datasets
|
||||
dstDset := fmt.Sprintf("%s/jails/%s", ds.ZFSDataset, jname)
|
||||
fmt.Printf(" > Initialize dataset %s\n", dstDset)
|
||||
err = zfsCreateDataset(dstDset, "", "")
|
||||
if err != nil {
|
||||
fmt.Printf("ERROR creating dataset %s: %s\n", dstDset, err.Error())
|
||||
return
|
||||
}
|
||||
// Create jail root datasets
|
||||
dstRootDset := fmt.Sprintf("%s/jails/%s/root", ds.ZFSDataset, jname)
|
||||
fmt.Printf(" > Initialize dataset %s\n", dstRootDset)
|
||||
err = zfsCreateDataset(dstRootDset, "", "")
|
||||
if err != nil {
|
||||
fmt.Printf("ERROR creating dataset %s: %s\n", dstRootDset, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// Create needed directories with basejail permissions
|
||||
fmt.Printf(" > Create base read-only directories\n")
|
||||
dstRootDir := fmt.Sprintf("%s/jails/%s/root", ds.Mountpoint, jname)
|
||||
for _, d := range append(gBaseDirs, gEmptyDirs...) {
|
||||
dstPath := dstRootDir
|
||||
srcPath := releasePath
|
||||
for _, cd := range strings.Split(d, "/") {
|
||||
srcPath = fmt.Sprintf("%s/%s", srcPath, cd)
|
||||
dstPath = fmt.Sprintf("%s/%s", dstPath, cd)
|
||||
_, err := os.Stat(dstPath)
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
srcPerm, err := getPermissions(srcPath)
|
||||
if err != nil {
|
||||
fmt.Printf("ERROR getting permissions of %s: %s\n", srcPath, err.Error())
|
||||
return
|
||||
}
|
||||
err = os.Mkdir(dstPath, srcPerm.Mode().Perm())
|
||||
if err != nil {
|
||||
fmt.Printf("ERROR creating directory %s: %s\n", dstPath, err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Copy these from basejail
|
||||
fmt.Printf(" > Create base writable directories\n")
|
||||
for _, d := range gCopyDirs {
|
||||
err := cp.Copy(fmt.Sprintf("%s/%s", releasePath, d), fmt.Sprintf("%s/%s", dstRootDir, d))
|
||||
if err != nil {
|
||||
fmt.Printf("ERROR copying %s to %s: %s\n", fmt.Sprintf("%s/%s", releasePath, d),
|
||||
fmt.Sprintf("%s/%s", dstRootDir, d), err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
// Copy defaults.json...
|
||||
jailConfPath := fmt.Sprintf("%s/jails/%s/config.json", ds.Mountpoint, jname)
|
||||
err = copyFile(fmt.Sprintf("%s/defaults.json", ds.Mountpoint),
|
||||
jailConfPath)
|
||||
if err != nil {
|
||||
fmt.Printf("ERROR creating config.json: %s\n", err.Error())
|
||||
return
|
||||
}
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
// ... and update it
|
||||
jailConf, err := getJailConfig(jailConfPath)
|
||||
if err != nil {
|
||||
log.Println("ERROR reading jail config from %s", jailConfPath)
|
||||
}
|
||||
|
||||
// Build jail object from config
|
||||
jailRootPath := fmt.Sprintf("%s/jails/%s/%s", ds.Mountpoint, jname, "root")
|
||||
j := Jail{
|
||||
Name: jailConf.Host_hostuuid,
|
||||
Config: jailConf,
|
||||
ConfigPath: jailConfPath,
|
||||
Datastore: ds.Name,
|
||||
RootPath: jailRootPath,
|
||||
Running: false,
|
||||
}
|
||||
|
||||
// We need to store the basejail template. We could :
|
||||
// 1. Use "origin" ?
|
||||
// 2. Add a json item to config ("basejail_template" p.e.), but iocage would delete it once jail is started from iocage
|
||||
// 3. Add a gocage specific config ("config.gocage.json" p.e.)
|
||||
j.Config.Jailtype = "basejail"
|
||||
j.Config.Origin = gCreateArgs.Release
|
||||
j.Config.Host_hostname = jname
|
||||
j.Config.Host_hostuuid = jname
|
||||
|
||||
j.WriteConfigToDisk(false)
|
||||
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
// Create fstab
|
||||
fstabHandle, err := os.Create(fmt.Sprintf("%s/jails/%s/fstab", ds.Mountpoint, jname))
|
||||
if err != nil {
|
||||
fmt.Printf("ERROR creating fstab: %s", err.Error())
|
||||
return
|
||||
}
|
||||
defer fstabHandle.Close()
|
||||
|
||||
for _, d := range gBaseDirs {
|
||||
fmt.Fprintf(fstabHandle, "%s\t%s\tnullfs\tro\t0\t0\n", fmt.Sprintf("%s/%s", releasePath, d), fmt.Sprintf("%s/%s", dstRootDir, d))
|
||||
}
|
||||
|
||||
fmt.Printf(" > Jail created!\n")
|
||||
} else {
|
||||
/**************************************************************************
|
||||
* Create normal jail with its own freebsd base
|
||||
*/
|
||||
log.Debugf("Creating jail with its own freebsd base\n")
|
||||
|
||||
// First check if we got release on the same datastore
|
||||
_, err := os.Stat(fmt.Sprintf("%s/releases/%s/root", ds.Mountpoint, gCreateArgs.Release))
|
||||
if os.IsNotExist(err) {
|
||||
fmt.Printf("ERROR: Release locally not available. Run \"gocage fetch\"\n")
|
||||
return
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
// Create and populate jail filesystem from release
|
||||
dstDset := fmt.Sprintf("%s/jails/%s", ds.ZFSDataset, jname)
|
||||
fmt.Printf(" > Initialize dataset %s\n", dstDset)
|
||||
sNow := time.Now().Format("20060102150405")
|
||||
reldset := fmt.Sprintf("%s/releases/%s", ds.ZFSDataset, gCreateArgs.Release)
|
||||
err = zfsSnapshot(reldset, sNow)
|
||||
if err != nil {
|
||||
fmt.Printf("ERROR Creating snapshot of %s: %s\n", reldset, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
err = zfsCopy(fmt.Sprintf("%s@%s", reldset, sNow), dstDset)
|
||||
if err != nil {
|
||||
fmt.Printf("ERROR sending snapshot to %s: %s\n", dstDset, err.Error())
|
||||
return
|
||||
}
|
||||
// Remove snapshot of release, then snapshot of destination dataset
|
||||
err = zfsDestroy(fmt.Sprintf("%s@%s", reldset, sNow))
|
||||
if err != nil {
|
||||
fmt.Printf("ERROR destroying snapshot %s: %s\n", reldset, err.Error())
|
||||
return
|
||||
}
|
||||
err = zfsDestroy(fmt.Sprintf("%s@%s", dstDset, sNow))
|
||||
if err != nil {
|
||||
fmt.Printf("ERROR destroying snapshot %s: %s\n", dstDset, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
dstRootDset := fmt.Sprintf("%s/jails/%s/root", ds.ZFSDataset, jname)
|
||||
fmt.Printf(" > Initialize dataset %s\n", dstRootDset)
|
||||
relrootdset := fmt.Sprintf("%s/releases/%s/root", ds.ZFSDataset, gCreateArgs.Release)
|
||||
err = zfsSnapshot(relrootdset, sNow)
|
||||
if err != nil {
|
||||
fmt.Printf("ERROR Creating snapshot of %s: %s\n", relrootdset, err.Error())
|
||||
return
|
||||
}
|
||||
err = zfsCopy(fmt.Sprintf("%s@%s", relrootdset, sNow), dstRootDset)
|
||||
if err != nil {
|
||||
fmt.Printf("ERROR sending snapshot to %s: %s\n", dstRootDset, err.Error())
|
||||
return
|
||||
}
|
||||
// Remove snapshot of release, then snapshot of destination dataset
|
||||
err = zfsDestroy(fmt.Sprintf("%s@%s", relrootdset, sNow))
|
||||
if err != nil {
|
||||
fmt.Printf("ERROR destroying snapshot %s: %s\n", relrootdset, err.Error())
|
||||
return
|
||||
}
|
||||
err = zfsDestroy(fmt.Sprintf("%s@%s", dstRootDset, sNow))
|
||||
if err != nil {
|
||||
fmt.Printf("ERROR destroying snapshot %s: %s\n", dstRootDset, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
// Copy defaults.json...
|
||||
jailConfPath := fmt.Sprintf("%s/jails/%s/config.json", ds.Mountpoint, jname)
|
||||
err = copyFile(fmt.Sprintf("%s/defaults.json", ds.Mountpoint),
|
||||
jailConfPath)
|
||||
if err != nil {
|
||||
fmt.Printf("ERROR creating config.json: %s\n", err.Error())
|
||||
return
|
||||
}
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
// ... and update it
|
||||
jailConf, err := getJailConfig(jailConfPath)
|
||||
if err != nil {
|
||||
log.Println("ERROR reading jail config from %s", jailConfPath)
|
||||
}
|
||||
|
||||
// Build jail object from config
|
||||
jailRootPath := fmt.Sprintf("%s/jails/%s/%s", ds.Mountpoint, jname, "root")
|
||||
j := Jail{
|
||||
Name: jailConf.Host_hostuuid,
|
||||
Config: jailConf,
|
||||
ConfigPath: jailConfPath,
|
||||
Datastore: ds.Name,
|
||||
RootPath: jailRootPath,
|
||||
Running: false,
|
||||
}
|
||||
|
||||
j.Config.Release = gCreateArgs.Release
|
||||
j.Config.Host_hostname = jname
|
||||
j.Config.Host_hostuuid = jname
|
||||
j.Config.Jailtype = "jail"
|
||||
|
||||
j.WriteConfigToDisk(false)
|
||||
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
// Create fstab
|
||||
fstabHandle, err := os.Create(fmt.Sprintf("%s/jails/%s/fstab", ds.Mountpoint, jname))
|
||||
if err != nil {
|
||||
fmt.Printf("ERROR creating fstab: %s", err.Error())
|
||||
return
|
||||
}
|
||||
defer fstabHandle.Close()
|
||||
fmt.Printf(" > Jail created!\n")
|
||||
}
|
||||
}
|
||||
}
|
59
cmd/destroy.go
Normal file
59
cmd/destroy.go
Normal file
@ -0,0 +1,59 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
//"log"
|
||||
"time"
|
||||
//"errors"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func DestroyJails(args []string) {
|
||||
for _, a := range args {
|
||||
cj, err := getJailFromArray(a, []string{""}, gJails)
|
||||
if err != nil {
|
||||
fmt.Printf("Error getting jail: %s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
if cj.Running == true {
|
||||
fmt.Printf("Jail %s is running\n", cj.Name)
|
||||
if gForce == false {
|
||||
var answer string
|
||||
fmt.Printf("Stop jail and delete? (y/n) ")
|
||||
fmt.Scanln(&answer)
|
||||
if false == strings.EqualFold(answer, "y") {
|
||||
return
|
||||
}
|
||||
}
|
||||
fmt.Printf("Stopping jail %s\n", cj.Name)
|
||||
StopJail([]string{fmt.Sprintf("%s/%s", cj.Datastore, cj.Name)})
|
||||
// Give some time to the host OS to free all mounts accessing processes
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
|
||||
// Get root and config datasets, then destroy
|
||||
dsRootName, err := zfsGetDatasetByMountpoint(cj.RootPath)
|
||||
if err != nil {
|
||||
fmt.Printf("Error getting root dataset: %s\n", err)
|
||||
return
|
||||
}
|
||||
//fmt.Printf("DEBUG: Prepare to zfs destroy %s\n", dsRootName)
|
||||
if err = zfsDestroy(dsRootName); err != nil {
|
||||
fmt.Printf("Error deleting root dataset: %s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
dsConfName, err := zfsGetDatasetByMountpoint(cj.ConfigPath)
|
||||
if err != nil {
|
||||
fmt.Printf("Error getting config dataset: %s\n", err)
|
||||
return
|
||||
}
|
||||
//fmt.Printf("DEBUG: Prepare to zfs destroy %s\n", dsConfName)
|
||||
if err = zfsDestroy(dsConfName); err != nil {
|
||||
fmt.Printf("Error deleting config dataset: %s\n", err)
|
||||
return
|
||||
}
|
||||
fmt.Printf("Jail %s is no more!\n", cj.Name)
|
||||
}
|
||||
}
|
378
cmd/fetch.go
Normal file
378
cmd/fetch.go
Normal file
@ -0,0 +1,378 @@
|
||||
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 (
|
||||
// TODO : Make this a config/cmd line setting
|
||||
//FetchFiles = []string{"base.txz", "lib32.txz", "src.txz"}
|
||||
FetchFiles = []string{"base.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 := zfsCreateDataset(downloadDsName, downloadDsMountPoint, "lz4"); err != nil {
|
||||
return fmt.Errorf("Error creating dataset %s: %v\n", downloadDsName, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Create download/XX.X dataset if necessary
|
||||
thisDownloadDsName := fmt.Sprintf("%s/%s", downloadDsName, release)
|
||||
thisDownloadDsMountPoint := fmt.Sprintf("%s/%s", 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 := zfsCreateDataset(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", fetchFrom, release)
|
||||
} else {
|
||||
fetchUrl = fmt.Sprintf("%s://%s/%s/%s/%s", 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 := zfsCreateDataset(releaseDsName, releaseDsMountPoint, "lz4"); err != nil {
|
||||
fmt.Printf("Error creating dataset %s: %v\n", releaseDsName, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Create releases/XX.X dataset if necessary
|
||||
thisReleaseDsName := fmt.Sprintf("%s/%s", releaseDsName, release)
|
||||
thisReleaseDsMountPoint := fmt.Sprintf("%s/%s", 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 := zfsCreateDataset(thisReleaseDsName, thisReleaseDsMountPoint, "lz4"); err != nil {
|
||||
fmt.Printf("Error creating dataset %s: %v\n", thisReleaseDsName, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Create releases/XX.X/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 := zfsCreateDataset(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/root
|
||||
downloadDsMountPoint := fmt.Sprintf("%s/download", ds.Mountpoint)
|
||||
downloadDir := fmt.Sprintf("%s/%s", 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 in FetchFiles
|
||||
for _, fi := range files {
|
||||
if false == fi.IsDir() {
|
||||
if strings.HasSuffix(fi.Name(), ".txz") {
|
||||
if isStringInArray(FetchFiles, fi.Name()) {
|
||||
ar := fmt.Sprintf("%s/%s", downloadDir, fi.Name())
|
||||
fmt.Printf("Extracting file %s to %s... ", ar, thisReleaseRootDsMountPoint)
|
||||
// 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
|
||||
}
|
@ -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
|
||||
}
|
||||
|
36
cmd/list.go
36
cmd/list.go
@ -15,24 +15,38 @@ import (
|
||||
* List all properties a jail have, with their internal name
|
||||
* Only print properties name. To get name & values, use GetJailProperties()
|
||||
*******************************************************************************/
|
||||
func ListJailsProps(args []string) {
|
||||
func ListJailsProps(format string) (string, error) {
|
||||
var conf Jail
|
||||
var result []string
|
||||
var props []string
|
||||
var out string
|
||||
// Mandatory constructor to init default values
|
||||
jailconf, err := NewJailConfig()
|
||||
if err != nil {
|
||||
fmt.Printf("Error allocating JailConfig: %s\n", err.Error())
|
||||
return
|
||||
return out, err
|
||||
}
|
||||
|
||||
conf.Config = jailconf
|
||||
|
||||
result = getStructFieldNames(conf, result, "")
|
||||
props = getStructFieldNames(conf, props, "")
|
||||
|
||||
for _, f := range result {
|
||||
fmt.Printf("%s\n", f)
|
||||
if format == "json" {
|
||||
out = "{\"properties\":["
|
||||
for i, p := range props {
|
||||
out += fmt.Sprintf("\"%s\"", p)
|
||||
if i < len(props) - 1 {
|
||||
out += ","
|
||||
}
|
||||
}
|
||||
out += "]}"
|
||||
} else {
|
||||
for _, p := range props {
|
||||
out += fmt.Sprintf("%s\n", p)
|
||||
}
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
/********************************************************************************
|
||||
* Get Jails from datastores. Store config and running metadata
|
||||
@ -249,7 +263,7 @@ func listJailsFromDirectory(dir string, dsname string) ([]Jail, error) {
|
||||
jailConfPath := fmt.Sprintf("%s/%s/%s", dir, fi.Name(), "config.json")
|
||||
jailConf, err := getJailConfig(jailConfPath)
|
||||
if err != nil {
|
||||
log.Println("ERROR reading jail config for %s", jailConfPath)
|
||||
fmt.Printf("ERROR reading jail config from %s\n", jailConfPath)
|
||||
}
|
||||
|
||||
// 2. Build jail object from config
|
||||
@ -272,8 +286,16 @@ 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
|
||||
// Update release
|
||||
r, err := getVersion(&j)
|
||||
if err != nil {
|
||||
fmt.Printf("ERROR getting jail %s version: %s\n", j.Name, err.Error())
|
||||
} else {
|
||||
j.Config.Release = r
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ func MigrateJail(args []string) {
|
||||
}
|
||||
|
||||
for _, jn := range jailNames {
|
||||
cj, err := getJailFromArray(jn, gJails)
|
||||
cj, err := getJailFromArray(jn, []string{""}, gJails)
|
||||
if cj == nil {
|
||||
fmt.Printf("Error getting jail %s: Not found\n", jn)
|
||||
return
|
||||
@ -177,7 +177,7 @@ func CleanMigrateMess(args []string) error {
|
||||
}
|
||||
|
||||
for _, jn := range jailNames {
|
||||
cj, err := getJailFromArray(jn, gJails)
|
||||
cj, err := getJailFromArray(jn, []string{""}, gJails)
|
||||
if cj == nil {
|
||||
return errors.New(fmt.Sprintf("Error getting jail %s: Not found\n", jn))
|
||||
}
|
||||
|
@ -1,8 +1,9 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"fmt"
|
||||
"errors"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
@ -17,10 +18,10 @@ func GetJailProperties(args []string) {
|
||||
for i, a := range args {
|
||||
// Last arg is the jail name
|
||||
if i == len(args)-1 {
|
||||
jail, err = getJailFromArray(a, gJails)
|
||||
jail, err = getJailFromArray(a, []string{""}, gJails)
|
||||
if err != nil {
|
||||
fmt.Printf("Error: %s\n", err.Error())
|
||||
return
|
||||
os.Exit(1)
|
||||
}
|
||||
} else {
|
||||
props = append(props, a)
|
||||
@ -105,19 +106,20 @@ func SetJailProperties(args []string) {
|
||||
return
|
||||
}
|
||||
|
||||
// Get jail by index to modify it
|
||||
for i, _ := range gJails {
|
||||
if gJails[i].Name == jail.Name {
|
||||
cj, err := getJailFromArray(jail.Name, []string{""}, gJails)
|
||||
if err != nil {
|
||||
fmt.Printf("Error getting jail %s: %v\n", jail.Name, err)
|
||||
return
|
||||
}
|
||||
|
||||
for _, p := range props {
|
||||
err := setStructFieldValue(&gJails[i], p.name, p.value)
|
||||
err := setStructFieldValue(cj, p.name, p.value)
|
||||
if err != nil {
|
||||
fmt.Printf("Error: %s\n", err.Error())
|
||||
return
|
||||
} else {
|
||||
fmt.Printf("%s: %s set to %s\n", gJails[i].Name, p.name, p.value)
|
||||
gJails[i].ConfigUpdated = true
|
||||
}
|
||||
}
|
||||
fmt.Printf("%s: %s set to %s\n", cj.Name, p.name, p.value)
|
||||
}
|
||||
}
|
||||
cj.WriteConfigToDisk(false)
|
||||
}
|
||||
|
214
cmd/root.go
214
cmd/root.go
@ -1,11 +1,11 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"fmt"
|
||||
"sync"
|
||||
"strings"
|
||||
"io/ioutil"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
@ -14,13 +14,23 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
gVersion = "0.31"
|
||||
gVersion = "0.42b"
|
||||
|
||||
// TODO : Get from $jail_zpool/defaults.json
|
||||
MIN_DYN_DEVFS_RULESET = 1000
|
||||
)
|
||||
|
||||
type createArgs struct {
|
||||
Release string
|
||||
BaseJail bool
|
||||
Datastore string
|
||||
JailType string
|
||||
}
|
||||
|
||||
var (
|
||||
gApiAddress string
|
||||
gApiPort int
|
||||
|
||||
gJailHost JailHost
|
||||
gJails []Jail
|
||||
gDatastores []Datastore
|
||||
@ -29,6 +39,8 @@ var (
|
||||
gForce bool
|
||||
gDebug bool
|
||||
|
||||
gCreateArgs createArgs
|
||||
|
||||
gConfigFile string
|
||||
gDisplayJColumns string
|
||||
gDisplaySColumns string
|
||||
@ -47,10 +59,28 @@ var (
|
||||
|
||||
gTimeZone string
|
||||
gSnapshotName string
|
||||
gZPool string
|
||||
gBridge string
|
||||
gInterface string
|
||||
|
||||
gMigrateDestDatastore string
|
||||
gYesToAll bool
|
||||
|
||||
gFetchRelease string
|
||||
gFetchIntoDS string
|
||||
gFetchFrom string
|
||||
gUpgradeRelease string
|
||||
|
||||
// For a based jail, these are directories binded to basejail
|
||||
gBaseDirs = []string{"bin", "boot", "lib", "libexec", "rescue", "sbin", "usr/bin", "usr/include",
|
||||
"usr/lib", "usr/lib32", "usr/libdata", "usr/libexec", "usr/sbin", "usr/share"}
|
||||
// These directories are to be created empty
|
||||
gEmptyDirs = []string{"dev", "media", "mnt", "net", "proc"}
|
||||
// Copy these from base template
|
||||
gCopyDirs = []string{"etc", "root", "tmp", "var"}
|
||||
|
||||
gMdevfs sync.Mutex
|
||||
|
||||
rootCmd = &cobra.Command{
|
||||
Use: "gocage",
|
||||
Short: "GoCage is a FreeBSD Jail management tool",
|
||||
@ -73,6 +103,14 @@ It support iocage jails and can coexist with iocage.`,
|
||||
},
|
||||
}
|
||||
|
||||
initCmd = &cobra.Command{
|
||||
Use: "init",
|
||||
Short: "Initialize GoCage",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
InitGoCage(args)
|
||||
},
|
||||
}
|
||||
|
||||
listCmd = &cobra.Command{
|
||||
Use: "list",
|
||||
Short: "Print jails",
|
||||
@ -89,11 +127,16 @@ ex: gocage list srv-db srv-web`,
|
||||
Short: "Print jails properties",
|
||||
Long: "Display jails properties. You can use properties to filter, get or set them.",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
ListJailsProps(args)
|
||||
out, err := ListJailsProps("text")
|
||||
if err != nil {
|
||||
fmt.Printf("Error listing properties : %v\n", err)
|
||||
} else {
|
||||
fmt.Printf("%s\n", out)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
/* destroyCmd = &cobra.Command{
|
||||
destroyCmd = &cobra.Command{
|
||||
Use: "destroy",
|
||||
Short: "destroy jails",
|
||||
Long: `Destroy jail filesystem, snapshots and configuration file.`,
|
||||
@ -102,7 +145,7 @@ ex: gocage list srv-db srv-web`,
|
||||
DestroyJails(args)
|
||||
},
|
||||
}
|
||||
*/
|
||||
|
||||
stopCmd = &cobra.Command{
|
||||
Use: "stop",
|
||||
Short: "stop jail",
|
||||
@ -129,12 +172,9 @@ ex: gocage list srv-db srv-web`,
|
||||
} else {
|
||||
StartJail(args)
|
||||
}
|
||||
WriteConfigToDisk(false)
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
|
||||
restartCmd = &cobra.Command{
|
||||
Use: "restart",
|
||||
Short: "restart jail",
|
||||
@ -143,7 +183,6 @@ ex: gocage list srv-db srv-web`,
|
||||
ListJails(args, false)
|
||||
StopJail(args)
|
||||
StartJail(args)
|
||||
WriteConfigToDisk(false)
|
||||
},
|
||||
}
|
||||
|
||||
@ -166,7 +205,6 @@ Multiples properties can be specified, separated with space (Ex: gocage set allo
|
||||
// Load inventory
|
||||
ListJails(args, false)
|
||||
SetJailProperties(args)
|
||||
WriteConfigToDisk(true)
|
||||
},
|
||||
}
|
||||
|
||||
@ -248,7 +286,6 @@ You can specify multiple jails.`,
|
||||
// Load inventory
|
||||
ListJails(args, false)
|
||||
MigrateJail(args)
|
||||
WriteConfigToDisk(false)
|
||||
},
|
||||
}
|
||||
|
||||
@ -285,6 +322,54 @@ 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)
|
||||
},
|
||||
}
|
||||
|
||||
upgradeCmd = &cobra.Command{
|
||||
Use: "upgrade",
|
||||
Short: "Upgrade FreeBSD release",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
ListJails(args, false)
|
||||
UpgradeJail(args)
|
||||
},
|
||||
}
|
||||
|
||||
createCmd = &cobra.Command{
|
||||
Use: "create",
|
||||
Short: "Create jail",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
ListJails(args, false)
|
||||
CreateJail(args)
|
||||
},
|
||||
}
|
||||
|
||||
apiCmd = &cobra.Command{
|
||||
Use: "startapi",
|
||||
Short: "Run docage as a daemon, exposing API",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
startApi(gApiAddress, gApiPort)
|
||||
},
|
||||
}
|
||||
|
||||
testCmd = &cobra.Command{
|
||||
Use: "test",
|
||||
Short: "temporary command to test some code snippet",
|
||||
@ -304,9 +389,13 @@ 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
|
||||
initCmd.Flags().StringVarP(&gZPool, "pool", "p", "", "ZFS pool to create datastore on")
|
||||
initCmd.Flags().StringVarP(&gBridge, "bridge", "b", "", "bridge to create for jails networking")
|
||||
initCmd.Flags().StringVarP(&gInterface, "interface", "i", "", "interface to add as bridge member. This should be your main interface")
|
||||
initCmd.MarkFlagsRequiredTogether("bridge", "interface")
|
||||
|
||||
// We reuse these flags in "gocage snapshot list myjail" and 'gocage datastore list" commands
|
||||
listCmd.Flags().StringVarP(&gDisplayJColumns, "outcol", "o", "JID,Name,Config.Release,Config.Ip4_addr,Running", "Show these columns in output")
|
||||
@ -314,7 +403,7 @@ func init() {
|
||||
listCmd.Flags().StringVarP(&gFilterJails, "filter", "f", "none", "Only display jails with these values. Ex: \"gocage list -f Config.Boot=1\" will only list started on boot jails")
|
||||
listCmd.Flags().StringVarP(&gSortJailFields, "sort", "s", "none", "Display jails sorted by field values. Ex: \"gocage list -s +Name,-Config.Priority\" will sort jails by their decreasing name, then increasing start priority. 3 critera max supported.")
|
||||
|
||||
// destroyCmd.Flags().BoolVarP(&gForce, "force", "f", false, "Force stop jail if running")
|
||||
destroyCmd.Flags().BoolVarP(&gForce, "force", "f", false, "Force stop jail if running")
|
||||
|
||||
snapshotListCmd.Flags().StringVarP(&gDisplaySColumns, "outcol", "o", "Jailname,Name,Creation,Referenced,Used", "Show these columns in output")
|
||||
snapshotListCmd.Flags().BoolVarP(&gNoSnapLineSep, "nolinesep", "l", false, "Do not display line separator between snapshots")
|
||||
@ -338,21 +427,42 @@ func init() {
|
||||
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-RELEASE\"")
|
||||
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")
|
||||
|
||||
upgradeCmd.Flags().StringVarP(&gUpgradeRelease, "release", "r", "", "Release to upgrade to (e.g.: \"13.1-RELEASE\"")
|
||||
upgradeCmd.MarkFlagRequired("release")
|
||||
|
||||
createCmd.Flags().StringVarP(&gCreateArgs.Release, "release", "r", "", "Release for the jail (e.g.: \"13.1-RELEASE\"")
|
||||
createCmd.Flags().BoolVarP(&gCreateArgs.BaseJail, "basejail", "b", false, "Basejail. This will create a jail mounted read only from a release, so every up(date|grade) made to this release will immediately propagate to new jail.\n")
|
||||
createCmd.Flags().StringVarP(&gCreateArgs.Datastore, "datastore", "d", "", "Datastore to create the jail on. Defaults to first declared in config.")
|
||||
|
||||
apiCmd.Flags().StringVarP(&gApiAddress, "listen-addr", "l", "127.0.0.1", "API listening address")
|
||||
apiCmd.Flags().IntVarP(&gApiPort, "listen-port", "p", 1234, "API listening port")
|
||||
|
||||
// Now declare commands
|
||||
rootCmd.AddCommand(initCmd)
|
||||
rootCmd.AddCommand(versionCmd)
|
||||
rootCmd.AddCommand(listCmd)
|
||||
listCmd.AddCommand(listPropsCmd)
|
||||
rootCmd.AddCommand(stopCmd)
|
||||
rootCmd.AddCommand(startCmd)
|
||||
rootCmd.AddCommand(restartCmd)
|
||||
// rootCmd.AddCommand(destroyCmd)
|
||||
rootCmd.AddCommand(destroyCmd)
|
||||
rootCmd.AddCommand(shellCmd)
|
||||
rootCmd.AddCommand(getCmd)
|
||||
rootCmd.AddCommand(setCmd)
|
||||
rootCmd.AddCommand(snapshotCmd)
|
||||
rootCmd.AddCommand(migrateCmd)
|
||||
rootCmd.AddCommand(datastoreCmd)
|
||||
|
||||
rootCmd.AddCommand(fetchCmd)
|
||||
rootCmd.AddCommand(updateCmd)
|
||||
rootCmd.AddCommand(upgradeCmd)
|
||||
rootCmd.AddCommand(createCmd)
|
||||
rootCmd.AddCommand(apiCmd)
|
||||
rootCmd.AddCommand(testCmd)
|
||||
|
||||
snapshotCmd.AddCommand(snapshotListCmd)
|
||||
@ -383,17 +493,6 @@ func initConfig() {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Load default configs from datastores
|
||||
err := ListDatastores(viper.GetStringSlice("datastore"), false)
|
||||
if err != nil {
|
||||
fmt.Printf("ERROR: error checking datastores: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// fmt.Println("Using config file:", viper.ConfigFileUsed())
|
||||
// fmt.Printf("datastore in config : %s\n", viper.GetStringSlice("datastore"))
|
||||
// fmt.Printf("datastore.0 in config : %s\n", viper.GetStringSlice("datastore.0"))
|
||||
|
||||
// Command line flags have priority on config file
|
||||
if rootCmd.Flags().Lookup("sudo") != nil && false == rootCmd.Flags().Lookup("sudo").Changed {
|
||||
gUseSudo = viper.GetBool("sudo")
|
||||
@ -427,66 +526,27 @@ func initConfig() {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
|
||||
if gDebug {
|
||||
log.SetLevel(log.DebugLevel)
|
||||
log.Debugf("Debug mode enabled\n")
|
||||
}
|
||||
|
||||
// no need to check prerequesites if we are initializing gocage
|
||||
for _, rc := range rootCmd.Commands() {
|
||||
//fmt.Printf("DEBUG: rootCmd subcommand: %v. Was it called? %s\n", rc.Use, rootCmd.Commands()[i].CalledAs())
|
||||
if len(rc.CalledAs()) > 0 && strings.EqualFold("init", rc.CalledAs()) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
/********************************************************************************
|
||||
* Write jails config which been updated to disk.
|
||||
* 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) {
|
||||
for _, j := range gJails {
|
||||
if j.ConfigUpdated {
|
||||
//log.Debug("%s config has changed, write changes to disk\n", j.Name)
|
||||
|
||||
// we will manipulate properties so get a copy
|
||||
jc := j.Config
|
||||
|
||||
if changeauto == false {
|
||||
// Overwrite "auto" properties
|
||||
ondiskjc, err := getJailConfig(j.ConfigPath)
|
||||
// Load default configs from datastores
|
||||
err := ListDatastores(viper.GetStringSlice("datastore"), false)
|
||||
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 err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if v.String() == "auto" {
|
||||
err = setStructFieldValue(&jc, p, "auto")
|
||||
if err != nil {
|
||||
fmt.Printf("ERROR sanitizing config: %s\n", err.Error())
|
||||
fmt.Printf("ERROR: error checking datastores: %v\n", err)
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func Execute() {
|
||||
if err := rootCmd.Execute(); err != nil {
|
||||
|
198
cmd/start.go
198
cmd/start.go
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -326,24 +330,26 @@ func configureDhcpOrAcceptRtadv(jail *Jail, ipproto int, enable bool) error {
|
||||
|
||||
for _, n := range nics {
|
||||
// vnet0 is epair0b inside jail
|
||||
if strings.Contains(n, "vnet") {
|
||||
n = fmt.Sprintf("%sb", strings.Replace(n, "vnet", "epair", 1))
|
||||
//if strings.Contains(n, "vnet") {
|
||||
if strings.HasPrefix(n, "vnet") {
|
||||
splitd := strings.Split(n, "|")
|
||||
n = fmt.Sprintf("%sb", strings.Replace(splitd[0], "vnet", "epair", 1))
|
||||
}
|
||||
key := fmt.Sprintf("ifconfig_%s", n)
|
||||
value := "SYNCDHCP"
|
||||
|
||||
if ipproto == IPv6 {
|
||||
key = fmt.Sprintf("%s_ipv6", key)
|
||||
value = "inet6 auto_linklocal accept_rtadv autoconf"
|
||||
value = "\"inet6 auto_linklocal accept_rtadv autoconf\""
|
||||
}
|
||||
|
||||
if enable == true {
|
||||
err := enableRcKeyValue(jail.ConfigPath, key, value)
|
||||
err := enableRcKeyValue(fmt.Sprintf("%s/etc/rc.conf", jail.RootPath), key, value)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ERROR setting %s=%s with sysrc for jail %s: %s\n", key, value, jail.Name, err)
|
||||
}
|
||||
} else {
|
||||
err := disableRcKey(jail.ConfigPath, key)
|
||||
err := disableRcKey(fmt.Sprintf("%s/etc/rc.conf", jail.RootPath), key)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ERROR deleting %s with sysrc for jail %s: %v\n", key, jail.Name, err)
|
||||
}
|
||||
@ -357,7 +363,7 @@ func checkRtsold(jail *Jail) error {
|
||||
if strings.Contains(jail.Config.Ip6_addr, "accept_rtadv") == false {
|
||||
return fmt.Errorf("Must set at least one ip6_addr to accept_rtadv!\n")
|
||||
}
|
||||
err := enableRcKeyValue(jail.ConfigPath, "rtsold_enable", "yes")
|
||||
err := enableRcKeyValue(fmt.Sprintf("%s/etc/rc.conf", jail.RootPath), "rtsold_enable", "yes")
|
||||
if err != nil {
|
||||
return fmt.Errorf("ERROR setting rtsold_enable=YES with sysrc for jail %s: %s\n", jail.Name, err)
|
||||
}
|
||||
@ -472,12 +478,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")
|
||||
@ -499,25 +510,29 @@ func buildDevfsRuleSet(jail *Jail) (error, int) {
|
||||
}
|
||||
}
|
||||
|
||||
log.Debug("buildDevfsRuleSet: Build ruleset %d\n", ruleset)
|
||||
log.Debugf("buildDevfsRuleSet: Build ruleset %d\n", ruleset)
|
||||
|
||||
// Get default devfs_ruleset for the datastore
|
||||
// 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
|
||||
}
|
||||
@ -795,7 +810,9 @@ func generateMAC(jail *Jail, nic string) ([]byte, []byte, error) {
|
||||
}
|
||||
|
||||
hsmac := append(prefix, suffix...)
|
||||
jsmac := append(hsmac[:5], hsmac[5]+1)
|
||||
jsmac := make([]byte, 6)
|
||||
copy(jsmac, hsmac)
|
||||
jsmac[5] = jsmac[5] + 1
|
||||
|
||||
// Save MACs to config
|
||||
pname := fmt.Sprintf("Config.%s_mac", strings.Title(nic))
|
||||
@ -825,7 +842,7 @@ func setupVnetInterfaceHostSide(jail *Jail) ([]string, error) {
|
||||
bridge := v[1]
|
||||
|
||||
// Get host side MAC
|
||||
pname := fmt.Sprintf("Config.%s_mac", nic)
|
||||
pname := fmt.Sprintf("Config.%s_mac", strings.Title(nic))
|
||||
var val *reflect.Value
|
||||
val, pname, err = getStructFieldValue(jail, pname)
|
||||
if err != nil {
|
||||
@ -838,13 +855,22 @@ func setupVnetInterfaceHostSide(jail *Jail) ([]string, error) {
|
||||
return []string{}, err
|
||||
}
|
||||
} else {
|
||||
hsmac = val.Bytes()
|
||||
if strings.EqualFold(val.String(), "none") {
|
||||
hsmac, _, err = generateMAC(jail, nic)
|
||||
if err != nil {
|
||||
return []string{}, err
|
||||
}
|
||||
}
|
||||
hsmac, err = hex.DecodeString(strings.Split(val.String(), " ")[0])
|
||||
if err != nil {
|
||||
return []string{}, fmt.Errorf("Error converting %s to hex\n", strings.Split(val.String(), " ")[0])
|
||||
}
|
||||
}
|
||||
|
||||
// Get bridge MTU
|
||||
mtu, err := gJailHost.GetBridgeMTU(bridge)
|
||||
if err != nil {
|
||||
return []string{}, fmt.Errorf("Error getting bridge mtu: %v\n", err)
|
||||
return []string{}, fmt.Errorf("Error getting bridge \"%s\" mtu: %v\n", bridge, err)
|
||||
}
|
||||
|
||||
// Create epair interface
|
||||
@ -880,11 +906,11 @@ func setupVnetInterfaceHostSide(jail *Jail) ([]string, error) {
|
||||
}
|
||||
epairs = append(epairs, hsepair)
|
||||
}
|
||||
|
||||
log.Debugf("setupVnetInterfaceHostSide: returning %v\n", epairs)
|
||||
return epairs, nil
|
||||
}
|
||||
|
||||
func setupVnetInterfaceJailSide(jail *Jail, hsepair string) error {
|
||||
func setupVnetInterfaceJailSide(jail *Jail, hostepairs []string) error {
|
||||
var jsmac []byte
|
||||
var err error
|
||||
|
||||
@ -894,8 +920,10 @@ func setupVnetInterfaceJailSide(jail *Jail, hsepair string) error {
|
||||
|
||||
for _, i := range strings.Split(jail.Config.Ip4_addr, ",") {
|
||||
v := strings.Split(i, "|")
|
||||
if len(v) > 1 {
|
||||
ip4s[v[0]] = v[1]
|
||||
}
|
||||
}
|
||||
for _, i := range strings.Split(jail.Config.Ip6_addr, ",") {
|
||||
v := strings.Split(i, "|")
|
||||
if len(v) > 1 {
|
||||
@ -904,7 +932,7 @@ func setupVnetInterfaceJailSide(jail *Jail, hsepair string) error {
|
||||
}
|
||||
|
||||
// Loop through configured interfaces
|
||||
for _, nicCnf := range strings.Split(jail.Config.Interfaces, ",") {
|
||||
for i, nicCnf := range strings.Split(jail.Config.Interfaces, ",") {
|
||||
v := strings.Split(nicCnf, ":")
|
||||
if len(v) != 2 {
|
||||
return fmt.Errorf("Invalid value for Interfaces: %s\n", nicCnf)
|
||||
@ -915,9 +943,11 @@ func setupVnetInterfaceJailSide(jail *Jail, hsepair string) error {
|
||||
// inside jail final nic name
|
||||
jnic := strings.Replace(v[0], "vnet", "epair", 1)
|
||||
jnic = jnic + "b"
|
||||
// host side associated jail nic name
|
||||
jsepair := fmt.Sprintf("%sb", strings.TrimSuffix(hostepairs[i], "a"))
|
||||
|
||||
// Get jail side MAC
|
||||
pname := fmt.Sprintf("Config.%s_mac", nic)
|
||||
pname := fmt.Sprintf("Config.%s_mac", strings.Title(nic))
|
||||
var val *reflect.Value
|
||||
val, pname, err = getStructFieldValue(jail, pname)
|
||||
if err != nil {
|
||||
@ -930,11 +960,12 @@ func setupVnetInterfaceJailSide(jail *Jail, hsepair string) error {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
jsmac = val.Bytes()
|
||||
jsmac, err = hex.DecodeString(strings.Split(val.String(), " ")[1])
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error converting %s to hex\n", strings.Split(val.String(), " ")[1])
|
||||
}
|
||||
|
||||
lasta := strings.LastIndex(hsepair, "a")
|
||||
jsepair := hsepair[:lasta] + strings.Replace(hsepair[lasta:], "a", "b", 1)
|
||||
}
|
||||
|
||||
cmd := fmt.Sprintf("/sbin/ifconfig %s vnet %s", jsepair, jail.InternalName)
|
||||
_, err := executeCommand(cmd)
|
||||
@ -945,7 +976,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)
|
||||
@ -997,6 +1028,8 @@ func setupVnetInterfaceJailSide(jail *Jail, hsepair string) error {
|
||||
}
|
||||
}
|
||||
|
||||
log.Debugf("setupVnetInterfaceJailSide: return with success\n")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -1010,7 +1043,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,10 +1085,13 @@ 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
|
||||
// Get boot enabled non-template jails
|
||||
for _, j := range gJails {
|
||||
if j.Config.Boot > 0 {
|
||||
if j.Config.Boot > 0 && !strings.EqualFold(j.Config.Jailtype, "template") {
|
||||
startList = append(startList, j)
|
||||
}
|
||||
}
|
||||
@ -1069,11 +1105,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()
|
||||
}
|
||||
|
||||
|
||||
@ -1109,7 +1185,7 @@ func StartJail(args []string) {
|
||||
|
||||
for _, a := range args {
|
||||
// Check if jail exist and is distinctly named
|
||||
cj, err = getJailFromArray(a, gJails)
|
||||
cj, err = getJailFromArray(a, []string{"basejail", "jail"}, gJails)
|
||||
if err != nil {
|
||||
fmt.Printf("Error getting jail: %s\n", err)
|
||||
continue
|
||||
@ -1120,7 +1196,7 @@ func StartJail(args []string) {
|
||||
continue
|
||||
}
|
||||
|
||||
fmt.Printf("> Starting jail %s\n", a)
|
||||
fmt.Printf("> Starting jail %s\n", cj.Name)
|
||||
|
||||
// Set InternalName as it is used by some of these
|
||||
cj.InternalName = fmt.Sprintf("ioc-%s", cj.Name)
|
||||
@ -1296,7 +1372,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 +1385,7 @@ func StartJail(args []string) {
|
||||
}
|
||||
|
||||
// Synchronize jail config to disk
|
||||
WriteConfigToDisk(false)
|
||||
cj.WriteConfigToDisk(false)
|
||||
|
||||
start_cmd := fmt.Sprintf("/usr/sbin/jail -f /var/run/jail.%s.conf -c", cj.InternalName)
|
||||
|
||||
@ -1345,6 +1421,25 @@ func StartJail(args []string) {
|
||||
}
|
||||
}
|
||||
|
||||
// Get a reference to current jail, to update its properties in RAM so API will get fresh data
|
||||
for i, j := range gJails {
|
||||
if strings.EqualFold(j.Name, cj.Name) && strings.EqualFold(j.Datastore, cj.Datastore) {
|
||||
if err = setStructFieldValue(&gJails[i], "Running", "true"); err != nil {
|
||||
fmt.Printf("ERROR: setting Running property to true: %s\n", err.Error())
|
||||
}
|
||||
if err = setStructFieldValue(&gJails[i], "JID", fmt.Sprintf("%d", cj.JID)); err != nil {
|
||||
fmt.Printf("ERROR: setting JID property to %d: %s\n", cj.JID, err.Error())
|
||||
}
|
||||
if err = setStructFieldValue(&gJails[i], "InternalName", cj.InternalName); err != nil {
|
||||
fmt.Printf("ERROR: Setting InternalName property: %s\n", err.Error())
|
||||
}
|
||||
// FIXME: this value of devfs_ruleset should not go in Config, it should reside in Jail root properties as it is volatile (only valid while jail is running)
|
||||
/*if err = setStructFieldValue(&gJails[i], "Devfs_ruleset", "0"); err != nil {
|
||||
fmt.Printf("ERROR: setting Devfs_ruleset property to 0: %s\n", err.Error())
|
||||
}*/
|
||||
}
|
||||
}
|
||||
|
||||
hostInt, err := gJailHost.GetInterfaces()
|
||||
if err != nil {
|
||||
fmt.Printf("Error listing jail host interfaces: %v\n", err)
|
||||
@ -1365,15 +1460,14 @@ func StartJail(args []string) {
|
||||
return
|
||||
}
|
||||
|
||||
for _, ep := range hsepairs {
|
||||
if err = setupVnetInterfaceJailSide(cj, ep); err != nil {
|
||||
if err = setupVnetInterfaceJailSide(cj, hsepairs); err != nil {
|
||||
fmt.Printf("Error setting VNet interface jail side: %v\n", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
fmt.Printf(" > Setup VNet network: OK\n")
|
||||
|
||||
// TODO: Handle DHCP
|
||||
// Set default route, unless main network is dhcp
|
||||
if ! cj.isFirstNetDhcp() && !strings.EqualFold(cj.Config.Ip4_addr, "none") {
|
||||
fmt.Printf(" > Setup default ipv4 gateway:\n")
|
||||
cmd := fmt.Sprintf("/usr/sbin/setfib %s /usr/sbin/jexec %d route add default %s", cj.Config.Exec_fib, cj.JID, cj.Config.Defaultrouter)
|
||||
out, err := executeCommand(cmd)
|
||||
@ -1382,6 +1476,7 @@ func StartJail(args []string) {
|
||||
} else {
|
||||
fmt.Printf(" > Setup default ipv4 gateway: OK\n")
|
||||
}
|
||||
}
|
||||
|
||||
if cj.Config.Ip6_addr != "none" {
|
||||
fmt.Printf(" > Setup default ipv6 gateway:\n")
|
||||
@ -1420,9 +1515,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)
|
||||
if err != nil && len(out) > 0 {
|
||||
fmt.Printf("Error: %v: %s\n", err, out)
|
||||
err := executeCommandNonBlocking(cmd)
|
||||
if err != nil {
|
||||
fmt.Printf("Error: %v\n", err)
|
||||
} else {
|
||||
fmt.Printf(" > Start services: OK\n")
|
||||
}
|
||||
@ -1455,7 +1550,12 @@ func StartJail(args []string) {
|
||||
// TODO: Handle dhcp
|
||||
// TODO: Apply rctl
|
||||
|
||||
|
||||
// Update last_started
|
||||
// 23/07/2023 : This is not working, when writing to disk the old value is used
|
||||
dt := time.Now()
|
||||
curDate := fmt.Sprintf("%s", dt.Format("2006-01-02 15:04:05"))
|
||||
cj.Config.Last_started = curDate
|
||||
writeConfigToDisk(cj, false)
|
||||
|
||||
/*
|
||||
out, err := executeCommand(fmt.Sprintf("rctl jail:%s", cj.InternalName))
|
||||
|
119
cmd/stop.go
119
cmd/stop.go
@ -4,10 +4,11 @@ import (
|
||||
"os"
|
||||
"fmt"
|
||||
//"log"
|
||||
"sync"
|
||||
"errors"
|
||||
"regexp"
|
||||
"slices"
|
||||
"os/exec"
|
||||
//"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -82,17 +83,45 @@ func umountAndUnjailZFS(jail *Jail) error {
|
||||
}
|
||||
|
||||
func destroyVNetInterfaces(jail *Jail) error {
|
||||
// Wherever ipv6/ipv4 is enabled, if interface exist we destroy it
|
||||
var vnetnames []string
|
||||
for _, i := range strings.Split(jail.Config.Ip4_addr, ",") {
|
||||
if len(strings.Split(i, "|")) == 2 {
|
||||
iname := fmt.Sprintf("%s.%d", strings.Split(i, "|")[0], jail.JID)
|
||||
fmt.Printf("%s: ", iname)
|
||||
_, err := executeCommand(fmt.Sprintf("ifconfig %s destroy", iname))
|
||||
//_, err := executeScript(fmt.Sprintf("ifconfig %s destroy >/dev/null 2>&1", iname))
|
||||
if !slices.Contains(vnetnames, iname) {
|
||||
vnetnames = append(vnetnames, iname)
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, i := range strings.Split(jail.Config.Ip6_addr, ",") {
|
||||
if len(strings.Split(i, "|")) == 2 {
|
||||
iname := fmt.Sprintf("%s.%d", strings.Split(i, "|")[0], jail.JID)
|
||||
if !slices.Contains(vnetnames, iname) {
|
||||
vnetnames = append(vnetnames, iname)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, iname := range vnetnames {
|
||||
fmt.Printf(" >%s: ", iname)
|
||||
_, err := executeCommand(fmt.Sprintf("ifconfig %s", iname))
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "does not exist") {
|
||||
fmt.Printf("OK\n")
|
||||
} else {
|
||||
fmt.Printf("ERR: %v\n", err)
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
_, err := executeCommand(fmt.Sprintf("ifconfig %s destroy", iname))
|
||||
if err != nil {
|
||||
fmt.Printf("ERR: %v\n", err)
|
||||
return err
|
||||
} else {
|
||||
fmt.Printf("OK\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@ -122,7 +151,7 @@ func deleteDevfsRuleset(ruleset int) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func umountJailFsFromHost(jail *Jail, mountpoint string) error {
|
||||
func umountFsFromHost(mountpoint string) error {
|
||||
cmd := "mount -p"
|
||||
out, err := executeCommand(cmd)
|
||||
if err != nil {
|
||||
@ -133,11 +162,11 @@ func umountJailFsFromHost(jail *Jail, mountpoint string) error {
|
||||
for _, l := range strings.Split(out, "\n") {
|
||||
f := strings.Split(remSpPtrn.ReplaceAllString(l, " "), " ")
|
||||
if len(f) > 2 {
|
||||
if strings.EqualFold(f[1], fmt.Sprintf("%s%s", jail.RootPath, mountpoint)) {
|
||||
cmd = fmt.Sprintf("umount %s%s", jail.RootPath, mountpoint)
|
||||
if strings.EqualFold(f[1], mountpoint) {
|
||||
cmd = fmt.Sprintf("umount %s", mountpoint)
|
||||
_, err := executeCommand(cmd)
|
||||
if err != nil {
|
||||
return errors.New(fmt.Sprintf("Error umounting %s%s: %s", jail.RootPath, mountpoint, err.Error()))
|
||||
return errors.New(fmt.Sprintf("Error umounting %s: %s", mountpoint, err.Error()))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -147,6 +176,10 @@ func umountJailFsFromHost(jail *Jail, mountpoint string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func umountJailFsFromHost(jail *Jail, mountpoint string) error {
|
||||
return umountFsFromHost(fmt.Sprintf("%s%s", jail.RootPath, mountpoint))
|
||||
}
|
||||
|
||||
// Internal usage only
|
||||
func stopJail(jail *Jail) error {
|
||||
cmd := "jail -q"
|
||||
@ -169,8 +202,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 {
|
||||
@ -188,11 +226,52 @@ func StopAllRunningJails() {
|
||||
}
|
||||
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()
|
||||
}
|
||||
|
||||
/*
|
||||
@ -223,7 +302,7 @@ func StopJail(args []string) {
|
||||
|
||||
for _, a := range args {
|
||||
// Check if jail exist and is distinctly named
|
||||
cj, err = getJailFromArray(a, gJails)
|
||||
cj, err = getJailFromArray(a, []string{"basejail", "jail"}, gJails)
|
||||
if err != nil {
|
||||
fmt.Printf("Error getting jail: %s\n", err)
|
||||
continue
|
||||
@ -234,7 +313,10 @@ func StopJail(args []string) {
|
||||
continue
|
||||
}
|
||||
|
||||
fmt.Printf("> Stopping jail %s\n", a)
|
||||
fmt.Printf("> Stopping jail %s\n", cj.Name)
|
||||
|
||||
// Get and write new release into config.json
|
||||
updateVersion(cj)
|
||||
|
||||
out, err := executeCommand(fmt.Sprintf("rctl jail:%s", cj.InternalName))
|
||||
if err == nil && len(out) > 0 {
|
||||
@ -354,7 +436,8 @@ func StopJail(args []string) {
|
||||
fmt.Printf(" > Umount mountpoints from %s\n", fstab)
|
||||
errs := 0
|
||||
for _, m := range mounts {
|
||||
err = umountJailFsFromHost(cj, m.Mountpoint)
|
||||
log.Debugf("Umounting %s\n", m.Mountpoint)
|
||||
err = umountFsFromHost(m.Mountpoint)
|
||||
if err != nil {
|
||||
fmt.Printf("ERROR: %s\n", err.Error())
|
||||
errs += 1
|
||||
@ -399,5 +482,7 @@ func StopJail(args []string) {
|
||||
}
|
||||
}
|
||||
}
|
||||
writeConfigToDisk(cj, false)
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,10 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"time"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -213,6 +216,7 @@ type FreeBSDVersion struct {
|
||||
type JailHost struct {
|
||||
hostname string
|
||||
hostid string
|
||||
arch string
|
||||
default_gateway4 string
|
||||
default_gateway6 string
|
||||
default_interface string
|
||||
@ -560,3 +564,90 @@ type DatastoreSort struct {
|
||||
AvailableDec datastoreLessFunc
|
||||
}
|
||||
|
||||
type Field struct {
|
||||
Name string
|
||||
MaxLen int
|
||||
Value string
|
||||
}
|
||||
|
||||
|
||||
func (j *Jail) PrintJSON(fields []string) ([]byte, error) {
|
||||
return j.PrintJSONWithUpper("", fields)
|
||||
}
|
||||
|
||||
func (j *Jail) PrintJSONWithUpper(upper string, fields []string) ([]byte, error) {
|
||||
var res []byte
|
||||
|
||||
if len(fields) < 1 {
|
||||
return res, nil
|
||||
}
|
||||
|
||||
if len(upper) > 0 {
|
||||
res = append(res, []byte(fmt.Sprintf("\"%s\": {", upper))...)
|
||||
} else {
|
||||
res = append(res, []byte("{")...)
|
||||
}
|
||||
|
||||
// Reorder fields so we got fields of a sub-structure adjacents
|
||||
sort.Strings(fields)
|
||||
|
||||
var ffname string
|
||||
offset := 0
|
||||
for i, _ := range fields {
|
||||
if i + offset >= len(fields) {
|
||||
break
|
||||
}
|
||||
// Is this struct in struct?
|
||||
if strings.Contains(fields[i+offset], ".") {
|
||||
// Count items in this struct
|
||||
var subfields []string
|
||||
var newUpper string
|
||||
for _, sf := range fields[i+offset:] {
|
||||
if strings.Split(sf, ".")[0] == strings.Split(fields[i+offset], ".")[0] {
|
||||
newUpper = strings.Split(sf, ".")[0]
|
||||
sub := strings.Join(strings.Split(string(sf), ".")[1:], ".")
|
||||
subfields = append(subfields, sub)
|
||||
}
|
||||
}
|
||||
out, err := j.PrintJSONWithUpper(newUpper, subfields)
|
||||
if err != nil {
|
||||
return []byte{}, err
|
||||
}
|
||||
res = append(res, out...)
|
||||
offset += len(subfields)-1
|
||||
if i + offset < len(fields) - 1 {
|
||||
res = append(res, []byte(",")...)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if len(upper) > 0 {
|
||||
ffname = fmt.Sprintf("%s.%s", upper, fields[i+offset])
|
||||
} else {
|
||||
ffname = fields[i+offset]
|
||||
}
|
||||
val, _, err := getStructFieldValue(j, ffname)
|
||||
if err != nil {
|
||||
return []byte{}, fmt.Errorf("Error accessing field \"%s\" : %v\n", fields[i+offset], err)
|
||||
}
|
||||
|
||||
res = append(res, []byte(fmt.Sprintf("\"%s\": ", fields[i+offset]))...)
|
||||
switch val.Interface().(type) {
|
||||
case string:
|
||||
res = append(res, []byte(fmt.Sprintf("\"%s\"", val.Interface().(string)))...)
|
||||
case int:
|
||||
res = append(res, []byte(fmt.Sprintf("%d", val.Interface().(int)))...)
|
||||
case bool:
|
||||
res = append(res, []byte(fmt.Sprintf("%t", val.Interface().(bool)))...)
|
||||
default:
|
||||
fmt.Printf("ERREUR dans Jail.PrintJSONWithUpper() : Type inconnu : %T\n", val.Interface())
|
||||
}
|
||||
if i + offset < len(fields) - 1 {
|
||||
res = append(res, []byte(",")...)
|
||||
}
|
||||
}
|
||||
|
||||
res = append(res, []byte("}")...)
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
93
cmd/update.go
Normal file
93
cmd/update.go
Normal file
@ -0,0 +1,93 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"os"
|
||||
"fmt"
|
||||
//"log"
|
||||
"time"
|
||||
"strings"
|
||||
)
|
||||
|
||||
|
||||
// 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())
|
||||
|
||||
// Folder containing update/upgrade temporary files. Common so we save bandwith when upgrading multiple jails
|
||||
// TODO: Variabilize /iocage/freebsd-update
|
||||
_, err = os.Stat("/iocage/freebsd-update")
|
||||
if os.IsNotExist(err) {
|
||||
if err := os.Mkdir("/iocage/freebsd-update", 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
cmd := fmt.Sprintf("/usr/sbin/freebsd-update --not-running-from-cron -f %s -b %s --currently-running %s fetch",
|
||||
cfgFile.Name(), jail.RootPath, jail.Config.Release)
|
||||
err = executeCommandWithOutputToStdout(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd = fmt.Sprintf("/usr/sbin/freebsd-update --not-running-from-cron -f %s -b %s --currently-running %s install",
|
||||
cfgFile.Name(), jail.RootPath, jail.Config.Release)
|
||||
err = executeCommandWithOutputToStdout(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Get and write new release into config.json
|
||||
updateVersion(jail)
|
||||
|
||||
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, []string{""}, gJails)
|
||||
if err != nil {
|
||||
fmt.Printf("Error getting jail: %s\n", err)
|
||||
continue
|
||||
}
|
||||
|
||||
// We cant update basejail as system is readonly
|
||||
if strings.EqualFold(cj.Config.Jailtype, "basejail") {
|
||||
fmt.Printf("%s is a basejail using %s system files. Please update %s!\n", cj.Name, cj.Config.Origin, cj.Config.Origin)
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
125
cmd/upgrade.go
Normal file
125
cmd/upgrade.go
Normal file
@ -0,0 +1,125 @@
|
||||
package cmd
|
||||
|
||||
|
||||
|
||||
import (
|
||||
"os"
|
||||
"fmt"
|
||||
//"log"
|
||||
"time"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Internal usage only
|
||||
func upgradeJail(jail *Jail, version string) error {
|
||||
// Create default config as temporary file
|
||||
cfgFile, err := os.CreateTemp("", "gocage-jail-upgrade-")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cfgFile.Write([]byte(fbsdUpdateConfig))
|
||||
|
||||
defer cfgFile.Close()
|
||||
defer os.Remove(cfgFile.Name())
|
||||
|
||||
// Folder containing update/uipgrade temporary files. Common so we save bandwith when upgrading multiple jails
|
||||
// TODO: Variabilize /iocage/freebsd-update
|
||||
_, err = os.Stat("/iocage/freebsd-update")
|
||||
if os.IsNotExist(err) {
|
||||
if err := os.Mkdir("/iocage/freebsd-update", 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Get current version. Won't work on stopped jail.
|
||||
fbsdvers, err := executeCommandInJail(jail, "/bin/freebsd-version")
|
||||
if err != nil {
|
||||
fmt.Printf("ERROR executeCommandInJail: %s\n", err.Error())
|
||||
return err
|
||||
}
|
||||
fbsdvers = strings.TrimRight(fbsdvers, "\n")
|
||||
//fbsdvers := jail.Config.Release
|
||||
|
||||
cmd := fmt.Sprintf("/usr/sbin/freebsd-update -f %s -b %s --currently-running %s -r %s upgrade",
|
||||
cfgFile.Name(), jail.RootPath, fbsdvers, version)
|
||||
|
||||
//fmt.Printf("DEBUG: Prepare to execute \"%s\"\n", cmd)
|
||||
|
||||
// Need to give user control, bc there could be merge edit needs
|
||||
err = executeCommandWithStdinStdoutStderr(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd = fmt.Sprintf("/usr/sbin/freebsd-update -f %s -b %s --currently-running %s -r %s install",
|
||||
cfgFile.Name(), jail.RootPath, fbsdvers, version)
|
||||
|
||||
//fmt.Printf("DEBUG: Prepare to execute \"%s\"\n", cmd)
|
||||
|
||||
err = executeCommandWithStdinStdoutStderr(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd = fmt.Sprintf("/usr/sbin/freebsd-update -f %s -b %s --currently-running %s -r %s install",
|
||||
cfgFile.Name(), jail.RootPath, fbsdvers, version)
|
||||
|
||||
//fmt.Printf("DEBUG: Prepare to execute \"%s\"\n", cmd)
|
||||
|
||||
err = executeCommandWithStdinStdoutStderr(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd = fmt.Sprintf("/usr/local/sbin/pkg-static -j %d install -q -f -y pkg", jail.JID)
|
||||
err = executeCommandWithStdinStdoutStderr(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Get and write new release into config.json
|
||||
updateVersion(jail)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func UpgradeJail(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, []string{""}, gJails)
|
||||
if err != nil {
|
||||
fmt.Printf("Error getting jail: %s\n", err)
|
||||
continue
|
||||
}
|
||||
|
||||
if cj.Running == false {
|
||||
fmt.Printf("Error: jail must be running for upgrade.\n")
|
||||
return
|
||||
}
|
||||
|
||||
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_upgrade_%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(" > Upgrade jail %s to %s\n", cj.Name, gUpgradeRelease)
|
||||
err = upgradeJail(cj, gUpgradeRelease)
|
||||
if err != nil {
|
||||
fmt.Printf("ERROR: %s\n", err.Error())
|
||||
} else {
|
||||
fmt.Printf(" > Upgrade jail %s: OK\n", cj.Name)
|
||||
}
|
||||
}
|
||||
}
|
792
cmd/utils.go
792
cmd/utils.go
@ -4,7 +4,7 @@ import (
|
||||
"io"
|
||||
"os"
|
||||
"fmt"
|
||||
"log"
|
||||
//"log"
|
||||
"sort"
|
||||
"bufio"
|
||||
"errors"
|
||||
@ -13,13 +13,233 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"io/ioutil"
|
||||
"encoding/json"
|
||||
"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
|
||||
|
||||
fbsdUpdateConfig = `# $FreeBSD$
|
||||
|
||||
# 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 /iocage/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 yes
|
||||
|
||||
# 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 no
|
||||
|
||||
# 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 no
|
||||
`
|
||||
|
||||
gDefaultsJson = `{
|
||||
"CONFIG_VERSION": "27",
|
||||
"allow_chflags": 0,
|
||||
"allow_mlock": 0,
|
||||
"allow_mount": 0,
|
||||
"allow_mount_devfs": 0,
|
||||
"allow_mount_fusefs": 0,
|
||||
"allow_mount_nullfs": 0,
|
||||
"allow_mount_procfs": 0,
|
||||
"allow_mount_tmpfs": 0,
|
||||
"allow_mount_zfs": 0,
|
||||
"allow_quotas": 0,
|
||||
"allow_raw_sockets": 0,
|
||||
"allow_set_hostname": 1,
|
||||
"allow_socket_af": 0,
|
||||
"allow_sysvipc": 0,
|
||||
"allow_tun": 0,
|
||||
"allow_vmm": 0,
|
||||
"assign_localhost": 0,
|
||||
"available": "readonly",
|
||||
"basejail": 0,
|
||||
"boot": 0,
|
||||
"bpf": 0,
|
||||
"children_max": "0",
|
||||
"comment": "none",
|
||||
"compression": "lz4",
|
||||
"compressratio": "readonly",
|
||||
"coredumpsize": "off",
|
||||
"count": "1",
|
||||
"cpuset": "off",
|
||||
"cputime": "off",
|
||||
"datasize": "off",
|
||||
"dedup": "off",
|
||||
"defaultrouter": "auto",
|
||||
"defaultrouter6": "auto",
|
||||
"depends": "none",
|
||||
"devfs_ruleset": "4",
|
||||
"dhcp": 0,
|
||||
"enforce_statfs": "2",
|
||||
"exec_clean": 1,
|
||||
"exec_created": "/usr/bin/true",
|
||||
"exec_fib": "0",
|
||||
"exec_jail_user": "root",
|
||||
"exec_poststart": "/usr/bin/true",
|
||||
"exec_poststop": "/usr/bin/true",
|
||||
"exec_prestart": "/usr/bin/true",
|
||||
"exec_prestop": "/usr/bin/true",
|
||||
"exec_start": "/bin/sh /etc/rc",
|
||||
"exec_stop": "/bin/sh /etc/rc.shutdown",
|
||||
"exec_system_jail_user": "0",
|
||||
"exec_system_user": "root",
|
||||
"exec_timeout": "60",
|
||||
"host_domainname": "none",
|
||||
"host_time": 1,
|
||||
"hostid": "TO-BE-REPLACED-WITH-HOSTID",
|
||||
"hostid_strict_check": 0,
|
||||
"interfaces": "vnet0:TO-BE-REPLACED-WITH-BRIDGE",
|
||||
"ip4": "new",
|
||||
"ip4_addr": "none",
|
||||
"ip4_saddrsel": 1,
|
||||
"ip6": "new",
|
||||
"ip6_addr": "none",
|
||||
"ip6_saddrsel": 1,
|
||||
"ip_hostname": 0,
|
||||
"jail_zfs": 0,
|
||||
"jail_zfs_mountpoint": "none",
|
||||
"last_started": "none",
|
||||
"localhost_ip": "none",
|
||||
"login_flags": "-f root",
|
||||
"mac_prefix": "2c44fd",
|
||||
"maxproc": "off",
|
||||
"memorylocked": "off",
|
||||
"memoryuse": "off",
|
||||
"min_dyn_devfs_ruleset": "1000",
|
||||
"mount_devfs": 1,
|
||||
"mount_fdescfs": 1,
|
||||
"mount_linprocfs": 0,
|
||||
"mount_procfs": 0,
|
||||
"mountpoint": "readonly",
|
||||
"msgqqueued": "off",
|
||||
"msgqsize": "off",
|
||||
"nat": 0,
|
||||
"nat_backend": "ipfw",
|
||||
"nat_forwards": "none",
|
||||
"nat_interface": "none",
|
||||
"nat_prefix": "172.16",
|
||||
"nmsgq": "off",
|
||||
"notes": "none",
|
||||
"nsem": "off",
|
||||
"nsemop": "off",
|
||||
"nshm": "off",
|
||||
"nthr": "off",
|
||||
"openfiles": "off",
|
||||
"origin": "readonly",
|
||||
"owner": "root",
|
||||
"pcpu": "off",
|
||||
"plugin_name": "none",
|
||||
"plugin_repository": "none",
|
||||
"priority": "99",
|
||||
"pseudoterminals": "off",
|
||||
"quota": "none",
|
||||
"readbps": "off",
|
||||
"readiops": "off",
|
||||
"reservation": "none",
|
||||
"resolver": "/etc/resolv.conf",
|
||||
"rlimits": "off",
|
||||
"rtsold": 0,
|
||||
"securelevel": "2",
|
||||
"shmsize": "off",
|
||||
"stacksize": "off",
|
||||
"stop_timeout": "30",
|
||||
"swapuse": "off",
|
||||
"sync_state": "none",
|
||||
"sync_target": "none",
|
||||
"sync_tgt_zpool": "none",
|
||||
"sysvmsg": "new",
|
||||
"sysvsem": "new",
|
||||
"sysvshm": "new",
|
||||
"template": 0,
|
||||
"type": "jail",
|
||||
"used": "readonly",
|
||||
"vmemoryuse": "off",
|
||||
"vnet": 0,
|
||||
"vnet0_mac": "none",
|
||||
"vnet1_mac": "none",
|
||||
"vnet2_mac": "none",
|
||||
"vnet3_mac": "none",
|
||||
"vnet_default_interface": "auto",
|
||||
"vnet_interfaces": "none",
|
||||
"wallclock": "off",
|
||||
"writebps": "off",
|
||||
"writeiops": "off"
|
||||
}
|
||||
`
|
||||
)
|
||||
|
||||
/*****************************************************************************
|
||||
@ -215,18 +435,234 @@ func executeCommand(cmdline string) (string, error) {
|
||||
word = word + string(c)
|
||||
}
|
||||
|
||||
log.Debugf("executeCommand: %s\n", strings.Join(cmd, " "))
|
||||
|
||||
if len(cmd) > 1 {
|
||||
out, err = exec.Command(cmd[0], cmd[1:]...).CombinedOutput()
|
||||
} else {
|
||||
out, err = exec.Command(cmd[0]).CombinedOutput()
|
||||
}
|
||||
|
||||
return string(out), err
|
||||
return strings.TrimSuffix(string(out), "\n"), 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)
|
||||
}
|
||||
|
||||
log.Debugf("executeCommandWithOutputToStdout: will execute \"%s\"\n", strings.Join(cmd, " "))
|
||||
|
||||
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")
|
||||
}
|
||||
|
||||
/* Execute command plugging stdin and stdout to those of the running command.
|
||||
* Blocking while the command run
|
||||
*/
|
||||
func executeCommandWithStdinStdoutStderr(cmdline string) (error) {
|
||||
var cmd []string
|
||||
|
||||
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 command *exec.Cmd
|
||||
if len(cmd) > 1 {
|
||||
command = exec.Command(cmd[0], cmd[1:]...)
|
||||
} else {
|
||||
command = exec.Command(cmd[0])
|
||||
}
|
||||
|
||||
// Get environment
|
||||
command.Env = os.Environ()
|
||||
|
||||
// Connect command to current stdin/out/err
|
||||
command.Stdin = os.Stdin
|
||||
command.Stdout = os.Stdout
|
||||
command.Stderr = os.Stderr
|
||||
|
||||
if err := command.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err := command.Wait()
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
func executeCommandInJail(jail *Jail, cmdline string) (string, error) {
|
||||
var cmd []string
|
||||
|
||||
// We can't execute on non-running jail
|
||||
if jail.Running == false {
|
||||
return "", errors.New("Can't execute command on stopped jail")
|
||||
}
|
||||
|
||||
if gUseSudo {
|
||||
cmd = append(cmd, "sudo")
|
||||
}
|
||||
@ -274,6 +710,8 @@ func executeCommandInJail(jail *Jail, cmdline string) (string, error) {
|
||||
word = word + string(c)
|
||||
}
|
||||
|
||||
log.Debugf("executeCommandInJail: will execute \"%s\"\n", strings.Join(cmd, " "))
|
||||
|
||||
out, err := exec.Command(cmd[0], cmd[1:]...).CombinedOutput()
|
||||
|
||||
return string(out), err
|
||||
@ -325,8 +763,11 @@ func zfsSnapshot(dataset string, snapname string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Copy snapshot to a new dataset
|
||||
// TODO : Intercept death of sending process, then kill receiving
|
||||
func zfsCopy(src string, dest string) error {
|
||||
// First, declare sending process & pipe
|
||||
log.Debugf("Execute: zfs send %s\n", src)
|
||||
cmd_send := exec.Command("zfs", "send", src)
|
||||
stdout_send, err := cmd_send.StdoutPipe()
|
||||
if err != nil {
|
||||
@ -335,6 +776,7 @@ func zfsCopy(src string, dest string) error {
|
||||
}
|
||||
|
||||
// then declare receiving process & pipe
|
||||
log.Debugf("Execute: zfs receive %s\n", dest)
|
||||
cmd_recv := exec.Command("zfs", "receive", dest)
|
||||
stdin_recv, err := cmd_recv.StdinPipe()
|
||||
if err != nil {
|
||||
@ -348,16 +790,19 @@ func zfsCopy(src string, dest string) error {
|
||||
// then start processes and wait for finish
|
||||
if err := cmd_recv.Start(); err != nil {
|
||||
//fmt.Printf("Error: %v\n", err)
|
||||
log.Debugf("zfs receive %s started: %v", dest, err)
|
||||
return errors.New(fmt.Sprintf("Error starting receive process: %v\n", err))
|
||||
}
|
||||
//fmt.Printf("DEBUG: Start \"zfs send %s\"\n", dsconf)
|
||||
if err := cmd_send.Start(); err != nil {
|
||||
//fmt.Printf("Error: %v\n", err)
|
||||
log.Debugf("zfs send %s started: %v", src, err)
|
||||
return errors.New(fmt.Sprintf("Error starting send process: %v\n", err))
|
||||
}
|
||||
|
||||
//fmt.Printf("DEBUG: Wait for zfs send to finish\n")
|
||||
if err := cmd_send.Wait(); err != nil {
|
||||
log.Debugf("zfs send %s stopped with %v", err)
|
||||
//fmt.Printf("Error: zfs send halted with %v\n", err)
|
||||
return errors.New(fmt.Sprintf("send halted with: %v\n", err))
|
||||
}
|
||||
@ -411,6 +856,110 @@ 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
|
||||
* mountpoint can be "", then it will be inherited
|
||||
* compression can be "", then it wil be inherited
|
||||
*/
|
||||
func zfsCreateDataset(dataset, mountpoint, compression string) error {
|
||||
cmd := "zfs create"
|
||||
if len(mountpoint) > 0 {
|
||||
cmd = fmt.Sprintf("%s -o mountpoint=%s", cmd, mountpoint)
|
||||
}
|
||||
if len(compression) > 0 {
|
||||
cmd = fmt.Sprintf("%s -o compression=%s", cmd, compression)
|
||||
}
|
||||
cmd = fmt.Sprintf("%s %s", cmd, dataset)
|
||||
out, err := executeCommand(cmd)
|
||||
if err != nil {
|
||||
return errors.New(fmt.Sprintf("%v; command returned \"%s\"", err, out))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
// Return dataset name for a given mountpoint
|
||||
func zfsGetDatasetByMountpoint(mountpoint string) (string, error) {
|
||||
// We dont want no recursivity
|
||||
//cmd := fmt.Sprintf("zfs list -p -r -H -o name %s", mountpoint)
|
||||
cmd := fmt.Sprintf("zfs list -p -H -o name %s", mountpoint)
|
||||
out, err := executeCommand(cmd)
|
||||
if err != nil {
|
||||
return "", errors.New(fmt.Sprintf("%v; command returned \"%s\"", err, out))
|
||||
}
|
||||
|
||||
return strings.TrimSuffix(out, "\n"), nil
|
||||
}
|
||||
|
||||
// Delete a ZFS Dataset by name
|
||||
func zfsDestroy(dataset string) error {
|
||||
log.Debugf("execute \"zfs destroy -r %s\"\n", dataset)
|
||||
cmd := fmt.Sprintf("zfs destroy -r %s", dataset)
|
||||
out, err := executeCommand(cmd)
|
||||
if err != nil {
|
||||
return errors.New(fmt.Sprintf("%v; command returned \"%s\"", err, out))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
/*****************************************************************************
|
||||
*
|
||||
* Filesystem operations
|
||||
*
|
||||
*****************************************************************************/
|
||||
/* Copy file */
|
||||
func copyFile(src, dst string) error {
|
||||
srcfinfo, err := os.Stat(src)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Cannot find source file: %s", err.Error())
|
||||
}
|
||||
if !srcfinfo.Mode().IsRegular() {
|
||||
return fmt.Errorf("%s is not a regular file", src)
|
||||
}
|
||||
|
||||
srcHandle, err := os.Open(src)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Cannot open source file: %s", err.Error())
|
||||
}
|
||||
defer srcHandle.Close()
|
||||
dstHandle, err := os.Create(dst)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Cannot create destination file: %s", err.Error())
|
||||
}
|
||||
defer dstHandle.Close()
|
||||
_, err = io.Copy(dstHandle, srcHandle)
|
||||
return err
|
||||
}
|
||||
|
||||
// Get permissions of file or folder
|
||||
func getPermissions(path string) (os.FileInfo, error) {
|
||||
return os.Stat(path)
|
||||
}
|
||||
|
||||
func doFileExist(filePath string) (bool, error) {
|
||||
if _, err := os.Stat(filePath); err != nil {
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
return false, nil
|
||||
} else {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
/*****************************************************************************
|
||||
*
|
||||
* rc.conf management
|
||||
@ -445,6 +994,43 @@ func disableRcKey(rcconfpath string, key string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// returns no error if rc key does not exist
|
||||
func getCurrentRcKeyValue(rcconfpath string, key string) (string, error) {
|
||||
cmd := "/usr/sbin/sysrc -a"
|
||||
kvs, err := executeCommand(cmd)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
for _, kv := range strings.Split(string(kvs), "\n") {
|
||||
fmt.Printf("%s\n", kv)
|
||||
if strings.HasPrefix(kv, fmt.Sprintf("%s:", key)) {
|
||||
return strings.TrimPrefix(strings.Join(strings.Split(kv, ":")[1:], ":"), " "), nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// Add a value to current existing key value
|
||||
func addRcKeyValue(rcconfpath string, key string, value string) error {
|
||||
var nv string
|
||||
cv, err := getCurrentRcKeyValue(rcconfpath, key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(cv) > 0 {
|
||||
log.Debugf("Current value of %s: %s\n", key, cv)
|
||||
nv = fmt.Sprintf("\"%s %s\"", cv, value)
|
||||
} else {
|
||||
nv = fmt.Sprintf("\"%s\"", value)
|
||||
}
|
||||
cmd := fmt.Sprintf("/usr/sbin/sysrc -f %s %s=%s", rcconfpath, key, nv)
|
||||
_, err = executeCommand(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
/*****************************************************************************
|
||||
* Parse an fstab file, returning an array of Mount
|
||||
*****************************************************************************/
|
||||
@ -460,7 +1046,8 @@ func getFstab(path string) ([]Mount, error) {
|
||||
scan := bufio.NewScanner(f)
|
||||
for scan.Scan() {
|
||||
res := strings.Fields(scan.Text())
|
||||
if len(res) != 6 {
|
||||
// iocage create lines like that : "/iocage/releases/13.2-RELEASE/root/bin /iocage/jails/smtp-router-02/root/bin nullfs ro 0 0 # Added by iocage on 2023-10-10 17:20:51"
|
||||
if (len(res) > 6 && !strings.EqualFold(res[6], "#")) || len(res) < 6 {
|
||||
return mounts, fmt.Errorf("Incorrect format for fstab line %s", scan.Text())
|
||||
}
|
||||
freq, err := strconv.Atoi(res[4])
|
||||
@ -516,34 +1103,6 @@ func copyDevfsRuleset(ruleset int, srcrs int) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
/********************************************************************************
|
||||
* Returns value of parameter as read in /var/run/jail.$InternalName.conf
|
||||
* Directoves without value will return "true" if found
|
||||
* Returns an error if parameter not found in file
|
||||
*******************************************************************************/
|
||||
func getValueFromRunningConfig(jname string, param string) (string, error) {
|
||||
content, err := ioutil.ReadFile(fmt.Sprintf("/var/run/jail.%s.conf", jname))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
for _, line := range strings.Split(string(content), "\n") {
|
||||
if strings.Contains(line, fmt.Sprintf("%s = ", param)) {
|
||||
split := strings.Split(line, "=")
|
||||
switch len(split) {
|
||||
// directives without value
|
||||
case 0:
|
||||
return "true", nil
|
||||
case 1:
|
||||
return "", fmt.Errorf("Invalid format: %s", line)
|
||||
case 2:
|
||||
return strings.Trim(split[1], " ;\""), nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("Parameter not found: %s", param)
|
||||
}
|
||||
/********************************************************************************
|
||||
* Add a rule to specified ruleset
|
||||
* Ex.: addDevfsRuleToRuleset("path bpf* unhide", 1002)
|
||||
@ -571,6 +1130,35 @@ func addDevfsRuleToRuleset(rule string, ruleset int) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
/********************************************************************************
|
||||
* Returns value of parameter as read in /var/run/jail.$InternalName.conf
|
||||
* 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) {
|
||||
content, err := ioutil.ReadFile(fmt.Sprintf("/var/run/jail.%s.conf", jname))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
for _, line := range strings.Split(string(content), "\n") {
|
||||
if strings.Contains(line, fmt.Sprintf("%s = ", param)) {
|
||||
split := strings.Split(line, "=")
|
||||
switch len(split) {
|
||||
// directives without value
|
||||
case 0:
|
||||
return "true", nil
|
||||
case 1:
|
||||
return "", fmt.Errorf("Invalid format: %s", line)
|
||||
case 2:
|
||||
return strings.Trim(split[1], " ;\""), nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("Parameter not found: %s", param)
|
||||
}
|
||||
|
||||
|
||||
/******************************************************************************
|
||||
*
|
||||
@ -586,14 +1174,29 @@ func isStringInArray(strarr []string, searched string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (j Jail) isFirstNetDhcp() bool {
|
||||
for _, n := range strings.Split(j.Config.Ip4_addr, ",") {
|
||||
splitd := strings.Split(n, "|")
|
||||
if len(splitd) > 1 && strings.EqualFold(splitd[1], "dhcp") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/********************************************************************************
|
||||
* Get a specific jail reference, to update properties after a range loop
|
||||
* Name can be short or long form ("myjail" vs "mystore/myjail")
|
||||
* An empty jailtype means "all types"
|
||||
*******************************************************************************/
|
||||
func getJailFromArray(name string, jarray []Jail) (*Jail, error) {
|
||||
func getJailFromArray(name string, jailtypes []string, jarray []Jail) (*Jail, error) {
|
||||
var ds, jail string
|
||||
var jails []Jail
|
||||
|
||||
if (len(jailtypes) == 1 && len(jailtypes[0]) == 0) || len(jailtypes) == 0 {
|
||||
jailtypes = []string{"basejail", "jail", "template"}
|
||||
}
|
||||
|
||||
if strings.Contains(name, "/") {
|
||||
split := strings.Split(name, "/")
|
||||
if len(split) != 2 {
|
||||
@ -606,7 +1209,8 @@ func getJailFromArray(name string, jarray []Jail) (*Jail, error) {
|
||||
}
|
||||
|
||||
for i, j := range jarray {
|
||||
if jail == j.Name {
|
||||
if strings.HasPrefix(j.Name, jail) {
|
||||
if isStringInArray(jailtypes, j.Config.Jailtype) {
|
||||
if len(ds) > 0 {
|
||||
if strings.EqualFold(ds, j.Datastore) {
|
||||
return &jarray[i], nil
|
||||
@ -618,9 +1222,10 @@ 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
|
||||
}
|
||||
@ -646,7 +1251,7 @@ func setJailConfigUpdated(jail *Jail) error {
|
||||
return errors.New(fmt.Sprintf("No config path for jail %s", jail.Name))
|
||||
}
|
||||
|
||||
j, err := getJailFromArray(jail.Name, gJails)
|
||||
j, err := getJailFromArray(jail.Name, []string{""}, gJails)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -655,6 +1260,121 @@ func setJailConfigUpdated(jail *Jail) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func getVersion(jail *Jail) (string, error) {
|
||||
cvers, err := executeCommand(fmt.Sprintf("%s/bin/freebsd-version", jail.RootPath))
|
||||
if err != nil {
|
||||
fmt.Printf("ERROR: %s\n", err.Error())
|
||||
return "", err
|
||||
}
|
||||
return strings.TrimRight(cvers, "\n"), nil
|
||||
}
|
||||
|
||||
func updateVersion(jail *Jail) error {
|
||||
cvers, err := executeCommand(fmt.Sprintf("%s/bin/freebsd-version", jail.RootPath))
|
||||
if err != nil {
|
||||
fmt.Printf("ERROR: %s\n", err.Error())
|
||||
return err
|
||||
}
|
||||
cvers = strings.TrimRight(cvers, "\n")
|
||||
jail.Config.Release = cvers
|
||||
jail.WriteConfigToDisk(false)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
/********************************************************************************
|
||||
* 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(j *Jail, changeauto bool) {
|
||||
// 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 err != nil {
|
||||
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("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 (j Jail) WriteConfigToDisk(changeauto bool) {
|
||||
// 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 err != nil {
|
||||
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())
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
/******************************************************************************
|
||||
* Return the quantity of jails with the name passed as parameter
|
||||
*****************************************************************************/
|
||||
@ -670,7 +1390,7 @@ func countOfJailsWithThisName(name string) int {
|
||||
|
||||
|
||||
func isNameDistinctive(name string, jails []Jail) bool {
|
||||
_, err := getJailFromArray(name, jails)
|
||||
_, err := getJailFromArray(name, []string{""}, jails)
|
||||
if err != nil {
|
||||
return false
|
||||
} else {
|
||||
|
63
go.mod
63
go.mod
@ -1,31 +1,60 @@
|
||||
module gocage
|
||||
|
||||
go 1.17
|
||||
go 1.21
|
||||
|
||||
require (
|
||||
github.com/c-robinson/iplib v1.0.3
|
||||
github.com/c2h5oh/datasize v0.0.0-20220606134207-859f65c6625b
|
||||
github.com/gin-contrib/cors v1.7.2
|
||||
github.com/gin-gonic/gin v1.10.0
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
|
||||
github.com/otiai10/copy v1.12.0
|
||||
github.com/sirupsen/logrus v1.8.1
|
||||
github.com/spf13/cobra v1.2.1
|
||||
github.com/spf13/viper v1.9.0
|
||||
golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420
|
||||
github.com/spf13/cobra v1.8.1
|
||||
github.com/spf13/viper v1.19.0
|
||||
golang.org/x/net v0.25.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/fsnotify/fsnotify v1.5.1 // indirect
|
||||
github.com/bytedance/sonic v1.11.6 // indirect
|
||||
github.com/bytedance/sonic/loader v0.1.1 // indirect
|
||||
github.com/cloudwego/base64x v0.1.4 // indirect
|
||||
github.com/cloudwego/iasm v0.2.0 // indirect
|
||||
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
|
||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-playground/validator/v10 v10.20.0 // indirect
|
||||
github.com/goccy/go-json v0.10.2 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
||||
github.com/magiconair/properties v1.8.5 // indirect
|
||||
github.com/mitchellh/mapstructure v1.4.2 // indirect
|
||||
github.com/pelletier/go-toml v1.9.4 // indirect
|
||||
github.com/spf13/afero v1.6.0 // indirect
|
||||
github.com/spf13/cast v1.4.1 // indirect
|
||||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/magiconair/properties v1.8.7 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
|
||||
github.com/sagikazarmark/locafero v0.4.0 // indirect
|
||||
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
|
||||
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||
github.com/spf13/afero v1.11.0 // indirect
|
||||
github.com/spf13/cast v1.6.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/subosito/gotenv v1.2.0 // indirect
|
||||
golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf // indirect
|
||||
golang.org/x/text v0.3.6 // indirect
|
||||
gopkg.in/ini.v1 v1.63.2 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
github.com/subosito/gotenv v1.6.0 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.2.12 // indirect
|
||||
go.uber.org/atomic v1.9.0 // indirect
|
||||
go.uber.org/multierr v1.9.0 // indirect
|
||||
golang.org/x/arch v0.8.0 // indirect
|
||||
golang.org/x/crypto v0.23.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
|
||||
golang.org/x/sys v0.20.0 // indirect
|
||||
golang.org/x/text v0.15.0 // indirect
|
||||
google.golang.org/protobuf v1.34.1 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
Reference in New Issue
Block a user